'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var cr = require('crypto');
var module$1 = require('module');
var process$1 = require('process');
var path$1 = require('path');
var readline = require('readline');
var require$$1$2 = require('http');
var require$$2 = require('https');
var require$$0$2 = require('url');
var require$$3 = require('stream');
var require$$4 = require('assert');
var require$$1 = require('tty');
var require$$1$1 = require('util');
var require$$0$1 = require('os');
var require$$8 = require('zlib');
var fs = require('fs');
var child_process = require('child_process');

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

function _interopNamespace(e) {
    if (e && e.__esModule) return e;
    var n = Object.create(null);
    if (e) {
        Object.keys(e).forEach(function (k) {
            if (k !== 'default') {
                var d = Object.getOwnPropertyDescriptor(e, k);
                Object.defineProperty(n, k, d.get ? d : {
                    enumerable: true,
                    get: function () { return e[k]; }
                });
            }
        });
    }
    n["default"] = e;
    return Object.freeze(n);
}

var cr__default = /*#__PURE__*/_interopDefaultLegacy(cr);
var process__default = /*#__PURE__*/_interopDefaultLegacy(process$1);
var path__default = /*#__PURE__*/_interopDefaultLegacy(path$1);
var readline__default = /*#__PURE__*/_interopDefaultLegacy(readline);
var require$$1__default$2 = /*#__PURE__*/_interopDefaultLegacy(require$$1$2);
var require$$2__default = /*#__PURE__*/_interopDefaultLegacy(require$$2);
var require$$0__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$0$2);
var require$$3__default = /*#__PURE__*/_interopDefaultLegacy(require$$3);
var require$$4__default = /*#__PURE__*/_interopDefaultLegacy(require$$4);
var require$$1__default = /*#__PURE__*/_interopDefaultLegacy(require$$1);
var require$$1__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$1$1);
var require$$0__default = /*#__PURE__*/_interopDefaultLegacy(require$$0$1);
var require$$8__default = /*#__PURE__*/_interopDefaultLegacy(require$$8);
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
var child_process__default = /*#__PURE__*/_interopDefaultLegacy(child_process);

/*! *****************************************************************************
Copyright (c) Microsoft Corporation.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */

function __awaiter(thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    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) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
}

// Ported in 2014 by Dmitry Chestnykh and Devi Mandiri.
// TypeScript port in 2019 by Florian Dold.
// Public domain.
//
// Implementation derived from TweetNaCl version 20140427.
// See for details: http://tweetnacl.cr.yp.to/
const gf = function (init = []) {
    const r = new Float64Array(16);
    if (init)
        for (let i = 0; i < init.length; i++)
            r[i] = init[i];
    return r;
};
//  Pluggable, initialized in high-level API below.
let randombytes = function (x, n) {
    throw new Error("no PRNG");
};
const _9 = new Uint8Array(32);
_9[0] = 9;
// prettier-ignore
const gf0 = gf();
const gf1 = gf([1]);
const _121665 = gf([0xdb41, 1]);
const D = gf([
    0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898,
    0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203,
]);
const D2 = gf([
    0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130,
    0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406,
]);
const X = gf([
    0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c,
    0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169,
]);
const Y = gf([
    0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666,
    0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666,
]);
const I = gf([
    0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7,
    0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83,
]);
function ts64(x, i, h, l) {
    x[i] = (h >> 24) & 0xff;
    x[i + 1] = (h >> 16) & 0xff;
    x[i + 2] = (h >> 8) & 0xff;
    x[i + 3] = h & 0xff;
    x[i + 4] = (l >> 24) & 0xff;
    x[i + 5] = (l >> 16) & 0xff;
    x[i + 6] = (l >> 8) & 0xff;
    x[i + 7] = l & 0xff;
}
function vn(x, xi, y, yi, n) {
    let i, d = 0;
    for (i = 0; i < n; i++)
        d |= x[xi + i] ^ y[yi + i];
    return (1 & ((d - 1) >>> 8)) - 1;
}
function crypto_verify_16(x, xi, y, yi) {
    return vn(x, xi, y, yi, 16);
}
function crypto_verify_32(x, xi, y, yi) {
    return vn(x, xi, y, yi, 32);
}
function core_salsa20(o, p, k, c) {
    var j0 = (c[0] & 0xff) |
        ((c[1] & 0xff) << 8) |
        ((c[2] & 0xff) << 16) |
        ((c[3] & 0xff) << 24), j1 = (k[0] & 0xff) |
        ((k[1] & 0xff) << 8) |
        ((k[2] & 0xff) << 16) |
        ((k[3] & 0xff) << 24), j2 = (k[4] & 0xff) |
        ((k[5] & 0xff) << 8) |
        ((k[6] & 0xff) << 16) |
        ((k[7] & 0xff) << 24), j3 = (k[8] & 0xff) |
        ((k[9] & 0xff) << 8) |
        ((k[10] & 0xff) << 16) |
        ((k[11] & 0xff) << 24), j4 = (k[12] & 0xff) |
        ((k[13] & 0xff) << 8) |
        ((k[14] & 0xff) << 16) |
        ((k[15] & 0xff) << 24), j5 = (c[4] & 0xff) |
        ((c[5] & 0xff) << 8) |
        ((c[6] & 0xff) << 16) |
        ((c[7] & 0xff) << 24), j6 = (p[0] & 0xff) |
        ((p[1] & 0xff) << 8) |
        ((p[2] & 0xff) << 16) |
        ((p[3] & 0xff) << 24), j7 = (p[4] & 0xff) |
        ((p[5] & 0xff) << 8) |
        ((p[6] & 0xff) << 16) |
        ((p[7] & 0xff) << 24), j8 = (p[8] & 0xff) |
        ((p[9] & 0xff) << 8) |
        ((p[10] & 0xff) << 16) |
        ((p[11] & 0xff) << 24), j9 = (p[12] & 0xff) |
        ((p[13] & 0xff) << 8) |
        ((p[14] & 0xff) << 16) |
        ((p[15] & 0xff) << 24), j10 = (c[8] & 0xff) |
        ((c[9] & 0xff) << 8) |
        ((c[10] & 0xff) << 16) |
        ((c[11] & 0xff) << 24), j11 = (k[16] & 0xff) |
        ((k[17] & 0xff) << 8) |
        ((k[18] & 0xff) << 16) |
        ((k[19] & 0xff) << 24), j12 = (k[20] & 0xff) |
        ((k[21] & 0xff) << 8) |
        ((k[22] & 0xff) << 16) |
        ((k[23] & 0xff) << 24), j13 = (k[24] & 0xff) |
        ((k[25] & 0xff) << 8) |
        ((k[26] & 0xff) << 16) |
        ((k[27] & 0xff) << 24), j14 = (k[28] & 0xff) |
        ((k[29] & 0xff) << 8) |
        ((k[30] & 0xff) << 16) |
        ((k[31] & 0xff) << 24), j15 = (c[12] & 0xff) |
        ((c[13] & 0xff) << 8) |
        ((c[14] & 0xff) << 16) |
        ((c[15] & 0xff) << 24);
    var x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7, x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14, x15 = j15, u;
    for (var i = 0; i < 20; i += 2) {
        u = (x0 + x12) | 0;
        x4 ^= (u << 7) | (u >>> (32 - 7));
        u = (x4 + x0) | 0;
        x8 ^= (u << 9) | (u >>> (32 - 9));
        u = (x8 + x4) | 0;
        x12 ^= (u << 13) | (u >>> (32 - 13));
        u = (x12 + x8) | 0;
        x0 ^= (u << 18) | (u >>> (32 - 18));
        u = (x5 + x1) | 0;
        x9 ^= (u << 7) | (u >>> (32 - 7));
        u = (x9 + x5) | 0;
        x13 ^= (u << 9) | (u >>> (32 - 9));
        u = (x13 + x9) | 0;
        x1 ^= (u << 13) | (u >>> (32 - 13));
        u = (x1 + x13) | 0;
        x5 ^= (u << 18) | (u >>> (32 - 18));
        u = (x10 + x6) | 0;
        x14 ^= (u << 7) | (u >>> (32 - 7));
        u = (x14 + x10) | 0;
        x2 ^= (u << 9) | (u >>> (32 - 9));
        u = (x2 + x14) | 0;
        x6 ^= (u << 13) | (u >>> (32 - 13));
        u = (x6 + x2) | 0;
        x10 ^= (u << 18) | (u >>> (32 - 18));
        u = (x15 + x11) | 0;
        x3 ^= (u << 7) | (u >>> (32 - 7));
        u = (x3 + x15) | 0;
        x7 ^= (u << 9) | (u >>> (32 - 9));
        u = (x7 + x3) | 0;
        x11 ^= (u << 13) | (u >>> (32 - 13));
        u = (x11 + x7) | 0;
        x15 ^= (u << 18) | (u >>> (32 - 18));
        u = (x0 + x3) | 0;
        x1 ^= (u << 7) | (u >>> (32 - 7));
        u = (x1 + x0) | 0;
        x2 ^= (u << 9) | (u >>> (32 - 9));
        u = (x2 + x1) | 0;
        x3 ^= (u << 13) | (u >>> (32 - 13));
        u = (x3 + x2) | 0;
        x0 ^= (u << 18) | (u >>> (32 - 18));
        u = (x5 + x4) | 0;
        x6 ^= (u << 7) | (u >>> (32 - 7));
        u = (x6 + x5) | 0;
        x7 ^= (u << 9) | (u >>> (32 - 9));
        u = (x7 + x6) | 0;
        x4 ^= (u << 13) | (u >>> (32 - 13));
        u = (x4 + x7) | 0;
        x5 ^= (u << 18) | (u >>> (32 - 18));
        u = (x10 + x9) | 0;
        x11 ^= (u << 7) | (u >>> (32 - 7));
        u = (x11 + x10) | 0;
        x8 ^= (u << 9) | (u >>> (32 - 9));
        u = (x8 + x11) | 0;
        x9 ^= (u << 13) | (u >>> (32 - 13));
        u = (x9 + x8) | 0;
        x10 ^= (u << 18) | (u >>> (32 - 18));
        u = (x15 + x14) | 0;
        x12 ^= (u << 7) | (u >>> (32 - 7));
        u = (x12 + x15) | 0;
        x13 ^= (u << 9) | (u >>> (32 - 9));
        u = (x13 + x12) | 0;
        x14 ^= (u << 13) | (u >>> (32 - 13));
        u = (x14 + x13) | 0;
        x15 ^= (u << 18) | (u >>> (32 - 18));
    }
    x0 = (x0 + j0) | 0;
    x1 = (x1 + j1) | 0;
    x2 = (x2 + j2) | 0;
    x3 = (x3 + j3) | 0;
    x4 = (x4 + j4) | 0;
    x5 = (x5 + j5) | 0;
    x6 = (x6 + j6) | 0;
    x7 = (x7 + j7) | 0;
    x8 = (x8 + j8) | 0;
    x9 = (x9 + j9) | 0;
    x10 = (x10 + j10) | 0;
    x11 = (x11 + j11) | 0;
    x12 = (x12 + j12) | 0;
    x13 = (x13 + j13) | 0;
    x14 = (x14 + j14) | 0;
    x15 = (x15 + j15) | 0;
    o[0] = (x0 >>> 0) & 0xff;
    o[1] = (x0 >>> 8) & 0xff;
    o[2] = (x0 >>> 16) & 0xff;
    o[3] = (x0 >>> 24) & 0xff;
    o[4] = (x1 >>> 0) & 0xff;
    o[5] = (x1 >>> 8) & 0xff;
    o[6] = (x1 >>> 16) & 0xff;
    o[7] = (x1 >>> 24) & 0xff;
    o[8] = (x2 >>> 0) & 0xff;
    o[9] = (x2 >>> 8) & 0xff;
    o[10] = (x2 >>> 16) & 0xff;
    o[11] = (x2 >>> 24) & 0xff;
    o[12] = (x3 >>> 0) & 0xff;
    o[13] = (x3 >>> 8) & 0xff;
    o[14] = (x3 >>> 16) & 0xff;
    o[15] = (x3 >>> 24) & 0xff;
    o[16] = (x4 >>> 0) & 0xff;
    o[17] = (x4 >>> 8) & 0xff;
    o[18] = (x4 >>> 16) & 0xff;
    o[19] = (x4 >>> 24) & 0xff;
    o[20] = (x5 >>> 0) & 0xff;
    o[21] = (x5 >>> 8) & 0xff;
    o[22] = (x5 >>> 16) & 0xff;
    o[23] = (x5 >>> 24) & 0xff;
    o[24] = (x6 >>> 0) & 0xff;
    o[25] = (x6 >>> 8) & 0xff;
    o[26] = (x6 >>> 16) & 0xff;
    o[27] = (x6 >>> 24) & 0xff;
    o[28] = (x7 >>> 0) & 0xff;
    o[29] = (x7 >>> 8) & 0xff;
    o[30] = (x7 >>> 16) & 0xff;
    o[31] = (x7 >>> 24) & 0xff;
    o[32] = (x8 >>> 0) & 0xff;
    o[33] = (x8 >>> 8) & 0xff;
    o[34] = (x8 >>> 16) & 0xff;
    o[35] = (x8 >>> 24) & 0xff;
    o[36] = (x9 >>> 0) & 0xff;
    o[37] = (x9 >>> 8) & 0xff;
    o[38] = (x9 >>> 16) & 0xff;
    o[39] = (x9 >>> 24) & 0xff;
    o[40] = (x10 >>> 0) & 0xff;
    o[41] = (x10 >>> 8) & 0xff;
    o[42] = (x10 >>> 16) & 0xff;
    o[43] = (x10 >>> 24) & 0xff;
    o[44] = (x11 >>> 0) & 0xff;
    o[45] = (x11 >>> 8) & 0xff;
    o[46] = (x11 >>> 16) & 0xff;
    o[47] = (x11 >>> 24) & 0xff;
    o[48] = (x12 >>> 0) & 0xff;
    o[49] = (x12 >>> 8) & 0xff;
    o[50] = (x12 >>> 16) & 0xff;
    o[51] = (x12 >>> 24) & 0xff;
    o[52] = (x13 >>> 0) & 0xff;
    o[53] = (x13 >>> 8) & 0xff;
    o[54] = (x13 >>> 16) & 0xff;
    o[55] = (x13 >>> 24) & 0xff;
    o[56] = (x14 >>> 0) & 0xff;
    o[57] = (x14 >>> 8) & 0xff;
    o[58] = (x14 >>> 16) & 0xff;
    o[59] = (x14 >>> 24) & 0xff;
    o[60] = (x15 >>> 0) & 0xff;
    o[61] = (x15 >>> 8) & 0xff;
    o[62] = (x15 >>> 16) & 0xff;
    o[63] = (x15 >>> 24) & 0xff;
}
function core_hsalsa20(o, p, k, c) {
    var j0 = (c[0] & 0xff) |
        ((c[1] & 0xff) << 8) |
        ((c[2] & 0xff) << 16) |
        ((c[3] & 0xff) << 24), j1 = (k[0] & 0xff) |
        ((k[1] & 0xff) << 8) |
        ((k[2] & 0xff) << 16) |
        ((k[3] & 0xff) << 24), j2 = (k[4] & 0xff) |
        ((k[5] & 0xff) << 8) |
        ((k[6] & 0xff) << 16) |
        ((k[7] & 0xff) << 24), j3 = (k[8] & 0xff) |
        ((k[9] & 0xff) << 8) |
        ((k[10] & 0xff) << 16) |
        ((k[11] & 0xff) << 24), j4 = (k[12] & 0xff) |
        ((k[13] & 0xff) << 8) |
        ((k[14] & 0xff) << 16) |
        ((k[15] & 0xff) << 24), j5 = (c[4] & 0xff) |
        ((c[5] & 0xff) << 8) |
        ((c[6] & 0xff) << 16) |
        ((c[7] & 0xff) << 24), j6 = (p[0] & 0xff) |
        ((p[1] & 0xff) << 8) |
        ((p[2] & 0xff) << 16) |
        ((p[3] & 0xff) << 24), j7 = (p[4] & 0xff) |
        ((p[5] & 0xff) << 8) |
        ((p[6] & 0xff) << 16) |
        ((p[7] & 0xff) << 24), j8 = (p[8] & 0xff) |
        ((p[9] & 0xff) << 8) |
        ((p[10] & 0xff) << 16) |
        ((p[11] & 0xff) << 24), j9 = (p[12] & 0xff) |
        ((p[13] & 0xff) << 8) |
        ((p[14] & 0xff) << 16) |
        ((p[15] & 0xff) << 24), j10 = (c[8] & 0xff) |
        ((c[9] & 0xff) << 8) |
        ((c[10] & 0xff) << 16) |
        ((c[11] & 0xff) << 24), j11 = (k[16] & 0xff) |
        ((k[17] & 0xff) << 8) |
        ((k[18] & 0xff) << 16) |
        ((k[19] & 0xff) << 24), j12 = (k[20] & 0xff) |
        ((k[21] & 0xff) << 8) |
        ((k[22] & 0xff) << 16) |
        ((k[23] & 0xff) << 24), j13 = (k[24] & 0xff) |
        ((k[25] & 0xff) << 8) |
        ((k[26] & 0xff) << 16) |
        ((k[27] & 0xff) << 24), j14 = (k[28] & 0xff) |
        ((k[29] & 0xff) << 8) |
        ((k[30] & 0xff) << 16) |
        ((k[31] & 0xff) << 24), j15 = (c[12] & 0xff) |
        ((c[13] & 0xff) << 8) |
        ((c[14] & 0xff) << 16) |
        ((c[15] & 0xff) << 24);
    var x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7, x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14, x15 = j15, u;
    for (var i = 0; i < 20; i += 2) {
        u = (x0 + x12) | 0;
        x4 ^= (u << 7) | (u >>> (32 - 7));
        u = (x4 + x0) | 0;
        x8 ^= (u << 9) | (u >>> (32 - 9));
        u = (x8 + x4) | 0;
        x12 ^= (u << 13) | (u >>> (32 - 13));
        u = (x12 + x8) | 0;
        x0 ^= (u << 18) | (u >>> (32 - 18));
        u = (x5 + x1) | 0;
        x9 ^= (u << 7) | (u >>> (32 - 7));
        u = (x9 + x5) | 0;
        x13 ^= (u << 9) | (u >>> (32 - 9));
        u = (x13 + x9) | 0;
        x1 ^= (u << 13) | (u >>> (32 - 13));
        u = (x1 + x13) | 0;
        x5 ^= (u << 18) | (u >>> (32 - 18));
        u = (x10 + x6) | 0;
        x14 ^= (u << 7) | (u >>> (32 - 7));
        u = (x14 + x10) | 0;
        x2 ^= (u << 9) | (u >>> (32 - 9));
        u = (x2 + x14) | 0;
        x6 ^= (u << 13) | (u >>> (32 - 13));
        u = (x6 + x2) | 0;
        x10 ^= (u << 18) | (u >>> (32 - 18));
        u = (x15 + x11) | 0;
        x3 ^= (u << 7) | (u >>> (32 - 7));
        u = (x3 + x15) | 0;
        x7 ^= (u << 9) | (u >>> (32 - 9));
        u = (x7 + x3) | 0;
        x11 ^= (u << 13) | (u >>> (32 - 13));
        u = (x11 + x7) | 0;
        x15 ^= (u << 18) | (u >>> (32 - 18));
        u = (x0 + x3) | 0;
        x1 ^= (u << 7) | (u >>> (32 - 7));
        u = (x1 + x0) | 0;
        x2 ^= (u << 9) | (u >>> (32 - 9));
        u = (x2 + x1) | 0;
        x3 ^= (u << 13) | (u >>> (32 - 13));
        u = (x3 + x2) | 0;
        x0 ^= (u << 18) | (u >>> (32 - 18));
        u = (x5 + x4) | 0;
        x6 ^= (u << 7) | (u >>> (32 - 7));
        u = (x6 + x5) | 0;
        x7 ^= (u << 9) | (u >>> (32 - 9));
        u = (x7 + x6) | 0;
        x4 ^= (u << 13) | (u >>> (32 - 13));
        u = (x4 + x7) | 0;
        x5 ^= (u << 18) | (u >>> (32 - 18));
        u = (x10 + x9) | 0;
        x11 ^= (u << 7) | (u >>> (32 - 7));
        u = (x11 + x10) | 0;
        x8 ^= (u << 9) | (u >>> (32 - 9));
        u = (x8 + x11) | 0;
        x9 ^= (u << 13) | (u >>> (32 - 13));
        u = (x9 + x8) | 0;
        x10 ^= (u << 18) | (u >>> (32 - 18));
        u = (x15 + x14) | 0;
        x12 ^= (u << 7) | (u >>> (32 - 7));
        u = (x12 + x15) | 0;
        x13 ^= (u << 9) | (u >>> (32 - 9));
        u = (x13 + x12) | 0;
        x14 ^= (u << 13) | (u >>> (32 - 13));
        u = (x14 + x13) | 0;
        x15 ^= (u << 18) | (u >>> (32 - 18));
    }
    o[0] = (x0 >>> 0) & 0xff;
    o[1] = (x0 >>> 8) & 0xff;
    o[2] = (x0 >>> 16) & 0xff;
    o[3] = (x0 >>> 24) & 0xff;
    o[4] = (x5 >>> 0) & 0xff;
    o[5] = (x5 >>> 8) & 0xff;
    o[6] = (x5 >>> 16) & 0xff;
    o[7] = (x5 >>> 24) & 0xff;
    o[8] = (x10 >>> 0) & 0xff;
    o[9] = (x10 >>> 8) & 0xff;
    o[10] = (x10 >>> 16) & 0xff;
    o[11] = (x10 >>> 24) & 0xff;
    o[12] = (x15 >>> 0) & 0xff;
    o[13] = (x15 >>> 8) & 0xff;
    o[14] = (x15 >>> 16) & 0xff;
    o[15] = (x15 >>> 24) & 0xff;
    o[16] = (x6 >>> 0) & 0xff;
    o[17] = (x6 >>> 8) & 0xff;
    o[18] = (x6 >>> 16) & 0xff;
    o[19] = (x6 >>> 24) & 0xff;
    o[20] = (x7 >>> 0) & 0xff;
    o[21] = (x7 >>> 8) & 0xff;
    o[22] = (x7 >>> 16) & 0xff;
    o[23] = (x7 >>> 24) & 0xff;
    o[24] = (x8 >>> 0) & 0xff;
    o[25] = (x8 >>> 8) & 0xff;
    o[26] = (x8 >>> 16) & 0xff;
    o[27] = (x8 >>> 24) & 0xff;
    o[28] = (x9 >>> 0) & 0xff;
    o[29] = (x9 >>> 8) & 0xff;
    o[30] = (x9 >>> 16) & 0xff;
    o[31] = (x9 >>> 24) & 0xff;
}
var sigma = new Uint8Array([
    101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107,
]);
// "expand 32-byte k"
function crypto_stream_salsa20_xor(c, cpos, m, mpos, b, n, k) {
    var z = new Uint8Array(16), x = new Uint8Array(64);
    var u, i;
    for (i = 0; i < 16; i++)
        z[i] = 0;
    for (i = 0; i < 8; i++)
        z[i] = n[i];
    while (b >= 64) {
        core_salsa20(x, z, k, sigma);
        for (i = 0; i < 64; i++)
            c[cpos + i] = m[mpos + i] ^ x[i];
        u = 1;
        for (i = 8; i < 16; i++) {
            u = (u + (z[i] & 0xff)) | 0;
            z[i] = u & 0xff;
            u >>>= 8;
        }
        b -= 64;
        cpos += 64;
        mpos += 64;
    }
    if (b > 0) {
        core_salsa20(x, z, k, sigma);
        for (i = 0; i < b; i++)
            c[cpos + i] = m[mpos + i] ^ x[i];
    }
    return 0;
}
function crypto_stream_salsa20(c, cpos, b, n, k) {
    var z = new Uint8Array(16), x = new Uint8Array(64);
    var u, i;
    for (i = 0; i < 16; i++)
        z[i] = 0;
    for (i = 0; i < 8; i++)
        z[i] = n[i];
    while (b >= 64) {
        core_salsa20(x, z, k, sigma);
        for (i = 0; i < 64; i++)
            c[cpos + i] = x[i];
        u = 1;
        for (i = 8; i < 16; i++) {
            u = (u + (z[i] & 0xff)) | 0;
            z[i] = u & 0xff;
            u >>>= 8;
        }
        b -= 64;
        cpos += 64;
    }
    if (b > 0) {
        core_salsa20(x, z, k, sigma);
        for (i = 0; i < b; i++)
            c[cpos + i] = x[i];
    }
    return 0;
}
function crypto_stream(c, cpos, d, n, k) {
    var s = new Uint8Array(32);
    core_hsalsa20(s, n, k, sigma);
    var sn = new Uint8Array(8);
    for (var i = 0; i < 8; i++)
        sn[i] = n[i + 16];
    return crypto_stream_salsa20(c, cpos, d, sn, s);
}
function crypto_stream_xor(c, cpos, m, mpos, d, n, k) {
    var s = new Uint8Array(32);
    core_hsalsa20(s, n, k, sigma);
    var sn = new Uint8Array(8);
    for (var i = 0; i < 8; i++)
        sn[i] = n[i + 16];
    return crypto_stream_salsa20_xor(c, cpos, m, mpos, d, sn, s);
}
/*
 * Port of Andrew Moon's Poly1305-donna-16. Public domain.
 * https://github.com/floodyberry/poly1305-donna
 */
class poly1305 {
    constructor(key) {
        this.buffer = new Uint8Array(16);
        this.r = new Uint16Array(10);
        this.h = new Uint16Array(10);
        this.pad = new Uint16Array(8);
        this.leftover = 0;
        this.fin = 0;
        var t0, t1, t2, t3, t4, t5, t6, t7;
        t0 = (key[0] & 0xff) | ((key[1] & 0xff) << 8);
        this.r[0] = t0 & 0x1fff;
        t1 = (key[2] & 0xff) | ((key[3] & 0xff) << 8);
        this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff;
        t2 = (key[4] & 0xff) | ((key[5] & 0xff) << 8);
        this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03;
        t3 = (key[6] & 0xff) | ((key[7] & 0xff) << 8);
        this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff;
        t4 = (key[8] & 0xff) | ((key[9] & 0xff) << 8);
        this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff;
        this.r[5] = (t4 >>> 1) & 0x1ffe;
        t5 = (key[10] & 0xff) | ((key[11] & 0xff) << 8);
        this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff;
        t6 = (key[12] & 0xff) | ((key[13] & 0xff) << 8);
        this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81;
        t7 = (key[14] & 0xff) | ((key[15] & 0xff) << 8);
        this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff;
        this.r[9] = (t7 >>> 5) & 0x007f;
        this.pad[0] = (key[16] & 0xff) | ((key[17] & 0xff) << 8);
        this.pad[1] = (key[18] & 0xff) | ((key[19] & 0xff) << 8);
        this.pad[2] = (key[20] & 0xff) | ((key[21] & 0xff) << 8);
        this.pad[3] = (key[22] & 0xff) | ((key[23] & 0xff) << 8);
        this.pad[4] = (key[24] & 0xff) | ((key[25] & 0xff) << 8);
        this.pad[5] = (key[26] & 0xff) | ((key[27] & 0xff) << 8);
        this.pad[6] = (key[28] & 0xff) | ((key[29] & 0xff) << 8);
        this.pad[7] = (key[30] & 0xff) | ((key[31] & 0xff) << 8);
    }
    blocks(m, mpos, bytes) {
        var hibit = this.fin ? 0 : 1 << 11;
        var t0, t1, t2, t3, t4, t5, t6, t7, c;
        var d0, d1, d2, d3, d4, d5, d6, d7, d8, d9;
        var h0 = this.h[0], h1 = this.h[1], h2 = this.h[2], h3 = this.h[3], h4 = this.h[4], h5 = this.h[5], h6 = this.h[6], h7 = this.h[7], h8 = this.h[8], h9 = this.h[9];
        var r0 = this.r[0], r1 = this.r[1], r2 = this.r[2], r3 = this.r[3], r4 = this.r[4], r5 = this.r[5], r6 = this.r[6], r7 = this.r[7], r8 = this.r[8], r9 = this.r[9];
        while (bytes >= 16) {
            t0 = (m[mpos + 0] & 0xff) | ((m[mpos + 1] & 0xff) << 8);
            h0 += t0 & 0x1fff;
            t1 = (m[mpos + 2] & 0xff) | ((m[mpos + 3] & 0xff) << 8);
            h1 += ((t0 >>> 13) | (t1 << 3)) & 0x1fff;
            t2 = (m[mpos + 4] & 0xff) | ((m[mpos + 5] & 0xff) << 8);
            h2 += ((t1 >>> 10) | (t2 << 6)) & 0x1fff;
            t3 = (m[mpos + 6] & 0xff) | ((m[mpos + 7] & 0xff) << 8);
            h3 += ((t2 >>> 7) | (t3 << 9)) & 0x1fff;
            t4 = (m[mpos + 8] & 0xff) | ((m[mpos + 9] & 0xff) << 8);
            h4 += ((t3 >>> 4) | (t4 << 12)) & 0x1fff;
            h5 += (t4 >>> 1) & 0x1fff;
            t5 = (m[mpos + 10] & 0xff) | ((m[mpos + 11] & 0xff) << 8);
            h6 += ((t4 >>> 14) | (t5 << 2)) & 0x1fff;
            t6 = (m[mpos + 12] & 0xff) | ((m[mpos + 13] & 0xff) << 8);
            h7 += ((t5 >>> 11) | (t6 << 5)) & 0x1fff;
            t7 = (m[mpos + 14] & 0xff) | ((m[mpos + 15] & 0xff) << 8);
            h8 += ((t6 >>> 8) | (t7 << 8)) & 0x1fff;
            h9 += (t7 >>> 5) | hibit;
            c = 0;
            d0 = c;
            d0 += h0 * r0;
            d0 += h1 * (5 * r9);
            d0 += h2 * (5 * r8);
            d0 += h3 * (5 * r7);
            d0 += h4 * (5 * r6);
            c = d0 >>> 13;
            d0 &= 0x1fff;
            d0 += h5 * (5 * r5);
            d0 += h6 * (5 * r4);
            d0 += h7 * (5 * r3);
            d0 += h8 * (5 * r2);
            d0 += h9 * (5 * r1);
            c += d0 >>> 13;
            d0 &= 0x1fff;
            d1 = c;
            d1 += h0 * r1;
            d1 += h1 * r0;
            d1 += h2 * (5 * r9);
            d1 += h3 * (5 * r8);
            d1 += h4 * (5 * r7);
            c = d1 >>> 13;
            d1 &= 0x1fff;
            d1 += h5 * (5 * r6);
            d1 += h6 * (5 * r5);
            d1 += h7 * (5 * r4);
            d1 += h8 * (5 * r3);
            d1 += h9 * (5 * r2);
            c += d1 >>> 13;
            d1 &= 0x1fff;
            d2 = c;
            d2 += h0 * r2;
            d2 += h1 * r1;
            d2 += h2 * r0;
            d2 += h3 * (5 * r9);
            d2 += h4 * (5 * r8);
            c = d2 >>> 13;
            d2 &= 0x1fff;
            d2 += h5 * (5 * r7);
            d2 += h6 * (5 * r6);
            d2 += h7 * (5 * r5);
            d2 += h8 * (5 * r4);
            d2 += h9 * (5 * r3);
            c += d2 >>> 13;
            d2 &= 0x1fff;
            d3 = c;
            d3 += h0 * r3;
            d3 += h1 * r2;
            d3 += h2 * r1;
            d3 += h3 * r0;
            d3 += h4 * (5 * r9);
            c = d3 >>> 13;
            d3 &= 0x1fff;
            d3 += h5 * (5 * r8);
            d3 += h6 * (5 * r7);
            d3 += h7 * (5 * r6);
            d3 += h8 * (5 * r5);
            d3 += h9 * (5 * r4);
            c += d3 >>> 13;
            d3 &= 0x1fff;
            d4 = c;
            d4 += h0 * r4;
            d4 += h1 * r3;
            d4 += h2 * r2;
            d4 += h3 * r1;
            d4 += h4 * r0;
            c = d4 >>> 13;
            d4 &= 0x1fff;
            d4 += h5 * (5 * r9);
            d4 += h6 * (5 * r8);
            d4 += h7 * (5 * r7);
            d4 += h8 * (5 * r6);
            d4 += h9 * (5 * r5);
            c += d4 >>> 13;
            d4 &= 0x1fff;
            d5 = c;
            d5 += h0 * r5;
            d5 += h1 * r4;
            d5 += h2 * r3;
            d5 += h3 * r2;
            d5 += h4 * r1;
            c = d5 >>> 13;
            d5 &= 0x1fff;
            d5 += h5 * r0;
            d5 += h6 * (5 * r9);
            d5 += h7 * (5 * r8);
            d5 += h8 * (5 * r7);
            d5 += h9 * (5 * r6);
            c += d5 >>> 13;
            d5 &= 0x1fff;
            d6 = c;
            d6 += h0 * r6;
            d6 += h1 * r5;
            d6 += h2 * r4;
            d6 += h3 * r3;
            d6 += h4 * r2;
            c = d6 >>> 13;
            d6 &= 0x1fff;
            d6 += h5 * r1;
            d6 += h6 * r0;
            d6 += h7 * (5 * r9);
            d6 += h8 * (5 * r8);
            d6 += h9 * (5 * r7);
            c += d6 >>> 13;
            d6 &= 0x1fff;
            d7 = c;
            d7 += h0 * r7;
            d7 += h1 * r6;
            d7 += h2 * r5;
            d7 += h3 * r4;
            d7 += h4 * r3;
            c = d7 >>> 13;
            d7 &= 0x1fff;
            d7 += h5 * r2;
            d7 += h6 * r1;
            d7 += h7 * r0;
            d7 += h8 * (5 * r9);
            d7 += h9 * (5 * r8);
            c += d7 >>> 13;
            d7 &= 0x1fff;
            d8 = c;
            d8 += h0 * r8;
            d8 += h1 * r7;
            d8 += h2 * r6;
            d8 += h3 * r5;
            d8 += h4 * r4;
            c = d8 >>> 13;
            d8 &= 0x1fff;
            d8 += h5 * r3;
            d8 += h6 * r2;
            d8 += h7 * r1;
            d8 += h8 * r0;
            d8 += h9 * (5 * r9);
            c += d8 >>> 13;
            d8 &= 0x1fff;
            d9 = c;
            d9 += h0 * r9;
            d9 += h1 * r8;
            d9 += h2 * r7;
            d9 += h3 * r6;
            d9 += h4 * r5;
            c = d9 >>> 13;
            d9 &= 0x1fff;
            d9 += h5 * r4;
            d9 += h6 * r3;
            d9 += h7 * r2;
            d9 += h8 * r1;
            d9 += h9 * r0;
            c += d9 >>> 13;
            d9 &= 0x1fff;
            c = ((c << 2) + c) | 0;
            c = (c + d0) | 0;
            d0 = c & 0x1fff;
            c = c >>> 13;
            d1 += c;
            h0 = d0;
            h1 = d1;
            h2 = d2;
            h3 = d3;
            h4 = d4;
            h5 = d5;
            h6 = d6;
            h7 = d7;
            h8 = d8;
            h9 = d9;
            mpos += 16;
            bytes -= 16;
        }
        this.h[0] = h0;
        this.h[1] = h1;
        this.h[2] = h2;
        this.h[3] = h3;
        this.h[4] = h4;
        this.h[5] = h5;
        this.h[6] = h6;
        this.h[7] = h7;
        this.h[8] = h8;
        this.h[9] = h9;
    }
    finish(mac, macpos) {
        var g = new Uint16Array(10);
        var c, mask, f, i;
        if (this.leftover) {
            i = this.leftover;
            this.buffer[i++] = 1;
            for (; i < 16; i++)
                this.buffer[i] = 0;
            this.fin = 1;
            this.blocks(this.buffer, 0, 16);
        }
        c = this.h[1] >>> 13;
        this.h[1] &= 0x1fff;
        for (i = 2; i < 10; i++) {
            this.h[i] += c;
            c = this.h[i] >>> 13;
            this.h[i] &= 0x1fff;
        }
        this.h[0] += c * 5;
        c = this.h[0] >>> 13;
        this.h[0] &= 0x1fff;
        this.h[1] += c;
        c = this.h[1] >>> 13;
        this.h[1] &= 0x1fff;
        this.h[2] += c;
        g[0] = this.h[0] + 5;
        c = g[0] >>> 13;
        g[0] &= 0x1fff;
        for (i = 1; i < 10; i++) {
            g[i] = this.h[i] + c;
            c = g[i] >>> 13;
            g[i] &= 0x1fff;
        }
        g[9] -= 1 << 13;
        mask = (c ^ 1) - 1;
        for (i = 0; i < 10; i++)
            g[i] &= mask;
        mask = ~mask;
        for (i = 0; i < 10; i++)
            this.h[i] = (this.h[i] & mask) | g[i];
        this.h[0] = (this.h[0] | (this.h[1] << 13)) & 0xffff;
        this.h[1] = ((this.h[1] >>> 3) | (this.h[2] << 10)) & 0xffff;
        this.h[2] = ((this.h[2] >>> 6) | (this.h[3] << 7)) & 0xffff;
        this.h[3] = ((this.h[3] >>> 9) | (this.h[4] << 4)) & 0xffff;
        this.h[4] =
            ((this.h[4] >>> 12) | (this.h[5] << 1) | (this.h[6] << 14)) & 0xffff;
        this.h[5] = ((this.h[6] >>> 2) | (this.h[7] << 11)) & 0xffff;
        this.h[6] = ((this.h[7] >>> 5) | (this.h[8] << 8)) & 0xffff;
        this.h[7] = ((this.h[8] >>> 8) | (this.h[9] << 5)) & 0xffff;
        f = this.h[0] + this.pad[0];
        this.h[0] = f & 0xffff;
        for (i = 1; i < 8; i++) {
            f = (((this.h[i] + this.pad[i]) | 0) + (f >>> 16)) | 0;
            this.h[i] = f & 0xffff;
        }
        mac[macpos + 0] = (this.h[0] >>> 0) & 0xff;
        mac[macpos + 1] = (this.h[0] >>> 8) & 0xff;
        mac[macpos + 2] = (this.h[1] >>> 0) & 0xff;
        mac[macpos + 3] = (this.h[1] >>> 8) & 0xff;
        mac[macpos + 4] = (this.h[2] >>> 0) & 0xff;
        mac[macpos + 5] = (this.h[2] >>> 8) & 0xff;
        mac[macpos + 6] = (this.h[3] >>> 0) & 0xff;
        mac[macpos + 7] = (this.h[3] >>> 8) & 0xff;
        mac[macpos + 8] = (this.h[4] >>> 0) & 0xff;
        mac[macpos + 9] = (this.h[4] >>> 8) & 0xff;
        mac[macpos + 10] = (this.h[5] >>> 0) & 0xff;
        mac[macpos + 11] = (this.h[5] >>> 8) & 0xff;
        mac[macpos + 12] = (this.h[6] >>> 0) & 0xff;
        mac[macpos + 13] = (this.h[6] >>> 8) & 0xff;
        mac[macpos + 14] = (this.h[7] >>> 0) & 0xff;
        mac[macpos + 15] = (this.h[7] >>> 8) & 0xff;
    }
    update(m, mpos, bytes) {
        let i;
        let want;
        if (this.leftover) {
            want = 16 - this.leftover;
            if (want > bytes)
                want = bytes;
            for (i = 0; i < want; i++)
                this.buffer[this.leftover + i] = m[mpos + i];
            bytes -= want;
            mpos += want;
            this.leftover += want;
            if (this.leftover < 16)
                return;
            this.blocks(this.buffer, 0, 16);
            this.leftover = 0;
        }
        if (bytes >= 16) {
            want = bytes - (bytes % 16);
            this.blocks(m, mpos, want);
            mpos += want;
            bytes -= want;
        }
        if (bytes) {
            for (i = 0; i < bytes; i++)
                this.buffer[this.leftover + i] = m[mpos + i];
            this.leftover += bytes;
        }
    }
}
function crypto_onetimeauth(out, outpos, m, mpos, n, k) {
    var s = new poly1305(k);
    s.update(m, mpos, n);
    s.finish(out, outpos);
    return 0;
}
function crypto_onetimeauth_verify(h, hpos, m, mpos, n, k) {
    var x = new Uint8Array(16);
    crypto_onetimeauth(x, 0, m, mpos, n, k);
    return crypto_verify_16(h, hpos, x, 0);
}
function crypto_secretbox(c, m, d, n, k) {
    var i;
    if (d < 32)
        return -1;
    crypto_stream_xor(c, 0, m, 0, d, n, k);
    crypto_onetimeauth(c, 16, c, 32, d - 32, c);
    for (i = 0; i < 16; i++)
        c[i] = 0;
    return 0;
}
function crypto_secretbox_open(m, c, d, n, k) {
    var i;
    var x = new Uint8Array(32);
    if (d < 32)
        return -1;
    crypto_stream(x, 0, 32, n, k);
    if (crypto_onetimeauth_verify(c, 16, c, 32, d - 32, x) !== 0)
        return -1;
    crypto_stream_xor(m, 0, c, 0, d, n, k);
    for (i = 0; i < 32; i++)
        m[i] = 0;
    return 0;
}
function set25519(r, a) {
    let i;
    for (i = 0; i < 16; i++)
        r[i] = a[i] | 0;
}
function car25519(o) {
    let i, v, c = 1;
    for (i = 0; i < 16; i++) {
        v = o[i] + c + 65535;
        c = Math.floor(v / 65536);
        o[i] = v - c * 65536;
    }
    o[0] += c - 1 + 37 * (c - 1);
}
function sel25519(p, q, b) {
    let t;
    const c = ~(b - 1);
    for (let i = 0; i < 16; i++) {
        t = c & (p[i] ^ q[i]);
        p[i] ^= t;
        q[i] ^= t;
    }
}
function pack25519(o, n) {
    let i, j, b;
    const m = gf(), t = gf();
    for (i = 0; i < 16; i++)
        t[i] = n[i];
    car25519(t);
    car25519(t);
    car25519(t);
    for (j = 0; j < 2; j++) {
        m[0] = t[0] - 0xffed;
        for (i = 1; i < 15; i++) {
            m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
            m[i - 1] &= 0xffff;
        }
        m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
        b = (m[15] >> 16) & 1;
        m[14] &= 0xffff;
        sel25519(t, m, 1 - b);
    }
    for (i = 0; i < 16; i++) {
        o[2 * i] = t[i] & 0xff;
        o[2 * i + 1] = t[i] >> 8;
    }
}
function neq25519(a, b) {
    const c = new Uint8Array(32), d = new Uint8Array(32);
    pack25519(c, a);
    pack25519(d, b);
    return crypto_verify_32(c, 0, d, 0);
}
function par25519(a) {
    const d = new Uint8Array(32);
    pack25519(d, a);
    return d[0] & 1;
}
function unpack25519(o, n) {
    let i;
    for (i = 0; i < 16; i++)
        o[i] = n[2 * i] + (n[2 * i + 1] << 8);
    o[15] &= 0x7fff;
}
function A(o, a, b) {
    for (let i = 0; i < 16; i++)
        o[i] = a[i] + b[i];
}
function Z(o, a, b) {
    for (let i = 0; i < 16; i++)
        o[i] = a[i] - b[i];
}
function M(o, a, b) {
    let v, c, t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, t7 = 0, t8 = 0, t9 = 0, t10 = 0, t11 = 0, t12 = 0, t13 = 0, t14 = 0, t15 = 0, t16 = 0, t17 = 0, t18 = 0, t19 = 0, t20 = 0, t21 = 0, t22 = 0, t23 = 0, t24 = 0, t25 = 0, t26 = 0, t27 = 0, t28 = 0, t29 = 0, t30 = 0;
    const b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5], b6 = b[6], b7 = b[7], b8 = b[8], b9 = b[9], b10 = b[10], b11 = b[11], b12 = b[12], b13 = b[13], b14 = b[14], b15 = b[15];
    v = a[0];
    t0 += v * b0;
    t1 += v * b1;
    t2 += v * b2;
    t3 += v * b3;
    t4 += v * b4;
    t5 += v * b5;
    t6 += v * b6;
    t7 += v * b7;
    t8 += v * b8;
    t9 += v * b9;
    t10 += v * b10;
    t11 += v * b11;
    t12 += v * b12;
    t13 += v * b13;
    t14 += v * b14;
    t15 += v * b15;
    v = a[1];
    t1 += v * b0;
    t2 += v * b1;
    t3 += v * b2;
    t4 += v * b3;
    t5 += v * b4;
    t6 += v * b5;
    t7 += v * b6;
    t8 += v * b7;
    t9 += v * b8;
    t10 += v * b9;
    t11 += v * b10;
    t12 += v * b11;
    t13 += v * b12;
    t14 += v * b13;
    t15 += v * b14;
    t16 += v * b15;
    v = a[2];
    t2 += v * b0;
    t3 += v * b1;
    t4 += v * b2;
    t5 += v * b3;
    t6 += v * b4;
    t7 += v * b5;
    t8 += v * b6;
    t9 += v * b7;
    t10 += v * b8;
    t11 += v * b9;
    t12 += v * b10;
    t13 += v * b11;
    t14 += v * b12;
    t15 += v * b13;
    t16 += v * b14;
    t17 += v * b15;
    v = a[3];
    t3 += v * b0;
    t4 += v * b1;
    t5 += v * b2;
    t6 += v * b3;
    t7 += v * b4;
    t8 += v * b5;
    t9 += v * b6;
    t10 += v * b7;
    t11 += v * b8;
    t12 += v * b9;
    t13 += v * b10;
    t14 += v * b11;
    t15 += v * b12;
    t16 += v * b13;
    t17 += v * b14;
    t18 += v * b15;
    v = a[4];
    t4 += v * b0;
    t5 += v * b1;
    t6 += v * b2;
    t7 += v * b3;
    t8 += v * b4;
    t9 += v * b5;
    t10 += v * b6;
    t11 += v * b7;
    t12 += v * b8;
    t13 += v * b9;
    t14 += v * b10;
    t15 += v * b11;
    t16 += v * b12;
    t17 += v * b13;
    t18 += v * b14;
    t19 += v * b15;
    v = a[5];
    t5 += v * b0;
    t6 += v * b1;
    t7 += v * b2;
    t8 += v * b3;
    t9 += v * b4;
    t10 += v * b5;
    t11 += v * b6;
    t12 += v * b7;
    t13 += v * b8;
    t14 += v * b9;
    t15 += v * b10;
    t16 += v * b11;
    t17 += v * b12;
    t18 += v * b13;
    t19 += v * b14;
    t20 += v * b15;
    v = a[6];
    t6 += v * b0;
    t7 += v * b1;
    t8 += v * b2;
    t9 += v * b3;
    t10 += v * b4;
    t11 += v * b5;
    t12 += v * b6;
    t13 += v * b7;
    t14 += v * b8;
    t15 += v * b9;
    t16 += v * b10;
    t17 += v * b11;
    t18 += v * b12;
    t19 += v * b13;
    t20 += v * b14;
    t21 += v * b15;
    v = a[7];
    t7 += v * b0;
    t8 += v * b1;
    t9 += v * b2;
    t10 += v * b3;
    t11 += v * b4;
    t12 += v * b5;
    t13 += v * b6;
    t14 += v * b7;
    t15 += v * b8;
    t16 += v * b9;
    t17 += v * b10;
    t18 += v * b11;
    t19 += v * b12;
    t20 += v * b13;
    t21 += v * b14;
    t22 += v * b15;
    v = a[8];
    t8 += v * b0;
    t9 += v * b1;
    t10 += v * b2;
    t11 += v * b3;
    t12 += v * b4;
    t13 += v * b5;
    t14 += v * b6;
    t15 += v * b7;
    t16 += v * b8;
    t17 += v * b9;
    t18 += v * b10;
    t19 += v * b11;
    t20 += v * b12;
    t21 += v * b13;
    t22 += v * b14;
    t23 += v * b15;
    v = a[9];
    t9 += v * b0;
    t10 += v * b1;
    t11 += v * b2;
    t12 += v * b3;
    t13 += v * b4;
    t14 += v * b5;
    t15 += v * b6;
    t16 += v * b7;
    t17 += v * b8;
    t18 += v * b9;
    t19 += v * b10;
    t20 += v * b11;
    t21 += v * b12;
    t22 += v * b13;
    t23 += v * b14;
    t24 += v * b15;
    v = a[10];
    t10 += v * b0;
    t11 += v * b1;
    t12 += v * b2;
    t13 += v * b3;
    t14 += v * b4;
    t15 += v * b5;
    t16 += v * b6;
    t17 += v * b7;
    t18 += v * b8;
    t19 += v * b9;
    t20 += v * b10;
    t21 += v * b11;
    t22 += v * b12;
    t23 += v * b13;
    t24 += v * b14;
    t25 += v * b15;
    v = a[11];
    t11 += v * b0;
    t12 += v * b1;
    t13 += v * b2;
    t14 += v * b3;
    t15 += v * b4;
    t16 += v * b5;
    t17 += v * b6;
    t18 += v * b7;
    t19 += v * b8;
    t20 += v * b9;
    t21 += v * b10;
    t22 += v * b11;
    t23 += v * b12;
    t24 += v * b13;
    t25 += v * b14;
    t26 += v * b15;
    v = a[12];
    t12 += v * b0;
    t13 += v * b1;
    t14 += v * b2;
    t15 += v * b3;
    t16 += v * b4;
    t17 += v * b5;
    t18 += v * b6;
    t19 += v * b7;
    t20 += v * b8;
    t21 += v * b9;
    t22 += v * b10;
    t23 += v * b11;
    t24 += v * b12;
    t25 += v * b13;
    t26 += v * b14;
    t27 += v * b15;
    v = a[13];
    t13 += v * b0;
    t14 += v * b1;
    t15 += v * b2;
    t16 += v * b3;
    t17 += v * b4;
    t18 += v * b5;
    t19 += v * b6;
    t20 += v * b7;
    t21 += v * b8;
    t22 += v * b9;
    t23 += v * b10;
    t24 += v * b11;
    t25 += v * b12;
    t26 += v * b13;
    t27 += v * b14;
    t28 += v * b15;
    v = a[14];
    t14 += v * b0;
    t15 += v * b1;
    t16 += v * b2;
    t17 += v * b3;
    t18 += v * b4;
    t19 += v * b5;
    t20 += v * b6;
    t21 += v * b7;
    t22 += v * b8;
    t23 += v * b9;
    t24 += v * b10;
    t25 += v * b11;
    t26 += v * b12;
    t27 += v * b13;
    t28 += v * b14;
    t29 += v * b15;
    v = a[15];
    t15 += v * b0;
    t16 += v * b1;
    t17 += v * b2;
    t18 += v * b3;
    t19 += v * b4;
    t20 += v * b5;
    t21 += v * b6;
    t22 += v * b7;
    t23 += v * b8;
    t24 += v * b9;
    t25 += v * b10;
    t26 += v * b11;
    t27 += v * b12;
    t28 += v * b13;
    t29 += v * b14;
    t30 += v * b15;
    t0 += 38 * t16;
    t1 += 38 * t17;
    t2 += 38 * t18;
    t3 += 38 * t19;
    t4 += 38 * t20;
    t5 += 38 * t21;
    t6 += 38 * t22;
    t7 += 38 * t23;
    t8 += 38 * t24;
    t9 += 38 * t25;
    t10 += 38 * t26;
    t11 += 38 * t27;
    t12 += 38 * t28;
    t13 += 38 * t29;
    t14 += 38 * t30;
    // t15 left as is
    // first car
    c = 1;
    v = t0 + c + 65535;
    c = Math.floor(v / 65536);
    t0 = v - c * 65536;
    v = t1 + c + 65535;
    c = Math.floor(v / 65536);
    t1 = v - c * 65536;
    v = t2 + c + 65535;
    c = Math.floor(v / 65536);
    t2 = v - c * 65536;
    v = t3 + c + 65535;
    c = Math.floor(v / 65536);
    t3 = v - c * 65536;
    v = t4 + c + 65535;
    c = Math.floor(v / 65536);
    t4 = v - c * 65536;
    v = t5 + c + 65535;
    c = Math.floor(v / 65536);
    t5 = v - c * 65536;
    v = t6 + c + 65535;
    c = Math.floor(v / 65536);
    t6 = v - c * 65536;
    v = t7 + c + 65535;
    c = Math.floor(v / 65536);
    t7 = v - c * 65536;
    v = t8 + c + 65535;
    c = Math.floor(v / 65536);
    t8 = v - c * 65536;
    v = t9 + c + 65535;
    c = Math.floor(v / 65536);
    t9 = v - c * 65536;
    v = t10 + c + 65535;
    c = Math.floor(v / 65536);
    t10 = v - c * 65536;
    v = t11 + c + 65535;
    c = Math.floor(v / 65536);
    t11 = v - c * 65536;
    v = t12 + c + 65535;
    c = Math.floor(v / 65536);
    t12 = v - c * 65536;
    v = t13 + c + 65535;
    c = Math.floor(v / 65536);
    t13 = v - c * 65536;
    v = t14 + c + 65535;
    c = Math.floor(v / 65536);
    t14 = v - c * 65536;
    v = t15 + c + 65535;
    c = Math.floor(v / 65536);
    t15 = v - c * 65536;
    t0 += c - 1 + 37 * (c - 1);
    // second car
    c = 1;
    v = t0 + c + 65535;
    c = Math.floor(v / 65536);
    t0 = v - c * 65536;
    v = t1 + c + 65535;
    c = Math.floor(v / 65536);
    t1 = v - c * 65536;
    v = t2 + c + 65535;
    c = Math.floor(v / 65536);
    t2 = v - c * 65536;
    v = t3 + c + 65535;
    c = Math.floor(v / 65536);
    t3 = v - c * 65536;
    v = t4 + c + 65535;
    c = Math.floor(v / 65536);
    t4 = v - c * 65536;
    v = t5 + c + 65535;
    c = Math.floor(v / 65536);
    t5 = v - c * 65536;
    v = t6 + c + 65535;
    c = Math.floor(v / 65536);
    t6 = v - c * 65536;
    v = t7 + c + 65535;
    c = Math.floor(v / 65536);
    t7 = v - c * 65536;
    v = t8 + c + 65535;
    c = Math.floor(v / 65536);
    t8 = v - c * 65536;
    v = t9 + c + 65535;
    c = Math.floor(v / 65536);
    t9 = v - c * 65536;
    v = t10 + c + 65535;
    c = Math.floor(v / 65536);
    t10 = v - c * 65536;
    v = t11 + c + 65535;
    c = Math.floor(v / 65536);
    t11 = v - c * 65536;
    v = t12 + c + 65535;
    c = Math.floor(v / 65536);
    t12 = v - c * 65536;
    v = t13 + c + 65535;
    c = Math.floor(v / 65536);
    t13 = v - c * 65536;
    v = t14 + c + 65535;
    c = Math.floor(v / 65536);
    t14 = v - c * 65536;
    v = t15 + c + 65535;
    c = Math.floor(v / 65536);
    t15 = v - c * 65536;
    t0 += c - 1 + 37 * (c - 1);
    o[0] = t0;
    o[1] = t1;
    o[2] = t2;
    o[3] = t3;
    o[4] = t4;
    o[5] = t5;
    o[6] = t6;
    o[7] = t7;
    o[8] = t8;
    o[9] = t9;
    o[10] = t10;
    o[11] = t11;
    o[12] = t12;
    o[13] = t13;
    o[14] = t14;
    o[15] = t15;
}
function S(o, a) {
    M(o, a, a);
}
function inv25519(o, i) {
    const c = gf();
    let a;
    for (a = 0; a < 16; a++)
        c[a] = i[a];
    for (a = 253; a >= 0; a--) {
        S(c, c);
        if (a !== 2 && a !== 4)
            M(c, c, i);
    }
    for (a = 0; a < 16; a++)
        o[a] = c[a];
}
function pow2523(o, i) {
    const c = gf();
    let a;
    for (a = 0; a < 16; a++)
        c[a] = i[a];
    for (a = 250; a >= 0; a--) {
        S(c, c);
        if (a !== 1)
            M(c, c, i);
    }
    for (a = 0; a < 16; a++)
        o[a] = c[a];
}
function crypto_scalarmult(q, n, p) {
    const z = new Uint8Array(32);
    const x = new Float64Array(80);
    let r;
    let i;
    const a = gf(), b = gf(), c = gf(), d = gf(), e = gf(), f = gf();
    for (i = 0; i < 31; i++)
        z[i] = n[i];
    z[31] = (n[31] & 127) | 64;
    z[0] &= 248;
    unpack25519(x, p);
    for (i = 0; i < 16; i++) {
        b[i] = x[i];
        d[i] = a[i] = c[i] = 0;
    }
    a[0] = d[0] = 1;
    for (i = 254; i >= 0; --i) {
        r = (z[i >>> 3] >>> (i & 7)) & 1;
        sel25519(a, b, r);
        sel25519(c, d, r);
        A(e, a, c);
        Z(a, a, c);
        A(c, b, d);
        Z(b, b, d);
        S(d, e);
        S(f, a);
        M(a, c, a);
        M(c, b, e);
        A(e, a, c);
        Z(a, a, c);
        S(b, a);
        Z(c, d, f);
        M(a, c, _121665);
        A(a, a, d);
        M(c, c, a);
        M(a, d, f);
        M(d, b, x);
        S(b, e);
        sel25519(a, b, r);
        sel25519(c, d, r);
    }
    for (i = 0; i < 16; i++) {
        x[i + 16] = a[i];
        x[i + 32] = c[i];
        x[i + 48] = b[i];
        x[i + 64] = d[i];
    }
    const x32 = x.subarray(32);
    const x16 = x.subarray(16);
    inv25519(x32, x32);
    M(x16, x16, x32);
    pack25519(q, x16);
    return 0;
}
function crypto_scalarmult_base(q, n) {
    return crypto_scalarmult(q, n, _9);
}
// prettier-ignore
const K$1 = [
    0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd,
    0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc,
    0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019,
    0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118,
    0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe,
    0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2,
    0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1,
    0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694,
    0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3,
    0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65,
    0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483,
    0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5,
    0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210,
    0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4,
    0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725,
    0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70,
    0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926,
    0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df,
    0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8,
    0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b,
    0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001,
    0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30,
    0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910,
    0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8,
    0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53,
    0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8,
    0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb,
    0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3,
    0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60,
    0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec,
    0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9,
    0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b,
    0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207,
    0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178,
    0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6,
    0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b,
    0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493,
    0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c,
    0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a,
    0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817
];
function crypto_hashblocks_hl(hh, hl, m, n) {
    const wh = new Int32Array(16), wl = new Int32Array(16);
    let bh0, bh1, bh2, bh3, bh4, bh5, bh6, bh7, bl0, bl1, bl2, bl3, bl4, bl5, bl6, bl7, th, tl, i, j, h, l, a, b, c, d;
    let ah0 = hh[0], ah1 = hh[1], ah2 = hh[2], ah3 = hh[3], ah4 = hh[4], ah5 = hh[5], ah6 = hh[6], ah7 = hh[7], al0 = hl[0], al1 = hl[1], al2 = hl[2], al3 = hl[3], al4 = hl[4], al5 = hl[5], al6 = hl[6], al7 = hl[7];
    let pos = 0;
    while (n >= 128) {
        for (i = 0; i < 16; i++) {
            j = 8 * i + pos;
            wh[i] = (m[j + 0] << 24) | (m[j + 1] << 16) | (m[j + 2] << 8) | m[j + 3];
            wl[i] = (m[j + 4] << 24) | (m[j + 5] << 16) | (m[j + 6] << 8) | m[j + 7];
        }
        for (i = 0; i < 80; i++) {
            bh0 = ah0;
            bh1 = ah1;
            bh2 = ah2;
            bh3 = ah3;
            bh4 = ah4;
            bh5 = ah5;
            bh6 = ah6;
            bh7 = ah7;
            bl0 = al0;
            bl1 = al1;
            bl2 = al2;
            bl3 = al3;
            bl4 = al4;
            bl5 = al5;
            bl6 = al6;
            bl7 = al7;
            // add
            h = ah7;
            l = al7;
            a = l & 0xffff;
            b = l >>> 16;
            c = h & 0xffff;
            d = h >>> 16;
            // Sigma1
            h =
                ((ah4 >>> 14) | (al4 << (32 - 14))) ^
                    ((ah4 >>> 18) | (al4 << (32 - 18))) ^
                    ((al4 >>> (41 - 32)) | (ah4 << (32 - (41 - 32))));
            l =
                ((al4 >>> 14) | (ah4 << (32 - 14))) ^
                    ((al4 >>> 18) | (ah4 << (32 - 18))) ^
                    ((ah4 >>> (41 - 32)) | (al4 << (32 - (41 - 32))));
            a += l & 0xffff;
            b += l >>> 16;
            c += h & 0xffff;
            d += h >>> 16;
            // Ch
            h = (ah4 & ah5) ^ (~ah4 & ah6);
            l = (al4 & al5) ^ (~al4 & al6);
            a += l & 0xffff;
            b += l >>> 16;
            c += h & 0xffff;
            d += h >>> 16;
            // K
            h = K$1[i * 2];
            l = K$1[i * 2 + 1];
            a += l & 0xffff;
            b += l >>> 16;
            c += h & 0xffff;
            d += h >>> 16;
            // w
            h = wh[i % 16];
            l = wl[i % 16];
            a += l & 0xffff;
            b += l >>> 16;
            c += h & 0xffff;
            d += h >>> 16;
            b += a >>> 16;
            c += b >>> 16;
            d += c >>> 16;
            th = (c & 0xffff) | (d << 16);
            tl = (a & 0xffff) | (b << 16);
            // add
            h = th;
            l = tl;
            a = l & 0xffff;
            b = l >>> 16;
            c = h & 0xffff;
            d = h >>> 16;
            // Sigma0
            h =
                ((ah0 >>> 28) | (al0 << (32 - 28))) ^
                    ((al0 >>> (34 - 32)) | (ah0 << (32 - (34 - 32)))) ^
                    ((al0 >>> (39 - 32)) | (ah0 << (32 - (39 - 32))));
            l =
                ((al0 >>> 28) | (ah0 << (32 - 28))) ^
                    ((ah0 >>> (34 - 32)) | (al0 << (32 - (34 - 32)))) ^
                    ((ah0 >>> (39 - 32)) | (al0 << (32 - (39 - 32))));
            a += l & 0xffff;
            b += l >>> 16;
            c += h & 0xffff;
            d += h >>> 16;
            // Maj
            h = (ah0 & ah1) ^ (ah0 & ah2) ^ (ah1 & ah2);
            l = (al0 & al1) ^ (al0 & al2) ^ (al1 & al2);
            a += l & 0xffff;
            b += l >>> 16;
            c += h & 0xffff;
            d += h >>> 16;
            b += a >>> 16;
            c += b >>> 16;
            d += c >>> 16;
            bh7 = (c & 0xffff) | (d << 16);
            bl7 = (a & 0xffff) | (b << 16);
            // add
            h = bh3;
            l = bl3;
            a = l & 0xffff;
            b = l >>> 16;
            c = h & 0xffff;
            d = h >>> 16;
            h = th;
            l = tl;
            a += l & 0xffff;
            b += l >>> 16;
            c += h & 0xffff;
            d += h >>> 16;
            b += a >>> 16;
            c += b >>> 16;
            d += c >>> 16;
            bh3 = (c & 0xffff) | (d << 16);
            bl3 = (a & 0xffff) | (b << 16);
            ah1 = bh0;
            ah2 = bh1;
            ah3 = bh2;
            ah4 = bh3;
            ah5 = bh4;
            ah6 = bh5;
            ah7 = bh6;
            ah0 = bh7;
            al1 = bl0;
            al2 = bl1;
            al3 = bl2;
            al4 = bl3;
            al5 = bl4;
            al6 = bl5;
            al7 = bl6;
            al0 = bl7;
            if (i % 16 === 15) {
                for (j = 0; j < 16; j++) {
                    // add
                    h = wh[j];
                    l = wl[j];
                    a = l & 0xffff;
                    b = l >>> 16;
                    c = h & 0xffff;
                    d = h >>> 16;
                    h = wh[(j + 9) % 16];
                    l = wl[(j + 9) % 16];
                    a += l & 0xffff;
                    b += l >>> 16;
                    c += h & 0xffff;
                    d += h >>> 16;
                    // sigma0
                    th = wh[(j + 1) % 16];
                    tl = wl[(j + 1) % 16];
                    h =
                        ((th >>> 1) | (tl << (32 - 1))) ^
                            ((th >>> 8) | (tl << (32 - 8))) ^
                            (th >>> 7);
                    l =
                        ((tl >>> 1) | (th << (32 - 1))) ^
                            ((tl >>> 8) | (th << (32 - 8))) ^
                            ((tl >>> 7) | (th << (32 - 7)));
                    a += l & 0xffff;
                    b += l >>> 16;
                    c += h & 0xffff;
                    d += h >>> 16;
                    // sigma1
                    th = wh[(j + 14) % 16];
                    tl = wl[(j + 14) % 16];
                    h =
                        ((th >>> 19) | (tl << (32 - 19))) ^
                            ((tl >>> (61 - 32)) | (th << (32 - (61 - 32)))) ^
                            (th >>> 6);
                    l =
                        ((tl >>> 19) | (th << (32 - 19))) ^
                            ((th >>> (61 - 32)) | (tl << (32 - (61 - 32)))) ^
                            ((tl >>> 6) | (th << (32 - 6)));
                    a += l & 0xffff;
                    b += l >>> 16;
                    c += h & 0xffff;
                    d += h >>> 16;
                    b += a >>> 16;
                    c += b >>> 16;
                    d += c >>> 16;
                    wh[j] = (c & 0xffff) | (d << 16);
                    wl[j] = (a & 0xffff) | (b << 16);
                }
            }
        }
        // add
        h = ah0;
        l = al0;
        a = l & 0xffff;
        b = l >>> 16;
        c = h & 0xffff;
        d = h >>> 16;
        h = hh[0];
        l = hl[0];
        a += l & 0xffff;
        b += l >>> 16;
        c += h & 0xffff;
        d += h >>> 16;
        b += a >>> 16;
        c += b >>> 16;
        d += c >>> 16;
        hh[0] = ah0 = (c & 0xffff) | (d << 16);
        hl[0] = al0 = (a & 0xffff) | (b << 16);
        h = ah1;
        l = al1;
        a = l & 0xffff;
        b = l >>> 16;
        c = h & 0xffff;
        d = h >>> 16;
        h = hh[1];
        l = hl[1];
        a += l & 0xffff;
        b += l >>> 16;
        c += h & 0xffff;
        d += h >>> 16;
        b += a >>> 16;
        c += b >>> 16;
        d += c >>> 16;
        hh[1] = ah1 = (c & 0xffff) | (d << 16);
        hl[1] = al1 = (a & 0xffff) | (b << 16);
        h = ah2;
        l = al2;
        a = l & 0xffff;
        b = l >>> 16;
        c = h & 0xffff;
        d = h >>> 16;
        h = hh[2];
        l = hl[2];
        a += l & 0xffff;
        b += l >>> 16;
        c += h & 0xffff;
        d += h >>> 16;
        b += a >>> 16;
        c += b >>> 16;
        d += c >>> 16;
        hh[2] = ah2 = (c & 0xffff) | (d << 16);
        hl[2] = al2 = (a & 0xffff) | (b << 16);
        h = ah3;
        l = al3;
        a = l & 0xffff;
        b = l >>> 16;
        c = h & 0xffff;
        d = h >>> 16;
        h = hh[3];
        l = hl[3];
        a += l & 0xffff;
        b += l >>> 16;
        c += h & 0xffff;
        d += h >>> 16;
        b += a >>> 16;
        c += b >>> 16;
        d += c >>> 16;
        hh[3] = ah3 = (c & 0xffff) | (d << 16);
        hl[3] = al3 = (a & 0xffff) | (b << 16);
        h = ah4;
        l = al4;
        a = l & 0xffff;
        b = l >>> 16;
        c = h & 0xffff;
        d = h >>> 16;
        h = hh[4];
        l = hl[4];
        a += l & 0xffff;
        b += l >>> 16;
        c += h & 0xffff;
        d += h >>> 16;
        b += a >>> 16;
        c += b >>> 16;
        d += c >>> 16;
        hh[4] = ah4 = (c & 0xffff) | (d << 16);
        hl[4] = al4 = (a & 0xffff) | (b << 16);
        h = ah5;
        l = al5;
        a = l & 0xffff;
        b = l >>> 16;
        c = h & 0xffff;
        d = h >>> 16;
        h = hh[5];
        l = hl[5];
        a += l & 0xffff;
        b += l >>> 16;
        c += h & 0xffff;
        d += h >>> 16;
        b += a >>> 16;
        c += b >>> 16;
        d += c >>> 16;
        hh[5] = ah5 = (c & 0xffff) | (d << 16);
        hl[5] = al5 = (a & 0xffff) | (b << 16);
        h = ah6;
        l = al6;
        a = l & 0xffff;
        b = l >>> 16;
        c = h & 0xffff;
        d = h >>> 16;
        h = hh[6];
        l = hl[6];
        a += l & 0xffff;
        b += l >>> 16;
        c += h & 0xffff;
        d += h >>> 16;
        b += a >>> 16;
        c += b >>> 16;
        d += c >>> 16;
        hh[6] = ah6 = (c & 0xffff) | (d << 16);
        hl[6] = al6 = (a & 0xffff) | (b << 16);
        h = ah7;
        l = al7;
        a = l & 0xffff;
        b = l >>> 16;
        c = h & 0xffff;
        d = h >>> 16;
        h = hh[7];
        l = hl[7];
        a += l & 0xffff;
        b += l >>> 16;
        c += h & 0xffff;
        d += h >>> 16;
        b += a >>> 16;
        c += b >>> 16;
        d += c >>> 16;
        hh[7] = ah7 = (c & 0xffff) | (d << 16);
        hl[7] = al7 = (a & 0xffff) | (b << 16);
        pos += 128;
        n -= 128;
    }
    return n;
}
function crypto_hash(out, m, n) {
    const hh = new Int32Array(8);
    const hl = new Int32Array(8);
    const x = new Uint8Array(256);
    const b = n;
    hh[0] = 0x6a09e667;
    hh[1] = 0xbb67ae85;
    hh[2] = 0x3c6ef372;
    hh[3] = 0xa54ff53a;
    hh[4] = 0x510e527f;
    hh[5] = 0x9b05688c;
    hh[6] = 0x1f83d9ab;
    hh[7] = 0x5be0cd19;
    hl[0] = 0xf3bcc908;
    hl[1] = 0x84caa73b;
    hl[2] = 0xfe94f82b;
    hl[3] = 0x5f1d36f1;
    hl[4] = 0xade682d1;
    hl[5] = 0x2b3e6c1f;
    hl[6] = 0xfb41bd6b;
    hl[7] = 0x137e2179;
    crypto_hashblocks_hl(hh, hl, m, n);
    n %= 128;
    for (let i = 0; i < n; i++)
        x[i] = m[b - n + i];
    x[n] = 128;
    n = 256 - 128 * (n < 112 ? 1 : 0);
    x[n - 9] = 0;
    ts64(x, n - 8, (b / 0x20000000) | 0, b << 3);
    crypto_hashblocks_hl(hh, hl, x, n);
    for (let i = 0; i < 8; i++)
        ts64(out, 8 * i, hh[i], hl[i]);
    return 0;
}
/**
 * Incremental version of crypto_hash.
 */
class HashState {
    constructor() {
        this.hh = new Int32Array(8);
        this.hl = new Int32Array(8);
        this.next = new Uint8Array(128);
        this.p = 0;
        this.total = 0;
        this.hh[0] = 0x6a09e667;
        this.hh[1] = 0xbb67ae85;
        this.hh[2] = 0x3c6ef372;
        this.hh[3] = 0xa54ff53a;
        this.hh[4] = 0x510e527f;
        this.hh[5] = 0x9b05688c;
        this.hh[6] = 0x1f83d9ab;
        this.hh[7] = 0x5be0cd19;
        this.hl[0] = 0xf3bcc908;
        this.hl[1] = 0x84caa73b;
        this.hl[2] = 0xfe94f82b;
        this.hl[3] = 0x5f1d36f1;
        this.hl[4] = 0xade682d1;
        this.hl[5] = 0x2b3e6c1f;
        this.hl[6] = 0xfb41bd6b;
        this.hl[7] = 0x137e2179;
    }
    update(data) {
        this.total += data.length;
        let i = 0;
        while (i < data.length) {
            const r = 128 - this.p;
            if (r > data.length - i) {
                for (let j = 0; i + j < data.length; j++) {
                    this.next[this.p + j] = data[i + j];
                }
                this.p += data.length - i;
                break;
            }
            else {
                for (let j = 0; this.p + j < 128; j++) {
                    this.next[this.p + j] = data[i + j];
                }
                crypto_hashblocks_hl(this.hh, this.hl, this.next, 128);
                i += 128 - this.p;
                this.p = 0;
            }
        }
        return this;
    }
    finish() {
        const out = new Uint8Array(64);
        let n = this.p;
        const x = new Uint8Array(256);
        const b = this.total;
        for (let i = 0; i < n; i++)
            x[i] = this.next[i];
        x[n] = 128;
        n = 256 - 128 * (n < 112 ? 1 : 0);
        x[n - 9] = 0;
        ts64(x, n - 8, (b / 0x20000000) | 0, b << 3);
        crypto_hashblocks_hl(this.hh, this.hl, x, n);
        for (let i = 0; i < 8; i++)
            ts64(out, 8 * i, this.hh[i], this.hl[i]);
        return out;
    }
}
function add(p, q) {
    const a = gf(), b = gf(), c = gf(), d = gf(), e = gf(), f = gf(), g = gf(), h = gf(), t = gf();
    Z(a, p[1], p[0]);
    Z(t, q[1], q[0]);
    M(a, a, t);
    A(b, p[0], p[1]);
    A(t, q[0], q[1]);
    M(b, b, t);
    M(c, p[3], q[3]);
    M(c, c, D2);
    M(d, p[2], q[2]);
    A(d, d, d);
    Z(e, b, a);
    Z(f, d, c);
    A(g, d, c);
    A(h, b, a);
    M(p[0], e, f);
    M(p[1], h, g);
    M(p[2], g, f);
    M(p[3], e, h);
}
function cswap(p, q, b) {
    let i;
    for (i = 0; i < 4; i++) {
        sel25519(p[i], q[i], b);
    }
}
function pack(r, p) {
    const tx = gf(), ty = gf(), zi = gf();
    inv25519(zi, p[2]);
    M(tx, p[0], zi);
    M(ty, p[1], zi);
    pack25519(r, ty);
    r[31] ^= par25519(tx) << 7;
}
/**
 * Ed25519 scalar multiplication
 */
function scalarmult(p, q, s) {
    let b, i;
    set25519(p[0], gf0);
    set25519(p[1], gf1);
    set25519(p[2], gf1);
    set25519(p[3], gf0);
    for (i = 255; i >= 0; --i) {
        b = (s[(i / 8) | 0] >> (i & 7)) & 1;
        cswap(p, q, b);
        add(q, p);
        add(p, p);
        cswap(p, q, b);
    }
}
function scalarbase(p, s) {
    const q = [gf(), gf(), gf(), gf()];
    set25519(q[0], X);
    set25519(q[1], Y);
    set25519(q[2], gf1);
    M(q[3], X, Y);
    scalarmult(p, q, s);
}
function crypto_sign_keypair(pk, sk, seeded) {
    const d = new Uint8Array(64);
    const p = [gf(), gf(), gf(), gf()];
    if (!seeded)
        randombytes(sk, 32);
    crypto_hash(d, sk, 32);
    d[0] &= 248;
    d[31] &= 127;
    d[31] |= 64;
    scalarbase(p, d);
    pack(pk, p);
    for (let i = 0; i < 32; i++)
        sk[i + 32] = pk[i];
    return 0;
}
const L = new Float64Array([
    0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde,
    0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10,
]);
function modL(r, x) {
    let carry, i, j, k;
    for (i = 63; i >= 32; --i) {
        carry = 0;
        for (j = i - 32, k = i - 12; j < k; ++j) {
            x[j] += carry - 16 * x[i] * L[j - (i - 32)];
            carry = Math.floor((x[j] + 128) / 256);
            x[j] -= carry * 256;
        }
        x[j] += carry;
        x[i] = 0;
    }
    carry = 0;
    for (j = 0; j < 32; j++) {
        x[j] += carry - (x[31] >> 4) * L[j];
        carry = x[j] >> 8;
        x[j] &= 255;
    }
    for (j = 0; j < 32; j++)
        x[j] -= carry * L[j];
    for (i = 0; i < 32; i++) {
        x[i + 1] += x[i] >> 8;
        r[i] = x[i] & 255;
    }
}
function reduce(r) {
    const x = new Float64Array(64);
    for (let i = 0; i < 64; i++)
        x[i] = r[i];
    for (let i = 0; i < 64; i++)
        r[i] = 0;
    modL(r, x);
}
// Note: difference from C - smlen returned, not passed as argument.
function crypto_sign(sm, m, n, sk) {
    const d = new Uint8Array(64), h = new Uint8Array(64), r = new Uint8Array(64);
    let i, j;
    const x = new Float64Array(64);
    const p = [gf(), gf(), gf(), gf()];
    crypto_hash(d, sk, 32);
    d[0] &= 248;
    d[31] &= 127;
    d[31] |= 64;
    const smlen = n + 64;
    for (i = 0; i < n; i++)
        sm[64 + i] = m[i];
    for (i = 0; i < 32; i++)
        sm[32 + i] = d[32 + i];
    crypto_hash(r, sm.subarray(32), n + 32);
    reduce(r);
    scalarbase(p, r);
    pack(sm, p);
    for (i = 32; i < 64; i++)
        sm[i] = sk[i];
    crypto_hash(h, sm, n + 64);
    reduce(h);
    for (i = 0; i < 64; i++)
        x[i] = 0;
    for (i = 0; i < 32; i++)
        x[i] = r[i];
    for (i = 0; i < 32; i++) {
        for (j = 0; j < 32; j++) {
            x[i + j] += h[i] * d[j];
        }
    }
    modL(sm.subarray(32), x);
    return smlen;
}
function unpackpos(r, p) {
    // FIXME: implement directly
    const q = [gf(), gf(), gf(), gf()];
    if (unpackneg(q, p))
        return -1;
    const scalar0 = new Uint8Array(32);
    const scalar1 = new Uint8Array(32);
    scalar1[0] = 1;
    const scalarNeg1 = crypto_core_ed25519_scalar_sub(scalar0, scalar1);
    scalarmult(r, q, scalarNeg1);
    return 0;
}
function unpackneg(r, p) {
    const t = gf();
    const chk = gf();
    const num = gf();
    const den = gf();
    const den2 = gf();
    const den4 = gf();
    const den6 = gf();
    set25519(r[2], gf1);
    unpack25519(r[1], p);
    S(num, r[1]);
    M(den, num, D);
    Z(num, num, r[2]);
    A(den, r[2], den);
    S(den2, den);
    S(den4, den2);
    M(den6, den4, den2);
    M(t, den6, num);
    M(t, t, den);
    pow2523(t, t);
    M(t, t, num);
    M(t, t, den);
    M(t, t, den);
    M(r[0], t, den);
    S(chk, r[0]);
    M(chk, chk, den);
    if (neq25519(chk, num))
        M(r[0], r[0], I);
    S(chk, r[0]);
    M(chk, chk, den);
    if (neq25519(chk, num))
        return -1;
    if (par25519(r[0]) === p[31] >> 7)
        Z(r[0], gf0, r[0]);
    M(r[3], r[0], r[1]);
    return 0;
}
function crypto_scalarmult_ed25519_base_noclamp(s) {
    const r = new Uint8Array(32);
    const p = [gf(), gf(), gf(), gf()];
    scalarbase(p, s);
    pack(r, p);
    return r;
}
function crypto_scalarmult_ed25519_noclamp(s, q) {
    const r = new Uint8Array(32);
    const p = [gf(), gf(), gf(), gf()];
    const ql = [gf(), gf(), gf(), gf()];
    if (unpackpos(ql, q))
        throw new Error();
    scalarmult(p, ql, s);
    pack(r, p);
    return r;
}
function crypto_sign_open(m, sm, n, pk) {
    let i, mlen;
    const t = new Uint8Array(32), h = new Uint8Array(64);
    const p = [gf(), gf(), gf(), gf()], q = [gf(), gf(), gf(), gf()];
    mlen = -1;
    if (n < 64)
        return -1;
    if (unpackneg(q, pk))
        return -1;
    for (i = 0; i < n; i++)
        m[i] = sm[i];
    for (i = 0; i < 32; i++)
        m[i + 32] = pk[i];
    crypto_hash(h, m, n);
    reduce(h);
    scalarmult(p, q, h);
    scalarbase(q, sm.subarray(32));
    add(p, q);
    pack(t, p);
    n -= 64;
    if (crypto_verify_32(sm, 0, t, 0)) {
        for (i = 0; i < n; i++)
            m[i] = 0;
        return -1;
    }
    for (i = 0; i < n; i++)
        m[i] = sm[i + 64];
    mlen = n;
    return mlen;
}
const crypto_secretbox_KEYBYTES = 32;
const crypto_secretbox_NONCEBYTES = 24;
const crypto_secretbox_ZEROBYTES = 32;
const crypto_secretbox_BOXZEROBYTES = 16;
const crypto_scalarmult_BYTES = 32;
const crypto_scalarmult_SCALARBYTES = 32;
const crypto_sign_BYTES = 64;
const crypto_sign_PUBLICKEYBYTES = 32;
const crypto_sign_SECRETKEYBYTES = 64;
const crypto_sign_SEEDBYTES = 32;
const crypto_hash_BYTES = 64;
/* High-level API */
function checkLengths(k, n) {
    if (k.length !== crypto_secretbox_KEYBYTES)
        throw new Error("bad key size");
    if (n.length !== crypto_secretbox_NONCEBYTES)
        throw new Error("bad nonce size");
}
function checkArrayTypes(...args) {
    for (let i = 0; i < args.length; i++) {
        if (!(args[i] instanceof Uint8Array))
            throw new TypeError("unexpected type, use Uint8Array");
    }
}
function randomBytes(n) {
    const b = new Uint8Array(n);
    randombytes(b, n);
    return b;
}
function scalarMult(n, p) {
    checkArrayTypes(n, p);
    if (n.length !== crypto_scalarmult_SCALARBYTES)
        throw new Error("bad n size");
    if (p.length !== crypto_scalarmult_BYTES)
        throw new Error("bad p size");
    const q = new Uint8Array(crypto_scalarmult_BYTES);
    crypto_scalarmult(q, n, p);
    return q;
}
function scalarMult_base(n) {
    checkArrayTypes(n);
    if (n.length !== crypto_scalarmult_SCALARBYTES)
        throw new Error("bad n size");
    const q = new Uint8Array(crypto_scalarmult_BYTES);
    crypto_scalarmult_base(q, n);
    return q;
}
function sign(msg, secretKey) {
    checkArrayTypes(msg, secretKey);
    if (secretKey.length !== crypto_sign_SECRETKEYBYTES)
        throw new Error("bad secret key size");
    const signedMsg = new Uint8Array(crypto_sign_BYTES + msg.length);
    crypto_sign(signedMsg, msg, msg.length, secretKey);
    return signedMsg;
}
function sign_detached(msg, secretKey) {
    const signedMsg = sign(msg, secretKey);
    const sig = new Uint8Array(crypto_sign_BYTES);
    for (let i = 0; i < sig.length; i++)
        sig[i] = signedMsg[i];
    return sig;
}
function sign_detached_verify(msg, sig, publicKey) {
    checkArrayTypes(msg, sig, publicKey);
    if (sig.length !== crypto_sign_BYTES)
        throw new Error("bad signature size");
    if (publicKey.length !== crypto_sign_PUBLICKEYBYTES)
        throw new Error("bad public key size");
    const sm = new Uint8Array(crypto_sign_BYTES + msg.length);
    const m = new Uint8Array(crypto_sign_BYTES + msg.length);
    let i;
    for (i = 0; i < crypto_sign_BYTES; i++)
        sm[i] = sig[i];
    for (i = 0; i < msg.length; i++)
        sm[i + crypto_sign_BYTES] = msg[i];
    return crypto_sign_open(m, sm, sm.length, publicKey) >= 0;
}
function crypto_sign_keyPair_fromSeed(seed) {
    checkArrayTypes(seed);
    if (seed.length !== crypto_sign_SEEDBYTES)
        throw new Error(`bad seed size: ${seed.length}`);
    const pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES);
    const sk = new Uint8Array(crypto_sign_SECRETKEYBYTES);
    for (let i = 0; i < 32; i++)
        sk[i] = seed[i];
    crypto_sign_keypair(pk, sk, true);
    return { publicKey: pk, secretKey: sk };
}
function hash$1(msg) {
    checkArrayTypes(msg);
    const h = new Uint8Array(crypto_hash_BYTES);
    crypto_hash(h, msg, msg.length);
    return h;
}
function setPRNG(fn) {
    randombytes = fn;
}
function sign_ed25519_pk_to_curve25519(ed25519_pk) {
    const ge_a = [gf(), gf(), gf(), gf()];
    const x = gf();
    const one_minus_y = gf();
    const x25519_pk = new Uint8Array(32);
    if (unpackneg(ge_a, ed25519_pk)) {
        throw Error("invalid public key");
    }
    set25519(one_minus_y, gf1);
    Z(one_minus_y, one_minus_y, ge_a[1]);
    set25519(x, gf1);
    A(x, x, ge_a[1]);
    inv25519(one_minus_y, one_minus_y);
    M(x, x, one_minus_y);
    pack25519(x25519_pk, x);
    return x25519_pk;
}
function secretbox(msg, nonce, key) {
    checkArrayTypes(msg, nonce, key);
    checkLengths(key, nonce);
    var m = new Uint8Array(crypto_secretbox_ZEROBYTES + msg.length);
    var c = new Uint8Array(m.length);
    for (var i = 0; i < msg.length; i++)
        m[i + crypto_secretbox_ZEROBYTES] = msg[i];
    crypto_secretbox(c, m, m.length, nonce, key);
    return c.subarray(crypto_secretbox_BOXZEROBYTES);
}
function secretbox_open(box, nonce, key) {
    checkArrayTypes(box, nonce, key);
    checkLengths(key, nonce);
    var c = new Uint8Array(crypto_secretbox_BOXZEROBYTES + box.length);
    var m = new Uint8Array(c.length);
    for (var i = 0; i < box.length; i++)
        c[i + crypto_secretbox_BOXZEROBYTES] = box[i];
    if (c.length < 32)
        return undefined;
    if (crypto_secretbox_open(m, c, c.length, nonce, key) !== 0)
        return undefined;
    return m.subarray(crypto_secretbox_ZEROBYTES);
}
/**
 * Reduce a scalar "s" to "s mod L".  The input can be up to 64 bytes long.
 */
function crypto_core_ed25519_scalar_reduce(x) {
    const len = x.length;
    const z = new Float64Array(64);
    for (let i = 0; i < len; i++)
        z[i] = x[i];
    const o = new Uint8Array(32);
    modL(o, z);
    return o;
}
function crypto_core_ed25519_scalar_sub(x, y) {
    const z = new Float64Array(64);
    for (let i = 0; i < 32; i++) {
        z[i] = x[i] - y[i];
    }
    const o = new Uint8Array(32);
    modL(o, z);
    return o;
}
function crypto_edx25519_private_key_create() {
    const seed = new Uint8Array(32);
    randombytes(seed, 32);
    return crypto_edx25519_private_key_create_from_seed(seed);
}
function crypto_edx25519_private_key_create_from_seed(seed) {
    const pk = hash$1(seed);
    pk[0] &= 248;
    pk[31] &= 127;
    pk[31] |= 64;
    return pk;
}
function crypto_edx25519_get_public(priv) {
    return crypto_scalarmult_ed25519_base_noclamp(priv.subarray(0, 32));
}
function crypto_edx25519_sign_detached(m, skx, pkx) {
    const n = m.length;
    const h = new Uint8Array(64);
    const r = new Uint8Array(64);
    let i, j;
    const x = new Float64Array(64);
    const p = [gf(), gf(), gf(), gf()];
    const sm = new Uint8Array(n + 64);
    for (i = 0; i < n; i++)
        sm[64 + i] = m[i];
    for (i = 0; i < 32; i++)
        sm[32 + i] = skx[32 + i];
    crypto_hash(r, sm.subarray(32), n + 32);
    reduce(r);
    scalarbase(p, r);
    pack(sm, p);
    for (i = 32; i < 64; i++)
        sm[i] = pkx[i - 32];
    crypto_hash(h, sm, n + 64);
    reduce(h);
    for (i = 0; i < 64; i++)
        x[i] = 0;
    for (i = 0; i < 32; i++)
        x[i] = r[i];
    for (i = 0; i < 32; i++) {
        for (j = 0; j < 32; j++) {
            x[i + j] += h[i] * skx[j];
        }
    }
    modL(sm.subarray(32), x);
    return sm.subarray(0, 64);
}
function crypto_edx25519_sign_detached_verify(msg, sig, publicKey) {
    checkArrayTypes(msg, sig, publicKey);
    if (sig.length !== crypto_sign_BYTES)
        throw new Error("bad signature size");
    if (publicKey.length !== crypto_sign_PUBLICKEYBYTES)
        throw new Error("bad public key size");
    const sm = new Uint8Array(crypto_sign_BYTES + msg.length);
    const m = new Uint8Array(crypto_sign_BYTES + msg.length);
    let i;
    for (i = 0; i < crypto_sign_BYTES; i++)
        sm[i] = sig[i];
    for (i = 0; i < msg.length; i++)
        sm[i + crypto_sign_BYTES] = msg[i];
    return crypto_sign_open(m, sm, sm.length, publicKey) >= 0;
}

/*
 This file is part of GNU Taler
 (C) 2021 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
function initNodePrng() {
    // Initialize PRNG if environment provides CSPRNG.
    // If not, methods calling randombytes will throw.
    if (cr__default["default"] && cr__default["default"].randomBytes) {
        setPRNG(function (x, n) {
            const v = cr__default["default"].randomBytes(n);
            for (let i = 0; i < n; i++)
                x[i] = v[i];
            for (let i = 0; i < v.length; i++)
                v[i] = 0;
        });
    }
}

/*
  This file is part of GNU Taler
  Copyright (C) 2012-2020 Taler Systems SA

  GNU Taler is free software: you can redistribute it and/or modify it
  under the terms of the GNU Lesser General Public License as published
  by the Free Software Foundation, either version 3 of the License,
  or (at your option) any later version.

  GNU 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

  SPDX-License-Identifier: LGPL3.0-or-later

  Note: the LGPL does not apply to all components of GNU Taler,
  but it does apply to this file.
 */
var TalerErrorCode;
(function (TalerErrorCode) {
    /**
     * Special code to indicate success (no error).
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["NONE"] = 0] = "NONE";
    /**
     * A non-integer error code was returned in the JSON response.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["INVALID"] = 1] = "INVALID";
    /**
     * An internal failure happened on the client side.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_CLIENT_INTERNAL_ERROR"] = 2] = "GENERIC_CLIENT_INTERNAL_ERROR";
    /**
     * The response we got from the server was not even in JSON format.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_INVALID_RESPONSE"] = 10] = "GENERIC_INVALID_RESPONSE";
    /**
     * An operation timed out.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_TIMEOUT"] = 11] = "GENERIC_TIMEOUT";
    /**
     * The version string given does not follow the expected CURRENT:REVISION:AGE Format.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_VERSION_MALFORMED"] = 12] = "GENERIC_VERSION_MALFORMED";
    /**
     * The service responded with a reply that was in JSON but did not satsify the protocol. Note that invalid cryptographic signatures should have signature-specific error codes.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_REPLY_MALFORMED"] = 13] = "GENERIC_REPLY_MALFORMED";
    /**
     * There is an error in the client-side configuration, for example the base URL specified is malformed.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_CONFIGURATION_INVALID"] = 14] = "GENERIC_CONFIGURATION_INVALID";
    /**
     * The client made a request to a service, but received an error response it does not know how to handle.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_UNEXPECTED_REQUEST_ERROR"] = 15] = "GENERIC_UNEXPECTED_REQUEST_ERROR";
    /**
     * The HTTP method used is invalid for this endpoint.
     * Returned with an HTTP status code of #MHD_HTTP_METHOD_NOT_ALLOWED (405).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_METHOD_INVALID"] = 20] = "GENERIC_METHOD_INVALID";
    /**
     * There is no endpoint defined for the URL provided by the client.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_ENDPOINT_UNKNOWN"] = 21] = "GENERIC_ENDPOINT_UNKNOWN";
    /**
     * The JSON in the client's request was malformed (generic parse error).
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_JSON_INVALID"] = 22] = "GENERIC_JSON_INVALID";
    /**
     * Some of the HTTP headers provided by the client caused the server to not be able to handle the request.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_HTTP_HEADERS_MALFORMED"] = 23] = "GENERIC_HTTP_HEADERS_MALFORMED";
    /**
     * The payto:// URI provided by the client is malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_PAYTO_URI_MALFORMED"] = 24] = "GENERIC_PAYTO_URI_MALFORMED";
    /**
     * A required parameter in the request was missing.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_PARAMETER_MISSING"] = 25] = "GENERIC_PARAMETER_MISSING";
    /**
     * A parameter in the request was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_PARAMETER_MALFORMED"] = 26] = "GENERIC_PARAMETER_MALFORMED";
    /**
     * The currencies involved in the operation do not match.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_CURRENCY_MISMATCH"] = 30] = "GENERIC_CURRENCY_MISMATCH";
    /**
     * The URI is longer than the longest URI the HTTP server is willing to parse.
     * Returned with an HTTP status code of #MHD_HTTP_URI_TOO_LONG (414).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_URI_TOO_LONG"] = 31] = "GENERIC_URI_TOO_LONG";
    /**
     * The body is too large to be permissible for the endpoint.
     * Returned with an HTTP status code of #MHD_HTTP_PAYLOAD_TOO_LARGE (413).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_UPLOAD_EXCEEDS_LIMIT"] = 32] = "GENERIC_UPLOAD_EXCEEDS_LIMIT";
    /**
     * The service failed initialize its connection to the database.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_DB_SETUP_FAILED"] = 50] = "GENERIC_DB_SETUP_FAILED";
    /**
     * The service encountered an error event to just start the database transaction.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_DB_START_FAILED"] = 51] = "GENERIC_DB_START_FAILED";
    /**
     * The service failed to store information in its database.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_DB_STORE_FAILED"] = 52] = "GENERIC_DB_STORE_FAILED";
    /**
     * The service failed to fetch information from its database.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_DB_FETCH_FAILED"] = 53] = "GENERIC_DB_FETCH_FAILED";
    /**
     * The service encountered an error event to commit the database transaction (hard, unrecoverable error).
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_DB_COMMIT_FAILED"] = 54] = "GENERIC_DB_COMMIT_FAILED";
    /**
     * The service encountered an error event to commit the database transaction, even after repeatedly retrying it there was always a conflicting transaction. (This indicates a repeated serialization error; should only happen if some client maliciously tries to create conflicting concurrent transactions.)
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_DB_SOFT_FAILURE"] = 55] = "GENERIC_DB_SOFT_FAILURE";
    /**
     * The service's database is inconsistent and violates service-internal invariants.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_DB_INVARIANT_FAILURE"] = 56] = "GENERIC_DB_INVARIANT_FAILURE";
    /**
     * The HTTP server experienced an internal invariant failure (bug).
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_INTERNAL_INVARIANT_FAILURE"] = 60] = "GENERIC_INTERNAL_INVARIANT_FAILURE";
    /**
     * The service could not compute a cryptographic hash over some JSON value.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_FAILED_COMPUTE_JSON_HASH"] = 61] = "GENERIC_FAILED_COMPUTE_JSON_HASH";
    /**
     * The service could not compute an amount.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_FAILED_COMPUTE_AMOUNT"] = 62] = "GENERIC_FAILED_COMPUTE_AMOUNT";
    /**
     * The HTTP server had insufficient memory to parse the request.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_PARSER_OUT_OF_MEMORY"] = 70] = "GENERIC_PARSER_OUT_OF_MEMORY";
    /**
     * The HTTP server failed to allocate memory.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_ALLOCATION_FAILURE"] = 71] = "GENERIC_ALLOCATION_FAILURE";
    /**
     * The HTTP server failed to allocate memory for building JSON reply.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_JSON_ALLOCATION_FAILURE"] = 72] = "GENERIC_JSON_ALLOCATION_FAILURE";
    /**
     * The HTTP server failed to allocate memory for making a CURL request.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_CURL_ALLOCATION_FAILURE"] = 73] = "GENERIC_CURL_ALLOCATION_FAILURE";
    /**
     * The backend could not locate a required template to generate an HTML reply.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_ACCEPTABLE (406).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_FAILED_TO_LOAD_TEMPLATE"] = 74] = "GENERIC_FAILED_TO_LOAD_TEMPLATE";
    /**
     * The backend could not expand the template to generate an HTML reply.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["GENERIC_FAILED_TO_EXPAND_TEMPLATE"] = 75] = "GENERIC_FAILED_TO_EXPAND_TEMPLATE";
    /**
     * Exchange is badly configured and thus cannot operate.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_BAD_CONFIGURATION"] = 1000] = "EXCHANGE_GENERIC_BAD_CONFIGURATION";
    /**
     * Operation specified unknown for this endpoint.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_OPERATION_UNKNOWN"] = 1001] = "EXCHANGE_GENERIC_OPERATION_UNKNOWN";
    /**
     * The number of segments included in the URI does not match the number of segments expected by the endpoint.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS"] = 1002] = "EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS";
    /**
     * The same coin was already used with a different denomination previously.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY"] = 1003] = "EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY";
    /**
     * The public key of given to a "/coins/" endpoint of the exchange was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_COINS_INVALID_COIN_PUB"] = 1004] = "EXCHANGE_GENERIC_COINS_INVALID_COIN_PUB";
    /**
     * The exchange is not aware of the denomination key the wallet requested for the operation.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN"] = 1005] = "EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN";
    /**
     * The signature of the denomination key over the coin is not valid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DENOMINATION_SIGNATURE_INVALID"] = 1006] = "EXCHANGE_DENOMINATION_SIGNATURE_INVALID";
    /**
     * The exchange failed to perform the operation as it could not find the private keys. This is a problem with the exchange setup, not with the client's request.
     * Returned with an HTTP status code of #MHD_HTTP_SERVICE_UNAVAILABLE (503).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_KEYS_MISSING"] = 1007] = "EXCHANGE_GENERIC_KEYS_MISSING";
    /**
     * Validity period of the denomination lies in the future.
     * Returned with an HTTP status code of #MHD_HTTP_PRECONDITION_FAILED (412).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE"] = 1008] = "EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE";
    /**
     * Denomination key of the coin is past its expiration time for the requested operation.
     * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_DENOMINATION_EXPIRED"] = 1009] = "EXCHANGE_GENERIC_DENOMINATION_EXPIRED";
    /**
     * Denomination key of the coin has been revoked.
     * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_DENOMINATION_REVOKED"] = 1010] = "EXCHANGE_GENERIC_DENOMINATION_REVOKED";
    /**
     * An operation where the exchange interacted with a security module timed out.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_SECMOD_TIMEOUT"] = 1011] = "EXCHANGE_GENERIC_SECMOD_TIMEOUT";
    /**
     * The respective coin did not have sufficient residual value for the operation.  The "history" in this response provides the "residual_value" of the coin, which may be less than its "original_value".
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_INSUFFICIENT_FUNDS"] = 1012] = "EXCHANGE_GENERIC_INSUFFICIENT_FUNDS";
    /**
     * The exchange had an internal error reconstructing the transaction history of the coin that was being processed.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_COIN_HISTORY_COMPUTATION_FAILED"] = 1013] = "EXCHANGE_GENERIC_COIN_HISTORY_COMPUTATION_FAILED";
    /**
     * The exchange failed to obtain the transaction history of the given coin from the database while generating an insufficient funds errors.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS"] = 1014] = "EXCHANGE_GENERIC_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS";
    /**
     * The same coin was already used with a different age hash previously.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH"] = 1015] = "EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH";
    /**
     * The requested operation is not valid for the cipher used by the selected denomination.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_INVALID_DENOMINATION_CIPHER_FOR_OPERATION"] = 1016] = "EXCHANGE_GENERIC_INVALID_DENOMINATION_CIPHER_FOR_OPERATION";
    /**
     * The provided arguments for the operation use inconsistent ciphers.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_CIPHER_MISMATCH"] = 1017] = "EXCHANGE_GENERIC_CIPHER_MISMATCH";
    /**
     * The number of denominations specified in the request exceeds the limit of the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE"] = 1018] = "EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE";
    /**
     * The reserve public key was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_RESERVE_PUB_MALFORMED"] = 1019] = "EXCHANGE_GENERIC_RESERVE_PUB_MALFORMED";
    /**
     * The time at the server is too far off from the time specified in the request. Most likely the client system time is wrong.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_CLOCK_SKEW"] = 1020] = "EXCHANGE_GENERIC_CLOCK_SKEW";
    /**
     * The specified amount for the coin is higher than the value of the denomination of the coin.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE"] = 1021] = "EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE";
    /**
     * The exchange was not properly configured with global fees.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_GLOBAL_FEES_MISSING"] = 1022] = "EXCHANGE_GENERIC_GLOBAL_FEES_MISSING";
    /**
     * The exchange was not properly configured with wire fees.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_WIRE_FEES_MISSING"] = 1023] = "EXCHANGE_GENERIC_WIRE_FEES_MISSING";
    /**
     * The purse public key was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_PURSE_PUB_MALFORMED"] = 1024] = "EXCHANGE_GENERIC_PURSE_PUB_MALFORMED";
    /**
     * The purse is unknown.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_PURSE_UNKNOWN"] = 1025] = "EXCHANGE_GENERIC_PURSE_UNKNOWN";
    /**
     * The purse has expired.
     * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_PURSE_EXPIRED"] = 1026] = "EXCHANGE_GENERIC_PURSE_EXPIRED";
    /**
     * The exchange has no information about the "reserve_pub" that was given.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_RESERVE_UNKNOWN"] = 1027] = "EXCHANGE_GENERIC_RESERVE_UNKNOWN";
    /**
     * The exchange is not allowed to proceed with the operation until the client has satisfied a KYC check.
     * Returned with an HTTP status code of #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS (451).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_GENERIC_KYC_REQUIRED"] = 1028] = "EXCHANGE_GENERIC_KYC_REQUIRED";
    /**
     * Inconsistency between provided age commitment and attest: either none or both must be provided
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_DEPOSIT_COIN_CONFLICTING_ATTEST_VS_AGE_COMMITMENT"] = 1029] = "EXCHANGE_PURSE_DEPOSIT_COIN_CONFLICTING_ATTEST_VS_AGE_COMMITMENT";
    /**
     * The provided attestation for the minimum age couldn't be verified by the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_DEPOSIT_COIN_AGE_ATTESTATION_FAILURE"] = 1030] = "EXCHANGE_PURSE_DEPOSIT_COIN_AGE_ATTESTATION_FAILURE";
    /**
     * The exchange did not find information about the specified transaction in the database.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSITS_GET_NOT_FOUND"] = 1100] = "EXCHANGE_DEPOSITS_GET_NOT_FOUND";
    /**
     * The wire hash of given to a "/deposits/" handler was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE"] = 1101] = "EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE";
    /**
     * The merchant key of given to a "/deposits/" handler was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB"] = 1102] = "EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB";
    /**
     * The hash of the contract terms given to a "/deposits/" handler was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS"] = 1103] = "EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS";
    /**
     * The coin public key of given to a "/deposits/" handler was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB"] = 1104] = "EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB";
    /**
     * The signature returned by the exchange in a /deposits/ request was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE"] = 1105] = "EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE";
    /**
     * The signature of the merchant is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID"] = 1106] = "EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID";
    /**
     * The given reserve does not have sufficient funds to admit the requested withdraw operation at this time.  The response includes the current "balance" of the reserve as well as the transaction "history" that lead to this balance.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS"] = 1150] = "EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS";
    /**
     * The amount to withdraw together with the fee exceeds the numeric range for Taler amounts.  This is not a client failure, as the coin value and fees come from the exchange's configuration.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW"] = 1152] = "EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW";
    /**
     * The exchange failed to create the signature using the denomination key.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_WITHDRAW_SIGNATURE_FAILED"] = 1153] = "EXCHANGE_WITHDRAW_SIGNATURE_FAILED";
    /**
     * The signature of the reserve is not valid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID"] = 1154] = "EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID";
    /**
     * When computing the reserve history, we ended up with a negative overall balance, which should be impossible.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_WITHDRAW_HISTORY_ERROR_INSUFFICIENT_FUNDS"] = 1155] = "EXCHANGE_WITHDRAW_HISTORY_ERROR_INSUFFICIENT_FUNDS";
    /**
     * Withdraw period of the coin to be withdrawn is in the past.
     * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_WITHDRAW_DENOMINATION_KEY_LOST"] = 1158] = "EXCHANGE_WITHDRAW_DENOMINATION_KEY_LOST";
    /**
     * The client failed to unblind the blind signature.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_WITHDRAW_UNBLIND_FAILURE"] = 1159] = "EXCHANGE_WITHDRAW_UNBLIND_FAILURE";
    /**
     * The client re-used a withdraw nonce, which is not allowed.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_WITHDRAW_NONCE_REUSE"] = 1160] = "EXCHANGE_WITHDRAW_NONCE_REUSE";
    /**
     * The batch withdraw included a planchet that was already withdrawn. This is not allowed.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_WITHDRAW_BATCH_IDEMPOTENT_PLANCHET"] = 1175] = "EXCHANGE_WITHDRAW_BATCH_IDEMPOTENT_PLANCHET";
    /**
     * The signature made by the coin over the deposit permission is not valid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID"] = 1205] = "EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID";
    /**
     * The same coin was already deposited for the same merchant and contract with other details.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT"] = 1206] = "EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT";
    /**
     * The stated value of the coin after the deposit fee is subtracted would be negative.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE"] = 1207] = "EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE";
    /**
     * The stated refund deadline is after the wire deadline.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE"] = 1208] = "EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE";
    /**
     * The stated wire deadline is "never", which makes no sense.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER"] = 1209] = "EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER";
    /**
     * The exchange failed to canonicalize and hash the given wire format. For example, the merchant failed to provide the "salt" or a valid payto:// URI in the wire details.  Note that while the exchange will do some basic sanity checking on the wire details, it cannot warrant that the banking system will ultimately be able to route to the specified address, even if this check passed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSIT_INVALID_WIRE_FORMAT_JSON"] = 1210] = "EXCHANGE_DEPOSIT_INVALID_WIRE_FORMAT_JSON";
    /**
     * The hash of the given wire address does not match the wire hash specified in the proposal data.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSIT_INVALID_WIRE_FORMAT_CONTRACT_HASH_CONFLICT"] = 1211] = "EXCHANGE_DEPOSIT_INVALID_WIRE_FORMAT_CONTRACT_HASH_CONFLICT";
    /**
     * The signature provided by the exchange is not valid.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE"] = 1221] = "EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE";
    /**
     * The deposited amount is smaller than the deposit fee, which would result in a negative contribution.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT"] = 1222] = "EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT";
    /**
     * The reserve balance, status or history was requested for a reserve which is not known to the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RESERVES_STATUS_UNKNOWN"] = 1250] = "EXCHANGE_RESERVES_STATUS_UNKNOWN";
    /**
     * The reserve status was requested with a bad signature.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RESERVES_STATUS_BAD_SIGNATURE"] = 1251] = "EXCHANGE_RESERVES_STATUS_BAD_SIGNATURE";
    /**
     * The reserve history was requested with a bad signature.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RESERVES_HISTORY_BAD_SIGNATURE"] = 1252] = "EXCHANGE_RESERVES_HISTORY_BAD_SIGNATURE";
    /**
     * The exchange encountered melt fees exceeding the melted coin's contribution.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MELT_FEES_EXCEED_CONTRIBUTION"] = 1302] = "EXCHANGE_MELT_FEES_EXCEED_CONTRIBUTION";
    /**
     * The signature made with the coin to be melted is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MELT_COIN_SIGNATURE_INVALID"] = 1303] = "EXCHANGE_MELT_COIN_SIGNATURE_INVALID";
    /**
     * The denomination of the given coin has past its expiration date and it is also not a valid zombie (that is, was not refreshed with the fresh coin being subjected to recoup).
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE"] = 1305] = "EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE";
    /**
     * The signature returned by the exchange in a melt request was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE"] = 1306] = "EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE";
    /**
     * The provided transfer keys do not match up with the original commitment.  Information about the original commitment is included in the response.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFRESHES_REVEAL_COMMITMENT_VIOLATION"] = 1353] = "EXCHANGE_REFRESHES_REVEAL_COMMITMENT_VIOLATION";
    /**
     * Failed to produce the blinded signatures over the coins to be returned.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFRESHES_REVEAL_SIGNING_ERROR"] = 1354] = "EXCHANGE_REFRESHES_REVEAL_SIGNING_ERROR";
    /**
     * The exchange is unaware of the refresh session specified in the request.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN"] = 1355] = "EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN";
    /**
     * The size of the cut-and-choose dimension of the private transfer keys request does not match #TALER_CNC_KAPPA - 1.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFRESHES_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID"] = 1356] = "EXCHANGE_REFRESHES_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID";
    /**
     * The number of envelopes given does not match the number of denomination keys given.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFRESHES_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISMATCH"] = 1358] = "EXCHANGE_REFRESHES_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISMATCH";
    /**
     * The exchange encountered a numeric overflow totaling up the cost for the refresh operation.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW"] = 1359] = "EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW";
    /**
     * The exchange's cost calculation shows that the melt amount is below the costs of the transaction.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFRESHES_REVEAL_AMOUNT_INSUFFICIENT"] = 1360] = "EXCHANGE_REFRESHES_REVEAL_AMOUNT_INSUFFICIENT";
    /**
     * The signature made with the coin over the link data is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFRESHES_REVEAL_LINK_SIGNATURE_INVALID"] = 1361] = "EXCHANGE_REFRESHES_REVEAL_LINK_SIGNATURE_INVALID";
    /**
     * The refresh session hash given to a /refreshes/ handler was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFRESHES_REVEAL_INVALID_RCH"] = 1362] = "EXCHANGE_REFRESHES_REVEAL_INVALID_RCH";
    /**
     * Operation specified invalid for this endpoint.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFRESHES_REVEAL_OPERATION_INVALID"] = 1363] = "EXCHANGE_REFRESHES_REVEAL_OPERATION_INVALID";
    /**
     * The client provided age commitment data, but age restriction is not supported on this server.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_NOT_SUPPORTED"] = 1364] = "EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_NOT_SUPPORTED";
    /**
     * The client provided invalid age commitment data: missing, not an array, or  array of invalid size.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID"] = 1365] = "EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID";
    /**
     * The coin specified in the link request is unknown to the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_LINK_COIN_UNKNOWN"] = 1400] = "EXCHANGE_LINK_COIN_UNKNOWN";
    /**
     * The public key of given to a /transfers/ handler was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_TRANSFERS_GET_WTID_MALFORMED"] = 1450] = "EXCHANGE_TRANSFERS_GET_WTID_MALFORMED";
    /**
     * The exchange did not find information about the specified wire transfer identifier in the database.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_TRANSFERS_GET_WTID_NOT_FOUND"] = 1451] = "EXCHANGE_TRANSFERS_GET_WTID_NOT_FOUND";
    /**
     * The exchange did not find information about the wire transfer fees it charged.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_TRANSFERS_GET_WIRE_FEE_NOT_FOUND"] = 1452] = "EXCHANGE_TRANSFERS_GET_WIRE_FEE_NOT_FOUND";
    /**
     * The exchange found a wire fee that was above the total transfer value (and thus could not have been charged).
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_TRANSFERS_GET_WIRE_FEE_INCONSISTENT"] = 1453] = "EXCHANGE_TRANSFERS_GET_WIRE_FEE_INCONSISTENT";
    /**
     * The wait target of the URL was not in the set of expected values.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSES_INVALID_WAIT_TARGET"] = 1475] = "EXCHANGE_PURSES_INVALID_WAIT_TARGET";
    /**
     * The signature on the purse status returned by the exchange was invalid.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE"] = 1476] = "EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE";
    /**
     * The exchange knows literally nothing about the coin we were asked to refund. But without a transaction history, we cannot issue a refund. This is kind-of OK, the owner should just refresh it directly without executing the refund.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFUND_COIN_NOT_FOUND"] = 1500] = "EXCHANGE_REFUND_COIN_NOT_FOUND";
    /**
     * We could not process the refund request as the coin's transaction history does not permit the requested refund because then refunds would exceed the deposit amount.  The "history" in the response proves this.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT"] = 1501] = "EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT";
    /**
     * The exchange knows about the coin we were asked to refund, but not about the specific /deposit operation.  Hence, we cannot issue a refund (as we do not know if this merchant public key is authorized to do a refund).
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFUND_DEPOSIT_NOT_FOUND"] = 1502] = "EXCHANGE_REFUND_DEPOSIT_NOT_FOUND";
    /**
     * The exchange can no longer refund the customer/coin as the money was already transferred (paid out) to the merchant. (It should be past the refund deadline.)
     * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFUND_MERCHANT_ALREADY_PAID"] = 1503] = "EXCHANGE_REFUND_MERCHANT_ALREADY_PAID";
    /**
     * The refund fee specified for the request is lower than the refund fee charged by the exchange for the given denomination key of the refunded coin.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFUND_FEE_TOO_LOW"] = 1504] = "EXCHANGE_REFUND_FEE_TOO_LOW";
    /**
     * The refunded amount is smaller than the refund fee, which would result in a negative refund.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFUND_FEE_ABOVE_AMOUNT"] = 1505] = "EXCHANGE_REFUND_FEE_ABOVE_AMOUNT";
    /**
     * The signature of the merchant is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFUND_MERCHANT_SIGNATURE_INVALID"] = 1506] = "EXCHANGE_REFUND_MERCHANT_SIGNATURE_INVALID";
    /**
     * Merchant backend failed to create the refund confirmation signature.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFUND_MERCHANT_SIGNING_FAILED"] = 1507] = "EXCHANGE_REFUND_MERCHANT_SIGNING_FAILED";
    /**
     * The signature returned by the exchange in a refund request was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE"] = 1508] = "EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE";
    /**
     * The failure proof returned by the exchange is incorrect.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE"] = 1509] = "EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE";
    /**
     * Conflicting refund granted before with different amount but same refund transaction ID.
     * Returned with an HTTP status code of #MHD_HTTP_FAILED_DEPENDENCY (424).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_REFUND_INCONSISTENT_AMOUNT"] = 1510] = "EXCHANGE_REFUND_INCONSISTENT_AMOUNT";
    /**
     * The given coin signature is invalid for the request.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RECOUP_SIGNATURE_INVALID"] = 1550] = "EXCHANGE_RECOUP_SIGNATURE_INVALID";
    /**
     * The exchange could not find the corresponding withdraw operation. The request is denied.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RECOUP_WITHDRAW_NOT_FOUND"] = 1551] = "EXCHANGE_RECOUP_WITHDRAW_NOT_FOUND";
    /**
     * The coin's remaining balance is zero.  The request is denied.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RECOUP_COIN_BALANCE_ZERO"] = 1552] = "EXCHANGE_RECOUP_COIN_BALANCE_ZERO";
    /**
     * The exchange failed to reproduce the coin's blinding.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RECOUP_BLINDING_FAILED"] = 1553] = "EXCHANGE_RECOUP_BLINDING_FAILED";
    /**
     * The coin's remaining balance is zero.  The request is denied.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RECOUP_COIN_BALANCE_NEGATIVE"] = 1554] = "EXCHANGE_RECOUP_COIN_BALANCE_NEGATIVE";
    /**
     * The coin's denomination has not been revoked yet.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RECOUP_NOT_ELIGIBLE"] = 1555] = "EXCHANGE_RECOUP_NOT_ELIGIBLE";
    /**
     * The given coin signature is invalid for the request.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RECOUP_REFRESH_SIGNATURE_INVALID"] = 1575] = "EXCHANGE_RECOUP_REFRESH_SIGNATURE_INVALID";
    /**
     * The exchange could not find the corresponding melt operation. The request is denied.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RECOUP_REFRESH_MELT_NOT_FOUND"] = 1576] = "EXCHANGE_RECOUP_REFRESH_MELT_NOT_FOUND";
    /**
     * The exchange failed to reproduce the coin's blinding.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RECOUP_REFRESH_BLINDING_FAILED"] = 1578] = "EXCHANGE_RECOUP_REFRESH_BLINDING_FAILED";
    /**
     * The coin's denomination has not been revoked yet.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RECOUP_REFRESH_NOT_ELIGIBLE"] = 1580] = "EXCHANGE_RECOUP_REFRESH_NOT_ELIGIBLE";
    /**
     * This exchange does not allow clients to request /keys for times other than the current (exchange) time.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KEYS_TIMETRAVEL_FORBIDDEN"] = 1600] = "EXCHANGE_KEYS_TIMETRAVEL_FORBIDDEN";
    /**
     * A signature in the server's response was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_WIRE_SIGNATURE_INVALID"] = 1650] = "EXCHANGE_WIRE_SIGNATURE_INVALID";
    /**
     * No bank accounts are enabled for the exchange. The administrator should enable-account using the taler-exchange-offline tool.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_WIRE_NO_ACCOUNTS_CONFIGURED"] = 1651] = "EXCHANGE_WIRE_NO_ACCOUNTS_CONFIGURED";
    /**
     * The payto:// URI stored in the exchange database for its bank account is malformed.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED"] = 1652] = "EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED";
    /**
     * No wire fees are configured for an enabled wire method of the exchange. The administrator must set the wire-fee using the taler-exchange-offline tool.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_WIRE_FEES_NOT_CONFIGURED"] = 1653] = "EXCHANGE_WIRE_FEES_NOT_CONFIGURED";
    /**
     * This purse was previously created with different meta data.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA"] = 1675] = "EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA";
    /**
     * This purse was previously merged with different meta data.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA"] = 1676] = "EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA";
    /**
     * The reserve has insufficient funds to create another purse.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS"] = 1677] = "EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS";
    /**
     * The purse fee specified for the request is lower than the purse fee charged by the exchange at this time.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RESERVES_PURSE_FEE_TOO_LOW"] = 1678] = "EXCHANGE_RESERVES_PURSE_FEE_TOO_LOW";
    /**
     * The exchange failed to talk to the process responsible for its private denomination keys.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE"] = 1700] = "EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE";
    /**
     * The response from the denomination key helper process was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DENOMINATION_HELPER_BUG"] = 1701] = "EXCHANGE_DENOMINATION_HELPER_BUG";
    /**
     * The helper refuses to sign with the key, because it is too early: the validity period has not yet started.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_DENOMINATION_HELPER_TOO_EARLY"] = 1702] = "EXCHANGE_DENOMINATION_HELPER_TOO_EARLY";
    /**
     * The signature of the exchange on the reply was invalid.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_DEPOSIT_EXCHANGE_SIGNATURE_INVALID"] = 1725] = "EXCHANGE_PURSE_DEPOSIT_EXCHANGE_SIGNATURE_INVALID";
    /**
     * The exchange failed to talk to the process responsible for its private signing keys.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE"] = 1750] = "EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE";
    /**
     * The response from the online signing key helper process was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_SIGNKEY_HELPER_BUG"] = 1751] = "EXCHANGE_SIGNKEY_HELPER_BUG";
    /**
     * The helper refuses to sign with the key, because it is too early: the validity period has not yet started.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_SIGNKEY_HELPER_TOO_EARLY"] = 1752] = "EXCHANGE_SIGNKEY_HELPER_TOO_EARLY";
    /**
     * The purse expiration time is in the past at the time of its creation.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RESERVES_PURSE_EXPIRATION_BEFORE_NOW"] = 1775] = "EXCHANGE_RESERVES_PURSE_EXPIRATION_BEFORE_NOW";
    /**
     * The purse expiration time is set to never, which is not allowed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RESERVES_PURSE_EXPIRATION_IS_NEVER"] = 1776] = "EXCHANGE_RESERVES_PURSE_EXPIRATION_IS_NEVER";
    /**
     * The signature affirming the merge of the purse is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID"] = 1777] = "EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID";
    /**
     * The signature by the reserve affirming the merge is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_RESERVES_RESERVE_MERGE_SIGNATURE_INVALID"] = 1778] = "EXCHANGE_RESERVES_RESERVE_MERGE_SIGNATURE_INVALID";
    /**
     * The auditor that was supposed to be disabled is unknown to this exchange.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_AUDITOR_NOT_FOUND"] = 1800] = "EXCHANGE_MANAGEMENT_AUDITOR_NOT_FOUND";
    /**
     * The exchange has a more recently signed conflicting instruction and is thus refusing the current change (replay detected).
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_AUDITOR_MORE_RECENT_PRESENT"] = 1801] = "EXCHANGE_MANAGEMENT_AUDITOR_MORE_RECENT_PRESENT";
    /**
     * The signature to add or enable the auditor does not validate.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_AUDITOR_ADD_SIGNATURE_INVALID"] = 1802] = "EXCHANGE_MANAGEMENT_AUDITOR_ADD_SIGNATURE_INVALID";
    /**
     * The signature to disable the auditor does not validate.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_AUDITOR_DEL_SIGNATURE_INVALID"] = 1803] = "EXCHANGE_MANAGEMENT_AUDITOR_DEL_SIGNATURE_INVALID";
    /**
     * The signature to revoke the denomination does not validate.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_DENOMINATION_REVOKE_SIGNATURE_INVALID"] = 1804] = "EXCHANGE_MANAGEMENT_DENOMINATION_REVOKE_SIGNATURE_INVALID";
    /**
     * The signature to revoke the online signing key does not validate.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_SIGNKEY_REVOKE_SIGNATURE_INVALID"] = 1805] = "EXCHANGE_MANAGEMENT_SIGNKEY_REVOKE_SIGNATURE_INVALID";
    /**
     * The exchange has a more recently signed conflicting instruction and is thus refusing the current change (replay detected).
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_WIRE_MORE_RECENT_PRESENT"] = 1806] = "EXCHANGE_MANAGEMENT_WIRE_MORE_RECENT_PRESENT";
    /**
     * The signingkey specified is unknown to the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_UNKNOWN"] = 1807] = "EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_UNKNOWN";
    /**
     * The signature to publish wire account does not validate.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_WIRE_DETAILS_SIGNATURE_INVALID"] = 1808] = "EXCHANGE_MANAGEMENT_WIRE_DETAILS_SIGNATURE_INVALID";
    /**
     * The signature to add the wire account does not validate.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_WIRE_ADD_SIGNATURE_INVALID"] = 1809] = "EXCHANGE_MANAGEMENT_WIRE_ADD_SIGNATURE_INVALID";
    /**
     * The signature to disable the wire account does not validate.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_WIRE_DEL_SIGNATURE_INVALID"] = 1810] = "EXCHANGE_MANAGEMENT_WIRE_DEL_SIGNATURE_INVALID";
    /**
     * The wire account to be disabled is unknown to the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_WIRE_NOT_FOUND"] = 1811] = "EXCHANGE_MANAGEMENT_WIRE_NOT_FOUND";
    /**
     * The signature to affirm wire fees does not validate.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_WIRE_FEE_SIGNATURE_INVALID"] = 1812] = "EXCHANGE_MANAGEMENT_WIRE_FEE_SIGNATURE_INVALID";
    /**
     * The signature conflicts with a previous signature affirming different fees.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_WIRE_FEE_MISMATCH"] = 1813] = "EXCHANGE_MANAGEMENT_WIRE_FEE_MISMATCH";
    /**
     * The signature affirming the denomination key is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_KEYS_DENOMKEY_ADD_SIGNATURE_INVALID"] = 1814] = "EXCHANGE_MANAGEMENT_KEYS_DENOMKEY_ADD_SIGNATURE_INVALID";
    /**
     * The signature affirming the signing key is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_ADD_SIGNATURE_INVALID"] = 1815] = "EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_ADD_SIGNATURE_INVALID";
    /**
     * The signature conflicts with a previous signature affirming different fees.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_GLOBAL_FEE_MISMATCH"] = 1816] = "EXCHANGE_MANAGEMENT_GLOBAL_FEE_MISMATCH";
    /**
     * The signature affirming the fee structure is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_GLOBAL_FEE_SIGNATURE_INVALID"] = 1817] = "EXCHANGE_MANAGEMENT_GLOBAL_FEE_SIGNATURE_INVALID";
    /**
     * The signature affirming the profit drain is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MANAGEMENT_DRAIN_PROFITS_SIGNATURE_INVALID"] = 1818] = "EXCHANGE_MANAGEMENT_DRAIN_PROFITS_SIGNATURE_INVALID";
    /**
     * The purse was previously created with different meta data.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA"] = 1850] = "EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA";
    /**
     * The purse was previously created with a different contract.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_CREATE_CONFLICTING_CONTRACT_STORED"] = 1851] = "EXCHANGE_PURSE_CREATE_CONFLICTING_CONTRACT_STORED";
    /**
     * A coin signature for a deposit into the purse is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_CREATE_COIN_SIGNATURE_INVALID"] = 1852] = "EXCHANGE_PURSE_CREATE_COIN_SIGNATURE_INVALID";
    /**
     * The purse expiration time is in the past.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_CREATE_EXPIRATION_BEFORE_NOW"] = 1853] = "EXCHANGE_PURSE_CREATE_EXPIRATION_BEFORE_NOW";
    /**
     * The purse expiration time is "never".
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_CREATE_EXPIRATION_IS_NEVER"] = 1854] = "EXCHANGE_PURSE_CREATE_EXPIRATION_IS_NEVER";
    /**
     * The purse signature over the purse meta data is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID"] = 1855] = "EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID";
    /**
     * The signature over the encrypted contract is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID"] = 1856] = "EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID";
    /**
     * The signature from the exchange over the confirmation is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID"] = 1857] = "EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID";
    /**
     * The coin was previously deposited with different meta data.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA"] = 1858] = "EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA";
    /**
     * The encrypted contract was previously uploaded with different meta data.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA"] = 1859] = "EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA";
    /**
     * The deposited amount is less than the purse fee.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_CREATE_PURSE_NEGATIVE_VALUE_AFTER_FEE"] = 1860] = "EXCHANGE_CREATE_PURSE_NEGATIVE_VALUE_AFTER_FEE";
    /**
     * The signature using the merge key is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_MERGE_INVALID_MERGE_SIGNATURE"] = 1876] = "EXCHANGE_PURSE_MERGE_INVALID_MERGE_SIGNATURE";
    /**
     * The signature using the reserve key is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_MERGE_INVALID_RESERVE_SIGNATURE"] = 1877] = "EXCHANGE_PURSE_MERGE_INVALID_RESERVE_SIGNATURE";
    /**
     * The targeted purse is not yet full and thus cannot be merged. Retrying the request later may succeed.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_NOT_FULL"] = 1878] = "EXCHANGE_PURSE_NOT_FULL";
    /**
     * The signature from the exchange over the confirmation is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID"] = 1879] = "EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID";
    /**
     * The exchange of the target account is not a partner of this exchange.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_MERGE_PURSE_PARTNER_UNKNOWN"] = 1880] = "EXCHANGE_MERGE_PURSE_PARTNER_UNKNOWN";
    /**
     * The auditor signature over the denomination meta data is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_AUDITORS_AUDITOR_SIGNATURE_INVALID"] = 1900] = "EXCHANGE_AUDITORS_AUDITOR_SIGNATURE_INVALID";
    /**
     * The auditor that was specified is unknown to this exchange.
     * Returned with an HTTP status code of #MHD_HTTP_PRECONDITION_FAILED (412).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_AUDITORS_AUDITOR_UNKNOWN"] = 1901] = "EXCHANGE_AUDITORS_AUDITOR_UNKNOWN";
    /**
     * The auditor that was specified is no longer used by this exchange.
     * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_AUDITORS_AUDITOR_INACTIVE"] = 1902] = "EXCHANGE_AUDITORS_AUDITOR_INACTIVE";
    /**
     * The signature affirming the wallet's KYC request was invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_WALLET_SIGNATURE_INVALID"] = 1925] = "EXCHANGE_KYC_WALLET_SIGNATURE_INVALID";
    /**
     * The exchange received an unexpected malformed response from its KYC backend.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE"] = 1926] = "EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE";
    /**
     * The backend signaled an unexpected failure.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_PROOF_BACKEND_ERROR"] = 1927] = "EXCHANGE_KYC_PROOF_BACKEND_ERROR";
    /**
     * The backend signaled an authorization failure.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_PROOF_BACKEND_AUTHORIZATION_FAILED"] = 1928] = "EXCHANGE_KYC_PROOF_BACKEND_AUTHORIZATION_FAILED";
    /**
     * The exchange is unaware of having made an the authorization request.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN"] = 1929] = "EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN";
    /**
     * The payto-URI hash did not match. Hence the request was denied.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED"] = 1930] = "EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED";
    /**
     * The request used a logic specifier that is not known to the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN"] = 1931] = "EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN";
    /**
     * The request requires a logic which is no longer configured at the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_GENERIC_LOGIC_GONE"] = 1932] = "EXCHANGE_KYC_GENERIC_LOGIC_GONE";
    /**
     * The logic plugin had a bug in its interaction with the KYC provider.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_GENERIC_LOGIC_BUG"] = 1933] = "EXCHANGE_KYC_GENERIC_LOGIC_BUG";
    /**
     * The exchange could not process the request with its KYC provider because the provider refused access to the service. This indicates some configuration issue at the Taler exchange operator.
     * Returned with an HTTP status code of #MHD_HTTP_NETWORK_AUTHENTICATION_REQUIRED (511).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED"] = 1934] = "EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED";
    /**
     * There was a timeout in the interaction between the exchange and the KYC provider. The most likely cause is some networking problem. Trying again later might succeed.
     * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT"] = 1935] = "EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT";
    /**
     * The KYC provider responded with a status that was completely unexpected by the KYC logic of the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY"] = 1936] = "EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY";
    /**
     * The rate limit of the exchange at the KYC provider has been exceeded. Trying much later might work.
     * Returned with an HTTP status code of #MHD_HTTP_SERVICE_UNAVAILABLE (503).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED"] = 1937] = "EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED";
    /**
     * The request to the webhook lacked proper authorization or authentication data.
     * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_KYC_WEBHOOK_UNAUTHORIZED"] = 1938] = "EXCHANGE_KYC_WEBHOOK_UNAUTHORIZED";
    /**
     * The exchange does not know a contract under the given contract public key.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_CONTRACTS_UNKNOWN"] = 1950] = "EXCHANGE_CONTRACTS_UNKNOWN";
    /**
     * The URL does not encode a valid exchange public key in its path.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_CONTRACTS_INVALID_CONTRACT_PUB"] = 1951] = "EXCHANGE_CONTRACTS_INVALID_CONTRACT_PUB";
    /**
     * The returned encrypted contract did not decrypt.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_CONTRACTS_DECRYPTION_FAILED"] = 1952] = "EXCHANGE_CONTRACTS_DECRYPTION_FAILED";
    /**
     * The signature on the encrypted contract did not validate.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_CONTRACTS_SIGNATURE_INVALID"] = 1953] = "EXCHANGE_CONTRACTS_SIGNATURE_INVALID";
    /**
     * The decrypted contract was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_CONTRACTS_DECODING_FAILED"] = 1954] = "EXCHANGE_CONTRACTS_DECODING_FAILED";
    /**
     * A coin signature for a deposit into the purse is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["EXCHANGE_PURSE_DEPOSIT_COIN_SIGNATURE_INVALID"] = 1975] = "EXCHANGE_PURSE_DEPOSIT_COIN_SIGNATURE_INVALID";
    /**
     * The backend could not find the merchant instance specified in the request.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_INSTANCE_UNKNOWN"] = 2000] = "MERCHANT_GENERIC_INSTANCE_UNKNOWN";
    /**
     * The start and end-times in the wire fee structure leave a hole. This is not allowed.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_HOLE_IN_WIRE_FEE_STRUCTURE"] = 2001] = "MERCHANT_GENERIC_HOLE_IN_WIRE_FEE_STRUCTURE";
    /**
     * The reserve key of given to a /reserves/ handler was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_RESERVE_PUB_MALFORMED"] = 2002] = "MERCHANT_GENERIC_RESERVE_PUB_MALFORMED";
    /**
     * The proposal is not known to the backend.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_ORDER_UNKNOWN"] = 2005] = "MERCHANT_GENERIC_ORDER_UNKNOWN";
    /**
     * The order provided to the backend could not be completed, because a product to be completed via inventory data is not actually in our inventory.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_PRODUCT_UNKNOWN"] = 2006] = "MERCHANT_GENERIC_PRODUCT_UNKNOWN";
    /**
     * The tip ID is unknown.  This could happen if the tip has expired.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_TIP_ID_UNKNOWN"] = 2007] = "MERCHANT_GENERIC_TIP_ID_UNKNOWN";
    /**
     * The contract obtained from the merchant backend was malformed.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID"] = 2008] = "MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID";
    /**
     * The order we found does not match the provided contract hash.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER"] = 2009] = "MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER";
    /**
     * The exchange failed to provide a valid response to the merchant's /keys request.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE"] = 2010] = "MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE";
    /**
     * The exchange failed to respond to the merchant on time.
     * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_EXCHANGE_TIMEOUT"] = 2011] = "MERCHANT_GENERIC_EXCHANGE_TIMEOUT";
    /**
     * The merchant failed to talk to the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE"] = 2012] = "MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE";
    /**
     * The exchange returned a maformed response.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_EXCHANGE_REPLY_MALFORMED"] = 2013] = "MERCHANT_GENERIC_EXCHANGE_REPLY_MALFORMED";
    /**
     * The exchange returned an unexpected response status.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS"] = 2014] = "MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS";
    /**
     * The merchant refused the request due to lack of authorization.
     * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_UNAUTHORIZED"] = 2015] = "MERCHANT_GENERIC_UNAUTHORIZED";
    /**
     * The merchant instance specified in the request was deleted.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_INSTANCE_DELETED"] = 2016] = "MERCHANT_GENERIC_INSTANCE_DELETED";
    /**
     * The backend could not find the inbound wire transfer specified in the request.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GENERIC_TRANSFER_UNKNOWN"] = 2017] = "MERCHANT_GENERIC_TRANSFER_UNKNOWN";
    /**
     * The exchange failed to provide a valid answer to the tracking request, thus those details are not in the response.
     * Returned with an HTTP status code of #MHD_HTTP_OK (200).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GET_ORDERS_EXCHANGE_TRACKING_FAILURE"] = 2100] = "MERCHANT_GET_ORDERS_EXCHANGE_TRACKING_FAILURE";
    /**
     * The merchant backend failed to construct the request for tracking to the exchange, thus tracking details are not in the response.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GET_ORDERS_ID_EXCHANGE_REQUEST_FAILURE"] = 2103] = "MERCHANT_GET_ORDERS_ID_EXCHANGE_REQUEST_FAILURE";
    /**
     * The merchant backend failed trying to contact the exchange for tracking details, thus those details are not in the response.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GET_ORDERS_ID_EXCHANGE_LOOKUP_START_FAILURE"] = 2104] = "MERCHANT_GET_ORDERS_ID_EXCHANGE_LOOKUP_START_FAILURE";
    /**
     * The claim token used to authenticate the client is invalid for this order.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GET_ORDERS_ID_INVALID_TOKEN"] = 2105] = "MERCHANT_GET_ORDERS_ID_INVALID_TOKEN";
    /**
     * The contract terms hash used to authenticate the client is invalid for this order.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_HASH"] = 2106] = "MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_HASH";
    /**
     * The exchange responded saying that funds were insufficient (for example, due to double-spending).
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS"] = 2150] = "MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS";
    /**
     * The denomination key used for payment is not listed among the denomination keys of the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND"] = 2151] = "MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND";
    /**
     * The denomination key used for payment is not audited by an auditor approved by the merchant.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_AUDITOR_FAILURE"] = 2152] = "MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_AUDITOR_FAILURE";
    /**
     * There was an integer overflow totaling up the amounts or deposit fees in the payment.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW"] = 2153] = "MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW";
    /**
     * The deposit fees exceed the total value of the payment.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_FEES_EXCEED_PAYMENT"] = 2154] = "MERCHANT_POST_ORDERS_ID_PAY_FEES_EXCEED_PAYMENT";
    /**
     * After considering deposit and wire fees, the payment is insufficient to satisfy the required amount for the contract.  The client should revisit the logic used to calculate fees it must cover.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_ACCEPTABLE (406).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_DUE_TO_FEES"] = 2155] = "MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_DUE_TO_FEES";
    /**
     * Even if we do not consider deposit and wire fees, the payment is insufficient to satisfy the required amount for the contract.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_ACCEPTABLE (406).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT"] = 2156] = "MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT";
    /**
     * The signature over the contract of one of the coins was invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_COIN_SIGNATURE_INVALID"] = 2157] = "MERCHANT_POST_ORDERS_ID_PAY_COIN_SIGNATURE_INVALID";
    /**
     * When we tried to find information about the exchange to issue the deposit, we failed.  This usually only happens if the merchant backend is somehow unable to get its own HTTP client logic to work.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED"] = 2158] = "MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED";
    /**
     * The refund deadline in the contract is after the transfer deadline.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE"] = 2159] = "MERCHANT_POST_ORDERS_ID_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE";
    /**
     * The order was already paid (maybe by another wallet).
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_ALREADY_PAID"] = 2160] = "MERCHANT_POST_ORDERS_ID_PAY_ALREADY_PAID";
    /**
     * The payment is too late, the offer has expired.
     * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED"] = 2161] = "MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED";
    /**
     * The "merchant" field is missing in the proposal data. This is an internal error as the proposal is from the merchant's own database at this point.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING"] = 2162] = "MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING";
    /**
     * Failed to locate merchant's account information matching the wire hash given in the proposal.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_WIRE_HASH_UNKNOWN"] = 2163] = "MERCHANT_POST_ORDERS_ID_PAY_WIRE_HASH_UNKNOWN";
    /**
     * The deposit time for the denomination has expired.
     * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_DEPOSIT_EXPIRED"] = 2165] = "MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_DEPOSIT_EXPIRED";
    /**
     * The exchange of the deposited coin charges a wire fee that could not be added to the total (total amount too high).
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED"] = 2166] = "MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED";
    /**
     * The contract was not fully paid because of refunds. Note that clients MAY treat this as paid if, for example, contracts must be executed despite of refunds.
     * Returned with an HTTP status code of #MHD_HTTP_PAYMENT_REQUIRED (402).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_REFUNDED"] = 2167] = "MERCHANT_POST_ORDERS_ID_PAY_REFUNDED";
    /**
     * According to our database, we have refunded more than we were paid (which should not be possible).
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_REFUNDS_EXCEED_PAYMENTS"] = 2168] = "MERCHANT_POST_ORDERS_ID_PAY_REFUNDS_EXCEED_PAYMENTS";
    /**
     * Legacy stuff. Remove me with protocol v1.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["DEAD_QQQ_PAY_MERCHANT_POST_ORDERS_ID_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE"] = 2169] = "DEAD_QQQ_PAY_MERCHANT_POST_ORDERS_ID_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE";
    /**
     * The payment failed at the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_FAILED"] = 2170] = "MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_FAILED";
    /**
     * The payment required a minimum age but one of the coins (of a denomination with support for age restriction) did not provide any age_commitment.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_MISSING"] = 2171] = "MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_MISSING";
    /**
     * The payment required a minimum age but one of the coins provided an age_commitment that contained a wrong number of public keys compared to the number of age groups defined in the denomination of the coin.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_SIZE_MISMATCH"] = 2172] = "MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_SIZE_MISMATCH";
    /**
     * The payment required a minimum age but one of the coins provided a minimum_age_sig that couldn't be verified with the given age_commitment for that particular minimum age.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_AGE_VERIFICATION_FAILED"] = 2173] = "MERCHANT_POST_ORDERS_ID_PAY_AGE_VERIFICATION_FAILED";
    /**
     * The payment required no minimum age but one of the coins (of a denomination with support for age restriction) did not provide the required h_age_commitment.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_HASH_MISSING"] = 2174] = "MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_HASH_MISSING";
    /**
     * The contract hash does not match the given order ID.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAID_CONTRACT_HASH_MISMATCH"] = 2200] = "MERCHANT_POST_ORDERS_ID_PAID_CONTRACT_HASH_MISMATCH";
    /**
     * The signature of the merchant is not valid for the given contract hash.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_PAID_COIN_SIGNATURE_INVALID"] = 2201] = "MERCHANT_POST_ORDERS_ID_PAID_COIN_SIGNATURE_INVALID";
    /**
     * The merchant failed to send the exchange the refund request.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_ABORT_EXCHANGE_REFUND_FAILED"] = 2251] = "MERCHANT_POST_ORDERS_ID_ABORT_EXCHANGE_REFUND_FAILED";
    /**
     * The merchant failed to find the exchange to process the lookup.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_ABORT_EXCHANGE_LOOKUP_FAILED"] = 2252] = "MERCHANT_POST_ORDERS_ID_ABORT_EXCHANGE_LOOKUP_FAILED";
    /**
     * The merchant could not find the contract.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_NOT_FOUND"] = 2253] = "MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_NOT_FOUND";
    /**
     * The payment was already completed and thus cannot be aborted anymore.
     * Returned with an HTTP status code of #MHD_HTTP_PRECONDITION_FAILED (412).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE"] = 2254] = "MERCHANT_POST_ORDERS_ID_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE";
    /**
     * The hash provided by the wallet does not match the order.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_HASH_MISSMATCH"] = 2255] = "MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_HASH_MISSMATCH";
    /**
     * The array of coins cannot be empty.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_ABORT_COINS_ARRAY_EMPTY"] = 2256] = "MERCHANT_POST_ORDERS_ID_ABORT_COINS_ARRAY_EMPTY";
    /**
     * We could not claim the order because the backend is unaware of it.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_CLAIM_NOT_FOUND"] = 2300] = "MERCHANT_POST_ORDERS_ID_CLAIM_NOT_FOUND";
    /**
     * We could not claim the order because someone else claimed it first.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED"] = 2301] = "MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED";
    /**
     * The client-side experienced an internal failure.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_CLAIM_CLIENT_INTERNAL_FAILURE"] = 2302] = "MERCHANT_POST_ORDERS_ID_CLAIM_CLIENT_INTERNAL_FAILURE";
    /**
     * The backend failed to sign the refund request.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_POST_ORDERS_ID_REFUND_SIGNATURE_FAILED"] = 2350] = "MERCHANT_POST_ORDERS_ID_REFUND_SIGNATURE_FAILED";
    /**
     * The client failed to unblind the signature returned by the merchant.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_TIP_PICKUP_UNBLIND_FAILURE"] = 2400] = "MERCHANT_TIP_PICKUP_UNBLIND_FAILURE";
    /**
     * The exchange returned a failure code for the withdraw operation.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_TIP_PICKUP_EXCHANGE_ERROR"] = 2403] = "MERCHANT_TIP_PICKUP_EXCHANGE_ERROR";
    /**
     * The merchant failed to add up the amounts to compute the pick up value.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_TIP_PICKUP_SUMMATION_FAILED"] = 2404] = "MERCHANT_TIP_PICKUP_SUMMATION_FAILED";
    /**
     * The tip expired.
     * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_TIP_PICKUP_HAS_EXPIRED"] = 2405] = "MERCHANT_TIP_PICKUP_HAS_EXPIRED";
    /**
     * The requested withdraw amount exceeds the amount remaining to be picked up.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING"] = 2406] = "MERCHANT_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING";
    /**
     * The merchant did not find the specified denomination key in the exchange's key set.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_TIP_PICKUP_DENOMINATION_UNKNOWN"] = 2407] = "MERCHANT_TIP_PICKUP_DENOMINATION_UNKNOWN";
    /**
     * The backend lacks a wire transfer method configuration option for the given instance. Thus, this instance is unavailable (not findable for creating new orders).
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE"] = 2500] = "MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE";
    /**
     * The proposal had no timestamp and the backend failed to obtain the local time. Likely to be an internal error.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_ORDERS_NO_LOCALTIME"] = 2501] = "MERCHANT_PRIVATE_POST_ORDERS_NO_LOCALTIME";
    /**
     * The order provided to the backend could not be parsed, some required fields were missing or ill-formed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR"] = 2502] = "MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR";
    /**
     * The backend encountered an error: the proposal already exists.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS"] = 2503] = "MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS";
    /**
     * The request is invalid: the wire deadline is before the refund deadline.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_ORDERS_REFUND_AFTER_WIRE_DEADLINE"] = 2504] = "MERCHANT_PRIVATE_POST_ORDERS_REFUND_AFTER_WIRE_DEADLINE";
    /**
     * The request is invalid: a delivery date was given, but it is in the past.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_ORDERS_DELIVERY_DATE_IN_PAST"] = 2505] = "MERCHANT_PRIVATE_POST_ORDERS_DELIVERY_DATE_IN_PAST";
    /**
     * The request is invalid: the wire deadline for the order would be "never".
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_ORDERS_WIRE_DEADLINE_IS_NEVER"] = 2506] = "MERCHANT_PRIVATE_POST_ORDERS_WIRE_DEADLINE_IS_NEVER";
    /**
     * One of the paths to forget is malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_PATCH_ORDERS_ID_FORGET_PATH_SYNTAX_INCORRECT"] = 2510] = "MERCHANT_PRIVATE_PATCH_ORDERS_ID_FORGET_PATH_SYNTAX_INCORRECT";
    /**
     * One of the paths to forget was not marked as forgettable.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_PATCH_ORDERS_ID_FORGET_PATH_NOT_FORGETTABLE"] = 2511] = "MERCHANT_PRIVATE_PATCH_ORDERS_ID_FORGET_PATH_NOT_FORGETTABLE";
    /**
     * The order provided to the backend could not be deleted, our offer is still valid and awaiting payment.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_DELETE_ORDERS_AWAITING_PAYMENT"] = 2520] = "MERCHANT_PRIVATE_DELETE_ORDERS_AWAITING_PAYMENT";
    /**
     * The order provided to the backend could not be deleted as the order was already paid.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_DELETE_ORDERS_ALREADY_PAID"] = 2521] = "MERCHANT_PRIVATE_DELETE_ORDERS_ALREADY_PAID";
    /**
     * The amount to be refunded is inconsistent: either is lower than the previous amount being awarded, or it is too big to be paid back. In this second case, the fault stays on the business dept. side.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_INCONSISTENT_AMOUNT"] = 2530] = "MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_INCONSISTENT_AMOUNT";
    /**
     * The frontend gave an unpaid order id to issue the refund to.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_ORDER_UNPAID"] = 2531] = "MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_ORDER_UNPAID";
    /**
     * The refund delay was set to 0 and thus no refunds are allowed for this order.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_NOT_ALLOWED_BY_CONTRACT"] = 2532] = "MERCHANT_PRIVATE_POST_ORDERS_ID_REFUND_NOT_ALLOWED_BY_CONTRACT";
    /**
     * The exchange says it does not know this transfer.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_TRANSFERS_EXCHANGE_UNKNOWN"] = 2550] = "MERCHANT_PRIVATE_POST_TRANSFERS_EXCHANGE_UNKNOWN";
    /**
     * We internally failed to execute the /track/transfer request.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_TRANSFERS_REQUEST_ERROR"] = 2551] = "MERCHANT_PRIVATE_POST_TRANSFERS_REQUEST_ERROR";
    /**
     * The amount transferred differs between what was submitted and what the exchange claimed.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_TRANSFERS"] = 2552] = "MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_TRANSFERS";
    /**
     * The exchange gave conflicting information about a coin which has been wire transferred.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_REPORTS"] = 2553] = "MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_REPORTS";
    /**
     * The exchange charged a different wire fee than what it originally advertised, and it is higher.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_TRANSFERS_BAD_WIRE_FEE"] = 2554] = "MERCHANT_PRIVATE_POST_TRANSFERS_BAD_WIRE_FEE";
    /**
     * We did not find the account that the transfer was made to.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_TRANSFERS_ACCOUNT_NOT_FOUND"] = 2555] = "MERCHANT_PRIVATE_POST_TRANSFERS_ACCOUNT_NOT_FOUND";
    /**
     * The backend could not delete the transfer as the echange already replied to our inquiry about it and we have integrated the result.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_DELETE_TRANSFERS_ALREADY_CONFIRMED"] = 2556] = "MERCHANT_PRIVATE_DELETE_TRANSFERS_ALREADY_CONFIRMED";
    /**
     * The backend was previously informed about a wire transfer with the same ID but a different amount. Multiple wire transfers with the same ID are not allowed. If the new amount is correct, the old transfer should first be deleted.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_SUBMISSION"] = 2557] = "MERCHANT_PRIVATE_POST_TRANSFERS_CONFLICTING_SUBMISSION";
    /**
     * The merchant backend cannot create an instance under the given identifier as one already exists. Use PATCH to modify the existing entry.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS"] = 2600] = "MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS";
    /**
     * The merchant backend cannot create an instance because the authentication configuration field is malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_INSTANCES_BAD_AUTH"] = 2601] = "MERCHANT_PRIVATE_POST_INSTANCES_BAD_AUTH";
    /**
     * The merchant backend cannot update an instance's authentication settings because the provided authentication settings are malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_INSTANCE_AUTH_BAD_AUTH"] = 2602] = "MERCHANT_PRIVATE_POST_INSTANCE_AUTH_BAD_AUTH";
    /**
     * The merchant backend cannot create an instance under the given identifier, the previous one was deleted but must be purged first.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_INSTANCES_PURGE_REQUIRED"] = 2603] = "MERCHANT_PRIVATE_POST_INSTANCES_PURGE_REQUIRED";
    /**
     * The merchant backend cannot update an instance under the given identifier, the previous one was deleted but must be purged first.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_PATCH_INSTANCES_PURGE_REQUIRED"] = 2625] = "MERCHANT_PRIVATE_PATCH_INSTANCES_PURGE_REQUIRED";
    /**
     * The product ID exists.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_PRODUCTS_CONFLICT_PRODUCT_EXISTS"] = 2650] = "MERCHANT_PRIVATE_POST_PRODUCTS_CONFLICT_PRODUCT_EXISTS";
    /**
     * The update would have reduced the total amount of product lost, which is not allowed.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_REDUCED"] = 2660] = "MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_REDUCED";
    /**
     * The update would have mean that more stocks were lost than what remains from total inventory after sales, which is not allowed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_EXCEEDS_STOCKS"] = 2661] = "MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_EXCEEDS_STOCKS";
    /**
     * The update would have reduced the total amount of product in stock, which is not allowed.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_STOCKED_REDUCED"] = 2662] = "MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_STOCKED_REDUCED";
    /**
     * The update would have reduced the total amount of product sold, which is not allowed.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_SOLD_REDUCED"] = 2663] = "MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_SOLD_REDUCED";
    /**
     * The lock request is for more products than we have left (unlocked) in stock.
     * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_PRODUCTS_LOCK_INSUFFICIENT_STOCKS"] = 2670] = "MERCHANT_PRIVATE_POST_PRODUCTS_LOCK_INSUFFICIENT_STOCKS";
    /**
     * The deletion request is for a product that is locked.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_DELETE_PRODUCTS_CONFLICTING_LOCK"] = 2680] = "MERCHANT_PRIVATE_DELETE_PRODUCTS_CONFLICTING_LOCK";
    /**
     * The requested wire method is not supported by the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_RESERVES_UNSUPPORTED_WIRE_METHOD"] = 2700] = "MERCHANT_PRIVATE_POST_RESERVES_UNSUPPORTED_WIRE_METHOD";
    /**
     * The reserve could not be deleted because it is unknown.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_DELETE_RESERVES_NO_SUCH_RESERVE"] = 2710] = "MERCHANT_PRIVATE_DELETE_RESERVES_NO_SUCH_RESERVE";
    /**
     * The reserve that was used to fund the tips has expired.
     * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_EXPIRED"] = 2750] = "MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_EXPIRED";
    /**
     * The reserve that was used to fund the tips was not found in the DB.
     * Returned with an HTTP status code of #MHD_HTTP_SERVICE_UNAVAILABLE (503).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_UNKNOWN"] = 2751] = "MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_UNKNOWN";
    /**
     * The backend knows the instance that was supposed to support the tip, and it was configured for tipping. However, the funds remaining are insufficient to cover the tip, and the merchant should top up the reserve.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS"] = 2752] = "MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_INSUFFICIENT_FUNDS";
    /**
     * The backend failed to find a reserve needed to authorize the tip.
     * Returned with an HTTP status code of #MHD_HTTP_SERVICE_UNAVAILABLE (503).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND"] = 2753] = "MERCHANT_PRIVATE_POST_TIP_AUTHORIZE_RESERVE_NOT_FOUND";
    /**
     * The merchant backend encountered a failure in computing the deposit total.
     * Returned with an HTTP status code of #MHD_HTTP_OK (200).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["MERCHANT_PRIVATE_GET_ORDERS_ID_AMOUNT_ARITHMETIC_FAILURE"] = 2800] = "MERCHANT_PRIVATE_GET_ORDERS_ID_AMOUNT_ARITHMETIC_FAILURE";
    /**
     * The signature from the exchange on the deposit confirmation is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["AUDITOR_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID"] = 3100] = "AUDITOR_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID";
    /**
     * The exchange key used for the signature on the deposit confirmation was revoked.
     * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["AUDITOR_EXCHANGE_SIGNING_KEY_REVOKED"] = 3101] = "AUDITOR_EXCHANGE_SIGNING_KEY_REVOKED";
    /**
     * Wire transfer attempted with credit and debit party being the same bank account.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_SAME_ACCOUNT"] = 5101] = "BANK_SAME_ACCOUNT";
    /**
     * Wire transfer impossible, due to financial limitation of the party that attempted the payment.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_UNALLOWED_DEBIT"] = 5102] = "BANK_UNALLOWED_DEBIT";
    /**
     * Negative numbers are not allowed (as value and/or fraction) to instantiate an amount object.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_NEGATIVE_NUMBER_AMOUNT"] = 5103] = "BANK_NEGATIVE_NUMBER_AMOUNT";
    /**
     * A too big number was used (as value and/or fraction) to instantiate an amount object.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_NUMBER_TOO_BIG"] = 5104] = "BANK_NUMBER_TOO_BIG";
    /**
     * Could not login for the requested operation.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_LOGIN_FAILED"] = 5105] = "BANK_LOGIN_FAILED";
    /**
     * The bank account referenced in the requested operation was not found.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_UNKNOWN_ACCOUNT"] = 5106] = "BANK_UNKNOWN_ACCOUNT";
    /**
     * The transaction referenced in the requested operation (typically a reject operation), was not found.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_TRANSACTION_NOT_FOUND"] = 5107] = "BANK_TRANSACTION_NOT_FOUND";
    /**
     * Bank received a malformed amount string.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_BAD_FORMAT_AMOUNT"] = 5108] = "BANK_BAD_FORMAT_AMOUNT";
    /**
     * The client does not own the account credited by the transaction which is to be rejected, so it has no rights do reject it.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_REJECT_NO_RIGHTS"] = 5109] = "BANK_REJECT_NO_RIGHTS";
    /**
     * This error code is returned when no known exception types captured the exception.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_UNMANAGED_EXCEPTION"] = 5110] = "BANK_UNMANAGED_EXCEPTION";
    /**
     * This error code is used for all those exceptions that do not really need a specific error code to return to the client. Used for example when a client is trying to register with a unavailable username.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_SOFT_EXCEPTION"] = 5111] = "BANK_SOFT_EXCEPTION";
    /**
     * The request UID for a request to transfer funds has already been used, but with different details for the transfer.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_TRANSFER_REQUEST_UID_REUSED"] = 5112] = "BANK_TRANSFER_REQUEST_UID_REUSED";
    /**
     * The withdrawal operation already has a reserve selected.  The current request conflicts with the existing selection.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT"] = 5113] = "BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT";
    /**
     * The wire transfer subject duplicates an existing reserve public key. But wire transfer subjects must be unique.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_DUPLICATE_RESERVE_PUB_SUBJECT"] = 5114] = "BANK_DUPLICATE_RESERVE_PUB_SUBJECT";
    /**
     * The client requested a transaction that is so far in the past, that it has been forgotten by the bank.
     * Returned with an HTTP status code of #MHD_HTTP_GONE (410).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_ANCIENT_TRANSACTION_GONE"] = 5115] = "BANK_ANCIENT_TRANSACTION_GONE";
    /**
     * The client attempted to abort a transaction that was already confirmed.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_ABORT_CONFIRM_CONFLICT"] = 5116] = "BANK_ABORT_CONFIRM_CONFLICT";
    /**
     * The client attempted to confirm a transaction that was already aborted.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_CONFIRM_ABORT_CONFLICT"] = 5117] = "BANK_CONFIRM_ABORT_CONFLICT";
    /**
     * The client attempted to register an account with the same name.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_REGISTER_CONFLICT"] = 5118] = "BANK_REGISTER_CONFLICT";
    /**
     * The client attempted to confirm a withdrawal operation before the wallet posted the required details.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["BANK_POST_WITHDRAWAL_OPERATION_REQUIRED"] = 5119] = "BANK_POST_WITHDRAWAL_OPERATION_REQUIRED";
    /**
     * The sync service failed find the account in its database.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_ACCOUNT_UNKNOWN"] = 6100] = "SYNC_ACCOUNT_UNKNOWN";
    /**
     * The SHA-512 hash provided in the If-None-Match header is malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_BAD_IF_NONE_MATCH"] = 6101] = "SYNC_BAD_IF_NONE_MATCH";
    /**
     * The SHA-512 hash provided in the If-Match header is malformed or missing.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_BAD_IF_MATCH"] = 6102] = "SYNC_BAD_IF_MATCH";
    /**
     * The signature provided in the "Sync-Signature" header is malformed or missing.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_BAD_SYNC_SIGNATURE"] = 6103] = "SYNC_BAD_SYNC_SIGNATURE";
    /**
     * The signature provided in the "Sync-Signature" header does not match the account, old or new Etags.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_INVALID_SIGNATURE"] = 6104] = "SYNC_INVALID_SIGNATURE";
    /**
     * The "Content-length" field for the upload is not a number.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_MALFORMED_CONTENT_LENGTH"] = 6105] = "SYNC_MALFORMED_CONTENT_LENGTH";
    /**
     * The "Content-length" field for the upload is too big based on the server's terms of service.
     * Returned with an HTTP status code of #MHD_HTTP_PAYLOAD_TOO_LARGE (413).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_EXCESSIVE_CONTENT_LENGTH"] = 6106] = "SYNC_EXCESSIVE_CONTENT_LENGTH";
    /**
     * The server is out of memory to handle the upload. Trying again later may succeed.
     * Returned with an HTTP status code of #MHD_HTTP_PAYLOAD_TOO_LARGE (413).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_OUT_OF_MEMORY_ON_CONTENT_LENGTH"] = 6107] = "SYNC_OUT_OF_MEMORY_ON_CONTENT_LENGTH";
    /**
     * The uploaded data does not match the Etag.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_INVALID_UPLOAD"] = 6108] = "SYNC_INVALID_UPLOAD";
    /**
     * HTTP server experienced a timeout while awaiting promised payment.
     * Returned with an HTTP status code of #MHD_HTTP_REQUEST_TIMEOUT (408).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_PAYMENT_GENERIC_TIMEOUT"] = 6109] = "SYNC_PAYMENT_GENERIC_TIMEOUT";
    /**
     * Sync could not setup the payment request with its own backend.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_PAYMENT_CREATE_BACKEND_ERROR"] = 6110] = "SYNC_PAYMENT_CREATE_BACKEND_ERROR";
    /**
     * The sync service failed find the backup to be updated in its database.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_PREVIOUS_BACKUP_UNKNOWN"] = 6111] = "SYNC_PREVIOUS_BACKUP_UNKNOWN";
    /**
     * The "Content-length" field for the upload is missing.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_MISSING_CONTENT_LENGTH"] = 6112] = "SYNC_MISSING_CONTENT_LENGTH";
    /**
     * Sync had problems communicating with its payment backend.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_GENERIC_BACKEND_ERROR"] = 6113] = "SYNC_GENERIC_BACKEND_ERROR";
    /**
     * Sync experienced a timeout communicating with its payment backend.
     * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["SYNC_GENERIC_BACKEND_TIMEOUT"] = 6114] = "SYNC_GENERIC_BACKEND_TIMEOUT";
    /**
     * The wallet does not implement a version of the exchange protocol that is compatible with the protocol version of the exchange.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_IMPLEMENTED (501).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE"] = 7000] = "WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE";
    /**
     * The wallet encountered an unexpected exception.  This is likely a bug in the wallet implementation.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_UNEXPECTED_EXCEPTION"] = 7001] = "WALLET_UNEXPECTED_EXCEPTION";
    /**
     * The wallet received a response from a server, but the response can't be parsed.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_RECEIVED_MALFORMED_RESPONSE"] = 7002] = "WALLET_RECEIVED_MALFORMED_RESPONSE";
    /**
     * The wallet tried to make a network request, but it received no response.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_NETWORK_ERROR"] = 7003] = "WALLET_NETWORK_ERROR";
    /**
     * The wallet tried to make a network request, but it was throttled.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_HTTP_REQUEST_THROTTLED"] = 7004] = "WALLET_HTTP_REQUEST_THROTTLED";
    /**
     * The wallet made a request to a service, but received an error response it does not know how to handle.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_UNEXPECTED_REQUEST_ERROR"] = 7005] = "WALLET_UNEXPECTED_REQUEST_ERROR";
    /**
     * The denominations offered by the exchange are insufficient.  Likely the exchange is badly configured or not maintained.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT"] = 7006] = "WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT";
    /**
     * The wallet does not support the operation requested by a client.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_CORE_API_OPERATION_UNKNOWN"] = 7007] = "WALLET_CORE_API_OPERATION_UNKNOWN";
    /**
     * The given taler://pay URI is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_INVALID_TALER_PAY_URI"] = 7008] = "WALLET_INVALID_TALER_PAY_URI";
    /**
     * The signature on a coin by the exchange's denomination key is invalid after unblinding it.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_EXCHANGE_COIN_SIGNATURE_INVALID"] = 7009] = "WALLET_EXCHANGE_COIN_SIGNATURE_INVALID";
    /**
     * The exchange does not know about the reserve (yet), and thus withdrawal can't progress.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_EXCHANGE_WITHDRAW_RESERVE_UNKNOWN_AT_EXCHANGE"] = 7010] = "WALLET_EXCHANGE_WITHDRAW_RESERVE_UNKNOWN_AT_EXCHANGE";
    /**
     * The wallet core service is not available.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_CORE_NOT_AVAILABLE"] = 7011] = "WALLET_CORE_NOT_AVAILABLE";
    /**
     * The bank has aborted a withdrawal operation, and thus a withdrawal can't complete.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK"] = 7012] = "WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK";
    /**
     * An HTTP request made by the wallet timed out.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_HTTP_REQUEST_GENERIC_TIMEOUT"] = 7013] = "WALLET_HTTP_REQUEST_GENERIC_TIMEOUT";
    /**
     * The order has already been claimed by another wallet.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_ORDER_ALREADY_CLAIMED"] = 7014] = "WALLET_ORDER_ALREADY_CLAIMED";
    /**
     * A group of withdrawal operations (typically for the same reserve at the same exchange) has errors and will be tried again later.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_WITHDRAWAL_GROUP_INCOMPLETE"] = 7015] = "WALLET_WITHDRAWAL_GROUP_INCOMPLETE";
    /**
     * The signature on a coin by the exchange's denomination key (obtained through the merchant via tipping) is invalid after unblinding it.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_TIPPING_COIN_SIGNATURE_INVALID"] = 7016] = "WALLET_TIPPING_COIN_SIGNATURE_INVALID";
    /**
     * The wallet does not implement a version of the bank integration API that is compatible with the version offered by the bank.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE"] = 7017] = "WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE";
    /**
     * The wallet processed a taler://pay URI, but the merchant base URL in the downloaded contract terms does not match the merchant base URL derived from the URI.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH"] = 7018] = "WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH";
    /**
     * The merchant's signature on the contract terms is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_CONTRACT_TERMS_SIGNATURE_INVALID"] = 7019] = "WALLET_CONTRACT_TERMS_SIGNATURE_INVALID";
    /**
     * The contract terms given by the merchant are malformed.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_CONTRACT_TERMS_MALFORMED"] = 7020] = "WALLET_CONTRACT_TERMS_MALFORMED";
    /**
     * A pending operation failed, and thus the request can't be completed.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_PENDING_OPERATION_FAILED"] = 7021] = "WALLET_PENDING_OPERATION_FAILED";
    /**
     * A payment was attempted, but the merchant had an internal server error (5xx).
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["WALLET_PAY_MERCHANT_SERVER_ERROR"] = 7022] = "WALLET_PAY_MERCHANT_SERVER_ERROR";
    /**
     * We encountered a timeout with our payment backend.
     * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_GENERIC_BACKEND_TIMEOUT"] = 8000] = "ANASTASIS_GENERIC_BACKEND_TIMEOUT";
    /**
     * The backend requested payment, but the request is malformed.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST"] = 8001] = "ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST";
    /**
     * The backend got an unexpected reply from the payment processor.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_GENERIC_BACKEND_ERROR"] = 8002] = "ANASTASIS_GENERIC_BACKEND_ERROR";
    /**
     * The "Content-length" field for the upload is missing.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_GENERIC_MISSING_CONTENT_LENGTH"] = 8003] = "ANASTASIS_GENERIC_MISSING_CONTENT_LENGTH";
    /**
     * The "Content-length" field for the upload is malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_GENERIC_MALFORMED_CONTENT_LENGTH"] = 8004] = "ANASTASIS_GENERIC_MALFORMED_CONTENT_LENGTH";
    /**
     * The backend failed to setup an order with the payment processor.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_GENERIC_ORDER_CREATE_BACKEND_ERROR"] = 8005] = "ANASTASIS_GENERIC_ORDER_CREATE_BACKEND_ERROR";
    /**
     * The backend was not authorized to check for payment with the payment processor.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_GENERIC_PAYMENT_CHECK_UNAUTHORIZED"] = 8006] = "ANASTASIS_GENERIC_PAYMENT_CHECK_UNAUTHORIZED";
    /**
     * The backend could not check payment status with the payment processor.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_GENERIC_PAYMENT_CHECK_START_FAILED"] = 8007] = "ANASTASIS_GENERIC_PAYMENT_CHECK_START_FAILED";
    /**
     * The Anastasis provider could not be reached.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_GENERIC_PROVIDER_UNREACHABLE"] = 8008] = "ANASTASIS_GENERIC_PROVIDER_UNREACHABLE";
    /**
     * HTTP server experienced a timeout while awaiting promised payment.
     * Returned with an HTTP status code of #MHD_HTTP_REQUEST_TIMEOUT (408).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_PAYMENT_GENERIC_TIMEOUT"] = 8009] = "ANASTASIS_PAYMENT_GENERIC_TIMEOUT";
    /**
     * The key share is unknown to the provider.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_UNKNOWN"] = 8108] = "ANASTASIS_TRUTH_UNKNOWN";
    /**
     * The authorization method used for the key share is no longer supported by the provider.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED"] = 8109] = "ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED";
    /**
     * The client needs to respond to the challenge.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED"] = 8110] = "ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED";
    /**
     * The client's response to the challenge was invalid.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_CHALLENGE_FAILED"] = 8111] = "ANASTASIS_TRUTH_CHALLENGE_FAILED";
    /**
     * The backend is not aware of having issued the provided challenge code. Either this is the wrong code, or it has expired.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_CHALLENGE_UNKNOWN"] = 8112] = "ANASTASIS_TRUTH_CHALLENGE_UNKNOWN";
    /**
     * The backend failed to initiate the authorization process.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED"] = 8114] = "ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED";
    /**
     * The authorization succeeded, but the key share is no longer available.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_KEY_SHARE_GONE"] = 8115] = "ANASTASIS_TRUTH_KEY_SHARE_GONE";
    /**
     * The backend forgot the order we asked the client to pay for
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_ORDER_DISAPPEARED"] = 8116] = "ANASTASIS_TRUTH_ORDER_DISAPPEARED";
    /**
     * The backend itself reported a bad exchange interaction.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_BACKEND_EXCHANGE_BAD"] = 8117] = "ANASTASIS_TRUTH_BACKEND_EXCHANGE_BAD";
    /**
     * The backend reported a payment status we did not expect.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_UNEXPECTED_PAYMENT_STATUS"] = 8118] = "ANASTASIS_TRUTH_UNEXPECTED_PAYMENT_STATUS";
    /**
     * The backend failed to setup the order for payment.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_PAYMENT_CREATE_BACKEND_ERROR"] = 8119] = "ANASTASIS_TRUTH_PAYMENT_CREATE_BACKEND_ERROR";
    /**
     * The decryption of the key share failed with the provided key.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_DECRYPTION_FAILED"] = 8120] = "ANASTASIS_TRUTH_DECRYPTION_FAILED";
    /**
     * The request rate is too high. The server is refusing requests to guard against brute-force attacks.
     * Returned with an HTTP status code of #MHD_HTTP_TOO_MANY_REQUESTS (429).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_RATE_LIMITED"] = 8121] = "ANASTASIS_TRUTH_RATE_LIMITED";
    /**
     * A request to issue a challenge is not valid for this authentication method.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_CHALLENGE_WRONG_METHOD"] = 8123] = "ANASTASIS_TRUTH_CHALLENGE_WRONG_METHOD";
    /**
     * The backend failed to store the key share because the UUID is already in use.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_UPLOAD_UUID_EXISTS"] = 8150] = "ANASTASIS_TRUTH_UPLOAD_UUID_EXISTS";
    /**
     * The backend failed to store the key share because the authorization method is not supported.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TRUTH_UPLOAD_METHOD_NOT_SUPPORTED"] = 8151] = "ANASTASIS_TRUTH_UPLOAD_METHOD_NOT_SUPPORTED";
    /**
     * The provided phone number is not an acceptable number.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_SMS_PHONE_INVALID"] = 8200] = "ANASTASIS_SMS_PHONE_INVALID";
    /**
     * Failed to run the SMS transmission helper process.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_SMS_HELPER_EXEC_FAILED"] = 8201] = "ANASTASIS_SMS_HELPER_EXEC_FAILED";
    /**
     * Provider failed to send SMS. Helper terminated with a non-successful result.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_SMS_HELPER_COMMAND_FAILED"] = 8202] = "ANASTASIS_SMS_HELPER_COMMAND_FAILED";
    /**
     * The provided email address is not an acceptable address.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_EMAIL_INVALID"] = 8210] = "ANASTASIS_EMAIL_INVALID";
    /**
     * Failed to run the E-mail transmission helper process.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_EMAIL_HELPER_EXEC_FAILED"] = 8211] = "ANASTASIS_EMAIL_HELPER_EXEC_FAILED";
    /**
     * Provider failed to send E-mail. Helper terminated with a non-successful result.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_EMAIL_HELPER_COMMAND_FAILED"] = 8212] = "ANASTASIS_EMAIL_HELPER_COMMAND_FAILED";
    /**
     * The provided postal address is not an acceptable address.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_POST_INVALID"] = 8220] = "ANASTASIS_POST_INVALID";
    /**
     * Failed to run the mail transmission helper process.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_POST_HELPER_EXEC_FAILED"] = 8221] = "ANASTASIS_POST_HELPER_EXEC_FAILED";
    /**
     * Provider failed to send mail. Helper terminated with a non-successful result.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_POST_HELPER_COMMAND_FAILED"] = 8222] = "ANASTASIS_POST_HELPER_COMMAND_FAILED";
    /**
     * The provided IBAN address is not an acceptable IBAN.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_IBAN_INVALID"] = 8230] = "ANASTASIS_IBAN_INVALID";
    /**
     * The provider has not yet received the IBAN wire transfer authorizing the disclosure of the key share.
     * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_IBAN_MISSING_TRANSFER"] = 8231] = "ANASTASIS_IBAN_MISSING_TRANSFER";
    /**
     * The backend did not find a TOTP key in the data provided.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TOTP_KEY_MISSING"] = 8240] = "ANASTASIS_TOTP_KEY_MISSING";
    /**
     * The key provided does not satisfy the format restrictions for an Anastasis TOTP key.
     * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_TOTP_KEY_INVALID"] = 8241] = "ANASTASIS_TOTP_KEY_INVALID";
    /**
     * The given if-none-match header is malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_POLICY_BAD_IF_NONE_MATCH"] = 8301] = "ANASTASIS_POLICY_BAD_IF_NONE_MATCH";
    /**
     * The server is out of memory to handle the upload. Trying again later may succeed.
     * Returned with an HTTP status code of #MHD_HTTP_PAYLOAD_TOO_LARGE (413).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_POLICY_OUT_OF_MEMORY_ON_CONTENT_LENGTH"] = 8304] = "ANASTASIS_POLICY_OUT_OF_MEMORY_ON_CONTENT_LENGTH";
    /**
     * The signature provided in the "Anastasis-Policy-Signature" header is malformed or missing.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_POLICY_BAD_SIGNATURE"] = 8305] = "ANASTASIS_POLICY_BAD_SIGNATURE";
    /**
     * The given if-match header is malformed.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_POLICY_BAD_IF_MATCH"] = 8306] = "ANASTASIS_POLICY_BAD_IF_MATCH";
    /**
     * The uploaded data does not match the Etag.
     * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_POLICY_INVALID_UPLOAD"] = 8307] = "ANASTASIS_POLICY_INVALID_UPLOAD";
    /**
     * The provider is unaware of the requested policy.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_POLICY_NOT_FOUND"] = 8350] = "ANASTASIS_POLICY_NOT_FOUND";
    /**
     * The given action is invalid for the current state of the reducer.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_ACTION_INVALID"] = 8400] = "ANASTASIS_REDUCER_ACTION_INVALID";
    /**
     * The given state of the reducer is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_STATE_INVALID"] = 8401] = "ANASTASIS_REDUCER_STATE_INVALID";
    /**
     * The given input to the reducer is invalid.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_INPUT_INVALID"] = 8402] = "ANASTASIS_REDUCER_INPUT_INVALID";
    /**
     * The selected authentication method does not work for the Anastasis provider.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_AUTHENTICATION_METHOD_NOT_SUPPORTED"] = 8403] = "ANASTASIS_REDUCER_AUTHENTICATION_METHOD_NOT_SUPPORTED";
    /**
     * The given input and action do not work for the current state.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE"] = 8404] = "ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE";
    /**
     * We experienced an unexpected failure interacting with the backend.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_BACKEND_FAILURE"] = 8405] = "ANASTASIS_REDUCER_BACKEND_FAILURE";
    /**
     * The contents of a resource file did not match our expectations.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_RESOURCE_MALFORMED"] = 8406] = "ANASTASIS_REDUCER_RESOURCE_MALFORMED";
    /**
     * A required resource file is missing.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_RESOURCE_MISSING"] = 8407] = "ANASTASIS_REDUCER_RESOURCE_MISSING";
    /**
     * An input did not match the regular expression.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_INPUT_REGEX_FAILED"] = 8408] = "ANASTASIS_REDUCER_INPUT_REGEX_FAILED";
    /**
     * An input did not match the custom validation logic.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_INPUT_VALIDATION_FAILED"] = 8409] = "ANASTASIS_REDUCER_INPUT_VALIDATION_FAILED";
    /**
     * Our attempts to download the recovery document failed with all providers. Most likely the personal information you entered differs from the information you provided during the backup process and you should go back to the previous step. Alternatively, if you used a backup provider that is unknown to this application, you should add that provider manually.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED"] = 8410] = "ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED";
    /**
     * Anastasis provider reported a fatal failure.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_BACKUP_PROVIDER_FAILED"] = 8411] = "ANASTASIS_REDUCER_BACKUP_PROVIDER_FAILED";
    /**
     * Anastasis provider failed to respond to the configuration request.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED"] = 8412] = "ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED";
    /**
     * The policy we downloaded is malformed. Must have been a client error while creating the backup.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_POLICY_MALFORMED"] = 8413] = "ANASTASIS_REDUCER_POLICY_MALFORMED";
    /**
     * We failed to obtain the policy, likely due to a network issue.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_NETWORK_FAILED"] = 8414] = "ANASTASIS_REDUCER_NETWORK_FAILED";
    /**
     * The recovered secret did not match the required syntax.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_SECRET_MALFORMED"] = 8415] = "ANASTASIS_REDUCER_SECRET_MALFORMED";
    /**
     * The challenge data provided is too large for the available providers.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_CHALLENGE_DATA_TOO_BIG"] = 8416] = "ANASTASIS_REDUCER_CHALLENGE_DATA_TOO_BIG";
    /**
     * The provided core secret is too large for some of the providers.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_SECRET_TOO_BIG"] = 8417] = "ANASTASIS_REDUCER_SECRET_TOO_BIG";
    /**
     * The provider returned in invalid configuration.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_PROVIDER_INVALID_CONFIG"] = 8418] = "ANASTASIS_REDUCER_PROVIDER_INVALID_CONFIG";
    /**
     * The reducer encountered an internal error, likely a bug that needs to be reported.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_INTERNAL_ERROR"] = 8419] = "ANASTASIS_REDUCER_INTERNAL_ERROR";
    /**
     * The reducer already synchronized with all providers.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["ANASTASIS_REDUCER_PROVIDERS_ALREADY_SYNCED"] = 8420] = "ANASTASIS_REDUCER_PROVIDERS_ALREADY_SYNCED";
    /**
     * A generic error happened in the LibEuFin nexus.  See the enclose details JSON for more information.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["LIBEUFIN_NEXUS_GENERIC_ERROR"] = 9000] = "LIBEUFIN_NEXUS_GENERIC_ERROR";
    /**
     * An uncaught exception happened in the LibEuFin nexus service.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["LIBEUFIN_NEXUS_UNCAUGHT_EXCEPTION"] = 9001] = "LIBEUFIN_NEXUS_UNCAUGHT_EXCEPTION";
    /**
     * A generic error happened in the LibEuFin sandbox.  See the enclose details JSON for more information.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["LIBEUFIN_SANDBOX_GENERIC_ERROR"] = 9500] = "LIBEUFIN_SANDBOX_GENERIC_ERROR";
    /**
     * An uncaught exception happened in the LibEuFin sandbox service.
     * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["LIBEUFIN_SANDBOX_UNCAUGHT_EXCEPTION"] = 9501] = "LIBEUFIN_SANDBOX_UNCAUGHT_EXCEPTION";
    /**
     * This validation method is not supported by the service.
     * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["TALDIR_METHOD_NOT_SUPPORTED"] = 9600] = "TALDIR_METHOD_NOT_SUPPORTED";
    /**
     * Number of allowed attempts for initiating a challenge exceeded.
     * Returned with an HTTP status code of #MHD_HTTP_TOO_MANY_REQUESTS (429).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["TALDIR_REGISTER_RATE_LIMITED"] = 9601] = "TALDIR_REGISTER_RATE_LIMITED";
    /**
     * End of error code range.
     * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
     * (A value of 0 indicates that the error is generated client-side).
     */
    TalerErrorCode[TalerErrorCode["END"] = 9999] = "END";
})(TalerErrorCode || (TalerErrorCode = {}));

/*
 This file is part of GNU Taler
 (C) 2018-2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * Type-safe codecs for converting from/to JSON.
 */
/* eslint-disable @typescript-eslint/ban-types */
/**
 * Error thrown when decoding fails.
 */
class DecodingError extends Error {
    constructor(message) {
        super(message);
        Object.setPrototypeOf(this, DecodingError.prototype);
        this.name = "DecodingError";
    }
}
function renderContext(c) {
    const p = c === null || c === void 0 ? void 0 : c.path;
    if (p) {
        return p.join(".");
    }
    else {
        return "(unknown)";
    }
}
function joinContext(c, part) {
    var _a;
    const path = (_a = c === null || c === void 0 ? void 0 : c.path) !== null && _a !== void 0 ? _a : [];
    return {
        path: path.concat([part]),
    };
}
class ObjectCodecBuilder {
    constructor() {
        this.propList = [];
    }
    /**
     * Define a property for the object.
     */
    property(x, codec) {
        if (!codec) {
            throw Error("inner codec must be defined");
        }
        this.propList.push({ name: x, codec: codec });
        return this;
    }
    /**
     * Return the built codec.
     *
     * @param objectDisplayName name of the object that this codec operates on,
     *   used in error messages.
     */
    build(objectDisplayName) {
        const propList = this.propList;
        return {
            decode(x, c) {
                if (!c) {
                    c = {
                        path: [`(${objectDisplayName})`],
                    };
                }
                if (typeof x !== "object") {
                    throw new DecodingError(`expected object for ${objectDisplayName} at ${renderContext(c)} but got ${typeof x}`);
                }
                const obj = {};
                for (const prop of propList) {
                    const propRawVal = x[prop.name];
                    const propVal = prop.codec.decode(propRawVal, joinContext(c, prop.name));
                    obj[prop.name] = propVal;
                }
                return obj;
            },
        };
    }
}
class UnionCodecBuilder {
    constructor(discriminator, baseCodec) {
        this.discriminator = discriminator;
        this.baseCodec = baseCodec;
        this.alternatives = new Map();
    }
    /**
     * Define a property for the object.
     */
    alternative(tagValue, codec) {
        if (!codec) {
            throw Error("inner codec must be defined");
        }
        this.alternatives.set(tagValue, { codec, tagValue });
        return this;
    }
    /**
     * Return the built codec.
     *
     * @param objectDisplayName name of the object that this codec operates on,
     *   used in error messages.
     */
    build(objectDisplayName) {
        const alternatives = this.alternatives;
        const discriminator = this.discriminator;
        const baseCodec = this.baseCodec;
        return {
            decode(x, c) {
                if (!c) {
                    c = {
                        path: [`(${objectDisplayName})`],
                    };
                }
                const d = x[discriminator];
                if (d === undefined) {
                    throw new DecodingError(`expected tag for ${objectDisplayName} at ${renderContext(c)}.${String(discriminator)}`);
                }
                const alt = alternatives.get(d);
                if (!alt) {
                    throw new DecodingError(`unknown tag for ${objectDisplayName} ${d} at ${renderContext(c)}.${String(discriminator)}`);
                }
                const altDecoded = alt.codec.decode(x);
                if (baseCodec) {
                    const baseDecoded = baseCodec.decode(x, c);
                    return Object.assign(Object.assign({}, baseDecoded), altDecoded);
                }
                else {
                    return altDecoded;
                }
            },
        };
    }
}
class UnionCodecPreBuilder {
    discriminateOn(discriminator, baseCodec) {
        return new UnionCodecBuilder(discriminator, baseCodec);
    }
}
/**
 * Return a builder for a codec that decodes an object with properties.
 */
function buildCodecForObject() {
    return new ObjectCodecBuilder();
}
function buildCodecForUnion() {
    return new UnionCodecPreBuilder();
}
/**
 * Return a codec for a mapping from a string to values described by the inner codec.
 */
function codecForMap(innerCodec) {
    if (!innerCodec) {
        throw Error("inner codec must be defined");
    }
    return {
        decode(x, c) {
            const map = {};
            if (typeof x !== "object") {
                throw new DecodingError(`expected object at ${renderContext(c)}`);
            }
            for (const i in x) {
                map[i] = innerCodec.decode(x[i], joinContext(c, `[${i}]`));
            }
            return map;
        },
    };
}
/**
 * Return a codec for a list, containing values described by the inner codec.
 */
function codecForList(innerCodec) {
    if (!innerCodec) {
        throw Error("inner codec must be defined");
    }
    return {
        decode(x, c) {
            const arr = [];
            if (!Array.isArray(x)) {
                throw new DecodingError(`expected array at ${renderContext(c)}`);
            }
            for (const i in x) {
                arr.push(innerCodec.decode(x[i], joinContext(c, `[${i}]`)));
            }
            return arr;
        },
    };
}
/**
 * Return a codec for a value that must be a number.
 */
function codecForNumber() {
    return {
        decode(x, c) {
            if (typeof x === "number") {
                return x;
            }
            throw new DecodingError(`expected number at ${renderContext(c)} but got ${typeof x}`);
        },
    };
}
/**
 * Return a codec for a value that must be a number.
 */
function codecForBoolean() {
    return {
        decode(x, c) {
            if (typeof x === "boolean") {
                return x;
            }
            throw new DecodingError(`expected boolean at ${renderContext(c)} but got ${typeof x}`);
        },
    };
}
/**
 * Return a codec for a value that must be a string.
 */
function codecForString() {
    return {
        decode(x, c) {
            if (typeof x === "string") {
                return x;
            }
            throw new DecodingError(`expected string at ${renderContext(c)} but got ${typeof x}`);
        },
    };
}
/**
 * Codec that allows any value.
 */
function codecForAny() {
    return {
        decode(x, c) {
            return x;
        },
    };
}
/**
 * Return a codec for a value that must be a string.
 */
function codecForConstString(s) {
    return {
        decode(x, c) {
            if (x === s) {
                return x;
            }
            if (typeof x !== "string") {
                throw new DecodingError(`expected string constant "${s}" at ${renderContext(c)} but got ${typeof x}`);
            }
            throw new DecodingError(`expected string constant "${s}" at ${renderContext(c)} but got string value "${x}"`);
        },
    };
}
/**
 * Return a codec for a value that must be a constant number.
 */
function codecForConstNumber(n) {
    return {
        decode(x, c) {
            if (x === n) {
                return x;
            }
            throw new DecodingError(`expected number constant "${n}" at ${renderContext(c)}  but got ${typeof x}`);
        },
    };
}
function codecOptional(innerCodec) {
    return {
        decode(x, c) {
            if (x === undefined || x === null) {
                return undefined;
            }
            return innerCodec.decode(x, c);
        },
    };
}

/*
 This file is part of GNU Taler
 (C) 2019 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * Number of fractional units that one value unit represents.
 */
const amountFractionalBase = 1e8;
/**
 * How many digits behind the comma are required to represent the
 * fractional value in human readable decimal format?  Must match
 * lg(fractionalBase)
 */
const amountFractionalLength = 8;
/**
 * Maximum allowed value field of an amount.
 */
const amountMaxValue = Math.pow(2, 52);
const codecForAmountJson = () => buildCodecForObject()
    .property("currency", codecForString())
    .property("value", codecForNumber())
    .property("fraction", codecForNumber())
    .build("AmountJson");
const codecForAmountString = () => codecForString();
/**
 * Helper class for dealing with amounts.
 */
class Amounts {
    constructor() {
        throw Error("not instantiable");
    }
    /**
     * Get an amount that represents zero units of a currency.
     */
    static getZero(currency) {
        return {
            currency,
            fraction: 0,
            value: 0,
        };
    }
    static jsonifyAmount(amt) {
        if (typeof amt === "string") {
            return Amounts.parseOrThrow(amt);
        }
        return amt;
    }
    static sum(amounts) {
        if (amounts.length <= 0) {
            throw Error("can't sum zero amounts");
        }
        const jsonAmounts = amounts.map((x) => Amounts.jsonifyAmount(x));
        return Amounts.add(jsonAmounts[0], ...jsonAmounts.slice(1));
    }
    static sumOrZero(currency, amounts) {
        if (amounts.length <= 0) {
            return {
                amount: Amounts.getZero(currency),
                saturated: false,
            };
        }
        const jsonAmounts = amounts.map((x) => Amounts.jsonifyAmount(x));
        return Amounts.add(jsonAmounts[0], ...jsonAmounts.slice(1));
    }
    /**
     * 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.
     */
    static add(first, ...rest) {
        const currency = first.currency;
        let value = first.value + Math.floor(first.fraction / amountFractionalBase);
        if (value > amountMaxValue) {
            return {
                amount: {
                    currency,
                    value: amountMaxValue,
                    fraction: amountFractionalBase - 1,
                },
                saturated: true,
            };
        }
        let fraction = first.fraction % amountFractionalBase;
        for (const x of rest) {
            if (x.currency.toUpperCase() !== currency.toUpperCase()) {
                throw Error(`Mismatched currency: ${x.currency} and ${currency}`);
            }
            value =
                value +
                    x.value +
                    Math.floor((fraction + x.fraction) / amountFractionalBase);
            fraction = Math.floor((fraction + x.fraction) % amountFractionalBase);
            if (value > amountMaxValue) {
                return {
                    amount: {
                        currency,
                        value: amountMaxValue,
                        fraction: amountFractionalBase - 1,
                    },
                    saturated: true,
                };
            }
        }
        return { amount: { currency, value, fraction }, saturated: false };
    }
    /**
     * 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.
     */
    static sub(a, ...rest) {
        const currency = a.currency;
        let value = a.value;
        let fraction = a.fraction;
        for (const b of rest) {
            if (b.currency.toUpperCase() !== a.currency.toUpperCase()) {
                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 += amountFractionalBase;
            }
            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 };
    }
    /**
     * Compare two amounts.  Returns 0 when equal, -1 when a < b
     * and +1 when a > b.  Throws when currencies don't match.
     */
    static cmp(a, b) {
        a = Amounts.jsonifyAmount(a);
        b = Amounts.jsonifyAmount(b);
        if (a.currency !== b.currency) {
            throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`);
        }
        const av = a.value + Math.floor(a.fraction / amountFractionalBase);
        const af = a.fraction % amountFractionalBase;
        const bv = b.value + Math.floor(b.fraction / amountFractionalBase);
        const bf = b.fraction % amountFractionalBase;
        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");
        }
    }
    /**
     * Create a copy of an amount.
     */
    static copy(a) {
        return {
            currency: a.currency,
            fraction: a.fraction,
            value: a.value,
        };
    }
    /**
     * Divide an amount.  Throws on division by zero.
     */
    static 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 * amountFractionalBase + a.fraction) / n),
            value: Math.floor(a.value / n),
        };
    }
    /**
     * Check if an amount is non-zero.
     */
    static isNonZero(a) {
        return a.value > 0 || a.fraction > 0;
    }
    static isZero(a) {
        a = Amounts.jsonifyAmount(a);
        return a.value === 0 && a.fraction === 0;
    }
    /**
     * Parse an amount like 'EUR:20.5' for 20 Euros and 50 ct.
     */
    static parse(s) {
        const res = s.match(/^([a-zA-Z0-9_*-]+):([0-9]+)([.][0-9]+)?$/);
        if (!res) {
            return undefined;
        }
        const tail = res[3] || ".0";
        if (tail.length > amountFractionalLength + 1) {
            return undefined;
        }
        const value = Number.parseInt(res[2]);
        if (value > amountMaxValue) {
            return undefined;
        }
        return {
            currency: res[1].toUpperCase(),
            fraction: Math.round(amountFractionalBase * Number.parseFloat(tail)),
            value,
        };
    }
    /**
     * Parse amount in standard string form (like 'EUR:20.5'),
     * throw if the input is not a valid amount.
     */
    static parseOrThrow(s) {
        const res = Amounts.parse(s);
        if (!res) {
            throw Error(`Can't parse amount: "${s}"`);
        }
        return res;
    }
    /**
     * Convert a float to a Taler amount.
     * Loss of precision possible.
     */
    static fromFloat(floatVal, currency) {
        return {
            currency,
            fraction: Math.floor((floatVal - Math.floor(floatVal)) * amountFractionalBase),
            value: Math.floor(floatVal),
        };
    }
    static min(a, b) {
        const cr = Amounts.cmp(a, b);
        if (cr >= 0) {
            return Amounts.jsonifyAmount(b);
        }
        else {
            return Amounts.jsonifyAmount(a);
        }
    }
    static max(a, b) {
        const cr = Amounts.cmp(a, b);
        if (cr >= 0) {
            return Amounts.jsonifyAmount(a);
        }
        else {
            return Amounts.jsonifyAmount(b);
        }
    }
    static mult(a, n) {
        a = this.jsonifyAmount(a);
        if (!Number.isInteger(n)) {
            throw Error("amount can only be multipied by an integer");
        }
        if (n < 0) {
            throw Error("amount can only be multiplied by a positive integer");
        }
        if (n == 0) {
            return { amount: Amounts.getZero(a.currency), saturated: false };
        }
        let x = a;
        let acc = Amounts.getZero(a.currency);
        while (n > 1) {
            if (n % 2 == 0) {
                n = n / 2;
            }
            else {
                n = (n - 1) / 2;
                const r2 = Amounts.add(acc, x);
                if (r2.saturated) {
                    return r2;
                }
                acc = r2.amount;
            }
            const r2 = Amounts.add(x, x);
            if (r2.saturated) {
                return r2;
            }
            x = r2.amount;
        }
        return Amounts.add(acc, x);
    }
    /**
     * Check if the argument is a valid amount in string form.
     */
    static check(a) {
        if (typeof a !== "string") {
            return false;
        }
        try {
            const parsedAmount = Amounts.parse(a);
            return !!parsedAmount;
        }
        catch (_a) {
            return false;
        }
    }
    /**
     * Convert to standard human-readable string representation that's
     * also used in JSON formats.
     */
    static stringify(a) {
        a = Amounts.jsonifyAmount(a);
        const s = this.stringifyValue(a);
        return `${a.currency}:${s}`;
    }
    static isSameCurrency(a1, a2) {
        const x1 = this.jsonifyAmount(a1);
        const x2 = this.jsonifyAmount(a2);
        return x1.currency.toUpperCase() === x2.currency.toUpperCase();
    }
    static stringifyValue(a, minFractional = 0) {
        const av = a.value + Math.floor(a.fraction / amountFractionalBase);
        const af = a.fraction % amountFractionalBase;
        let s = av.toString();
        if (af) {
            s = s + ".";
            let n = af;
            for (let i = 0; i < amountFractionalLength; i++) {
                if (!n && i >= minFractional) {
                    break;
                }
                s = s + Math.floor((n / amountFractionalBase) * 10).toString();
                n = (n * 10) % amountFractionalBase;
            }
        }
        return s;
    }
    /**
     * Number of fractional digits needed to fully represent the amount
     * @param a amount
     * @returns
     */
    static maxFractionalDigits(a) {
        if (a.fraction === 0)
            return 0;
        if (a.fraction < 0) {
            console.error("amount fraction can not be negative", a);
            return 0;
        }
        let i = 0;
        let check = true;
        let rest = a.fraction;
        while (rest > 0 && check) {
            check = rest % 10 === 0;
            rest = rest / 10;
            i++;
        }
        return amountFractionalLength - i + 1;
    }
}

/*
 This file is part of GNU Taler
 (C) 2020 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * Major version.  Each increment means a backwards-incompatible change.
 * Typically this means that a custom converter needs to be written.
 */
const BACKUP_VERSION_MAJOR = 1;
/**
 * Minor version.  Each increment means that information is added to the backup
 * in a backwards-compatible way.
 *
 * Wallets can always import a smaller minor version than their own backup code version.
 * When importing a bigger version, data loss is possible and the user should be urged to
 * upgrade their wallet first.
 */
const BACKUP_VERSION_MINOR = 1;
var BackupOperationStatus;
(function (BackupOperationStatus) {
    BackupOperationStatus["Cancelled"] = "cancelled";
    BackupOperationStatus["Finished"] = "finished";
    BackupOperationStatus["Pending"] = "pending";
})(BackupOperationStatus || (BackupOperationStatus = {}));
var BackupWgType;
(function (BackupWgType) {
    BackupWgType["BankManual"] = "bank-manual";
    BackupWgType["BankIntegrated"] = "bank-integrated";
    BackupWgType["PeerPullCredit"] = "peer-pull-credit";
    BackupWgType["PeerPushCredit"] = "peer-push-credit";
    BackupWgType["Recoup"] = "recoup";
})(BackupWgType || (BackupWgType = {}));
/**
 * Types of coin sources.
 */
var BackupCoinSourceType;
(function (BackupCoinSourceType) {
    BackupCoinSourceType["Withdraw"] = "withdraw";
    BackupCoinSourceType["Refresh"] = "refresh";
    BackupCoinSourceType["Tip"] = "tip";
})(BackupCoinSourceType || (BackupCoinSourceType = {}));
/**
 * Reasons for why a coin is being refreshed.
 */
var BackupRefreshReason;
(function (BackupRefreshReason) {
    BackupRefreshReason["Manual"] = "manual";
    BackupRefreshReason["Pay"] = "pay";
    BackupRefreshReason["Refund"] = "refund";
    BackupRefreshReason["AbortPay"] = "abort-pay";
    BackupRefreshReason["Recoup"] = "recoup";
    BackupRefreshReason["BackupRestored"] = "backup-restored";
    BackupRefreshReason["Scheduled"] = "scheduled";
})(BackupRefreshReason || (BackupRefreshReason = {}));
var BackupRefundState;
(function (BackupRefundState) {
    BackupRefundState["Failed"] = "failed";
    BackupRefundState["Applied"] = "applied";
    BackupRefundState["Pending"] = "pending";
})(BackupRefundState || (BackupRefundState = {}));
var BackupProposalStatus;
(function (BackupProposalStatus) {
    /**
     * Proposed (and either downloaded or not,
     * depending on whether contract terms are present),
     * but the user needs to accept/reject it.
     */
    BackupProposalStatus["Proposed"] = "proposed";
    /**
     * The user has rejected the proposal.
     */
    BackupProposalStatus["Refused"] = "refused";
    /**
     * Downloading or processing the proposal has failed permanently.
     *
     * FIXME:  Should this be modeled as a "misbehavior report" instead?
     */
    BackupProposalStatus["PermanentlyFailed"] = "permanently-failed";
    /**
     * Downloaded proposal was detected as a re-purchase.
     */
    BackupProposalStatus["Repurchase"] = "repurchase";
})(BackupProposalStatus || (BackupProposalStatus = {}));

/*
 This file is part of GNU Taler
 (C) 2020 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
// globalThis polyfill, see https://mathiasbynens.be/notes/globalthis
(function () {
    if (typeof globalThis === "object")
        return;
    Object.defineProperty(Object.prototype, "__magic__", {
        get: function () {
            return this;
        },
        configurable: true, // This makes it possible to `delete` the getter later.
    });
    // @ts-ignore: polyfill magic
    __magic__.globalThis = __magic__; // lolwat
    // @ts-ignore: polyfill magic
    delete Object.prototype.__magic__;
})();
// @ts-ignore
const _URL = globalThis.URL;
if (!_URL) {
    throw Error("FATAL: URL not available");
}
const URL$1 = _URL;
// @ts-ignore
const _URLSearchParams = globalThis.URLSearchParams;
if (!_URLSearchParams) {
    throw Error("FATAL: URLSearchParams not available");
}
const URLSearchParams = _URLSearchParams;

/*
 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/>
 */
/**
 * 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 / amountFractionalBase;
    return `${x} ${amount.currency}`;
}
/**
 * Canonicalize a base url, typically for the exchange.
 *
 * See http://api.taler.net/wallet.html#general
 */
function canonicalizeBaseUrl(url) {
    if (!url.startsWith("http") && !url.startsWith("https")) {
        url = "https://" + url;
    }
    const x = new URL$1(url);
    if (!x.pathname.endsWith("/")) {
        x.pathname = x.pathname + "/";
    }
    x.search = "";
    x.hash = "";
    return x.href;
}
/**
 * Convert object to JSON with canonical ordering of keys
 * and whitespace omitted.
 *
 * See RFC 4885 (https://tools.ietf.org/html/rfc8785).
 */
function canonicalJson(obj) {
    // Check for cycles, etc.
    obj = JSON.parse(JSON.stringify(obj));
    if (typeof obj === "string") {
        return JSON.stringify(obj);
    }
    if (typeof obj === "number" || typeof obj === "boolean" || 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 + "}";
}
/**
 * Lexically compare two strings.
 */
function strcmp(s1, s2) {
    if (s1 < s2) {
        return -1;
    }
    if (s1 > s2) {
        return 1;
    }
    return 0;
}
/**
 * Shorthand function for formatted JSON stringification.
 */
function j2s(x) {
    return JSON.stringify(x, undefined, 2);
}
/**
 * Use this to filter null or undefined from an array in a type-safe fashion
 *
 * example:
 * const array: Array<T | undefined> = [undefined, null]
 * const filtered: Array<T> = array.filter(notEmpty)
 *
 * @param value
 * @returns
 */
function notEmpty(value) {
    return value !== null && value !== undefined;
}

/*
 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/>
 */
var LibtoolVersion;
(function (LibtoolVersion) {
    /**
     * 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 };
    }
    LibtoolVersion.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 };
    }
    LibtoolVersion.parseVersion = parseVersion;
})(LibtoolVersion || (LibtoolVersion = {}));

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
var NotificationType;
(function (NotificationType) {
    NotificationType["CoinWithdrawn"] = "coin-withdrawn";
    NotificationType["ProposalAccepted"] = "proposal-accepted";
    NotificationType["ProposalDownloaded"] = "proposal-downloaded";
    NotificationType["RefundsSubmitted"] = "refunds-submitted";
    NotificationType["RecoupStarted"] = "recoup-started";
    NotificationType["RecoupFinished"] = "recoup-finished";
    NotificationType["RefreshRevealed"] = "refresh-revealed";
    NotificationType["RefreshMelted"] = "refresh-melted";
    NotificationType["RefreshStarted"] = "refresh-started";
    NotificationType["RefreshUnwarranted"] = "refresh-unwarranted";
    NotificationType["ReserveUpdated"] = "reserve-updated";
    NotificationType["ReserveConfirmed"] = "reserve-confirmed";
    NotificationType["ReserveCreated"] = "reserve-created";
    NotificationType["WithdrawGroupCreated"] = "withdraw-group-created";
    NotificationType["WithdrawGroupFinished"] = "withdraw-group-finished";
    NotificationType["WaitingForRetry"] = "waiting-for-retry";
    NotificationType["RefundStarted"] = "refund-started";
    NotificationType["RefundQueried"] = "refund-queried";
    NotificationType["RefundFinished"] = "refund-finished";
    NotificationType["ExchangeOperationError"] = "exchange-operation-error";
    NotificationType["ExchangeAdded"] = "exchange-added";
    NotificationType["RefreshOperationError"] = "refresh-operation-error";
    NotificationType["RecoupOperationError"] = "recoup-operation-error";
    NotificationType["RefundApplyOperationError"] = "refund-apply-error";
    NotificationType["RefundStatusOperationError"] = "refund-status-error";
    NotificationType["ProposalOperationError"] = "proposal-error";
    NotificationType["BackupOperationError"] = "backup-error";
    NotificationType["TipOperationError"] = "tip-error";
    NotificationType["PayOperationError"] = "pay-error";
    NotificationType["PayOperationSuccess"] = "pay-operation-success";
    NotificationType["WithdrawOperationError"] = "withdraw-error";
    NotificationType["ReserveNotYetFound"] = "reserve-not-yet-found";
    NotificationType["ReserveOperationError"] = "reserve-error";
    NotificationType["InternalError"] = "internal-error";
    NotificationType["PendingOperationProcessed"] = "pending-operation-processed";
    NotificationType["ProposalRefused"] = "proposal-refused";
    NotificationType["ReserveRegisteredWithBank"] = "reserve-registered-with-bank";
    NotificationType["DepositOperationError"] = "deposit-operation-error";
})(NotificationType || (NotificationType = {}));

// SHA-256 for JavaScript.
//
// Written in 2014-2016 by Dmitry Chestnykh.
// Public domain, no warranty.
//
// Functions (accept and return Uint8Arrays):
//
//   sha256(message) -> hash
//   sha256.hmac(key, message) -> mac
//
//  Classes:
//
//   new sha256.Hash()
const digestLength = 32;
const blockSize = 64;
// SHA-256 constants
const K = new Uint32Array([
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
    0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
    0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
    0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
    0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
    0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
]);
function hashBlocks(w, v, p, pos, len) {
    let a, b, c, d, e, f, g, h, u, i, j, t1, t2;
    while (len >= 64) {
        a = v[0];
        b = v[1];
        c = v[2];
        d = v[3];
        e = v[4];
        f = v[5];
        g = v[6];
        h = v[7];
        for (i = 0; i < 16; i++) {
            j = pos + i * 4;
            w[i] =
                ((p[j] & 0xff) << 24) |
                    ((p[j + 1] & 0xff) << 16) |
                    ((p[j + 2] & 0xff) << 8) |
                    (p[j + 3] & 0xff);
        }
        for (i = 16; i < 64; i++) {
            u = w[i - 2];
            t1 =
                ((u >>> 17) | (u << (32 - 17))) ^
                    ((u >>> 19) | (u << (32 - 19))) ^
                    (u >>> 10);
            u = w[i - 15];
            t2 =
                ((u >>> 7) | (u << (32 - 7))) ^
                    ((u >>> 18) | (u << (32 - 18))) ^
                    (u >>> 3);
            w[i] = ((t1 + w[i - 7]) | 0) + ((t2 + w[i - 16]) | 0);
        }
        for (i = 0; i < 64; i++) {
            t1 =
                ((((((e >>> 6) | (e << (32 - 6))) ^
                    ((e >>> 11) | (e << (32 - 11))) ^
                    ((e >>> 25) | (e << (32 - 25)))) +
                    ((e & f) ^ (~e & g))) |
                    0) +
                    ((h + ((K[i] + w[i]) | 0)) | 0)) |
                    0;
            t2 =
                ((((a >>> 2) | (a << (32 - 2))) ^
                    ((a >>> 13) | (a << (32 - 13))) ^
                    ((a >>> 22) | (a << (32 - 22)))) +
                    ((a & b) ^ (a & c) ^ (b & c))) |
                    0;
            h = g;
            g = f;
            f = e;
            e = (d + t1) | 0;
            d = c;
            c = b;
            b = a;
            a = (t1 + t2) | 0;
        }
        v[0] += a;
        v[1] += b;
        v[2] += c;
        v[3] += d;
        v[4] += e;
        v[5] += f;
        v[6] += g;
        v[7] += h;
        pos += 64;
        len -= 64;
    }
    return pos;
}
// Hash implements SHA256 hash algorithm.
class HashSha256 {
    constructor() {
        this.digestLength = digestLength;
        this.blockSize = blockSize;
        // Note: Int32Array is used instead of Uint32Array for performance reasons.
        this.state = new Int32Array(8); // hash state
        this.temp = new Int32Array(64); // temporary state
        this.buffer = new Uint8Array(128); // buffer for data to hash
        this.bufferLength = 0; // number of bytes in buffer
        this.bytesHashed = 0; // number of total bytes hashed
        this.finished = false; // indicates whether the hash was finalized
        this.reset();
    }
    // Resets hash state making it possible
    // to re-use this instance to hash other data.
    reset() {
        this.state[0] = 0x6a09e667;
        this.state[1] = 0xbb67ae85;
        this.state[2] = 0x3c6ef372;
        this.state[3] = 0xa54ff53a;
        this.state[4] = 0x510e527f;
        this.state[5] = 0x9b05688c;
        this.state[6] = 0x1f83d9ab;
        this.state[7] = 0x5be0cd19;
        this.bufferLength = 0;
        this.bytesHashed = 0;
        this.finished = false;
        return this;
    }
    // Cleans internal buffers and re-initializes hash state.
    clean() {
        for (let i = 0; i < this.buffer.length; i++) {
            this.buffer[i] = 0;
        }
        for (let i = 0; i < this.temp.length; i++) {
            this.temp[i] = 0;
        }
        this.reset();
    }
    // Updates hash state with the given data.
    //
    // Optionally, length of the data can be specified to hash
    // fewer bytes than data.length.
    //
    // Throws error when trying to update already finalized hash:
    // instance must be reset to use it again.
    update(data, dataLength = data.length) {
        if (this.finished) {
            throw new Error("SHA256: can't update because hash was finished.");
        }
        let dataPos = 0;
        this.bytesHashed += dataLength;
        if (this.bufferLength > 0) {
            while (this.bufferLength < 64 && dataLength > 0) {
                this.buffer[this.bufferLength++] = data[dataPos++];
                dataLength--;
            }
            if (this.bufferLength === 64) {
                hashBlocks(this.temp, this.state, this.buffer, 0, 64);
                this.bufferLength = 0;
            }
        }
        if (dataLength >= 64) {
            dataPos = hashBlocks(this.temp, this.state, data, dataPos, dataLength);
            dataLength %= 64;
        }
        while (dataLength > 0) {
            this.buffer[this.bufferLength++] = data[dataPos++];
            dataLength--;
        }
        return this;
    }
    // Finalizes hash state and puts hash into out.
    //
    // If hash was already finalized, puts the same value.
    finish(out) {
        if (!this.finished) {
            const bytesHashed = this.bytesHashed;
            const left = this.bufferLength;
            const bitLenHi = (bytesHashed / 0x20000000) | 0;
            const bitLenLo = bytesHashed << 3;
            const padLength = bytesHashed % 64 < 56 ? 64 : 128;
            this.buffer[left] = 0x80;
            for (let i = left + 1; i < padLength - 8; i++) {
                this.buffer[i] = 0;
            }
            this.buffer[padLength - 8] = (bitLenHi >>> 24) & 0xff;
            this.buffer[padLength - 7] = (bitLenHi >>> 16) & 0xff;
            this.buffer[padLength - 6] = (bitLenHi >>> 8) & 0xff;
            this.buffer[padLength - 5] = (bitLenHi >>> 0) & 0xff;
            this.buffer[padLength - 4] = (bitLenLo >>> 24) & 0xff;
            this.buffer[padLength - 3] = (bitLenLo >>> 16) & 0xff;
            this.buffer[padLength - 2] = (bitLenLo >>> 8) & 0xff;
            this.buffer[padLength - 1] = (bitLenLo >>> 0) & 0xff;
            hashBlocks(this.temp, this.state, this.buffer, 0, padLength);
            this.finished = true;
        }
        for (let i = 0; i < 8; i++) {
            out[i * 4 + 0] = (this.state[i] >>> 24) & 0xff;
            out[i * 4 + 1] = (this.state[i] >>> 16) & 0xff;
            out[i * 4 + 2] = (this.state[i] >>> 8) & 0xff;
            out[i * 4 + 3] = (this.state[i] >>> 0) & 0xff;
        }
        return this;
    }
    // Returns the final hash digest.
    digest() {
        const out = new Uint8Array(this.digestLength);
        this.finish(out);
        return out;
    }
    // Internal function for use in HMAC for optimization.
    _saveState(out) {
        for (let i = 0; i < this.state.length; i++) {
            out[i] = this.state[i];
        }
    }
    // Internal function for use in HMAC for optimization.
    _restoreState(from, bytesHashed) {
        for (let i = 0; i < this.state.length; i++) {
            this.state[i] = from[i];
        }
        this.bytesHashed = bytesHashed;
        this.finished = false;
        this.bufferLength = 0;
    }
}
// Returns SHA256 hash of data.
function sha256(data) {
    const h = new HashSha256().update(data);
    const digest = h.digest();
    h.clean();
    return digest;
}

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
function sha512(data) {
    return hash$1(data);
}
function hmac(digest, blockSize, key, message) {
    if (key.byteLength > blockSize) {
        key = digest(key);
    }
    if (key.byteLength < blockSize) {
        const k = key;
        key = new Uint8Array(blockSize);
        key.set(k, 0);
    }
    const okp = new Uint8Array(blockSize);
    const ikp = new Uint8Array(blockSize);
    for (let i = 0; i < blockSize; i++) {
        ikp[i] = key[i] ^ 0x36;
        okp[i] = key[i] ^ 0x5c;
    }
    const b1 = new Uint8Array(blockSize + message.byteLength);
    b1.set(ikp, 0);
    b1.set(message, blockSize);
    const h0 = digest(b1);
    const b2 = new Uint8Array(blockSize + h0.length);
    b2.set(okp, 0);
    b2.set(h0, blockSize);
    return digest(b2);
}
function hmacSha512(key, message) {
    return hmac(sha512, 128, key, message);
}
function hmacSha256(key, message) {
    return hmac(sha256, 64, key, message);
}
/**
 * HMAC-SHA512-SHA256 (see RFC 5869).
 */
function kdfKw(args) {
    return kdf(args.outputLength, args.ikm, args.salt, args.info);
}
function kdf(outputLength, ikm, salt, info) {
    salt = salt !== null && salt !== void 0 ? salt : new Uint8Array(64);
    // extract
    const prk = hmacSha512(salt, ikm);
    info = info !== null && info !== void 0 ? info : new Uint8Array(0);
    // expand
    const N = Math.ceil(outputLength / 32);
    const output = new Uint8Array(N * 32);
    for (let i = 0; i < N; i++) {
        let buf;
        if (i == 0) {
            buf = new Uint8Array(info.byteLength + 1);
            buf.set(info, 0);
        }
        else {
            buf = new Uint8Array(info.byteLength + 1 + 32);
            for (let j = 0; j < 32; j++) {
                buf[j] = output[(i - 1) * 32 + j];
            }
            buf.set(info, 32);
        }
        buf[buf.length - 1] = i + 1;
        const chunk = hmacSha256(prk, buf);
        output.set(chunk, i * 32);
    }
    return output.slice(0, outputLength);
}

function getDefaultExportFromCjs (x) {
	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}

var BigInteger = {exports: {}};

(function (module) {
	var bigInt = (function (undefined$1) {

	    var BASE = 1e7,
	        LOG_BASE = 7,
	        MAX_INT = 9007199254740992,
	        MAX_INT_ARR = smallToArray(MAX_INT),
	        DEFAULT_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz";

	    var supportsNativeBigInt = typeof BigInt === "function";

	    function Integer(v, radix, alphabet, caseSensitive) {
	        if (typeof v === "undefined") return Integer[0];
	        if (typeof radix !== "undefined") return +radix === 10 && !alphabet ? parseValue(v) : parseBase(v, radix, alphabet, caseSensitive);
	        return parseValue(v);
	    }

	    function BigInteger(value, sign) {
	        this.value = value;
	        this.sign = sign;
	        this.isSmall = false;
	    }
	    BigInteger.prototype = Object.create(Integer.prototype);

	    function SmallInteger(value) {
	        this.value = value;
	        this.sign = value < 0;
	        this.isSmall = true;
	    }
	    SmallInteger.prototype = Object.create(Integer.prototype);

	    function NativeBigInt(value) {
	        this.value = value;
	    }
	    NativeBigInt.prototype = Object.create(Integer.prototype);

	    function isPrecise(n) {
	        return -MAX_INT < n && n < MAX_INT;
	    }

	    function smallToArray(n) { // For performance reasons doesn't reference BASE, need to change this function if BASE changes
	        if (n < 1e7)
	            return [n];
	        if (n < 1e14)
	            return [n % 1e7, Math.floor(n / 1e7)];
	        return [n % 1e7, Math.floor(n / 1e7) % 1e7, Math.floor(n / 1e14)];
	    }

	    function arrayToSmall(arr) { // If BASE changes this function may need to change
	        trim(arr);
	        var length = arr.length;
	        if (length < 4 && compareAbs(arr, MAX_INT_ARR) < 0) {
	            switch (length) {
	                case 0: return 0;
	                case 1: return arr[0];
	                case 2: return arr[0] + arr[1] * BASE;
	                default: return arr[0] + (arr[1] + arr[2] * BASE) * BASE;
	            }
	        }
	        return arr;
	    }

	    function trim(v) {
	        var i = v.length;
	        while (v[--i] === 0);
	        v.length = i + 1;
	    }

	    function createArray(length) { // function shamelessly stolen from Yaffle's library https://github.com/Yaffle/BigInteger
	        var x = new Array(length);
	        var i = -1;
	        while (++i < length) {
	            x[i] = 0;
	        }
	        return x;
	    }

	    function truncate(n) {
	        if (n > 0) return Math.floor(n);
	        return Math.ceil(n);
	    }

	    function add(a, b) { // assumes a and b are arrays with a.length >= b.length
	        var l_a = a.length,
	            l_b = b.length,
	            r = new Array(l_a),
	            carry = 0,
	            base = BASE,
	            sum, i;
	        for (i = 0; i < l_b; i++) {
	            sum = a[i] + b[i] + carry;
	            carry = sum >= base ? 1 : 0;
	            r[i] = sum - carry * base;
	        }
	        while (i < l_a) {
	            sum = a[i] + carry;
	            carry = sum === base ? 1 : 0;
	            r[i++] = sum - carry * base;
	        }
	        if (carry > 0) r.push(carry);
	        return r;
	    }

	    function addAny(a, b) {
	        if (a.length >= b.length) return add(a, b);
	        return add(b, a);
	    }

	    function addSmall(a, carry) { // assumes a is array, carry is number with 0 <= carry < MAX_INT
	        var l = a.length,
	            r = new Array(l),
	            base = BASE,
	            sum, i;
	        for (i = 0; i < l; i++) {
	            sum = a[i] - base + carry;
	            carry = Math.floor(sum / base);
	            r[i] = sum - carry * base;
	            carry += 1;
	        }
	        while (carry > 0) {
	            r[i++] = carry % base;
	            carry = Math.floor(carry / base);
	        }
	        return r;
	    }

	    BigInteger.prototype.add = function (v) {
	        var n = parseValue(v);
	        if (this.sign !== n.sign) {
	            return this.subtract(n.negate());
	        }
	        var a = this.value, b = n.value;
	        if (n.isSmall) {
	            return new BigInteger(addSmall(a, Math.abs(b)), this.sign);
	        }
	        return new BigInteger(addAny(a, b), this.sign);
	    };
	    BigInteger.prototype.plus = BigInteger.prototype.add;

	    SmallInteger.prototype.add = function (v) {
	        var n = parseValue(v);
	        var a = this.value;
	        if (a < 0 !== n.sign) {
	            return this.subtract(n.negate());
	        }
	        var b = n.value;
	        if (n.isSmall) {
	            if (isPrecise(a + b)) return new SmallInteger(a + b);
	            b = smallToArray(Math.abs(b));
	        }
	        return new BigInteger(addSmall(b, Math.abs(a)), a < 0);
	    };
	    SmallInteger.prototype.plus = SmallInteger.prototype.add;

	    NativeBigInt.prototype.add = function (v) {
	        return new NativeBigInt(this.value + parseValue(v).value);
	    };
	    NativeBigInt.prototype.plus = NativeBigInt.prototype.add;

	    function subtract(a, b) { // assumes a and b are arrays with a >= b
	        var a_l = a.length,
	            b_l = b.length,
	            r = new Array(a_l),
	            borrow = 0,
	            base = BASE,
	            i, difference;
	        for (i = 0; i < b_l; i++) {
	            difference = a[i] - borrow - b[i];
	            if (difference < 0) {
	                difference += base;
	                borrow = 1;
	            } else borrow = 0;
	            r[i] = difference;
	        }
	        for (i = b_l; i < a_l; i++) {
	            difference = a[i] - borrow;
	            if (difference < 0) difference += base;
	            else {
	                r[i++] = difference;
	                break;
	            }
	            r[i] = difference;
	        }
	        for (; i < a_l; i++) {
	            r[i] = a[i];
	        }
	        trim(r);
	        return r;
	    }

	    function subtractAny(a, b, sign) {
	        var value;
	        if (compareAbs(a, b) >= 0) {
	            value = subtract(a, b);
	        } else {
	            value = subtract(b, a);
	            sign = !sign;
	        }
	        value = arrayToSmall(value);
	        if (typeof value === "number") {
	            if (sign) value = -value;
	            return new SmallInteger(value);
	        }
	        return new BigInteger(value, sign);
	    }

	    function subtractSmall(a, b, sign) { // assumes a is array, b is number with 0 <= b < MAX_INT
	        var l = a.length,
	            r = new Array(l),
	            carry = -b,
	            base = BASE,
	            i, difference;
	        for (i = 0; i < l; i++) {
	            difference = a[i] + carry;
	            carry = Math.floor(difference / base);
	            difference %= base;
	            r[i] = difference < 0 ? difference + base : difference;
	        }
	        r = arrayToSmall(r);
	        if (typeof r === "number") {
	            if (sign) r = -r;
	            return new SmallInteger(r);
	        } return new BigInteger(r, sign);
	    }

	    BigInteger.prototype.subtract = function (v) {
	        var n = parseValue(v);
	        if (this.sign !== n.sign) {
	            return this.add(n.negate());
	        }
	        var a = this.value, b = n.value;
	        if (n.isSmall)
	            return subtractSmall(a, Math.abs(b), this.sign);
	        return subtractAny(a, b, this.sign);
	    };
	    BigInteger.prototype.minus = BigInteger.prototype.subtract;

	    SmallInteger.prototype.subtract = function (v) {
	        var n = parseValue(v);
	        var a = this.value;
	        if (a < 0 !== n.sign) {
	            return this.add(n.negate());
	        }
	        var b = n.value;
	        if (n.isSmall) {
	            return new SmallInteger(a - b);
	        }
	        return subtractSmall(b, Math.abs(a), a >= 0);
	    };
	    SmallInteger.prototype.minus = SmallInteger.prototype.subtract;

	    NativeBigInt.prototype.subtract = function (v) {
	        return new NativeBigInt(this.value - parseValue(v).value);
	    };
	    NativeBigInt.prototype.minus = NativeBigInt.prototype.subtract;

	    BigInteger.prototype.negate = function () {
	        return new BigInteger(this.value, !this.sign);
	    };
	    SmallInteger.prototype.negate = function () {
	        var sign = this.sign;
	        var small = new SmallInteger(-this.value);
	        small.sign = !sign;
	        return small;
	    };
	    NativeBigInt.prototype.negate = function () {
	        return new NativeBigInt(-this.value);
	    };

	    BigInteger.prototype.abs = function () {
	        return new BigInteger(this.value, false);
	    };
	    SmallInteger.prototype.abs = function () {
	        return new SmallInteger(Math.abs(this.value));
	    };
	    NativeBigInt.prototype.abs = function () {
	        return new NativeBigInt(this.value >= 0 ? this.value : -this.value);
	    };


	    function multiplyLong(a, b) {
	        var a_l = a.length,
	            b_l = b.length,
	            l = a_l + b_l,
	            r = createArray(l),
	            base = BASE,
	            product, carry, i, a_i, b_j;
	        for (i = 0; i < a_l; ++i) {
	            a_i = a[i];
	            for (var j = 0; j < b_l; ++j) {
	                b_j = b[j];
	                product = a_i * b_j + r[i + j];
	                carry = Math.floor(product / base);
	                r[i + j] = product - carry * base;
	                r[i + j + 1] += carry;
	            }
	        }
	        trim(r);
	        return r;
	    }

	    function multiplySmall(a, b) { // assumes a is array, b is number with |b| < BASE
	        var l = a.length,
	            r = new Array(l),
	            base = BASE,
	            carry = 0,
	            product, i;
	        for (i = 0; i < l; i++) {
	            product = a[i] * b + carry;
	            carry = Math.floor(product / base);
	            r[i] = product - carry * base;
	        }
	        while (carry > 0) {
	            r[i++] = carry % base;
	            carry = Math.floor(carry / base);
	        }
	        return r;
	    }

	    function shiftLeft(x, n) {
	        var r = [];
	        while (n-- > 0) r.push(0);
	        return r.concat(x);
	    }

	    function multiplyKaratsuba(x, y) {
	        var n = Math.max(x.length, y.length);

	        if (n <= 30) return multiplyLong(x, y);
	        n = Math.ceil(n / 2);

	        var b = x.slice(n),
	            a = x.slice(0, n),
	            d = y.slice(n),
	            c = y.slice(0, n);

	        var ac = multiplyKaratsuba(a, c),
	            bd = multiplyKaratsuba(b, d),
	            abcd = multiplyKaratsuba(addAny(a, b), addAny(c, d));

	        var product = addAny(addAny(ac, shiftLeft(subtract(subtract(abcd, ac), bd), n)), shiftLeft(bd, 2 * n));
	        trim(product);
	        return product;
	    }

	    // The following function is derived from a surface fit of a graph plotting the performance difference
	    // between long multiplication and karatsuba multiplication versus the lengths of the two arrays.
	    function useKaratsuba(l1, l2) {
	        return -0.012 * l1 - 0.012 * l2 + 0.000015 * l1 * l2 > 0;
	    }

	    BigInteger.prototype.multiply = function (v) {
	        var n = parseValue(v),
	            a = this.value, b = n.value,
	            sign = this.sign !== n.sign,
	            abs;
	        if (n.isSmall) {
	            if (b === 0) return Integer[0];
	            if (b === 1) return this;
	            if (b === -1) return this.negate();
	            abs = Math.abs(b);
	            if (abs < BASE) {
	                return new BigInteger(multiplySmall(a, abs), sign);
	            }
	            b = smallToArray(abs);
	        }
	        if (useKaratsuba(a.length, b.length)) // Karatsuba is only faster for certain array sizes
	            return new BigInteger(multiplyKaratsuba(a, b), sign);
	        return new BigInteger(multiplyLong(a, b), sign);
	    };

	    BigInteger.prototype.times = BigInteger.prototype.multiply;

	    function multiplySmallAndArray(a, b, sign) { // a >= 0
	        if (a < BASE) {
	            return new BigInteger(multiplySmall(b, a), sign);
	        }
	        return new BigInteger(multiplyLong(b, smallToArray(a)), sign);
	    }
	    SmallInteger.prototype._multiplyBySmall = function (a) {
	        if (isPrecise(a.value * this.value)) {
	            return new SmallInteger(a.value * this.value);
	        }
	        return multiplySmallAndArray(Math.abs(a.value), smallToArray(Math.abs(this.value)), this.sign !== a.sign);
	    };
	    BigInteger.prototype._multiplyBySmall = function (a) {
	        if (a.value === 0) return Integer[0];
	        if (a.value === 1) return this;
	        if (a.value === -1) return this.negate();
	        return multiplySmallAndArray(Math.abs(a.value), this.value, this.sign !== a.sign);
	    };
	    SmallInteger.prototype.multiply = function (v) {
	        return parseValue(v)._multiplyBySmall(this);
	    };
	    SmallInteger.prototype.times = SmallInteger.prototype.multiply;

	    NativeBigInt.prototype.multiply = function (v) {
	        return new NativeBigInt(this.value * parseValue(v).value);
	    };
	    NativeBigInt.prototype.times = NativeBigInt.prototype.multiply;

	    function square(a) {
	        //console.assert(2 * BASE * BASE < MAX_INT);
	        var l = a.length,
	            r = createArray(l + l),
	            base = BASE,
	            product, carry, i, a_i, a_j;
	        for (i = 0; i < l; i++) {
	            a_i = a[i];
	            carry = 0 - a_i * a_i;
	            for (var j = i; j < l; j++) {
	                a_j = a[j];
	                product = 2 * (a_i * a_j) + r[i + j] + carry;
	                carry = Math.floor(product / base);
	                r[i + j] = product - carry * base;
	            }
	            r[i + l] = carry;
	        }
	        trim(r);
	        return r;
	    }

	    BigInteger.prototype.square = function () {
	        return new BigInteger(square(this.value), false);
	    };

	    SmallInteger.prototype.square = function () {
	        var value = this.value * this.value;
	        if (isPrecise(value)) return new SmallInteger(value);
	        return new BigInteger(square(smallToArray(Math.abs(this.value))), false);
	    };

	    NativeBigInt.prototype.square = function (v) {
	        return new NativeBigInt(this.value * this.value);
	    };

	    function divMod1(a, b) { // Left over from previous version. Performs faster than divMod2 on smaller input sizes.
	        var a_l = a.length,
	            b_l = b.length,
	            base = BASE,
	            result = createArray(b.length),
	            divisorMostSignificantDigit = b[b_l - 1],
	            // normalization
	            lambda = Math.ceil(base / (2 * divisorMostSignificantDigit)),
	            remainder = multiplySmall(a, lambda),
	            divisor = multiplySmall(b, lambda),
	            quotientDigit, shift, carry, borrow, i, l, q;
	        if (remainder.length <= a_l) remainder.push(0);
	        divisor.push(0);
	        divisorMostSignificantDigit = divisor[b_l - 1];
	        for (shift = a_l - b_l; shift >= 0; shift--) {
	            quotientDigit = base - 1;
	            if (remainder[shift + b_l] !== divisorMostSignificantDigit) {
	                quotientDigit = Math.floor((remainder[shift + b_l] * base + remainder[shift + b_l - 1]) / divisorMostSignificantDigit);
	            }
	            // quotientDigit <= base - 1
	            carry = 0;
	            borrow = 0;
	            l = divisor.length;
	            for (i = 0; i < l; i++) {
	                carry += quotientDigit * divisor[i];
	                q = Math.floor(carry / base);
	                borrow += remainder[shift + i] - (carry - q * base);
	                carry = q;
	                if (borrow < 0) {
	                    remainder[shift + i] = borrow + base;
	                    borrow = -1;
	                } else {
	                    remainder[shift + i] = borrow;
	                    borrow = 0;
	                }
	            }
	            while (borrow !== 0) {
	                quotientDigit -= 1;
	                carry = 0;
	                for (i = 0; i < l; i++) {
	                    carry += remainder[shift + i] - base + divisor[i];
	                    if (carry < 0) {
	                        remainder[shift + i] = carry + base;
	                        carry = 0;
	                    } else {
	                        remainder[shift + i] = carry;
	                        carry = 1;
	                    }
	                }
	                borrow += carry;
	            }
	            result[shift] = quotientDigit;
	        }
	        // denormalization
	        remainder = divModSmall(remainder, lambda)[0];
	        return [arrayToSmall(result), arrayToSmall(remainder)];
	    }

	    function divMod2(a, b) { // Implementation idea shamelessly stolen from Silent Matt's library http://silentmatt.com/biginteger/
	        // Performs faster than divMod1 on larger input sizes.
	        var a_l = a.length,
	            b_l = b.length,
	            result = [],
	            part = [],
	            base = BASE,
	            guess, xlen, highx, highy, check;
	        while (a_l) {
	            part.unshift(a[--a_l]);
	            trim(part);
	            if (compareAbs(part, b) < 0) {
	                result.push(0);
	                continue;
	            }
	            xlen = part.length;
	            highx = part[xlen - 1] * base + part[xlen - 2];
	            highy = b[b_l - 1] * base + b[b_l - 2];
	            if (xlen > b_l) {
	                highx = (highx + 1) * base;
	            }
	            guess = Math.ceil(highx / highy);
	            do {
	                check = multiplySmall(b, guess);
	                if (compareAbs(check, part) <= 0) break;
	                guess--;
	            } while (guess);
	            result.push(guess);
	            part = subtract(part, check);
	        }
	        result.reverse();
	        return [arrayToSmall(result), arrayToSmall(part)];
	    }

	    function divModSmall(value, lambda) {
	        var length = value.length,
	            quotient = createArray(length),
	            base = BASE,
	            i, q, remainder, divisor;
	        remainder = 0;
	        for (i = length - 1; i >= 0; --i) {
	            divisor = remainder * base + value[i];
	            q = truncate(divisor / lambda);
	            remainder = divisor - q * lambda;
	            quotient[i] = q | 0;
	        }
	        return [quotient, remainder | 0];
	    }

	    function divModAny(self, v) {
	        var value, n = parseValue(v);
	        if (supportsNativeBigInt) {
	            return [new NativeBigInt(self.value / n.value), new NativeBigInt(self.value % n.value)];
	        }
	        var a = self.value, b = n.value;
	        var quotient;
	        if (b === 0) throw new Error("Cannot divide by zero");
	        if (self.isSmall) {
	            if (n.isSmall) {
	                return [new SmallInteger(truncate(a / b)), new SmallInteger(a % b)];
	            }
	            return [Integer[0], self];
	        }
	        if (n.isSmall) {
	            if (b === 1) return [self, Integer[0]];
	            if (b == -1) return [self.negate(), Integer[0]];
	            var abs = Math.abs(b);
	            if (abs < BASE) {
	                value = divModSmall(a, abs);
	                quotient = arrayToSmall(value[0]);
	                var remainder = value[1];
	                if (self.sign) remainder = -remainder;
	                if (typeof quotient === "number") {
	                    if (self.sign !== n.sign) quotient = -quotient;
	                    return [new SmallInteger(quotient), new SmallInteger(remainder)];
	                }
	                return [new BigInteger(quotient, self.sign !== n.sign), new SmallInteger(remainder)];
	            }
	            b = smallToArray(abs);
	        }
	        var comparison = compareAbs(a, b);
	        if (comparison === -1) return [Integer[0], self];
	        if (comparison === 0) return [Integer[self.sign === n.sign ? 1 : -1], Integer[0]];

	        // divMod1 is faster on smaller input sizes
	        if (a.length + b.length <= 200)
	            value = divMod1(a, b);
	        else value = divMod2(a, b);

	        quotient = value[0];
	        var qSign = self.sign !== n.sign,
	            mod = value[1],
	            mSign = self.sign;
	        if (typeof quotient === "number") {
	            if (qSign) quotient = -quotient;
	            quotient = new SmallInteger(quotient);
	        } else quotient = new BigInteger(quotient, qSign);
	        if (typeof mod === "number") {
	            if (mSign) mod = -mod;
	            mod = new SmallInteger(mod);
	        } else mod = new BigInteger(mod, mSign);
	        return [quotient, mod];
	    }

	    BigInteger.prototype.divmod = function (v) {
	        var result = divModAny(this, v);
	        return {
	            quotient: result[0],
	            remainder: result[1]
	        };
	    };
	    NativeBigInt.prototype.divmod = SmallInteger.prototype.divmod = BigInteger.prototype.divmod;


	    BigInteger.prototype.divide = function (v) {
	        return divModAny(this, v)[0];
	    };
	    NativeBigInt.prototype.over = NativeBigInt.prototype.divide = function (v) {
	        return new NativeBigInt(this.value / parseValue(v).value);
	    };
	    SmallInteger.prototype.over = SmallInteger.prototype.divide = BigInteger.prototype.over = BigInteger.prototype.divide;

	    BigInteger.prototype.mod = function (v) {
	        return divModAny(this, v)[1];
	    };
	    NativeBigInt.prototype.mod = NativeBigInt.prototype.remainder = function (v) {
	        return new NativeBigInt(this.value % parseValue(v).value);
	    };
	    SmallInteger.prototype.remainder = SmallInteger.prototype.mod = BigInteger.prototype.remainder = BigInteger.prototype.mod;

	    BigInteger.prototype.pow = function (v) {
	        var n = parseValue(v),
	            a = this.value,
	            b = n.value,
	            value, x, y;
	        if (b === 0) return Integer[1];
	        if (a === 0) return Integer[0];
	        if (a === 1) return Integer[1];
	        if (a === -1) return n.isEven() ? Integer[1] : Integer[-1];
	        if (n.sign) {
	            return Integer[0];
	        }
	        if (!n.isSmall) throw new Error("The exponent " + n.toString() + " is too large.");
	        if (this.isSmall) {
	            if (isPrecise(value = Math.pow(a, b)))
	                return new SmallInteger(truncate(value));
	        }
	        x = this;
	        y = Integer[1];
	        while (true) {
	            if (b & 1 === 1) {
	                y = y.times(x);
	                --b;
	            }
	            if (b === 0) break;
	            b /= 2;
	            x = x.square();
	        }
	        return y;
	    };
	    SmallInteger.prototype.pow = BigInteger.prototype.pow;

	    NativeBigInt.prototype.pow = function (v) {
	        var n = parseValue(v);
	        var a = this.value, b = n.value;
	        var _0 = BigInt(0), _1 = BigInt(1), _2 = BigInt(2);
	        if (b === _0) return Integer[1];
	        if (a === _0) return Integer[0];
	        if (a === _1) return Integer[1];
	        if (a === BigInt(-1)) return n.isEven() ? Integer[1] : Integer[-1];
	        if (n.isNegative()) return new NativeBigInt(_0);
	        var x = this;
	        var y = Integer[1];
	        while (true) {
	            if ((b & _1) === _1) {
	                y = y.times(x);
	                --b;
	            }
	            if (b === _0) break;
	            b /= _2;
	            x = x.square();
	        }
	        return y;
	    };

	    BigInteger.prototype.modPow = function (exp, mod) {
	        exp = parseValue(exp);
	        mod = parseValue(mod);
	        if (mod.isZero()) throw new Error("Cannot take modPow with modulus 0");
	        var r = Integer[1],
	            base = this.mod(mod);
	        if (exp.isNegative()) {
	            exp = exp.multiply(Integer[-1]);
	            base = base.modInv(mod);
	        }
	        while (exp.isPositive()) {
	            if (base.isZero()) return Integer[0];
	            if (exp.isOdd()) r = r.multiply(base).mod(mod);
	            exp = exp.divide(2);
	            base = base.square().mod(mod);
	        }
	        return r;
	    };
	    NativeBigInt.prototype.modPow = SmallInteger.prototype.modPow = BigInteger.prototype.modPow;

	    function compareAbs(a, b) {
	        if (a.length !== b.length) {
	            return a.length > b.length ? 1 : -1;
	        }
	        for (var i = a.length - 1; i >= 0; i--) {
	            if (a[i] !== b[i]) return a[i] > b[i] ? 1 : -1;
	        }
	        return 0;
	    }

	    BigInteger.prototype.compareAbs = function (v) {
	        var n = parseValue(v),
	            a = this.value,
	            b = n.value;
	        if (n.isSmall) return 1;
	        return compareAbs(a, b);
	    };
	    SmallInteger.prototype.compareAbs = function (v) {
	        var n = parseValue(v),
	            a = Math.abs(this.value),
	            b = n.value;
	        if (n.isSmall) {
	            b = Math.abs(b);
	            return a === b ? 0 : a > b ? 1 : -1;
	        }
	        return -1;
	    };
	    NativeBigInt.prototype.compareAbs = function (v) {
	        var a = this.value;
	        var b = parseValue(v).value;
	        a = a >= 0 ? a : -a;
	        b = b >= 0 ? b : -b;
	        return a === b ? 0 : a > b ? 1 : -1;
	    };

	    BigInteger.prototype.compare = function (v) {
	        // See discussion about comparison with Infinity:
	        // https://github.com/peterolson/BigInteger.js/issues/61
	        if (v === Infinity) {
	            return -1;
	        }
	        if (v === -Infinity) {
	            return 1;
	        }

	        var n = parseValue(v),
	            a = this.value,
	            b = n.value;
	        if (this.sign !== n.sign) {
	            return n.sign ? 1 : -1;
	        }
	        if (n.isSmall) {
	            return this.sign ? -1 : 1;
	        }
	        return compareAbs(a, b) * (this.sign ? -1 : 1);
	    };
	    BigInteger.prototype.compareTo = BigInteger.prototype.compare;

	    SmallInteger.prototype.compare = function (v) {
	        if (v === Infinity) {
	            return -1;
	        }
	        if (v === -Infinity) {
	            return 1;
	        }

	        var n = parseValue(v),
	            a = this.value,
	            b = n.value;
	        if (n.isSmall) {
	            return a == b ? 0 : a > b ? 1 : -1;
	        }
	        if (a < 0 !== n.sign) {
	            return a < 0 ? -1 : 1;
	        }
	        return a < 0 ? 1 : -1;
	    };
	    SmallInteger.prototype.compareTo = SmallInteger.prototype.compare;

	    NativeBigInt.prototype.compare = function (v) {
	        if (v === Infinity) {
	            return -1;
	        }
	        if (v === -Infinity) {
	            return 1;
	        }
	        var a = this.value;
	        var b = parseValue(v).value;
	        return a === b ? 0 : a > b ? 1 : -1;
	    };
	    NativeBigInt.prototype.compareTo = NativeBigInt.prototype.compare;

	    BigInteger.prototype.equals = function (v) {
	        return this.compare(v) === 0;
	    };
	    NativeBigInt.prototype.eq = NativeBigInt.prototype.equals = SmallInteger.prototype.eq = SmallInteger.prototype.equals = BigInteger.prototype.eq = BigInteger.prototype.equals;

	    BigInteger.prototype.notEquals = function (v) {
	        return this.compare(v) !== 0;
	    };
	    NativeBigInt.prototype.neq = NativeBigInt.prototype.notEquals = SmallInteger.prototype.neq = SmallInteger.prototype.notEquals = BigInteger.prototype.neq = BigInteger.prototype.notEquals;

	    BigInteger.prototype.greater = function (v) {
	        return this.compare(v) > 0;
	    };
	    NativeBigInt.prototype.gt = NativeBigInt.prototype.greater = SmallInteger.prototype.gt = SmallInteger.prototype.greater = BigInteger.prototype.gt = BigInteger.prototype.greater;

	    BigInteger.prototype.lesser = function (v) {
	        return this.compare(v) < 0;
	    };
	    NativeBigInt.prototype.lt = NativeBigInt.prototype.lesser = SmallInteger.prototype.lt = SmallInteger.prototype.lesser = BigInteger.prototype.lt = BigInteger.prototype.lesser;

	    BigInteger.prototype.greaterOrEquals = function (v) {
	        return this.compare(v) >= 0;
	    };
	    NativeBigInt.prototype.geq = NativeBigInt.prototype.greaterOrEquals = SmallInteger.prototype.geq = SmallInteger.prototype.greaterOrEquals = BigInteger.prototype.geq = BigInteger.prototype.greaterOrEquals;

	    BigInteger.prototype.lesserOrEquals = function (v) {
	        return this.compare(v) <= 0;
	    };
	    NativeBigInt.prototype.leq = NativeBigInt.prototype.lesserOrEquals = SmallInteger.prototype.leq = SmallInteger.prototype.lesserOrEquals = BigInteger.prototype.leq = BigInteger.prototype.lesserOrEquals;

	    BigInteger.prototype.isEven = function () {
	        return (this.value[0] & 1) === 0;
	    };
	    SmallInteger.prototype.isEven = function () {
	        return (this.value & 1) === 0;
	    };
	    NativeBigInt.prototype.isEven = function () {
	        return (this.value & BigInt(1)) === BigInt(0);
	    };

	    BigInteger.prototype.isOdd = function () {
	        return (this.value[0] & 1) === 1;
	    };
	    SmallInteger.prototype.isOdd = function () {
	        return (this.value & 1) === 1;
	    };
	    NativeBigInt.prototype.isOdd = function () {
	        return (this.value & BigInt(1)) === BigInt(1);
	    };

	    BigInteger.prototype.isPositive = function () {
	        return !this.sign;
	    };
	    SmallInteger.prototype.isPositive = function () {
	        return this.value > 0;
	    };
	    NativeBigInt.prototype.isPositive = SmallInteger.prototype.isPositive;

	    BigInteger.prototype.isNegative = function () {
	        return this.sign;
	    };
	    SmallInteger.prototype.isNegative = function () {
	        return this.value < 0;
	    };
	    NativeBigInt.prototype.isNegative = SmallInteger.prototype.isNegative;

	    BigInteger.prototype.isUnit = function () {
	        return false;
	    };
	    SmallInteger.prototype.isUnit = function () {
	        return Math.abs(this.value) === 1;
	    };
	    NativeBigInt.prototype.isUnit = function () {
	        return this.abs().value === BigInt(1);
	    };

	    BigInteger.prototype.isZero = function () {
	        return false;
	    };
	    SmallInteger.prototype.isZero = function () {
	        return this.value === 0;
	    };
	    NativeBigInt.prototype.isZero = function () {
	        return this.value === BigInt(0);
	    };

	    BigInteger.prototype.isDivisibleBy = function (v) {
	        var n = parseValue(v);
	        if (n.isZero()) return false;
	        if (n.isUnit()) return true;
	        if (n.compareAbs(2) === 0) return this.isEven();
	        return this.mod(n).isZero();
	    };
	    NativeBigInt.prototype.isDivisibleBy = SmallInteger.prototype.isDivisibleBy = BigInteger.prototype.isDivisibleBy;

	    function isBasicPrime(v) {
	        var n = v.abs();
	        if (n.isUnit()) return false;
	        if (n.equals(2) || n.equals(3) || n.equals(5)) return true;
	        if (n.isEven() || n.isDivisibleBy(3) || n.isDivisibleBy(5)) return false;
	        if (n.lesser(49)) return true;
	        // we don't know if it's prime: let the other functions figure it out
	    }

	    function millerRabinTest(n, a) {
	        var nPrev = n.prev(),
	            b = nPrev,
	            r = 0,
	            d, i, x;
	        while (b.isEven()) b = b.divide(2), r++;
	        next: for (i = 0; i < a.length; i++) {
	            if (n.lesser(a[i])) continue;
	            x = bigInt(a[i]).modPow(b, n);
	            if (x.isUnit() || x.equals(nPrev)) continue;
	            for (d = r - 1; d != 0; d--) {
	                x = x.square().mod(n);
	                if (x.isUnit()) return false;
	                if (x.equals(nPrev)) continue next;
	            }
	            return false;
	        }
	        return true;
	    }

	    // Set "strict" to true to force GRH-supported lower bound of 2*log(N)^2
	    BigInteger.prototype.isPrime = function (strict) {
	        var isPrime = isBasicPrime(this);
	        if (isPrime !== undefined$1) return isPrime;
	        var n = this.abs();
	        var bits = n.bitLength();
	        if (bits <= 64)
	            return millerRabinTest(n, [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]);
	        var logN = Math.log(2) * bits.toJSNumber();
	        var t = Math.ceil((strict === true) ? (2 * Math.pow(logN, 2)) : logN);
	        for (var a = [], i = 0; i < t; i++) {
	            a.push(bigInt(i + 2));
	        }
	        return millerRabinTest(n, a);
	    };
	    NativeBigInt.prototype.isPrime = SmallInteger.prototype.isPrime = BigInteger.prototype.isPrime;

	    BigInteger.prototype.isProbablePrime = function (iterations, rng) {
	        var isPrime = isBasicPrime(this);
	        if (isPrime !== undefined$1) return isPrime;
	        var n = this.abs();
	        var t = iterations === undefined$1 ? 5 : iterations;
	        for (var a = [], i = 0; i < t; i++) {
	            a.push(bigInt.randBetween(2, n.minus(2), rng));
	        }
	        return millerRabinTest(n, a);
	    };
	    NativeBigInt.prototype.isProbablePrime = SmallInteger.prototype.isProbablePrime = BigInteger.prototype.isProbablePrime;

	    BigInteger.prototype.modInv = function (n) {
	        var t = bigInt.zero, newT = bigInt.one, r = parseValue(n), newR = this.abs(), q, lastT, lastR;
	        while (!newR.isZero()) {
	            q = r.divide(newR);
	            lastT = t;
	            lastR = r;
	            t = newT;
	            r = newR;
	            newT = lastT.subtract(q.multiply(newT));
	            newR = lastR.subtract(q.multiply(newR));
	        }
	        if (!r.isUnit()) throw new Error(this.toString() + " and " + n.toString() + " are not co-prime");
	        if (t.compare(0) === -1) {
	            t = t.add(n);
	        }
	        if (this.isNegative()) {
	            return t.negate();
	        }
	        return t;
	    };

	    NativeBigInt.prototype.modInv = SmallInteger.prototype.modInv = BigInteger.prototype.modInv;

	    BigInteger.prototype.next = function () {
	        var value = this.value;
	        if (this.sign) {
	            return subtractSmall(value, 1, this.sign);
	        }
	        return new BigInteger(addSmall(value, 1), this.sign);
	    };
	    SmallInteger.prototype.next = function () {
	        var value = this.value;
	        if (value + 1 < MAX_INT) return new SmallInteger(value + 1);
	        return new BigInteger(MAX_INT_ARR, false);
	    };
	    NativeBigInt.prototype.next = function () {
	        return new NativeBigInt(this.value + BigInt(1));
	    };

	    BigInteger.prototype.prev = function () {
	        var value = this.value;
	        if (this.sign) {
	            return new BigInteger(addSmall(value, 1), true);
	        }
	        return subtractSmall(value, 1, this.sign);
	    };
	    SmallInteger.prototype.prev = function () {
	        var value = this.value;
	        if (value - 1 > -MAX_INT) return new SmallInteger(value - 1);
	        return new BigInteger(MAX_INT_ARR, true);
	    };
	    NativeBigInt.prototype.prev = function () {
	        return new NativeBigInt(this.value - BigInt(1));
	    };

	    var powersOfTwo = [1];
	    while (2 * powersOfTwo[powersOfTwo.length - 1] <= BASE) powersOfTwo.push(2 * powersOfTwo[powersOfTwo.length - 1]);
	    var powers2Length = powersOfTwo.length, highestPower2 = powersOfTwo[powers2Length - 1];

	    function shift_isSmall(n) {
	        return Math.abs(n) <= BASE;
	    }

	    BigInteger.prototype.shiftLeft = function (v) {
	        var n = parseValue(v).toJSNumber();
	        if (!shift_isSmall(n)) {
	            throw new Error(String(n) + " is too large for shifting.");
	        }
	        if (n < 0) return this.shiftRight(-n);
	        var result = this;
	        if (result.isZero()) return result;
	        while (n >= powers2Length) {
	            result = result.multiply(highestPower2);
	            n -= powers2Length - 1;
	        }
	        return result.multiply(powersOfTwo[n]);
	    };
	    NativeBigInt.prototype.shiftLeft = SmallInteger.prototype.shiftLeft = BigInteger.prototype.shiftLeft;

	    BigInteger.prototype.shiftRight = function (v) {
	        var remQuo;
	        var n = parseValue(v).toJSNumber();
	        if (!shift_isSmall(n)) {
	            throw new Error(String(n) + " is too large for shifting.");
	        }
	        if (n < 0) return this.shiftLeft(-n);
	        var result = this;
	        while (n >= powers2Length) {
	            if (result.isZero() || (result.isNegative() && result.isUnit())) return result;
	            remQuo = divModAny(result, highestPower2);
	            result = remQuo[1].isNegative() ? remQuo[0].prev() : remQuo[0];
	            n -= powers2Length - 1;
	        }
	        remQuo = divModAny(result, powersOfTwo[n]);
	        return remQuo[1].isNegative() ? remQuo[0].prev() : remQuo[0];
	    };
	    NativeBigInt.prototype.shiftRight = SmallInteger.prototype.shiftRight = BigInteger.prototype.shiftRight;

	    function bitwise(x, y, fn) {
	        y = parseValue(y);
	        var xSign = x.isNegative(), ySign = y.isNegative();
	        var xRem = xSign ? x.not() : x,
	            yRem = ySign ? y.not() : y;
	        var xDigit = 0, yDigit = 0;
	        var xDivMod = null, yDivMod = null;
	        var result = [];
	        while (!xRem.isZero() || !yRem.isZero()) {
	            xDivMod = divModAny(xRem, highestPower2);
	            xDigit = xDivMod[1].toJSNumber();
	            if (xSign) {
	                xDigit = highestPower2 - 1 - xDigit; // two's complement for negative numbers
	            }

	            yDivMod = divModAny(yRem, highestPower2);
	            yDigit = yDivMod[1].toJSNumber();
	            if (ySign) {
	                yDigit = highestPower2 - 1 - yDigit; // two's complement for negative numbers
	            }

	            xRem = xDivMod[0];
	            yRem = yDivMod[0];
	            result.push(fn(xDigit, yDigit));
	        }
	        var sum = fn(xSign ? 1 : 0, ySign ? 1 : 0) !== 0 ? bigInt(-1) : bigInt(0);
	        for (var i = result.length - 1; i >= 0; i -= 1) {
	            sum = sum.multiply(highestPower2).add(bigInt(result[i]));
	        }
	        return sum;
	    }

	    BigInteger.prototype.not = function () {
	        return this.negate().prev();
	    };
	    NativeBigInt.prototype.not = SmallInteger.prototype.not = BigInteger.prototype.not;

	    BigInteger.prototype.and = function (n) {
	        return bitwise(this, n, function (a, b) { return a & b; });
	    };
	    NativeBigInt.prototype.and = SmallInteger.prototype.and = BigInteger.prototype.and;

	    BigInteger.prototype.or = function (n) {
	        return bitwise(this, n, function (a, b) { return a | b; });
	    };
	    NativeBigInt.prototype.or = SmallInteger.prototype.or = BigInteger.prototype.or;

	    BigInteger.prototype.xor = function (n) {
	        return bitwise(this, n, function (a, b) { return a ^ b; });
	    };
	    NativeBigInt.prototype.xor = SmallInteger.prototype.xor = BigInteger.prototype.xor;

	    var LOBMASK_I = 1 << 30, LOBMASK_BI = (BASE & -BASE) * (BASE & -BASE) | LOBMASK_I;
	    function roughLOB(n) { // get lowestOneBit (rough)
	        // SmallInteger: return Min(lowestOneBit(n), 1 << 30)
	        // BigInteger: return Min(lowestOneBit(n), 1 << 14) [BASE=1e7]
	        var v = n.value,
	            x = typeof v === "number" ? v | LOBMASK_I :
	                typeof v === "bigint" ? v | BigInt(LOBMASK_I) :
	                    v[0] + v[1] * BASE | LOBMASK_BI;
	        return x & -x;
	    }

	    function integerLogarithm(value, base) {
	        if (base.compareTo(value) <= 0) {
	            var tmp = integerLogarithm(value, base.square(base));
	            var p = tmp.p;
	            var e = tmp.e;
	            var t = p.multiply(base);
	            return t.compareTo(value) <= 0 ? { p: t, e: e * 2 + 1 } : { p: p, e: e * 2 };
	        }
	        return { p: bigInt(1), e: 0 };
	    }

	    BigInteger.prototype.bitLength = function () {
	        var n = this;
	        if (n.compareTo(bigInt(0)) < 0) {
	            n = n.negate().subtract(bigInt(1));
	        }
	        if (n.compareTo(bigInt(0)) === 0) {
	            return bigInt(0);
	        }
	        return bigInt(integerLogarithm(n, bigInt(2)).e).add(bigInt(1));
	    };
	    NativeBigInt.prototype.bitLength = SmallInteger.prototype.bitLength = BigInteger.prototype.bitLength;

	    function max(a, b) {
	        a = parseValue(a);
	        b = parseValue(b);
	        return a.greater(b) ? a : b;
	    }
	    function min(a, b) {
	        a = parseValue(a);
	        b = parseValue(b);
	        return a.lesser(b) ? a : b;
	    }
	    function gcd(a, b) {
	        a = parseValue(a).abs();
	        b = parseValue(b).abs();
	        if (a.equals(b)) return a;
	        if (a.isZero()) return b;
	        if (b.isZero()) return a;
	        var c = Integer[1], d, t;
	        while (a.isEven() && b.isEven()) {
	            d = min(roughLOB(a), roughLOB(b));
	            a = a.divide(d);
	            b = b.divide(d);
	            c = c.multiply(d);
	        }
	        while (a.isEven()) {
	            a = a.divide(roughLOB(a));
	        }
	        do {
	            while (b.isEven()) {
	                b = b.divide(roughLOB(b));
	            }
	            if (a.greater(b)) {
	                t = b; b = a; a = t;
	            }
	            b = b.subtract(a);
	        } while (!b.isZero());
	        return c.isUnit() ? a : a.multiply(c);
	    }
	    function lcm(a, b) {
	        a = parseValue(a).abs();
	        b = parseValue(b).abs();
	        return a.divide(gcd(a, b)).multiply(b);
	    }
	    function randBetween(a, b, rng) {
	        a = parseValue(a);
	        b = parseValue(b);
	        var usedRNG = rng || Math.random;
	        var low = min(a, b), high = max(a, b);
	        var range = high.subtract(low).add(1);
	        if (range.isSmall) return low.add(Math.floor(usedRNG() * range));
	        var digits = toBase(range, BASE).value;
	        var result = [], restricted = true;
	        for (var i = 0; i < digits.length; i++) {
	            var top = restricted ? digits[i] + (i + 1 < digits.length ? digits[i + 1] / BASE : 0) : BASE;
	            var digit = truncate(usedRNG() * top);
	            result.push(digit);
	            if (digit < digits[i]) restricted = false;
	        }
	        return low.add(Integer.fromArray(result, BASE, false));
	    }

	    var parseBase = function (text, base, alphabet, caseSensitive) {
	        alphabet = alphabet || DEFAULT_ALPHABET;
	        text = String(text);
	        if (!caseSensitive) {
	            text = text.toLowerCase();
	            alphabet = alphabet.toLowerCase();
	        }
	        var length = text.length;
	        var i;
	        var absBase = Math.abs(base);
	        var alphabetValues = {};
	        for (i = 0; i < alphabet.length; i++) {
	            alphabetValues[alphabet[i]] = i;
	        }
	        for (i = 0; i < length; i++) {
	            var c = text[i];
	            if (c === "-") continue;
	            if (c in alphabetValues) {
	                if (alphabetValues[c] >= absBase) {
	                    if (c === "1" && absBase === 1) continue;
	                    throw new Error(c + " is not a valid digit in base " + base + ".");
	                }
	            }
	        }
	        base = parseValue(base);
	        var digits = [];
	        var isNegative = text[0] === "-";
	        for (i = isNegative ? 1 : 0; i < text.length; i++) {
	            var c = text[i];
	            if (c in alphabetValues) digits.push(parseValue(alphabetValues[c]));
	            else if (c === "<") {
	                var start = i;
	                do { i++; } while (text[i] !== ">" && i < text.length);
	                digits.push(parseValue(text.slice(start + 1, i)));
	            }
	            else throw new Error(c + " is not a valid character");
	        }
	        return parseBaseFromArray(digits, base, isNegative);
	    };

	    function parseBaseFromArray(digits, base, isNegative) {
	        var val = Integer[0], pow = Integer[1], i;
	        for (i = digits.length - 1; i >= 0; i--) {
	            val = val.add(digits[i].times(pow));
	            pow = pow.times(base);
	        }
	        return isNegative ? val.negate() : val;
	    }

	    function stringify(digit, alphabet) {
	        alphabet = alphabet || DEFAULT_ALPHABET;
	        if (digit < alphabet.length) {
	            return alphabet[digit];
	        }
	        return "<" + digit + ">";
	    }

	    function toBase(n, base) {
	        base = bigInt(base);
	        if (base.isZero()) {
	            if (n.isZero()) return { value: [0], isNegative: false };
	            throw new Error("Cannot convert nonzero numbers to base 0.");
	        }
	        if (base.equals(-1)) {
	            if (n.isZero()) return { value: [0], isNegative: false };
	            if (n.isNegative())
	                return {
	                    value: [].concat.apply([], Array.apply(null, Array(-n.toJSNumber()))
	                        .map(Array.prototype.valueOf, [1, 0])
	                    ),
	                    isNegative: false
	                };

	            var arr = Array.apply(null, Array(n.toJSNumber() - 1))
	                .map(Array.prototype.valueOf, [0, 1]);
	            arr.unshift([1]);
	            return {
	                value: [].concat.apply([], arr),
	                isNegative: false
	            };
	        }

	        var neg = false;
	        if (n.isNegative() && base.isPositive()) {
	            neg = true;
	            n = n.abs();
	        }
	        if (base.isUnit()) {
	            if (n.isZero()) return { value: [0], isNegative: false };

	            return {
	                value: Array.apply(null, Array(n.toJSNumber()))
	                    .map(Number.prototype.valueOf, 1),
	                isNegative: neg
	            };
	        }
	        var out = [];
	        var left = n, divmod;
	        while (left.isNegative() || left.compareAbs(base) >= 0) {
	            divmod = left.divmod(base);
	            left = divmod.quotient;
	            var digit = divmod.remainder;
	            if (digit.isNegative()) {
	                digit = base.minus(digit).abs();
	                left = left.next();
	            }
	            out.push(digit.toJSNumber());
	        }
	        out.push(left.toJSNumber());
	        return { value: out.reverse(), isNegative: neg };
	    }

	    function toBaseString(n, base, alphabet) {
	        var arr = toBase(n, base);
	        return (arr.isNegative ? "-" : "") + arr.value.map(function (x) {
	            return stringify(x, alphabet);
	        }).join('');
	    }

	    BigInteger.prototype.toArray = function (radix) {
	        return toBase(this, radix);
	    };

	    SmallInteger.prototype.toArray = function (radix) {
	        return toBase(this, radix);
	    };

	    NativeBigInt.prototype.toArray = function (radix) {
	        return toBase(this, radix);
	    };

	    BigInteger.prototype.toString = function (radix, alphabet) {
	        if (radix === undefined$1) radix = 10;
	        if (radix !== 10) return toBaseString(this, radix, alphabet);
	        var v = this.value, l = v.length, str = String(v[--l]), zeros = "0000000", digit;
	        while (--l >= 0) {
	            digit = String(v[l]);
	            str += zeros.slice(digit.length) + digit;
	        }
	        var sign = this.sign ? "-" : "";
	        return sign + str;
	    };

	    SmallInteger.prototype.toString = function (radix, alphabet) {
	        if (radix === undefined$1) radix = 10;
	        if (radix != 10) return toBaseString(this, radix, alphabet);
	        return String(this.value);
	    };

	    NativeBigInt.prototype.toString = SmallInteger.prototype.toString;

	    NativeBigInt.prototype.toJSON = BigInteger.prototype.toJSON = SmallInteger.prototype.toJSON = function () { return this.toString(); };

	    BigInteger.prototype.valueOf = function () {
	        return parseInt(this.toString(), 10);
	    };
	    BigInteger.prototype.toJSNumber = BigInteger.prototype.valueOf;

	    SmallInteger.prototype.valueOf = function () {
	        return this.value;
	    };
	    SmallInteger.prototype.toJSNumber = SmallInteger.prototype.valueOf;
	    NativeBigInt.prototype.valueOf = NativeBigInt.prototype.toJSNumber = function () {
	        return parseInt(this.toString(), 10);
	    };

	    function parseStringValue(v) {
	        if (isPrecise(+v)) {
	            var x = +v;
	            if (x === truncate(x))
	                return supportsNativeBigInt ? new NativeBigInt(BigInt(x)) : new SmallInteger(x);
	            throw new Error("Invalid integer: " + v);
	        }
	        var sign = v[0] === "-";
	        if (sign) v = v.slice(1);
	        var split = v.split(/e/i);
	        if (split.length > 2) throw new Error("Invalid integer: " + split.join("e"));
	        if (split.length === 2) {
	            var exp = split[1];
	            if (exp[0] === "+") exp = exp.slice(1);
	            exp = +exp;
	            if (exp !== truncate(exp) || !isPrecise(exp)) throw new Error("Invalid integer: " + exp + " is not a valid exponent.");
	            var text = split[0];
	            var decimalPlace = text.indexOf(".");
	            if (decimalPlace >= 0) {
	                exp -= text.length - decimalPlace - 1;
	                text = text.slice(0, decimalPlace) + text.slice(decimalPlace + 1);
	            }
	            if (exp < 0) throw new Error("Cannot include negative exponent part for integers");
	            text += (new Array(exp + 1)).join("0");
	            v = text;
	        }
	        var isValid = /^([0-9][0-9]*)$/.test(v);
	        if (!isValid) throw new Error("Invalid integer: " + v);
	        if (supportsNativeBigInt) {
	            return new NativeBigInt(BigInt(sign ? "-" + v : v));
	        }
	        var r = [], max = v.length, l = LOG_BASE, min = max - l;
	        while (max > 0) {
	            r.push(+v.slice(min, max));
	            min -= l;
	            if (min < 0) min = 0;
	            max -= l;
	        }
	        trim(r);
	        return new BigInteger(r, sign);
	    }

	    function parseNumberValue(v) {
	        if (supportsNativeBigInt) {
	            return new NativeBigInt(BigInt(v));
	        }
	        if (isPrecise(v)) {
	            if (v !== truncate(v)) throw new Error(v + " is not an integer.");
	            return new SmallInteger(v);
	        }
	        return parseStringValue(v.toString());
	    }

	    function parseValue(v) {
	        if (typeof v === "number") {
	            return parseNumberValue(v);
	        }
	        if (typeof v === "string") {
	            return parseStringValue(v);
	        }
	        if (typeof v === "bigint") {
	            return new NativeBigInt(v);
	        }
	        return v;
	    }
	    // Pre-define numbers in range [-999,999]
	    for (var i = 0; i < 1000; i++) {
	        Integer[i] = parseValue(i);
	        if (i > 0) Integer[-i] = parseValue(-i);
	    }
	    // Backwards compatibility
	    Integer.one = Integer[1];
	    Integer.zero = Integer[0];
	    Integer.minusOne = Integer[-1];
	    Integer.max = max;
	    Integer.min = min;
	    Integer.gcd = gcd;
	    Integer.lcm = lcm;
	    Integer.isInstance = function (x) { return x instanceof BigInteger || x instanceof SmallInteger || x instanceof NativeBigInt; };
	    Integer.randBetween = randBetween;

	    Integer.fromArray = function (digits, base, isNegative) {
	        return parseBaseFromArray(digits.map(parseValue), parseValue(base || 10), isNegative);
	    };

	    return Integer;
	})();

	// Node.js check
	if (module.hasOwnProperty("exports")) {
	    module.exports = bigInt;
	}
} (BigInteger));

var bigint = BigInteger.exports;

/*
 This file is part of GNU Taler
 (C) 2017-2019 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
var TalerProtocolTimestamp;
(function (TalerProtocolTimestamp) {
    function now() {
        return AbsoluteTime.toTimestamp(AbsoluteTime.now());
    }
    TalerProtocolTimestamp.now = now;
    function zero() {
        return {
            t_s: 0,
        };
    }
    TalerProtocolTimestamp.zero = zero;
    function never() {
        return {
            t_s: "never",
        };
    }
    TalerProtocolTimestamp.never = never;
    function fromSeconds(s) {
        return {
            t_s: s,
        };
    }
    TalerProtocolTimestamp.fromSeconds = fromSeconds;
    function min(t1, t2) {
        if (t1.t_s === "never") {
            return { t_s: t2.t_s };
        }
        if (t2.t_s === "never") {
            return { t_s: t2.t_s };
        }
        return { t_s: Math.min(t1.t_s, t2.t_s) };
    }
    TalerProtocolTimestamp.min = min;
})(TalerProtocolTimestamp || (TalerProtocolTimestamp = {}));
let timeshift = 0;
var Duration;
(function (Duration) {
    function getRemaining(deadline, now = AbsoluteTime.now()) {
        if (deadline.t_ms === "never") {
            return { d_ms: "forever" };
        }
        if (now.t_ms === "never") {
            throw Error("invalid argument for 'now'");
        }
        if (deadline.t_ms < now.t_ms) {
            return { d_ms: 0 };
        }
        return { d_ms: deadline.t_ms - now.t_ms };
    }
    Duration.getRemaining = getRemaining;
    function max(d1, d2) {
        return durationMax(d1, d2);
    }
    Duration.max = max;
    function min(d1, d2) {
        return durationMin(d1, d2);
    }
    Duration.min = min;
    function multiply(d1, n) {
        return durationMul(d1, n);
    }
    Duration.multiply = multiply;
    function toIntegerYears(d) {
        if (typeof d.d_ms !== "number") {
            throw Error("infinite duration");
        }
        return Math.ceil(d.d_ms / 1000 / 60 / 60 / 24 / 365);
    }
    Duration.toIntegerYears = toIntegerYears;
    Duration.fromSpec = durationFromSpec;
    function getForever() {
        return { d_ms: "forever" };
    }
    Duration.getForever = getForever;
    function getZero() {
        return { d_ms: 0 };
    }
    Duration.getZero = getZero;
    function fromTalerProtocolDuration(d) {
        if (d.d_us === "forever") {
            return {
                d_ms: "forever",
            };
        }
        return {
            d_ms: d.d_us / 1000,
        };
    }
    Duration.fromTalerProtocolDuration = fromTalerProtocolDuration;
    function toTalerProtocolDuration(d) {
        if (d.d_ms === "forever") {
            return {
                d_us: "forever",
            };
        }
        return {
            d_us: d.d_ms * 1000,
        };
    }
    Duration.toTalerProtocolDuration = toTalerProtocolDuration;
    function clamp(args) {
        return durationMax(durationMin(args.value, args.upper), args.lower);
    }
    Duration.clamp = clamp;
})(Duration || (Duration = {}));
var AbsoluteTime;
(function (AbsoluteTime) {
    function now() {
        return {
            t_ms: new Date().getTime() + timeshift,
        };
    }
    AbsoluteTime.now = now;
    function never() {
        return {
            t_ms: "never",
        };
    }
    AbsoluteTime.never = never;
    function cmp(t1, t2) {
        if (t1.t_ms === "never") {
            if (t2.t_ms === "never") {
                return 0;
            }
            return 1;
        }
        if (t2.t_ms === "never") {
            return -1;
        }
        if (t1.t_ms == t2.t_ms) {
            return 0;
        }
        if (t1.t_ms > t2.t_ms) {
            return 1;
        }
        return -1;
    }
    AbsoluteTime.cmp = cmp;
    function min(t1, t2) {
        if (t1.t_ms === "never") {
            return { t_ms: t2.t_ms };
        }
        if (t2.t_ms === "never") {
            return { t_ms: t2.t_ms };
        }
        return { t_ms: Math.min(t1.t_ms, t2.t_ms) };
    }
    AbsoluteTime.min = min;
    function max(t1, t2) {
        if (t1.t_ms === "never") {
            return { t_ms: "never" };
        }
        if (t2.t_ms === "never") {
            return { t_ms: "never" };
        }
        return { t_ms: Math.max(t1.t_ms, t2.t_ms) };
    }
    AbsoluteTime.max = max;
    function difference(t1, t2) {
        if (t1.t_ms === "never") {
            return { d_ms: "forever" };
        }
        if (t2.t_ms === "never") {
            return { d_ms: "forever" };
        }
        return { d_ms: Math.abs(t1.t_ms - t2.t_ms) };
    }
    AbsoluteTime.difference = difference;
    function isExpired(t) {
        return cmp(t, now()) <= 0;
    }
    AbsoluteTime.isExpired = isExpired;
    function fromTimestamp(t) {
        if (t.t_s === "never") {
            return { t_ms: "never" };
        }
        return {
            t_ms: t.t_s * 1000,
        };
    }
    AbsoluteTime.fromTimestamp = fromTimestamp;
    function toTimestamp(at) {
        if (at.t_ms === "never") {
            return { t_s: "never" };
        }
        return {
            t_s: Math.floor(at.t_ms / 1000),
        };
    }
    AbsoluteTime.toTimestamp = toTimestamp;
    function isBetween(t, start, end) {
        if (cmp(t, start) < 0) {
            return false;
        }
        if (cmp(t, end) > 0) {
            return false;
        }
        return true;
    }
    AbsoluteTime.isBetween = isBetween;
    function toIsoString(t) {
        if (t.t_ms === "never") {
            return "<never>";
        }
        else {
            return new Date(t.t_ms).toISOString();
        }
    }
    AbsoluteTime.toIsoString = toIsoString;
    function addDuration(t1, d) {
        if (t1.t_ms === "never" || d.d_ms === "forever") {
            return { t_ms: "never" };
        }
        return { t_ms: t1.t_ms + d.d_ms };
    }
    AbsoluteTime.addDuration = addDuration;
    function subtractDuraction(t1, d) {
        if (t1.t_ms === "never") {
            return { t_ms: "never" };
        }
        if (d.d_ms === "forever") {
            return { t_ms: 0 };
        }
        return { t_ms: Math.max(0, t1.t_ms - d.d_ms) };
    }
    AbsoluteTime.subtractDuraction = subtractDuraction;
    function stringify(t) {
        if (t.t_ms === "never") {
            return "never";
        }
        return new Date(t.t_ms).toISOString();
    }
    AbsoluteTime.stringify = stringify;
})(AbsoluteTime || (AbsoluteTime = {}));
const SECONDS = 1000;
const MINUTES = SECONDS * 60;
const HOURS = MINUTES * 60;
const DAYS = HOURS * 24;
const MONTHS = DAYS * 30;
const YEARS = DAYS * 365;
function durationFromSpec(spec) {
    var _a, _b, _c, _d, _e, _f;
    let d_ms = 0;
    d_ms += ((_a = spec.seconds) !== null && _a !== void 0 ? _a : 0) * SECONDS;
    d_ms += ((_b = spec.minutes) !== null && _b !== void 0 ? _b : 0) * MINUTES;
    d_ms += ((_c = spec.hours) !== null && _c !== void 0 ? _c : 0) * HOURS;
    d_ms += ((_d = spec.days) !== null && _d !== void 0 ? _d : 0) * DAYS;
    d_ms += ((_e = spec.months) !== null && _e !== void 0 ? _e : 0) * MONTHS;
    d_ms += ((_f = spec.years) !== null && _f !== void 0 ? _f : 0) * YEARS;
    return { d_ms };
}
function durationMin(d1, d2) {
    if (d1.d_ms === "forever") {
        return { d_ms: d2.d_ms };
    }
    if (d2.d_ms === "forever") {
        return { d_ms: d1.d_ms };
    }
    return { d_ms: Math.min(d1.d_ms, d2.d_ms) };
}
function durationMax(d1, d2) {
    if (d1.d_ms === "forever") {
        return { d_ms: "forever" };
    }
    if (d2.d_ms === "forever") {
        return { d_ms: "forever" };
    }
    return { d_ms: Math.max(d1.d_ms, d2.d_ms) };
}
function durationMul(d, n) {
    if (d.d_ms === "forever") {
        return { d_ms: "forever" };
    }
    return { d_ms: Math.round(d.d_ms * n) };
}
const codecForTimestamp = {
    decode(x, c) {
        // Compatibility, should be removed soon.
        const t_ms = x.t_ms;
        if (typeof t_ms === "string") {
            if (t_ms === "never") {
                return { t_s: "never" };
            }
        }
        else if (typeof t_ms === "number") {
            return { t_s: Math.floor(t_ms / 1000) };
        }
        const t_s = x.t_s;
        if (typeof t_s === "string") {
            if (t_s === "never") {
                return { t_s: "never" };
            }
            throw Error(`expected timestamp at ${renderContext(c)}`);
        }
        if (typeof t_s === "number") {
            return { t_s };
        }
        throw Error(`expected timestamp at ${renderContext(c)}`);
    },
};
const codecForDuration = {
    decode(x, c) {
        const d_us = x.d_us;
        if (typeof d_us === "string") {
            if (d_us === "forever") {
                return { d_us: "forever" };
            }
            throw Error(`expected duration at ${renderContext(c)}`);
        }
        if (typeof d_us === "number") {
            return { d_us };
        }
        throw Error(`expected duration at ${renderContext(c)}`);
    },
};

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
var DenomKeyType;
(function (DenomKeyType) {
    DenomKeyType["Rsa"] = "RSA";
    DenomKeyType["ClauseSchnorr"] = "CS";
})(DenomKeyType || (DenomKeyType = {}));
(function (DenomKeyType) {
    function toIntTag(t) {
        switch (t) {
            case DenomKeyType.Rsa:
                return 1;
            case DenomKeyType.ClauseSchnorr:
                return 2;
        }
    }
    DenomKeyType.toIntTag = toIntTag;
})(DenomKeyType || (DenomKeyType = {}));
const codecForRsaBlindedDenominationSignature = () => buildCodecForObject()
    .property("cipher", codecForConstString(DenomKeyType.Rsa))
    .property("blinded_rsa_signature", codecForString())
    .build("RsaBlindedDenominationSignature");
const codecForBlindedDenominationSignature = () => buildCodecForUnion()
    .discriminateOn("cipher")
    .alternative(DenomKeyType.Rsa, codecForRsaBlindedDenominationSignature())
    .build("BlindedDenominationSignature");
var DenominationPubKey;
(function (DenominationPubKey) {
    function cmp(p1, p2) {
        var _a, _b, _c, _d, _e, _f, _g, _h;
        if (p1.cipher < p2.cipher) {
            return -1;
        }
        else if (p1.cipher > p2.cipher) {
            return +1;
        }
        else if (p1.cipher === DenomKeyType.Rsa &&
            p2.cipher === DenomKeyType.Rsa) {
            if (((_a = p1.age_mask) !== null && _a !== void 0 ? _a : 0) < ((_b = p2.age_mask) !== null && _b !== void 0 ? _b : 0)) {
                return -1;
            }
            else if (((_c = p1.age_mask) !== null && _c !== void 0 ? _c : 0) > ((_d = p2.age_mask) !== null && _d !== void 0 ? _d : 0)) {
                return 1;
            }
            return strcmp(p1.rsa_public_key, p2.rsa_public_key);
        }
        else if (p1.cipher === DenomKeyType.ClauseSchnorr &&
            p2.cipher === DenomKeyType.ClauseSchnorr) {
            if (((_e = p1.age_mask) !== null && _e !== void 0 ? _e : 0) < ((_f = p2.age_mask) !== null && _f !== void 0 ? _f : 0)) {
                return -1;
            }
            else if (((_g = p1.age_mask) !== null && _g !== void 0 ? _g : 0) > ((_h = p2.age_mask) !== null && _h !== void 0 ? _h : 0)) {
                return 1;
            }
            return strcmp(p1.cs_public_key, p2.cs_public_key);
        }
        else {
            throw Error("unsupported cipher");
        }
    }
    DenominationPubKey.cmp = cmp;
})(DenominationPubKey || (DenominationPubKey = {}));
const codecForDenominationPubKey = () => buildCodecForUnion()
    .discriminateOn("cipher")
    .alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey())
    .alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey())
    .build("DenominationPubKey");
const codecForRsaDenominationPubKey = () => buildCodecForObject()
    .property("cipher", codecForConstString(DenomKeyType.Rsa))
    .property("rsa_public_key", codecForString())
    .property("age_mask", codecForNumber())
    .build("DenominationPubKey");
const codecForCsDenominationPubKey = () => buildCodecForObject()
    .property("cipher", codecForConstString(DenomKeyType.ClauseSchnorr))
    .property("cs_public_key", codecForString())
    .property("age_mask", codecForNumber())
    .build("CsDenominationPubKey");
const codecForBankWithdrawalOperationPostResponse = () => buildCodecForObject()
    .property("transfer_done", codecForBoolean())
    .build("BankWithdrawalOperationPostResponse");
const codecForDenomination = () => buildCodecForObject()
    .property("value", codecForString())
    .property("denom_pub", codecForDenominationPubKey())
    .property("fee_withdraw", codecForString())
    .property("fee_deposit", codecForString())
    .property("fee_refresh", codecForString())
    .property("fee_refund", codecForString())
    .property("stamp_start", codecForTimestamp)
    .property("stamp_expire_withdraw", codecForTimestamp)
    .property("stamp_expire_legal", codecForTimestamp)
    .property("stamp_expire_deposit", codecForTimestamp)
    .property("master_sig", codecForString())
    .build("Denomination");
const codecForAuditorDenomSig = () => buildCodecForObject()
    .property("denom_pub_h", codecForString())
    .property("auditor_sig", codecForString())
    .build("AuditorDenomSig");
const codecForAuditor = () => buildCodecForObject()
    .property("auditor_pub", codecForString())
    .property("auditor_url", codecForString())
    .property("denomination_keys", codecForList(codecForAuditorDenomSig()))
    .build("Auditor");
const codecForExchangeHandle = () => buildCodecForObject()
    .property("master_pub", codecForString())
    .property("url", codecForString())
    .build("ExchangeHandle");
const codecForAuditorHandle = () => buildCodecForObject()
    .property("name", codecForString())
    .property("auditor_pub", codecForString())
    .property("url", codecForString())
    .build("AuditorHandle");
const codecForLocation = () => buildCodecForObject()
    .property("country", codecOptional(codecForString()))
    .property("country_subdivision", codecOptional(codecForString()))
    .property("building_name", codecOptional(codecForString()))
    .property("building_number", codecOptional(codecForString()))
    .property("district", codecOptional(codecForString()))
    .property("street", codecOptional(codecForString()))
    .property("post_code", codecOptional(codecForString()))
    .property("town", codecOptional(codecForString()))
    .property("town_location", codecOptional(codecForString()))
    .property("address_lines", codecOptional(codecForList(codecForString())))
    .build("Location");
const codecForMerchantInfo = () => buildCodecForObject()
    .property("name", codecForString())
    .property("address", codecOptional(codecForLocation()))
    .property("jurisdiction", codecOptional(codecForLocation()))
    .build("MerchantInfo");
const codecForInternationalizedString = () => codecForMap(codecForString());
const codecForProduct = () => buildCodecForObject()
    .property("product_id", codecOptional(codecForString()))
    .property("description", codecForString())
    .property("description_i18n", codecOptional(codecForInternationalizedString()))
    .property("quantity", codecOptional(codecForNumber()))
    .property("unit", codecOptional(codecForString()))
    .property("price", codecOptional(codecForString()))
    .build("Tax");
const codecForContractTerms = () => buildCodecForObject()
    .property("order_id", codecForString())
    .property("fulfillment_url", codecOptional(codecForString()))
    .property("fulfillment_message", codecOptional(codecForString()))
    .property("fulfillment_message_i18n", codecOptional(codecForInternationalizedString()))
    .property("merchant_base_url", codecForString())
    .property("h_wire", codecForString())
    .property("auto_refund", codecOptional(codecForDuration))
    .property("wire_method", codecForString())
    .property("summary", codecForString())
    .property("summary_i18n", codecOptional(codecForInternationalizedString()))
    .property("nonce", codecForString())
    .property("amount", codecForString())
    .property("auditors", codecForList(codecForAuditorHandle()))
    .property("pay_deadline", codecForTimestamp)
    .property("refund_deadline", codecForTimestamp)
    .property("wire_transfer_deadline", codecForTimestamp)
    .property("timestamp", codecForTimestamp)
    .property("delivery_location", codecOptional(codecForLocation()))
    .property("delivery_date", codecOptional(codecForTimestamp))
    .property("max_fee", codecForString())
    .property("max_wire_fee", codecOptional(codecForString()))
    .property("merchant", codecForMerchantInfo())
    .property("merchant_pub", codecForString())
    .property("exchanges", codecForList(codecForExchangeHandle()))
    .property("products", codecOptional(codecForList(codecForProduct())))
    .property("extra", codecForAny())
    .property("minimum_age", codecOptional(codecForNumber()))
    .build("ContractTerms");
const codecForBlindSigWrapperV2 = () => buildCodecForObject()
    .property("blind_sig", codecForBlindedDenominationSignature())
    .build("MerchantBlindSigWrapperV2");
const codecForMerchantTipResponseV2 = () => buildCodecForObject()
    .property("blind_sigs", codecForList(codecForBlindSigWrapperV2()))
    .build("MerchantTipResponseV2");
const codecForRecoup = () => buildCodecForObject()
    .property("h_denom_pub", codecForString())
    .build("Recoup");
const codecForExchangeSigningKey = () => buildCodecForObject()
    .property("key", codecForString())
    .property("master_sig", codecForString())
    .property("stamp_end", codecForTimestamp)
    .property("stamp_start", codecForTimestamp)
    .property("stamp_expire", codecForTimestamp)
    .build("ExchangeSignKeyJson");
const codecForExchangeKeysJson = () => buildCodecForObject()
    .property("denoms", codecForList(codecForDenomination()))
    .property("master_public_key", codecForString())
    .property("auditors", codecForList(codecForAuditor()))
    .property("list_issue_date", codecForTimestamp)
    .property("recoup", codecOptional(codecForList(codecForRecoup())))
    .property("signkeys", codecForList(codecForExchangeSigningKey()))
    .property("version", codecForString())
    .property("reserve_closing_delay", codecForDuration)
    .build("ExchangeKeysJson");
const codecForWireFeesJson = () => buildCodecForObject()
    .property("wire_fee", codecForString())
    .property("closing_fee", codecForString())
    .property("wad_fee", codecForString())
    .property("sig", codecForString())
    .property("start_date", codecForTimestamp)
    .property("end_date", codecForTimestamp)
    .build("WireFeesJson");
const codecForAccountInfo = () => buildCodecForObject()
    .property("payto_uri", codecForString())
    .property("master_sig", codecForString())
    .build("AccountInfo");
const codecForExchangeWireJson = () => buildCodecForObject()
    .property("accounts", codecForList(codecForAccountInfo()))
    .property("fees", codecForMap(codecForList(codecForWireFeesJson())))
    .build("ExchangeWireJson");
const codecForProposal = () => buildCodecForObject()
    .property("contract_terms", codecForAny())
    .property("sig", codecForString())
    .build("Proposal");
const codecForCheckPaymentResponse = () => buildCodecForObject()
    .property("order_status", codecForString())
    .property("refunded", codecOptional(codecForBoolean()))
    .property("refunded_amount", codecOptional(codecForString()))
    .property("contract_terms", codecOptional(codecForAny()))
    .property("taler_pay_uri", codecOptional(codecForString()))
    .property("contract_url", codecOptional(codecForString()))
    .build("CheckPaymentResponse");
const codecForWithdrawOperationStatusResponse = () => buildCodecForObject()
    .property("selection_done", codecForBoolean())
    .property("transfer_done", codecForBoolean())
    .property("aborted", codecForBoolean())
    .property("amount", codecForString())
    .property("sender_wire", codecOptional(codecForString()))
    .property("suggested_exchange", codecOptional(codecForString()))
    .property("confirm_transfer_url", codecOptional(codecForString()))
    .property("wire_types", codecForList(codecForString()))
    .build("WithdrawOperationStatusResponse");
const codecForTipPickupGetResponse = () => buildCodecForObject()
    .property("tip_amount", codecForString())
    .property("exchange_url", codecForString())
    .property("expiration", codecForTimestamp)
    .build("TipPickupGetResponse");
const codecForRecoupConfirmation = () => buildCodecForObject()
    .property("reserve_pub", codecOptional(codecForString()))
    .property("old_coin_pub", codecOptional(codecForString()))
    .build("RecoupConfirmation");
const codecForWithdrawResponse = () => buildCodecForObject()
    .property("ev_sig", codecForBlindedDenominationSignature())
    .build("WithdrawResponse");
const codecForWithdrawBatchResponse = () => buildCodecForObject()
    .property("ev_sigs", codecForList(codecForWithdrawResponse()))
    .build("WithdrawBatchResponse");
const codecForMerchantPayResponse = () => buildCodecForObject()
    .property("sig", codecForString())
    .build("MerchantPayResponse");
const codecForExchangeMeltResponse = () => buildCodecForObject()
    .property("exchange_pub", codecForString())
    .property("exchange_sig", codecForString())
    .property("noreveal_index", codecForNumber())
    .property("refresh_base_url", codecOptional(codecForString()))
    .build("ExchangeMeltResponse");
const codecForExchangeRevealItem = () => buildCodecForObject()
    .property("ev_sig", codecForBlindedDenominationSignature())
    .build("ExchangeRevealItem");
const codecForExchangeRevealResponse = () => buildCodecForObject()
    .property("ev_sigs", codecForList(codecForExchangeRevealItem()))
    .build("ExchangeRevealResponse");
const codecForMerchantCoinRefundSuccessStatus = () => buildCodecForObject()
    .property("type", codecForConstString("success"))
    .property("coin_pub", codecForString())
    .property("exchange_status", codecForConstNumber(200))
    .property("exchange_sig", codecForString())
    .property("rtransaction_id", codecForNumber())
    .property("refund_amount", codecForString())
    .property("exchange_pub", codecForString())
    .property("execution_time", codecForTimestamp)
    .build("MerchantCoinRefundSuccessStatus");
const codecForMerchantCoinRefundFailureStatus = () => buildCodecForObject()
    .property("type", codecForConstString("failure"))
    .property("coin_pub", codecForString())
    .property("exchange_status", codecForNumber())
    .property("rtransaction_id", codecForNumber())
    .property("refund_amount", codecForString())
    .property("exchange_code", codecOptional(codecForNumber()))
    .property("exchange_reply", codecOptional(codecForAny()))
    .property("execution_time", codecForTimestamp)
    .build("MerchantCoinRefundFailureStatus");
const codecForMerchantCoinRefundStatus = () => buildCodecForUnion()
    .discriminateOn("type")
    .alternative("success", codecForMerchantCoinRefundSuccessStatus())
    .alternative("failure", codecForMerchantCoinRefundFailureStatus())
    .build("MerchantCoinRefundStatus");
const codecForMerchantOrderStatusPaid = () => buildCodecForObject()
    .property("refund_amount", codecForString())
    .property("refund_taken", codecForString())
    .property("refund_pending", codecForBoolean())
    .property("refunded", codecForBoolean())
    .build("MerchantOrderStatusPaid");
const codecForMerchantOrderRefundPickupResponse = () => buildCodecForObject()
    .property("merchant_pub", codecForString())
    .property("refund_amount", codecForString())
    .property("refunds", codecForList(codecForMerchantCoinRefundStatus()))
    .build("MerchantOrderRefundPickupResponse");
const codecForAbortResponse = () => buildCodecForObject()
    .property("refunds", codecForList(codecForMerchantAbortPayRefundStatus()))
    .build("AbortResponse");
const codecForMerchantAbortPayRefundSuccessStatus = () => buildCodecForObject()
    .property("exchange_pub", codecForString())
    .property("exchange_sig", codecForString())
    .property("exchange_status", codecForConstNumber(200))
    .property("type", codecForConstString("success"))
    .build("MerchantAbortPayRefundSuccessStatus");
const codecForMerchantAbortPayRefundFailureStatus = () => buildCodecForObject()
    .property("exchange_code", codecForNumber())
    .property("exchange_reply", codecForAny())
    .property("exchange_status", codecForNumber())
    .property("type", codecForConstString("failure"))
    .build("MerchantAbortPayRefundFailureStatus");
const codecForMerchantAbortPayRefundStatus = () => buildCodecForUnion()
    .discriminateOn("type")
    .alternative("success", codecForMerchantAbortPayRefundSuccessStatus())
    .alternative("failure", codecForMerchantAbortPayRefundFailureStatus())
    .build("MerchantAbortPayRefundStatus");
const codecForTalerConfigResponse = () => buildCodecForObject()
    .property("name", codecForString())
    .property("version", codecForString())
    .property("currency", codecOptional(codecForString()))
    .build("TalerConfigResponse");
const codecForMerchantConfigResponse = () => buildCodecForObject()
    .property("currency", codecForString())
    .property("name", codecForString())
    .property("version", codecForString())
    .build("MerchantConfigResponse");
var ExchangeProtocolVersion;
(function (ExchangeProtocolVersion) {
    /**
     * Current version supported by the wallet.
     */
    ExchangeProtocolVersion[ExchangeProtocolVersion["V12"] = 12] = "V12";
})(ExchangeProtocolVersion || (ExchangeProtocolVersion = {}));
var MerchantProtocolVersion;
(function (MerchantProtocolVersion) {
    /**
     * Current version supported by the wallet.
     */
    MerchantProtocolVersion[MerchantProtocolVersion["V3"] = 3] = "V3";
})(MerchantProtocolVersion || (MerchantProtocolVersion = {}));
const codecForDepositSuccess = () => buildCodecForObject()
    .property("exchange_pub", codecForString())
    .property("exchange_sig", codecForString())
    .property("exchange_timestamp", codecForTimestamp)
    .property("transaction_base_url", codecOptional(codecForString()))
    .build("DepositSuccess");
const codecForExchangeGetContractResponse = () => buildCodecForObject()
    .property("purse_pub", codecForString())
    .property("econtract_sig", codecForString())
    .property("econtract", codecForString())
    .build("ExchangeGetContractResponse");

/*
 This file is part of TALER
 (C) 2019 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/>
 */
/**
 * Check if we are running under nodejs.
 */
const isNode = typeof process !== "undefined" &&
    typeof process.release !== "undefined" &&
    process.release.name === "node";
var LogLevel;
(function (LogLevel) {
    LogLevel["Trace"] = "trace";
    LogLevel["Message"] = "message";
    LogLevel["Info"] = "info";
    LogLevel["Warn"] = "warn";
    LogLevel["Error"] = "error";
    LogLevel["None"] = "none";
})(LogLevel || (LogLevel = {}));
let globalLogLevel = LogLevel.Info;
function writeNodeLog(message, tag, level, args) {
    try {
        let msg = `${new Date().toISOString()} ${tag} ${level} ${message}`;
        if (args.length != 0) {
            msg += ` ${JSON.stringify(args, undefined, 2)}\n`;
        }
        else {
            msg += `\n`;
        }
        process.stderr.write(msg);
    }
    catch (e) {
        // This can happen when we're trying to log something that doesn't want to be
        // converted to a string.
        let msg = `${new Date().toISOString()} (logger) FATAL `;
        if (e instanceof Error) {
            msg += `failed to write log: ${e.message}\n`;
        }
        else {
            msg += "failed to write log\n";
        }
        process.stderr.write(msg);
    }
}
/**
 * Logger that writes to stderr when running under node,
 * and uses the corresponding console.* method to log in the browser.
 */
class Logger {
    constructor(tag) {
        this.tag = tag;
    }
    shouldLogTrace() {
        switch (globalLogLevel) {
            case LogLevel.Trace:
                return true;
            case LogLevel.Message:
            case LogLevel.Info:
            case LogLevel.Warn:
            case LogLevel.Error:
            case LogLevel.None:
                return false;
        }
    }
    shouldLogInfo() {
        switch (globalLogLevel) {
            case LogLevel.Trace:
            case LogLevel.Message:
            case LogLevel.Info:
                return true;
            case LogLevel.Warn:
            case LogLevel.Error:
            case LogLevel.None:
                return false;
        }
    }
    shouldLogWarn() {
        switch (globalLogLevel) {
            case LogLevel.Trace:
            case LogLevel.Message:
            case LogLevel.Info:
            case LogLevel.Warn:
                return true;
            case LogLevel.Error:
            case LogLevel.None:
                return false;
        }
    }
    shouldLogError() {
        switch (globalLogLevel) {
            case LogLevel.Trace:
            case LogLevel.Message:
            case LogLevel.Info:
            case LogLevel.Warn:
            case LogLevel.Error:
                return true;
            case LogLevel.None:
                return false;
        }
    }
    info(message, ...args) {
        if (!this.shouldLogInfo()) {
            return;
        }
        if (isNode) {
            writeNodeLog(message, this.tag, "INFO", args);
        }
        else {
            console.info(`${new Date().toISOString()} ${this.tag} INFO ` + message, ...args);
        }
    }
    warn(message, ...args) {
        if (!this.shouldLogWarn()) {
            return;
        }
        if (isNode) {
            writeNodeLog(message, this.tag, "WARN", args);
        }
        else {
            console.warn(`${new Date().toISOString()} ${this.tag} INFO ` + message, ...args);
        }
    }
    error(message, ...args) {
        if (!this.shouldLogError()) {
            return;
        }
        if (isNode) {
            writeNodeLog(message, this.tag, "ERROR", args);
        }
        else {
            console.info(`${new Date().toISOString()} ${this.tag} ERROR ` + message, ...args);
        }
    }
    trace(message, ...args) {
        if (!this.shouldLogTrace()) {
            return;
        }
        if (isNode) {
            writeNodeLog(message, this.tag, "TRACE", args);
        }
        else {
            console.info(`${new Date().toISOString()} ${this.tag} TRACE ` + message, ...args);
        }
    }
    reportBreak() {
        if (!this.shouldLogError()) {
            return;
        }
        const location = new Error("programming error");
        this.error(`assertion failed: ${location.stack}`);
    }
}

var require$1 = module$1.createRequire('/');
// DEFLATE is a complex format; to read this code, you should probably check the RFC first:
// https://tools.ietf.org/html/rfc1951
// You may also wish to take a look at the guide I made about this program:
// https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad
// Some of the following code is similar to that of UZIP.js:
// https://github.com/photopea/UZIP.js
// However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size.
// Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint
// is better for memory in most engines (I *think*).
// Mediocre shim
var Worker;
try {
    Worker = require$1('worker_threads').Worker;
}
catch (e) {
}

// aliases for shorter compressed code (most minifers don't do this)
var u8 = Uint8Array, u16 = Uint16Array, u32 = Uint32Array;
// fixed length extra bits
var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]);
// fixed distance extra bits
// see fleb note
var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]);
// code length index map
var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
// get base, reverse index map from extra bits
var freb = function (eb, start) {
    var b = new u16(31);
    for (var i = 0; i < 31; ++i) {
        b[i] = start += 1 << eb[i - 1];
    }
    // numbers here are at max 18 bits
    var r = new u32(b[30]);
    for (var i = 1; i < 30; ++i) {
        for (var j = b[i]; j < b[i + 1]; ++j) {
            r[j] = ((j - b[i]) << 5) | i;
        }
    }
    return [b, r];
};
var _a$1 = freb(fleb, 2), fl = _a$1[0], revfl = _a$1[1];
// we can ignore the fact that the other numbers are wrong; they never happen anyway
fl[28] = 258, revfl[258] = 28;
var _b = freb(fdeb, 0), fd = _b[0], revfd = _b[1];
// map of value to reverse (assuming 16 bits)
var rev = new u16(32768);
for (var i = 0; i < 32768; ++i) {
    // reverse table algorithm from SO
    var x = ((i & 0xAAAA) >>> 1) | ((i & 0x5555) << 1);
    x = ((x & 0xCCCC) >>> 2) | ((x & 0x3333) << 2);
    x = ((x & 0xF0F0) >>> 4) | ((x & 0x0F0F) << 4);
    rev[i] = (((x & 0xFF00) >>> 8) | ((x & 0x00FF) << 8)) >>> 1;
}
// create huffman tree from u8 "map": index -> code length for code index
// mb (max bits) must be at most 15
// TODO: optimize/split up?
var hMap = (function (cd, mb, r) {
    var s = cd.length;
    // index
    var i = 0;
    // u16 "map": index -> # of codes with bit length = index
    var l = new u16(mb);
    // length of cd must be 288 (total # of codes)
    for (; i < s; ++i) {
        if (cd[i])
            ++l[cd[i] - 1];
    }
    // u16 "map": index -> minimum code for bit length = index
    var le = new u16(mb);
    for (i = 0; i < mb; ++i) {
        le[i] = (le[i - 1] + l[i - 1]) << 1;
    }
    var co;
    if (r) {
        // u16 "map": index -> number of actual bits, symbol for code
        co = new u16(1 << mb);
        // bits to remove for reverser
        var rvb = 15 - mb;
        for (i = 0; i < s; ++i) {
            // ignore 0 lengths
            if (cd[i]) {
                // num encoding both symbol and bits read
                var sv = (i << 4) | cd[i];
                // free bits
                var r_1 = mb - cd[i];
                // start value
                var v = le[cd[i] - 1]++ << r_1;
                // m is end value
                for (var m = v | ((1 << r_1) - 1); v <= m; ++v) {
                    // every 16 bit value starting with the code yields the same result
                    co[rev[v] >>> rvb] = sv;
                }
            }
        }
    }
    else {
        co = new u16(s);
        for (i = 0; i < s; ++i) {
            if (cd[i]) {
                co[i] = rev[le[cd[i] - 1]++] >>> (15 - cd[i]);
            }
        }
    }
    return co;
});
// fixed length tree
var flt = new u8(288);
for (var i = 0; i < 144; ++i)
    flt[i] = 8;
for (var i = 144; i < 256; ++i)
    flt[i] = 9;
for (var i = 256; i < 280; ++i)
    flt[i] = 7;
for (var i = 280; i < 288; ++i)
    flt[i] = 8;
// fixed distance tree
var fdt = new u8(32);
for (var i = 0; i < 32; ++i)
    fdt[i] = 5;
// fixed length map
var flm = /*#__PURE__*/ hMap(flt, 9, 0), flrm = /*#__PURE__*/ hMap(flt, 9, 1);
// fixed distance map
var fdm = /*#__PURE__*/ hMap(fdt, 5, 0), fdrm = /*#__PURE__*/ hMap(fdt, 5, 1);
// find max of array
var max = function (a) {
    var m = a[0];
    for (var i = 1; i < a.length; ++i) {
        if (a[i] > m)
            m = a[i];
    }
    return m;
};
// read d, starting at bit p and mask with m
var bits = function (d, p, m) {
    var o = (p / 8) | 0;
    return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m;
};
// read d, starting at bit p continuing for at least 16 bits
var bits16 = function (d, p) {
    var o = (p / 8) | 0;
    return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7));
};
// get end of byte
var shft = function (p) { return ((p + 7) / 8) | 0; };
// typed array slice - allows garbage collector to free original reference,
// while being more compatible than .slice
var slc = function (v, s, e) {
    if (s == null || s < 0)
        s = 0;
    if (e == null || e > v.length)
        e = v.length;
    // can't use .constructor in case user-supplied
    var n = new (v.BYTES_PER_ELEMENT == 2 ? u16 : v.BYTES_PER_ELEMENT == 4 ? u32 : u8)(e - s);
    n.set(v.subarray(s, e));
    return n;
};
// error codes
var ec = [
    'unexpected EOF',
    'invalid block type',
    'invalid length/literal',
    'invalid distance',
    'stream finished',
    'no stream handler',
    ,
    'no callback',
    'invalid UTF-8 data',
    'extra field too long',
    'date not in range 1980-2099',
    'filename too long',
    'stream finishing',
    'invalid zip data'
    // determined by unknown compression method
];
var err = function (ind, msg, nt) {
    var e = new Error(msg || ec[ind]);
    e.code = ind;
    if (Error.captureStackTrace)
        Error.captureStackTrace(e, err);
    if (!nt)
        throw e;
    return e;
};
// expands raw DEFLATE data
var inflt = function (dat, buf, st) {
    // source length
    var sl = dat.length;
    if (!sl || (st && st.f && !st.l))
        return buf || new u8(0);
    // have to estimate size
    var noBuf = !buf || st;
    // no state
    var noSt = !st || st.i;
    if (!st)
        st = {};
    // Assumes roughly 33% compression ratio average
    if (!buf)
        buf = new u8(sl * 3);
    // ensure buffer can fit at least l elements
    var cbuf = function (l) {
        var bl = buf.length;
        // need to increase size to fit
        if (l > bl) {
            // Double or set to necessary, whichever is greater
            var nbuf = new u8(Math.max(bl * 2, l));
            nbuf.set(buf);
            buf = nbuf;
        }
    };
    //  last chunk         bitpos           bytes
    var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;
    // total bits
    var tbts = sl * 8;
    do {
        if (!lm) {
            // BFINAL - this is only 1 when last chunk is next
            final = bits(dat, pos, 1);
            // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman
            var type = bits(dat, pos + 1, 3);
            pos += 3;
            if (!type) {
                // go to end of byte boundary
                var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l;
                if (t > sl) {
                    if (noSt)
                        err(0);
                    break;
                }
                // ensure size
                if (noBuf)
                    cbuf(bt + l);
                // Copy over uncompressed data
                buf.set(dat.subarray(s, t), bt);
                // Get new bitpos, update byte count
                st.b = bt += l, st.p = pos = t * 8, st.f = final;
                continue;
            }
            else if (type == 1)
                lm = flrm, dm = fdrm, lbt = 9, dbt = 5;
            else if (type == 2) {
                //  literal                            lengths
                var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
                var tl = hLit + bits(dat, pos + 5, 31) + 1;
                pos += 14;
                // length+distance tree
                var ldt = new u8(tl);
                // code length tree
                var clt = new u8(19);
                for (var i = 0; i < hcLen; ++i) {
                    // use index map to get real code
                    clt[clim[i]] = bits(dat, pos + i * 3, 7);
                }
                pos += hcLen * 3;
                // code lengths bits
                var clb = max(clt), clbmsk = (1 << clb) - 1;
                // code lengths map
                var clm = hMap(clt, clb, 1);
                for (var i = 0; i < tl;) {
                    var r = clm[bits(dat, pos, clbmsk)];
                    // bits read
                    pos += r & 15;
                    // symbol
                    var s = r >>> 4;
                    // code length to copy
                    if (s < 16) {
                        ldt[i++] = s;
                    }
                    else {
                        //  copy   count
                        var c = 0, n = 0;
                        if (s == 16)
                            n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1];
                        else if (s == 17)
                            n = 3 + bits(dat, pos, 7), pos += 3;
                        else if (s == 18)
                            n = 11 + bits(dat, pos, 127), pos += 7;
                        while (n--)
                            ldt[i++] = c;
                    }
                }
                //    length tree                 distance tree
                var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
                // max length bits
                lbt = max(lt);
                // max dist bits
                dbt = max(dt);
                lm = hMap(lt, lbt, 1);
                dm = hMap(dt, dbt, 1);
            }
            else
                err(1);
            if (pos > tbts) {
                if (noSt)
                    err(0);
                break;
            }
        }
        // Make sure the buffer can hold this + the largest possible addition
        // Maximum chunk size (practically, theoretically infinite) is 2^17;
        if (noBuf)
            cbuf(bt + 131072);
        var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
        var lpos = pos;
        for (;; lpos = pos) {
            // bits read, code
            var c = lm[bits16(dat, pos) & lms], sym = c >>> 4;
            pos += c & 15;
            if (pos > tbts) {
                if (noSt)
                    err(0);
                break;
            }
            if (!c)
                err(2);
            if (sym < 256)
                buf[bt++] = sym;
            else if (sym == 256) {
                lpos = pos, lm = null;
                break;
            }
            else {
                var add = sym - 254;
                // no extra bits needed if less
                if (sym > 264) {
                    // index
                    var i = sym - 257, b = fleb[i];
                    add = bits(dat, pos, (1 << b) - 1) + fl[i];
                    pos += b;
                }
                // dist
                var d = dm[bits16(dat, pos) & dms], dsym = d >>> 4;
                if (!d)
                    err(3);
                pos += d & 15;
                var dt = fd[dsym];
                if (dsym > 3) {
                    var b = fdeb[dsym];
                    dt += bits16(dat, pos) & ((1 << b) - 1), pos += b;
                }
                if (pos > tbts) {
                    if (noSt)
                        err(0);
                    break;
                }
                if (noBuf)
                    cbuf(bt + 131072);
                var end = bt + add;
                for (; bt < end; bt += 4) {
                    buf[bt] = buf[bt - dt];
                    buf[bt + 1] = buf[bt + 1 - dt];
                    buf[bt + 2] = buf[bt + 2 - dt];
                    buf[bt + 3] = buf[bt + 3 - dt];
                }
                bt = end;
            }
        }
        st.l = lm, st.p = lpos, st.b = bt, st.f = final;
        if (lm)
            final = 1, st.m = lbt, st.d = dm, st.n = dbt;
    } while (!final);
    return bt == buf.length ? buf : slc(buf, 0, bt);
};
// starting at p, write the minimum number of bits that can hold v to d
var wbits = function (d, p, v) {
    v <<= p & 7;
    var o = (p / 8) | 0;
    d[o] |= v;
    d[o + 1] |= v >>> 8;
};
// starting at p, write the minimum number of bits (>8) that can hold v to d
var wbits16 = function (d, p, v) {
    v <<= p & 7;
    var o = (p / 8) | 0;
    d[o] |= v;
    d[o + 1] |= v >>> 8;
    d[o + 2] |= v >>> 16;
};
// creates code lengths from a frequency table
var hTree = function (d, mb) {
    // Need extra info to make a tree
    var t = [];
    for (var i = 0; i < d.length; ++i) {
        if (d[i])
            t.push({ s: i, f: d[i] });
    }
    var s = t.length;
    var t2 = t.slice();
    if (!s)
        return [et, 0];
    if (s == 1) {
        var v = new u8(t[0].s + 1);
        v[t[0].s] = 1;
        return [v, 1];
    }
    t.sort(function (a, b) { return a.f - b.f; });
    // after i2 reaches last ind, will be stopped
    // freq must be greater than largest possible number of symbols
    t.push({ s: -1, f: 25001 });
    var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2;
    t[0] = { s: -1, f: l.f + r.f, l: l, r: r };
    // efficient algorithm from UZIP.js
    // i0 is lookbehind, i2 is lookahead - after processing two low-freq
    // symbols that combined have high freq, will start processing i2 (high-freq,
    // non-composite) symbols instead
    // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/
    while (i1 != s - 1) {
        l = t[t[i0].f < t[i2].f ? i0++ : i2++];
        r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++];
        t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r };
    }
    var maxSym = t2[0].s;
    for (var i = 1; i < s; ++i) {
        if (t2[i].s > maxSym)
            maxSym = t2[i].s;
    }
    // code lengths
    var tr = new u16(maxSym + 1);
    // max bits in tree
    var mbt = ln(t[i1 - 1], tr, 0);
    if (mbt > mb) {
        // more algorithms from UZIP.js
        // TODO: find out how this code works (debt)
        //  ind    debt
        var i = 0, dt = 0;
        //    left            cost
        var lft = mbt - mb, cst = 1 << lft;
        t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; });
        for (; i < s; ++i) {
            var i2_1 = t2[i].s;
            if (tr[i2_1] > mb) {
                dt += cst - (1 << (mbt - tr[i2_1]));
                tr[i2_1] = mb;
            }
            else
                break;
        }
        dt >>>= lft;
        while (dt > 0) {
            var i2_2 = t2[i].s;
            if (tr[i2_2] < mb)
                dt -= 1 << (mb - tr[i2_2]++ - 1);
            else
                ++i;
        }
        for (; i >= 0 && dt; --i) {
            var i2_3 = t2[i].s;
            if (tr[i2_3] == mb) {
                --tr[i2_3];
                ++dt;
            }
        }
        mbt = mb;
    }
    return [new u8(tr), mbt];
};
// get the max length and assign length codes
var ln = function (n, l, d) {
    return n.s == -1
        ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1))
        : (l[n.s] = d);
};
// length codes generation
var lc = function (c) {
    var s = c.length;
    // Note that the semicolon was intentional
    while (s && !c[--s])
        ;
    var cl = new u16(++s);
    //  ind      num         streak
    var cli = 0, cln = c[0], cls = 1;
    var w = function (v) { cl[cli++] = v; };
    for (var i = 1; i <= s; ++i) {
        if (c[i] == cln && i != s)
            ++cls;
        else {
            if (!cln && cls > 2) {
                for (; cls > 138; cls -= 138)
                    w(32754);
                if (cls > 2) {
                    w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305);
                    cls = 0;
                }
            }
            else if (cls > 3) {
                w(cln), --cls;
                for (; cls > 6; cls -= 6)
                    w(8304);
                if (cls > 2)
                    w(((cls - 3) << 5) | 8208), cls = 0;
            }
            while (cls--)
                w(cln);
            cls = 1;
            cln = c[i];
        }
    }
    return [cl.subarray(0, cli), s];
};
// calculate the length of output from tree, code lengths
var clen = function (cf, cl) {
    var l = 0;
    for (var i = 0; i < cl.length; ++i)
        l += cf[i] * cl[i];
    return l;
};
// writes a fixed block
// returns the new bit pos
var wfblk = function (out, pos, dat) {
    // no need to write 00 as type: TypedArray defaults to 0
    var s = dat.length;
    var o = shft(pos + 2);
    out[o] = s & 255;
    out[o + 1] = s >>> 8;
    out[o + 2] = out[o] ^ 255;
    out[o + 3] = out[o + 1] ^ 255;
    for (var i = 0; i < s; ++i)
        out[o + i + 4] = dat[i];
    return (o + 4 + s) * 8;
};
// writes a block
var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
    wbits(out, p++, final);
    ++lf[256];
    var _a = hTree(lf, 15), dlt = _a[0], mlb = _a[1];
    var _b = hTree(df, 15), ddt = _b[0], mdb = _b[1];
    var _c = lc(dlt), lclt = _c[0], nlc = _c[1];
    var _d = lc(ddt), lcdt = _d[0], ndc = _d[1];
    var lcfreq = new u16(19);
    for (var i = 0; i < lclt.length; ++i)
        lcfreq[lclt[i] & 31]++;
    for (var i = 0; i < lcdt.length; ++i)
        lcfreq[lcdt[i] & 31]++;
    var _e = hTree(lcfreq, 7), lct = _e[0], mlcb = _e[1];
    var nlcc = 19;
    for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
        ;
    var flen = (bl + 5) << 3;
    var ftlen = clen(lf, flt) + clen(df, fdt) + eb;
    var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + (2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]);
    if (flen <= ftlen && flen <= dtlen)
        return wfblk(out, p, dat.subarray(bs, bs + bl));
    var lm, ll, dm, dl;
    wbits(out, p, 1 + (dtlen < ftlen)), p += 2;
    if (dtlen < ftlen) {
        lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;
        var llm = hMap(lct, mlcb, 0);
        wbits(out, p, nlc - 257);
        wbits(out, p + 5, ndc - 1);
        wbits(out, p + 10, nlcc - 4);
        p += 14;
        for (var i = 0; i < nlcc; ++i)
            wbits(out, p + 3 * i, lct[clim[i]]);
        p += 3 * nlcc;
        var lcts = [lclt, lcdt];
        for (var it = 0; it < 2; ++it) {
            var clct = lcts[it];
            for (var i = 0; i < clct.length; ++i) {
                var len = clct[i] & 31;
                wbits(out, p, llm[len]), p += lct[len];
                if (len > 15)
                    wbits(out, p, (clct[i] >>> 5) & 127), p += clct[i] >>> 12;
            }
        }
    }
    else {
        lm = flm, ll = flt, dm = fdm, dl = fdt;
    }
    for (var i = 0; i < li; ++i) {
        if (syms[i] > 255) {
            var len = (syms[i] >>> 18) & 31;
            wbits16(out, p, lm[len + 257]), p += ll[len + 257];
            if (len > 7)
                wbits(out, p, (syms[i] >>> 23) & 31), p += fleb[len];
            var dst = syms[i] & 31;
            wbits16(out, p, dm[dst]), p += dl[dst];
            if (dst > 3)
                wbits16(out, p, (syms[i] >>> 5) & 8191), p += fdeb[dst];
        }
        else {
            wbits16(out, p, lm[syms[i]]), p += ll[syms[i]];
        }
    }
    wbits16(out, p, lm[256]);
    return p + ll[256];
};
// deflate options (nice << 13) | chain
var deo = /*#__PURE__*/ new u32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);
// empty
var et = /*#__PURE__*/ new u8(0);
// compresses data into a raw DEFLATE buffer
var dflt = function (dat, lvl, plvl, pre, post, lst) {
    var s = dat.length;
    var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post);
    // writing to this writes to the output buffer
    var w = o.subarray(pre, o.length - post);
    var pos = 0;
    if (!lvl || s < 8) {
        for (var i = 0; i <= s; i += 65535) {
            // end
            var e = i + 65535;
            if (e >= s) {
                // write final block
                w[pos >> 3] = lst;
            }
            pos = wfblk(w, pos + 1, dat.subarray(i, e));
        }
    }
    else {
        var opt = deo[lvl - 1];
        var n = opt >>> 13, c = opt & 8191;
        var msk_1 = (1 << plvl) - 1;
        //    prev 2-byte val map    curr 2-byte val map
        var prev = new u16(32768), head = new u16(msk_1 + 1);
        var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
        var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; };
        // 24576 is an arbitrary number of maximum symbols per block
        // 424 buffer for last block
        var syms = new u32(25000);
        // length/literal freq   distance freq
        var lf = new u16(288), df = new u16(32);
        //  l/lcnt  exbits  index  l/lind  waitdx  bitpos
        var lc_1 = 0, eb = 0, i = 0, li = 0, wi = 0, bs = 0;
        for (; i < s; ++i) {
            // hash value
            // deopt when i > s - 3 - at end, deopt acceptable
            var hv = hsh(i);
            // index mod 32768    previous index mod
            var imod = i & 32767, pimod = head[hv];
            prev[imod] = pimod;
            head[hv] = imod;
            // We always should modify head and prev, but only add symbols if
            // this data is not yet processed ("wait" for wait index)
            if (wi <= i) {
                // bytes remaining
                var rem = s - i;
                if ((lc_1 > 7000 || li > 24576) && rem > 423) {
                    pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);
                    li = lc_1 = eb = 0, bs = i;
                    for (var j = 0; j < 286; ++j)
                        lf[j] = 0;
                    for (var j = 0; j < 30; ++j)
                        df[j] = 0;
                }
                //  len    dist   chain
                var l = 2, d = 0, ch_1 = c, dif = (imod - pimod) & 32767;
                if (rem > 2 && hv == hsh(i - dif)) {
                    var maxn = Math.min(n, rem) - 1;
                    var maxd = Math.min(32767, i);
                    // max possible length
                    // not capped at dif because decompressors implement "rolling" index population
                    var ml = Math.min(258, rem);
                    while (dif <= maxd && --ch_1 && imod != pimod) {
                        if (dat[i + l] == dat[i + l - dif]) {
                            var nl = 0;
                            for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)
                                ;
                            if (nl > l) {
                                l = nl, d = dif;
                                // break out early when we reach "nice" (we are satisfied enough)
                                if (nl > maxn)
                                    break;
                                // now, find the rarest 2-byte sequence within this
                                // length of literals and search for that instead.
                                // Much faster than just using the start
                                var mmd = Math.min(dif, nl - 2);
                                var md = 0;
                                for (var j = 0; j < mmd; ++j) {
                                    var ti = (i - dif + j + 32768) & 32767;
                                    var pti = prev[ti];
                                    var cd = (ti - pti + 32768) & 32767;
                                    if (cd > md)
                                        md = cd, pimod = ti;
                                }
                            }
                        }
                        // check the previous match
                        imod = pimod, pimod = prev[imod];
                        dif += (imod - pimod + 32768) & 32767;
                    }
                }
                // d will be nonzero only when a match was found
                if (d) {
                    // store both dist and len data in one Uint32
                    // Make sure this is recognized as a len/dist with 28th bit (2^28)
                    syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d];
                    var lin = revfl[l] & 31, din = revfd[d] & 31;
                    eb += fleb[lin] + fdeb[din];
                    ++lf[257 + lin];
                    ++df[din];
                    wi = i + l;
                    ++lc_1;
                }
                else {
                    syms[li++] = dat[i];
                    ++lf[dat[i]];
                }
            }
        }
        pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);
        // this is the easiest way to avoid needing to maintain state
        if (!lst && pos & 7)
            pos = wfblk(w, pos + 1, et);
    }
    return slc(o, 0, pre + shft(pos) + post);
};
// CRC32 table
var crct = /*#__PURE__*/ (function () {
    var t = new Int32Array(256);
    for (var i = 0; i < 256; ++i) {
        var c = i, k = 9;
        while (--k)
            c = ((c & 1) && -306674912) ^ (c >>> 1);
        t[i] = c;
    }
    return t;
})();
// CRC32
var crc = function () {
    var c = -1;
    return {
        p: function (d) {
            // closures have awful performance
            var cr = c;
            for (var i = 0; i < d.length; ++i)
                cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8);
            c = cr;
        },
        d: function () { return ~c; }
    };
};
// Alder32
var adler = function () {
    var a = 1, b = 0;
    return {
        p: function (d) {
            // closures have awful performance
            var n = a, m = b;
            var l = d.length | 0;
            for (var i = 0; i != l;) {
                var e = Math.min(i + 2655, l);
                for (; i < e; ++i)
                    m += n += d[i];
                n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16);
            }
            a = n, b = m;
        },
        d: function () {
            a %= 65521, b %= 65521;
            return (a & 255) << 24 | (a >>> 8) << 16 | (b & 255) << 8 | (b >>> 8);
        }
    };
};
// deflate with opts
var dopt = function (dat, opt, pre, post, st) {
    return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : (12 + opt.mem), pre, post, !st);
};
// write bytes
var wbytes = function (d, b, v) {
    for (; v; ++b)
        d[b] = v, v >>>= 8;
};
// gzip header
var gzh = function (c, o) {
    var fn = o.filename;
    c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix
    if (o.mtime != 0)
        wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000));
    if (fn) {
        c[3] = 8;
        for (var i = 0; i <= fn.length; ++i)
            c[i + 10] = fn.charCodeAt(i);
    }
};
// gzip footer: -8 to -4 = CRC, -4 to -0 is length
// gzip start
var gzs = function (d) {
    if (d[0] != 31 || d[1] != 139 || d[2] != 8)
        err(6, 'invalid gzip data');
    var flg = d[3];
    var st = 10;
    if (flg & 4)
        st += d[10] | (d[11] << 8) + 2;
    for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++])
        ;
    return st + (flg & 2);
};
// gzip length
var gzl = function (d) {
    var l = d.length;
    return ((d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16) | (d[l - 1] << 24)) >>> 0;
};
// gzip header length
var gzhl = function (o) { return 10 + ((o.filename && (o.filename.length + 1)) || 0); };
// zlib header
var zlh = function (c, o) {
    var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2;
    c[0] = 120, c[1] = (fl << 6) | (fl ? (32 - 2 * fl) : 1);
};
// zlib valid
var zlv = function (d) {
    if ((d[0] & 15) != 8 || (d[0] >>> 4) > 7 || ((d[0] << 8 | d[1]) % 31))
        err(6, 'invalid zlib data');
    if (d[1] & 32)
        err(6, 'invalid zlib data: preset dictionaries not supported');
};
/**
 * Compresses data with GZIP
 * @param data The data to compress
 * @param opts The compression options
 * @returns The gzipped version of the data
 */
function gzipSync(data, opts) {
    if (!opts)
        opts = {};
    var c = crc(), l = data.length;
    c.p(data);
    var d = dopt(data, opts, gzhl(opts), 8), s = d.length;
    return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d;
}
/**
 * Expands GZIP data
 * @param data The data to decompress
 * @param out Where to write the data. GZIP already encodes the output size, so providing this doesn't save memory.
 * @returns The decompressed version of the data
 */
function gunzipSync(data, out) {
    return inflt(data.subarray(gzs(data), -8), out || new u8(gzl(data)));
}
/**
 * Compress data with Zlib
 * @param data The data to compress
 * @param opts The compression options
 * @returns The zlib-compressed version of the data
 */
function zlibSync(data, opts) {
    if (!opts)
        opts = {};
    var a = adler();
    a.p(data);
    var d = dopt(data, opts, 2, 4);
    return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d;
}
/**
 * Expands Zlib data
 * @param data The data to decompress
 * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length.
 * @returns The decompressed version of the data
 */
function unzlibSync(data, out) {
    return inflt((zlv(data), data.subarray(2, -4)), out);
}
// text decoder
var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder();
// text decoder stream
var tds = 0;
try {
    td.decode(et, { stream: true });
    tds = 1;
}
catch (e) { }

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
function getRandomBytes(n) {
    return randomBytes(n);
}
function getRandomBytesF(n) {
    return randomBytes(n);
}
const encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
class EncodingError extends Error {
    constructor() {
        super("Encoding error");
        Object.setPrototypeOf(this, EncodingError.prototype);
    }
}
function getValue(chr) {
    let a = chr;
    switch (chr) {
        case "O":
        case "o":
            a = "0;";
            break;
        case "i":
        case "I":
        case "l":
        case "L":
            a = "1";
            break;
        case "u":
        case "U":
            a = "V";
    }
    if (a >= "0" && a <= "9") {
        return a.charCodeAt(0) - "0".charCodeAt(0);
    }
    if (a >= "a" && a <= "z")
        a = a.toUpperCase();
    let dec = 0;
    if (a >= "A" && a <= "Z") {
        if ("I" < a)
            dec++;
        if ("L" < a)
            dec++;
        if ("O" < a)
            dec++;
        if ("U" < a)
            dec++;
        return a.charCodeAt(0) - "A".charCodeAt(0) + 10 - dec;
    }
    throw new EncodingError();
}
function encodeCrock(data) {
    const dataBytes = new Uint8Array(data);
    let sb = "";
    const size = data.byteLength;
    let bitBuf = 0;
    let numBits = 0;
    let pos = 0;
    while (pos < size || numBits > 0) {
        if (pos < size && numBits < 5) {
            const d = dataBytes[pos++];
            bitBuf = (bitBuf << 8) | d;
            numBits += 8;
        }
        if (numBits < 5) {
            // zero-padding
            bitBuf = bitBuf << (5 - numBits);
            numBits = 5;
        }
        const v = (bitBuf >>> (numBits - 5)) & 31;
        sb += encTable[v];
        numBits -= 5;
    }
    return sb;
}
function decodeCrock(encoded) {
    const size = encoded.length;
    let bitpos = 0;
    let bitbuf = 0;
    let readPosition = 0;
    const outLen = Math.floor((size * 5) / 8);
    const out = new Uint8Array(outLen);
    let outPos = 0;
    while (readPosition < size || bitpos > 0) {
        if (readPosition < size) {
            const v = getValue(encoded[readPosition++]);
            bitbuf = (bitbuf << 5) | v;
            bitpos += 5;
        }
        while (bitpos >= 8) {
            const d = (bitbuf >>> (bitpos - 8)) & 0xff;
            out[outPos++] = d;
            bitpos -= 8;
        }
        if (readPosition == size && bitpos > 0) {
            bitbuf = (bitbuf << (8 - bitpos)) & 0xff;
            bitpos = bitbuf == 0 ? 0 : 8;
        }
    }
    return out;
}
function eddsaGetPublic(eddsaPriv) {
    const pair = crypto_sign_keyPair_fromSeed(eddsaPriv);
    return pair.publicKey;
}
function ecdheGetPublic(ecdhePriv) {
    return scalarMult_base(ecdhePriv);
}
function keyExchangeEcdheEddsa(ecdhePriv, eddsaPub) {
    const curve25519Pub = sign_ed25519_pk_to_curve25519(eddsaPub);
    const x = scalarMult(ecdhePriv, curve25519Pub);
    return hash$1(x);
}
/**
 * KDF modulo a big integer.
 */
function kdfMod(n, ikm, salt, info) {
    const nbits = n.bitLength().toJSNumber();
    const buflen = Math.floor((nbits - 1) / 8 + 1);
    const mask = (1 << (8 - (buflen * 8 - nbits))) - 1;
    let counter = 0;
    while (true) {
        const ctx = new Uint8Array(info.byteLength + 2);
        ctx.set(info, 0);
        ctx[ctx.length - 2] = (counter >>> 8) & 0xff;
        ctx[ctx.length - 1] = counter & 0xff;
        const buf = kdf(buflen, ikm, salt, ctx);
        const arr = Array.from(buf);
        arr[0] = arr[0] & mask;
        const r = bigint.fromArray(arr, 256, false);
        if (r.lt(n)) {
            return r;
        }
        counter++;
    }
}
// Newer versions of node have TextEncoder and TextDecoder as a global,
// just like modern browsers.
// In older versions of node or environments that do not have these
// globals, they must be polyfilled (by adding them to globa/globalThis)
// before stringToBytes or bytesToString is called the first time.
let encoder;
let decoder;
function stringToBytes(s) {
    if (!encoder) {
        // @ts-ignore
        encoder = new TextEncoder();
    }
    return encoder.encode(s);
}
function bytesToString(b) {
    if (!decoder) {
        // @ts-ignore
        decoder = new TextDecoder();
    }
    return decoder.decode(b);
}
function loadBigInt(arr) {
    return bigint.fromArray(Array.from(arr), 256, false);
}
function rsaBlindingKeyDerive(rsaPub, bks) {
    const salt = stringToBytes("Blinding KDF extractor HMAC key");
    const info = stringToBytes("Blinding KDF");
    return kdfMod(rsaPub.N, bks, salt, info);
}
/*
 * Test for malicious RSA key.
 *
 * Assuming n is an RSA modulous and r is generated using a call to
 * GNUNET_CRYPTO_kdf_mod_mpi, if gcd(r,n) != 1 then n must be a
 * malicious RSA key designed to deanomize the user.
 *
 * @param r KDF result
 * @param n RSA modulus of the public key
 */
function rsaGcdValidate(r, n) {
    const t = bigint.gcd(r, n);
    if (!t.equals(bigint.one)) {
        throw Error("malicious RSA public key");
    }
}
function rsaFullDomainHash(hm, rsaPub) {
    const info = stringToBytes("RSA-FDA FTpsW!");
    const salt = rsaPubEncode(rsaPub);
    const r = kdfMod(rsaPub.N, hm, salt, info);
    rsaGcdValidate(r, rsaPub.N);
    return r;
}
function rsaPubDecode(rsaPub) {
    const modulusLength = (rsaPub[0] << 8) | rsaPub[1];
    const exponentLength = (rsaPub[2] << 8) | rsaPub[3];
    if (4 + exponentLength + modulusLength != rsaPub.length) {
        throw Error("invalid RSA public key (format wrong)");
    }
    const modulus = rsaPub.slice(4, 4 + modulusLength);
    const exponent = rsaPub.slice(4 + modulusLength, 4 + modulusLength + exponentLength);
    const res = {
        N: loadBigInt(modulus),
        e: loadBigInt(exponent),
    };
    return res;
}
function rsaPubEncode(rsaPub) {
    const mb = rsaPub.N.toArray(256).value;
    const eb = rsaPub.e.toArray(256).value;
    const out = new Uint8Array(4 + mb.length + eb.length);
    out[0] = (mb.length >>> 8) & 0xff;
    out[1] = mb.length & 0xff;
    out[2] = (eb.length >>> 8) & 0xff;
    out[3] = eb.length & 0xff;
    out.set(mb, 4);
    out.set(eb, 4 + mb.length);
    return out;
}
function rsaBlind(hm, bks, rsaPubEnc) {
    const rsaPub = rsaPubDecode(rsaPubEnc);
    const data = rsaFullDomainHash(hm, rsaPub);
    const r = rsaBlindingKeyDerive(rsaPub, bks);
    const r_e = r.modPow(rsaPub.e, rsaPub.N);
    const bm = r_e.multiply(data).mod(rsaPub.N);
    return new Uint8Array(bm.toArray(256).value);
}
function rsaUnblind(sig, rsaPubEnc, bks) {
    const rsaPub = rsaPubDecode(rsaPubEnc);
    const blinded_s = loadBigInt(sig);
    const r = rsaBlindingKeyDerive(rsaPub, bks);
    const r_inv = r.modInv(rsaPub.N);
    const s = blinded_s.multiply(r_inv).mod(rsaPub.N);
    return new Uint8Array(s.toArray(256).value);
}
function rsaVerify(hm, rsaSig, rsaPubEnc) {
    const rsaPub = rsaPubDecode(rsaPubEnc);
    const d = rsaFullDomainHash(hm, rsaPub);
    const sig = loadBigInt(rsaSig);
    const sig_e = sig.modPow(rsaPub.e, rsaPub.N);
    return sig_e.equals(d);
}
function typedArrayConcat(chunks) {
    let payloadLen = 0;
    for (const c of chunks) {
        payloadLen += c.byteLength;
    }
    const buf = new ArrayBuffer(payloadLen);
    const u8buf = new Uint8Array(buf);
    let p = 0;
    for (const c of chunks) {
        u8buf.set(c, p);
        p += c.byteLength;
    }
    return u8buf;
}
function hash(d) {
    return hash$1(d);
}
/**
 * Hash the input with SHA-512 and truncate the result
 * to 32 bytes.
 */
function hashTruncate32(d) {
    const sha512HashCode = hash$1(d);
    return sha512HashCode.subarray(0, 32);
}
function hashCoinEv(coinEv, denomPubHash) {
    const hashContext = createHashContext();
    hashContext.update(decodeCrock(denomPubHash));
    hashCoinEvInner(coinEv, hashContext);
    return hashContext.finish();
}
new Logger("talerCrypto.ts");
function hashCoinEvInner(coinEv, hashState) {
    const hashInputBuf = new ArrayBuffer(4);
    const uint8ArrayBuf = new Uint8Array(hashInputBuf);
    const dv = new DataView(hashInputBuf);
    dv.setUint32(0, DenomKeyType.toIntTag(coinEv.cipher));
    hashState.update(uint8ArrayBuf);
    switch (coinEv.cipher) {
        case DenomKeyType.Rsa:
            hashState.update(decodeCrock(coinEv.rsa_blinded_planchet));
            return;
        default:
            throw new Error();
    }
}
function hashCoinPub(coinPub, ach) {
    if (!ach) {
        return hash(decodeCrock(coinPub));
    }
    return hash(typedArrayConcat([decodeCrock(coinPub), decodeCrock(ach)]));
}
/**
 * Hash a denomination public key.
 */
function hashDenomPub(pub) {
    var _a, _b;
    if (pub.cipher === DenomKeyType.Rsa) {
        const pubBuf = decodeCrock(pub.rsa_public_key);
        const hashInputBuf = new ArrayBuffer(pubBuf.length + 4 + 4);
        const uint8ArrayBuf = new Uint8Array(hashInputBuf);
        const dv = new DataView(hashInputBuf);
        dv.setUint32(0, (_a = pub.age_mask) !== null && _a !== void 0 ? _a : 0);
        dv.setUint32(4, DenomKeyType.toIntTag(pub.cipher));
        uint8ArrayBuf.set(pubBuf, 8);
        return hash$1(uint8ArrayBuf);
    }
    else if (pub.cipher === DenomKeyType.ClauseSchnorr) {
        const pubBuf = decodeCrock(pub.cs_public_key);
        const hashInputBuf = new ArrayBuffer(pubBuf.length + 4 + 4);
        const uint8ArrayBuf = new Uint8Array(hashInputBuf);
        const dv = new DataView(hashInputBuf);
        dv.setUint32(0, (_b = pub.age_mask) !== null && _b !== void 0 ? _b : 0);
        dv.setUint32(4, DenomKeyType.toIntTag(pub.cipher));
        uint8ArrayBuf.set(pubBuf, 8);
        return hash$1(uint8ArrayBuf);
    }
    else {
        throw Error(`unsupported cipher (${pub.cipher}), unable to hash`);
    }
}
function eddsaSign(msg, eddsaPriv) {
    const pair = crypto_sign_keyPair_fromSeed(eddsaPriv);
    return sign_detached(msg, pair.secretKey);
}
function eddsaVerify(msg, sig, eddsaPub) {
    return sign_detached_verify(msg, sig, eddsaPub);
}
function createHashContext() {
    return new HashState();
}
function bufferForUint32(n) {
    const arrBuf = new ArrayBuffer(4);
    const buf = new Uint8Array(arrBuf);
    const dv = new DataView(arrBuf);
    dv.setUint32(0, n);
    return buf;
}
function setupTipPlanchet(secretSeed, denomPub, coinNumber) {
    return __awaiter(this, void 0, void 0, function* () {
        const info = stringToBytes("taler-tip-coin-derivation");
        const saltArrBuf = new ArrayBuffer(4);
        const salt = new Uint8Array(saltArrBuf);
        const saltDataView = new DataView(saltArrBuf);
        saltDataView.setUint32(0, coinNumber);
        const out = kdf(64, secretSeed, salt, info);
        const coinPriv = out.slice(0, 32);
        const bks = out.slice(32, 64);
        let maybeAcp;
        if (denomPub.age_mask != 0) {
            maybeAcp = yield AgeRestriction.restrictionCommitSeeded(denomPub.age_mask, AgeRestriction.AGE_UNRESTRICTED, secretSeed);
        }
        return {
            bks,
            coinPriv,
            coinPub: eddsaGetPublic(coinPriv),
            maxAge: AgeRestriction.AGE_UNRESTRICTED,
            ageCommitmentProof: maybeAcp,
        };
    });
}
/**
 *
 * @param paytoUri
 * @param salt 16-byte salt
 * @returns
 */
function hashWire(paytoUri, salt) {
    const r = kdf(64, stringToBytes(paytoUri + "\0"), decodeCrock(salt), stringToBytes("merchant-wire-signature"));
    return encodeCrock(r);
}
var TalerSignaturePurpose;
(function (TalerSignaturePurpose) {
    TalerSignaturePurpose[TalerSignaturePurpose["MERCHANT_TRACK_TRANSACTION"] = 1103] = "MERCHANT_TRACK_TRANSACTION";
    TalerSignaturePurpose[TalerSignaturePurpose["WALLET_RESERVE_WITHDRAW"] = 1200] = "WALLET_RESERVE_WITHDRAW";
    TalerSignaturePurpose[TalerSignaturePurpose["WALLET_COIN_DEPOSIT"] = 1201] = "WALLET_COIN_DEPOSIT";
    TalerSignaturePurpose[TalerSignaturePurpose["MASTER_DENOMINATION_KEY_VALIDITY"] = 1025] = "MASTER_DENOMINATION_KEY_VALIDITY";
    TalerSignaturePurpose[TalerSignaturePurpose["MASTER_WIRE_FEES"] = 1028] = "MASTER_WIRE_FEES";
    TalerSignaturePurpose[TalerSignaturePurpose["MASTER_WIRE_DETAILS"] = 1030] = "MASTER_WIRE_DETAILS";
    TalerSignaturePurpose[TalerSignaturePurpose["WALLET_COIN_MELT"] = 1202] = "WALLET_COIN_MELT";
    TalerSignaturePurpose[TalerSignaturePurpose["TEST"] = 4242] = "TEST";
    TalerSignaturePurpose[TalerSignaturePurpose["MERCHANT_PAYMENT_OK"] = 1104] = "MERCHANT_PAYMENT_OK";
    TalerSignaturePurpose[TalerSignaturePurpose["MERCHANT_CONTRACT"] = 1101] = "MERCHANT_CONTRACT";
    TalerSignaturePurpose[TalerSignaturePurpose["WALLET_COIN_RECOUP"] = 1203] = "WALLET_COIN_RECOUP";
    TalerSignaturePurpose[TalerSignaturePurpose["WALLET_COIN_LINK"] = 1204] = "WALLET_COIN_LINK";
    TalerSignaturePurpose[TalerSignaturePurpose["WALLET_COIN_RECOUP_REFRESH"] = 1206] = "WALLET_COIN_RECOUP_REFRESH";
    TalerSignaturePurpose[TalerSignaturePurpose["WALLET_AGE_ATTESTATION"] = 1207] = "WALLET_AGE_ATTESTATION";
    TalerSignaturePurpose[TalerSignaturePurpose["WALLET_PURSE_CREATE"] = 1210] = "WALLET_PURSE_CREATE";
    TalerSignaturePurpose[TalerSignaturePurpose["WALLET_PURSE_DEPOSIT"] = 1211] = "WALLET_PURSE_DEPOSIT";
    TalerSignaturePurpose[TalerSignaturePurpose["WALLET_PURSE_MERGE"] = 1213] = "WALLET_PURSE_MERGE";
    TalerSignaturePurpose[TalerSignaturePurpose["WALLET_ACCOUNT_MERGE"] = 1214] = "WALLET_ACCOUNT_MERGE";
    TalerSignaturePurpose[TalerSignaturePurpose["WALLET_PURSE_ECONTRACT"] = 1216] = "WALLET_PURSE_ECONTRACT";
    TalerSignaturePurpose[TalerSignaturePurpose["EXCHANGE_CONFIRM_RECOUP"] = 1039] = "EXCHANGE_CONFIRM_RECOUP";
    TalerSignaturePurpose[TalerSignaturePurpose["EXCHANGE_CONFIRM_RECOUP_REFRESH"] = 1041] = "EXCHANGE_CONFIRM_RECOUP_REFRESH";
    TalerSignaturePurpose[TalerSignaturePurpose["ANASTASIS_POLICY_UPLOAD"] = 1400] = "ANASTASIS_POLICY_UPLOAD";
    TalerSignaturePurpose[TalerSignaturePurpose["ANASTASIS_POLICY_DOWNLOAD"] = 1401] = "ANASTASIS_POLICY_DOWNLOAD";
    TalerSignaturePurpose[TalerSignaturePurpose["SYNC_BACKUP_UPLOAD"] = 1450] = "SYNC_BACKUP_UPLOAD";
})(TalerSignaturePurpose || (TalerSignaturePurpose = {}));
class SignaturePurposeBuilder {
    constructor(purposeNum) {
        this.purposeNum = purposeNum;
        this.chunks = [];
    }
    put(bytes) {
        this.chunks.push(Uint8Array.from(bytes));
        return this;
    }
    build() {
        let payloadLen = 0;
        for (const c of this.chunks) {
            payloadLen += c.byteLength;
        }
        const buf = new ArrayBuffer(4 + 4 + payloadLen);
        const u8buf = new Uint8Array(buf);
        let p = 8;
        for (const c of this.chunks) {
            u8buf.set(c, p);
            p += c.byteLength;
        }
        const dvbuf = new DataView(buf);
        dvbuf.setUint32(0, payloadLen + 4 + 4);
        dvbuf.setUint32(4, this.purposeNum);
        return u8buf;
    }
}
function buildSigPS(purposeNum) {
    return new SignaturePurposeBuilder(purposeNum);
}
/**
 * Convert a big integer to a fixed-size, little-endian array.
 */
function bigintToNaclArr(x, size) {
    const byteArr = new Uint8Array(size);
    const arr = x.toArray(256).value.reverse();
    byteArr.set(arr, 0);
    return byteArr;
}
function bigintFromNaclArr(arr) {
    let rev = new Uint8Array(arr);
    rev = rev.reverse();
    return bigint.fromArray(Array.from(rev), 256, false);
}
var Edx25519;
(function (Edx25519) {
    const revL = [
        0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2,
        0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10,
    ];
    const L = bigint.fromArray(revL.reverse(), 256, false);
    function keyCreateFromSeed(seed) {
        return __awaiter(this, void 0, void 0, function* () {
            return crypto_edx25519_private_key_create_from_seed(seed);
        });
    }
    Edx25519.keyCreateFromSeed = keyCreateFromSeed;
    function keyCreate() {
        return __awaiter(this, void 0, void 0, function* () {
            return crypto_edx25519_private_key_create();
        });
    }
    Edx25519.keyCreate = keyCreate;
    function getPublic(priv) {
        return __awaiter(this, void 0, void 0, function* () {
            return crypto_edx25519_get_public(priv);
        });
    }
    Edx25519.getPublic = getPublic;
    function sign(msg, key) {
        throw Error("not implemented");
    }
    Edx25519.sign = sign;
    function deriveFactor(pub, seed) {
        return __awaiter(this, void 0, void 0, function* () {
            const res = kdfKw({
                outputLength: 64,
                salt: seed,
                ikm: pub,
                info: stringToBytes("edx25519-derivation"),
            });
            return res;
        });
    }
    function privateKeyDerive(priv, seed) {
        return __awaiter(this, void 0, void 0, function* () {
            const pub = yield getPublic(priv);
            const privDec = priv;
            const a = bigintFromNaclArr(privDec.subarray(0, 32));
            const factorEnc = yield deriveFactor(pub, seed);
            const factorModL = bigintFromNaclArr(factorEnc).mod(L);
            const aPrime = a.divide(8).multiply(factorModL).mod(L).multiply(8).mod(L);
            const bPrime = hash$1(typedArrayConcat([privDec.subarray(32, 64), factorEnc]))
                .subarray(0, 32);
            const newPriv = typedArrayConcat([bigintToNaclArr(aPrime, 32), bPrime]);
            return newPriv;
        });
    }
    Edx25519.privateKeyDerive = privateKeyDerive;
    function publicKeyDerive(pub, seed) {
        return __awaiter(this, void 0, void 0, function* () {
            const factorEnc = yield deriveFactor(pub, seed);
            const factorReduced = crypto_core_ed25519_scalar_reduce(factorEnc);
            const res = crypto_scalarmult_ed25519_noclamp(factorReduced, pub);
            return res;
        });
    }
    Edx25519.publicKeyDerive = publicKeyDerive;
})(Edx25519 || (Edx25519 = {}));
function invariant(cond) {
    if (!cond) {
        throw Error("invariant failed");
    }
}
var AgeRestriction;
(function (AgeRestriction) {
    /**
     * Smallest age value that the protocol considers "unrestricted".
     */
    AgeRestriction.AGE_UNRESTRICTED = 32;
    function hashCommitment(ac) {
        const hc = new HashState();
        for (const pub of ac.publicKeys) {
            hc.update(decodeCrock(pub));
        }
        return encodeCrock(hc.finish().subarray(0, 32));
    }
    AgeRestriction.hashCommitment = hashCommitment;
    function countAgeGroups(mask) {
        let count = 0;
        let m = mask;
        while (m > 0) {
            count += m & 1;
            m = m >> 1;
        }
        return count;
    }
    AgeRestriction.countAgeGroups = countAgeGroups;
    function getAgeGroupIndex(mask, age) {
        invariant((mask & 1) === 1);
        let i = 0;
        let m = mask;
        let a = age;
        while (m > 0) {
            if (a <= 0) {
                break;
            }
            m = m >> 1;
            i += m & 1;
            a--;
        }
        return i;
    }
    AgeRestriction.getAgeGroupIndex = getAgeGroupIndex;
    function ageGroupSpecToMask(ageGroupSpec) {
        throw Error("not implemented");
    }
    AgeRestriction.ageGroupSpecToMask = ageGroupSpecToMask;
    function restrictionCommit(ageMask, age) {
        return __awaiter(this, void 0, void 0, function* () {
            invariant((ageMask & 1) === 1);
            const numPubs = countAgeGroups(ageMask) - 1;
            const numPrivs = getAgeGroupIndex(ageMask, age);
            const pubs = [];
            const privs = [];
            for (let i = 0; i < numPubs; i++) {
                const priv = yield Edx25519.keyCreate();
                const pub = yield Edx25519.getPublic(priv);
                pubs.push(pub);
                if (i < numPrivs) {
                    privs.push(priv);
                }
            }
            return {
                commitment: {
                    mask: ageMask,
                    publicKeys: pubs.map((x) => encodeCrock(x)),
                },
                proof: {
                    privateKeys: privs.map((x) => encodeCrock(x)),
                },
            };
        });
    }
    AgeRestriction.restrictionCommit = restrictionCommit;
    function restrictionCommitSeeded(ageMask, age, seed) {
        return __awaiter(this, void 0, void 0, function* () {
            invariant((ageMask & 1) === 1);
            const numPubs = countAgeGroups(ageMask) - 1;
            const numPrivs = getAgeGroupIndex(ageMask, age);
            const pubs = [];
            const privs = [];
            for (let i = 0; i < numPubs; i++) {
                const privSeed = yield kdfKw({
                    outputLength: 32,
                    ikm: seed,
                    info: stringToBytes("age-restriction-commit"),
                    salt: bufferForUint32(i),
                });
                const priv = yield Edx25519.keyCreateFromSeed(privSeed);
                const pub = yield Edx25519.getPublic(priv);
                pubs.push(pub);
                if (i < numPrivs) {
                    privs.push(priv);
                }
            }
            return {
                commitment: {
                    mask: ageMask,
                    publicKeys: pubs.map((x) => encodeCrock(x)),
                },
                proof: {
                    privateKeys: privs.map((x) => encodeCrock(x)),
                },
            };
        });
    }
    AgeRestriction.restrictionCommitSeeded = restrictionCommitSeeded;
    /**
     * Check that c1 = c2*salt
     */
    function commitCompare(c1, c2, salt) {
        return __awaiter(this, void 0, void 0, function* () {
            if (c1.publicKeys.length != c2.publicKeys.length) {
                return false;
            }
            for (let i = 0; i < c1.publicKeys.length; i++) {
                const k1 = decodeCrock(c1.publicKeys[i]);
                const k2 = yield Edx25519.publicKeyDerive(decodeCrock(c2.publicKeys[i]), salt);
                if (k1 != k2) {
                    return false;
                }
            }
            return true;
        });
    }
    AgeRestriction.commitCompare = commitCompare;
    function commitmentDerive(commitmentProof, salt) {
        return __awaiter(this, void 0, void 0, function* () {
            const newPrivs = [];
            const newPubs = [];
            for (const oldPub of commitmentProof.commitment.publicKeys) {
                newPubs.push(yield Edx25519.publicKeyDerive(decodeCrock(oldPub), salt));
            }
            for (const oldPriv of commitmentProof.proof.privateKeys) {
                newPrivs.push(yield Edx25519.privateKeyDerive(decodeCrock(oldPriv), salt));
            }
            return {
                commitment: {
                    mask: commitmentProof.commitment.mask,
                    publicKeys: newPubs.map((x) => encodeCrock(x)),
                },
                proof: {
                    privateKeys: newPrivs.map((x) => encodeCrock(x)),
                },
            };
        });
    }
    AgeRestriction.commitmentDerive = commitmentDerive;
    function commitmentAttest(commitmentProof, age) {
        const d = buildSigPS(TalerSignaturePurpose.WALLET_AGE_ATTESTATION)
            .put(bufferForUint32(commitmentProof.commitment.mask))
            .put(bufferForUint32(age))
            .build();
        const group = getAgeGroupIndex(commitmentProof.commitment.mask, age);
        if (group === 0) {
            // No attestation required.
            return new Uint8Array(64);
        }
        const priv = commitmentProof.proof.privateKeys[group - 1];
        const pub = commitmentProof.commitment.publicKeys[group - 1];
        const sig = crypto_edx25519_sign_detached(d, decodeCrock(priv), decodeCrock(pub));
        return sig;
    }
    AgeRestriction.commitmentAttest = commitmentAttest;
    function commitmentVerify(commitment, sig, age) {
        const d = buildSigPS(TalerSignaturePurpose.WALLET_AGE_ATTESTATION)
            .put(bufferForUint32(commitment.mask))
            .put(bufferForUint32(age))
            .build();
        const group = getAgeGroupIndex(commitment.mask, age);
        if (group === 0) {
            // No attestation required.
            return true;
        }
        const pub = commitment.publicKeys[group - 1];
        return crypto_edx25519_sign_detached_verify(d, decodeCrock(sig), decodeCrock(pub));
    }
    AgeRestriction.commitmentVerify = commitmentVerify;
})(AgeRestriction || (AgeRestriction = {}));
function deriveKey(keySeed, nonce, salt) {
    return __awaiter(this, void 0, void 0, function* () {
        return kdfKw({
            outputLength: 32,
            salt: nonce,
            ikm: keySeed,
            info: stringToBytes(salt),
        });
    });
}
function encryptWithDerivedKey(nonce, keySeed, plaintext, salt) {
    return __awaiter(this, void 0, void 0, function* () {
        const key = yield deriveKey(keySeed, nonce, salt);
        const cipherText = secretbox(plaintext, nonce, key);
        return typedArrayConcat([nonce, cipherText]);
    });
}
const nonceSize = 24;
function decryptWithDerivedKey(ciphertext, keySeed, salt) {
    return __awaiter(this, void 0, void 0, function* () {
        const ctBuf = ciphertext;
        const nonceBuf = ctBuf.slice(0, nonceSize);
        const enc = ctBuf.slice(nonceSize);
        const key = yield deriveKey(keySeed, nonceBuf, salt);
        const clearText = secretbox_open(enc, nonceBuf, key);
        if (!clearText) {
            throw Error("could not decrypt");
        }
        return clearText;
    });
}
var ContractFormatTag;
(function (ContractFormatTag) {
    ContractFormatTag[ContractFormatTag["PaymentOffer"] = 0] = "PaymentOffer";
    ContractFormatTag[ContractFormatTag["PaymentRequest"] = 1] = "PaymentRequest";
})(ContractFormatTag || (ContractFormatTag = {}));
const mergeSalt = "p2p-merge-contract";
const depositSalt = "p2p-deposit-contract";
function encryptContractForMerge(pursePub, contractPriv, mergePriv, contractTerms) {
    const contractTermsCanon = canonicalJson(contractTerms) + "\0";
    const contractTermsBytes = stringToBytes(contractTermsCanon);
    const contractTermsCompressed = zlibSync(contractTermsBytes);
    const data = typedArrayConcat([
        bufferForUint32(ContractFormatTag.PaymentOffer),
        bufferForUint32(contractTermsBytes.length),
        mergePriv,
        contractTermsCompressed,
    ]);
    const key = keyExchangeEcdheEddsa(contractPriv, pursePub);
    return encryptWithDerivedKey(getRandomBytesF(24), key, data, mergeSalt);
}
function encryptContractForDeposit(pursePub, contractPriv, contractTerms) {
    const contractTermsCanon = canonicalJson(contractTerms) + "\0";
    const contractTermsBytes = stringToBytes(contractTermsCanon);
    const contractTermsCompressed = zlibSync(contractTermsBytes);
    const data = typedArrayConcat([
        bufferForUint32(ContractFormatTag.PaymentRequest),
        bufferForUint32(contractTermsBytes.length),
        contractTermsCompressed,
    ]);
    const key = keyExchangeEcdheEddsa(contractPriv, pursePub);
    return encryptWithDerivedKey(getRandomBytesF(24), key, data, depositSalt);
}
function decryptContractForMerge(enc, pursePub, contractPriv) {
    return __awaiter(this, void 0, void 0, function* () {
        const key = keyExchangeEcdheEddsa(contractPriv, pursePub);
        const dec = yield decryptWithDerivedKey(enc, key, mergeSalt);
        const mergePriv = dec.slice(8, 8 + 32);
        const contractTermsCompressed = dec.slice(8 + 32);
        const contractTermsBuf = unzlibSync(contractTermsCompressed);
        // Slice of the '\0' at the end and decode to a string
        const contractTermsString = bytesToString(contractTermsBuf.slice(0, contractTermsBuf.length - 1));
        return {
            mergePriv: mergePriv,
            contractTerms: JSON.parse(contractTermsString),
        };
    });
}
function decryptContractForDeposit(enc, pursePub, contractPriv) {
    return __awaiter(this, void 0, void 0, function* () {
        const key = keyExchangeEcdheEddsa(contractPriv, pursePub);
        const dec = yield decryptWithDerivedKey(enc, key, depositSalt);
        const contractTermsCompressed = dec.slice(8);
        const contractTermsBuf = unzlibSync(contractTermsCompressed);
        // Slice of the '\0' at the end and decode to a string
        const contractTermsString = bytesToString(contractTermsBuf.slice(0, contractTermsBuf.length - 1));
        return {
            contractTerms: JSON.parse(contractTermsString),
        };
    });
}

// Copyright (c) 2017, 2021 Pieter Wuille
//
// 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.
var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
var GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
const encodings = {
    BECH32: "bech32",
    BECH32M: "bech32m",
};
var bech32 = {
    decode: decode$1,
    encode: encode$2,
    encodings: encodings,
};
function getEncodingConst(enc) {
    if (enc == encodings.BECH32) {
        return 1;
    }
    else if (enc == encodings.BECH32M) {
        return 0x2bc830a3;
    }
    else {
        throw new Error("unknown encoding");
    }
}
function polymod(values) {
    var chk = 1;
    for (var p = 0; p < values.length; ++p) {
        var top = chk >> 25;
        chk = ((chk & 0x1ffffff) << 5) ^ values[p];
        for (var i = 0; i < 5; ++i) {
            if ((top >> i) & 1) {
                chk ^= GENERATOR[i];
            }
        }
    }
    return chk;
}
function hrpExpand(hrp) {
    var ret = [];
    var p;
    for (p = 0; p < hrp.length; ++p) {
        ret.push(hrp.charCodeAt(p) >> 5);
    }
    ret.push(0);
    for (p = 0; p < hrp.length; ++p) {
        ret.push(hrp.charCodeAt(p) & 31);
    }
    return ret;
}
function verifyChecksum(hrp, data, enc) {
    return polymod(hrpExpand(hrp).concat(data)) === getEncodingConst(enc);
}
function createChecksum(hrp, data, enc) {
    var values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
    var mod = polymod(values) ^ getEncodingConst(enc);
    var ret = [];
    for (var p = 0; p < 6; ++p) {
        ret.push((mod >> (5 * (5 - p))) & 31);
    }
    return ret;
}
function encode$2(hrp, data, enc) {
    var combined = data.concat(createChecksum(hrp, data, enc));
    var ret = hrp + "1";
    for (var p = 0; p < combined.length; ++p) {
        ret += CHARSET.charAt(combined[p]);
    }
    return ret;
}
function decode$1(bechString, enc) {
    var p;
    var has_lower = false;
    var has_upper = false;
    for (p = 0; p < bechString.length; ++p) {
        if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) {
            return null;
        }
        if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) {
            has_lower = true;
        }
        if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) {
            has_upper = true;
        }
    }
    if (has_lower && has_upper) {
        return null;
    }
    bechString = bechString.toLowerCase();
    var pos = bechString.lastIndexOf("1");
    if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) {
        return null;
    }
    var hrp = bechString.substring(0, pos);
    var data = [];
    for (p = pos + 1; p < bechString.length; ++p) {
        var d = CHARSET.indexOf(bechString.charAt(p));
        if (d === -1) {
            return null;
        }
        data.push(d);
    }
    if (!verifyChecksum(hrp, data, enc)) {
        return null;
    }
    return { hrp: hrp, data: data.slice(0, data.length - 6) };
}

// Copyright (c) 2017, 2021 Pieter Wuille
var segwit_addr = {
    encode: encode$1,
    decode: decode,
};
function convertbits(data, frombits, tobits, pad) {
    var acc = 0;
    var bits = 0;
    var ret = [];
    var maxv = (1 << tobits) - 1;
    for (var p = 0; p < data.length; ++p) {
        var value = data[p];
        if (value < 0 || value >> frombits !== 0) {
            return []; //check this, was returning null
        }
        acc = (acc << frombits) | value;
        bits += frombits;
        while (bits >= tobits) {
            bits -= tobits;
            ret.push((acc >> bits) & maxv);
        }
    }
    if (pad) {
        if (bits > 0) {
            ret.push((acc << (tobits - bits)) & maxv);
        }
    }
    else if (bits >= frombits || (acc << (tobits - bits)) & maxv) {
        return []; //check this, was returning null
    }
    return ret;
}
function decode(hrp, addr) {
    var bech32m = false;
    var dec = bech32.decode(addr, bech32.encodings.BECH32);
    if (dec === null) {
        dec = bech32.decode(addr, bech32.encodings.BECH32M);
        bech32m = true;
    }
    if (dec === null ||
        dec.hrp !== hrp ||
        dec.data.length < 1 ||
        dec.data[0] > 16) {
        return null;
    }
    var res = convertbits(dec.data.slice(1), 5, 8, false);
    if (res === null || res.length < 2 || res.length > 40) {
        return null;
    }
    if (dec.data[0] === 0 && res.length !== 20 && res.length !== 32) {
        return null;
    }
    if (dec.data[0] === 0 && bech32m) {
        return null;
    }
    if (dec.data[0] !== 0 && !bech32m) {
        return null;
    }
    return { version: dec.data[0], program: res };
}
function encode$1(hrp, version, program) {
    var enc = bech32.encodings.BECH32;
    if (version > 0) {
        enc = bech32.encodings.BECH32M;
    }
    var ret = bech32.encode(hrp, [version].concat(convertbits(program, 8, 5, true)), enc);
    if (decode(hrp, ret /*, enc*/) === null) {
        return ""; //check this was returning null
    }
    return ret;
}

/*
 This file is part of GNU Taler
 (C) 2019 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
function generateFakeSegwitAddress(reservePub, addr) {
    if (!reservePub)
        return [];
    let pub;
    try {
        pub = decodeCrock(reservePub);
    }
    catch (_a) {
        // pub = new Uint8Array(0)
    }
    if (!pub || pub.length !== 32)
        return [];
    const first_rnd = new Uint8Array(4);
    first_rnd.set(pub.subarray(0, 4));
    const second_rnd = new Uint8Array(4);
    second_rnd.set(pub.subarray(0, 4));
    first_rnd[0] = first_rnd[0] & 127;
    second_rnd[0] = second_rnd[0] | 128;
    const first_part = new Uint8Array(first_rnd.length + pub.length / 2);
    first_part.set(first_rnd, 0);
    first_part.set(pub.subarray(0, 16), 4);
    const second_part = new Uint8Array(first_rnd.length + pub.length / 2);
    second_part.set(second_rnd, 0);
    second_part.set(pub.subarray(16, 32), 4);
    const prefix = addr[0] === "t" && addr[1] == "b"
        ? "tb"
        : addr[0] === "b" && addr[1] == "c" && addr[2] === "r" && addr[3] == "t"
            ? "bcrt"
            : addr[0] === "b" && addr[1] == "c"
                ? "bc"
                : undefined;
    if (prefix === undefined)
        throw new Error("unknown bitcoin net");
    const addr1 = segwit_addr.encode(prefix, 0, first_part);
    const addr2 = segwit_addr.encode(prefix, 0, second_part);
    return [addr1, addr2];
}

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const paytoPfx = "payto://";
/**
 * Add query parameters to a payto URI
 */
function addPaytoQueryParams(s, params) {
    const [acct, search] = s.slice(paytoPfx.length).split("?");
    const searchParams = new URLSearchParams(search || "");
    const keys = Object.keys(params);
    if (keys.length === 0) {
        return paytoPfx + acct;
    }
    for (const k of keys) {
        searchParams.set(k, params[k]);
    }
    return paytoPfx + acct + "?" + searchParams.toString();
}
/**
 * Parse a valid payto:// uri into a PaytoUri object
 * RFC 8905
 *
 * @param s
 * @returns
 */
function parsePaytoUri(s) {
    if (!s.startsWith(paytoPfx)) {
        return undefined;
    }
    const [acct, search] = s.slice(paytoPfx.length).split("?");
    const firstSlashPos = acct.indexOf("/");
    if (firstSlashPos === -1) {
        return undefined;
    }
    const targetType = acct.slice(0, firstSlashPos);
    const targetPath = acct.slice(firstSlashPos + 1);
    const params = {};
    const searchParams = new URLSearchParams(search || "");
    searchParams.forEach((v, k) => {
        params[k] = v;
    });
    if (targetType === "x-taler-bank") {
        const parts = targetPath.split("/");
        const host = parts[0];
        const account = parts[1];
        return {
            targetPath,
            targetType,
            params,
            isKnown: true,
            host,
            account,
        };
    }
    if (targetType === "iban") {
        return {
            isKnown: true,
            targetPath,
            targetType,
            params,
            iban: targetPath,
        };
    }
    if (targetType === "bitcoin") {
        const msg = /\b([A-Z0-9]{52})\b/.exec(params["message"]);
        const reserve = !msg ? params["subject"] : msg[0];
        const segwitAddrs = !reserve
            ? []
            : generateFakeSegwitAddress(reserve, targetPath);
        const result = {
            isKnown: true,
            targetPath,
            targetType,
            params,
            segwitAddrs,
        };
        return result;
    }
    return {
        targetPath,
        targetType,
        params,
        isKnown: false,
    };
}

/*
 This file is part of GNU Taler
 (C) 2019 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const codecForReserveStatus = () => buildCodecForObject()
    .property("balance", codecForString())
    .build("ReserveStatus");

/*
 This file is part of GNU Taler
 (C) 2019 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
var ReserveTransactionType;
(function (ReserveTransactionType) {
    ReserveTransactionType["Withdraw"] = "WITHDRAW";
    ReserveTransactionType["Credit"] = "CREDIT";
    ReserveTransactionType["Recoup"] = "RECOUP";
    ReserveTransactionType["Closing"] = "CLOSING";
})(ReserveTransactionType || (ReserveTransactionType = {}));

/*
 This file is part of GNU Taler
 (C) 2019-2020 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * Parse a taler[+http]://withdraw URI.
 * Return undefined if not passed a valid URI.
 */
function parseWithdrawUri(s) {
    const pi = parseProtoInfo(s, "withdraw");
    if (!pi) {
        return undefined;
    }
    const parts = pi.rest.split("/");
    if (parts.length < 2) {
        return undefined;
    }
    const host = parts[0].toLowerCase();
    const pathSegments = parts.slice(1, parts.length - 1);
    /**
     * The statement below does not tolerate a slash-ended URI.
     * This results in (1) the withdrawalId being passed as the
     * empty string, and (2) the bankIntegrationApi ending with the
     * actual withdrawal operation ID.  That can be fixed by
     * trimming the parts-list.  FIXME
     */
    const withdrawId = parts[parts.length - 1];
    const p = [host, ...pathSegments].join("/");
    return {
        bankIntegrationApiBaseUrl: canonicalizeBaseUrl(`${pi.innerProto}://${p}/`),
        withdrawalOperationId: withdrawId,
    };
}
var TalerUriType;
(function (TalerUriType) {
    TalerUriType["TalerPay"] = "taler-pay";
    TalerUriType["TalerWithdraw"] = "taler-withdraw";
    TalerUriType["TalerTip"] = "taler-tip";
    TalerUriType["TalerRefund"] = "taler-refund";
    TalerUriType["TalerNotifyReserve"] = "taler-notify-reserve";
    TalerUriType["TalerPayPush"] = "taler-pay-push";
    TalerUriType["TalerPayPull"] = "taler-pay-pull";
    TalerUriType["Unknown"] = "unknown";
})(TalerUriType || (TalerUriType = {}));
const talerActionPayPull = "pay-pull";
const talerActionPayPush = "pay-push";
function parseProtoInfo(s, action) {
    const pfxPlain = `taler://${action}/`;
    const pfxHttp = `taler+http://${action}/`;
    if (s.toLowerCase().startsWith(pfxPlain)) {
        return {
            innerProto: "https",
            rest: s.substring(pfxPlain.length),
        };
    }
    else if (s.toLowerCase().startsWith(pfxHttp)) {
        return {
            innerProto: "http",
            rest: s.substring(pfxHttp.length),
        };
    }
    else {
        return undefined;
    }
}
/**
 * Parse a taler[+http]://pay URI.
 * Return undefined if not passed a valid URI.
 */
function parsePayUri(s) {
    var _a, _b, _c;
    const pi = parseProtoInfo(s, "pay");
    if (!pi) {
        return undefined;
    }
    const c = pi === null || pi === void 0 ? void 0 : pi.rest.split("?");
    const q = new URLSearchParams((_a = c[1]) !== null && _a !== void 0 ? _a : "");
    const claimToken = (_b = q.get("c")) !== null && _b !== void 0 ? _b : undefined;
    const noncePriv = (_c = q.get("n")) !== null && _c !== void 0 ? _c : undefined;
    const parts = c[0].split("/");
    if (parts.length < 3) {
        return undefined;
    }
    const host = parts[0].toLowerCase();
    const sessionId = parts[parts.length - 1];
    const orderId = parts[parts.length - 2];
    const pathSegments = parts.slice(1, parts.length - 2);
    const p = [host, ...pathSegments].join("/");
    const merchantBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
    return {
        merchantBaseUrl,
        orderId,
        sessionId: sessionId,
        claimToken,
        noncePriv,
    };
}
function parsePayPushUri(s) {
    const pi = parseProtoInfo(s, talerActionPayPush);
    if (!pi) {
        return undefined;
    }
    const c = pi === null || pi === void 0 ? void 0 : pi.rest.split("?");
    const parts = c[0].split("/");
    if (parts.length < 2) {
        return undefined;
    }
    const host = parts[0].toLowerCase();
    const contractPriv = parts[parts.length - 1];
    const pathSegments = parts.slice(1, parts.length - 1);
    const p = [host, ...pathSegments].join("/");
    const exchangeBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
    return {
        exchangeBaseUrl,
        contractPriv,
    };
}
function parsePayPullUri(s) {
    const pi = parseProtoInfo(s, talerActionPayPull);
    if (!pi) {
        return undefined;
    }
    const c = pi === null || pi === void 0 ? void 0 : pi.rest.split("?");
    const parts = c[0].split("/");
    if (parts.length < 2) {
        return undefined;
    }
    const host = parts[0].toLowerCase();
    const contractPriv = parts[parts.length - 1];
    const pathSegments = parts.slice(1, parts.length - 1);
    const p = [host, ...pathSegments].join("/");
    const exchangeBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
    return {
        exchangeBaseUrl,
        contractPriv,
    };
}
/**
 * Parse a taler[+http]://tip URI.
 * Return undefined if not passed a valid URI.
 */
function parseTipUri(s) {
    const pi = parseProtoInfo(s, "tip");
    if (!pi) {
        return undefined;
    }
    const c = pi === null || pi === void 0 ? void 0 : pi.rest.split("?");
    const parts = c[0].split("/");
    if (parts.length < 2) {
        return undefined;
    }
    const host = parts[0].toLowerCase();
    const tipId = parts[parts.length - 1];
    const pathSegments = parts.slice(1, parts.length - 1);
    const p = [host, ...pathSegments].join("/");
    const merchantBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
    return {
        merchantBaseUrl,
        merchantTipId: tipId,
    };
}
/**
 * Parse a taler[+http]://refund URI.
 * Return undefined if not passed a valid URI.
 */
function parseRefundUri(s) {
    const pi = parseProtoInfo(s, "refund");
    if (!pi) {
        return undefined;
    }
    const c = pi === null || pi === void 0 ? void 0 : pi.rest.split("?");
    const parts = c[0].split("/");
    if (parts.length < 3) {
        return undefined;
    }
    const host = parts[0].toLowerCase();
    parts[parts.length - 1];
    const orderId = parts[parts.length - 2];
    const pathSegments = parts.slice(1, parts.length - 2);
    const p = [host, ...pathSegments].join("/");
    const merchantBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
    return {
        merchantBaseUrl,
        orderId,
    };
}
function constructPayPushUri(args) {
    const url = new URL$1(args.exchangeBaseUrl);
    let proto;
    if (url.protocol === "https:") {
        proto = "taler";
    }
    else if (url.protocol === "http:") {
        proto = "taler+http";
    }
    else {
        throw Error(`Unsupported exchange URL protocol ${args.exchangeBaseUrl}`);
    }
    if (!url.pathname.endsWith("/")) {
        throw Error(`exchange base URL must end with a slash (got ${args.exchangeBaseUrl}instead)`);
    }
    return `${proto}://pay-push/${url.host}${url.pathname}${args.contractPriv}`;
}
function constructPayPullUri(args) {
    const url = new URL$1(args.exchangeBaseUrl);
    let proto;
    if (url.protocol === "https:") {
        proto = "taler";
    }
    else if (url.protocol === "http:") {
        proto = "taler+http";
    }
    else {
        throw Error(`Unsupported exchange URL protocol ${args.exchangeBaseUrl}`);
    }
    if (!url.pathname.endsWith("/")) {
        throw Error(`exchange base URL must end with a slash (got ${args.exchangeBaseUrl}instead)`);
    }
    return `${proto}://pay-pull/${url.host}${url.pathname}${args.contractPriv}`;
}

/*
 This file is part of GNU Taler
 (C) 2019 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
var TransactionType;
(function (TransactionType) {
    TransactionType["Withdrawal"] = "withdrawal";
    TransactionType["Payment"] = "payment";
    TransactionType["Refund"] = "refund";
    TransactionType["Refresh"] = "refresh";
    TransactionType["Tip"] = "tip";
    TransactionType["Deposit"] = "deposit";
    TransactionType["PeerPushDebit"] = "peer-push-debit";
    TransactionType["PeerPushCredit"] = "peer-push-credit";
    TransactionType["PeerPullDebit"] = "peer-pull-debit";
    TransactionType["PeerPullCredit"] = "peer-pull-credit";
})(TransactionType || (TransactionType = {}));
var WithdrawalType;
(function (WithdrawalType) {
    WithdrawalType["TalerBankIntegrationApi"] = "taler-bank-integration-api";
    WithdrawalType["ManualTransfer"] = "manual-transfer";
})(WithdrawalType || (WithdrawalType = {}));
var PaymentStatus;
(function (PaymentStatus) {
    /**
     * Explicitly aborted after timeout / failure
     */
    PaymentStatus["Aborted"] = "aborted";
    /**
     * Payment failed, wallet will auto-retry.
     * User should be given the option to retry now / abort.
     */
    PaymentStatus["Failed"] = "failed";
    /**
     * Paid successfully
     */
    PaymentStatus["Paid"] = "paid";
    /**
     * User accepted, payment is processing.
     */
    PaymentStatus["Accepted"] = "accepted";
})(PaymentStatus || (PaymentStatus = {}));
const codecForTransactionByIdRequest = () => buildCodecForObject()
    .property("transactionId", codecForString())
    .build("TransactionByIdRequest");
const codecForTransactionsRequest = () => buildCodecForObject()
    .property("currency", codecOptional(codecForString()))
    .property("search", codecOptional(codecForString()))
    .build("TransactionsRequest");

/*
 This file is part of GNU Taler
 (C) 2015-2020 Taler Systems SA

 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 ConfirmPayResultType;
(function (ConfirmPayResultType) {
    ConfirmPayResultType["Done"] = "done";
    ConfirmPayResultType["Pending"] = "pending";
})(ConfirmPayResultType || (ConfirmPayResultType = {}));
var PreparePayResultType;
(function (PreparePayResultType) {
    PreparePayResultType["PaymentPossible"] = "payment-possible";
    PreparePayResultType["InsufficientBalance"] = "insufficient-balance";
    PreparePayResultType["AlreadyConfirmed"] = "already-confirmed";
})(PreparePayResultType || (PreparePayResultType = {}));
/**
 * Reasons for why a coin is being refreshed.
 */
var RefreshReason;
(function (RefreshReason) {
    RefreshReason["Manual"] = "manual";
    RefreshReason["PayMerchant"] = "pay-merchant";
    RefreshReason["PayDeposit"] = "pay-deposit";
    RefreshReason["PayPeerPush"] = "pay-peer-push";
    RefreshReason["PayPeerPull"] = "pay-peer-pull";
    RefreshReason["Refund"] = "refund";
    RefreshReason["AbortPay"] = "abort-pay";
    RefreshReason["Recoup"] = "recoup";
    RefreshReason["BackupRestored"] = "backup-restored";
    RefreshReason["Scheduled"] = "scheduled";
})(RefreshReason || (RefreshReason = {}));
const codecForTestPayArgs = () => buildCodecForObject()
    .property("merchantBaseUrl", codecForString())
    .property("merchantAuthToken", codecOptional(codecForString()))
    .property("amount", codecForString())
    .property("summary", codecForString())
    .property("forcedCoinSel", codecForAny())
    .build("TestPayArgs");
const codecForIntegrationTestArgs = () => buildCodecForObject()
    .property("exchangeBaseUrl", codecForString())
    .property("bankBaseUrl", codecForString())
    .property("merchantBaseUrl", codecForString())
    .property("merchantAuthToken", codecOptional(codecForString()))
    .property("amountToSpend", codecForAmountString())
    .property("amountToWithdraw", codecForAmountString())
    .property("bankAccessApiBaseUrl", codecOptional(codecForAmountString()))
    .build("IntegrationTestArgs");
const codecForAddExchangeRequest = () => buildCodecForObject()
    .property("exchangeBaseUrl", codecForString())
    .property("forceUpdate", codecOptional(codecForBoolean()))
    .build("AddExchangeRequest");
const codecForGetExchangeTosRequest = () => buildCodecForObject()
    .property("exchangeBaseUrl", codecForString())
    .property("acceptedFormat", codecOptional(codecForList(codecForString())))
    .build("GetExchangeTosRequest");
const codecForAcceptManualWithdrawalRequet = () => buildCodecForObject()
    .property("exchangeBaseUrl", codecForString())
    .property("amount", codecForString())
    .property("restrictAge", codecOptional(codecForNumber()))
    .build("AcceptManualWithdrawalRequest");
const codecForAcceptBankIntegratedWithdrawalRequest = () => buildCodecForObject()
    .property("exchangeBaseUrl", codecForString())
    .property("talerWithdrawUri", codecForString())
    .property("forcedDenomSel", codecForAny())
    .property("restrictAge", codecOptional(codecForNumber()))
    .build("AcceptBankIntegratedWithdrawalRequest");
const codecForGetWithdrawalDetailsForAmountRequest = () => buildCodecForObject()
    .property("exchangeBaseUrl", codecForString())
    .property("amount", codecForString())
    .property("restrictAge", codecOptional(codecForNumber()))
    .build("GetWithdrawalDetailsForAmountRequest");
const codecForAcceptExchangeTosRequest = () => buildCodecForObject()
    .property("exchangeBaseUrl", codecForString())
    .property("etag", codecOptional(codecForString()))
    .build("AcceptExchangeTosRequest");
const codecForApplyRefundRequest = () => buildCodecForObject()
    .property("talerRefundUri", codecForString())
    .build("ApplyRefundRequest");
const codecForApplyRefundFromPurchaseIdRequest = () => buildCodecForObject()
    .property("purchaseId", codecForString())
    .build("ApplyRefundFromPurchaseIdRequest");
const codecForGetWithdrawalDetailsForUri = () => buildCodecForObject()
    .property("talerWithdrawUri", codecForString())
    .property("restrictAge", codecOptional(codecForNumber()))
    .build("GetWithdrawalDetailsForUriRequest");
const codecForListKnownBankAccounts = () => buildCodecForObject()
    .property("currency", codecOptional(codecForString()))
    .build("ListKnownBankAccountsRequest");
const codecForAddKnownBankAccounts = () => buildCodecForObject()
    .property("payto", (codecForString()))
    .property("alias", (codecForString()))
    .property("currency", (codecForString()))
    .build("AddKnownBankAccountsRequest");
const codecForForgetKnownBankAccounts = () => buildCodecForObject()
    .property("payto", (codecForString()))
    .build("ForgetKnownBankAccountsRequest");
const codecForGetExchangeWithdrawalInfo = () => buildCodecForObject()
    .property("exchangeBaseUrl", codecForString())
    .property("amount", codecForAmountJson())
    .property("tosAcceptedFormat", codecOptional(codecForList(codecForString())))
    .build("GetExchangeWithdrawalInfo");
const codecForGetContractTermsDetails = () => buildCodecForObject()
    .property("proposalId", codecForString())
    .build("GetContractTermsDetails");
const codecForPreparePayRequest = () => buildCodecForObject()
    .property("talerPayUri", codecForString())
    .build("PreparePay");
const codecForConfirmPayRequest = () => buildCodecForObject()
    .property("proposalId", codecForString())
    .property("sessionId", codecOptional(codecForString()))
    .property("forcedCoinSel", codecForAny())
    .build("ConfirmPay");
/**
 * Strategy for loading recovery information.
 */
var RecoveryMergeStrategy;
(function (RecoveryMergeStrategy) {
    /**
     * Keep the local wallet root key, import and take over providers.
     */
    RecoveryMergeStrategy["Ours"] = "ours";
    /**
     * Migrate to the wallet root key from the recovery information.
     */
    RecoveryMergeStrategy["Theirs"] = "theirs";
})(RecoveryMergeStrategy || (RecoveryMergeStrategy = {}));
const codecForWithdrawTestBalance = () => buildCodecForObject()
    .property("amount", codecForString())
    .property("bankBaseUrl", codecForString())
    .property("exchangeBaseUrl", codecForString())
    .property("forcedDenomSel", codecForAny())
    .property("bankAccessApiBaseUrl", codecOptional(codecForString()))
    .build("WithdrawTestBalanceRequest");
const codecForSetCoinSuspendedRequest = () => buildCodecForObject()
    .property("coinPub", codecForString())
    .property("suspended", codecForBoolean())
    .build("SetCoinSuspendedRequest");
const codecForForceRefreshRequest = () => buildCodecForObject()
    .property("coinPubList", codecForList(codecForString()))
    .build("ForceRefreshRequest");
const codecForPrepareRefundRequest = () => buildCodecForObject()
    .property("talerRefundUri", codecForString())
    .build("PrepareRefundRequest");
const codecForPrepareTipRequest = () => buildCodecForObject()
    .property("talerTipUri", codecForString())
    .build("PrepareTipRequest");
const codecForAcceptTipRequest = () => buildCodecForObject()
    .property("walletTipId", codecForString())
    .build("AcceptTipRequest");
const codecForAbortPayWithRefundRequest = () => buildCodecForObject()
    .property("proposalId", codecForString())
    .build("AbortPayWithRefundRequest");
const codecForGetFeeForDeposit = () => buildCodecForObject()
    .property("amount", codecForAmountString())
    .property("depositPaytoUri", codecForString())
    .build("GetFeeForDepositRequest");
const codecForPrepareDepositRequest = () => buildCodecForObject()
    .property("amount", codecForAmountString())
    .property("depositPaytoUri", codecForString())
    .build("PrepareDepositRequest");
const codecForCreateDepositGroupRequest = () => buildCodecForObject()
    .property("amount", codecForAmountString())
    .property("depositPaytoUri", codecForString())
    .build("CreateDepositGroupRequest");
const codecForTrackDepositGroupRequest = () => buildCodecForObject()
    .property("depositGroupId", codecForAmountString())
    .build("TrackDepositGroupRequest");
const codecForDeleteTransactionRequest = () => buildCodecForObject()
    .property("transactionId", codecForString())
    .build("DeleteTransactionRequest");
const codecForRetryTransactionRequest = () => buildCodecForObject()
    .property("transactionId", codecForString())
    .build("RetryTransactionRequest");
const codecForSetWalletDeviceIdRequest = () => buildCodecForObject()
    .property("walletDeviceId", codecForString())
    .build("SetWalletDeviceIdRequest");
const codecForWithdrawFakebankRequest = () => buildCodecForObject()
    .property("amount", codecForAmountString())
    .property("bank", codecForString())
    .property("exchange", codecForString())
    .build("WithdrawFakebankRequest");
const codecForImportDbRequest = () => buildCodecForObject()
    .property("dump", codecForAny())
    .build("ImportDbRequest");
const codecForInitiatePeerPushPaymentRequest = () => buildCodecForObject()
    .property("amount", codecForAmountString())
    .property("partialContractTerms", codecForAny())
    .build("InitiatePeerPushPaymentRequest");
const codecForCheckPeerPushPaymentRequest = () => buildCodecForObject()
    .property("talerUri", codecForString())
    .build("CheckPeerPushPaymentRequest");
const codecForCheckPeerPullPaymentRequest = () => buildCodecForObject()
    .property("talerUri", codecForString())
    .build("CheckPeerPullPaymentRequest");
const codecForAcceptPeerPushPaymentRequest = () => buildCodecForObject()
    .property("peerPushPaymentIncomingId", codecForString())
    .build("AcceptPeerPushPaymentRequest");
const codecForAcceptPeerPullPaymentRequest = () => buildCodecForObject()
    .property("peerPullPaymentIncomingId", codecForString())
    .build("AcceptPeerPllPaymentRequest");
const codecForInitiatePeerPullPaymentRequest = () => buildCodecForObject()
    .property("partialContractTerms", codecForAny())
    .property("amount", codecForAmountString())
    .property("exchangeBaseUrl", codecForAmountString())
    .build("InitiatePeerPullPaymentRequest");

var jed = {exports: {}};

/**
 * @preserve jed.js https://github.com/SlexAxton/Jed
 */

(function (module, exports) {
	/*
	-----------
	A gettext compatible i18n library for modern JavaScript Applications

	by Alex Sexton - AlexSexton [at] gmail - @SlexAxton

	MIT License

	A jQuery Foundation project - requires CLA to contribute -
	https://contribute.jquery.org/CLA/



	Jed offers the entire applicable GNU gettext spec'd set of
	functions, but also offers some nicer wrappers around them.
	The api for gettext was written for a language with no function
	overloading, so Jed allows a little more of that.

	Many thanks to Joshua I. Miller - unrtst@cpan.org - who wrote
	gettext.js back in 2008. I was able to vet a lot of my ideas
	against his. I also made sure Jed passed against his tests
	in order to offer easy upgrades -- jsgettext.berlios.de
	*/
	(function (root, undef) {

	  // Set up some underscore-style functions, if you already have
	  // underscore, feel free to delete this section, and use it
	  // directly, however, the amount of functions used doesn't
	  // warrant having underscore as a full dependency.
	  // Underscore 1.3.0 was used to port and is licensed
	  // under the MIT License by Jeremy Ashkenas.
	  var ArrayProto    = Array.prototype,
	      ObjProto      = Object.prototype,
	      slice         = ArrayProto.slice,
	      hasOwnProp    = ObjProto.hasOwnProperty,
	      nativeForEach = ArrayProto.forEach,
	      breaker       = {};

	  // We're not using the OOP style _ so we don't need the
	  // extra level of indirection. This still means that you
	  // sub out for real `_` though.
	  var _ = {
	    forEach : function( obj, iterator, context ) {
	      var i, l, key;
	      if ( obj === null ) {
	        return;
	      }

	      if ( nativeForEach && obj.forEach === nativeForEach ) {
	        obj.forEach( iterator, context );
	      }
	      else if ( obj.length === +obj.length ) {
	        for ( i = 0, l = obj.length; i < l; i++ ) {
	          if ( i in obj && iterator.call( context, obj[i], i, obj ) === breaker ) {
	            return;
	          }
	        }
	      }
	      else {
	        for ( key in obj) {
	          if ( hasOwnProp.call( obj, key ) ) {
	            if ( iterator.call (context, obj[key], key, obj ) === breaker ) {
	              return;
	            }
	          }
	        }
	      }
	    },
	    extend : function( obj ) {
	      this.forEach( slice.call( arguments, 1 ), function ( source ) {
	        for ( var prop in source ) {
	          obj[prop] = source[prop];
	        }
	      });
	      return obj;
	    }
	  };
	  // END Miniature underscore impl

	  // Jed is a constructor function
	  var Jed = function ( options ) {
	    // Some minimal defaults
	    this.defaults = {
	      "locale_data" : {
	        "messages" : {
	          "" : {
	            "domain"       : "messages",
	            "lang"         : "en",
	            "plural_forms" : "nplurals=2; plural=(n != 1);"
	          }
	          // There are no default keys, though
	        }
	      },
	      // The default domain if one is missing
	      "domain" : "messages",
	      // enable debug mode to log untranslated strings to the console
	      "debug" : false
	    };

	    // Mix in the sent options with the default options
	    this.options = _.extend( {}, this.defaults, options );
	    this.textdomain( this.options.domain );

	    if ( options.domain && ! this.options.locale_data[ this.options.domain ] ) {
	      throw new Error('Text domain set to non-existent domain: `' + options.domain + '`');
	    }
	  };

	  // The gettext spec sets this character as the default
	  // delimiter for context lookups.
	  // e.g.: context\u0004key
	  // If your translation company uses something different,
	  // just change this at any time and it will use that instead.
	  Jed.context_delimiter = String.fromCharCode( 4 );

	  function getPluralFormFunc ( plural_form_string ) {
	    return Jed.PF.compile( plural_form_string || "nplurals=2; plural=(n != 1);");
	  }

	  function Chain( key, i18n ){
	    this._key = key;
	    this._i18n = i18n;
	  }

	  // Create a chainable api for adding args prettily
	  _.extend( Chain.prototype, {
	    onDomain : function ( domain ) {
	      this._domain = domain;
	      return this;
	    },
	    withContext : function ( context ) {
	      this._context = context;
	      return this;
	    },
	    ifPlural : function ( num, pkey ) {
	      this._val = num;
	      this._pkey = pkey;
	      return this;
	    },
	    fetch : function ( sArr ) {
	      if ( {}.toString.call( sArr ) != '[object Array]' ) {
	        sArr = [].slice.call(arguments, 0);
	      }
	      return ( sArr && sArr.length ? Jed.sprintf : function(x){ return x; } )(
	        this._i18n.dcnpgettext(this._domain, this._context, this._key, this._pkey, this._val),
	        sArr
	      );
	    }
	  });

	  // Add functions to the Jed prototype.
	  // These will be the functions on the object that's returned
	  // from creating a `new Jed()`
	  // These seem redundant, but they gzip pretty well.
	  _.extend( Jed.prototype, {
	    // The sexier api start point
	    translate : function ( key ) {
	      return new Chain( key, this );
	    },

	    textdomain : function ( domain ) {
	      if ( ! domain ) {
	        return this._textdomain;
	      }
	      this._textdomain = domain;
	    },

	    gettext : function ( key ) {
	      return this.dcnpgettext.call( this, undef, undef, key );
	    },

	    dgettext : function ( domain, key ) {
	     return this.dcnpgettext.call( this, domain, undef, key );
	    },

	    dcgettext : function ( domain , key /*, category */ ) {
	      // Ignores the category anyways
	      return this.dcnpgettext.call( this, domain, undef, key );
	    },

	    ngettext : function ( skey, pkey, val ) {
	      return this.dcnpgettext.call( this, undef, undef, skey, pkey, val );
	    },

	    dngettext : function ( domain, skey, pkey, val ) {
	      return this.dcnpgettext.call( this, domain, undef, skey, pkey, val );
	    },

	    dcngettext : function ( domain, skey, pkey, val/*, category */) {
	      return this.dcnpgettext.call( this, domain, undef, skey, pkey, val );
	    },

	    pgettext : function ( context, key ) {
	      return this.dcnpgettext.call( this, undef, context, key );
	    },

	    dpgettext : function ( domain, context, key ) {
	      return this.dcnpgettext.call( this, domain, context, key );
	    },

	    dcpgettext : function ( domain, context, key/*, category */) {
	      return this.dcnpgettext.call( this, domain, context, key );
	    },

	    npgettext : function ( context, skey, pkey, val ) {
	      return this.dcnpgettext.call( this, undef, context, skey, pkey, val );
	    },

	    dnpgettext : function ( domain, context, skey, pkey, val ) {
	      return this.dcnpgettext.call( this, domain, context, skey, pkey, val );
	    },

	    // The most fully qualified gettext function. It has every option.
	    // Since it has every option, we can use it from every other method.
	    // This is the bread and butter.
	    // Technically there should be one more argument in this function for 'Category',
	    // but since we never use it, we might as well not waste the bytes to define it.
	    dcnpgettext : function ( domain, context, singular_key, plural_key, val ) {
	      // Set some defaults

	      plural_key = plural_key || singular_key;

	      // Use the global domain default if one
	      // isn't explicitly passed in
	      domain = domain || this._textdomain;

	      var fallback;

	      // Handle special cases

	      // No options found
	      if ( ! this.options ) {
	        // There's likely something wrong, but we'll return the correct key for english
	        // We do this by instantiating a brand new Jed instance with the default set
	        // for everything that could be broken.
	        fallback = new Jed();
	        return fallback.dcnpgettext.call( fallback, undefined, undefined, singular_key, plural_key, val );
	      }

	      // No translation data provided
	      if ( ! this.options.locale_data ) {
	        throw new Error('No locale data provided.');
	      }

	      if ( ! this.options.locale_data[ domain ] ) {
	        throw new Error('Domain `' + domain + '` was not found.');
	      }

	      if ( ! this.options.locale_data[ domain ][ "" ] ) {
	        throw new Error('No locale meta information provided.');
	      }

	      // Make sure we have a truthy key. Otherwise we might start looking
	      // into the empty string key, which is the options for the locale
	      // data.
	      if ( ! singular_key ) {
	        throw new Error('No translation key found.');
	      }

	      var key  = context ? context + Jed.context_delimiter + singular_key : singular_key,
	          locale_data = this.options.locale_data,
	          dict = locale_data[ domain ],
	          defaultConf = (locale_data.messages || this.defaults.locale_data.messages)[""],
	          pluralForms = dict[""].plural_forms || dict[""]["Plural-Forms"] || dict[""]["plural-forms"] || defaultConf.plural_forms || defaultConf["Plural-Forms"] || defaultConf["plural-forms"],
	          val_list,
	          res;

	      var val_idx;
	      if (val === undefined) {
	        // No value passed in; assume singular key lookup.
	        val_idx = 0;

	      } else {
	        // Value has been passed in; use plural-forms calculations.

	        // Handle invalid numbers, but try casting strings for good measure
	        if ( typeof val != 'number' ) {
	          val = parseInt( val, 10 );

	          if ( isNaN( val ) ) {
	            throw new Error('The number that was passed in is not a number.');
	          }
	        }

	        val_idx = getPluralFormFunc(pluralForms)(val);
	      }

	      // Throw an error if a domain isn't found
	      if ( ! dict ) {
	        throw new Error('No domain named `' + domain + '` could be found.');
	      }

	      val_list = dict[ key ];

	      // If there is no match, then revert back to
	      // english style singular/plural with the keys passed in.
	      if ( ! val_list || val_idx > val_list.length ) {
	        if (this.options.missing_key_callback) {
	          this.options.missing_key_callback(key, domain);
	        }
	        res = [ singular_key, plural_key ];

	        // collect untranslated strings
	        if (this.options.debug===true) {
	          console.log(res[ getPluralFormFunc(pluralForms)( val ) ]);
	        }
	        return res[ getPluralFormFunc()( val ) ];
	      }

	      res = val_list[ val_idx ];

	      // This includes empty strings on purpose
	      if ( ! res  ) {
	        res = [ singular_key, plural_key ];
	        return res[ getPluralFormFunc()( val ) ];
	      }
	      return res;
	    }
	  });


	  // We add in sprintf capabilities for post translation value interolation
	  // This is not internally used, so you can remove it if you have this
	  // available somewhere else, or want to use a different system.

	  // We _slightly_ modify the normal sprintf behavior to more gracefully handle
	  // undefined values.

	  /**
	   sprintf() for JavaScript 0.7-beta1
	   http://www.diveintojavascript.com/projects/javascript-sprintf

	   Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
	   All rights reserved.

	   Redistribution and use in source and binary forms, with or without
	   modification, are permitted provided that the following conditions are met:
	       * Redistributions of source code must retain the above copyright
	         notice, this list of conditions and the following disclaimer.
	       * Redistributions in binary form must reproduce the above copyright
	         notice, this list of conditions and the following disclaimer in the
	         documentation and/or other materials provided with the distribution.
	       * Neither the name of sprintf() for JavaScript nor the
	         names of its contributors may be used to endorse or promote products
	         derived from this software without specific prior written permission.

	   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
	   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
	   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
	   DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
	   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
	   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
	   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
	   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
	   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
	   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
	  */
	  var sprintf = (function() {
	    function get_type(variable) {
	      return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
	    }
	    function str_repeat(input, multiplier) {
	      for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
	      return output.join('');
	    }

	    var str_format = function() {
	      if (!str_format.cache.hasOwnProperty(arguments[0])) {
	        str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
	      }
	      return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
	    };

	    str_format.format = function(parse_tree, argv) {
	      var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
	      for (i = 0; i < tree_length; i++) {
	        node_type = get_type(parse_tree[i]);
	        if (node_type === 'string') {
	          output.push(parse_tree[i]);
	        }
	        else if (node_type === 'array') {
	          match = parse_tree[i]; // convenience purposes only
	          if (match[2]) { // keyword argument
	            arg = argv[cursor];
	            for (k = 0; k < match[2].length; k++) {
	              if (!arg.hasOwnProperty(match[2][k])) {
	                throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
	              }
	              arg = arg[match[2][k]];
	            }
	          }
	          else if (match[1]) { // positional argument (explicit)
	            arg = argv[match[1]];
	          }
	          else { // positional argument (implicit)
	            arg = argv[cursor++];
	          }

	          if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
	            throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
	          }

	          // Jed EDIT
	          if ( typeof arg == 'undefined' || arg === null ) {
	            arg = '';
	          }
	          // Jed EDIT

	          switch (match[8]) {
	            case 'b': arg = arg.toString(2); break;
	            case 'c': arg = String.fromCharCode(arg); break;
	            case 'd': arg = parseInt(arg, 10); break;
	            case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
	            case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
	            case 'o': arg = arg.toString(8); break;
	            case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
	            case 'u': arg = Math.abs(arg); break;
	            case 'x': arg = arg.toString(16); break;
	            case 'X': arg = arg.toString(16).toUpperCase(); break;
	          }
	          arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
	          pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
	          pad_length = match[6] - String(arg).length;
	          pad = match[6] ? str_repeat(pad_character, pad_length) : '';
	          output.push(match[5] ? arg + pad : pad + arg);
	        }
	      }
	      return output.join('');
	    };

	    str_format.cache = {};

	    str_format.parse = function(fmt) {
	      var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
	      while (_fmt) {
	        if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
	          parse_tree.push(match[0]);
	        }
	        else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
	          parse_tree.push('%');
	        }
	        else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
	          if (match[2]) {
	            arg_names |= 1;
	            var field_list = [], replacement_field = match[2], field_match = [];
	            if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
	              field_list.push(field_match[1]);
	              while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
	                if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
	                  field_list.push(field_match[1]);
	                }
	                else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
	                  field_list.push(field_match[1]);
	                }
	                else {
	                  throw('[sprintf] huh?');
	                }
	              }
	            }
	            else {
	              throw('[sprintf] huh?');
	            }
	            match[2] = field_list;
	          }
	          else {
	            arg_names |= 2;
	          }
	          if (arg_names === 3) {
	            throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
	          }
	          parse_tree.push(match);
	        }
	        else {
	          throw('[sprintf] huh?');
	        }
	        _fmt = _fmt.substring(match[0].length);
	      }
	      return parse_tree;
	    };

	    return str_format;
	  })();

	  var vsprintf = function(fmt, argv) {
	    argv.unshift(fmt);
	    return sprintf.apply(null, argv);
	  };

	  Jed.parse_plural = function ( plural_forms, n ) {
	    plural_forms = plural_forms.replace(/n/g, n);
	    return Jed.parse_expression(plural_forms);
	  };

	  Jed.sprintf = function ( fmt, args ) {
	    if ( {}.toString.call( args ) == '[object Array]' ) {
	      return vsprintf( fmt, [].slice.call(args) );
	    }
	    return sprintf.apply(this, [].slice.call(arguments) );
	  };

	  Jed.prototype.sprintf = function () {
	    return Jed.sprintf.apply(this, arguments);
	  };
	  // END sprintf Implementation

	  // Start the Plural forms section
	  // This is a full plural form expression parser. It is used to avoid
	  // running 'eval' or 'new Function' directly against the plural
	  // forms.
	  //
	  // This can be important if you get translations done through a 3rd
	  // party vendor. I encourage you to use this instead, however, I
	  // also will provide a 'precompiler' that you can use at build time
	  // to output valid/safe function representations of the plural form
	  // expressions. This means you can build this code out for the most
	  // part.
	  Jed.PF = {};

	  Jed.PF.parse = function ( p ) {
	    var plural_str = Jed.PF.extractPluralExpr( p );
	    return Jed.PF.parser.parse.call(Jed.PF.parser, plural_str);
	  };

	  Jed.PF.compile = function ( p ) {
	    // Handle trues and falses as 0 and 1
	    function imply( val ) {
	      return (val === true ? 1 : val ? val : 0);
	    }

	    var ast = Jed.PF.parse( p );
	    return function ( n ) {
	      return imply( Jed.PF.interpreter( ast )( n ) );
	    };
	  };

	  Jed.PF.interpreter = function ( ast ) {
	    return function ( n ) {
	      switch ( ast.type ) {
	        case 'GROUP':
	          return Jed.PF.interpreter( ast.expr )( n );
	        case 'TERNARY':
	          if ( Jed.PF.interpreter( ast.expr )( n ) ) {
	            return Jed.PF.interpreter( ast.truthy )( n );
	          }
	          return Jed.PF.interpreter( ast.falsey )( n );
	        case 'OR':
	          return Jed.PF.interpreter( ast.left )( n ) || Jed.PF.interpreter( ast.right )( n );
	        case 'AND':
	          return Jed.PF.interpreter( ast.left )( n ) && Jed.PF.interpreter( ast.right )( n );
	        case 'LT':
	          return Jed.PF.interpreter( ast.left )( n ) < Jed.PF.interpreter( ast.right )( n );
	        case 'GT':
	          return Jed.PF.interpreter( ast.left )( n ) > Jed.PF.interpreter( ast.right )( n );
	        case 'LTE':
	          return Jed.PF.interpreter( ast.left )( n ) <= Jed.PF.interpreter( ast.right )( n );
	        case 'GTE':
	          return Jed.PF.interpreter( ast.left )( n ) >= Jed.PF.interpreter( ast.right )( n );
	        case 'EQ':
	          return Jed.PF.interpreter( ast.left )( n ) == Jed.PF.interpreter( ast.right )( n );
	        case 'NEQ':
	          return Jed.PF.interpreter( ast.left )( n ) != Jed.PF.interpreter( ast.right )( n );
	        case 'MOD':
	          return Jed.PF.interpreter( ast.left )( n ) % Jed.PF.interpreter( ast.right )( n );
	        case 'VAR':
	          return n;
	        case 'NUM':
	          return ast.val;
	        default:
	          throw new Error("Invalid Token found.");
	      }
	    };
	  };

	  Jed.PF.extractPluralExpr = function ( p ) {
	    // trim first
	    p = p.replace(/^\s\s*/, '').replace(/\s\s*$/, '');

	    if (! /;\s*$/.test(p)) {
	      p = p.concat(';');
	    }

	    var nplurals_re = /nplurals\=(\d+);/,
	        plural_re = /plural\=(.*);/,
	        nplurals_matches = p.match( nplurals_re ),
	        plural_matches;

	    // Find the nplurals number
	    if ( nplurals_matches.length > 1 ) {
	      nplurals_matches[1];
	    }
	    else {
	      throw new Error('nplurals not found in plural_forms string: ' + p );
	    }

	    // remove that data to get to the formula
	    p = p.replace( nplurals_re, "" );
	    plural_matches = p.match( plural_re );

	    if (!( plural_matches && plural_matches.length > 1 ) ) {
	      throw new Error('`plural` expression not found: ' + p);
	    }
	    return plural_matches[ 1 ];
	  };

	  /* Jison generated parser */
	  Jed.PF.parser = (function(){

	var parser = {trace: function trace() { },
	yy: {},
	symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"?":6,":":7,"||":8,"&&":9,"<":10,"<=":11,">":12,">=":13,"!=":14,"==":15,"%":16,"(":17,")":18,"n":19,"NUMBER":20,"$accept":0,"$end":1},
	terminals_: {2:"error",5:"EOF",6:"?",7:":",8:"||",9:"&&",10:"<",11:"<=",12:">",13:">=",14:"!=",15:"==",16:"%",17:"(",18:")",19:"n",20:"NUMBER"},
	productions_: [0,[3,2],[4,5],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,1],[4,1]],
	performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {

	var $0 = $$.length - 1;
	switch (yystate) {
	case 1: return { type : 'GROUP', expr: $$[$0-1] };
	case 2:this.$ = { type: 'TERNARY', expr: $$[$0-4], truthy : $$[$0-2], falsey: $$[$0] };
	break;
	case 3:this.$ = { type: "OR", left: $$[$0-2], right: $$[$0] };
	break;
	case 4:this.$ = { type: "AND", left: $$[$0-2], right: $$[$0] };
	break;
	case 5:this.$ = { type: 'LT', left: $$[$0-2], right: $$[$0] };
	break;
	case 6:this.$ = { type: 'LTE', left: $$[$0-2], right: $$[$0] };
	break;
	case 7:this.$ = { type: 'GT', left: $$[$0-2], right: $$[$0] };
	break;
	case 8:this.$ = { type: 'GTE', left: $$[$0-2], right: $$[$0] };
	break;
	case 9:this.$ = { type: 'NEQ', left: $$[$0-2], right: $$[$0] };
	break;
	case 10:this.$ = { type: 'EQ', left: $$[$0-2], right: $$[$0] };
	break;
	case 11:this.$ = { type: 'MOD', left: $$[$0-2], right: $$[$0] };
	break;
	case 12:this.$ = { type: 'GROUP', expr: $$[$0-1] };
	break;
	case 13:this.$ = { type: 'VAR' };
	break;
	case 14:this.$ = { type: 'NUM', val: Number(yytext) };
	break;
	}
	},
	table: [{3:1,4:2,17:[1,3],19:[1,4],20:[1,5]},{1:[3]},{5:[1,6],6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{4:17,17:[1,3],19:[1,4],20:[1,5]},{5:[2,13],6:[2,13],7:[2,13],8:[2,13],9:[2,13],10:[2,13],11:[2,13],12:[2,13],13:[2,13],14:[2,13],15:[2,13],16:[2,13],18:[2,13]},{5:[2,14],6:[2,14],7:[2,14],8:[2,14],9:[2,14],10:[2,14],11:[2,14],12:[2,14],13:[2,14],14:[2,14],15:[2,14],16:[2,14],18:[2,14]},{1:[2,1]},{4:18,17:[1,3],19:[1,4],20:[1,5]},{4:19,17:[1,3],19:[1,4],20:[1,5]},{4:20,17:[1,3],19:[1,4],20:[1,5]},{4:21,17:[1,3],19:[1,4],20:[1,5]},{4:22,17:[1,3],19:[1,4],20:[1,5]},{4:23,17:[1,3],19:[1,4],20:[1,5]},{4:24,17:[1,3],19:[1,4],20:[1,5]},{4:25,17:[1,3],19:[1,4],20:[1,5]},{4:26,17:[1,3],19:[1,4],20:[1,5]},{4:27,17:[1,3],19:[1,4],20:[1,5]},{6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[1,28]},{6:[1,7],7:[1,29],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{5:[2,3],6:[2,3],7:[2,3],8:[2,3],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,3]},{5:[2,4],6:[2,4],7:[2,4],8:[2,4],9:[2,4],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,4]},{5:[2,5],6:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[2,5],12:[2,5],13:[2,5],14:[2,5],15:[2,5],16:[1,16],18:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[2,6],12:[2,6],13:[2,6],14:[2,6],15:[2,6],16:[1,16],18:[2,6]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[2,7],12:[2,7],13:[2,7],14:[2,7],15:[2,7],16:[1,16],18:[2,7]},{5:[2,8],6:[2,8],7:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[2,8],12:[2,8],13:[2,8],14:[2,8],15:[2,8],16:[1,16],18:[2,8]},{5:[2,9],6:[2,9],7:[2,9],8:[2,9],9:[2,9],10:[2,9],11:[2,9],12:[2,9],13:[2,9],14:[2,9],15:[2,9],16:[1,16],18:[2,9]},{5:[2,10],6:[2,10],7:[2,10],8:[2,10],9:[2,10],10:[2,10],11:[2,10],12:[2,10],13:[2,10],14:[2,10],15:[2,10],16:[1,16],18:[2,10]},{5:[2,11],6:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[2,11],12:[2,11],13:[2,11],14:[2,11],15:[2,11],16:[2,11],18:[2,11]},{5:[2,12],6:[2,12],7:[2,12],8:[2,12],9:[2,12],10:[2,12],11:[2,12],12:[2,12],13:[2,12],14:[2,12],15:[2,12],16:[2,12],18:[2,12]},{4:30,17:[1,3],19:[1,4],20:[1,5]},{5:[2,2],6:[1,7],7:[2,2],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,2]}],
	defaultActions: {6:[2,1]},
	parseError: function parseError(str, hash) {
	    throw new Error(str);
	},
	parse: function parse(input) {
	    var self = this,
	        stack = [0],
	        vstack = [null], // semantic value stack
	        lstack = [], // location stack
	        table = this.table,
	        yytext = '',
	        yylineno = 0,
	        yyleng = 0,
	        recovering = 0,
	        TERROR = 2,
	        EOF = 1;

	    //this.reductionCount = this.shiftCount = 0;

	    this.lexer.setInput(input);
	    this.lexer.yy = this.yy;
	    this.yy.lexer = this.lexer;
	    if (typeof this.lexer.yylloc == 'undefined')
	        this.lexer.yylloc = {};
	    var yyloc = this.lexer.yylloc;
	    lstack.push(yyloc);

	    if (typeof this.yy.parseError === 'function')
	        this.parseError = this.yy.parseError;

	    function popStack (n) {
	        stack.length = stack.length - 2*n;
	        vstack.length = vstack.length - n;
	        lstack.length = lstack.length - n;
	    }

	    function lex() {
	        var token;
	        token = self.lexer.lex() || 1; // $end = 1
	        // if token isn't its numeric value, convert
	        if (typeof token !== 'number') {
	            token = self.symbols_[token] || token;
	        }
	        return token;
	    }

	    var symbol, preErrorSymbol, state, action, r, yyval={},p,len,newState, expected;
	    while (true) {
	        // retreive state number from top of stack
	        state = stack[stack.length-1];

	        // use default actions if available
	        if (this.defaultActions[state]) {
	            action = this.defaultActions[state];
	        } else {
	            if (symbol == null)
	                symbol = lex();
	            // read action for current state and first input
	            action = table[state] && table[state][symbol];
	        }

	        // handle parse error
	        if (typeof action === 'undefined' || !action.length || !action[0]) {

	            if (!recovering) {
	                // Report error
	                expected = [];
	                for (p in table[state]) if (this.terminals_[p] && p > 2) {
	                    expected.push("'"+this.terminals_[p]+"'");
	                }
	                var errStr = '';
	                if (this.lexer.showPosition) {
	                    errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'";
	                } else {
	                    errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
	                                  (symbol == 1 /*EOF*/ ? "end of input" :
	                                              ("'"+(this.terminals_[symbol] || symbol)+"'"));
	                }
	                this.parseError(errStr,
	                    {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
	            }

	            // just recovered from another error
	            if (recovering == 3) {
	                if (symbol == EOF) {
	                    throw new Error(errStr || 'Parsing halted.');
	                }

	                // discard current lookahead and grab another
	                yyleng = this.lexer.yyleng;
	                yytext = this.lexer.yytext;
	                yylineno = this.lexer.yylineno;
	                yyloc = this.lexer.yylloc;
	                symbol = lex();
	            }

	            // try to recover from error
	            while (1) {
	                // check for error recovery rule in this state
	                if ((TERROR.toString()) in table[state]) {
	                    break;
	                }
	                if (state == 0) {
	                    throw new Error(errStr || 'Parsing halted.');
	                }
	                popStack(1);
	                state = stack[stack.length-1];
	            }

	            preErrorSymbol = symbol; // save the lookahead token
	            symbol = TERROR;         // insert generic error symbol as new lookahead
	            state = stack[stack.length-1];
	            action = table[state] && table[state][TERROR];
	            recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
	        }

	        // this shouldn't happen, unless resolve defaults are off
	        if (action[0] instanceof Array && action.length > 1) {
	            throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
	        }

	        switch (action[0]) {

	            case 1: // shift
	                //this.shiftCount++;

	                stack.push(symbol);
	                vstack.push(this.lexer.yytext);
	                lstack.push(this.lexer.yylloc);
	                stack.push(action[1]); // push state
	                symbol = null;
	                if (!preErrorSymbol) { // normal execution/no error
	                    yyleng = this.lexer.yyleng;
	                    yytext = this.lexer.yytext;
	                    yylineno = this.lexer.yylineno;
	                    yyloc = this.lexer.yylloc;
	                    if (recovering > 0)
	                        recovering--;
	                } else { // error just occurred, resume old lookahead f/ before error
	                    symbol = preErrorSymbol;
	                    preErrorSymbol = null;
	                }
	                break;

	            case 2: // reduce
	                //this.reductionCount++;

	                len = this.productions_[action[1]][1];

	                // perform semantic action
	                yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
	                // default location, uses first token for firsts, last for lasts
	                yyval._$ = {
	                    first_line: lstack[lstack.length-(len||1)].first_line,
	                    last_line: lstack[lstack.length-1].last_line,
	                    first_column: lstack[lstack.length-(len||1)].first_column,
	                    last_column: lstack[lstack.length-1].last_column
	                };
	                r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);

	                if (typeof r !== 'undefined') {
	                    return r;
	                }

	                // pop off stack
	                if (len) {
	                    stack = stack.slice(0,-1*len*2);
	                    vstack = vstack.slice(0, -1*len);
	                    lstack = lstack.slice(0, -1*len);
	                }

	                stack.push(this.productions_[action[1]][0]);    // push nonterminal (reduce)
	                vstack.push(yyval.$);
	                lstack.push(yyval._$);
	                // goto new state = table[STATE][NONTERMINAL]
	                newState = table[stack[stack.length-2]][stack[stack.length-1]];
	                stack.push(newState);
	                break;

	            case 3: // accept
	                return true;
	        }

	    }

	    return true;
	}};/* Jison generated lexer */
	var lexer = (function(){

	var lexer = ({EOF:1,
	parseError:function parseError(str, hash) {
	        if (this.yy.parseError) {
	            this.yy.parseError(str, hash);
	        } else {
	            throw new Error(str);
	        }
	    },
	setInput:function (input) {
	        this._input = input;
	        this._more = this._less = this.done = false;
	        this.yylineno = this.yyleng = 0;
	        this.yytext = this.matched = this.match = '';
	        this.conditionStack = ['INITIAL'];
	        this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
	        return this;
	    },
	input:function () {
	        var ch = this._input[0];
	        this.yytext+=ch;
	        this.yyleng++;
	        this.match+=ch;
	        this.matched+=ch;
	        var lines = ch.match(/\n/);
	        if (lines) this.yylineno++;
	        this._input = this._input.slice(1);
	        return ch;
	    },
	unput:function (ch) {
	        this._input = ch + this._input;
	        return this;
	    },
	more:function () {
	        this._more = true;
	        return this;
	    },
	pastInput:function () {
	        var past = this.matched.substr(0, this.matched.length - this.match.length);
	        return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
	    },
	upcomingInput:function () {
	        var next = this.match;
	        if (next.length < 20) {
	            next += this._input.substr(0, 20-next.length);
	        }
	        return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
	    },
	showPosition:function () {
	        var pre = this.pastInput();
	        var c = new Array(pre.length + 1).join("-");
	        return pre + this.upcomingInput() + "\n" + c+"^";
	    },
	next:function () {
	        if (this.done) {
	            return this.EOF;
	        }
	        if (!this._input) this.done = true;

	        var token,
	            match,
	            lines;
	        if (!this._more) {
	            this.yytext = '';
	            this.match = '';
	        }
	        var rules = this._currentRules();
	        for (var i=0;i < rules.length; i++) {
	            match = this._input.match(this.rules[rules[i]]);
	            if (match) {
	                lines = match[0].match(/\n.*/g);
	                if (lines) this.yylineno += lines.length;
	                this.yylloc = {first_line: this.yylloc.last_line,
	                               last_line: this.yylineno+1,
	                               first_column: this.yylloc.last_column,
	                               last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length};
	                this.yytext += match[0];
	                this.match += match[0];
	                this.matches = match;
	                this.yyleng = this.yytext.length;
	                this._more = false;
	                this._input = this._input.slice(match[0].length);
	                this.matched += match[0];
	                token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]);
	                if (token) return token;
	                else return;
	            }
	        }
	        if (this._input === "") {
	            return this.EOF;
	        } else {
	            this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
	                    {text: "", token: null, line: this.yylineno});
	        }
	    },
	lex:function lex() {
	        var r = this.next();
	        if (typeof r !== 'undefined') {
	            return r;
	        } else {
	            return this.lex();
	        }
	    },
	begin:function begin(condition) {
	        this.conditionStack.push(condition);
	    },
	popState:function popState() {
	        return this.conditionStack.pop();
	    },
	_currentRules:function _currentRules() {
	        return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
	    },
	topState:function () {
	        return this.conditionStack[this.conditionStack.length-2];
	    },
	pushState:function begin(condition) {
	        this.begin(condition);
	    }});
	lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
	switch($avoiding_name_collisions) {
	case 0:/* skip whitespace */
	break;
	case 1:return 20
	case 2:return 19
	case 3:return 8
	case 4:return 9
	case 5:return 6
	case 6:return 7
	case 7:return 11
	case 8:return 13
	case 9:return 10
	case 10:return 12
	case 11:return 14
	case 12:return 15
	case 13:return 16
	case 14:return 17
	case 15:return 18
	case 16:return 5
	case 17:return 'INVALID'
	}
	};
	lexer.rules = [/^\s+/,/^[0-9]+(\.[0-9]+)?\b/,/^n\b/,/^\|\|/,/^&&/,/^\?/,/^:/,/^<=/,/^>=/,/^</,/^>/,/^!=/,/^==/,/^%/,/^\(/,/^\)/,/^$/,/^./];
	lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"inclusive":true}};return lexer;})();
	parser.lexer = lexer;
	return parser;
	})();
	// End parser

	  // Handle node, amd, and global systems
	  {
	    if (module.exports) {
	      exports = module.exports = Jed;
	    }
	    exports.Jed = Jed;
	  }

	})();
} (jed, jed.exports));

// @ts-ignore: no type decl for this library
new Logger("i18n/index.ts");

/*
 This file is part of GNU Taler
 (C) 2021 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * Functional programming utilities.
 */
var fnutil;
(function (fnutil) {
    function all(arr, f) {
        for (const x of arr) {
            if (!f(x)) {
                return false;
            }
        }
        return true;
    }
    fnutil.all = all;
    function any(arr, f) {
        for (const x of arr) {
            if (f(x)) {
                return true;
            }
        }
        return false;
    }
    fnutil.any = any;
})(fnutil || (fnutil = {}));

/**
 * Hypertext Transfer Protocol (HTTP) response status codes.
 *
 * @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes}
 */
var HttpStatusCode;
(function (HttpStatusCode) {
    /**
     * The server has received the request headers and the client should proceed to send the request body
     * (in the case of a request for which a body needs to be sent; for example, a POST request).
     * Sending a large request body to a server after a request has been rejected for inappropriate headers would be inefficient.
     * To have a server check the request's headers, a client must send Expect: 100-continue as a header in its initial request
     * and receive a 100 Continue status code in response before sending the body. The response 417 Expectation Failed indicates the request should not be continued.
     */
    HttpStatusCode[HttpStatusCode["Continue"] = 100] = "Continue";
    /**
     * The requester has asked the server to switch protocols and the server has agreed to do so.
     */
    HttpStatusCode[HttpStatusCode["SwitchingProtocols"] = 101] = "SwitchingProtocols";
    /**
     * A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request.
     * This code indicates that the server has received and is processing the request, but no response is available yet.
     * This prevents the client from timing out and assuming the request was lost.
     */
    HttpStatusCode[HttpStatusCode["Processing"] = 102] = "Processing";
    /**
     * Standard response for successful HTTP requests.
     * The actual response will depend on the request method used.
     * In a GET request, the response will contain an entity corresponding to the requested resource.
     * In a POST request, the response will contain an entity describing or containing the result of the action.
     */
    HttpStatusCode[HttpStatusCode["Ok"] = 200] = "Ok";
    /**
     * The request has been fulfilled, resulting in the creation of a new resource.
     */
    HttpStatusCode[HttpStatusCode["Created"] = 201] = "Created";
    /**
     * The request has been accepted for processing, but the processing has not been completed.
     * The request might or might not be eventually acted upon, and may be disallowed when processing occurs.
     */
    HttpStatusCode[HttpStatusCode["Accepted"] = 202] = "Accepted";
    /**
     * SINCE HTTP/1.1
     * The server is a transforming proxy that received a 200 OK from its origin,
     * but is returning a modified version of the origin's response.
     */
    HttpStatusCode[HttpStatusCode["NonAuthoritativeInformation"] = 203] = "NonAuthoritativeInformation";
    /**
     * The server successfully processed the request and is not returning any content.
     */
    HttpStatusCode[HttpStatusCode["NoContent"] = 204] = "NoContent";
    /**
     * The server successfully processed the request, but is not returning any content.
     * Unlike a 204 response, this response requires that the requester reset the document view.
     */
    HttpStatusCode[HttpStatusCode["ResetContent"] = 205] = "ResetContent";
    /**
     * The server is delivering only part of the resource (byte serving) due to a range header sent by the client.
     * The range header is used by HTTP clients to enable resuming of interrupted downloads,
     * or split a download into multiple simultaneous streams.
     */
    HttpStatusCode[HttpStatusCode["PartialContent"] = 206] = "PartialContent";
    /**
     * The message body that follows is an XML message and can contain a number of separate response codes,
     * depending on how many sub-requests were made.
     */
    HttpStatusCode[HttpStatusCode["MultiStatus"] = 207] = "MultiStatus";
    /**
     * The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response,
     * and are not being included again.
     */
    HttpStatusCode[HttpStatusCode["AlreadyReported"] = 208] = "AlreadyReported";
    /**
     * The server has fulfilled a request for the resource,
     * and the response is a representation of the result of one or more instance-manipulations applied to the current instance.
     */
    HttpStatusCode[HttpStatusCode["ImUsed"] = 226] = "ImUsed";
    /**
     * Indicates multiple options for the resource from which the client may choose (via agent-driven content negotiation).
     * For example, this code could be used to present multiple video format options,
     * to list files with different filename extensions, or to suggest word-sense disambiguation.
     */
    HttpStatusCode[HttpStatusCode["MultipleChoices"] = 300] = "MultipleChoices";
    /**
     * This and all future requests should be directed to the given URI.
     */
    HttpStatusCode[HttpStatusCode["MovedPermanently"] = 301] = "MovedPermanently";
    /**
     * This is an example of industry practice contradicting the standard.
     * The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect
     * (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302
     * with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307
     * to distinguish between the two behaviours. However, some Web applications and frameworks
     * use the 302 status code as if it were the 303.
     */
    HttpStatusCode[HttpStatusCode["Found"] = 302] = "Found";
    /**
     * SINCE HTTP/1.1
     * The response to the request can be found under another URI using a GET method.
     * When received in response to a POST (or PUT/DELETE), the client should presume that
     * the server has received the data and should issue a redirect with a separate GET message.
     */
    HttpStatusCode[HttpStatusCode["SeeOther"] = 303] = "SeeOther";
    /**
     * Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.
     * In such case, there is no need to retransmit the resource since the client still has a previously-downloaded copy.
     */
    HttpStatusCode[HttpStatusCode["NotModified"] = 304] = "NotModified";
    /**
     * SINCE HTTP/1.1
     * The requested resource is available only through a proxy, the address for which is provided in the response.
     * Many HTTP clients (such as Mozilla and Internet Explorer) do not correctly handle responses with this status code, primarily for security reasons.
     */
    HttpStatusCode[HttpStatusCode["UseProxy"] = 305] = "UseProxy";
    /**
     * No longer used. Originally meant "Subsequent requests should use the specified proxy."
     */
    HttpStatusCode[HttpStatusCode["SwitchProxy"] = 306] = "SwitchProxy";
    /**
     * SINCE HTTP/1.1
     * In this case, the request should be repeated with another URI; however, future requests should still use the original URI.
     * In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request.
     * For example, a POST request should be repeated using another POST request.
     */
    HttpStatusCode[HttpStatusCode["TemporaryRedirect"] = 307] = "TemporaryRedirect";
    /**
     * The request and all future requests should be repeated using another URI.
     * 307 and 308 parallel the behaviors of 302 and 301, but do not allow the HTTP method to change.
     * So, for example, submitting a form to a permanently redirected resource may continue smoothly.
     */
    HttpStatusCode[HttpStatusCode["PermanentRedirect"] = 308] = "PermanentRedirect";
    /**
     * The server cannot or will not process the request due to an apparent client error
     * (e.g., malformed request syntax, too large size, invalid request message framing, or deceptive request routing).
     */
    HttpStatusCode[HttpStatusCode["BadRequest"] = 400] = "BadRequest";
    /**
     * Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet
     * been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the
     * requested resource. See Basic access authentication and Digest access authentication. 401 semantically means
     * "unauthenticated",i.e. the user does not have the necessary credentials.
     */
    HttpStatusCode[HttpStatusCode["Unauthorized"] = 401] = "Unauthorized";
    /**
     * Reserved for future use. The original intention was that this code might be used as part of some form of digital
     * cash or micro payment scheme, but that has not happened, and this code is not usually used.
     * Google Developers API uses this status if a particular developer has exceeded the daily limit on requests.
     */
    HttpStatusCode[HttpStatusCode["PaymentRequired"] = 402] = "PaymentRequired";
    /**
     * The request was valid, but the server is refusing action.
     * The user might not have the necessary permissions for a resource.
     */
    HttpStatusCode[HttpStatusCode["Forbidden"] = 403] = "Forbidden";
    /**
     * The requested resource could not be found but may be available in the future.
     * Subsequent requests by the client are permissible.
     */
    HttpStatusCode[HttpStatusCode["NotFound"] = 404] = "NotFound";
    /**
     * A request method is not supported for the requested resource;
     * for example, a GET request on a form that requires data to be presented via POST, or a PUT request on a read-only resource.
     */
    HttpStatusCode[HttpStatusCode["MethodNotAllowed"] = 405] = "MethodNotAllowed";
    /**
     * The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.
     */
    HttpStatusCode[HttpStatusCode["NotAcceptable"] = 406] = "NotAcceptable";
    /**
     * The client must first authenticate itself with the proxy.
     */
    HttpStatusCode[HttpStatusCode["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired";
    /**
     * The server timed out waiting for the request.
     * According to HTTP specifications:
     * "The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time."
     */
    HttpStatusCode[HttpStatusCode["RequestTimeout"] = 408] = "RequestTimeout";
    /**
     * Indicates that the request could not be processed because of conflict in the request,
     * such as an edit conflict between multiple simultaneous updates.
     */
    HttpStatusCode[HttpStatusCode["Conflict"] = 409] = "Conflict";
    /**
     * Indicates that the resource requested is no longer available and will not be available again.
     * This should be used when a resource has been intentionally removed and the resource should be purged.
     * Upon receiving a 410 status code, the client should not request the resource in the future.
     * Clients such as search engines should remove the resource from their indices.
     * Most use cases do not require clients and search engines to purge the resource, and a "404 Not Found" may be used instead.
     */
    HttpStatusCode[HttpStatusCode["Gone"] = 410] = "Gone";
    /**
     * The request did not specify the length of its content, which is required by the requested resource.
     */
    HttpStatusCode[HttpStatusCode["LengthRequired"] = 411] = "LengthRequired";
    /**
     * The server does not meet one of the preconditions that the requester put on the request.
     */
    HttpStatusCode[HttpStatusCode["PreconditionFailed"] = 412] = "PreconditionFailed";
    /**
     * The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".
     */
    HttpStatusCode[HttpStatusCode["PayloadTooLarge"] = 413] = "PayloadTooLarge";
    /**
     * The URI provided was too long for the server to process. Often the result of too much data being encoded as a query-string of a GET request,
     * in which case it should be converted to a POST request.
     * Called "Request-URI Too Long" previously.
     */
    HttpStatusCode[HttpStatusCode["UriTooLong"] = 414] = "UriTooLong";
    /**
     * The request entity has a media type which the server or resource does not support.
     * For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format.
     */
    HttpStatusCode[HttpStatusCode["UnsupportedMediaType"] = 415] = "UnsupportedMediaType";
    /**
     * The client has asked for a portion of the file (byte serving), but the server cannot supply that portion.
     * For example, if the client asked for a part of the file that lies beyond the end of the file.
     * Called "Requested Range Not Satisfiable" previously.
     */
    HttpStatusCode[HttpStatusCode["RangeNotSatisfiable"] = 416] = "RangeNotSatisfiable";
    /**
     * The server cannot meet the requirements of the Expect request-header field.
     */
    HttpStatusCode[HttpStatusCode["ExpectationFailed"] = 417] = "ExpectationFailed";
    /**
     * This code was defined in 1998 as one of the traditional IETF April Fools' jokes, in RFC 2324, Hyper Text Coffee Pot Control Protocol,
     * and is not expected to be implemented by actual HTTP servers. The RFC specifies this code should be returned by
     * teapots requested to brew coffee. This HTTP status is used as an Easter egg in some websites, including Google.com.
     */
    HttpStatusCode[HttpStatusCode["IAmATeapot"] = 418] = "IAmATeapot";
    /**
     * The request was directed at a server that is not able to produce a response (for example because a connection reuse).
     */
    HttpStatusCode[HttpStatusCode["MisdirectedRequest"] = 421] = "MisdirectedRequest";
    /**
     * The request was well-formed but was unable to be followed due to semantic errors.
     */
    HttpStatusCode[HttpStatusCode["UnprocessableEntity"] = 422] = "UnprocessableEntity";
    /**
     * The resource that is being accessed is locked.
     */
    HttpStatusCode[HttpStatusCode["Locked"] = 423] = "Locked";
    /**
     * The request failed due to failure of a previous request (e.g., a PROPPATCH).
     */
    HttpStatusCode[HttpStatusCode["FailedDependency"] = 424] = "FailedDependency";
    /**
     * The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field.
     */
    HttpStatusCode[HttpStatusCode["UpgradeRequired"] = 426] = "UpgradeRequired";
    /**
     * The origin server requires the request to be conditional.
     * Intended to prevent "the 'lost update' problem, where a client
     * GETs a resource's state, modifies it, and PUTs it back to the server,
     * when meanwhile a third party has modified the state on the server, leading to a conflict."
     */
    HttpStatusCode[HttpStatusCode["PreconditionRequired"] = 428] = "PreconditionRequired";
    /**
     * The user has sent too many requests in a given amount of time. Intended for use with rate-limiting schemes.
     */
    HttpStatusCode[HttpStatusCode["TooManyRequests"] = 429] = "TooManyRequests";
    /**
     * The server is unwilling to process the request because either an individual header field,
     * or all the header fields collectively, are too large.
     */
    HttpStatusCode[HttpStatusCode["RequestHeaderFieldsTooLarge"] = 431] = "RequestHeaderFieldsTooLarge";
    /**
     * A server operator has received a legal demand to deny access to a resource or to a set of resources
     * that includes the requested resource. The code 451 was chosen as a reference to the novel Fahrenheit 451.
     */
    HttpStatusCode[HttpStatusCode["UnavailableForLegalReasons"] = 451] = "UnavailableForLegalReasons";
    /**
     * A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.
     */
    HttpStatusCode[HttpStatusCode["InternalServerError"] = 500] = "InternalServerError";
    /**
     * The server either does not recognize the request method, or it lacks the ability to fulfill the request.
     * Usually this implies future availability (e.g., a new feature of a web-service API).
     */
    HttpStatusCode[HttpStatusCode["NotImplemented"] = 501] = "NotImplemented";
    /**
     * The server was acting as a gateway or proxy and received an invalid response from the upstream server.
     */
    HttpStatusCode[HttpStatusCode["BadGateway"] = 502] = "BadGateway";
    /**
     * The server is currently unavailable (because it is overloaded or down for maintenance).
     * Generally, this is a temporary state.
     */
    HttpStatusCode[HttpStatusCode["ServiceUnavailable"] = 503] = "ServiceUnavailable";
    /**
     * The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.
     */
    HttpStatusCode[HttpStatusCode["GatewayTimeout"] = 504] = "GatewayTimeout";
    /**
     * The server does not support the HTTP protocol version used in the request
     */
    HttpStatusCode[HttpStatusCode["HttpVersionNotSupported"] = 505] = "HttpVersionNotSupported";
    /**
     * Transparent content negotiation for the request results in a circular reference.
     */
    HttpStatusCode[HttpStatusCode["VariantAlsoNegotiates"] = 506] = "VariantAlsoNegotiates";
    /**
     * The server is unable to store the representation needed to complete the request.
     */
    HttpStatusCode[HttpStatusCode["InsufficientStorage"] = 507] = "InsufficientStorage";
    /**
     * The server detected an infinite loop while processing the request.
     */
    HttpStatusCode[HttpStatusCode["LoopDetected"] = 508] = "LoopDetected";
    /**
     * Further extensions to the request are required for the server to fulfill it.
     */
    HttpStatusCode[HttpStatusCode["NotExtended"] = 510] = "NotExtended";
    /**
     * The client needs to authenticate to gain network access.
     * Intended for use by intercepting proxies used to control access to the network (e.g., "captive portals" used
     * to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot).
     */
    HttpStatusCode[HttpStatusCode["NetworkAuthenticationRequired"] = 511] = "NetworkAuthenticationRequired";
})(HttpStatusCode || (HttpStatusCode = {}));

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * Implementation of token bucket throttling.
 */
const logger$t = new Logger("RequestThrottler.ts");
/**
 * Maximum request per second, per origin.
 */
const MAX_PER_SECOND = 100;
/**
 * Maximum request per minute, per origin.
 */
const MAX_PER_MINUTE = 500;
/**
 * Maximum request per hour, per origin.
 */
const MAX_PER_HOUR = 2000;
/**
 * Throttling state for one origin.
 */
class OriginState {
    constructor() {
        this.tokensSecond = MAX_PER_SECOND;
        this.tokensMinute = MAX_PER_MINUTE;
        this.tokensHour = MAX_PER_HOUR;
        this.lastUpdate = AbsoluteTime.now();
    }
    refill() {
        const now = AbsoluteTime.now();
        if (AbsoluteTime.cmp(now, this.lastUpdate) < 0) {
            // Did the system time change?
            this.lastUpdate = now;
            return;
        }
        const d = AbsoluteTime.difference(now, this.lastUpdate);
        if (d.d_ms === "forever") {
            throw Error("assertion failed");
        }
        this.tokensSecond = Math.min(MAX_PER_SECOND, this.tokensSecond + d.d_ms / 1000);
        this.tokensMinute = Math.min(MAX_PER_MINUTE, this.tokensMinute + d.d_ms / 1000 / 60);
        this.tokensHour = Math.min(MAX_PER_HOUR, this.tokensHour + d.d_ms / 1000 / 60 / 60);
        this.lastUpdate = now;
    }
    /**
     * Return true if the request for this origin should be throttled.
     * Otherwise, take a token out of the respective buckets.
     */
    applyThrottle() {
        this.refill();
        if (this.tokensSecond < 1) {
            logger$t.warn("request throttled (per second limit exceeded)");
            return true;
        }
        if (this.tokensMinute < 1) {
            logger$t.warn("request throttled (per minute limit exceeded)");
            return true;
        }
        if (this.tokensHour < 1) {
            logger$t.warn("request throttled (per hour limit exceeded)");
            return true;
        }
        this.tokensSecond--;
        this.tokensMinute--;
        this.tokensHour--;
        return false;
    }
}
/**
 * Request throttler, used as a "last layer of defense" when some
 * other part of the re-try logic is broken and we're sending too
 * many requests to the same exchange/bank/merchant.
 */
class RequestThrottler {
    constructor() {
        this.perOriginInfo = {};
    }
    /**
     * Get the throttling state for an origin, or
     * initialize if no state is associated with the
     * origin yet.
     */
    getState(origin) {
        const s = this.perOriginInfo[origin];
        if (s) {
            return s;
        }
        const ns = (this.perOriginInfo[origin] = new OriginState());
        return ns;
    }
    /**
     * Apply throttling to a request.
     *
     * @returns whether the request should be throttled.
     */
    applyThrottle(requestUrl) {
        const origin = new URL(requestUrl).origin;
        return this.getState(origin).applyThrottle();
    }
    /**
     * Get the throttle statistics for a particular URL.
     */
    getThrottleStats(requestUrl) {
        const origin = new URL(requestUrl).origin;
        const state = this.getState(origin);
        return {
            tokensHour: state.tokensHour,
            tokensMinute: state.tokensMinute,
            tokensSecond: state.tokensSecond,
            maxTokensHour: MAX_PER_HOUR,
            maxTokensMinute: MAX_PER_MINUTE,
            maxTokensSecond: MAX_PER_SECOND,
        };
    }
}

/*
MIT License

Copyright (c) 2017 Conrad Reuter

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.
*/
const NOOP = () => { };
/**
 * A token that can be passed around to inform consumers of the token that a
 * certain operation has been cancelled.
 */
class CancellationToken {
    constructor(
    /**
     * Whether the token is already cancelled.
     */
    _isCancelled, 
    /**
     * Whether the token can be cancelled.
     */
    _canBeCancelled) {
        this._isCancelled = _isCancelled;
        this._canBeCancelled = _canBeCancelled;
        this._callbacks = new Set();
    }
    /**
     * Whether the token has been cancelled.
     */
    get isCancelled() {
        return this._isCancelled;
    }
    /**
     * Whether the token can be cancelled.
     */
    get canBeCancelled() {
        return this._canBeCancelled;
    }
    /**
     * Why this token has been cancelled.
     */
    get reason() {
        if (this.isCancelled) {
            return this._reason;
        }
        else {
            throw new Error("This token is not cancelled.");
        }
    }
    /**
     * Make a promise that resolves when the async operation resolves,
     * or rejects when the operation is rejected or this token is cancelled.
     */
    racePromise(asyncOperation) {
        if (!this.canBeCancelled) {
            return asyncOperation;
        }
        return new Promise((resolve, reject) => {
            // we could use Promise.finally here as soon as it's implemented in the major browsers
            const unregister = this.onCancelled((reason) => reject(new CancellationToken.CancellationError(reason)));
            asyncOperation.then((value) => {
                resolve(value);
                unregister();
            }, (err) => {
                reject(err);
                unregister();
            });
        });
    }
    /**
     * Throw a {CancellationToken.CancellationError} if this token is cancelled.
     */
    throwIfCancelled() {
        if (this._isCancelled) {
            throw new CancellationToken.CancellationError(this._reason);
        }
    }
    /**
     * Invoke the callback when this token is cancelled.
     * If this token is already cancelled, the callback is invoked immediately.
     * Returns a function that unregisters the cancellation callback.
     */
    onCancelled(cb) {
        var _a;
        if (!this.canBeCancelled) {
            return NOOP;
        }
        if (this.isCancelled) {
            cb(this.reason);
            return NOOP;
        }
        /* istanbul ignore next */
        (_a = this._callbacks) === null || _a === void 0 ? void 0 : _a.add(cb);
        return () => { var _a; return (_a = this._callbacks) === null || _a === void 0 ? void 0 : _a.delete(cb); };
    }
    /**
     * Create a {CancellationTokenSource}.
     */
    static create() {
        const token = new CancellationToken(false, true);
        const cancel = (reason) => {
            var _a;
            if (token._isCancelled)
                return;
            token._isCancelled = true;
            token._reason = reason;
            (_a = token._callbacks) === null || _a === void 0 ? void 0 : _a.forEach((cb) => cb(reason));
            dispose();
        };
        const dispose = () => {
            token._canBeCancelled = token.isCancelled;
            delete token._callbacks; // release memory
        };
        return { token, cancel, dispose };
    }
    /**
     * Create a {CancellationTokenSource}.
     * The token will be cancelled automatically after the specified timeout in milliseconds.
     */
    static timeout(ms) {
        const { token, cancel: originalCancel, dispose: originalDispose, } = CancellationToken.create();
        let timer;
        timer = setTimeout(() => originalCancel(CancellationToken.timeout), ms);
        const disposeTimer = () => {
            if (timer == null)
                return;
            clearTimeout(timer);
            timer = null;
        };
        const cancel = (reason) => {
            disposeTimer();
            originalCancel(reason);
        };
        /* istanbul ignore next */
        const dispose = () => {
            disposeTimer();
            originalDispose();
        };
        return { token, cancel, dispose };
    }
    /**
     * Create a {CancellationToken} that is cancelled when all of the given tokens are cancelled.
     *
     * This is like {Promise<T>.all} for {CancellationToken}s.
     */
    static all(...tokens) {
        // If *any* of the tokens cannot be cancelled, then the token we return can never be.
        if (tokens.some((token) => !token.canBeCancelled)) {
            return CancellationToken.CONTINUE;
        }
        const combined = CancellationToken.create();
        let countdown = tokens.length;
        const handleNextTokenCancelled = () => {
            if (--countdown === 0) {
                const reasons = tokens.map((token) => token._reason);
                combined.cancel(reasons);
            }
        };
        tokens.forEach((token) => token.onCancelled(handleNextTokenCancelled));
        return combined.token;
    }
    /**
     * Create a {CancellationToken} that is cancelled when at least one of the given tokens is cancelled.
     *
     * This is like {Promise<T>.race} for {CancellationToken}s.
     */
    static race(...tokens) {
        // If *any* of the tokens is already cancelled, immediately return that token.
        for (const token of tokens) {
            if (token._isCancelled) {
                return token;
            }
        }
        const combined = CancellationToken.create();
        let unregistrations;
        const handleAnyTokenCancelled = (reason) => {
            unregistrations.forEach((unregister) => unregister()); // release memory
            combined.cancel(reason);
        };
        unregistrations = tokens.map((token) => token.onCancelled(handleAnyTokenCancelled));
        return combined.token;
    }
}
/**
 * A cancellation token that is already cancelled.
 */
CancellationToken.CANCELLED = new CancellationToken(true, true);
/**
 * A cancellation token that is never cancelled.
 */
CancellationToken.CONTINUE = new CancellationToken(false, false);
/* istanbul ignore next */
(function (CancellationToken) {
    /**
     * The error that is thrown when a {CancellationToken} has been cancelled and a
     * consumer of the token calls {CancellationToken.throwIfCancelled} on it.
     */
    class CancellationError extends Error {
        constructor(
        /**
         * The reason why the token was cancelled.
         */
        reason) {
            super("Operation cancelled");
            this.reason = reason;
            Object.setPrototypeOf(this, CancellationError.prototype);
        }
    }
    CancellationToken.CancellationError = CancellationError;
})(CancellationToken || (CancellationToken = {}));

/*
 This file is part of GNU Taler
 (C) 2021 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
new Logger("contractTerms.ts");
var ContractTermsUtil;
(function (ContractTermsUtil) {
    function forgetAllImpl(anyJson, path, pred) {
        const dup = JSON.parse(JSON.stringify(anyJson));
        if (Array.isArray(dup)) {
            for (let i = 0; i < dup.length; i++) {
                dup[i] = forgetAllImpl(dup[i], [...path, `${i}`], pred);
            }
        }
        else if (typeof dup === "object" && dup != null) {
            if (typeof dup.$forgettable === "object") {
                for (const x of Object.keys(dup.$forgettable)) {
                    if (!pred([...path, x])) {
                        continue;
                    }
                    if (!dup.$forgotten) {
                        dup.$forgotten = {};
                    }
                    if (!dup.$forgotten[x]) {
                        const membValCanon = stringToBytes(canonicalJson(scrub(dup[x])) + "\0");
                        const membSalt = stringToBytes(dup.$forgettable[x] + "\0");
                        const h = kdf(64, membValCanon, membSalt, new Uint8Array([]));
                        dup.$forgotten[x] = encodeCrock(h);
                    }
                    delete dup[x];
                    delete dup.$forgettable[x];
                }
                if (Object.keys(dup.$forgettable).length === 0) {
                    delete dup.$forgettable;
                }
            }
            for (const x of Object.keys(dup)) {
                if (x.startsWith("$")) {
                    continue;
                }
                dup[x] = forgetAllImpl(dup[x], [...path, x], pred);
            }
        }
        return dup;
    }
    ContractTermsUtil.forgetAllImpl = forgetAllImpl;
    /**
     * Scrub all forgettable members from an object.
     */
    function scrub(anyJson) {
        return forgetAllImpl(anyJson, [], () => true);
    }
    ContractTermsUtil.scrub = scrub;
    /**
     * Recursively forget all forgettable members of an object,
     * where the path matches a predicate.
     */
    function forgetAll(anyJson, pred) {
        return forgetAllImpl(anyJson, [], pred);
    }
    ContractTermsUtil.forgetAll = forgetAll;
    /**
     * Generate a salt for all members marked as forgettable,
     * but which don't have an actual salt yet.
     */
    function saltForgettable(anyJson) {
        const dup = JSON.parse(JSON.stringify(anyJson));
        if (Array.isArray(dup)) {
            for (let i = 0; i < dup.length; i++) {
                dup[i] = saltForgettable(dup[i]);
            }
        }
        else if (typeof dup === "object" && dup !== null) {
            if (typeof dup.$forgettable === "object") {
                for (const k of Object.keys(dup.$forgettable)) {
                    if (dup.$forgettable[k] === true) {
                        dup.$forgettable[k] = encodeCrock(getRandomBytes(32));
                    }
                }
            }
            for (const x of Object.keys(dup)) {
                if (x.startsWith("$")) {
                    continue;
                }
                dup[x] = saltForgettable(dup[x]);
            }
        }
        return dup;
    }
    ContractTermsUtil.saltForgettable = saltForgettable;
    const nameRegex = /^[0-9A-Za-z_]+$/;
    /**
     * Check that the given JSON object is well-formed with regards
     * to forgettable fields and other restrictions for forgettable JSON.
     */
    function validateForgettable(anyJson) {
        var _a;
        if (typeof anyJson === "string") {
            return true;
        }
        if (typeof anyJson === "number") {
            return (Number.isInteger(anyJson) &&
                anyJson >= Number.MIN_SAFE_INTEGER &&
                anyJson <= Number.MAX_SAFE_INTEGER);
        }
        if (typeof anyJson === "boolean") {
            return true;
        }
        if (anyJson === null) {
            return true;
        }
        if (Array.isArray(anyJson)) {
            return anyJson.every((x) => validateForgettable(x));
        }
        if (typeof anyJson === "object") {
            for (const k of Object.keys(anyJson)) {
                if (k.match(nameRegex)) {
                    if (validateForgettable(anyJson[k])) {
                        continue;
                    }
                    else {
                        return false;
                    }
                }
                if (k === "$forgettable") {
                    const fga = anyJson.$forgettable;
                    if (!fga || typeof fga !== "object") {
                        return false;
                    }
                    for (const fk of Object.keys(fga)) {
                        if (!fk.match(nameRegex)) {
                            return false;
                        }
                        if (!(fk in anyJson)) {
                            return false;
                        }
                        const fv = anyJson.$forgettable[fk];
                        if (typeof fv !== "string") {
                            return false;
                        }
                    }
                }
                else if (k === "$forgotten") {
                    const fgo = anyJson.$forgotten;
                    if (!fgo || typeof fgo !== "object") {
                        return false;
                    }
                    for (const fk of Object.keys(fgo)) {
                        if (!fk.match(nameRegex)) {
                            return false;
                        }
                        // Check that the value has actually been forgotten.
                        if (fk in anyJson) {
                            return false;
                        }
                        const fv = anyJson.$forgotten[fk];
                        if (typeof fv !== "string") {
                            return false;
                        }
                        try {
                            const decFv = decodeCrock(fv);
                            if (decFv.length != 64) {
                                return false;
                            }
                        }
                        catch (e) {
                            return false;
                        }
                        // Check that salt has been deleted after forgetting.
                        if (((_a = anyJson.$forgettable) === null || _a === void 0 ? void 0 : _a[k]) !== undefined) {
                            return false;
                        }
                    }
                }
                else {
                    return false;
                }
            }
            return true;
        }
        return false;
    }
    ContractTermsUtil.validateForgettable = validateForgettable;
    /**
     * Check that no forgettable information has been forgotten.
     *
     * Must only be called on an object already validated with validateForgettable.
     */
    function validateNothingForgotten(contractTerms) {
        throw Error("not implemented yet");
    }
    ContractTermsUtil.validateNothingForgotten = validateNothingForgotten;
    /**
     * Hash a contract terms object.  Forgettable fields
     * are scrubbed and JSON canonicalization is applied
     * before hashing.
     */
    function hashContractTerms(contractTerms) {
        const cleaned = scrub(contractTerms);
        const canon = canonicalJson(cleaned) + "\0";
        const bytes = stringToBytes(canon);
        return encodeCrock(hash(bytes));
    }
    ContractTermsUtil.hashContractTerms = hashContractTerms;
})(ContractTermsUtil || (ContractTermsUtil = {}));

/*
Original work Copyright (c) Isaac Z. Schlueter and Contributors
Modified work Copyright (c) 2021 Taler Systems S.A.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
*/
let path = { sep: "/" };
try {
    path.sep = require("path").sep;
}
catch (er) { }
// characters that need to be escaped in RegExp.
charSet("().*{}+?[]^$\\!");
// "abc" -> { a:true, b:true, c:true }
function charSet(s) {
    return s.split("").reduce(function (set, c) {
        set[c] = true;
        return set;
    }, {});
}

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
var clk;
(function (clk) {
    class Converter {
    }
    clk.INT = new Converter();
    clk.STRING = new Converter();
    function splitOpt(opt) {
        const idx = opt.indexOf("=");
        if (idx == -1) {
            return { key: opt };
        }
        return { key: opt.substring(0, idx), value: opt.substring(idx + 1) };
    }
    function formatListing(key, value) {
        const res = "  " + key;
        if (!value) {
            return res;
        }
        if (res.length >= 25) {
            return res + "\n" + "    " + value;
        }
        else {
            return res.padEnd(24) + " " + value;
        }
    }
    class CommandGroup {
        constructor(argKey, name, scArgs) {
            this.argKey = argKey;
            this.name = name;
            this.scArgs = scArgs;
            this.shortOptions = {};
            this.longOptions = {};
            this.subcommandMap = {};
            this.subcommands = [];
            this.options = [];
            this.arguments = [];
        }
        action(f) {
            if (this.myAction) {
                throw Error("only one action supported per command");
            }
            this.myAction = f;
        }
        requiredOption(name, flagspec, conv, args = {}) {
            const def = {
                args: args,
                conv: conv,
                flagspec: flagspec,
                isFlag: false,
                required: true,
                name: name,
            };
            this.options.push(def);
            for (const flag of flagspec) {
                if (flag.startsWith("--")) {
                    const flagname = flag.substring(2);
                    this.longOptions[flagname] = def;
                }
                else if (flag.startsWith("-")) {
                    const flagname = flag.substring(1);
                    this.shortOptions[flagname] = def;
                }
                else {
                    throw Error("option must start with '-' or '--'");
                }
            }
            return this;
        }
        maybeOption(name, flagspec, conv, args = {}) {
            const def = {
                args: args,
                conv: conv,
                flagspec: flagspec,
                isFlag: false,
                required: false,
                name: name,
            };
            this.options.push(def);
            for (const flag of flagspec) {
                if (flag.startsWith("--")) {
                    const flagname = flag.substring(2);
                    this.longOptions[flagname] = def;
                }
                else if (flag.startsWith("-")) {
                    const flagname = flag.substring(1);
                    this.shortOptions[flagname] = def;
                }
                else {
                    throw Error("option must start with '-' or '--'");
                }
            }
            return this;
        }
        requiredArgument(name, conv, args = {}) {
            const argDef = {
                args: args,
                conv: conv,
                name: name,
                required: true,
            };
            this.arguments.push(argDef);
            return this;
        }
        maybeArgument(name, conv, args = {}) {
            const argDef = {
                args: args,
                conv: conv,
                name: name,
                required: false,
            };
            this.arguments.push(argDef);
            return this;
        }
        flag(name, flagspec, args = {}) {
            const def = {
                args: args,
                flagspec: flagspec,
                isFlag: true,
                required: false,
                name: name,
            };
            this.options.push(def);
            for (const flag of flagspec) {
                if (flag.startsWith("--")) {
                    const flagname = flag.substring(2);
                    this.longOptions[flagname] = def;
                }
                else if (flag.startsWith("-")) {
                    const flagname = flag.substring(1);
                    this.shortOptions[flagname] = def;
                }
                else {
                    throw Error("option must start with '-' or '--'");
                }
            }
            return this;
        }
        subcommand(argKey, name, args = {}) {
            const cg = new CommandGroup(argKey, name, args);
            const def = {
                commandGroup: cg,
                name: name,
                args: args,
            };
            cg.flag("help", ["-h", "--help"], {
                help: "Show this message and exit.",
            });
            this.subcommandMap[name] = def;
            this.subcommands.push(def);
            this.subcommands = this.subcommands.sort((x1, x2) => {
                const a = x1.name;
                const b = x2.name;
                if (a === b) {
                    return 0;
                }
                else if (a < b) {
                    return -1;
                }
                else {
                    return 1;
                }
            });
            return cg;
        }
        printHelp(progName, parents) {
            var _a, _b, _c;
            let usageSpec = "";
            for (const p of parents) {
                usageSpec += ((_a = p.name) !== null && _a !== void 0 ? _a : progName) + " ";
                if (p.arguments.length >= 1) {
                    usageSpec += "<ARGS...> ";
                }
            }
            usageSpec += ((_b = this.name) !== null && _b !== void 0 ? _b : progName) + " ";
            if (this.subcommands.length != 0) {
                usageSpec += "COMMAND ";
            }
            for (const a of this.arguments) {
                const argName = (_c = a.args.metavar) !== null && _c !== void 0 ? _c : a.name;
                usageSpec += `<${argName}> `;
            }
            usageSpec = usageSpec.trimRight();
            console.log(`Usage: ${usageSpec}`);
            if (this.scArgs.help) {
                console.log();
                console.log(this.scArgs.help);
            }
            if (this.options.length != 0) {
                console.log();
                console.log("Options:");
                for (const opt of this.options) {
                    let optSpec = opt.flagspec.join(", ");
                    if (!opt.isFlag) {
                        optSpec = optSpec + "=VALUE";
                    }
                    console.log(formatListing(optSpec, opt.args.help));
                }
            }
            if (this.subcommands.length != 0) {
                console.log();
                console.log("Commands:");
                for (const subcmd of this.subcommands) {
                    console.log(formatListing(subcmd.name, subcmd.args.help));
                }
            }
        }
        /**
         * Run the (sub-)command with the given command line parameters.
         */
        run(progname, parents, unparsedArgs, parsedArgs) {
            var _a;
            let posArgIndex = 0;
            let argsTerminated = false;
            let i;
            let foundSubcommand = undefined;
            const myArgs = (parsedArgs[this.argKey] = {});
            const foundOptions = {};
            const currentName = (_a = this.name) !== null && _a !== void 0 ? _a : progname;
            const storeOption = (def, value) => {
                foundOptions[def.name] = true;
                if (def.conv === clk.INT) {
                    myArgs[def.name] = Number.parseInt(value);
                }
                else if (def.conv == null || def.conv === clk.STRING) {
                    myArgs[def.name] = value;
                }
                else {
                    throw Error("unknown converter");
                }
            };
            const storeFlag = (def, value) => {
                foundOptions[def.name] = true;
                myArgs[def.name] = value;
            };
            for (i = 0; i < unparsedArgs.length; i++) {
                const argVal = unparsedArgs[i];
                if (argsTerminated == false) {
                    if (argVal === "--") {
                        argsTerminated = true;
                        continue;
                    }
                    if (argVal.startsWith("--")) {
                        const opt = argVal.substring(2);
                        const r = splitOpt(opt);
                        const d = this.longOptions[r.key];
                        if (!d) {
                            console.error(`error: unknown option '--${r.key}' for ${currentName}`);
                            process__default["default"].exit(-1);
                            throw Error("not reached");
                        }
                        if (d.isFlag) {
                            if (r.value !== undefined) {
                                console.error(`error: flag '--${r.key}' does not take a value`);
                                process__default["default"].exit(-1);
                                throw Error("not reached");
                            }
                            storeFlag(d, true);
                        }
                        else {
                            if (r.value === undefined) {
                                if (i === unparsedArgs.length - 1) {
                                    console.error(`error: option '--${r.key}' needs an argument`);
                                    process__default["default"].exit(-1);
                                    throw Error("not reached");
                                }
                                storeOption(d, unparsedArgs[i + 1]);
                                i++;
                            }
                            else {
                                storeOption(d, r.value);
                            }
                        }
                        continue;
                    }
                    if (argVal.startsWith("-") && argVal != "-") {
                        const optShort = argVal.substring(1);
                        for (let si = 0; si < optShort.length; si++) {
                            const chr = optShort[si];
                            const opt = this.shortOptions[chr];
                            if (!opt) {
                                console.error(`error: option '-${chr}' not known`);
                                process__default["default"].exit(-1);
                            }
                            if (opt.isFlag) {
                                storeFlag(opt, true);
                            }
                            else {
                                if (si == optShort.length - 1) {
                                    if (i === unparsedArgs.length - 1) {
                                        console.error(`error: option '-${chr}' needs an argument`);
                                        process__default["default"].exit(-1);
                                        throw Error("not reached");
                                    }
                                    else {
                                        storeOption(opt, unparsedArgs[i + 1]);
                                        i++;
                                    }
                                }
                                else {
                                    storeOption(opt, optShort.substring(si + 1));
                                }
                                break;
                            }
                        }
                        continue;
                    }
                }
                if (this.subcommands.length != 0) {
                    const subcmd = this.subcommandMap[argVal];
                    if (!subcmd) {
                        console.error(`error: unknown command '${argVal}'`);
                        process__default["default"].exit(-1);
                        throw Error("not reached");
                    }
                    foundSubcommand = subcmd.commandGroup;
                    break;
                }
                else {
                    const d = this.arguments[posArgIndex];
                    if (!d) {
                        console.error(`error: too many arguments for ${currentName}`);
                        process__default["default"].exit(-1);
                        throw Error("not reached");
                    }
                    myArgs[d.name] = unparsedArgs[i];
                    posArgIndex++;
                }
            }
            if (parsedArgs[this.argKey].help) {
                this.printHelp(progname, parents);
                process__default["default"].exit(0);
                throw Error("not reached");
            }
            for (let i = posArgIndex; i < this.arguments.length; i++) {
                const d = this.arguments[i];
                if (d.required) {
                    if (d.args.default !== undefined) {
                        myArgs[d.name] = d.args.default;
                    }
                    else {
                        console.error(`error: missing positional argument '${d.name}' for ${currentName}`);
                        process__default["default"].exit(-1);
                        throw Error("not reached");
                    }
                }
            }
            for (const option of this.options) {
                if (option.isFlag == false && option.required == true) {
                    if (!foundOptions[option.name]) {
                        if (option.args.default !== undefined) {
                            myArgs[option.name] = option.args.default;
                        }
                        else {
                            const name = option.flagspec.join(",");
                            console.error(`error: missing option '${name}'`);
                            process__default["default"].exit(-1);
                            throw Error("not reached");
                        }
                    }
                }
            }
            for (const option of this.options) {
                const ph = option.args.onPresentHandler;
                if (ph && foundOptions[option.name]) {
                    ph(myArgs[option.name]);
                }
            }
            if (foundSubcommand) {
                foundSubcommand.run(progname, Array.prototype.concat(parents, [this]), unparsedArgs.slice(i + 1), parsedArgs);
            }
            else if (this.myAction) {
                let r;
                try {
                    r = this.myAction(parsedArgs);
                }
                catch (e) {
                    console.error(`An error occurred while running ${currentName}`);
                    console.error(e);
                    process__default["default"].exit(1);
                }
                Promise.resolve(r).catch((e) => {
                    console.error(`An error occurred while running ${currentName}`);
                    console.error(e);
                    process__default["default"].exit(1);
                });
            }
            else {
                this.printHelp(progname, parents);
                process__default["default"].exit(-1);
                throw Error("not reached");
            }
        }
    }
    clk.CommandGroup = CommandGroup;
    class Program {
        constructor(argKey, args = {}) {
            this.mainCommand = new CommandGroup(argKey, null, {
                help: args.help,
            });
            this.mainCommand.flag("help", ["-h", "--help"], {
                help: "Show this message and exit.",
            });
        }
        run(cmdlineArgs) {
            let args;
            if (cmdlineArgs) {
                args = cmdlineArgs;
            }
            else {
                args = process__default["default"].argv.slice(1);
            }
            if (args.length < 1) {
                console.error("Error while parsing command line arguments: not enough arguments");
                process__default["default"].exit(-1);
            }
            const progname = path__default["default"].basename(args[0]);
            const rest = args.slice(1);
            this.mainCommand.run(progname, [], rest, {});
        }
        subcommand(argKey, name, args = {}) {
            const cmd = this.mainCommand.subcommand(argKey, name, args);
            return cmd;
        }
        requiredOption(name, flagspec, conv, args = {}) {
            this.mainCommand.requiredOption(name, flagspec, conv, args);
            return this;
        }
        maybeOption(name, flagspec, conv, args = {}) {
            this.mainCommand.maybeOption(name, flagspec, conv, args);
            return this;
        }
        /**
         * Add a flag (option without value) to the program.
         */
        flag(name, flagspec, args = {}) {
            this.mainCommand.flag(name, flagspec, args);
            return this;
        }
        /**
         * Add a required positional argument to the program.
         */
        requiredArgument(name, conv, args = {}) {
            this.mainCommand.requiredArgument(name, conv, args);
            return this;
        }
        /**
         * Add an optional argument to the program.
         */
        maybeArgument(name, conv, args = {}) {
            this.mainCommand.maybeArgument(name, conv, args);
            return this;
        }
        action(f) {
            this.mainCommand.action(f);
        }
    }
    clk.Program = Program;
    function program(argKey, args = {}) {
        return new Program(argKey, args);
    }
    clk.program = program;
    function prompt(question) {
        const stdinReadline = readline__default["default"].createInterface({
            input: process__default["default"].stdin,
            output: process__default["default"].stdout,
        });
        return new Promise((resolve, reject) => {
            stdinReadline.question(question, (res) => {
                resolve(res);
                stdinReadline.close();
            });
        });
    }
    clk.prompt = prompt;
})(clk || (clk = {}));

/*
 This file is part of GNU Taler
 (C) 2021 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
initNodePrng();

/*
 This file is part of GNU Taler
 (C) 2019-2020 Taler Systems SA

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
function makeErrorDetail(code, detail, hint) {
    if (!hint && !detail.hint) {
        hint = getDefaultHint(code);
    }
    return Object.assign({ code, hint }, detail);
}
function makePendingOperationFailedError(innerError, tag, uid) {
    return TalerError.fromDetail(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED, {
        innerError,
        transactionId: `${tag}:${uid}`,
    });
}
function getDefaultHint(code) {
    const errName = TalerErrorCode[code];
    if (errName) {
        return `Error (${errName})`;
    }
    else {
        return `Error (<unknown>)`;
    }
}
class TalerProtocolViolationError extends Error {
    constructor(hint) {
        let msg;
        if (hint) {
            msg = `Taler protocol violation error (${hint})`;
        }
        else {
            msg = `Taler protocol violation error`;
        }
        super(msg);
        Object.setPrototypeOf(this, TalerProtocolViolationError.prototype);
    }
}
class TalerError extends Error {
    constructor(d) {
        var _a;
        super((_a = d.hint) !== null && _a !== void 0 ? _a : `Error (code ${d.code})`);
        this.errorDetail = d;
        Object.setPrototypeOf(this, TalerError.prototype);
    }
    static fromDetail(code, detail, hint) {
        if (!hint) {
            hint = getDefaultHint(code);
        }
        return new TalerError(Object.assign({ code, hint }, detail));
    }
    static fromUncheckedDetail(d) {
        return new TalerError(Object.assign({}, d));
    }
    static fromException(e) {
        const errDetail = getErrorDetailFromException(e);
        return new TalerError(errDetail);
    }
    hasErrorCode(code) {
        return this.errorDetail.code === code;
    }
}
/**
 * Convert an exception (or anything that was thrown) into
 * a TalerErrorDetail object.
 */
function getErrorDetailFromException(e) {
    if (e instanceof TalerError) {
        return e.errorDetail;
    }
    if (e instanceof Error) {
        const err = makeErrorDetail(TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, {
            stack: e.stack,
        }, `unexpected exception (message: ${e.message})`);
        return err;
    }
    // Something was thrown that is not even an exception!
    // Try to stringify it.
    let excString;
    try {
        excString = e.toString();
    }
    catch (e) {
        // Something went horribly wrong.
        excString = "can't stringify exception";
    }
    const err = makeErrorDetail(TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, {}, `unexpected exception (not an exception, ${excString})`);
    return err;
}

/*
 This file is part of GNU Taler
 (C) 2019 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/>
 */
/**
 * Get an unresolved promise together with its extracted resolve / reject
 * function.
 */
function openPromise$1() {
    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 };
}
class AsyncCondition$1 {
    constructor() {
        const op = openPromise$1();
        this._waitPromise = op.promise;
        this._resolveWaitPromise = op.resolve;
    }
    wait() {
        return this._waitPromise;
    }
    trigger() {
        this._resolveWaitPromise();
        const op = openPromise$1();
        this._waitPromise = op.promise;
        this._resolveWaitPromise = op.resolve;
    }
}

/*
 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/>
 */
const logger$s = new Logger("query.ts");
/**
 * Exception that should be thrown by client code to abort a transaction.
 */
const TransactionAbort = Symbol("transaction_abort");
function requestToPromise(req) {
    const stack = Error("Failed request was started here.");
    return new Promise((resolve, reject) => {
        req.onsuccess = () => {
            resolve(req.result);
        };
        req.onerror = () => {
            console.error("error in DB request", req.error);
            reject(req.error);
            console.error("Request failed:", stack);
        };
    });
}
class TransactionAbortedError extends Error {
    constructor(m) {
        super(m);
        // Set the prototype explicitly.
        Object.setPrototypeOf(this, TransactionAbortedError.prototype);
    }
}
class ResultStream {
    constructor(req) {
        this.req = req;
        this.gotCursorEnd = false;
        this.awaitingResult = false;
        this.awaitingResult = true;
        let p = openPromise$1();
        this.currentPromise = p.promise;
        req.onsuccess = () => {
            if (!this.awaitingResult) {
                throw Error("BUG: invariant violated");
            }
            const cursor = req.result;
            if (cursor) {
                this.awaitingResult = false;
                p.resolve();
                p = openPromise$1();
                this.currentPromise = p.promise;
            }
            else {
                this.gotCursorEnd = true;
                p.resolve();
            }
        };
        req.onerror = () => {
            p.reject(req.error);
        };
    }
    async toArray() {
        const arr = [];
        while (true) {
            const x = await this.next();
            if (x.hasValue) {
                arr.push(x.value);
            }
            else {
                break;
            }
        }
        return arr;
    }
    async map(f) {
        const arr = [];
        while (true) {
            const x = await this.next();
            if (x.hasValue) {
                arr.push(f(x.value));
            }
            else {
                break;
            }
        }
        return arr;
    }
    async mapAsync(f) {
        const arr = [];
        while (true) {
            const x = await this.next();
            if (x.hasValue) {
                arr.push(await f(x.value));
            }
            else {
                break;
            }
        }
        return arr;
    }
    async forEachAsync(f) {
        while (true) {
            const x = await this.next();
            if (x.hasValue) {
                await f(x.value);
            }
            else {
                break;
            }
        }
    }
    async forEach(f) {
        while (true) {
            const x = await this.next();
            if (x.hasValue) {
                f(x.value);
            }
            else {
                break;
            }
        }
    }
    async filter(f) {
        const arr = [];
        while (true) {
            const x = await this.next();
            if (x.hasValue) {
                if (f(x.value)) {
                    arr.push(x.value);
                }
            }
            else {
                break;
            }
        }
        return arr;
    }
    async next() {
        if (this.gotCursorEnd) {
            return { hasValue: false };
        }
        if (!this.awaitingResult) {
            const cursor = this.req.result;
            if (!cursor) {
                throw Error("assertion failed");
            }
            this.awaitingResult = true;
            cursor.continue();
        }
        await this.currentPromise;
        if (this.gotCursorEnd) {
            return { hasValue: false };
        }
        const cursor = this.req.result;
        if (!cursor) {
            throw Error("assertion failed");
        }
        return { hasValue: true, value: cursor.value };
    }
}
/**
 * Return a promise that resolves to the opened IndexedDB database.
 */
function openDatabase(idbFactory, databaseName, databaseVersion, onVersionChange, onUpgradeNeeded) {
    return new Promise((resolve, reject) => {
        const req = idbFactory.open(databaseName, databaseVersion);
        req.onerror = (e) => {
            logger$s.error("database error", e);
            reject(new Error("database error"));
        };
        req.onsuccess = (e) => {
            req.result.onversionchange = (evt) => {
                logger$s.info(`handling live db version change from ${evt.oldVersion} to ${evt.newVersion}`);
                req.result.close();
                onVersionChange();
            };
            resolve(req.result);
        };
        req.onupgradeneeded = (e) => {
            const db = req.result;
            const newVersion = e.newVersion;
            if (!newVersion) {
                throw Error("upgrade needed, but new version unknown");
            }
            const transaction = req.transaction;
            if (!transaction) {
                throw Error("no transaction handle available in upgrade handler");
            }
            onUpgradeNeeded(db, e.oldVersion, newVersion, transaction);
        };
    });
}
function describeContents(options) {
    return {
        keyPath: options.keyPath,
        _dummy: undefined,
        autoIncrement: options.autoIncrement,
        versionAdded: options.versionAdded,
    };
}
function describeIndex(name, keyPath, options = {}) {
    return {
        keyPath,
        name,
        multiEntry: options.multiEntry,
        unique: options.unique,
        versionAdded: options.versionAdded,
    };
}
const storeWithIndexesSymbol = Symbol("StoreWithIndexesMark");
function describeStore(name, s, m) {
    return {
        storeName: name,
        store: s,
        indexMap: m,
        mark: storeWithIndexesSymbol,
    };
}
function runTx(tx, arg, f) {
    const stack = Error("Failed transaction was started here.");
    return new Promise((resolve, reject) => {
        let funResult = undefined;
        let gotFunResult = false;
        let transactionException = undefined;
        tx.oncomplete = () => {
            // This is a fatal error: The transaction completed *before*
            // the transaction function returned.  Likely, the transaction
            // function waited on a promise that is *not* resolved in the
            // microtask queue, thus triggering the auto-commit behavior.
            // Unfortunately, the auto-commit behavior of IDB can't be switched
            // of.  There are some proposals to add this functionality in the future.
            if (!gotFunResult) {
                const msg = "BUG: transaction closed before transaction function returned";
                logger$s.error(msg);
                logger$s.error(`${stack.stack}`);
                reject(Error(msg));
            }
            resolve(funResult);
        };
        tx.onerror = () => {
            logger$s.error("error in transaction");
            logger$s.error(`${stack.stack}`);
        };
        tx.onabort = () => {
            let msg;
            if (tx.error) {
                msg = `Transaction aborted (transaction error): ${tx.error}`;
            }
            else if (transactionException !== undefined) {
                msg = `Transaction aborted (exception thrown): ${transactionException}`;
            }
            else {
                msg = "Transaction aborted (no DB error)";
            }
            logger$s.error(msg);
            reject(new TransactionAbortedError(msg));
        };
        const resP = Promise.resolve().then(() => f(arg, tx));
        resP
            .then((result) => {
            gotFunResult = true;
            funResult = result;
        })
            .catch((e) => {
            if (e == TransactionAbort) {
                logger$s.trace("aborting transaction");
            }
            else {
                transactionException = e;
                console.error("Transaction failed:", e);
                console.error(stack);
                tx.abort();
            }
        })
            .catch((e) => {
            console.error("fatal: aborting transaction failed", e);
        });
    });
}
function makeReadContext(tx, storePick) {
    const ctx = {};
    for (const storeAlias in storePick) {
        const indexes = {};
        const swi = storePick[storeAlias];
        const storeName = swi.storeName;
        for (const indexAlias in storePick[storeAlias].indexMap) {
            const indexDescriptor = storePick[storeAlias].indexMap[indexAlias];
            const indexName = indexDescriptor.name;
            indexes[indexAlias] = {
                get(key) {
                    const req = tx.objectStore(storeName).index(indexName).get(key);
                    return requestToPromise(req);
                },
                iter(query) {
                    const req = tx
                        .objectStore(storeName)
                        .index(indexName)
                        .openCursor(query);
                    return new ResultStream(req);
                },
                getAll(query, count) {
                    const req = tx
                        .objectStore(storeName)
                        .index(indexName)
                        .getAll(query, count);
                    return requestToPromise(req);
                },
            };
        }
        ctx[storeAlias] = {
            indexes,
            get(key) {
                const req = tx.objectStore(storeName).get(key);
                return requestToPromise(req);
            },
            iter(query) {
                const req = tx.objectStore(storeName).openCursor(query);
                return new ResultStream(req);
            },
        };
    }
    return ctx;
}
function makeWriteContext(tx, storePick) {
    const ctx = {};
    for (const storeAlias in storePick) {
        const indexes = {};
        const swi = storePick[storeAlias];
        const storeName = swi.storeName;
        for (const indexAlias in storePick[storeAlias].indexMap) {
            const indexDescriptor = storePick[storeAlias].indexMap[indexAlias];
            const indexName = indexDescriptor.name;
            indexes[indexAlias] = {
                get(key) {
                    const req = tx.objectStore(storeName).index(indexName).get(key);
                    return requestToPromise(req);
                },
                iter(query) {
                    const req = tx
                        .objectStore(storeName)
                        .index(indexName)
                        .openCursor(query);
                    return new ResultStream(req);
                },
                getAll(query, count) {
                    const req = tx
                        .objectStore(storeName)
                        .index(indexName)
                        .getAll(query, count);
                    return requestToPromise(req);
                },
            };
        }
        ctx[storeAlias] = {
            indexes,
            get(key) {
                const req = tx.objectStore(storeName).get(key);
                return requestToPromise(req);
            },
            iter(query) {
                const req = tx.objectStore(storeName).openCursor(query);
                return new ResultStream(req);
            },
            async add(r) {
                const req = tx.objectStore(storeName).add(r);
                const key = await requestToPromise(req);
                return {
                    key: key,
                };
            },
            async put(r) {
                const req = tx.objectStore(storeName).put(r);
                const key = await requestToPromise(req);
                return {
                    key: key,
                };
            },
            delete(k) {
                const req = tx.objectStore(storeName).delete(k);
                return requestToPromise(req);
            },
        };
    }
    return ctx;
}
/**
 * Type-safe access to a database with a particular store map.
 *
 * A store map is the metadata that describes the store.
 */
class DbAccess {
    constructor(db, stores) {
        this.db = db;
        this.stores = stores;
    }
    idbHandle() {
        return this.db;
    }
    /**
     * Run a transaction with all object stores.
     */
    mktxAll() {
        const storeNames = [];
        const accessibleStores = {};
        for (let i = 0; i < this.db.objectStoreNames.length; i++) {
            const sn = this.db.objectStoreNames[i];
            const swi = this.stores[sn];
            if (!swi) {
                throw Error(`store metadata not available (${sn})`);
            }
            storeNames.push(sn);
            accessibleStores[sn] = swi;
        }
        const runReadOnly = (txf) => {
            const tx = this.db.transaction(storeNames, "readonly");
            const readContext = makeReadContext(tx, accessibleStores);
            return runTx(tx, readContext, txf);
        };
        const runReadWrite = (txf) => {
            const tx = this.db.transaction(storeNames, "readwrite");
            const writeContext = makeWriteContext(tx, accessibleStores);
            return runTx(tx, writeContext, txf);
        };
        return {
            runReadOnly,
            runReadWrite,
        };
    }
    /**
     * Run a transaction with selected object stores.
     *
     * The {@link namePicker} must be a function that selects a list of object
     * stores from all available object stores.
     */
    mktx(namePicker) {
        const storeNames = [];
        const accessibleStores = {};
        const storePick = namePicker(this.stores);
        if (typeof storePick !== "object" || storePick === null) {
            throw Error();
        }
        for (const swiPicked of storePick) {
            const swi = swiPicked;
            if (swi.mark !== storeWithIndexesSymbol) {
                throw Error("invalid store descriptor returned from selector function");
            }
            storeNames.push(swi.storeName);
            accessibleStores[swi.storeName] = swi;
        }
        const runReadOnly = (txf) => {
            const tx = this.db.transaction(storeNames, "readonly");
            const readContext = makeReadContext(tx, accessibleStores);
            return runTx(tx, readContext, txf);
        };
        const runReadWrite = (txf) => {
            const tx = this.db.transaction(storeNames, "readwrite");
            const writeContext = makeWriteContext(tx, accessibleStores);
            return runTx(tx, writeContext, txf);
        };
        return {
            runReadOnly,
            runReadWrite,
        };
    }
}

/*
 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/>
 */
const logger$r = new Logger("http.ts");
const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
/**
 * Headers, roughly modeled after the fetch API's headers object.
 */
class Headers {
    constructor() {
        this.headerMap = new Map();
    }
    get(name) {
        const r = this.headerMap.get(name.toLowerCase());
        if (r) {
            return r;
        }
        return null;
    }
    set(name, value) {
        const normalizedName = name.toLowerCase();
        const existing = this.headerMap.get(normalizedName);
        if (existing !== undefined) {
            this.headerMap.set(normalizedName, existing + "," + value);
        }
        else {
            this.headerMap.set(normalizedName, value);
        }
    }
    toJSON() {
        const m = {};
        this.headerMap.forEach((v, k) => (m[k] = v));
        return m;
    }
}
async function readTalerErrorResponse(httpResponse) {
    const errJson = await httpResponse.json();
    const talerErrorCode = errJson.code;
    if (typeof talerErrorCode !== "number") {
        logger$r.warn(`malformed error response (status ${httpResponse.status}): ${j2s(errJson)}`);
        throw TalerError.fromDetail(TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, {
            requestUrl: httpResponse.requestUrl,
            requestMethod: httpResponse.requestMethod,
            httpStatusCode: httpResponse.status,
        }, "Error response did not contain error code");
    }
    return errJson;
}
async function readUnexpectedResponseDetails(httpResponse) {
    const errJson = await httpResponse.json();
    const talerErrorCode = errJson.code;
    if (typeof talerErrorCode !== "number") {
        return makeErrorDetail(TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, {
            requestUrl: httpResponse.requestUrl,
            requestMethod: httpResponse.requestMethod,
            httpStatusCode: httpResponse.status,
        }, "Error response did not contain error code");
    }
    return makeErrorDetail(TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, {
        requestUrl: httpResponse.requestUrl,
        httpStatusCode: httpResponse.status,
        errorResponse: errJson,
    }, `Unexpected HTTP status (${httpResponse.status}) in response`);
}
async function readSuccessResponseJsonOrErrorCode(httpResponse, codec) {
    if (!(httpResponse.status >= 200 && httpResponse.status < 300)) {
        return {
            isError: true,
            talerErrorResponse: await readTalerErrorResponse(httpResponse),
        };
    }
    const respJson = await httpResponse.json();
    let parsedResponse;
    try {
        parsedResponse = codec.decode(respJson);
    }
    catch (e) {
        throw TalerError.fromDetail(TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, {
            requestUrl: httpResponse.requestUrl,
            httpStatusCode: httpResponse.status,
            validationError: e.toString(),
        }, "Response invalid");
    }
    return {
        isError: false,
        response: parsedResponse,
    };
}
function getHttpResponseErrorDetails(httpResponse) {
    return {
        requestUrl: httpResponse.requestUrl,
        httpStatusCode: httpResponse.status,
    };
}
function throwUnexpectedRequestError(httpResponse, talerErrorResponse) {
    throw TalerError.fromDetail(TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, {
        requestUrl: httpResponse.requestUrl,
        httpStatusCode: httpResponse.status,
        errorResponse: talerErrorResponse,
    }, `Unexpected HTTP status ${httpResponse.status} in response`);
}
async function readSuccessResponseJsonOrThrow(httpResponse, codec) {
    const r = await readSuccessResponseJsonOrErrorCode(httpResponse, codec);
    if (!r.isError) {
        return r.response;
    }
    throwUnexpectedRequestError(httpResponse, r.talerErrorResponse);
}
async function readSuccessResponseTextOrErrorCode(httpResponse) {
    if (!(httpResponse.status >= 200 && httpResponse.status < 300)) {
        const errJson = await httpResponse.json();
        const talerErrorCode = errJson.code;
        if (typeof talerErrorCode !== "number") {
            throw TalerError.fromDetail(TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, {
                httpStatusCode: httpResponse.status,
                requestUrl: httpResponse.requestUrl,
                requestMethod: httpResponse.requestMethod,
            }, "Error response did not contain error code");
        }
        return {
            isError: true,
            talerErrorResponse: errJson,
        };
    }
    const respJson = await httpResponse.text();
    return {
        isError: false,
        response: respJson,
    };
}
async function checkSuccessResponseOrThrow(httpResponse) {
    if (!(httpResponse.status >= 200 && httpResponse.status < 300)) {
        const errJson = await httpResponse.json();
        const talerErrorCode = errJson.code;
        if (typeof talerErrorCode !== "number") {
            throw TalerError.fromDetail(TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, {
                httpStatusCode: httpResponse.status,
                requestUrl: httpResponse.requestUrl,
                requestMethod: httpResponse.requestMethod,
            }, "Error response did not contain error code");
        }
        throwUnexpectedRequestError(httpResponse, errJson);
    }
}
async function readSuccessResponseTextOrThrow(httpResponse) {
    const r = await readSuccessResponseTextOrErrorCode(httpResponse);
    if (!r.isError) {
        return r.response;
    }
    throwUnexpectedRequestError(httpResponse, r.talerErrorResponse);
}
/**
 * Get the timestamp at which the response's content is considered expired.
 */
function getExpiry(httpResponse, opt) {
    var _a;
    const expiryDateMs = new Date((_a = httpResponse.headers.get("expiry")) !== null && _a !== void 0 ? _a : "").getTime();
    let t;
    if (Number.isNaN(expiryDateMs)) {
        t = AbsoluteTime.now();
    }
    else {
        t = {
            t_ms: expiryDateMs,
        };
    }
    if (opt.minDuration) {
        const t2 = AbsoluteTime.addDuration(AbsoluteTime.now(), opt.minDuration);
        return AbsoluteTime.max(t, t2);
    }
    return t;
}

/*
 This file is part of GNU Taler
 (C) 2019 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * Protocol version spoken with the exchange.
 *
 * Uses libtool's current:revision:age versioning.
 */
const WALLET_EXCHANGE_PROTOCOL_VERSION = "12:0:0";
/**
 * Protocol version spoken with the merchant.
 *
 * Uses libtool's current:revision:age versioning.
 */
const WALLET_MERCHANT_PROTOCOL_VERSION = "2:0:1";
/**
 * Protocol version spoken with the merchant.
 *
 * Uses libtool's current:revision:age versioning.
 */
const WALLET_BANK_INTEGRATION_PROTOCOL_VERSION = "0:0:0";

/*
 This file is part of GNU Taler
 (C) 2021-2022 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * This file contains the database schema of the Taler wallet together
 * with some helper functions.
 *
 * Some design considerations:
 * - By convention, each object store must have a corresponding "<Name>Record"
 *   interface defined for it.
 * - For records that represent operations, there should be exactly
 *   one top-level enum field that indicates the status of the operation.
 *   This field should be present even if redundant, because the field
 *   will have an index.
 * - Amounts are stored as strings, except when they are needed for
 *   indexing.
 * - Every record that has a corresponding transaction item must have
 *   an index for a mandatory timestamp field.
 * - Optional fields should be avoided, use "T | undefined" instead.
 * - Do all records have some obvious, indexed field that can
 *   be used for range queries?
 *
 * @author Florian Dold <dold@taler.net>
 */
/**
 * Name of the Taler database.  This is effectively the major
 * version of the DB schema. Whenever it changes, custom import logic
 * for all previous versions must be written, which should be
 * avoided.
 */
const TALER_DB_NAME = "taler-wallet-main-v6";
/**
 * Name of the metadata database.  This database is used
 * to track major migrations of the main Taler database.
 *
 * (Minor migrations are handled via upgrade transactions.)
 */
const TALER_META_DB_NAME = "taler-wallet-meta";
const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
/**
 * Current database minor version, should be incremented
 * each time we do minor schema changes on the database.
 * A change is considered minor when fields are added in a
 * backwards-compatible way or object stores and indices
 * are added.
 */
const WALLET_DB_MINOR_VERSION = 2;
var OperationStatusRange;
(function (OperationStatusRange) {
    OperationStatusRange.ACTIVE_START = 10;
    OperationStatusRange.ACTIVE_END = 29;
    OperationStatusRange.DORMANT_START = 50;
    OperationStatusRange.DORMANT_END = 69;
})(OperationStatusRange || (OperationStatusRange = {}));
/**
 * Status of a withdrawal.
 */
var WithdrawalGroupStatus;
(function (WithdrawalGroupStatus) {
    /**
     * Reserve must be registered with the bank.
     */
    WithdrawalGroupStatus[WithdrawalGroupStatus["RegisteringBank"] = OperationStatusRange.ACTIVE_START] = "RegisteringBank";
    /**
     * We've registered reserve's information with the bank
     * and are now waiting for the user to confirm the withdraw
     * with the bank (typically 2nd factor auth).
     */
    WithdrawalGroupStatus[WithdrawalGroupStatus["WaitConfirmBank"] = OperationStatusRange.ACTIVE_START + 1] = "WaitConfirmBank";
    /**
     * Querying reserve status with the exchange.
     */
    WithdrawalGroupStatus[WithdrawalGroupStatus["QueryingStatus"] = OperationStatusRange.ACTIVE_START + 2] = "QueryingStatus";
    /**
     * Ready for withdrawal.
     */
    WithdrawalGroupStatus[WithdrawalGroupStatus["Ready"] = OperationStatusRange.ACTIVE_START + 3] = "Ready";
    /**
     * The corresponding withdraw record has been created.
     * No further processing is done, unless explicitly requested
     * by the user.
     */
    WithdrawalGroupStatus[WithdrawalGroupStatus["Finished"] = OperationStatusRange.DORMANT_START] = "Finished";
    /**
     * The bank aborted the withdrawal.
     */
    WithdrawalGroupStatus[WithdrawalGroupStatus["BankAborted"] = OperationStatusRange.DORMANT_START + 1] = "BankAborted";
})(WithdrawalGroupStatus || (WithdrawalGroupStatus = {}));
/**
 * Status of a denomination.
 */
var DenominationVerificationStatus;
(function (DenominationVerificationStatus) {
    /**
     * Verification was delayed.
     */
    DenominationVerificationStatus[DenominationVerificationStatus["Unverified"] = OperationStatusRange.ACTIVE_START] = "Unverified";
    /**
     * Verified as valid.
     */
    DenominationVerificationStatus[DenominationVerificationStatus["VerifiedGood"] = OperationStatusRange.DORMANT_START] = "VerifiedGood";
    /**
     * Verified as invalid.
     */
    DenominationVerificationStatus[DenominationVerificationStatus["VerifiedBad"] = OperationStatusRange.DORMANT_START + 1] = "VerifiedBad";
})(DenominationVerificationStatus || (DenominationVerificationStatus = {}));
var DenominationRecord;
(function (DenominationRecord) {
    function getValue(d) {
        return {
            currency: d.currency,
            fraction: d.amountFrac,
            value: d.amountVal,
        };
    }
    DenominationRecord.getValue = getValue;
    function toDenomInfo(d) {
        return {
            denomPub: d.denomPub,
            denomPubHash: d.denomPubHash,
            feeDeposit: d.fees.feeDeposit,
            feeRefresh: d.fees.feeRefresh,
            feeRefund: d.fees.feeRefund,
            feeWithdraw: d.fees.feeWithdraw,
            stampExpireDeposit: d.stampExpireDeposit,
            stampExpireLegal: d.stampExpireLegal,
            stampExpireWithdraw: d.stampExpireWithdraw,
            stampStart: d.stampStart,
            value: DenominationRecord.getValue(d),
            exchangeBaseUrl: d.exchangeBaseUrl,
        };
    }
    DenominationRecord.toDenomInfo = toDenomInfo;
})(DenominationRecord || (DenominationRecord = {}));
/**
 * Status of a coin.
 */
var CoinStatus;
(function (CoinStatus) {
    /**
     * Withdrawn and never shown to anybody.
     */
    CoinStatus["Fresh"] = "fresh";
    /**
     * Fresh, but currently marked as "suspended", thus won't be used
     * for spending.  Used for testing.
     */
    CoinStatus["FreshSuspended"] = "fresh-suspended";
    /**
     * A coin that has been spent and refreshed.
     */
    CoinStatus["Dormant"] = "dormant";
})(CoinStatus || (CoinStatus = {}));
var CoinSourceType;
(function (CoinSourceType) {
    CoinSourceType["Withdraw"] = "withdraw";
    CoinSourceType["Refresh"] = "refresh";
    CoinSourceType["Tip"] = "tip";
})(CoinSourceType || (CoinSourceType = {}));
var ProposalStatus;
(function (ProposalStatus) {
    /**
     * Not downloaded yet.
     */
    ProposalStatus["Downloading"] = "downloading";
    /**
     * Proposal downloaded, but the user needs to accept/reject it.
     */
    ProposalStatus["Proposed"] = "proposed";
    /**
     * The user has accepted the proposal.
     */
    ProposalStatus["Accepted"] = "accepted";
    /**
     * The user has rejected the proposal.
     */
    ProposalStatus["Refused"] = "refused";
    /**
     * Downloading or processing the proposal has failed permanently.
     */
    ProposalStatus["PermanentlyFailed"] = "permanently-failed";
    /**
     * Downloaded proposal was detected as a re-purchase.
     */
    ProposalStatus["Repurchase"] = "repurchase";
})(ProposalStatus || (ProposalStatus = {}));
var RefreshCoinStatus;
(function (RefreshCoinStatus) {
    RefreshCoinStatus[RefreshCoinStatus["Pending"] = OperationStatusRange.ACTIVE_START] = "Pending";
    RefreshCoinStatus[RefreshCoinStatus["Finished"] = OperationStatusRange.DORMANT_START] = "Finished";
    /**
     * The refresh for this coin has been frozen, because of a permanent error.
     * More info in lastErrorPerCoin.
     */
    RefreshCoinStatus[RefreshCoinStatus["Frozen"] = OperationStatusRange.DORMANT_START + 1] = "Frozen";
})(RefreshCoinStatus || (RefreshCoinStatus = {}));
var OperationStatus;
(function (OperationStatus) {
    OperationStatus[OperationStatus["Finished"] = OperationStatusRange.DORMANT_START] = "Finished";
    OperationStatus[OperationStatus["Pending"] = OperationStatusRange.ACTIVE_START] = "Pending";
})(OperationStatus || (OperationStatus = {}));
var RefundState;
(function (RefundState) {
    RefundState["Failed"] = "failed";
    RefundState["Applied"] = "applied";
    RefundState["Pending"] = "pending";
})(RefundState || (RefundState = {}));
var RefundReason;
(function (RefundReason) {
    /**
     * Normal refund given by the merchant.
     */
    RefundReason["NormalRefund"] = "normal-refund";
    /**
     * Refund from an aborted payment.
     */
    RefundReason["AbortRefund"] = "abort-pay-refund";
})(RefundReason || (RefundReason = {}));
var AbortStatus;
(function (AbortStatus) {
    AbortStatus["None"] = "none";
    AbortStatus["AbortRefund"] = "abort-refund";
    AbortStatus["AbortFinished"] = "abort-finished";
})(AbortStatus || (AbortStatus = {}));
const WALLET_BACKUP_STATE_KEY = "walletBackupState";
var BackupProviderStateTag;
(function (BackupProviderStateTag) {
    BackupProviderStateTag["Provisional"] = "provisional";
    BackupProviderStateTag["Ready"] = "ready";
    BackupProviderStateTag["Retrying"] = "retrying";
})(BackupProviderStateTag || (BackupProviderStateTag = {}));
/**
 * Schema definition for the IndexedDB
 * wallet database.
 */
const WalletStoresV1 = {
    coinAvailability: describeStore("coinAvailability", describeContents({
        keyPath: ["exchangeBaseUrl", "denomPubHash", "maxAge"],
    }), {
        byExchangeAgeAvailability: describeIndex("byExchangeAgeAvailability", [
            "exchangeBaseUrl",
            "maxAge",
            "freshCoinCount",
        ]),
    }),
    coins: describeStore("coins", describeContents({
        keyPath: "coinPub",
    }), {
        byBaseUrl: describeIndex("byBaseUrl", "exchangeBaseUrl"),
        byDenomPubHash: describeIndex("byDenomPubHash", "denomPubHash"),
        byExchangeDenomPubHashAndAgeAndStatus: describeIndex("byExchangeDenomPubHashAndAgeAndStatus", ["exchangeBaseUrl", "denomPubHash", "maxAge", "status"]),
        byCoinEvHash: describeIndex("byCoinEvHash", "coinEvHash"),
    }),
    reserves: describeStore("reserves", describeContents({
        keyPath: "rowId",
        autoIncrement: true,
    }), {
        byReservePub: describeIndex("byReservePub", "reservePub", {}),
    }),
    config: describeStore("config", describeContents({ keyPath: "key" }), {}),
    auditorTrust: describeStore("auditorTrust", describeContents({
        keyPath: ["currency", "auditorBaseUrl"],
    }), {
        byAuditorPub: describeIndex("byAuditorPub", "auditorPub"),
        byUid: describeIndex("byUid", "uids", {
            multiEntry: true,
        }),
    }),
    exchangeTrust: describeStore("exchangeTrust", describeContents({
        keyPath: ["currency", "exchangeBaseUrl"],
    }), {
        byExchangeMasterPub: describeIndex("byExchangeMasterPub", "exchangeMasterPub"),
    }),
    denominations: describeStore("denominations", describeContents({
        keyPath: ["exchangeBaseUrl", "denomPubHash"],
    }), {
        byExchangeBaseUrl: describeIndex("byExchangeBaseUrl", "exchangeBaseUrl"),
    }),
    exchanges: describeStore("exchanges", describeContents({
        keyPath: "baseUrl",
    }), {}),
    exchangeDetails: describeStore("exchangeDetails", describeContents({
        keyPath: ["exchangeBaseUrl", "currency", "masterPublicKey"],
    }), {}),
    proposals: describeStore("proposals", describeContents({ keyPath: "proposalId" }), {
        byUrlAndOrderId: describeIndex("byUrlAndOrderId", [
            "merchantBaseUrl",
            "orderId",
        ]),
    }),
    refreshGroups: describeStore("refreshGroups", describeContents({
        keyPath: "refreshGroupId",
    }), {
        byStatus: describeIndex("byStatus", "operationStatus"),
    }),
    recoupGroups: describeStore("recoupGroups", describeContents({
        keyPath: "recoupGroupId",
    }), {}),
    purchases: describeStore("purchases", describeContents({ keyPath: "proposalId" }), {
        byFulfillmentUrl: describeIndex("byFulfillmentUrl", "download.contractData.fulfillmentUrl"),
        byMerchantUrlAndOrderId: describeIndex("byMerchantUrlAndOrderId", [
            "download.contractData.merchantBaseUrl",
            "download.contractData.orderId",
        ]),
    }),
    tips: describeStore("tips", describeContents({ keyPath: "walletTipId" }), {
        byMerchantTipIdAndBaseUrl: describeIndex("byMerchantTipIdAndBaseUrl", [
            "merchantTipId",
            "merchantBaseUrl",
        ]),
    }),
    withdrawalGroups: describeStore("withdrawalGroups", describeContents({
        keyPath: "withdrawalGroupId",
    }), {
        byStatus: describeIndex("byStatus", "status"),
        byTalerWithdrawUri: describeIndex("byTalerWithdrawUri", "wgInfo.bankInfo.talerWithdrawUri"),
    }),
    planchets: describeStore("planchets", describeContents({ keyPath: "coinPub" }), {
        byGroupAndIndex: describeIndex("byGroupAndIndex", [
            "withdrawalGroupId",
            "coinIdx",
        ]),
        byGroup: describeIndex("byGroup", "withdrawalGroupId"),
        byCoinEvHash: describeIndex("byCoinEv", "coinEvHash"),
    }),
    bankWithdrawUris: describeStore("bankWithdrawUris", describeContents({
        keyPath: "talerWithdrawUri",
    }), {}),
    backupProviders: describeStore("backupProviders", describeContents({
        keyPath: "baseUrl",
    }), {
        byPaymentProposalId: describeIndex("byPaymentProposalId", "paymentProposalIds", {
            multiEntry: true,
        }),
    }),
    depositGroups: describeStore("depositGroups", describeContents({
        keyPath: "depositGroupId",
    }), {
        byStatus: describeIndex("byStatus", "operationStatus"),
    }),
    tombstones: describeStore("tombstones", describeContents({ keyPath: "id" }), {}),
    operationRetries: describeStore("operationRetries", describeContents({
        keyPath: "id",
    }), {}),
    ghostDepositGroups: describeStore("ghostDepositGroups", describeContents({
        keyPath: "contractTermsHash",
    }), {}),
    balancesPerCurrency: describeStore("balancesPerCurrency", describeContents({
        keyPath: "currency",
    }), {}),
    peerPushPaymentIncoming: describeStore("peerPushPaymentIncoming", describeContents({
        keyPath: "peerPushPaymentIncomingId",
    }), {
        byExchangeAndPurse: describeIndex("byExchangeAndPurse", [
            "exchangeBaseUrl",
            "pursePub",
        ]),
    }),
    peerPullPaymentIncoming: describeStore("peerPullPaymentIncoming", describeContents({
        keyPath: "peerPullPaymentIncomingId",
    }), {
        byExchangeAndPurse: describeIndex("byExchangeAndPurse", [
            "exchangeBaseUrl",
            "pursePub",
        ]),
    }),
    peerPullPaymentInitiations: describeStore("peerPullPaymentInitiations", describeContents({
        keyPath: "pursePub",
    }), {}),
    peerPushPaymentInitiations: describeStore("peerPushPaymentInitiations", describeContents({
        keyPath: "pursePub",
    }), {}),
    bankAccounts: describeStore("bankAccounts", describeContents({
        keyPath: "uri",
        versionAdded: 2,
    }), {}),
};
const walletMetadataStore = {
    metaConfig: describeStore("metaConfig", describeContents({ keyPath: "key" }), {}),
};
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();
                }
            });
        }
    });
}
async function recoverFromDump(db, dump) {
    return new Promise((resolve, reject) => {
        const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
        tx.addEventListener("complete", () => {
            resolve();
        });
        for (let i = 0; i < db.objectStoreNames.length; i++) {
            const name = db.objectStoreNames[i];
            const storeDump = dump.stores[name];
            if (!storeDump)
                continue;
            Object.keys(storeDump).forEach(async (key) => {
                const value = storeDump[key];
                if (!value)
                    return;
                tx.objectStore(name).put(value);
            });
        }
        tx.commit();
    });
}
async function importDb(db, object) {
    if ("name" in object && "stores" in object && "version" in object) {
        // looks like a database dump
        const dump = object;
        return recoverFromDump(db, dump);
    }
    if ("databases" in object && "$types" in object) {
        // looks like a IDBDatabase
        const someDatabase = object.databases;
        if (TALER_META_DB_NAME in someDatabase) {
            //looks like a taler database
            const currentMainDbValue = someDatabase[TALER_META_DB_NAME].objectStores.metaConfig.records[0]
                .value.value;
            if (currentMainDbValue !== TALER_DB_NAME) {
                console.log("not the current database version");
            }
            const talerDb = someDatabase[currentMainDbValue];
            const objectStoreNames = Object.keys(talerDb.objectStores);
            const dump = {
                name: talerDb.schema.databaseName,
                version: talerDb.schema.databaseVersion,
                stores: {},
            };
            for (let i = 0; i < objectStoreNames.length; i++) {
                const name = objectStoreNames[i];
                const storeDump = {};
                dump.stores[name] = storeDump;
                talerDb.objectStores[name].records.map((r) => {
                    const pkey = r.primaryKey;
                    const key = typeof pkey === "string" ? pkey : pkey.join(",");
                    storeDump[key] = r.value;
                });
            }
            return recoverFromDump(db, dump);
        }
    }
    throw Error("could not import database");
}

/*
 This file is part of GNU Taler
 (C) 2021 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$q = new Logger("db-utils.ts");
function upgradeFromStoreMap(storeMap, db, oldVersion, newVersion, upgradeTransaction) {
    var _a, _b;
    if (oldVersion === 0) {
        for (const n in storeMap) {
            const swi = storeMap[n];
            const storeDesc = swi.store;
            const s = db.createObjectStore(swi.storeName, {
                autoIncrement: storeDesc.autoIncrement,
                keyPath: storeDesc.keyPath,
            });
            for (const indexName in swi.indexMap) {
                const indexDesc = swi.indexMap[indexName];
                s.createIndex(indexDesc.name, indexDesc.keyPath, {
                    multiEntry: indexDesc.multiEntry,
                    unique: indexDesc.unique,
                });
            }
        }
        return;
    }
    if (oldVersion === newVersion) {
        return;
    }
    logger$q.info(`upgrading database from ${oldVersion} to ${newVersion}`);
    for (const n in storeMap) {
        const swi = storeMap[n];
        const storeDesc = swi.store;
        const storeAddedVersion = (_a = storeDesc.versionAdded) !== null && _a !== void 0 ? _a : 0;
        if (storeAddedVersion <= oldVersion) {
            continue;
        }
        const s = db.createObjectStore(swi.storeName, {
            autoIncrement: storeDesc.autoIncrement,
            keyPath: storeDesc.keyPath,
        });
        for (const indexName in swi.indexMap) {
            const indexDesc = swi.indexMap[indexName];
            const indexAddedVersion = (_b = indexDesc.versionAdded) !== null && _b !== void 0 ? _b : 0;
            if (indexAddedVersion <= oldVersion) {
                continue;
            }
            s.createIndex(indexDesc.name, indexDesc.keyPath, {
                multiEntry: indexDesc.multiEntry,
                unique: indexDesc.unique,
            });
        }
    }
}
function promiseFromTransaction(transaction) {
    return new Promise((resolve, reject) => {
        transaction.oncomplete = () => {
            resolve();
        };
        transaction.onerror = () => {
            reject();
        };
    });
}
/**
 * Purge all data in the given database.
 */
function clearDatabase(db) {
    // db.objectStoreNames is a DOMStringList, so we need to convert
    let stores = [];
    for (let i = 0; i < db.objectStoreNames.length; i++) {
        stores.push(db.objectStoreNames[i]);
    }
    const tx = db.transaction(stores, "readwrite");
    for (const store of stores) {
        tx.objectStore(store).clear();
    }
    return promiseFromTransaction(tx);
}
function onTalerDbUpgradeNeeded(db, oldVersion, newVersion, upgradeTransaction) {
    upgradeFromStoreMap(WalletStoresV1, db, oldVersion, newVersion);
}
function onMetaDbUpgradeNeeded(db, oldVersion, newVersion, upgradeTransaction) {
    upgradeFromStoreMap(walletMetadataStore, db, oldVersion, newVersion);
}
/**
 * Return a promise that resolves
 * to the taler wallet db.
 */
async function openTalerDatabase(idbFactory, onVersionChange) {
    const metaDbHandle = await openDatabase(idbFactory, TALER_META_DB_NAME, 1, () => { }, onMetaDbUpgradeNeeded);
    const metaDb = new DbAccess(metaDbHandle, walletMetadataStore);
    let currentMainVersion;
    await metaDb
        .mktx((stores) => [stores.metaConfig])
        .runReadWrite(async (tx) => {
        const dbVersionRecord = await tx.metaConfig.get(CURRENT_DB_CONFIG_KEY);
        if (!dbVersionRecord) {
            currentMainVersion = TALER_DB_NAME;
            await tx.metaConfig.put({
                key: CURRENT_DB_CONFIG_KEY,
                value: TALER_DB_NAME,
            });
        }
        else {
            currentMainVersion = dbVersionRecord.value;
        }
    });
    if (currentMainVersion !== TALER_DB_NAME) {
        switch (currentMainVersion) {
            case "taler-wallet-main-v2":
            case "taler-wallet-main-v3":
            case "taler-wallet-main-v4": // temporary, we might migrate v4 later
            case "taler-wallet-main-v5":
                // We consider this a pre-release
                // development version, no migration is done.
                await metaDb
                    .mktx((stores) => [stores.metaConfig])
                    .runReadWrite(async (tx) => {
                    await tx.metaConfig.put({
                        key: CURRENT_DB_CONFIG_KEY,
                        value: TALER_DB_NAME,
                    });
                });
                break;
            default:
                throw Error(`migration from database ${currentMainVersion} not supported`);
        }
    }
    const mainDbHandle = await openDatabase(idbFactory, TALER_DB_NAME, WALLET_DB_MINOR_VERSION, onVersionChange, onTalerDbUpgradeNeeded);
    return new DbAccess(mainDbHandle, WalletStoresV1);
}

/*
 This file is part of GNU Taler
 (C) 2017-2019 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$p = new Logger("timer.ts");
class IntervalHandle {
    constructor(h) {
        this.h = h;
    }
    clear() {
        clearInterval(this.h);
    }
    /**
     * Make sure the event loop exits when the timer is the
     * only event left.  Has no effect in the browser.
     */
    unref() {
        if (typeof this.h === "object") {
            this.h.unref();
        }
    }
}
class TimeoutHandle {
    constructor(h) {
        this.h = h;
    }
    clear() {
        clearTimeout(this.h);
    }
    /**
     * Make sure the event loop exits when the timer is the
     * only event left.  Has no effect in the browser.
     */
    unref() {
        if (typeof this.h === "object") {
            this.h.unref();
        }
    }
}
/**
 * Get a performance counter in nanoseconds.
 */
const performanceNow = (() => {
    // @ts-ignore
    if (typeof process !== "undefined" && process.hrtime) {
        return () => {
            return process.hrtime.bigint();
        };
    }
    // @ts-ignore
    if (typeof performance !== "undefined") {
        // @ts-ignore
        return () => BigInt(Math.floor(performance.now() * 1000)) * BigInt(1000);
    }
    return () => BigInt(0);
})();
const nullTimerHandle = {
    clear() {
        // do nothing
        return;
    },
    unref() {
        // do nothing
        return;
    },
};
class SetTimeoutTimerAPI {
    /**
     * Call a function every time the delay given in milliseconds passes.
     */
    every(delayMs, callback) {
        return new IntervalHandle(setInterval(callback, delayMs));
    }
    /**
     * Call a function after the delay given in milliseconds passes.
     */
    after(delayMs, callback) {
        return new TimeoutHandle(setTimeout(callback, delayMs));
    }
}
const timer = new SetTimeoutTimerAPI();
/**
 * Implementation of [[TimerGroup]] using setTimeout
 */
class TimerGroup {
    constructor(timerApi) {
        this.timerApi = timerApi;
        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];
        }
    }
    resolveAfter(delayMs) {
        return new Promise((resolve, reject) => {
            if (delayMs.d_ms !== "forever") {
                this.after(delayMs.d_ms, () => {
                    resolve();
                });
            }
        });
    }
    after(delayMs, callback) {
        if (this.stopped) {
            logger$p.warn("dropping timer since timer group is stopped");
            return nullTimerHandle;
        }
        const h = this.timerApi.after(delayMs, callback);
        const myId = this.idGen++;
        this.timerMap[myId] = h;
        const tm = this.timerMap;
        return {
            clear() {
                h.clear();
                delete tm[myId];
            },
            unref() {
                h.unref();
            },
        };
    }
    every(delayMs, callback) {
        if (this.stopped) {
            logger$p.warn("dropping timer since timer group is stopped");
            return nullTimerHandle;
        }
        const h = this.timerApi.every(delayMs, callback);
        const myId = this.idGen++;
        this.timerMap[myId] = h;
        const tm = this.timerMap;
        return {
            clear() {
                h.clear();
                delete tm[myId];
            },
            unref() {
                h.unref();
            },
        };
    }
}

/*
 This file is part of GNU Taler
 (C) 2019-2020 Taler Systems SA

 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/>
 */
const logger$o = new Logger("cryptoImplementation.ts");
/**
 * Implementation of the Taler crypto interface where every function
 * always throws.  Only useful in practice as a way to iterate through
 * all possible crypto functions.
 *
 * (This list can be easily auto-generated by your favorite IDE).
 */
const nullCrypto = {
    createPlanchet: function (req) {
        throw new Error("Function not implemented.");
    },
    eddsaSign: function (req) {
        throw new Error("Function not implemented.");
    },
    createTipPlanchet: function (req) {
        throw new Error("Function not implemented.");
    },
    signTrackTransaction: function (req) {
        throw new Error("Function not implemented.");
    },
    createRecoupRequest: function (req) {
        throw new Error("Function not implemented.");
    },
    createRecoupRefreshRequest: function (req) {
        throw new Error("Function not implemented.");
    },
    isValidPaymentSignature: function (req) {
        throw new Error("Function not implemented.");
    },
    isValidWireFee: function (req) {
        throw new Error("Function not implemented.");
    },
    isValidDenom: function (req) {
        throw new Error("Function not implemented.");
    },
    isValidWireAccount: function (req) {
        throw new Error("Function not implemented.");
    },
    isValidContractTermsSignature: function (req) {
        throw new Error("Function not implemented.");
    },
    createEddsaKeypair: function (req) {
        throw new Error("Function not implemented.");
    },
    eddsaGetPublic: function (req) {
        throw new Error("Function not implemented.");
    },
    unblindDenominationSignature: function (req) {
        throw new Error("Function not implemented.");
    },
    rsaUnblind: function (req) {
        throw new Error("Function not implemented.");
    },
    rsaVerify: function (req) {
        throw new Error("Function not implemented.");
    },
    signDepositPermission: function (depositInfo) {
        throw new Error("Function not implemented.");
    },
    deriveRefreshSession: function (req) {
        throw new Error("Function not implemented.");
    },
    hashString: function (req) {
        throw new Error("Function not implemented.");
    },
    signCoinLink: function (req) {
        throw new Error("Function not implemented.");
    },
    makeSyncSignature: function (req) {
        throw new Error("Function not implemented.");
    },
    setupRefreshPlanchet: function (req) {
        throw new Error("Function not implemented.");
    },
    rsaBlind: function (req) {
        throw new Error("Function not implemented.");
    },
    keyExchangeEcdheEddsa: function (req) {
        throw new Error("Function not implemented.");
    },
    setupWithdrawalPlanchet: function (req) {
        throw new Error("Function not implemented.");
    },
    ecdheGetPublic: function (req) {
        throw new Error("Function not implemented.");
    },
    setupRefreshTransferPub: function (req) {
        throw new Error("Function not implemented.");
    },
    signPurseCreation: function (req) {
        throw new Error("Function not implemented.");
    },
    signPurseDeposits: function (req) {
        throw new Error("Function not implemented.");
    },
    encryptContractForMerge: function (req) {
        throw new Error("Function not implemented.");
    },
    decryptContractForMerge: function (req) {
        throw new Error("Function not implemented.");
    },
    signPurseMerge: function (req) {
        throw new Error("Function not implemented.");
    },
    encryptContractForDeposit: function (req) {
        throw new Error("Function not implemented.");
    },
    decryptContractForDeposit: function (req) {
        throw new Error("Function not implemented.");
    },
    signReservePurseCreate: function (req) {
        throw new Error("Function not implemented.");
    },
};
/**
 * JS-native implementation of the Taler crypto worker operations.
 */
const nativeCryptoR = {
    async eddsaSign(tci, req) {
        return {
            sig: encodeCrock(eddsaSign(decodeCrock(req.msg), decodeCrock(req.priv))),
        };
    },
    async rsaBlind(tci, req) {
        const res = rsaBlind(decodeCrock(req.hm), decodeCrock(req.bks), decodeCrock(req.pub));
        return {
            blinded: encodeCrock(res),
        };
    },
    async setupRefreshPlanchet(tci, req) {
        const transferSecret = decodeCrock(req.transferSecret);
        const coinNumber = req.coinNumber;
        // See TALER_transfer_secret_to_planchet_secret in C impl
        const planchetMasterSecret = kdfKw({
            ikm: transferSecret,
            outputLength: 32,
            salt: bufferForUint32(coinNumber),
            info: stringToBytes("taler-coin-derivation"),
        });
        const coinPriv = kdfKw({
            ikm: planchetMasterSecret,
            outputLength: 32,
            salt: stringToBytes("coin"),
        });
        const bks = kdfKw({
            ikm: planchetMasterSecret,
            outputLength: 32,
            salt: stringToBytes("bks"),
        });
        const coinPrivEnc = encodeCrock(coinPriv);
        const coinPubRes = await tci.eddsaGetPublic(tci, {
            priv: coinPrivEnc,
        });
        return {
            bks: encodeCrock(bks),
            coinPriv: coinPrivEnc,
            coinPub: coinPubRes.pub,
        };
    },
    async setupWithdrawalPlanchet(tci, req) {
        const info = stringToBytes("taler-withdrawal-coin-derivation");
        const saltArrBuf = new ArrayBuffer(4);
        const salt = new Uint8Array(saltArrBuf);
        const saltDataView = new DataView(saltArrBuf);
        saltDataView.setUint32(0, req.coinNumber);
        const out = kdf(64, decodeCrock(req.secretSeed), salt, info);
        const coinPriv = out.slice(0, 32);
        const bks = out.slice(32, 64);
        const coinPrivEnc = encodeCrock(coinPriv);
        const coinPubRes = await tci.eddsaGetPublic(tci, {
            priv: coinPrivEnc,
        });
        return {
            bks: encodeCrock(bks),
            coinPriv: coinPrivEnc,
            coinPub: coinPubRes.pub,
        };
    },
    async createPlanchet(tci, req) {
        const denomPub = req.denomPub;
        if (denomPub.cipher === DenomKeyType.Rsa) {
            const reservePub = decodeCrock(req.reservePub);
            const derivedPlanchet = await tci.setupWithdrawalPlanchet(tci, {
                coinNumber: req.coinIndex,
                secretSeed: req.secretSeed,
            });
            let maybeAcp = undefined;
            let maybeAgeCommitmentHash = undefined;
            if (denomPub.age_mask) {
                const age = req.restrictAge || AgeRestriction.AGE_UNRESTRICTED;
                logger$o.info(`creating age-restricted planchet (age ${age})`);
                maybeAcp = await AgeRestriction.restrictionCommit(denomPub.age_mask, age);
                maybeAgeCommitmentHash = AgeRestriction.hashCommitment(maybeAcp.commitment);
            }
            const coinPubHash = hashCoinPub(derivedPlanchet.coinPub, maybeAgeCommitmentHash);
            const blindResp = await tci.rsaBlind(tci, {
                bks: derivedPlanchet.bks,
                hm: encodeCrock(coinPubHash),
                pub: denomPub.rsa_public_key,
            });
            const coinEv = {
                cipher: DenomKeyType.Rsa,
                rsa_blinded_planchet: blindResp.blinded,
            };
            const amountWithFee = Amounts.add(req.value, req.feeWithdraw).amount;
            const denomPubHash = hashDenomPub(req.denomPub);
            const evHash = hashCoinEv(coinEv, encodeCrock(denomPubHash));
            const withdrawRequest = buildSigPS(TalerSignaturePurpose.WALLET_RESERVE_WITHDRAW)
                .put(amountToBuffer(amountWithFee))
                .put(denomPubHash)
                .put(evHash)
                .build();
            const sigResult = await tci.eddsaSign(tci, {
                msg: encodeCrock(withdrawRequest),
                priv: req.reservePriv,
            });
            const planchet = {
                blindingKey: derivedPlanchet.bks,
                coinEv,
                coinPriv: derivedPlanchet.coinPriv,
                coinPub: derivedPlanchet.coinPub,
                coinValue: req.value,
                denomPub,
                denomPubHash: encodeCrock(denomPubHash),
                reservePub: encodeCrock(reservePub),
                withdrawSig: sigResult.sig,
                coinEvHash: encodeCrock(evHash),
                ageCommitmentProof: maybeAcp,
            };
            return planchet;
        }
        else {
            throw Error("unsupported cipher, unable to create planchet");
        }
    },
    async createTipPlanchet(tci, req) {
        if (req.denomPub.cipher !== DenomKeyType.Rsa) {
            throw Error(`unsupported cipher (${req.denomPub.cipher})`);
        }
        const fc = await setupTipPlanchet(decodeCrock(req.secretSeed), req.denomPub, req.planchetIndex);
        const maybeAch = fc.ageCommitmentProof
            ? AgeRestriction.hashCommitment(fc.ageCommitmentProof.commitment)
            : undefined;
        const denomPub = decodeCrock(req.denomPub.rsa_public_key);
        const coinPubHash = hashCoinPub(encodeCrock(fc.coinPub), maybeAch);
        const blindResp = await tci.rsaBlind(tci, {
            bks: encodeCrock(fc.bks),
            hm: encodeCrock(coinPubHash),
            pub: encodeCrock(denomPub),
        });
        const coinEv = {
            cipher: DenomKeyType.Rsa,
            rsa_blinded_planchet: blindResp.blinded,
        };
        const tipPlanchet = {
            blindingKey: encodeCrock(fc.bks),
            coinEv,
            coinEvHash: encodeCrock(hashCoinEv(coinEv, encodeCrock(hashDenomPub(req.denomPub)))),
            coinPriv: encodeCrock(fc.coinPriv),
            coinPub: encodeCrock(fc.coinPub),
            ageCommitmentProof: fc.ageCommitmentProof,
        };
        return tipPlanchet;
    },
    async signTrackTransaction(tci, req) {
        const p = buildSigPS(TalerSignaturePurpose.MERCHANT_TRACK_TRANSACTION)
            .put(decodeCrock(req.contractTermsHash))
            .put(decodeCrock(req.wireHash))
            .put(decodeCrock(req.merchantPub))
            .put(decodeCrock(req.coinPub))
            .build();
        return { sig: encodeCrock(eddsaSign(p, decodeCrock(req.merchantPriv))) };
    },
    /**
     * Create and sign a message to recoup a coin.
     */
    async createRecoupRequest(tci, req) {
        const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP)
            .put(decodeCrock(req.denomPubHash))
            .put(decodeCrock(req.blindingKey))
            .build();
        const coinPriv = decodeCrock(req.coinPriv);
        const coinSig = eddsaSign(p, coinPriv);
        if (req.denomPub.cipher === DenomKeyType.Rsa) {
            const paybackRequest = {
                coin_blind_key_secret: req.blindingKey,
                coin_sig: encodeCrock(coinSig),
                denom_pub_hash: req.denomPubHash,
                denom_sig: req.denomSig,
                // FIXME!
                ewv: {
                    cipher: "RSA",
                },
            };
            return paybackRequest;
        }
        else {
            throw new Error();
        }
    },
    /**
     * Create and sign a message to recoup a coin.
     */
    async createRecoupRefreshRequest(tci, req) {
        const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP_REFRESH)
            .put(decodeCrock(req.denomPubHash))
            .put(decodeCrock(req.blindingKey))
            .build();
        const coinPriv = decodeCrock(req.coinPriv);
        const coinSig = eddsaSign(p, coinPriv);
        if (req.denomPub.cipher === DenomKeyType.Rsa) {
            const recoupRequest = {
                coin_blind_key_secret: req.blindingKey,
                coin_sig: encodeCrock(coinSig),
                denom_pub_hash: req.denomPubHash,
                denom_sig: req.denomSig,
                // FIXME!
                ewv: {
                    cipher: "RSA",
                },
            };
            return recoupRequest;
        }
        else {
            throw new Error();
        }
    },
    /**
     * Check if a payment signature is valid.
     */
    async isValidPaymentSignature(tci, req) {
        const { contractHash, sig, merchantPub } = req;
        const p = buildSigPS(TalerSignaturePurpose.MERCHANT_PAYMENT_OK)
            .put(decodeCrock(contractHash))
            .build();
        const sigBytes = decodeCrock(sig);
        const pubBytes = decodeCrock(merchantPub);
        return { valid: eddsaVerify(p, sigBytes, pubBytes) };
    },
    /**
     * Check if a wire fee is correctly signed.
     */
    async isValidWireFee(tci, req) {
        const { type, wf, masterPub } = req;
        const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_FEES)
            .put(hash(stringToBytes(type + "\0")))
            .put(timestampRoundedToBuffer(wf.startStamp))
            .put(timestampRoundedToBuffer(wf.endStamp))
            .put(amountToBuffer(wf.wireFee))
            .put(amountToBuffer(wf.closingFee))
            .put(amountToBuffer(wf.wadFee))
            .build();
        const sig = decodeCrock(wf.sig);
        const pub = decodeCrock(masterPub);
        return { valid: eddsaVerify(p, sig, pub) };
    },
    /**
     * Check if the signature of a denomination is valid.
     */
    async isValidDenom(tci, req) {
        const { masterPub, denom } = req;
        const value = {
            currency: denom.currency,
            fraction: denom.amountFrac,
            value: denom.amountVal,
        };
        const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY)
            .put(decodeCrock(masterPub))
            .put(timestampRoundedToBuffer(denom.stampStart))
            .put(timestampRoundedToBuffer(denom.stampExpireWithdraw))
            .put(timestampRoundedToBuffer(denom.stampExpireDeposit))
            .put(timestampRoundedToBuffer(denom.stampExpireLegal))
            .put(amountToBuffer(value))
            .put(amountToBuffer(denom.fees.feeWithdraw))
            .put(amountToBuffer(denom.fees.feeDeposit))
            .put(amountToBuffer(denom.fees.feeRefresh))
            .put(amountToBuffer(denom.fees.feeRefund))
            .put(decodeCrock(denom.denomPubHash))
            .build();
        const sig = decodeCrock(denom.masterSig);
        const pub = decodeCrock(masterPub);
        const res = eddsaVerify(p, sig, pub);
        return { valid: res };
    },
    async isValidWireAccount(tci, req) {
        const { sig, masterPub, paytoUri } = req;
        const paytoHash = hashTruncate32(stringToBytes(paytoUri + "\0"));
        const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS)
            .put(paytoHash)
            .build();
        return { valid: eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub)) };
    },
    async isValidContractTermsSignature(tci, req) {
        const cthDec = decodeCrock(req.contractTermsHash);
        const p = buildSigPS(TalerSignaturePurpose.MERCHANT_CONTRACT)
            .put(cthDec)
            .build();
        return {
            valid: eddsaVerify(p, decodeCrock(req.sig), decodeCrock(req.merchantPub)),
        };
    },
    /**
     * Create a new EdDSA key pair.
     */
    async createEddsaKeypair(tci) {
        const eddsaPriv = encodeCrock(getRandomBytes(32));
        const eddsaPubRes = await tci.eddsaGetPublic(tci, {
            priv: eddsaPriv,
        });
        return {
            priv: eddsaPriv,
            pub: eddsaPubRes.pub,
        };
    },
    async eddsaGetPublic(tci, req) {
        return {
            priv: req.priv,
            pub: encodeCrock(eddsaGetPublic(decodeCrock(req.priv))),
        };
    },
    async unblindDenominationSignature(tci, req) {
        if (req.evSig.cipher === DenomKeyType.Rsa) {
            if (req.planchet.denomPub.cipher !== DenomKeyType.Rsa) {
                throw new Error("planchet cipher does not match blind signature cipher");
            }
            const denomSig = rsaUnblind(decodeCrock(req.evSig.blinded_rsa_signature), decodeCrock(req.planchet.denomPub.rsa_public_key), decodeCrock(req.planchet.blindingKey));
            return {
                cipher: DenomKeyType.Rsa,
                rsa_signature: encodeCrock(denomSig),
            };
        }
        else {
            throw Error(`unblinding for cipher ${req.evSig.cipher} not implemented`);
        }
    },
    /**
     * Unblind a blindly signed value.
     */
    async rsaUnblind(tci, req) {
        const denomSig = rsaUnblind(decodeCrock(req.blindedSig), decodeCrock(req.pk), decodeCrock(req.bk));
        return { sig: encodeCrock(denomSig) };
    },
    /**
     * Unblind a blindly signed value.
     */
    async rsaVerify(tci, req) {
        return {
            valid: rsaVerify(hash(decodeCrock(req.hm)), decodeCrock(req.sig), decodeCrock(req.pk)),
        };
    },
    /**
     * Generate updated coins (to store in the database)
     * and deposit permissions for each given coin.
     */
    async signDepositPermission(tci, depositInfo) {
        var _a;
        // FIXME: put extensions here if used
        const hExt = new Uint8Array(64);
        let hAgeCommitment;
        let minimumAgeSig = undefined;
        if (depositInfo.ageCommitmentProof) {
            const ach = AgeRestriction.hashCommitment(depositInfo.ageCommitmentProof.commitment);
            hAgeCommitment = decodeCrock(ach);
            if (depositInfo.requiredMinimumAge != null) {
                minimumAgeSig = encodeCrock(AgeRestriction.commitmentAttest(depositInfo.ageCommitmentProof, depositInfo.requiredMinimumAge));
            }
        }
        else {
            // All zeros.
            hAgeCommitment = new Uint8Array(32);
        }
        let d;
        if (depositInfo.denomKeyType === DenomKeyType.Rsa) {
            d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT)
                .put(decodeCrock(depositInfo.contractTermsHash))
                .put(hAgeCommitment)
                .put(hExt)
                .put(decodeCrock(depositInfo.wireInfoHash))
                .put(decodeCrock(depositInfo.denomPubHash))
                .put(timestampRoundedToBuffer(depositInfo.timestamp))
                .put(timestampRoundedToBuffer(depositInfo.refundDeadline))
                .put(amountToBuffer(depositInfo.spendAmount))
                .put(amountToBuffer(depositInfo.feeDeposit))
                .put(decodeCrock(depositInfo.merchantPub))
                .build();
        }
        else {
            throw Error("unsupported exchange protocol version");
        }
        const coinSigRes = await this.eddsaSign(tci, {
            msg: encodeCrock(d),
            priv: depositInfo.coinPriv,
        });
        if (depositInfo.denomKeyType === DenomKeyType.Rsa) {
            const s = {
                coin_pub: depositInfo.coinPub,
                coin_sig: coinSigRes.sig,
                contribution: Amounts.stringify(depositInfo.spendAmount),
                h_denom: depositInfo.denomPubHash,
                exchange_url: depositInfo.exchangeBaseUrl,
                ub_sig: {
                    cipher: DenomKeyType.Rsa,
                    rsa_signature: depositInfo.denomSig.rsa_signature,
                },
            };
            if (depositInfo.requiredMinimumAge != null) {
                s.minimum_age_sig = minimumAgeSig;
                s.age_commitment =
                    (_a = depositInfo.ageCommitmentProof) === null || _a === void 0 ? void 0 : _a.commitment.publicKeys;
            }
            else if (depositInfo.ageCommitmentProof) {
                s.h_age_commitment = encodeCrock(hAgeCommitment);
            }
            return s;
        }
        else {
            throw Error(`unsupported denomination cipher (${depositInfo.denomKeyType})`);
        }
    },
    async deriveRefreshSession(tci, req) {
        const { newCoinDenoms, feeRefresh: meltFee, kappa, meltCoinDenomPubHash, meltCoinPriv, meltCoinPub, sessionSecretSeed: refreshSessionSecretSeed, } = req;
        const currency = newCoinDenoms[0].value.currency;
        let valueWithFee = Amounts.getZero(currency);
        for (const ncd of newCoinDenoms) {
            const t = Amounts.add(ncd.value, ncd.feeWithdraw).amount;
            valueWithFee = Amounts.add(valueWithFee, Amounts.mult(t, ncd.count).amount).amount;
        }
        // melt fee
        valueWithFee = Amounts.add(valueWithFee, meltFee).amount;
        const sessionHc = createHashContext();
        const transferPubs = [];
        const transferPrivs = [];
        const planchetsForGammas = [];
        for (let i = 0; i < kappa; i++) {
            const transferKeyPair = await tci.setupRefreshTransferPub(tci, {
                secretSeed: refreshSessionSecretSeed,
                transferPubIndex: i,
            });
            sessionHc.update(decodeCrock(transferKeyPair.transferPub));
            transferPrivs.push(transferKeyPair.transferPriv);
            transferPubs.push(transferKeyPair.transferPub);
        }
        for (const denomSel of newCoinDenoms) {
            for (let i = 0; i < denomSel.count; i++) {
                if (denomSel.denomPub.cipher === DenomKeyType.Rsa) {
                    const denomPubHash = hashDenomPub(denomSel.denomPub);
                    sessionHc.update(denomPubHash);
                }
                else {
                    throw new Error();
                }
            }
        }
        sessionHc.update(decodeCrock(meltCoinPub));
        sessionHc.update(amountToBuffer(valueWithFee));
        for (let i = 0; i < kappa; i++) {
            const planchets = [];
            for (let j = 0; j < newCoinDenoms.length; j++) {
                const denomSel = newCoinDenoms[j];
                for (let k = 0; k < denomSel.count; k++) {
                    const coinIndex = planchets.length;
                    const transferSecretRes = await tci.keyExchangeEcdheEddsa(tci, {
                        ecdhePriv: transferPrivs[i],
                        eddsaPub: meltCoinPub,
                    });
                    let coinPub;
                    let coinPriv;
                    let blindingFactor;
                    let fresh = await tci.setupRefreshPlanchet(tci, {
                        coinNumber: coinIndex,
                        transferSecret: transferSecretRes.h,
                    });
                    let newAc = undefined;
                    let newAch = undefined;
                    if (req.meltCoinAgeCommitmentProof) {
                        newAc = await AgeRestriction.commitmentDerive(req.meltCoinAgeCommitmentProof, decodeCrock(transferSecretRes.h));
                        newAch = AgeRestriction.hashCommitment(newAc.commitment);
                    }
                    coinPriv = decodeCrock(fresh.coinPriv);
                    coinPub = decodeCrock(fresh.coinPub);
                    blindingFactor = decodeCrock(fresh.bks);
                    const coinPubHash = hashCoinPub(fresh.coinPub, newAch);
                    if (denomSel.denomPub.cipher !== DenomKeyType.Rsa) {
                        throw Error("unsupported cipher, can't create refresh session");
                    }
                    const blindResult = await tci.rsaBlind(tci, {
                        bks: encodeCrock(blindingFactor),
                        hm: encodeCrock(coinPubHash),
                        pub: denomSel.denomPub.rsa_public_key,
                    });
                    const coinEv = {
                        cipher: DenomKeyType.Rsa,
                        rsa_blinded_planchet: blindResult.blinded,
                    };
                    const coinEvHash = hashCoinEv(coinEv, encodeCrock(hashDenomPub(denomSel.denomPub)));
                    const planchet = {
                        blindingKey: encodeCrock(blindingFactor),
                        coinEv,
                        coinPriv: encodeCrock(coinPriv),
                        coinPub: encodeCrock(coinPub),
                        coinEvHash: encodeCrock(coinEvHash),
                        maxAge: req.meltCoinMaxAge,
                        ageCommitmentProof: newAc,
                    };
                    planchets.push(planchet);
                    hashCoinEvInner(coinEv, sessionHc);
                }
            }
            planchetsForGammas.push(planchets);
        }
        const sessionHash = sessionHc.finish();
        let confirmData;
        let hAgeCommitment;
        if (req.meltCoinAgeCommitmentProof) {
            hAgeCommitment = decodeCrock(AgeRestriction.hashCommitment(req.meltCoinAgeCommitmentProof.commitment));
        }
        else {
            hAgeCommitment = new Uint8Array(32);
        }
        confirmData = buildSigPS(TalerSignaturePurpose.WALLET_COIN_MELT)
            .put(sessionHash)
            .put(decodeCrock(meltCoinDenomPubHash))
            .put(hAgeCommitment)
            .put(amountToBuffer(valueWithFee))
            .put(amountToBuffer(meltFee))
            .build();
        const confirmSigResp = await tci.eddsaSign(tci, {
            msg: encodeCrock(confirmData),
            priv: meltCoinPriv,
        });
        const refreshSession = {
            confirmSig: confirmSigResp.sig,
            hash: encodeCrock(sessionHash),
            meltCoinPub: meltCoinPub,
            planchetsForGammas: planchetsForGammas,
            transferPrivs,
            transferPubs,
            meltValueWithFee: valueWithFee,
        };
        return refreshSession;
    },
    /**
     * Hash a string including the zero terminator.
     */
    async hashString(tci, req) {
        const b = stringToBytes(req.str + "\0");
        return { h: encodeCrock(hash(b)) };
    },
    async signCoinLink(tci, req) {
        const coinEvHash = hashCoinEv(req.coinEv, req.newDenomHash);
        // FIXME: fill in
        const hAgeCommitment = new Uint8Array(32);
        const coinLink = buildSigPS(TalerSignaturePurpose.WALLET_COIN_LINK)
            .put(decodeCrock(req.newDenomHash))
            .put(decodeCrock(req.transferPub))
            .put(hAgeCommitment)
            .put(coinEvHash)
            .build();
        return tci.eddsaSign(tci, {
            msg: encodeCrock(coinLink),
            priv: req.oldCoinPriv,
        });
    },
    async makeSyncSignature(tci, req) {
        const hNew = decodeCrock(req.newHash);
        let hOld;
        if (req.oldHash) {
            hOld = decodeCrock(req.oldHash);
        }
        else {
            hOld = new Uint8Array(64);
        }
        const sigBlob = buildSigPS(TalerSignaturePurpose.SYNC_BACKUP_UPLOAD)
            .put(hOld)
            .put(hNew)
            .build();
        const uploadSig = eddsaSign(sigBlob, decodeCrock(req.accountPriv));
        return { sig: encodeCrock(uploadSig) };
    },
    async keyExchangeEcdheEddsa(tci, req) {
        return {
            h: encodeCrock(keyExchangeEcdheEddsa(decodeCrock(req.ecdhePriv), decodeCrock(req.eddsaPub))),
        };
    },
    async ecdheGetPublic(tci, req) {
        return {
            pub: encodeCrock(ecdheGetPublic(decodeCrock(req.priv))),
        };
    },
    async setupRefreshTransferPub(tci, req) {
        const info = stringToBytes("taler-transfer-pub-derivation");
        const saltArrBuf = new ArrayBuffer(4);
        const salt = new Uint8Array(saltArrBuf);
        const saltDataView = new DataView(saltArrBuf);
        saltDataView.setUint32(0, req.transferPubIndex);
        const out = kdf(32, decodeCrock(req.secretSeed), salt, info);
        const transferPriv = encodeCrock(out);
        return {
            transferPriv,
            transferPub: (await tci.ecdheGetPublic(tci, { priv: transferPriv })).pub,
        };
    },
    async signPurseCreation(tci, req) {
        const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_CREATE)
            .put(timestampRoundedToBuffer(req.purseExpiration))
            .put(amountToBuffer(Amounts.parseOrThrow(req.purseAmount)))
            .put(decodeCrock(req.hContractTerms))
            .put(decodeCrock(req.mergePub))
            .put(bufferForUint32(req.minAge))
            .build();
        return await tci.eddsaSign(tci, {
            msg: encodeCrock(sigBlob),
            priv: req.pursePriv,
        });
    },
    async signPurseDeposits(tci, req) {
        const hExchangeBaseUrl = hash(stringToBytes(req.exchangeBaseUrl + "\0"));
        const deposits = [];
        for (const c of req.coins) {
            let maybeAch;
            if (c.ageCommitmentProof) {
                maybeAch = decodeCrock(AgeRestriction.hashCommitment(c.ageCommitmentProof.commitment));
            }
            else {
                maybeAch = new Uint8Array(32);
            }
            const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_DEPOSIT)
                .put(amountToBuffer(Amounts.parseOrThrow(c.contribution)))
                .put(decodeCrock(c.denomPubHash))
                .put(maybeAch)
                .put(decodeCrock(req.pursePub))
                .put(hExchangeBaseUrl)
                .build();
            const sigResp = await tci.eddsaSign(tci, {
                msg: encodeCrock(sigBlob),
                priv: c.coinPriv,
            });
            deposits.push({
                amount: c.contribution,
                coin_pub: c.coinPub,
                coin_sig: sigResp.sig,
                denom_pub_hash: c.denomPubHash,
                ub_sig: c.denomSig,
                age_commitment: c.ageCommitmentProof
                    ? c.ageCommitmentProof.commitment.publicKeys
                    : undefined,
            });
        }
        return {
            deposits,
        };
    },
    async encryptContractForMerge(tci, req) {
        const contractKeyPair = await this.createEddsaKeypair(tci, {});
        const enc = await encryptContractForMerge(decodeCrock(req.pursePub), decodeCrock(contractKeyPair.priv), decodeCrock(req.mergePriv), req.contractTerms);
        const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_ECONTRACT)
            .put(hash(enc))
            .put(decodeCrock(contractKeyPair.pub))
            .build();
        const sig = eddsaSign(sigBlob, decodeCrock(req.pursePriv));
        return {
            econtract: {
                contract_pub: contractKeyPair.pub,
                econtract: encodeCrock(enc),
                econtract_sig: encodeCrock(sig),
            },
            contractPriv: contractKeyPair.priv,
        };
    },
    async decryptContractForMerge(tci, req) {
        const res = await decryptContractForMerge(decodeCrock(req.ciphertext), decodeCrock(req.pursePub), decodeCrock(req.contractPriv));
        return {
            contractTerms: res.contractTerms,
            mergePriv: encodeCrock(res.mergePriv),
        };
    },
    async encryptContractForDeposit(tci, req) {
        const contractKeyPair = await this.createEddsaKeypair(tci, {});
        const enc = await encryptContractForDeposit(decodeCrock(req.pursePub), decodeCrock(contractKeyPair.priv), req.contractTerms);
        const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_ECONTRACT)
            .put(hash(enc))
            .put(decodeCrock(contractKeyPair.pub))
            .build();
        const sig = eddsaSign(sigBlob, decodeCrock(req.pursePriv));
        return {
            econtract: {
                contract_pub: contractKeyPair.pub,
                econtract: encodeCrock(enc),
                econtract_sig: encodeCrock(sig),
            },
            contractPriv: contractKeyPair.priv,
        };
    },
    async decryptContractForDeposit(tci, req) {
        const res = await decryptContractForDeposit(decodeCrock(req.ciphertext), decodeCrock(req.pursePub), decodeCrock(req.contractPriv));
        return {
            contractTerms: res.contractTerms,
        };
    },
    async signPurseMerge(tci, req) {
        const mergeSigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_MERGE)
            .put(timestampRoundedToBuffer(req.mergeTimestamp))
            .put(decodeCrock(req.pursePub))
            .put(hashTruncate32(stringToBytes(req.reservePayto + "\0")))
            .build();
        const mergeSigResp = await tci.eddsaSign(tci, {
            msg: encodeCrock(mergeSigBlob),
            priv: req.mergePriv,
        });
        const reserveSigBlob = buildSigPS(TalerSignaturePurpose.WALLET_ACCOUNT_MERGE)
            .put(timestampRoundedToBuffer(req.purseExpiration))
            .put(amountToBuffer(Amounts.parseOrThrow(req.purseAmount)))
            .put(amountToBuffer(Amounts.parseOrThrow(req.purseFee)))
            .put(decodeCrock(req.contractTermsHash))
            .put(decodeCrock(req.pursePub))
            .put(timestampRoundedToBuffer(req.mergeTimestamp))
            // FIXME: put in min_age
            .put(bufferForUint32(0))
            .put(bufferForUint32(req.flags))
            .build();
        logger$o.info(`signing WALLET_ACCOUNT_MERGE over ${encodeCrock(reserveSigBlob)}`);
        const reserveSigResp = await tci.eddsaSign(tci, {
            msg: encodeCrock(reserveSigBlob),
            priv: req.reservePriv,
        });
        return {
            mergeSig: mergeSigResp.sig,
            accountSig: reserveSigResp.sig,
        };
    },
    async signReservePurseCreate(tci, req) {
        const mergeSigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_MERGE)
            .put(timestampRoundedToBuffer(req.mergeTimestamp))
            .put(decodeCrock(req.pursePub))
            .put(hashTruncate32(stringToBytes(req.reservePayto + "\0")))
            .build();
        const mergeSigResp = await tci.eddsaSign(tci, {
            msg: encodeCrock(mergeSigBlob),
            priv: req.mergePriv,
        });
        logger$o.info(`payto URI: ${req.reservePayto}`);
        logger$o.info(`signing WALLET_PURSE_MERGE over ${encodeCrock(mergeSigBlob)}`);
        const reserveSigBlob = buildSigPS(TalerSignaturePurpose.WALLET_ACCOUNT_MERGE)
            .put(timestampRoundedToBuffer(req.purseExpiration))
            .put(amountToBuffer(Amounts.parseOrThrow(req.purseAmount)))
            .put(amountToBuffer(Amounts.parseOrThrow(req.purseFee)))
            .put(decodeCrock(req.contractTermsHash))
            .put(decodeCrock(req.pursePub))
            .put(timestampRoundedToBuffer(req.mergeTimestamp))
            // FIXME: put in min_age
            .put(bufferForUint32(0))
            .put(bufferForUint32(req.flags))
            .build();
        logger$o.info(`signing WALLET_ACCOUNT_MERGE over ${encodeCrock(reserveSigBlob)}`);
        const reserveSigResp = await tci.eddsaSign(tci, {
            msg: encodeCrock(reserveSigBlob),
            priv: req.reservePriv,
        });
        const mergePub = encodeCrock(eddsaGetPublic(decodeCrock(req.mergePriv)));
        const purseSigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_CREATE)
            .put(timestampRoundedToBuffer(req.purseExpiration))
            .put(amountToBuffer(Amounts.parseOrThrow(req.purseAmount)))
            .put(decodeCrock(req.contractTermsHash))
            .put(decodeCrock(mergePub))
            // FIXME: add age!
            .put(bufferForUint32(0))
            .build();
        const purseSigResp = await tci.eddsaSign(tci, {
            msg: encodeCrock(purseSigBlob),
            priv: req.pursePriv,
        });
        return {
            mergeSig: mergeSigResp.sig,
            accountSig: reserveSigResp.sig,
            purseSig: purseSigResp.sig,
        };
    },
};
function amountToBuffer(amount) {
    const buffer = new ArrayBuffer(8 + 4 + 12);
    const dvbuf = new DataView(buffer);
    const u8buf = new Uint8Array(buffer);
    const curr = stringToBytes(amount.currency);
    if (typeof dvbuf.setBigUint64 !== "undefined") {
        dvbuf.setBigUint64(0, BigInt(amount.value));
    }
    else {
        const arr = bigint(amount.value).toArray(2 ** 8).value;
        let offset = 8 - arr.length;
        for (let i = 0; i < arr.length; i++) {
            dvbuf.setUint8(offset++, arr[i]);
        }
    }
    dvbuf.setUint32(8, amount.fraction);
    u8buf.set(curr, 8 + 4);
    return u8buf;
}
function timestampRoundedToBuffer(ts) {
    const b = new ArrayBuffer(8);
    const v = new DataView(b);
    // The buffer we sign over represents the timestamp in microseconds.
    if (typeof v.setBigUint64 !== "undefined") {
        const s = BigInt(ts.t_s) * BigInt(1000 * 1000);
        v.setBigUint64(0, s);
    }
    else {
        const s = ts.t_s === "never" ? bigint.zero : bigint(ts.t_s).multiply(1000 * 1000);
        const arr = s.toArray(2 ** 8).value;
        let offset = 8 - arr.length;
        for (let i = 0; i < arr.length; i++) {
            v.setUint8(offset++, arr[i]);
        }
    }
    return new Uint8Array(b);
}
Object.fromEntries(Object.keys(nativeCryptoR).map((name) => {
    return [
        name,
        (req) => nativeCryptoR[name](nativeCryptoR, req),
    ];
}));

/*
 This file is part of GNU Taler
 (C) 2016 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$n = new Logger("cryptoApi.ts");
/**
 * Number of different priorities. Each priority p
 * must be 0 <= p < NUM_PRIO.
 */
const NUM_PRIO = 5;
class CryptoApiStoppedError extends Error {
    constructor() {
        super("Crypto API stopped");
        Object.setPrototypeOf(this, CryptoApiStoppedError.prototype);
    }
}
/**
 * Dispatcher for cryptographic operations to underlying crypto workers.
 */
class CryptoDispatcher {
    constructor(workerFactory) {
        this.nextRpcId = 1;
        /**
         * Number of busy workers.
         */
        this.numBusy = 0;
        /**
         * Did we stop accepting new requests?
         */
        this.stopped = false;
        const fns = {};
        for (const name of Object.keys(nullCrypto)) {
            fns[name] = (x) => this.doRpc(name, 0, x);
        }
        this.cryptoApi = fns;
        this.workerFactory = workerFactory;
        this.workers = new Array(workerFactory.getConcurrency());
        for (let i = 0; i < this.workers.length; i++) {
            this.workers[i] = {
                currentWorkItem: null,
                idleTimeoutHandle: null,
                w: null,
            };
        }
        this.workQueues = [];
        for (let i = 0; i < NUM_PRIO; i++) {
            this.workQueues.push([]);
        }
    }
    /**
     * Terminate all worker threads.
     */
    terminateWorkers() {
        for (const worker of this.workers) {
            if (worker.idleTimeoutHandle) {
                worker.idleTimeoutHandle.clear();
                worker.idleTimeoutHandle = null;
            }
            if (worker.currentWorkItem) {
                worker.currentWorkItem.reject(new CryptoApiStoppedError());
                worker.currentWorkItem = null;
            }
            if (worker.w) {
                logger$n.trace("terminating worker");
                worker.w.terminate();
                worker.w = null;
            }
        }
    }
    stop() {
        this.stopped = true;
        this.terminateWorkers();
    }
    /**
     * Start a worker (if not started) and set as busy.
     */
    wake(ws, work) {
        if (this.stopped) {
            return;
        }
        if (ws.currentWorkItem !== null) {
            throw Error("assertion failed");
        }
        ws.currentWorkItem = work;
        this.numBusy++;
        let worker;
        if (!ws.w) {
            worker = this.workerFactory.startWorker();
            worker.onmessage = (m) => this.handleWorkerMessage(ws, m);
            worker.onerror = (e) => this.handleWorkerError(ws, e);
            ws.w = worker;
        }
        else {
            worker = ws.w;
        }
        const msg = {
            req: work.req,
            id: work.rpcId,
            operation: work.operation,
        };
        this.resetWorkerTimeout(ws);
        work.startTime = performanceNow();
        timer.after(0, () => worker.postMessage(msg));
    }
    resetWorkerTimeout(ws) {
        if (ws.idleTimeoutHandle !== null) {
            ws.idleTimeoutHandle.clear();
            ws.idleTimeoutHandle = null;
        }
        const destroy = () => {
            logger$n.trace("destroying crypto worker after idle timeout");
            // terminate worker if it's idle
            if (ws.w && ws.currentWorkItem === null) {
                ws.w.terminate();
                ws.w = null;
            }
        };
        ws.idleTimeoutHandle = timer.after(15 * 1000, destroy);
        ws.idleTimeoutHandle.unref();
    }
    handleWorkerError(ws, e) {
        if (ws.currentWorkItem) {
            logger$n.error(`error in worker during ${ws.currentWorkItem.operation}`, e);
        }
        else {
            logger$n.error("error in worker", e);
        }
        logger$n.error(e.message);
        try {
            if (ws.w) {
                ws.w.terminate();
                ws.w = null;
            }
        }
        catch (e) {
            logger$n.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();
                if (!work) {
                    continue;
                }
                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;
        }
        currentWorkItem.resolve(msg.data.result);
    }
    doRpc(operation, priority, req) {
        if (this.stopped) {
            throw new CryptoApiStoppedError();
        }
        const p = new Promise((resolve, reject) => {
            const rpcId = this.nextRpcId++;
            const workItem = {
                operation,
                req,
                resolve,
                reject,
                rpcId,
                startTime: BigInt(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");
        });
        // Make sure that we wait for the result while a timer is active
        // to prevent the event loop from dying, as just waiting for a promise
        // does not keep the process alive in Node.
        // (The worker child process won't keep us alive either, because we un-ref
        // it to make sure it doesn't keep us alive if there is no work.)
        return new Promise((resolve, reject) => {
            let timedOut = false;
            const timeout = timer.after(5000, () => {
                logger$n.warn(`crypto RPC call ('${operation}') timed out`);
                timedOut = true;
                reject(new Error(`crypto RPC call ('${operation}') timed out`));
            });
            p.then((x) => {
                if (timedOut) {
                    return;
                }
                timeout.clear();
                resolve(x);
            }).catch((x) => {
                logger$n.info(`crypto RPC call ${operation} threw`);
                if (timedOut) {
                    return;
                }
                timeout.clear();
                reject(x);
            });
        });
    }
}

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
var PendingTaskType;
(function (PendingTaskType) {
    PendingTaskType["ExchangeUpdate"] = "exchange-update";
    PendingTaskType["ExchangeCheckRefresh"] = "exchange-check-refresh";
    PendingTaskType["Pay"] = "pay";
    PendingTaskType["ProposalDownload"] = "proposal-download";
    PendingTaskType["Refresh"] = "refresh";
    PendingTaskType["Recoup"] = "recoup";
    PendingTaskType["RefundQuery"] = "refund-query";
    PendingTaskType["TipPickup"] = "tip-pickup";
    PendingTaskType["Withdraw"] = "withdraw";
    PendingTaskType["Deposit"] = "deposit";
    PendingTaskType["Backup"] = "backup";
})(PendingTaskType || (PendingTaskType = {}));
var ReserveType;
(function (ReserveType) {
    /**
     * Manually created.
     */
    ReserveType["Manual"] = "manual";
    /**
     * Withdrawn from a bank that has "tight" Taler integration
     */
    ReserveType["TalerBankWithdraw"] = "taler-bank-withdraw";
})(ReserveType || (ReserveType = {}));

/*
 This file is part of GNU Taler
 (C) 2021 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
var WalletApiOperation;
(function (WalletApiOperation) {
    WalletApiOperation["InitWallet"] = "initWallet";
    WalletApiOperation["WithdrawTestkudos"] = "withdrawTestkudos";
    WalletApiOperation["WithdrawTestBalance"] = "withdrawTestBalance";
    WalletApiOperation["PreparePayForUri"] = "preparePayForUri";
    WalletApiOperation["RunIntegrationTest"] = "runIntegrationTest";
    WalletApiOperation["TestPay"] = "testPay";
    WalletApiOperation["AddExchange"] = "addExchange";
    WalletApiOperation["GetTransactions"] = "getTransactions";
    WalletApiOperation["ListExchanges"] = "listExchanges";
    WalletApiOperation["ListKnownBankAccounts"] = "listKnownBankAccounts";
    WalletApiOperation["GetWithdrawalDetailsForUri"] = "getWithdrawalDetailsForUri";
    WalletApiOperation["GetWithdrawalDetailsForAmount"] = "getWithdrawalDetailsForAmount";
    WalletApiOperation["AcceptManualWithdrawal"] = "acceptManualWithdrawal";
    WalletApiOperation["GetBalances"] = "getBalances";
    WalletApiOperation["GetPendingOperations"] = "getPendingOperations";
    WalletApiOperation["SetExchangeTosAccepted"] = "setExchangeTosAccepted";
    WalletApiOperation["ApplyRefund"] = "applyRefund";
    WalletApiOperation["AcceptBankIntegratedWithdrawal"] = "acceptBankIntegratedWithdrawal";
    WalletApiOperation["GetExchangeTos"] = "getExchangeTos";
    WalletApiOperation["RetryPendingNow"] = "retryPendingNow";
    WalletApiOperation["AbortFailedPayWithRefund"] = "abortFailedPayWithRefund";
    WalletApiOperation["ConfirmPay"] = "confirmPay";
    WalletApiOperation["DumpCoins"] = "dumpCoins";
    WalletApiOperation["SetCoinSuspended"] = "setCoinSuspended";
    WalletApiOperation["ForceRefresh"] = "forceRefresh";
    WalletApiOperation["PrepareTip"] = "prepareTip";
    WalletApiOperation["AcceptTip"] = "acceptTip";
    WalletApiOperation["ExportBackup"] = "exportBackup";
    WalletApiOperation["AddBackupProvider"] = "addBackupProvider";
    WalletApiOperation["RunBackupCycle"] = "runBackupCycle";
    WalletApiOperation["ExportBackupRecovery"] = "exportBackupRecovery";
    WalletApiOperation["ImportBackupRecovery"] = "importBackupRecovery";
    WalletApiOperation["GetBackupInfo"] = "getBackupInfo";
    WalletApiOperation["TrackDepositGroup"] = "trackDepositGroup";
    WalletApiOperation["DeleteTransaction"] = "deleteTransaction";
    WalletApiOperation["RetryTransaction"] = "retryTransaction";
    WalletApiOperation["GetCoins"] = "getCoins";
    WalletApiOperation["ListCurrencies"] = "listCurrencies";
    WalletApiOperation["CreateDepositGroup"] = "createDepositGroup";
    WalletApiOperation["SetWalletDeviceId"] = "setWalletDeviceId";
    WalletApiOperation["ExportBackupPlain"] = "exportBackupPlain";
    WalletApiOperation["WithdrawFakebank"] = "withdrawFakebank";
    WalletApiOperation["ExportDb"] = "exportDb";
    WalletApiOperation["InitiatePeerPushPayment"] = "initiatePeerPushPayment";
    WalletApiOperation["CheckPeerPushPayment"] = "checkPeerPushPayment";
    WalletApiOperation["AcceptPeerPushPayment"] = "acceptPeerPushPayment";
    WalletApiOperation["InitiatePeerPullPayment"] = "initiatePeerPullPayment";
    WalletApiOperation["CheckPeerPullPayment"] = "checkPeerPullPayment";
    WalletApiOperation["AcceptPeerPullPayment"] = "acceptPeerPullPayment";
    WalletApiOperation["ClearDb"] = "clearDb";
    WalletApiOperation["Recycle"] = "recycle";
})(WalletApiOperation || (WalletApiOperation = {}));

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
function assertUnreachable(x) {
    throw new Error("Didn't expect to get here");
}

/*
 This file is part of GNU Taler
 (C) 2020 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
class InvariantViolatedError extends Error {
    constructor(message) {
        super(message);
        Object.setPrototypeOf(this, InvariantViolatedError.prototype);
    }
}
/**
 * Helpers for invariants.
 */
function checkDbInvariant(b, m) {
    if (!b) {
        if (m) {
            throw Error(`BUG: database invariant failed (${m})`);
        }
        else {
            throw Error("BUG: database invariant failed");
        }
    }
}
function checkLogicInvariant(b, m) {
    if (!b) {
        if (m) {
            throw Error(`BUG: logic invariant failed (${m})`);
        }
        else {
            throw Error("BUG: logic invariant failed");
        }
    }
}

/*
 This file is part of GNU Taler
 (C) 2020 Taler Systems SA

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
async function provideBackupState(ws) {
    const bs = await ws.db
        .mktx((stores) => [stores.config])
        .runReadOnly(async (tx) => {
        return await tx.config.get(WALLET_BACKUP_STATE_KEY);
    });
    if (bs) {
        checkDbInvariant(bs.key === WALLET_BACKUP_STATE_KEY);
        return bs.value;
    }
    // We need to generate the key outside of the transaction
    // due to how IndexedDB works.
    const k = await ws.cryptoApi.createEddsaKeypair({});
    const d = getRandomBytes(5);
    // FIXME: device ID should be configured when wallet is initialized
    // and be based on hostname
    const deviceId = `wallet-core-${encodeCrock(d)}`;
    return await ws.db
        .mktx((x) => [x.config])
        .runReadWrite(async (tx) => {
        let backupStateEntry = await tx.config.get(WALLET_BACKUP_STATE_KEY);
        if (!backupStateEntry) {
            backupStateEntry = {
                key: WALLET_BACKUP_STATE_KEY,
                value: {
                    deviceId,
                    walletRootPub: k.pub,
                    walletRootPriv: k.priv,
                    lastBackupPlainHash: undefined,
                },
            };
            await tx.config.put(backupStateEntry);
        }
        checkDbInvariant(backupStateEntry.key === WALLET_BACKUP_STATE_KEY);
        return backupStateEntry.value;
    });
}
async function getWalletBackupState(ws, tx) {
    const bs = await tx.config.get(WALLET_BACKUP_STATE_KEY);
    checkDbInvariant(!!bs, "wallet backup state should be in DB");
    checkDbInvariant(bs.key === WALLET_BACKUP_STATE_KEY);
    return bs.value;
}
async function setWalletDeviceId(ws, deviceId) {
    await provideBackupState(ws);
    await ws.db
        .mktx((x) => [x.config])
        .runReadWrite(async (tx) => {
        let backupStateEntry = await tx.config.get(WALLET_BACKUP_STATE_KEY);
        if (!backupStateEntry ||
            backupStateEntry.key !== WALLET_BACKUP_STATE_KEY) {
            return;
        }
        backupStateEntry.value.deviceId = deviceId;
        await tx.config.put(backupStateEntry);
    });
}

/*
 This file is part of GNU Taler
 (C) 2020 Taler Systems SA

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$m = new Logger("backup/export.ts");
async function exportBackup(ws) {
    await provideBackupState(ws);
    return ws.db
        .mktx((x) => [
        x.config,
        x.exchanges,
        x.exchangeDetails,
        x.coins,
        x.denominations,
        x.purchases,
        x.proposals,
        x.refreshGroups,
        x.backupProviders,
        x.tips,
        x.recoupGroups,
        x.withdrawalGroups,
    ])
        .runReadWrite(async (tx) => {
        const bs = await getWalletBackupState(ws, tx);
        const backupExchangeDetails = [];
        const backupExchanges = [];
        const backupCoinsByDenom = {};
        const backupDenominationsByExchange = {};
        const backupPurchases = [];
        const backupProposals = [];
        const backupRefreshGroups = [];
        const backupBackupProviders = [];
        const backupTips = [];
        const backupRecoupGroups = [];
        const backupWithdrawalGroups = [];
        await tx.withdrawalGroups.iter().forEachAsync(async (wg) => {
            let info;
            switch (wg.wgInfo.withdrawalType) {
                case "bank-integrated" /* WithdrawalRecordType.BankIntegrated */:
                    info = {
                        type: BackupWgType.BankIntegrated,
                        exchange_payto_uri: wg.wgInfo.bankInfo.exchangePaytoUri,
                        taler_withdraw_uri: wg.wgInfo.bankInfo.talerWithdrawUri,
                        confirm_url: wg.wgInfo.bankInfo.confirmUrl,
                        timestamp_bank_confirmed: wg.wgInfo.bankInfo.timestampBankConfirmed,
                        timestamp_reserve_info_posted: wg.wgInfo.bankInfo.timestampReserveInfoPosted,
                    };
                    break;
                case "bank-manual" /* WithdrawalRecordType.BankManual */:
                    info = {
                        type: BackupWgType.BankManual,
                    };
                    break;
                case "peer-pull-credit" /* WithdrawalRecordType.PeerPullCredit */:
                    info = {
                        type: BackupWgType.PeerPullCredit,
                        contract_priv: wg.wgInfo.contractPriv,
                        contract_terms: wg.wgInfo.contractTerms,
                    };
                    break;
                case "peer-push-credit" /* WithdrawalRecordType.PeerPushCredit */:
                    info = {
                        type: BackupWgType.PeerPushCredit,
                        contract_terms: wg.wgInfo.contractTerms,
                    };
                    break;
                case "recoup" /* WithdrawalRecordType.Recoup */:
                    info = {
                        type: BackupWgType.Recoup,
                    };
                    break;
                default:
                    assertUnreachable(wg.wgInfo);
            }
            backupWithdrawalGroups.push({
                raw_withdrawal_amount: Amounts.stringify(wg.rawWithdrawalAmount),
                info,
                timestamp_created: wg.timestampStart,
                timestamp_finish: wg.timestampFinish,
                withdrawal_group_id: wg.withdrawalGroupId,
                secret_seed: wg.secretSeed,
                exchange_base_url: wg.exchangeBaseUrl,
                instructed_amount: Amounts.stringify(wg.instructedAmount),
                reserve_priv: wg.reservePriv,
                restrict_age: wg.restrictAge,
                // FIXME: proper status conversion!
                operation_status: wg.status == WithdrawalGroupStatus.Finished
                    ? BackupOperationStatus.Finished
                    : BackupOperationStatus.Pending,
                selected_denoms_uid: wg.denomSelUid,
                selected_denoms: wg.denomsSel.selectedDenoms.map((x) => ({
                    count: x.count,
                    denom_pub_hash: x.denomPubHash,
                })),
            });
        });
        await tx.tips.iter().forEach((tip) => {
            backupTips.push({
                exchange_base_url: tip.exchangeBaseUrl,
                merchant_base_url: tip.merchantBaseUrl,
                merchant_tip_id: tip.merchantTipId,
                wallet_tip_id: tip.walletTipId,
                secret_seed: tip.secretSeed,
                selected_denoms: tip.denomsSel.selectedDenoms.map((x) => ({
                    count: x.count,
                    denom_pub_hash: x.denomPubHash,
                })),
                timestamp_finished: tip.pickedUpTimestamp,
                timestamp_accepted: tip.acceptedTimestamp,
                timestamp_created: tip.createdTimestamp,
                timestamp_expiration: tip.tipExpiration,
                tip_amount_raw: Amounts.stringify(tip.tipAmountRaw),
                selected_denoms_uid: tip.denomSelUid,
            });
        });
        await tx.recoupGroups.iter().forEach((recoupGroup) => {
            backupRecoupGroups.push({
                recoup_group_id: recoupGroup.recoupGroupId,
                timestamp_created: recoupGroup.timestampStarted,
                timestamp_finish: recoupGroup.timestampFinished,
                coins: recoupGroup.coinPubs.map((x, i) => ({
                    coin_pub: x,
                    recoup_finished: recoupGroup.recoupFinishedPerCoin[i],
                    old_amount: Amounts.stringify(recoupGroup.oldAmountPerCoin[i]),
                })),
            });
        });
        await tx.backupProviders.iter().forEach((bp) => {
            let terms;
            if (bp.terms) {
                terms = {
                    annual_fee: Amounts.stringify(bp.terms.annualFee),
                    storage_limit_in_megabytes: bp.terms.storageLimitInMegabytes,
                    supported_protocol_version: bp.terms.supportedProtocolVersion,
                };
            }
            backupBackupProviders.push({
                terms,
                base_url: canonicalizeBaseUrl(bp.baseUrl),
                pay_proposal_ids: bp.paymentProposalIds,
                uids: bp.uids,
            });
        });
        await tx.coins.iter().forEach((coin) => {
            var _a;
            var _b;
            let bcs;
            switch (coin.coinSource.type) {
                case CoinSourceType.Refresh:
                    bcs = {
                        type: BackupCoinSourceType.Refresh,
                        old_coin_pub: coin.coinSource.oldCoinPub,
                    };
                    break;
                case CoinSourceType.Tip:
                    bcs = {
                        type: BackupCoinSourceType.Tip,
                        coin_index: coin.coinSource.coinIndex,
                        wallet_tip_id: coin.coinSource.walletTipId,
                    };
                    break;
                case CoinSourceType.Withdraw:
                    bcs = {
                        type: BackupCoinSourceType.Withdraw,
                        coin_index: coin.coinSource.coinIndex,
                        reserve_pub: coin.coinSource.reservePub,
                        withdrawal_group_id: coin.coinSource.withdrawalGroupId,
                    };
                    break;
            }
            const coins = ((_a = backupCoinsByDenom[_b = coin.denomPubHash]) !== null && _a !== void 0 ? _a : (backupCoinsByDenom[_b] = []));
            coins.push({
                blinding_key: coin.blindingKey,
                coin_priv: coin.coinPriv,
                coin_source: bcs,
                current_amount: Amounts.stringify(coin.currentAmount),
                fresh: coin.status === CoinStatus.Fresh,
                denom_sig: coin.denomSig,
            });
        });
        await tx.denominations.iter().forEach((denom) => {
            var _a, _b;
            var _c;
            const backupDenoms = ((_a = backupDenominationsByExchange[_c = denom.exchangeBaseUrl]) !== null && _a !== void 0 ? _a : (backupDenominationsByExchange[_c] = []));
            backupDenoms.push({
                coins: (_b = backupCoinsByDenom[denom.denomPubHash]) !== null && _b !== void 0 ? _b : [],
                denom_pub: denom.denomPub,
                fee_deposit: Amounts.stringify(denom.fees.feeDeposit),
                fee_refresh: Amounts.stringify(denom.fees.feeRefresh),
                fee_refund: Amounts.stringify(denom.fees.feeRefund),
                fee_withdraw: Amounts.stringify(denom.fees.feeWithdraw),
                is_offered: denom.isOffered,
                is_revoked: denom.isRevoked,
                master_sig: denom.masterSig,
                stamp_expire_deposit: denom.stampExpireDeposit,
                stamp_expire_legal: denom.stampExpireLegal,
                stamp_expire_withdraw: denom.stampExpireWithdraw,
                stamp_start: denom.stampStart,
                value: Amounts.stringify(DenominationRecord.getValue(denom)),
                list_issue_date: denom.listIssueDate,
            });
        });
        await tx.exchanges.iter().forEachAsync(async (ex) => {
            const dp = ex.detailsPointer;
            if (!dp) {
                return;
            }
            backupExchanges.push({
                base_url: ex.baseUrl,
                currency: dp.currency,
                master_public_key: dp.masterPublicKey,
                update_clock: dp.updateClock,
                protocol_version_range: dp.protocolVersionRange,
            });
        });
        await tx.exchangeDetails.iter().forEachAsync(async (ex) => {
            // Only back up permanently added exchanges.
            var _a;
            const wi = ex.wireInfo;
            const wireFees = [];
            Object.keys(wi.feesForType).forEach((x) => {
                for (const f of wi.feesForType[x]) {
                    wireFees.push({
                        wire_type: x,
                        closing_fee: Amounts.stringify(f.closingFee),
                        wad_fee: Amounts.stringify(f.wadFee),
                        end_stamp: f.endStamp,
                        sig: f.sig,
                        start_stamp: f.startStamp,
                        wire_fee: Amounts.stringify(f.wireFee),
                    });
                }
            });
            backupExchangeDetails.push({
                base_url: ex.exchangeBaseUrl,
                reserve_closing_delay: ex.reserveClosingDelay,
                accounts: ex.wireInfo.accounts.map((x) => ({
                    payto_uri: x.payto_uri,
                    master_sig: x.master_sig,
                })),
                auditors: ex.auditors.map((x) => ({
                    auditor_pub: x.auditor_pub,
                    auditor_url: x.auditor_url,
                    denomination_keys: x.denomination_keys,
                })),
                master_public_key: ex.masterPublicKey,
                currency: ex.currency,
                protocol_version: ex.protocolVersion,
                wire_fees: wireFees,
                signing_keys: ex.signingKeys.map((x) => ({
                    key: x.key,
                    master_sig: x.master_sig,
                    stamp_end: x.stamp_end,
                    stamp_expire: x.stamp_expire,
                    stamp_start: x.stamp_start,
                })),
                tos_accepted_etag: ex.termsOfServiceAcceptedEtag,
                tos_accepted_timestamp: ex.termsOfServiceAcceptedTimestamp,
                denominations: (_a = backupDenominationsByExchange[ex.exchangeBaseUrl]) !== null && _a !== void 0 ? _a : [],
            });
        });
        const purchaseProposalIdSet = new Set();
        await tx.purchases.iter().forEach((purch) => {
            const refunds = [];
            purchaseProposalIdSet.add(purch.proposalId);
            for (const refundKey of Object.keys(purch.refunds)) {
                const ri = purch.refunds[refundKey];
                const common = {
                    coin_pub: ri.coinPub,
                    execution_time: ri.executionTime,
                    obtained_time: ri.obtainedTime,
                    refund_amount: Amounts.stringify(ri.refundAmount),
                    rtransaction_id: ri.rtransactionId,
                    total_refresh_cost_bound: Amounts.stringify(ri.totalRefreshCostBound),
                };
                switch (ri.type) {
                    case RefundState.Applied:
                        refunds.push(Object.assign({ type: BackupRefundState.Applied }, common));
                        break;
                    case RefundState.Failed:
                        refunds.push(Object.assign({ type: BackupRefundState.Failed }, common));
                        break;
                    case RefundState.Pending:
                        refunds.push(Object.assign({ type: BackupRefundState.Pending }, common));
                        break;
                }
            }
            backupPurchases.push({
                contract_terms_raw: purch.download.contractTermsRaw,
                auto_refund_deadline: purch.autoRefundDeadline,
                merchant_pay_sig: purch.merchantPaySig,
                pay_coins: purch.payCoinSelection.coinPubs.map((x, i) => ({
                    coin_pub: x,
                    contribution: Amounts.stringify(purch.payCoinSelection.coinContributions[i]),
                })),
                proposal_id: purch.proposalId,
                refunds,
                timestamp_accept: purch.timestampAccept,
                timestamp_first_successful_pay: purch.timestampFirstSuccessfulPay,
                abort_status: purch.abortStatus === AbortStatus.None
                    ? undefined
                    : purch.abortStatus,
                nonce_priv: purch.noncePriv,
                merchant_sig: purch.download.contractData.merchantSig,
                total_pay_cost: Amounts.stringify(purch.totalPayCost),
                pay_coins_uid: purch.payCoinSelectionUid,
            });
        });
        await tx.proposals.iter().forEach((prop) => {
            var _a, _b;
            if (purchaseProposalIdSet.has(prop.proposalId)) {
                return;
            }
            let propStatus;
            switch (prop.proposalStatus) {
                case ProposalStatus.Accepted:
                    return;
                case ProposalStatus.Downloading:
                case ProposalStatus.Proposed:
                    propStatus = BackupProposalStatus.Proposed;
                    break;
                case ProposalStatus.PermanentlyFailed:
                    propStatus = BackupProposalStatus.PermanentlyFailed;
                    break;
                case ProposalStatus.Refused:
                    propStatus = BackupProposalStatus.Refused;
                    break;
                case ProposalStatus.Repurchase:
                    propStatus = BackupProposalStatus.Repurchase;
                    break;
            }
            backupProposals.push({
                claim_token: prop.claimToken,
                nonce_priv: prop.noncePriv,
                proposal_id: prop.noncePriv,
                proposal_status: propStatus,
                repurchase_proposal_id: prop.repurchaseProposalId,
                timestamp: prop.timestamp,
                contract_terms_raw: (_a = prop.download) === null || _a === void 0 ? void 0 : _a.contractTermsRaw,
                download_session_id: prop.downloadSessionId,
                merchant_base_url: prop.merchantBaseUrl,
                order_id: prop.orderId,
                merchant_sig: (_b = prop.download) === null || _b === void 0 ? void 0 : _b.contractData.merchantSig,
            });
        });
        await tx.refreshGroups.iter().forEach((rg) => {
            const oldCoins = [];
            for (let i = 0; i < rg.oldCoinPubs.length; i++) {
                let refreshSession;
                const s = rg.refreshSessionPerCoin[i];
                if (s) {
                    refreshSession = {
                        new_denoms: s.newDenoms.map((x) => ({
                            count: x.count,
                            denom_pub_hash: x.denomPubHash,
                        })),
                        session_secret_seed: s.sessionSecretSeed,
                        noreveal_index: s.norevealIndex,
                    };
                }
                oldCoins.push({
                    coin_pub: rg.oldCoinPubs[i],
                    estimated_output_amount: Amounts.stringify(rg.estimatedOutputPerCoin[i]),
                    finished: rg.statusPerCoin[i] === RefreshCoinStatus.Finished,
                    input_amount: Amounts.stringify(rg.inputPerCoin[i]),
                    refresh_session: refreshSession,
                });
            }
            backupRefreshGroups.push({
                reason: rg.reason,
                refresh_group_id: rg.refreshGroupId,
                timestamp_created: rg.timestampCreated,
                timestamp_finish: rg.timestampFinished,
                old_coins: oldCoins,
            });
        });
        const ts = AbsoluteTime.toTimestamp(AbsoluteTime.now());
        if (!bs.lastBackupTimestamp) {
            bs.lastBackupTimestamp = ts;
        }
        const backupBlob = {
            schema_id: "gnu-taler-wallet-backup-content",
            schema_version: BACKUP_VERSION_MAJOR,
            minor_version: BACKUP_VERSION_MINOR,
            exchanges: backupExchanges,
            exchange_details: backupExchangeDetails,
            wallet_root_pub: bs.walletRootPub,
            backup_providers: backupBackupProviders,
            current_device_id: bs.deviceId,
            proposals: backupProposals,
            purchases: backupPurchases,
            recoup_groups: backupRecoupGroups,
            refresh_groups: backupRefreshGroups,
            tips: backupTips,
            timestamp: bs.lastBackupTimestamp,
            trusted_auditors: {},
            trusted_exchanges: {},
            intern_table: {},
            error_reports: [],
            tombstones: [],
            // FIXME!
            withdrawal_groups: backupWithdrawalGroups,
        };
        // If the backup changed, we change our nonce and timestamp.
        let h = encodeCrock(hash(stringToBytes(canonicalJson(backupBlob))));
        if (h !== bs.lastBackupPlainHash) {
            logger$m.trace(`plain backup hash changed (from ${bs.lastBackupPlainHash}to ${h})`);
            bs.lastBackupTimestamp = ts;
            backupBlob.timestamp = ts;
            bs.lastBackupPlainHash = encodeCrock(hash(stringToBytes(canonicalJson(backupBlob))));
            bs.lastBackupNonce = encodeCrock(getRandomBytes(32));
            logger$m.trace(`setting timestamp to ${AbsoluteTime.toIsoString(AbsoluteTime.fromTimestamp(ts))} and nonce to ${bs.lastBackupNonce}`);
            await tx.config.put({
                key: WALLET_BACKUP_STATE_KEY,
                value: bs,
            });
        }
        else {
            logger$m.trace("backup hash did not change");
        }
        return backupBlob;
    });
}

/*
 This file is part of GNU Taler
 (C) 2020 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
var OperationAttemptResultType;
(function (OperationAttemptResultType) {
    OperationAttemptResultType["Finished"] = "finished";
    OperationAttemptResultType["Pending"] = "pending";
    OperationAttemptResultType["Error"] = "error";
    OperationAttemptResultType["Longpoll"] = "longpoll";
})(OperationAttemptResultType || (OperationAttemptResultType = {}));
var OperationAttemptResult;
(function (OperationAttemptResult) {
    function finishedEmpty() {
        return {
            type: OperationAttemptResultType.Finished,
            result: undefined,
        };
    }
    OperationAttemptResult.finishedEmpty = finishedEmpty;
})(OperationAttemptResult || (OperationAttemptResult = {}));
const defaultRetryPolicy = {
    backoffBase: 1.5,
    backoffDelta: Duration.fromSpec({ seconds: 1 }),
    maxTimeout: Duration.fromSpec({ minutes: 2 }),
};
function updateTimeout(r, p = defaultRetryPolicy) {
    const now = AbsoluteTime.now();
    if (now.t_ms === "never") {
        throw Error("assertion failed");
    }
    if (p.backoffDelta.d_ms === "forever") {
        r.nextRetry = { t_ms: "never" };
        return;
    }
    const nextIncrement = p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter);
    const t = now.t_ms +
        (p.maxTimeout.d_ms === "forever"
            ? nextIncrement
            : Math.min(p.maxTimeout.d_ms, nextIncrement));
    r.nextRetry = { t_ms: t };
}
var RetryInfo;
(function (RetryInfo) {
    function getDuration(r, p = defaultRetryPolicy) {
        if (!r) {
            // If we don't have any retry info, run immediately.
            return { d_ms: 0 };
        }
        if (p.backoffDelta.d_ms === "forever") {
            return { d_ms: "forever" };
        }
        const t = p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter);
        return {
            d_ms: p.maxTimeout.d_ms === "forever" ? t : Math.min(p.maxTimeout.d_ms, t),
        };
    }
    RetryInfo.getDuration = getDuration;
    function reset(p = defaultRetryPolicy) {
        const now = AbsoluteTime.now();
        const info = {
            firstTry: now,
            nextRetry: now,
            retryCounter: 0,
        };
        updateTimeout(info, p);
        return info;
    }
    RetryInfo.reset = reset;
    function increment(r, p = defaultRetryPolicy) {
        if (!r) {
            return reset(p);
        }
        const r2 = Object.assign({}, r);
        r2.retryCounter++;
        updateTimeout(r2, p);
        return r2;
    }
    RetryInfo.increment = increment;
})(RetryInfo || (RetryInfo = {}));
var RetryTags;
(function (RetryTags) {
    function forWithdrawal(wg) {
        return `${PendingTaskType.Withdraw}:${wg.withdrawalGroupId}`;
    }
    RetryTags.forWithdrawal = forWithdrawal;
    function forExchangeUpdate(exch) {
        return `${PendingTaskType.ExchangeUpdate}:${exch.baseUrl}`;
    }
    RetryTags.forExchangeUpdate = forExchangeUpdate;
    function forExchangeCheckRefresh(exch) {
        return `${PendingTaskType.ExchangeCheckRefresh}:${exch.baseUrl}`;
    }
    RetryTags.forExchangeCheckRefresh = forExchangeCheckRefresh;
    function forProposalClaim(pr) {
        return `${PendingTaskType.ProposalDownload}:${pr.proposalId}`;
    }
    RetryTags.forProposalClaim = forProposalClaim;
    function forTipPickup(tipRecord) {
        return `${PendingTaskType.TipPickup}:${tipRecord.walletTipId}`;
    }
    RetryTags.forTipPickup = forTipPickup;
    function forRefresh(refreshGroupRecord) {
        return `${PendingTaskType.TipPickup}:${refreshGroupRecord.refreshGroupId}`;
    }
    RetryTags.forRefresh = forRefresh;
    function forPay(purchaseRecord) {
        return `${PendingTaskType.Pay}:${purchaseRecord.proposalId}`;
    }
    RetryTags.forPay = forPay;
    function forRefundQuery(purchaseRecord) {
        return `${PendingTaskType.RefundQuery}:${purchaseRecord.proposalId}`;
    }
    RetryTags.forRefundQuery = forRefundQuery;
    function forRecoup(recoupRecord) {
        return `${PendingTaskType.Recoup}:${recoupRecord.recoupGroupId}`;
    }
    RetryTags.forRecoup = forRecoup;
    function forDeposit(depositRecord) {
        return `${PendingTaskType.Deposit}:${depositRecord.depositGroupId}`;
    }
    RetryTags.forDeposit = forDeposit;
    function forBackup(backupRecord) {
        return `${PendingTaskType.Backup}:${backupRecord.baseUrl}`;
    }
    RetryTags.forBackup = forBackup;
    function byPaymentProposalId(proposalId) {
        return `${PendingTaskType.Pay}:${proposalId}`;
    }
    RetryTags.byPaymentProposalId = byPaymentProposalId;
})(RetryTags || (RetryTags = {}));
async function scheduleRetryInTx(ws, tx, opId, errorDetail) {
    let retryRecord = await tx.operationRetries.get(opId);
    if (!retryRecord) {
        retryRecord = {
            id: opId,
            retryInfo: RetryInfo.reset(),
        };
        if (errorDetail) {
            retryRecord.lastError = errorDetail;
        }
    }
    else {
        retryRecord.retryInfo = RetryInfo.increment(retryRecord.retryInfo);
        if (errorDetail) {
            retryRecord.lastError = errorDetail;
        }
        else {
            delete retryRecord.lastError;
        }
    }
    await tx.operationRetries.put(retryRecord);
}
async function scheduleRetry(ws, opId, errorDetail) {
    return await ws.db
        .mktx((x) => [x.operationRetries])
        .runReadWrite(async (tx) => {
        tx.operationRetries;
        scheduleRetryInTx(ws, tx, opId, errorDetail);
    });
}
/**
 * Run an operation handler, expect a success result and extract the success value.
 */
async function runOperationHandlerForResult(res) {
    switch (res.type) {
        case OperationAttemptResultType.Finished:
            return res.result;
        case OperationAttemptResultType.Error:
            throw TalerError.fromUncheckedDetail(res.errorDetail);
        default:
            throw Error(`unexpected operation result (${res.type})`);
    }
}

/*
 Copyright 2019 Florian Dold

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
 */
/** @public */
var ResultLevel;
(function (ResultLevel) {
    ResultLevel[ResultLevel["OnlyCount"] = 0] = "OnlyCount";
    ResultLevel[ResultLevel["OnlyKeys"] = 1] = "OnlyKeys";
    ResultLevel[ResultLevel["Full"] = 2] = "Full";
})(ResultLevel || (ResultLevel = {}));
/** @public */
var StoreLevel;
(function (StoreLevel) {
    StoreLevel[StoreLevel["NoOverwrite"] = 0] = "NoOverwrite";
    StoreLevel[StoreLevel["AllowOverwrite"] = 1] = "AllowOverwrite";
    StoreLevel[StoreLevel["UpdateExisting"] = 2] = "UpdateExisting";
})(StoreLevel || (StoreLevel = {}));

/*
 Copyright 2017 Jeremy Scheff
 Copyright 20201 Taler Systems SA

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
*/
/**
 * Check that a key could be injected into a value.
 *
 * https://www.w3.org/TR/IndexedDB/#check-that-a-key-could-be-injected-into-a-value
 */
function canInjectKey(keyPath, value) {
    if (Array.isArray(keyPath)) {
        throw new Error("The key paths used in this section are always strings and never sequences, since it is not possible to create a object store which has a key generator and also has a key path that is a sequence.");
    }
    const identifiers = keyPath.split(".");
    if (identifiers.length === 0) {
        throw new Error("Assert: identifiers is not empty");
    }
    identifiers.pop();
    for (const identifier of identifiers) {
        if (value === null ||
            (typeof value !== "object" && !Array.isArray(value))) {
            return false;
        }
        const hop = value.hasOwnProperty(identifier);
        if (!hop) {
            return true;
        }
        value = value[identifier];
    }
    return value !== null && (typeof value === "object" || Array.isArray(value));
}

/*
 Copyright 2017 Jeremy Scheff

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
 */
/* tslint:disable: max-classes-per-file max-line-length */
const messages = {
    AbortError: "A request was aborted, for example through a call to IDBTransaction.abort.",
    ConstraintError: "A mutation operation in the transaction failed because a constraint was not satisfied. For example, an object such as an object store or index already exists and a request attempted to create a new one.",
    DataCloneError: "The data being stored could not be cloned by the internal structured cloning algorithm.",
    DataError: "Data provided to an operation does not meet requirements.",
    InvalidAccessError: "An invalid operation was performed on an object. For example transaction creation attempt was made, but an empty scope was provided.",
    InvalidStateError: "An operation was called on an object on which it is not allowed or at a time when it is not allowed. Also occurs if a request is made on a source object that has been deleted or removed. Use TransactionInactiveError or ReadOnlyError when possible, as they are more specific variations of InvalidStateError.",
    NotFoundError: "The operation failed because the requested database object could not be found. For example, an object store did not exist but was being opened.",
    ReadOnlyError: 'The mutating operation was attempted in a "readonly" transaction.',
    TransactionInactiveError: "A request was placed against a transaction which is currently not active, or which is finished.",
    VersionError: "An attempt was made to open a database using a lower version than the existing version.",
};
class AbortError extends Error {
    constructor(message = messages.AbortError) {
        super();
        Object.setPrototypeOf(this, ConstraintError.prototype);
        this.name = "AbortError";
        this.message = message;
    }
}
class ConstraintError extends Error {
    constructor(message = messages.ConstraintError) {
        super();
        Object.setPrototypeOf(this, ConstraintError.prototype);
        this.name = "ConstraintError";
        this.message = message;
    }
}
class DataCloneError extends Error {
    constructor(message = messages.DataCloneError) {
        super();
        Object.setPrototypeOf(this, DataCloneError.prototype);
        this.name = "DataCloneError";
        this.message = message;
    }
}
class DataError extends Error {
    constructor(message = messages.DataError) {
        super();
        Object.setPrototypeOf(this, DataError.prototype);
        this.name = "DataError";
        this.message = message;
    }
}
class InvalidAccessError extends Error {
    constructor(message = messages.InvalidAccessError) {
        super();
        Object.setPrototypeOf(this, InvalidAccessError.prototype);
        this.name = "InvalidAccessError";
        this.message = message;
    }
}
class InvalidStateError extends Error {
    constructor(message = messages.InvalidStateError) {
        super();
        Object.setPrototypeOf(this, InvalidStateError.prototype);
        this.name = "InvalidStateError";
        this.message = message;
    }
}
class NotFoundError extends Error {
    constructor(message = messages.NotFoundError) {
        super();
        Object.setPrototypeOf(this, NotFoundError.prototype);
        this.name = "NotFoundError";
        this.message = message;
    }
}
class ReadOnlyError extends Error {
    constructor(message = messages.ReadOnlyError) {
        super();
        Object.setPrototypeOf(this, ReadOnlyError.prototype);
        this.name = "ReadOnlyError";
        this.message = message;
    }
}
class TransactionInactiveError extends Error {
    constructor(message = messages.TransactionInactiveError) {
        super();
        Object.setPrototypeOf(this, TransactionInactiveError.prototype);
        this.name = "TransactionInactiveError";
        this.message = message;
    }
}
class VersionError extends Error {
    constructor(message = messages.VersionError) {
        super();
        Object.setPrototypeOf(this, VersionError.prototype);
        this.name = "VersionError";
        this.message = message;
    }
}

/*
 Copyright 2017 Jeremy Scheff

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
 */
// https://www.w3.org/TR/IndexedDB-2/#convert-a-value-to-a-key
function valueToKey(input, seen) {
    if (typeof input === "number") {
        if (isNaN(input)) {
            throw new DataError();
        }
        return input;
    }
    else if (input instanceof Date) {
        const ms = input.valueOf();
        if (isNaN(ms)) {
            throw new DataError();
        }
        return new Date(ms);
    }
    else if (typeof input === "string") {
        return input;
    }
    else if (input instanceof ArrayBuffer ||
        (typeof ArrayBuffer !== "undefined" &&
            ArrayBuffer.isView &&
            ArrayBuffer.isView(input))) {
        if (input instanceof ArrayBuffer) {
            return new Uint8Array(input).buffer;
        }
        return new Uint8Array(input.buffer).buffer;
    }
    else if (Array.isArray(input)) {
        if (seen === undefined) {
            seen = new Set();
        }
        else if (seen.has(input)) {
            throw new DataError();
        }
        seen.add(input);
        const keys = [];
        for (let i = 0; i < input.length; i++) {
            const hop = input.hasOwnProperty(i);
            if (!hop) {
                throw new DataError();
            }
            const entry = input[i];
            const key = valueToKey(entry, seen);
            keys.push(key);
        }
        return keys;
    }
    else {
        throw new DataError();
    }
}

/*
 Copyright 2017 Jeremy Scheff

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
 */
const getType = (x) => {
    if (typeof x === "number") {
        return "Number";
    }
    if (x instanceof Date) {
        return "Date";
    }
    if (Array.isArray(x)) {
        return "Array";
    }
    if (typeof x === "string") {
        return "String";
    }
    if (x instanceof ArrayBuffer) {
        return "Binary";
    }
    throw new DataError();
};
// https://w3c.github.io/IndexedDB/#compare-two-keys
const compareKeys = (first, second) => {
    if (second === undefined) {
        throw new TypeError();
    }
    first = valueToKey(first);
    second = valueToKey(second);
    const t1 = getType(first);
    const t2 = getType(second);
    if (t1 !== t2) {
        if (t1 === "Array") {
            return 1;
        }
        if (t1 === "Binary" &&
            (t2 === "String" || t2 === "Date" || t2 === "Number")) {
            return 1;
        }
        if (t1 === "String" && (t2 === "Date" || t2 === "Number")) {
            return 1;
        }
        if (t1 === "Date" && t2 === "Number") {
            return 1;
        }
        return -1;
    }
    if (t1 === "Binary") {
        first = new Uint8Array(first);
        second = new Uint8Array(second);
    }
    if (t1 === "Array" || t1 === "Binary") {
        const length = Math.min(first.length, second.length);
        for (let i = 0; i < length; i++) {
            const result = compareKeys(first[i], second[i]);
            if (result !== 0) {
                return result;
            }
        }
        if (first.length > second.length) {
            return 1;
        }
        if (first.length < second.length) {
            return -1;
        }
        return 0;
    }
    if (t1 === "Date") {
        if (first.getTime() === second.getTime()) {
            return 0;
        }
    }
    else {
        if (first === second) {
            return 0;
        }
    }
    return first > second ? 1 : -1;
};

/*
 Copyright 2017 Jeremy Scheff
 Copyright 2019 Florian Dold

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
*/
// https://heycam.github.io/webidl/#EnforceRange
const enforceRange = (num, type) => {
    const min = 0;
    const max = type === "unsigned long" ? 4294967295 : 9007199254740991;
    if (isNaN(num) || num < min || num > max) {
        throw new TypeError();
    }
    if (num >= 0) {
        return Math.floor(num);
    }
};

/*
 * Copyright 2017 Jeremy Scheff
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
// Would be nicer to sublcass Array, but I'd have to sacrifice Node 4 support to do that.
const fakeDOMStringList = (arr) => {
    const arr2 = arr.slice();
    Object.defineProperty(arr2, "contains", {
        value: (value) => arr2.indexOf(value) >= 0,
    });
    Object.defineProperty(arr2, "item", {
        value: (i) => {
            if (i < 0 || i >= arr2.length) {
                return null;
            }
            return arr2[i];
        },
    });
    return arr2;
};

/*
 Copyright 2017 Jeremy Scheff

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
*/
class FakeEvent {
    constructor(type, eventInitDict = {}) {
        this.eventPath = [];
        this.NONE = 0;
        this.CAPTURING_PHASE = 1;
        this.AT_TARGET = 2;
        this.BUBBLING_PHASE = 3;
        // Flags
        this.propagationStopped = false;
        this.immediatePropagationStopped = false;
        this.canceled = false;
        this.initialized = true;
        this.dispatched = false;
        this.target = null;
        this.currentTarget = null;
        this.eventPhase = 0;
        this.defaultPrevented = false;
        this.isTrusted = false;
        this.timeStamp = Date.now();
        this.cancelBubble = false;
        this.composed = false;
        this.returnValue = false;
        this.type = type;
        this.bubbles =
            eventInitDict.bubbles !== undefined ? eventInitDict.bubbles : false;
        this.cancelable =
            eventInitDict.cancelable !== undefined ? eventInitDict.cancelable : false;
    }
    get srcElement() {
        return this.target;
    }
    composedPath() {
        throw new Error("Method not implemented.");
    }
    initEvent(type, bubbles, cancelable) {
        throw new Error("Method not implemented.");
    }
    preventDefault() {
        if (this.cancelable) {
            this.canceled = true;
        }
    }
    stopPropagation() {
        this.propagationStopped = true;
    }
    stopImmediatePropagation() {
        this.propagationStopped = true;
        this.immediatePropagationStopped = true;
    }
}

/*
 Copyright 2017 Jeremy Scheff

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
 */
const stopped = (event, listener) => {
    return (event.immediatePropagationStopped ||
        (event.eventPhase === event.CAPTURING_PHASE &&
            listener.capture === false) ||
        (event.eventPhase === event.BUBBLING_PHASE && listener.capture === true));
};
// http://www.w3.org/TR/dom/#concept-event-listener-invoke
const invokeEventListeners = (event, obj) => {
    event.currentTarget = obj;
    // The callback might cause obj.listeners to mutate as we traverse it.
    // Take a copy of the array so that nothing sneaks in and we don't lose
    // our place.
    for (const listener of obj.listeners.slice()) {
        if (event.type !== listener.type || stopped(event, listener)) {
            continue;
        }
        // @ts-ignore
        listener.callback.call(event.currentTarget, event);
    }
    const typeToProp = {
        abort: "onabort",
        blocked: "onblocked",
        complete: "oncomplete",
        error: "onerror",
        success: "onsuccess",
        upgradeneeded: "onupgradeneeded",
        versionchange: "onversionchange",
    };
    const prop = typeToProp[event.type];
    if (prop === undefined) {
        throw new Error(`Unknown event type: "${event.type}"`);
    }
    const callback = event.currentTarget[prop];
    if (callback) {
        const listener = {
            callback,
            capture: false,
            type: event.type,
        };
        if (!stopped(event, listener)) {
            // @ts-ignore
            listener.callback.call(event.currentTarget, event);
        }
    }
};
/** @public */
class FakeEventTarget {
    constructor() {
        this.listeners = [];
        // These will be overridden in individual subclasses and made not readonly
        this.onabort = null;
        this.onblocked = null;
        this.oncomplete = null;
        this.onerror = null;
        this.onsuccess = null;
        this.onclose = null;
        this.onupgradeneeded = null;
        this.onversionchange = null;
    }
    addEventListener(type, listener, capture = false) {
        if (typeof listener === "function") {
            this.listeners.push({
                callback: listener,
                capture,
                type,
            });
        }
        else if (typeof listener === "object" && listener != null) {
            this.listeners.push({
                callback: (e) => listener.handleEvent(e),
                capture,
                type,
            });
        }
    }
    removeEventListener(type, callback, capture = false) {
        const i = this.listeners.findIndex((listener) => {
            return (listener.type === type &&
                listener.callback === callback &&
                listener.capture === capture);
        });
        this.listeners.splice(i, 1);
    }
    // http://www.w3.org/TR/dom/#dispatching-events
    dispatchEvent(event) {
        if (!(event instanceof FakeEvent)) {
            throw Error("dispatchEvent only works with FakeEvent");
        }
        const fe = event;
        if (event.dispatched || !event.initialized) {
            throw new InvalidStateError("The object is in an invalid state.");
        }
        fe.isTrusted = false;
        fe.dispatched = true;
        fe.target = this;
        // NOT SURE WHEN THIS SHOULD BE SET        event.eventPath = [];
        fe.eventPhase = event.CAPTURING_PHASE;
        if (FakeEventTarget.enableTracing) {
            console.log(`dispatching '${event.type}' event along path with ${event.eventPath.length} elements`);
        }
        for (const obj of event.eventPath) {
            if (!event.propagationStopped) {
                invokeEventListeners(event, obj);
            }
        }
        fe.eventPhase = event.AT_TARGET;
        if (!event.propagationStopped) {
            invokeEventListeners(event, fe.target);
        }
        if (event.bubbles) {
            fe.eventPath.reverse();
            fe.eventPhase = event.BUBBLING_PHASE;
            if (fe.eventPath.length === 0 && event.type === "error") {
                console.error("Unhandled error event: ", event.target);
            }
            for (const obj of event.eventPath) {
                if (!event.propagationStopped) {
                    invokeEventListeners(event, obj);
                }
            }
        }
        fe.dispatched = false;
        fe.eventPhase = event.NONE;
        fe.currentTarget = null;
        if (event.canceled) {
            return false;
        }
        return true;
    }
}
FakeEventTarget.enableTracing = false;

/*
 Copyright 2017 Jeremy Scheff
 Copyright 2019 Florian Dold

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
*/
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-extracting-a-key-from-a-value-using-a-key-path
const extractKey = (keyPath, value) => {
    if (Array.isArray(keyPath)) {
        const result = [];
        for (let item of keyPath) {
            // This doesn't make sense to me based on the spec, but it is needed to pass the W3C KeyPath tests (see same
            // comment in validateKeyPath)
            if (item !== undefined &&
                item !== null &&
                typeof item !== "string" &&
                item.toString) {
                item = item.toString();
            }
            result.push(valueToKey(extractKey(item, value)));
        }
        return result;
    }
    if (keyPath === "") {
        return value;
    }
    let remainingKeyPath = keyPath;
    let object = value;
    while (remainingKeyPath !== null) {
        let identifier;
        const i = remainingKeyPath.indexOf(".");
        if (i >= 0) {
            identifier = remainingKeyPath.slice(0, i);
            remainingKeyPath = remainingKeyPath.slice(i + 1);
        }
        else {
            identifier = remainingKeyPath;
            remainingKeyPath = null;
        }
        if (object == null || !object.hasOwnProperty(identifier)) {
            return;
        }
        object = object[identifier];
    }
    return object;
};

/*
 Copyright 2019 Florian Dold

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
*/
const { toString: toStr } = {};
const hasOwn = {}.hasOwnProperty;
const getProto = Object.getPrototypeOf;
const fnToString = hasOwn.toString;
function toStringTag(val) {
    return toStr.call(val).slice(8, -1);
}
function hasConstructorOf(a, b) {
    if (!a || typeof a !== "object") {
        return false;
    }
    const proto = getProto(a);
    if (!proto) {
        return b === null;
    }
    const Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
    if (typeof Ctor !== "function") {
        return b === null;
    }
    if (b === Ctor) {
        return true;
    }
    if (b !== null && fnToString.call(Ctor) === fnToString.call(b)) {
        return true;
    }
    return false;
}
function isPlainObject$1(val) {
    if (!val || toStringTag(val) !== "Object") {
        return false;
    }
    const proto = getProto(val);
    if (!proto) {
        // `Object.create(null)`
        return true;
    }
    return hasConstructorOf(val, Object);
}
function isUserObject(val) {
    if (!val || toStringTag(val) !== "Object") {
        return false;
    }
    const proto = getProto(val);
    if (!proto) {
        // `Object.create(null)`
        return true;
    }
    return hasConstructorOf(val, Object) || isUserObject(proto);
}
function copyBuffer(cur) {
    if (cur instanceof Buffer) {
        return Buffer.from(cur);
    }
    return new cur.constructor(cur.buffer.slice(), cur.byteOffset, cur.length);
}
function checkCloneableOrThrow(x) {
    if (x == null)
        return;
    if (typeof x !== "object" && typeof x !== "function")
        return;
    if (x instanceof Date)
        return;
    if (Array.isArray(x))
        return;
    if (x instanceof Map)
        return;
    if (x instanceof Set)
        return;
    if (isUserObject(x))
        return;
    if (isPlainObject$1(x))
        return;
    throw new DataCloneError();
}
function mkDeepClone() {
    const refs = [];
    const refsNew = [];
    return clone;
    function cloneArray(a) {
        var keys = Object.keys(a);
        var a2 = new Array(keys.length);
        refs.push(a);
        refsNew.push(a2);
        for (var i = 0; i < keys.length; i++) {
            var k = keys[i];
            var cur = a[k];
            checkCloneableOrThrow(cur);
            if (typeof cur !== "object" || cur === null) {
                a2[k] = cur;
            }
            else if (cur instanceof Date) {
                a2[k] = new Date(cur);
            }
            else if (ArrayBuffer.isView(cur)) {
                a2[k] = copyBuffer(cur);
            }
            else {
                var index = refs.indexOf(cur);
                if (index !== -1) {
                    a2[k] = refsNew[index];
                }
                else {
                    a2[k] = clone(cur);
                }
            }
        }
        refs.pop();
        refsNew.pop();
        return a2;
    }
    function clone(o) {
        checkCloneableOrThrow(o);
        if (typeof o !== "object" || o === null)
            return o;
        if (o instanceof Date)
            return new Date(o);
        if (Array.isArray(o))
            return cloneArray(o);
        if (o instanceof Map)
            return new Map(cloneArray(Array.from(o)));
        if (o instanceof Set)
            return new Set(cloneArray(Array.from(o)));
        var o2 = {};
        refs.push(o);
        refsNew.push(o2);
        for (var k in o) {
            if (Object.hasOwnProperty.call(o, k) === false)
                continue;
            var cur = o[k];
            checkCloneableOrThrow(cur);
            if (typeof cur !== "object" || cur === null) {
                o2[k] = cur;
            }
            else if (cur instanceof Date) {
                o2[k] = new Date(cur);
            }
            else if (cur instanceof Map) {
                o2[k] = new Map(cloneArray(Array.from(cur)));
            }
            else if (cur instanceof Set) {
                o2[k] = new Set(cloneArray(Array.from(cur)));
            }
            else if (ArrayBuffer.isView(cur)) {
                o2[k] = copyBuffer(cur);
            }
            else {
                var i = refs.indexOf(cur);
                if (i !== -1) {
                    o2[k] = refsNew[i];
                }
                else {
                    o2[k] = clone(cur);
                }
            }
        }
        refs.pop();
        refsNew.pop();
        return o2;
    }
}
/**
 * Check if an object is deeply cloneable.
 * Only called for the side-effect of throwing an exception.
 */
function mkDeepCloneCheckOnly() {
    const refs = [];
    return clone;
    function cloneArray(a) {
        var keys = Object.keys(a);
        refs.push(a);
        for (var i = 0; i < keys.length; i++) {
            var k = keys[i];
            var cur = a[k];
            checkCloneableOrThrow(cur);
            if (typeof cur !== "object" || cur === null) ;
            else if (cur instanceof Date) ;
            else if (ArrayBuffer.isView(cur)) ;
            else {
                var index = refs.indexOf(cur);
                if (index !== -1) ;
                else {
                    clone(cur);
                }
            }
        }
        refs.pop();
    }
    function clone(o) {
        checkCloneableOrThrow(o);
        if (typeof o !== "object" || o === null)
            return o;
        if (o instanceof Date)
            return;
        if (Array.isArray(o))
            return cloneArray(o);
        if (o instanceof Map)
            return cloneArray(Array.from(o));
        if (o instanceof Set)
            return cloneArray(Array.from(o));
        refs.push(o);
        for (var k in o) {
            if (Object.hasOwnProperty.call(o, k) === false)
                continue;
            var cur = o[k];
            checkCloneableOrThrow(cur);
            if (typeof cur !== "object" || cur === null) ;
            else if (cur instanceof Date) ;
            else if (cur instanceof Map) {
                cloneArray(Array.from(cur));
            }
            else if (cur instanceof Set) {
                cloneArray(Array.from(cur));
            }
            else if (ArrayBuffer.isView(cur)) ;
            else {
                var i = refs.indexOf(cur);
                if (i !== -1) ;
                else {
                    clone(cur);
                }
            }
        }
        refs.pop();
    }
}
function internalEncapsulate(val, outRoot, path, memo, types) {
    const memoPath = memo.get(val);
    if (memoPath) {
        types.push([path, "ref"]);
        return memoPath;
    }
    if (val === null) {
        return null;
    }
    if (val === undefined) {
        types.push([path, "undef"]);
        return 0;
    }
    if (Array.isArray(val)) {
        memo.set(val, path);
        const outArr = [];
        let special = false;
        for (const x in val) {
            const n = Number(x);
            if (n < 0 || n >= val.length || Number.isNaN(n)) {
                special = true;
                break;
            }
        }
        if (special) {
            types.push([path, "array"]);
        }
        for (const x in val) {
            const p = [...path, x];
            outArr[x] = internalEncapsulate(val[x], outRoot, p, memo, types);
        }
        return outArr;
    }
    if (val instanceof Date) {
        types.push([path, "date"]);
        return val.getTime();
    }
    if (isUserObject(val) || isPlainObject$1(val)) {
        memo.set(val, path);
        const outObj = {};
        for (const x in val) {
            const p = [...path, x];
            outObj[x] = internalEncapsulate(val[x], outRoot, p, memo, types);
        }
        return outObj;
    }
    if (typeof val === "bigint") {
        types.push([path, "bigint"]);
        return val.toString();
    }
    if (typeof val === "boolean") {
        return val;
    }
    if (typeof val === "number") {
        return val;
    }
    if (typeof val === "string") {
        return val;
    }
    throw Error();
}
/**
 * Encapsulate a cloneable value into a plain JSON object.
 */
function structuredEncapsulate(val) {
    const outRoot = {};
    const types = [];
    let res;
    res = internalEncapsulate(val, outRoot, [], new Map(), types);
    if (res === null) {
        return res;
    }
    // We need to further encapsulate the outer layer
    if (Array.isArray(res) ||
        typeof res !== "object" ||
        "$" in res ||
        "$types" in res) {
        res = { $: res };
    }
    if (types.length > 0) {
        res["$types"] = types;
    }
    return res;
}
function internalStructuredRevive(val) {
    var _a;
    val = JSON.parse(JSON.stringify(val));
    if (val === null) {
        return null;
    }
    if (typeof val === "number") {
        return val;
    }
    if (typeof val === "string") {
        return val;
    }
    if (typeof val === "boolean") {
        return val;
    }
    if (!isPlainObject$1(val)) {
        throw Error();
    }
    let types = (_a = val.$types) !== null && _a !== void 0 ? _a : [];
    delete val.$types;
    let outRoot;
    if ("$" in val) {
        outRoot = val.$;
    }
    else {
        outRoot = val;
    }
    function mutatePath(path, f) {
        if (path.length == 0) {
            outRoot = f(outRoot);
            return;
        }
        let obj = outRoot;
        for (let i = 0; i < path.length - 1; i++) {
            const n = path[i];
            if (!(n in obj)) {
                obj[n] = {};
            }
            obj = obj[n];
        }
        const last = path[path.length - 1];
        obj[last] = f(obj[last]);
    }
    function lookupPath(path) {
        let obj = outRoot;
        for (const n of path) {
            obj = obj[n];
        }
        return obj;
    }
    for (const [path, type] of types) {
        switch (type) {
            case "bigint": {
                mutatePath(path, (x) => BigInt(x));
                break;
            }
            case "array": {
                mutatePath(path, (x) => {
                    const newArr = [];
                    for (const k in x) {
                        newArr[k] = x[k];
                    }
                    return newArr;
                });
                break;
            }
            case "date": {
                mutatePath(path, (x) => new Date(x));
                break;
            }
            case "undef": {
                mutatePath(path, (x) => undefined);
                break;
            }
            case "ref": {
                mutatePath(path, (x) => lookupPath(x));
                break;
            }
            default:
                throw Error(`type '${type}' not implemented`);
        }
    }
    return outRoot;
}
function structuredRevive(val) {
    return internalStructuredRevive(val);
}
/**
 * Structured clone for IndexedDB.
 */
function structuredClone(val) {
    return mkDeepClone()(val);
}
/**
 * Structured clone for IndexedDB.
 */
function checkStructuredCloneOrThrow(val) {
    return mkDeepCloneCheckOnly()(val);
}

/*
 Copyright 2019 Florian Dold

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
*/
function injectKey(keyPath, value, key) {
    if (Array.isArray(keyPath)) {
        throw new Error("The key paths used in this section are always strings and never sequences, since it is not possible to create a object store which has a key generator and also has a key path that is a sequence.");
    }
    const newValue = structuredClone(value);
    // Position inside the new value where we'll place the key eventually.
    let ptr = newValue;
    const identifiers = keyPath.split(".");
    if (identifiers.length === 0) {
        throw new Error("Assert: identifiers is not empty");
    }
    const lastIdentifier = identifiers.pop();
    if (lastIdentifier === null || lastIdentifier === undefined) {
        throw Error();
    }
    for (const identifier of identifiers) {
        if (typeof ptr !== "object" && !Array.isArray(ptr)) {
            throw new Error("can't inject key");
        }
        const hop = value.hasOwnProperty(identifier);
        if (!hop) {
            ptr[identifier] = {};
        }
        ptr = ptr[identifier];
    }
    if (!(typeof ptr === "object" || Array.isArray(ptr))) {
        throw new Error("can't inject key");
    }
    ptr[lastIdentifier] = structuredClone(key);
    return newValue;
}
function makeStoreKeyValue(value, key, currentKeyGenerator, autoIncrement, keyPath) {
    const haveKey = key !== null && key !== undefined;
    const haveKeyPath = keyPath !== null && keyPath !== undefined;
    // This models a decision table on (haveKey, haveKeyPath, autoIncrement)
    try {
        value = structuredClone(value);
    }
    catch (e) {
        throw new DataCloneError();
    }
    if (haveKey) {
        if (haveKeyPath) {
            // (yes, yes, no)
            // (yes, yes, yes)
            throw new DataError();
        }
        else {
            if (autoIncrement) {
                // (yes, no, yes)
                key = valueToKey(key);
                let updatedKeyGenerator;
                if (typeof key !== "number") {
                    updatedKeyGenerator = currentKeyGenerator;
                }
                else {
                    updatedKeyGenerator = key;
                }
                return {
                    key: key,
                    value: value,
                    updatedKeyGenerator,
                };
            }
            else {
                // (yes, no, no)
                return {
                    key: key,
                    value: value,
                    updatedKeyGenerator: currentKeyGenerator,
                };
            }
        }
    }
    else {
        if (haveKeyPath) {
            if (autoIncrement) {
                // (no, yes, yes)
                let updatedKeyGenerator;
                const maybeInlineKey = extractKey(keyPath, value);
                if (maybeInlineKey === undefined) {
                    value = injectKey(keyPath, value, currentKeyGenerator);
                    key = currentKeyGenerator;
                    updatedKeyGenerator = currentKeyGenerator + 1;
                }
                else if (typeof maybeInlineKey === "number") {
                    key = maybeInlineKey;
                    if (maybeInlineKey >= currentKeyGenerator) {
                        updatedKeyGenerator = maybeInlineKey + 1;
                    }
                    else {
                        updatedKeyGenerator = currentKeyGenerator;
                    }
                }
                else {
                    key = maybeInlineKey;
                    updatedKeyGenerator = currentKeyGenerator;
                }
                return {
                    key: key,
                    value: value,
                    updatedKeyGenerator,
                };
            }
            else {
                // (no, yes, no)
                key = extractKey(keyPath, value);
                key = valueToKey(key);
                return {
                    key: key,
                    value: value,
                    updatedKeyGenerator: currentKeyGenerator,
                };
            }
        }
        else {
            if (autoIncrement) {
                // (no, no, yes)
                return {
                    key: currentKeyGenerator,
                    value: value,
                    updatedKeyGenerator: currentKeyGenerator + 1,
                };
            }
            else {
                // (no, no, no)
                throw new DataError();
            }
        }
    }
}

/*
 Copyright 2017 Jeremy Scheff
 Copyright 2019 Florian Dold

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
*/
function normalizeKeyPath(keyPath) {
    if (Array.isArray(keyPath)) {
        const path = [];
        for (let item of keyPath) {
            // This doesn't make sense to me based on the spec, but it is needed to pass the W3C KeyPath tests (see same
            // comment in validateKeyPath)
            if (item !== undefined &&
                item !== null &&
                typeof item !== "string" &&
                item.toString) {
                item = item.toString();
            }
            path.push(item);
        }
        return path;
    }
    return keyPath;
}

/*
 Copyright 2019 Florian Dold

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
*/
function openPromise() {
    let resolve;
    let reject;
    const promise = new Promise((resolve2, reject2) => {
        resolve = resolve2;
        reject = reject2;
    });
    if (!resolve) {
        throw Error("broken invariant");
    }
    if (!reject) {
        throw Error("broken invariant");
    }
    return { promise, resolve, reject };
}

/*
  Copyright 2019 Florian Dold

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  or implied. See the License for the specific language governing
  permissions and limitations under the License.
 */
function queueTask(fn) {
    let called = false;
    const callFirst = () => {
        if (called) {
            return;
        }
        called = true;
        fn();
    };
    // We must schedule both of these,
    // since on node, there is no guarantee
    // that a setImmediate function that is registered
    // before a setTimeout function is called first.
    setImmediate(callFirst);
    setTimeout(callFirst, 0);
}

/*
 Copyright 2017 Jeremy Scheff

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
*/
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-valid-key-path
const validateKeyPath = (keyPath, parent) => {
    // This doesn't make sense to me based on the spec, but it is needed to pass the W3C KeyPath tests (see same
    // comment in extractKey)
    let myKeyPath = keyPath;
    if (myKeyPath !== undefined &&
        myKeyPath !== null &&
        typeof myKeyPath !== "string" &&
        myKeyPath.toString &&
        (parent === "array" || !Array.isArray(myKeyPath))) {
        myKeyPath = myKeyPath.toString();
    }
    if (typeof myKeyPath === "string") {
        if (myKeyPath === "" && parent !== "string") {
            return;
        }
        try {
            // https://mathiasbynens.be/demo/javascript-identifier-regex for ECMAScript 5.1 / Unicode v7.0.0, with
            // reserved words at beginning removed
            // tslint:disable-next-line max-line-length
            const validIdentifierRegex = /^(?:[\$A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC])(?:[\$0-9A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B2\u08E4-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA69D\uA69F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2D\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC])*$/;
            if (myKeyPath.length >= 1 && validIdentifierRegex.test(myKeyPath)) {
                return;
            }
        }
        catch (err) {
            throw new SyntaxError(err.message);
        }
        if (myKeyPath.indexOf(" ") >= 0) {
            throw new SyntaxError("The keypath argument contains an invalid key path (no spaces allowed).");
        }
    }
    if (Array.isArray(myKeyPath) && myKeyPath.length > 0) {
        if (parent) {
            // No nested arrays
            throw new SyntaxError("The keypath argument contains an invalid key path (nested arrays).");
        }
        for (const part of myKeyPath) {
            validateKeyPath(part, "array");
        }
        return;
    }
    else if (typeof myKeyPath === "string" && myKeyPath.indexOf(".") >= 0) {
        myKeyPath = myKeyPath.split(".");
        for (const part of myKeyPath) {
            validateKeyPath(part, "string");
        }
        return;
    }
    throw new SyntaxError();
};

/*
 Copyright 2017 Jeremy Scheff
 Copyright 2019-2021 Taler Systems S.A.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
 */
function simplifyRange(r) {
    if (r && typeof r === "object" && "lower" in r) {
        return r;
    }
    if (r === undefined || r === null) {
        return null;
    }
    return BridgeIDBKeyRange.bound(r, r, false, false);
}
/**
 * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#cursor
 *
 * @public
 */
class BridgeIDBCursor {
    constructor(source, objectStoreName, indexName, range, direction, request, keyOnly) {
        this._gotValue = false;
        // Key of previously returned record
        this._indexPosition = undefined;
        this._objectStorePosition = undefined;
        this._key = undefined;
        this._primaryKey = undefined;
        this._value = undefined;
        this._indexName = indexName;
        this._objectStoreName = objectStoreName;
        this._range = range;
        this._source = source;
        this._direction = direction;
        this._request = request;
        this._keyOnly = keyOnly;
    }
    get _effectiveObjectStore() {
        if (this.source instanceof BridgeIDBObjectStore) {
            return this.source;
        }
        return this.source._objectStore;
    }
    get _backend() {
        return this._source._backend;
    }
    // Read only properties
    get source() {
        return this._source;
    }
    set source(val) {
        /* For babel */
    }
    get direction() {
        return this._direction;
    }
    set direction(val) {
        /* For babel */
    }
    get key() {
        const k = this._key;
        if (k === null || k === undefined) {
            throw Error("no key");
        }
        return k;
    }
    set key(val) {
        /* For babel */
    }
    get primaryKey() {
        const k = this._primaryKey;
        if (k === 0 || k === undefined) {
            throw Error("no key");
        }
        return k;
    }
    set primaryKey(val) {
        /* For babel */
    }
    get _isValueCursor() {
        return false;
    }
    /**
     * https://w3c.github.io/IndexedDB/#iterate-a-cursor
     */
    _iterate(key, primaryKey) {
        return __awaiter(this, void 0, void 0, function* () {
            BridgeIDBFactory.enableTracing &&
                console.log(`iterating cursor os=${this._objectStoreName},idx=${this._indexName}`);
            BridgeIDBFactory.enableTracing &&
                console.log("cursor type ", this.toString());
            const isIndex = this._indexName !== undefined;
            const recordGetRequest = {
                direction: this.direction,
                indexName: this._indexName,
                lastIndexPosition: this._indexPosition,
                lastObjectStorePosition: this._objectStorePosition,
                limit: 1,
                range: simplifyRange(this._range),
                objectStoreName: this._objectStoreName,
                advanceIndexKey: isIndex ? key : undefined,
                advancePrimaryKey: isIndex ? primaryKey : key,
                resultLevel: this._keyOnly ? ResultLevel.OnlyKeys : ResultLevel.Full,
            };
            const { btx } = this.source._confirmStartedBackendTransaction();
            let response = yield this._backend.getRecords(btx, recordGetRequest);
            if (response.count === 0) {
                if (BridgeIDBFactory.enableTracing) {
                    console.log("cursor is returning empty result");
                }
                this._gotValue = false;
                return null;
            }
            if (response.count !== 1) {
                throw Error("invariant failed");
            }
            if (BridgeIDBFactory.enableTracing) {
                console.log("request is:", JSON.stringify(recordGetRequest));
                console.log("get response is:", JSON.stringify(response));
            }
            if (this._indexName !== undefined) {
                this._key = response.indexKeys[0];
            }
            else {
                this._key = response.primaryKeys[0];
            }
            this._primaryKey = response.primaryKeys[0];
            if (!this._keyOnly) {
                this._value = response.values[0];
            }
            this._gotValue = true;
            this._objectStorePosition = response.primaryKeys[0];
            if (response.indexKeys !== undefined && response.indexKeys.length > 0) {
                this._indexPosition = response.indexKeys[0];
            }
            return this;
        });
    }
    // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-update-IDBRequest-any-value
    update(value) {
        if (value === undefined) {
            throw new TypeError();
        }
        const transaction = this._effectiveObjectStore._transaction;
        if (!transaction._active) {
            throw new TransactionInactiveError();
        }
        if (transaction.mode === "readonly") {
            throw new ReadOnlyError();
        }
        if (this._effectiveObjectStore._deleted) {
            throw new InvalidStateError();
        }
        if (!(this.source instanceof BridgeIDBObjectStore) &&
            this.source._deleted) {
            throw new InvalidStateError();
        }
        if (!this._gotValue || !this._isValueCursor) {
            throw new InvalidStateError();
        }
        const key = this._primaryKey;
        // Effective object store
        let os;
        if (this.source instanceof BridgeIDBObjectStore) {
            os = this.source;
        }
        else {
            os = this.source._objectStore;
        }
        try {
            // Only called for the side effect of throwing an exception
            checkStructuredCloneOrThrow(value);
        }
        catch (e) {
            throw new DataCloneError();
        }
        if (os.keyPath !== null && os.keyPath !== undefined) {
            if (!canInjectKey(os.keyPath, value)) {
                throw new DataError();
            }
        }
        const storeReq = {
            key,
            value,
            objectStoreName: this._objectStoreName,
            storeLevel: StoreLevel.UpdateExisting,
        };
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            if (BridgeIDBFactory.enableTracing) {
                console.log("updating at cursor");
            }
            const { btx } = this.source._confirmStartedBackendTransaction();
            yield this._backend.storeRecord(btx, storeReq);
            // FIXME: update the index position here!
        });
        return transaction._execRequestAsync({
            operation,
            source: this,
        });
    }
    /**
     * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-advance-void-unsigned-long-count
     */
    advance(count) {
        if (typeof count !== "number" || count <= 0) {
            throw TypeError("count must be positive number");
        }
        const transaction = this._effectiveObjectStore._transaction;
        if (!transaction._active) {
            throw new TransactionInactiveError();
        }
        if (this._effectiveObjectStore._deleted) {
            throw new InvalidStateError();
        }
        if (!(this.source instanceof BridgeIDBObjectStore) &&
            this.source._deleted) {
            throw new InvalidStateError();
        }
        if (!this._gotValue) {
            throw new InvalidStateError();
        }
        if (this._request) {
            this._request.readyState = "pending";
        }
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            let res = null;
            for (let i = 0; i < count; i++) {
                res = yield this._iterate();
            }
            return res;
        });
        transaction._execRequestAsync({
            operation,
            request: this._request,
            source: this.source,
        });
        this._gotValue = false;
    }
    /**
     * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-continue-void-any-key
     */
    continue(key) {
        const transaction = this._effectiveObjectStore._transaction;
        if (!transaction._active) {
            throw new TransactionInactiveError();
        }
        if (this._effectiveObjectStore._deleted) {
            throw new InvalidStateError();
        }
        if (!(this.source instanceof BridgeIDBObjectStore) &&
            this.source._deleted) {
            throw new InvalidStateError();
        }
        if (!this._gotValue) {
            throw new InvalidStateError();
        }
        if (key !== undefined) {
            key = valueToKey(key);
            let lastKey = this._indexName === undefined
                ? this._objectStorePosition
                : this._indexPosition;
            const cmpResult = compareKeys(key, lastKey);
            if ((cmpResult <= 0 &&
                (this.direction === "next" || this.direction === "nextunique")) ||
                (cmpResult >= 0 &&
                    (this.direction === "prev" || this.direction === "prevunique"))) {
                throw new DataError();
            }
        }
        if (this._request) {
            this._request.readyState = "pending";
        }
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            return this._iterate(key);
        });
        transaction._execRequestAsync({
            operation,
            request: this._request,
            source: this.source,
        });
        this._gotValue = false;
    }
    // https://w3c.github.io/IndexedDB/#dom-idbcursor-continueprimarykey
    continuePrimaryKey(key, primaryKey) {
        throw Error("not implemented");
    }
    delete() {
        const transaction = this._effectiveObjectStore._transaction;
        if (!transaction._active) {
            throw new TransactionInactiveError();
        }
        if (transaction.mode === "readonly") {
            throw new ReadOnlyError();
        }
        if (this._effectiveObjectStore._deleted) {
            throw new InvalidStateError();
        }
        if (!(this.source instanceof BridgeIDBObjectStore) &&
            this.source._deleted) {
            throw new InvalidStateError();
        }
        if (!this._gotValue || !this._isValueCursor) {
            throw new InvalidStateError();
        }
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            const { btx } = this.source._confirmStartedBackendTransaction();
            this._backend.deleteRecord(btx, this._objectStoreName, BridgeIDBKeyRange._valueToKeyRange(this._primaryKey));
        });
        return transaction._execRequestAsync({
            operation,
            source: this,
        });
    }
    toString() {
        return "[object IDBCursor]";
    }
}
class BridgeIDBCursorWithValue extends BridgeIDBCursor {
    get value() {
        return this._value;
    }
    get _isValueCursor() {
        return true;
    }
    constructor(source, objectStoreName, indexName, range, direction, request) {
        super(source, objectStoreName, indexName, range, direction, request, false);
    }
    toString() {
        return "[object IDBCursorWithValue]";
    }
}
/**
 * Ensure that an active version change transaction is currently running.
 */
const confirmActiveVersionchangeTransaction = (database) => {
    if (!database._upgradeTransaction) {
        throw new InvalidStateError();
    }
    // Find the latest versionchange transaction
    const transactions = database._transactions.filter((tx) => {
        return tx.mode === "versionchange";
    });
    const transaction = transactions[transactions.length - 1];
    if (!transaction || transaction._finished) {
        throw new InvalidStateError();
    }
    if (!transaction._active) {
        throw new TransactionInactiveError();
    }
    return transaction;
};
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#database-interface
/** @public */
class BridgeIDBDatabase extends FakeEventTarget {
    constructor(backend, backendConnection) {
        super();
        this._closePending = false;
        this._closed = false;
        this._transactions = [];
        this._upgradeTransaction = null;
        /**
         * Name that can be set to identify the object store in logs.
         */
        this._debugName = undefined;
        this._schema = backend.getSchema(backendConnection);
        this._backend = backend;
        this._backendConnection = backendConnection;
    }
    get name() {
        return this._schema.databaseName;
    }
    get version() {
        return this._schema.databaseVersion;
    }
    get objectStoreNames() {
        return fakeDOMStringList(Object.keys(this._schema.objectStores)).sort();
    }
    /**
     * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#database-closing-steps
     */
    _closeConnection() {
        this._closePending = true;
        // Spec is unclear what "complete" means, we assume it's
        // the same as "finished".
        const transactionsComplete = this._transactions.every((transaction) => {
            return transaction._finished;
        });
        if (transactionsComplete) {
            this._closed = true;
            this._backend.close(this._backendConnection);
        }
        else {
            queueTask(() => {
                this._closeConnection();
            });
        }
    }
    // http://w3c.github.io/IndexedDB/#dom-idbdatabase-createobjectstore
    createObjectStore(name, options = {}) {
        if (name === undefined) {
            throw new TypeError();
        }
        const transaction = confirmActiveVersionchangeTransaction(this);
        const backendTx = transaction._backendTransaction;
        if (!backendTx) {
            throw Error("invariant violated");
        }
        const keyPath = options !== null && options.keyPath !== undefined
            ? options.keyPath
            : null;
        const autoIncrement = options !== null && options.autoIncrement !== undefined
            ? options.autoIncrement
            : false;
        if (keyPath !== null) {
            validateKeyPath(keyPath);
        }
        if (Object.keys(this._schema.objectStores).includes(name)) {
            throw new ConstraintError();
        }
        if (autoIncrement && (keyPath === "" || Array.isArray(keyPath))) {
            throw new InvalidAccessError();
        }
        transaction._backend.createObjectStore(backendTx, name, keyPath !== null ? normalizeKeyPath(keyPath) : null, autoIncrement);
        this._schema = this._backend.getCurrentTransactionSchema(backendTx);
        const newObjectStore = transaction.objectStore(name);
        newObjectStore._justCreated = true;
        return newObjectStore;
    }
    deleteObjectStore(name) {
        if (name === undefined) {
            throw new TypeError();
        }
        const transaction = confirmActiveVersionchangeTransaction(this);
        const backendTx = transaction._backendTransaction;
        if (!backendTx) {
            throw Error("invariant violated");
        }
        this._backend.deleteObjectStore(backendTx, name);
        const os = transaction._objectStoresCache.get(name);
        if (os) {
            os._deleted = true;
            transaction._objectStoresCache.delete(name);
        }
    }
    _internalTransaction(storeNames, mode, backendTransaction, openRequest) {
        mode = mode !== undefined ? mode : "readonly";
        if (mode !== "readonly" &&
            mode !== "readwrite" &&
            mode !== "versionchange") {
            throw new TypeError("Invalid mode: " + mode);
        }
        if (this._upgradeTransaction) {
            throw new InvalidStateError();
        }
        if (this._closePending) {
            throw new InvalidStateError();
        }
        if (!Array.isArray(storeNames)) {
            storeNames = [storeNames];
        }
        if (storeNames.length === 0 && mode !== "versionchange") {
            throw new InvalidAccessError();
        }
        for (const storeName of storeNames) {
            if (!this.objectStoreNames.contains(storeName)) {
                throw new NotFoundError("No objectStore named " + storeName + " in this database");
            }
        }
        const tx = new BridgeIDBTransaction(storeNames, mode, this, backendTransaction, openRequest);
        this._transactions.push(tx);
        queueTask(() => {
            BridgeIDBFactory.enableTracing &&
                console.log("TRACE: calling auto-commit", this._getReadableName());
            tx._start();
        });
        if (BridgeIDBFactory.enableTracing) {
            console.log("TRACE: queued task to auto-commit", this._getReadableName());
        }
        // "When a transaction is created its active flag is initially set."
        tx._active = true;
        return tx;
    }
    _getReadableName() {
        var _a;
        return `${this.name}(${(_a = this._debugName) !== null && _a !== void 0 ? _a : "??"})`;
    }
    transaction(storeNames, mode) {
        if (mode === "versionchange") {
            throw new TypeError("Invalid mode: " + mode);
        }
        return this._internalTransaction(storeNames, mode);
    }
    close() {
        this._closeConnection();
    }
    toString() {
        return "[object IDBDatabase]";
    }
}
/** @public */
class BridgeIDBFactory {
    constructor(backend) {
        this.cmp = compareKeys;
        this.connections = [];
        this.backend = backend;
    }
    // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBFactory-deleteDatabase-IDBOpenDBRequest-DOMString-name
    deleteDatabase(name) {
        const request = new BridgeIDBOpenDBRequest();
        request._source = null;
        queueTask(() => __awaiter(this, void 0, void 0, function* () {
            const databases = yield this.backend.getDatabases();
            const dbInfo = databases.find((x) => x.name == name);
            if (!dbInfo) {
                // Database already doesn't exist, success!
                const event = new BridgeIDBVersionChangeEvent("success", {
                    newVersion: null,
                    oldVersion: 0,
                });
                request.dispatchEvent(event);
                return;
            }
            const oldVersion = dbInfo.version;
            try {
                yield this.backend.deleteDatabase(name);
                request.result = undefined;
                request.readyState = "done";
                const event2 = new BridgeIDBVersionChangeEvent("success", {
                    newVersion: null,
                    oldVersion,
                });
                request.dispatchEvent(event2);
            }
            catch (err) {
                request.error = new Error();
                request.error.name = err.name;
                request.readyState = "done";
                const event = new FakeEvent("error", {
                    bubbles: true,
                    cancelable: true,
                });
                event.eventPath = [];
                request.dispatchEvent(event);
            }
        }));
        return request;
    }
    // tslint:disable-next-line max-line-length
    // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBFactory-open-IDBOpenDBRequest-DOMString-name-unsigned-long-long-version
    open(name, version) {
        if (arguments.length > 1 && version !== undefined) {
            // Based on spec, not sure why "MAX_SAFE_INTEGER" instead of "unsigned long long", but it's needed to pass
            // tests
            version = enforceRange(version, "MAX_SAFE_INTEGER");
        }
        if (version === 0) {
            throw new TypeError();
        }
        const request = new BridgeIDBOpenDBRequest();
        queueTask(() => __awaiter(this, void 0, void 0, function* () {
            let dbconn;
            try {
                if (BridgeIDBFactory.enableTracing) {
                    console.log("TRACE: connecting to database");
                }
                dbconn = yield this.backend.connectDatabase(name);
                if (BridgeIDBFactory.enableTracing) {
                    console.log("TRACE: connected!");
                }
            }
            catch (err) {
                if (BridgeIDBFactory.enableTracing) {
                    console.log("TRACE: caught exception while trying to connect with backend");
                }
                request._finishWithError(err);
                return;
            }
            const schema = this.backend.getSchema(dbconn);
            const existingVersion = schema.databaseVersion;
            if (version === undefined) {
                version = existingVersion !== 0 ? existingVersion : 1;
            }
            const requestedVersion = version;
            BridgeIDBFactory.enableTracing &&
                console.log(`TRACE: existing version ${existingVersion}, requested version ${requestedVersion}`);
            if (existingVersion > requestedVersion) {
                request._finishWithError(new VersionError());
                return;
            }
            const db = new BridgeIDBDatabase(this.backend, dbconn);
            if (existingVersion == requestedVersion) {
                request.result = db;
                request.readyState = "done";
                const event2 = new FakeEvent("success", {
                    bubbles: false,
                    cancelable: false,
                });
                event2.eventPath = [];
                if (BridgeIDBFactory.enableTracing) {
                    console.log("open() requested same version, dispatching 'success' event on transaction");
                }
                request.dispatchEvent(event2);
            }
            else if (existingVersion < requestedVersion) {
                // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction
                for (const otherConn of this.connections) {
                    if (otherConn._closePending) {
                        continue;
                    }
                    if (BridgeIDBFactory.enableTracing) {
                        console.log("dispatching 'versionchange' event to other connection");
                    }
                    const event = new BridgeIDBVersionChangeEvent("versionchange", {
                        newVersion: version,
                        oldVersion: existingVersion,
                    });
                    otherConn.dispatchEvent(event);
                }
                if (this._anyOpen()) {
                    if (BridgeIDBFactory.enableTracing) {
                        console.log("other connections are still open, dispatching 'blocked' event to other connection");
                    }
                    const event = new BridgeIDBVersionChangeEvent("blocked", {
                        newVersion: version,
                        oldVersion: existingVersion,
                    });
                    request.dispatchEvent(event);
                }
                const backendTransaction = yield this.backend.enterVersionChange(dbconn, requestedVersion);
                // We need to expose the new version number to the upgrade transaction.
                db._schema =
                    this.backend.getCurrentTransactionSchema(backendTransaction);
                const transaction = db._internalTransaction([], "versionchange", backendTransaction, request);
                db._upgradeTransaction = transaction;
                if (BridgeIDBFactory.enableTracing) {
                    console.log("dispatching upgradeneeded event");
                }
                const event = new BridgeIDBVersionChangeEvent("upgradeneeded", {
                    newVersion: version,
                    oldVersion: existingVersion,
                });
                transaction._active = true;
                request.readyState = "done";
                request.result = db;
                request.transaction = transaction;
                request.dispatchEvent(event);
                yield transaction._waitDone();
                // We re-use the same transaction (as per spec) here.
                transaction._active = true;
                if (db._closed || db._closePending) {
                    request.result = undefined;
                    request.error = new AbortError();
                    request.readyState = "done";
                    const event2 = new FakeEvent("error", {
                        bubbles: false,
                        cancelable: false,
                    });
                    event2.eventPath = [];
                    request.dispatchEvent(event2);
                }
                else if (transaction._aborted) {
                    try {
                        yield db._backend.close(db._backendConnection);
                    }
                    catch (e) {
                        console.error("failed to close database");
                    }
                    request.result = undefined;
                    request.error = new AbortError();
                    request.readyState = "done";
                    const event2 = new FakeEvent("error", {
                        bubbles: false,
                        cancelable: false,
                    });
                    event2.eventPath = [];
                    request.dispatchEvent(event2);
                }
                else {
                    if (BridgeIDBFactory.enableTracing) {
                        console.log("dispatching 'success' event for opening db");
                    }
                    const event2 = new FakeEvent("success", {
                        bubbles: false,
                        cancelable: false,
                    });
                    event2.eventPath = [];
                    request.dispatchEvent(event2);
                }
            }
            this.connections.push(db);
            return db;
        }));
        return request;
    }
    // https://w3c.github.io/IndexedDB/#dom-idbfactory-databases
    databases() {
        return this.backend.getDatabases();
    }
    toString() {
        return "[object IDBFactory]";
    }
    _anyOpen() {
        return this.connections.some((c) => !c._closed && !c._closePending);
    }
}
BridgeIDBFactory.enableTracing = false;
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#idl-def-IDBIndex
/** @public */
class BridgeIDBIndex {
    constructor(objectStore, name) {
        this._deleted = false;
        /**
         * Was this index newly created in the current transaction?
         */
        this._justCreated = false;
        this._name = name;
        this._objectStore = objectStore;
    }
    get objectStore() {
        return this._objectStore;
    }
    get _schema() {
        return this._objectStore._transaction._db._schema;
    }
    get keyPath() {
        return this._schema.objectStores[this._objectStore.name].indexes[this._name]
            .keyPath;
    }
    get multiEntry() {
        return this._schema.objectStores[this._objectStore.name].indexes[this._name]
            .multiEntry;
    }
    get unique() {
        return this._schema.objectStores[this._objectStore.name].indexes[this._name]
            .unique;
    }
    get _backend() {
        return this._objectStore._backend;
    }
    _confirmStartedBackendTransaction() {
        return this._objectStore._confirmStartedBackendTransaction();
    }
    _confirmActiveTransaction() {
        this._objectStore._confirmActiveTransaction();
    }
    get name() {
        return this._name;
    }
    // https://w3c.github.io/IndexedDB/#dom-idbindex-name
    set name(name) {
        const transaction = this._objectStore._transaction;
        if (!transaction._db._upgradeTransaction) {
            throw new InvalidStateError();
        }
        if (!transaction._active) {
            throw new TransactionInactiveError();
        }
        const { btx } = this._confirmStartedBackendTransaction();
        const oldName = this._name;
        const newName = String(name);
        if (newName === oldName) {
            return;
        }
        this._backend.renameIndex(btx, this._objectStore.name, oldName, newName);
        this._objectStore._transaction._db._schema =
            this._backend.getCurrentTransactionSchema(btx);
        this._objectStore._indexesCache.delete(oldName);
        this._objectStore._indexesCache.set(newName, this);
        this._name = newName;
        if (this._objectStore._indexNames.indexOf(name) >= 0) {
            throw new Error("internal invariant violated");
        }
    }
    // tslint:disable-next-line max-line-length
    // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-openCursor-IDBRequest-any-range-IDBCursorDirection-direction
    openCursor(range, direction = "next") {
        if (this._deleted) {
            throw new InvalidStateError("tried to call 'openCursor' on a deleted index");
        }
        this._confirmActiveTransaction();
        range = simplifyRange(range);
        const request = new BridgeIDBRequest();
        request._source = this;
        request.transaction = this._objectStore._transaction;
        const cursor = new BridgeIDBCursorWithValue(this, this._objectStore.name, this._name, range, direction, request);
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            return cursor._iterate();
        });
        return this._objectStore._transaction._execRequestAsync({
            operation,
            request,
            source: this,
        });
    }
    // tslint:disable-next-line max-line-length
    // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-openKeyCursor-IDBRequest-any-range-IDBCursorDirection-direction
    openKeyCursor(range, direction = "next") {
        this._confirmActiveTransaction();
        if (range === null) {
            range = undefined;
        }
        if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) {
            range = BridgeIDBKeyRange.only(valueToKey(range));
        }
        const request = new BridgeIDBRequest();
        request._source = this;
        request.transaction = this._objectStore._transaction;
        const cursor = new BridgeIDBCursor(this, this._objectStore.name, this._name, range, direction, request, true);
        return this._objectStore._transaction._execRequestAsync({
            operation: cursor._iterate.bind(cursor),
            request,
            source: this,
        });
    }
    _confirmIndexExists() {
        const storeSchema = this._schema.objectStores[this._objectStore._name];
        if (!storeSchema) {
            throw new InvalidStateError(`no schema for object store '${this._objectStore._name}'`);
        }
        if (!storeSchema.indexes[this._name]) {
            throw new InvalidStateError(`no schema for index '${this._name}' of object store '${this._objectStore._name}'`);
        }
    }
    get(key) {
        if (this._deleted) {
            throw new InvalidStateError();
        }
        if (this._objectStore._deleted) {
            throw new InvalidStateError();
        }
        this._confirmActiveTransaction();
        this._confirmIndexExists();
        if (!(key instanceof BridgeIDBKeyRange)) {
            key = BridgeIDBKeyRange._valueToKeyRange(key);
        }
        const getReq = {
            direction: "next",
            indexName: this._name,
            limit: 1,
            range: key,
            objectStoreName: this._objectStore._name,
            resultLevel: ResultLevel.Full,
        };
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            const { btx } = this._confirmStartedBackendTransaction();
            const result = yield this._backend.getRecords(btx, getReq);
            if (result.count == 0) {
                return undefined;
            }
            const values = result.values;
            if (!values) {
                throw Error("invariant violated");
            }
            return values[0];
        });
        return this._objectStore._transaction._execRequestAsync({
            operation,
            source: this,
        });
    }
    // http://w3c.github.io/IndexedDB/#dom-idbindex-getall
    getAll(query, count) {
        this._confirmIndexExists();
        this._confirmActiveTransaction();
        if (this._deleted) {
            throw new InvalidStateError();
        }
        if (!(query instanceof BridgeIDBKeyRange)) {
            query = BridgeIDBKeyRange._valueToKeyRange(query);
        }
        if (count === undefined) {
            count = -1;
        }
        const getReq = {
            direction: "next",
            indexName: this._name,
            limit: count,
            range: query,
            objectStoreName: this._objectStore._name,
            resultLevel: ResultLevel.Full,
        };
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            const { btx } = this._confirmStartedBackendTransaction();
            const result = yield this._backend.getRecords(btx, getReq);
            const values = result.values;
            if (!values) {
                throw Error("invariant violated");
            }
            return values;
        });
        return this._objectStore._transaction._execRequestAsync({
            operation,
            source: this,
        });
    }
    // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-getKey-IDBRequest-any-key
    getKey(key) {
        this._confirmIndexExists();
        this._confirmActiveTransaction();
        if (!(key instanceof BridgeIDBKeyRange)) {
            key = BridgeIDBKeyRange._valueToKeyRange(key);
        }
        const getReq = {
            direction: "next",
            indexName: this._name,
            limit: 1,
            range: key,
            objectStoreName: this._objectStore._name,
            resultLevel: ResultLevel.OnlyKeys,
        };
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            const { btx } = this._confirmStartedBackendTransaction();
            const result = yield this._backend.getRecords(btx, getReq);
            if (result.count == 0) {
                return undefined;
            }
            const primaryKeys = result.primaryKeys;
            if (!primaryKeys) {
                throw Error("invariant violated");
            }
            return primaryKeys[0];
        });
        return this._objectStore._transaction._execRequestAsync({
            operation,
            source: this,
        });
    }
    // http://w3c.github.io/IndexedDB/#dom-idbindex-getallkeys
    getAllKeys(query, count) {
        this._confirmIndexExists();
        this._confirmActiveTransaction();
        if (!(query instanceof BridgeIDBKeyRange)) {
            query = BridgeIDBKeyRange._valueToKeyRange(query);
        }
        if (count === undefined) {
            count = -1;
        }
        const getReq = {
            direction: "next",
            indexName: this._name,
            limit: count,
            range: query,
            objectStoreName: this._objectStore._name,
            resultLevel: ResultLevel.OnlyKeys,
        };
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            const { btx } = this._confirmStartedBackendTransaction();
            const result = yield this._backend.getRecords(btx, getReq);
            const primaryKeys = result.primaryKeys;
            if (!primaryKeys) {
                throw Error("invariant violated");
            }
            return primaryKeys;
        });
        return this._objectStore._transaction._execRequestAsync({
            operation,
            source: this,
        });
    }
    // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-count-IDBRequest-any-key
    count(key) {
        this._confirmIndexExists();
        this._confirmActiveTransaction();
        if (key === null) {
            key = undefined;
        }
        if (key !== undefined && !(key instanceof BridgeIDBKeyRange)) {
            key = BridgeIDBKeyRange.only(valueToKey(key));
        }
        const getReq = {
            direction: "next",
            indexName: this._name,
            limit: 1,
            range: key,
            objectStoreName: this._objectStore._name,
            resultLevel: ResultLevel.OnlyCount,
        };
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            const { btx } = this._confirmStartedBackendTransaction();
            const result = yield this._backend.getRecords(btx, getReq);
            return result.count;
        });
        return this._objectStore._transaction._execRequestAsync({
            operation,
            source: this,
        });
    }
    toString() {
        return "[object IDBIndex]";
    }
}
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#range-concept
/** @public */
class BridgeIDBKeyRange {
    constructor(lower, upper, lowerOpen, upperOpen) {
        this.lower = lower;
        this.upper = upper;
        this.lowerOpen = lowerOpen;
        this.upperOpen = upperOpen;
    }
    static only(value) {
        if (arguments.length === 0) {
            throw new TypeError();
        }
        value = valueToKey(value);
        return new BridgeIDBKeyRange(value, value, false, false);
    }
    static lowerBound(lower, open = false) {
        if (arguments.length === 0) {
            throw new TypeError();
        }
        lower = valueToKey(lower);
        return new BridgeIDBKeyRange(lower, undefined, open, true);
    }
    static upperBound(upper, open = false) {
        if (arguments.length === 0) {
            throw new TypeError();
        }
        upper = valueToKey(upper);
        return new BridgeIDBKeyRange(undefined, upper, true, open);
    }
    static bound(lower, upper, lowerOpen = false, upperOpen = false) {
        if (arguments.length < 2) {
            throw new TypeError();
        }
        const cmpResult = compareKeys(lower, upper);
        if (cmpResult === 1 || (cmpResult === 0 && (lowerOpen || upperOpen))) {
            throw new DataError();
        }
        lower = valueToKey(lower);
        upper = valueToKey(upper);
        return new BridgeIDBKeyRange(lower, upper, lowerOpen, upperOpen);
    }
    // https://w3c.github.io/IndexedDB/#dom-idbkeyrange-includes
    includes(key) {
        if (arguments.length === 0) {
            throw new TypeError();
        }
        key = valueToKey(key);
        if (this.lower !== undefined) {
            const cmpResult = compareKeys(this.lower, key);
            if (cmpResult === 1 || (cmpResult === 0 && this.lowerOpen)) {
                return false;
            }
        }
        if (this.upper !== undefined) {
            const cmpResult = compareKeys(this.upper, key);
            if (cmpResult === -1 || (cmpResult === 0 && this.upperOpen)) {
                return false;
            }
        }
        return true;
    }
    toString() {
        return "[object IDBKeyRange]";
    }
    static _valueToKeyRange(value, nullDisallowedFlag = false) {
        if (value instanceof BridgeIDBKeyRange) {
            return value;
        }
        if (value === null || value === undefined) {
            if (nullDisallowedFlag) {
                throw new DataError();
            }
            return new BridgeIDBKeyRange(undefined, undefined, false, false);
        }
        const key = valueToKey(value);
        return BridgeIDBKeyRange.only(key);
    }
}
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#object-store
/** @public */
class BridgeIDBObjectStore {
    constructor(transaction, name) {
        this._indexesCache = new Map();
        /**
         * Name that can be set to identify the object store in logs.
         */
        this._debugName = undefined;
        this._justCreated = false;
        this._deleted = false;
        this._name = name;
        this._transaction = transaction;
    }
    get transaction() {
        return this._transaction;
    }
    get autoIncrement() {
        return this._schema.objectStores[this._name].autoIncrement;
    }
    get _indexNames() {
        return fakeDOMStringList(Object.keys(this._schema.objectStores[this._name].indexes)).sort();
    }
    get indexNames() {
        return this._indexNames;
    }
    get keyPath() {
        return this._schema.objectStores[this._name].keyPath;
    }
    get _schema() {
        return this._transaction._db._schema;
    }
    get name() {
        return this._name;
    }
    get _backend() {
        return this._transaction._db._backend;
    }
    get _backendConnection() {
        return this._transaction._db._backendConnection;
    }
    _confirmStartedBackendTransaction() {
        const btx = this._transaction._backendTransaction;
        if (!btx) {
            throw new InvalidStateError();
        }
        return { btx };
    }
    /**
     * Confirm that requests can currently placed against the
     * transaction of this object.
     *
     * Note that this is independent from the state of the backend
     * connection.
     */
    _confirmActiveTransaction() {
        if (!this._transaction._active) {
            throw new TransactionInactiveError("transaction is not active");
        }
        if (this._transaction._aborted) {
            throw new TransactionInactiveError("transaction has been aborted");
        }
    }
    // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-name
    set name(newName) {
        const transaction = this._transaction;
        if (!transaction._db._upgradeTransaction) {
            throw new InvalidStateError();
        }
        let { btx } = this._confirmStartedBackendTransaction();
        newName = String(newName);
        const oldName = this._name;
        if (newName === oldName) {
            return;
        }
        this._backend.renameObjectStore(btx, oldName, newName);
        this._transaction._db._schema =
            this._backend.getCurrentTransactionSchema(btx);
        // We don't modify scope, as the scope of the transaction
        // doesn't matter if we're in an upgrade transaction.
        this._transaction._objectStoresCache.delete(oldName);
        this._transaction._objectStoresCache.set(newName, this);
        this._transaction._cachedObjectStoreNames = undefined;
        this._name = newName;
    }
    _store(value, key, overwrite) {
        if (BridgeIDBFactory.enableTracing) {
            console.log(`TRACE: IDBObjectStore._store, db=${this._transaction._db._getReadableName()}`);
        }
        if (!this._transaction._active) {
            throw new TransactionInactiveError();
        }
        if (this._transaction.mode === "readonly") {
            throw new ReadOnlyError();
        }
        const { keyPath, autoIncrement } = this._schema.objectStores[this._name];
        if (key !== null && key !== undefined) {
            valueToKey(key);
        }
        // We only call this to synchronously verify the request.
        makeStoreKeyValue(value, key, 1, autoIncrement, keyPath);
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            const { btx } = this._confirmStartedBackendTransaction();
            const result = yield this._backend.storeRecord(btx, {
                objectStoreName: this._name,
                key: key,
                value,
                storeLevel: overwrite
                    ? StoreLevel.AllowOverwrite
                    : StoreLevel.NoOverwrite,
            });
            return result.key;
        });
        return this._transaction._execRequestAsync({ operation, source: this });
    }
    put(value, key) {
        if (arguments.length === 0) {
            throw new TypeError();
        }
        if (this._deleted) {
            throw new InvalidStateError("tried to call 'put' on a deleted object store");
        }
        return this._store(value, key, true);
    }
    add(value, key) {
        if (arguments.length === 0) {
            throw new TypeError();
        }
        if (!this._schema.objectStores[this._name]) {
            throw new InvalidStateError("object store does not exist");
        }
        return this._store(value, key, false);
    }
    delete(key) {
        if (arguments.length === 0) {
            throw new TypeError();
        }
        if (this._deleted) {
            throw new InvalidStateError("tried to call 'delete' on a deleted object store");
        }
        if (this._transaction.mode === "readonly") {
            throw new ReadOnlyError();
        }
        let keyRange;
        if (key instanceof BridgeIDBKeyRange) {
            keyRange = key;
        }
        else {
            keyRange = BridgeIDBKeyRange.only(valueToKey(key));
        }
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            const { btx } = this._confirmStartedBackendTransaction();
            return this._backend.deleteRecord(btx, this._name, keyRange);
        });
        return this._transaction._execRequestAsync({
            operation,
            source: this,
        });
    }
    get(key) {
        if (BridgeIDBFactory.enableTracing) {
            console.log(`getting from object store ${this._name} key ${key}`);
        }
        if (arguments.length === 0) {
            throw new TypeError();
        }
        if (!this._transaction._active) {
            throw new TransactionInactiveError();
        }
        if (this._deleted) {
            throw new InvalidStateError("tried to call 'delete' on a deleted object store");
        }
        let keyRange;
        if (key instanceof BridgeIDBKeyRange) {
            keyRange = key;
        }
        else {
            try {
                keyRange = BridgeIDBKeyRange.only(valueToKey(key));
            }
            catch (e) {
                throw new DataError(`invalid key (type ${typeof key}) for object store '${this._name}'`);
            }
        }
        const recordRequest = {
            objectStoreName: this._name,
            indexName: undefined,
            lastIndexPosition: undefined,
            lastObjectStorePosition: undefined,
            direction: "next",
            limit: 1,
            resultLevel: ResultLevel.Full,
            range: keyRange,
        };
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            if (BridgeIDBFactory.enableTracing) {
                console.log("running get operation:", recordRequest);
            }
            const { btx } = this._confirmStartedBackendTransaction();
            const result = yield this._backend.getRecords(btx, recordRequest);
            if (BridgeIDBFactory.enableTracing) {
                console.log("get operation result count:", result.count);
            }
            if (result.count === 0) {
                return undefined;
            }
            const values = result.values;
            if (!values) {
                throw Error("invariant violated");
            }
            if (values.length !== 1) {
                throw Error("invariant violated");
            }
            return values[0];
        });
        return this._transaction._execRequestAsync({
            operation,
            source: this,
        });
    }
    // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getall
    getAll(query, count) {
        if (BridgeIDBFactory.enableTracing) {
            console.log(`getting from object store ${this._name} key ${query}`);
        }
        if (!this._transaction._active) {
            throw new TransactionInactiveError();
        }
        if (this._deleted) {
            throw new InvalidStateError("tried to call 'delete' on a deleted object store");
        }
        if (count === undefined) {
            count = -1;
        }
        let keyRange = simplifyRange(query);
        const recordRequest = {
            objectStoreName: this._name,
            indexName: undefined,
            lastIndexPosition: undefined,
            lastObjectStorePosition: undefined,
            direction: "next",
            limit: count,
            resultLevel: ResultLevel.Full,
            range: keyRange,
        };
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            if (BridgeIDBFactory.enableTracing) {
                console.log("running getAll operation:", recordRequest);
            }
            const { btx } = this._confirmStartedBackendTransaction();
            const result = yield this._backend.getRecords(btx, recordRequest);
            if (BridgeIDBFactory.enableTracing) {
                console.log("get operation result count:", result.count);
            }
            const values = result.values;
            if (!values) {
                throw Error("invariant violated");
            }
            return values;
        });
        return this._transaction._execRequestAsync({
            operation,
            source: this,
        });
    }
    // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getkey
    getKey(query) {
        if (arguments.length === 0) {
            throw new TypeError();
        }
        if (!this._transaction._active) {
            throw new TransactionInactiveError();
        }
        if (this._deleted) {
            throw new InvalidStateError("tried to call 'delete' on a deleted object store");
        }
        let keyRange = simplifyRange(query);
        const recordRequest = {
            objectStoreName: this._name,
            indexName: undefined,
            lastIndexPosition: undefined,
            lastObjectStorePosition: undefined,
            direction: "next",
            limit: 1,
            resultLevel: ResultLevel.OnlyKeys,
            range: keyRange,
        };
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            if (BridgeIDBFactory.enableTracing) {
                console.log("running getKey operation:", recordRequest);
            }
            const { btx } = this._confirmStartedBackendTransaction();
            const result = yield this._backend.getRecords(btx, recordRequest);
            if (BridgeIDBFactory.enableTracing) {
                console.log("getKey operation result count:", result.count);
            }
            if (result.count === 0) {
                return undefined;
            }
            const primaryKeys = result.primaryKeys;
            if (!primaryKeys) {
                throw Error("invariant violated");
            }
            if (primaryKeys.length !== 1) {
                throw Error("invariant violated");
            }
            return primaryKeys[0];
        });
        return this._transaction._execRequestAsync({
            operation,
            source: this,
        });
    }
    // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getallkeys
    getAllKeys(query, count) {
        if (arguments.length === 0) {
            throw new TypeError();
        }
        if (!this._transaction._active) {
            throw new TransactionInactiveError();
        }
        if (this._deleted) {
            throw new InvalidStateError("tried to call 'delete' on a deleted object store");
        }
        if (count === undefined) {
            count = -1;
        }
        let keyRange;
        if (query instanceof BridgeIDBKeyRange) {
            keyRange = query;
        }
        else {
            try {
                keyRange = BridgeIDBKeyRange.only(valueToKey(query));
            }
            catch (e) {
                throw new DataError(`invalid key (type ${typeof query}) for object store '${this._name}'`);
            }
        }
        const recordRequest = {
            objectStoreName: this._name,
            indexName: undefined,
            lastIndexPosition: undefined,
            lastObjectStorePosition: undefined,
            direction: "next",
            limit: count,
            resultLevel: ResultLevel.OnlyKeys,
            range: keyRange,
        };
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            const { btx } = this._confirmStartedBackendTransaction();
            const result = yield this._backend.getRecords(btx, recordRequest);
            const primaryKeys = result.primaryKeys;
            if (!primaryKeys) {
                throw Error("invariant violated");
            }
            return primaryKeys;
        });
        return this._transaction._execRequestAsync({
            operation,
            source: this,
        });
    }
    clear() {
        if (!this._transaction._active) {
            throw new TransactionInactiveError();
        }
        if (this._deleted) {
            throw new InvalidStateError("tried to call 'delete' on a deleted object store");
        }
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            const { btx } = this._confirmStartedBackendTransaction();
            yield this._backend.clearObjectStore(btx, this._name);
        });
        return this._transaction._execRequestAsync({
            operation,
            source: this,
        });
    }
    openCursor(range, direction = "next") {
        if (this._deleted) {
            throw new InvalidStateError("tried to call 'openCursor' on a deleted object store");
        }
        if (range === null) {
            range = undefined;
        }
        if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) {
            range = BridgeIDBKeyRange.only(valueToKey(range));
        }
        const request = new BridgeIDBRequest();
        request._source = this;
        request.transaction = this._transaction;
        const cursor = new BridgeIDBCursorWithValue(this, this._name, undefined, range, direction, request);
        return this._transaction._execRequestAsync({
            operation: () => cursor._iterate(),
            request,
            source: this,
        });
    }
    openKeyCursor(range, direction) {
        if (this._deleted) {
            throw new InvalidStateError("tried to call 'openKeyCursor' on a deleted object store");
        }
        if (range === null) {
            range = undefined;
        }
        if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) {
            range = BridgeIDBKeyRange.only(valueToKey(range));
        }
        if (!direction) {
            direction = "next";
        }
        const request = new BridgeIDBRequest();
        request._source = this;
        request.transaction = this._transaction;
        const cursor = new BridgeIDBCursor(this, this._name, undefined, range, direction, request, true);
        return this._transaction._execRequestAsync({
            operation: cursor._iterate.bind(cursor),
            request,
            source: this,
        });
    }
    // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBObjectStore-createIndex-IDBIndex-DOMString-name-DOMString-sequence-DOMString--keyPath-IDBIndexParameters-optionalParameters
    createIndex(indexName, keyPath, optionalParameters = {}) {
        if (arguments.length < 2) {
            throw new TypeError();
        }
        if (!this._transaction._db._upgradeTransaction) {
            throw new InvalidStateError();
        }
        const { btx } = this._confirmStartedBackendTransaction();
        const multiEntry = optionalParameters.multiEntry !== undefined
            ? optionalParameters.multiEntry
            : false;
        const unique = optionalParameters.unique !== undefined
            ? optionalParameters.unique
            : false;
        if (this._transaction.mode !== "versionchange") {
            throw new InvalidStateError();
        }
        if (this._indexNames.indexOf(indexName) >= 0) {
            throw new ConstraintError();
        }
        validateKeyPath(keyPath);
        if (Array.isArray(keyPath) && multiEntry) {
            throw new InvalidAccessError();
        }
        this._backend.createIndex(btx, indexName, this._name, normalizeKeyPath(keyPath), multiEntry, unique);
        const idx = this.index(indexName);
        idx._justCreated = true;
        return idx;
    }
    // https://w3c.github.io/IndexedDB/#dom-idbobjectstore-index
    index(name) {
        if (arguments.length === 0) {
            throw new TypeError();
        }
        if (this._transaction._finished) {
            throw new InvalidStateError();
        }
        const index = this._indexesCache.get(name);
        if (index !== undefined) {
            return index;
        }
        const newIndex = new BridgeIDBIndex(this, name);
        this._indexesCache.set(name, newIndex);
        this._transaction._usedIndexes.push(newIndex);
        return newIndex;
    }
    deleteIndex(indexName) {
        if (arguments.length === 0) {
            throw new TypeError();
        }
        if (this._transaction.mode !== "versionchange") {
            throw new InvalidStateError();
        }
        if (!this._transaction._db._upgradeTransaction) {
            throw new InvalidStateError();
        }
        const { btx } = this._confirmStartedBackendTransaction();
        const index = this._indexesCache.get(indexName);
        if (index !== undefined) {
            index._deleted = true;
            this._indexesCache.delete(indexName);
        }
        this._backend.deleteIndex(btx, this._name, indexName);
    }
    // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBObjectStore-count-IDBRequest-any-key
    count(key) {
        if (key === null) {
            key = undefined;
        }
        if (key !== undefined && !(key instanceof BridgeIDBKeyRange)) {
            key = BridgeIDBKeyRange.only(valueToKey(key));
        }
        const recordGetRequest = {
            direction: "next",
            indexName: undefined,
            lastIndexPosition: undefined,
            limit: -1,
            objectStoreName: this._name,
            lastObjectStorePosition: undefined,
            range: key,
            resultLevel: ResultLevel.OnlyCount,
        };
        const operation = () => __awaiter(this, void 0, void 0, function* () {
            const { btx } = this._confirmStartedBackendTransaction();
            const result = yield this._backend.getRecords(btx, recordGetRequest);
            return result.count;
        });
        return this._transaction._execRequestAsync({ operation, source: this });
    }
    toString() {
        return "[object IDBObjectStore]";
    }
}
/** @public */
class BridgeIDBRequest extends FakeEventTarget {
    constructor() {
        super(...arguments);
        this._result = null;
        this._error = null;
        this._source = null;
        this.transaction = null;
        this.readyState = "pending";
        this.onsuccess = null;
        this.onerror = null;
    }
    get source() {
        if (!this._source) {
            throw Error("internal invariant failed: source is null");
        }
        return this._source;
    }
    get error() {
        if (this.readyState === "pending") {
            throw new InvalidStateError();
        }
        return this._error;
    }
    set error(value) {
        this._error = value;
    }
    get result() {
        if (this.readyState === "pending") {
            throw new InvalidStateError();
        }
        return this._result;
    }
    set result(value) {
        this._result = value;
    }
    toString() {
        return "[object IDBRequest]";
    }
    _finishWithError(err) {
        this.result = undefined;
        this.readyState = "done";
        this.error = new Error(err.message);
        this.error.name = err.name;
        const event = new FakeEvent("error", {
            bubbles: true,
            cancelable: true,
        });
        event.eventPath = [];
        this.dispatchEvent(event);
    }
    _finishWithResult(result) {
        this.result = result;
        this.readyState = "done";
        const event = new FakeEvent("success");
        event.eventPath = [];
        this.dispatchEvent(event);
    }
}
/** @public */
class BridgeIDBOpenDBRequest extends BridgeIDBRequest {
    constructor() {
        super();
        this.onupgradeneeded = null;
        this.onblocked = null;
        // https://www.w3.org/TR/IndexedDB/#open-requests
        this._source = null;
    }
    get source() {
        // This is a type safety violation, but it is required by the
        // IndexedDB standard.
        // On the one hand, IDBOpenDBRequest implements IDBRequest.
        // But that's technically impossible, as the "source" of the
        // IDBOpenDB request may be null, while the one from IDBRequest
        // may not be null.
        return this._source;
    }
    toString() {
        return "[object IDBOpenDBRequest]";
    }
}
function waitMacroQueue() {
    return new Promise((resolve, reject) => {
        let immCalled = false;
        let timeoutCalled = false;
        setImmediate(() => {
            immCalled = true;
            if (immCalled && timeoutCalled) {
                resolve();
            }
        });
        setTimeout(() => {
            timeoutCalled = true;
            if (immCalled && timeoutCalled) {
                resolve();
            }
        }, 0);
    });
}
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#transaction
/** @public */
class BridgeIDBTransaction extends FakeEventTarget {
    constructor(storeNames, mode, db, backendTransaction, openRequest) {
        super();
        this._committed = false;
        /**
         * A transaction is active as long as new operations can be
         * placed against it.
         */
        this._active = false;
        this._started = false;
        this._aborted = false;
        this._objectStoresCache = new Map();
        /**
         * Object stores used during the transaction.
         */
        this._usedObjectStores = [];
        /**
         * Object stores used during the transaction.
         */
        this._usedIndexes = [];
        /**
         * Name that can be set to identify the transaction in logs.
         */
        this._debugName = undefined;
        this._openRequest = null;
        this._error = null;
        this.onabort = null;
        this.oncomplete = null;
        this.onerror = null;
        this._requests = [];
        const myOpenPromise = openPromise();
        this._waitPromise = myOpenPromise.promise;
        this._resolveWait = myOpenPromise.resolve;
        this._scope = new Set(storeNames);
        this._backendTransaction = backendTransaction;
        this.mode = mode;
        this._db = db;
        this._db._transactions.push(this);
        this._openRequest = openRequest !== null && openRequest !== void 0 ? openRequest : null;
    }
    /**
     * https://www.w3.org/TR/IndexedDB-2/#transaction-lifetime-concept
     *
     * When a transaction is committed or aborted, it is said to be finished.
     */
    get _finished() {
        return this._committed || this._aborted;
    }
    get objectStoreNames() {
        if (!this._cachedObjectStoreNames) {
            if (this._openRequest) {
                this._cachedObjectStoreNames = this._db.objectStoreNames;
            }
            else {
                this._cachedObjectStoreNames = fakeDOMStringList(Array.from(this._scope).sort());
            }
        }
        return this._cachedObjectStoreNames;
    }
    get db() {
        return this._db;
    }
    get error() {
        return this._error;
    }
    get _backend() {
        return this._db._backend;
    }
    // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-aborting-a-transaction
    _abort(errName) {
        return __awaiter(this, void 0, void 0, function* () {
            if (BridgeIDBFactory.enableTracing) {
                console.log("TRACE: aborting transaction");
            }
            if (this._aborted) {
                return;
            }
            this._aborted = true;
            this._active = false;
            if (errName !== null) {
                const e = new Error();
                e.name = errName;
                this._error = e;
            }
            if (BridgeIDBFactory.enableTracing) {
                console.log(`TRACE: aborting ${this._requests.length} requests`);
            }
            // Should this directly remove from _requests?
            for (const { request } of this._requests) {
                if (request.readyState !== "done") {
                    // This will cancel execution of this request's operation
                    request.readyState = "done";
                    if (BridgeIDBFactory.enableTracing) {
                        console.log("dispatching error event");
                    }
                    request.result = undefined;
                    request.error = new AbortError();
                    const event = new FakeEvent("error", {
                        bubbles: true,
                        cancelable: true,
                    });
                    event.eventPath = [this._db, this];
                    request.dispatchEvent(event);
                }
            }
            // "Any object stores and indexes which were created during the
            // transaction are now considered deleted for the purposes of other
            // algorithms."
            if (this._db._upgradeTransaction) {
                for (const os of this._usedObjectStores) {
                    if (os._justCreated) {
                        os._deleted = true;
                    }
                }
                for (const ind of this._usedIndexes) {
                    if (ind._justCreated) {
                        ind._deleted = true;
                    }
                }
            }
            // ("abort a transaction", step 5.1)
            if (this._openRequest) {
                this._db._upgradeTransaction = null;
            }
            // All steps before happened synchronously.  Now
            // we asynchronously roll back the backend transaction,
            // if necessary/possible.
            const maybeBtx = this._backendTransaction;
            if (maybeBtx) {
                this._db._schema = this._backend.getInitialTransactionSchema(maybeBtx);
                // Only roll back if we actually executed the scheduled operations.
                yield this._backend.rollback(maybeBtx);
                this._backendTransaction = undefined;
            }
            else {
                this._db._schema = this._backend.getSchema(this._db._backendConnection);
            }
            queueTask(() => {
                const event = new FakeEvent("abort", {
                    bubbles: true,
                    cancelable: false,
                });
                event.eventPath = [this._db];
                this.dispatchEvent(event);
            });
            if (this._openRequest) {
                this._openRequest.transaction = null;
                this._openRequest.result = undefined;
                this._openRequest.readyState = "pending";
            }
        });
    }
    abort() {
        if (this._finished) {
            throw new InvalidStateError();
        }
        this._abort(null);
    }
    // http://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstore
    objectStore(name) {
        if (!this._active) {
            throw new TransactionInactiveError();
        }
        if (!this._db._schema.objectStores[name]) {
            throw new NotFoundError();
        }
        if (!this._db._upgradeTransaction) {
            if (!this._scope.has(name)) {
                throw new NotFoundError();
            }
        }
        const objectStore = this._objectStoresCache.get(name);
        if (objectStore !== undefined) {
            return objectStore;
        }
        const newObjectStore = new BridgeIDBObjectStore(this, name);
        this._objectStoresCache.set(name, newObjectStore);
        this._usedObjectStores.push(newObjectStore);
        return newObjectStore;
    }
    // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-asynchronously-executing-a-request
    _execRequestAsync(obj) {
        const source = obj.source;
        const operation = obj.operation;
        let request = obj.hasOwnProperty("request") ? obj.request : null;
        if (!this._active) {
            throw new TransactionInactiveError();
        }
        // Request should only be passed for cursors
        if (!request) {
            if (!source) {
                // Special requests like indexes that just need to run some code
                request = new BridgeIDBRequest();
            }
            else {
                request = new BridgeIDBRequest();
                request._source = source;
                request.transaction = source.transaction;
            }
        }
        this._requests.push({
            operation,
            request,
        });
        return request;
    }
    /**
     * Actually execute the scheduled work for this transaction.
     */
    _start() {
        var _a, _b;
        return __awaiter(this, void 0, void 0, function* () {
            if (BridgeIDBFactory.enableTracing) {
                console.log(`TRACE: IDBTransaction._start, ${this._requests.length} queued`);
            }
            this._started = true;
            // Remove from request queue - cursor ones will be added back if necessary
            // by cursor.continue and such
            let operation;
            let request;
            while (this._requests.length > 0) {
                const r = this._requests.shift();
                // This should only be false if transaction was aborted
                if (r && r.request.readyState !== "done") {
                    request = r.request;
                    operation = r.operation;
                    break;
                }
            }
            if (request && operation) {
                if (!this._backendTransaction && !this._aborted) {
                    if (BridgeIDBFactory.enableTracing) {
                        console.log("beginning backend transaction to process operation");
                    }
                    this._backendTransaction = yield this._backend.beginTransaction(this._db._backendConnection, Array.from(this._scope), this.mode);
                    if (BridgeIDBFactory.enableTracing) {
                        console.log(`started backend transaction (${this._backendTransaction.transactionCookie})`);
                    }
                }
                if (!request._source) {
                    // Special requests like indexes that just need to run some code,
                    // with error handling already built into operation
                    yield operation();
                }
                else {
                    yield waitMacroQueue();
                    let event;
                    try {
                        BridgeIDBFactory.enableTracing &&
                            console.log("TRACE: running operation in transaction");
                        const result = yield operation();
                        // Wait until setTimeout/setImmediate tasks are run
                        BridgeIDBFactory.enableTracing &&
                            console.log(`TRACE: request (${(_a = request._debugName) !== null && _a !== void 0 ? _a : "??"}) in transaction finished with success`);
                        request.readyState = "done";
                        request.result = result;
                        request.error = undefined;
                        // https://www.w3.org/TR/IndexedDB-2/#fire-error-event
                        this._active = true;
                        event = new FakeEvent("success", {
                            bubbles: false,
                            cancelable: false,
                        });
                        queueTask(() => {
                            this._active = false;
                        });
                        try {
                            event.eventPath = [this._db, this];
                            request.dispatchEvent(event);
                        }
                        catch (err) {
                            if (BridgeIDBFactory.enableTracing) {
                                console.log("TRACING: caught error in transaction success event handler");
                            }
                            this._abort("AbortError");
                            this._active = false;
                            throw err;
                        }
                    }
                    catch (err) {
                        if (BridgeIDBFactory.enableTracing) {
                            console.log("TRACING: error during operation: ", err);
                        }
                        request.readyState = "done";
                        request.result = undefined;
                        request.error = err;
                        // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-fire-an-error-event
                        this._active = true;
                        queueTask(() => {
                            this._active = false;
                        });
                        event = new FakeEvent("error", {
                            bubbles: true,
                            cancelable: true,
                        });
                        try {
                            event.eventPath = [this._db, this];
                            request.dispatchEvent(event);
                        }
                        catch (err) {
                            this._abort("AbortError");
                            throw err;
                        }
                        if (!event.canceled) {
                            this._abort(err.name);
                        }
                    }
                }
                // On to the next one
                if (this._requests.length > 0) {
                    this._start();
                }
                else {
                    // Give it another chance for new handlers to be set before finishing
                    queueTask(() => this._start());
                }
                return;
            }
            if (!this._finished && !this._committed) {
                if (BridgeIDBFactory.enableTracing) {
                    console.log(`setting transaction to inactive, db=${this._db._getReadableName()}`);
                }
                this._active = false;
                // We only have a backend transaction if any requests were placed
                // against the transactions.
                if (this._backendTransaction) {
                    yield this._backend.commit(this._backendTransaction);
                }
                // We must exit the upgrade transaction here, so that the "complete"
                // event handler can already do other transactions.
                this._db._upgradeTransaction = null;
                this._committed = true;
                if (!this._error) {
                    if (BridgeIDBFactory.enableTracing) {
                        console.log(`dispatching 'complete' event on transaction (${(_b = this._debugName) !== null && _b !== void 0 ? _b : "??"})`);
                    }
                    const event = new FakeEvent("complete");
                    event.eventPath = [this._db, this];
                    this.dispatchEvent(event);
                }
                const idx = this._db._transactions.indexOf(this);
                if (idx < 0) {
                    throw Error("invariant failed");
                }
                this._db._transactions.splice(idx, 1);
                this._resolveWait();
            }
            if (this._aborted) {
                this._resolveWait();
            }
        });
    }
    commit() {
        // The current spec doesn't even have an explicit commit method.
        // We still support it, effectively as a "no-operation" that
        // prevents new operations from being scheduled.
        if (!this._active) {
            throw new InvalidStateError();
        }
        this._active = false;
    }
    toString() {
        return "[object IDBRequest]";
    }
    _waitDone() {
        return this._waitPromise;
    }
}
class BridgeIDBVersionChangeEvent extends FakeEvent {
    constructor(type, parameters = {}) {
        super(type);
        this.newVersion =
            parameters.newVersion !== undefined ? parameters.newVersion : null;
        this.oldVersion =
            parameters.oldVersion !== undefined ? parameters.oldVersion : 0;
    }
    toString() {
        return "[object IDBVersionChangeEvent]";
    }
}

/**
 * Compares DefaultComparables to form a strict partial ordering.
 *
 * Handles +/-0 and NaN like Map: NaN is equal to NaN, and -0 is equal to +0.
 *
 * Arrays are compared using '<' and '>', which may cause unexpected equality:
 * for example [1] will be considered equal to ['1'].
 *
 * Two objects with equal valueOf compare the same, but compare unequal to
 * primitives that have the same value.
 */
function defaultComparator(a, b) {
    // Special case finite numbers first for performance.
    // Note that the trick of using 'a - b' and checking for NaN to detect non-numbers
    // does not work if the strings are numeric (ex: "5"). This would leading most
    // comparison functions using that approach to fail to have transitivity.
    if (Number.isFinite(a) && Number.isFinite(b)) {
        return a - b;
    }
    // The default < and > operators are not totally ordered. To allow types to be mixed
    // in a single collection, compare types and order values of different types by type.
    let ta = typeof a;
    let tb = typeof b;
    if (ta !== tb) {
        return ta < tb ? -1 : 1;
    }
    if (ta === "object") {
        // standardized JavaScript bug: null is not an object, but typeof says it is
        if (a === null)
            return b === null ? 0 : -1;
        else if (b === null)
            return 1;
        a = a.valueOf();
        b = b.valueOf();
        ta = typeof a;
        tb = typeof b;
        // Deal with the two valueOf()s producing different types
        if (ta !== tb) {
            return ta < tb ? -1 : 1;
        }
    }
    // a and b are now the same type, and will be a number, string or array
    // (which we assume holds numbers or strings), or something unsupported.
    if (a < b)
        return -1;
    if (a > b)
        return 1;
    if (a === b)
        return 0;
    // Order NaN less than other numbers
    if (Number.isNaN(a))
        return Number.isNaN(b) ? 0 : -1;
    else if (Number.isNaN(b))
        return 1;
    // This could be two objects (e.g. [7] and ['7']) that aren't ordered
    return Array.isArray(a) ? 0 : Number.NaN;
}
/**
 * A reasonably fast collection of key-value pairs with a powerful API.
 * Largely compatible with the standard Map. BTree is a B+ tree data structure,
 * so the collection is sorted by key.
 *
 * B+ trees tend to use memory more efficiently than hashtables such as the
 * standard Map, especially when the collection contains a large number of
 * items. However, maintaining the sort order makes them modestly slower:
 * O(log size) rather than O(1). This B+ tree implementation supports O(1)
 * fast cloning. It also supports freeze(), which can be used to ensure that
 * a BTree is not changed accidentally.
 *
 * Confusingly, the ES6 Map.forEach(c) method calls c(value,key) instead of
 * c(key,value), in contrast to other methods such as set() and entries()
 * which put the key first. I can only assume that the order was reversed on
 * the theory that users would usually want to examine values and ignore keys.
 * BTree's forEach() therefore works the same way, but a second method
 * `.forEachPair((key,value)=>{...})` is provided which sends you the key
 * first and the value second; this method is slightly faster because it is
 * the "native" for-each method for this class.
 *
 * Out of the box, BTree supports keys that are numbers, strings, arrays of
 * numbers/strings, Date, and objects that have a valueOf() method returning a
 * number or string. Other data types, such as arrays of Date or custom
 * objects, require a custom comparator, which you must pass as the second
 * argument to the constructor (the first argument is an optional list of
 * initial items). Symbols cannot be used as keys because they are unordered
 * (one Symbol is never "greater" or "less" than another).
 *
 * @example
 * Given a {name: string, age: number} object, you can create a tree sorted by
 * name and then by age like this:
 *
 *     var tree = new BTree(undefined, (a, b) => {
 *       if (a.name > b.name)
 *         return 1; // Return a number >0 when a > b
 *       else if (a.name < b.name)
 *         return -1; // Return a number <0 when a < b
 *       else // names are equal (or incomparable)
 *         return a.age - b.age; // Return >0 when a.age > b.age
 *     });
 *
 *     tree.set({name:"Bill", age:17}, "happy");
 *     tree.set({name:"Fran", age:40}, "busy & stressed");
 *     tree.set({name:"Bill", age:55}, "recently laid off");
 *     tree.forEachPair((k, v) => {
 *       console.log(`Name: ${k.name} Age: ${k.age} Status: ${v}`);
 *     });
 *
 * @description
 * The "range" methods (`forEach, forRange, editRange`) will return the number
 * of elements that were scanned. In addition, the callback can return {break:R}
 * to stop early and return R from the outer function.
 *
 * - TODO: Test performance of preallocating values array at max size
 * - TODO: Add fast initialization when a sorted array is provided to constructor
 *
 * For more documentation see https://github.com/qwertie/btree-typescript
 *
 * Are you a C# developer? You might like the similar data structures I made for C#:
 * BDictionary, BList, etc. See http://core.loyc.net/collections/
 *
 * @author David Piepgrass
 */
class BTree {
    /**
     * Initializes an empty B+ tree.
     * @param compare Custom function to compare pairs of elements in the tree.
     *   If not specified, defaultComparator will be used which is valid as long as K extends DefaultComparable.
     * @param entries A set of key-value pairs to initialize the tree
     * @param maxNodeSize Branching factor (maximum items or children per node)
     *   Must be in range 4..256. If undefined or <4 then default is used; if >256 then 256.
     */
    constructor(entries, compare, maxNodeSize) {
        this._root = EmptyLeaf;
        this._size = 0;
        this._maxNodeSize = maxNodeSize >= 4 ? Math.min(maxNodeSize, 256) : 32;
        this._compare =
            compare || defaultComparator;
        if (entries)
            this.setPairs(entries);
    }
    /////////////////////////////////////////////////////////////////////////////
    // ES6 Map<K,V> methods /////////////////////////////////////////////////////
    /** Gets the number of key-value pairs in the tree. */
    get size() {
        return this._size;
    }
    /** Gets the number of key-value pairs in the tree. */
    get length() {
        return this._size;
    }
    /** Returns true iff the tree contains no key-value pairs. */
    get isEmpty() {
        return this._size === 0;
    }
    /** Releases the tree so that its size is 0. */
    clear() {
        this._root = EmptyLeaf;
        this._size = 0;
    }
    /** Runs a function for each key-value pair, in order from smallest to
     *  largest key. For compatibility with ES6 Map, the argument order to
     *  the callback is backwards: value first, then key. Call forEachPair
     *  instead to receive the key as the first argument.
     * @param thisArg If provided, this parameter is assigned as the `this`
     *        value for each callback.
     * @returns the number of values that were sent to the callback,
     *        or the R value if the callback returned {break:R}. */
    forEach(callback, thisArg) {
        if (thisArg !== undefined)
            callback = callback.bind(thisArg);
        return this.forEachPair((k, v) => callback(v, k, this));
    }
    /** Runs a function for each key-value pair, in order from smallest to
     *  largest key. The callback can return {break:R} (where R is any value
     *  except undefined) to stop immediately and return R from forEachPair.
     * @param onFound A function that is called for each key-value pair. This
     *        function can return {break:R} to stop early with result R.
     *        The reason that you must return {break:R} instead of simply R
     *        itself is for consistency with editRange(), which allows
     *        multiple actions, not just breaking.
     * @param initialCounter This is the value of the third argument of
     *        `onFound` the first time it is called. The counter increases
     *        by one each time `onFound` is called. Default value: 0
     * @returns the number of pairs sent to the callback (plus initialCounter,
     *        if you provided one). If the callback returned {break:R} then
     *        the R value is returned instead. */
    forEachPair(callback, initialCounter) {
        var low = this.minKey(), high = this.maxKey();
        return this.forRange(low, high, true, callback, initialCounter);
    }
    /**
     * Finds a pair in the tree and returns the associated value.
     * @param defaultValue a value to return if the key was not found.
     * @returns the value, or defaultValue if the key was not found.
     * @description Computational complexity: O(log size)
     */
    get(key, defaultValue) {
        return this._root.get(key, defaultValue, this);
    }
    /**
     * Adds or overwrites a key-value pair in the B+ tree.
     * @param key the key is used to determine the sort order of
     *        data in the tree.
     * @param value data to associate with the key (optional)
     * @param overwrite Whether to overwrite an existing key-value pair
     *        (default: true). If this is false and there is an existing
     *        key-value pair then this method has no effect.
     * @returns true if a new key-value pair was added.
     * @description Computational complexity: O(log size)
     * Note: when overwriting a previous entry, the key is updated
     * as well as the value. This has no effect unless the new key
     * has data that does not affect its sort order.
     */
    set(key, value, overwrite) {
        if (this._root.isShared)
            this._root = this._root.clone();
        var result = this._root.set(key, value, overwrite, this);
        if (result === true || result === false)
            return result;
        // Root node has split, so create a new root node.
        this._root = new BNodeInternal([this._root, result]);
        return true;
    }
    /**
     * Returns true if the key exists in the B+ tree, false if not.
     * Use get() for best performance; use has() if you need to
     * distinguish between "undefined value" and "key not present".
     * @param key Key to detect
     * @description Computational complexity: O(log size)
     */
    has(key) {
        return this.forRange(key, key, true, undefined) !== 0;
    }
    /**
     * Removes a single key-value pair from the B+ tree.
     * @param key Key to find
     * @returns true if a pair was found and removed, false otherwise.
     * @description Computational complexity: O(log size)
     */
    delete(key) {
        return this.editRange(key, key, true, DeleteRange) !== 0;
    }
    with(key, value, overwrite) {
        let nu = this.clone();
        return nu.set(key, value, overwrite) || overwrite ? nu : this;
    }
    /** Returns a copy of the tree with the specified key-value pairs set. */
    withPairs(pairs, overwrite) {
        let nu = this.clone();
        return nu.setPairs(pairs, overwrite) !== 0 || overwrite ? nu : this;
    }
    /** Returns a copy of the tree with the specified keys present.
     *  @param keys The keys to add. If a key is already present in the tree,
     *         neither the existing key nor the existing value is modified.
     *  @param returnThisIfUnchanged if true, returns this if all keys already
     *  existed. Performance note: due to the architecture of this class, all
     *  node(s) leading to existing keys are cloned even if the collection is
     *  ultimately unchanged.
     */
    withKeys(keys, returnThisIfUnchanged) {
        let nu = this.clone(), changed = false;
        for (var i = 0; i < keys.length; i++)
            changed = nu.set(keys[i], undefined, false) || changed;
        return returnThisIfUnchanged && !changed ? this : nu;
    }
    /** Returns a copy of the tree with the specified key removed.
     * @param returnThisIfUnchanged if true, returns this if the key didn't exist.
     *  Performance note: due to the architecture of this class, node(s) leading
     *  to where the key would have been stored are cloned even when the key
     *  turns out not to exist and the collection is unchanged.
     */
    without(key, returnThisIfUnchanged) {
        return this.withoutRange(key, key, true, returnThisIfUnchanged);
    }
    /** Returns a copy of the tree with the specified keys removed.
     * @param returnThisIfUnchanged if true, returns this if none of the keys
     *  existed. Performance note: due to the architecture of this class,
     *  node(s) leading to where the key would have been stored are cloned
     *  even when the key turns out not to exist.
     */
    withoutKeys(keys, returnThisIfUnchanged) {
        let nu = this.clone();
        return nu.deleteKeys(keys) || !returnThisIfUnchanged ? nu : this;
    }
    /** Returns a copy of the tree with the specified range of keys removed. */
    withoutRange(low, high, includeHigh, returnThisIfUnchanged) {
        let nu = this.clone();
        if (nu.deleteRange(low, high, includeHigh) === 0 && returnThisIfUnchanged)
            return this;
        return nu;
    }
    /** Returns a copy of the tree with pairs removed whenever the callback
     *  function returns false. `where()` is a synonym for this method. */
    filter(callback, returnThisIfUnchanged) {
        var nu = this.greedyClone();
        var del;
        nu.editAll((k, v, i) => {
            if (!callback(k, v, i))
                return (del = Delete);
        });
        if (!del && returnThisIfUnchanged)
            return this;
        return nu;
    }
    /** Returns a copy of the tree with all values altered by a callback function. */
    mapValues(callback) {
        var tmp = {};
        var nu = this.greedyClone();
        nu.editAll((k, v, i) => {
            return (tmp.value = callback(v, k, i)), tmp;
        });
        return nu;
    }
    reduce(callback, initialValue) {
        let i = 0, p = initialValue;
        var it = this.entries(this.minKey(), ReusedArray), next;
        while (!(next = it.next()).done)
            p = callback(p, next.value, i++, this);
        return p;
    }
    /////////////////////////////////////////////////////////////////////////////
    // Iterator methods /////////////////////////////////////////////////////////
    /** Returns an iterator that provides items in order (ascending order if
     *  the collection's comparator uses ascending order, as is the default.)
     *  @param lowestKey First key to be iterated, or undefined to start at
     *         minKey(). If the specified key doesn't exist then iteration
     *         starts at the next higher key (according to the comparator).
     *  @param reusedArray Optional array used repeatedly to store key-value
     *         pairs, to avoid creating a new array on every iteration.
     */
    entries(lowestKey, reusedArray) {
        var info = this.findPath(lowestKey);
        if (info === undefined)
            return iterator();
        var { nodequeue, nodeindex, leaf } = info;
        var state = reusedArray !== undefined ? 1 : 0;
        var i = lowestKey === undefined
            ? -1
            : leaf.indexOf(lowestKey, 0, this._compare) - 1;
        return iterator(() => {
            jump: for (;;) {
                switch (state) {
                    case 0:
                        if (++i < leaf.keys.length)
                            return { done: false, value: [leaf.keys[i], leaf.values[i]] };
                        state = 2;
                        continue;
                    case 1:
                        if (++i < leaf.keys.length) {
                            (reusedArray[0] = leaf.keys[i]),
                                (reusedArray[1] = leaf.values[i]);
                            return { done: false, value: reusedArray };
                        }
                        state = 2;
                    case 2:
                        // Advance to the next leaf node
                        for (var level = -1;;) {
                            if (++level >= nodequeue.length) {
                                state = 3;
                                continue jump;
                            }
                            if (++nodeindex[level] < nodequeue[level].length)
                                break;
                        }
                        for (; level > 0; level--) {
                            nodequeue[level - 1] = nodequeue[level][nodeindex[level]].children;
                            nodeindex[level - 1] = 0;
                        }
                        leaf = nodequeue[0][nodeindex[0]];
                        i = -1;
                        state = reusedArray !== undefined ? 1 : 0;
                        continue;
                    case 3:
                        return { done: true, value: undefined };
                }
            }
        });
    }
    /** Returns an iterator that provides items in reversed order.
     *  @param highestKey Key at which to start iterating, or undefined to
     *         start at maxKey(). If the specified key doesn't exist then iteration
     *         starts at the next lower key (according to the comparator).
     *  @param reusedArray Optional array used repeatedly to store key-value
     *         pairs, to avoid creating a new array on every iteration.
     *  @param skipHighest Iff this flag is true and the highestKey exists in the
     *         collection, the pair matching highestKey is skipped, not iterated.
     */
    entriesReversed(highestKey, reusedArray, skipHighest) {
        if (highestKey === undefined) {
            highestKey = this.maxKey();
            skipHighest = undefined;
            if (highestKey === undefined)
                return iterator(); // collection is empty
        }
        var { nodequeue, nodeindex, leaf } = this.findPath(highestKey) || this.findPath(this.maxKey());
        check(!nodequeue[0] || leaf === nodequeue[0][nodeindex[0]], "wat!");
        var i = leaf.indexOf(highestKey, 0, this._compare);
        if (!skipHighest &&
            i < leaf.keys.length &&
            this._compare(leaf.keys[i], highestKey) <= 0)
            i++;
        var state = reusedArray !== undefined ? 1 : 0;
        return iterator(() => {
            jump: for (;;) {
                switch (state) {
                    case 0:
                        if (--i >= 0)
                            return { done: false, value: [leaf.keys[i], leaf.values[i]] };
                        state = 2;
                        continue;
                    case 1:
                        if (--i >= 0) {
                            (reusedArray[0] = leaf.keys[i]),
                                (reusedArray[1] = leaf.values[i]);
                            return { done: false, value: reusedArray };
                        }
                        state = 2;
                    case 2:
                        // Advance to the next leaf node
                        for (var level = -1;;) {
                            if (++level >= nodequeue.length) {
                                state = 3;
                                continue jump;
                            }
                            if (--nodeindex[level] >= 0)
                                break;
                        }
                        for (; level > 0; level--) {
                            nodequeue[level - 1] = nodequeue[level][nodeindex[level]].children;
                            nodeindex[level - 1] = nodequeue[level - 1].length - 1;
                        }
                        leaf = nodequeue[0][nodeindex[0]];
                        i = leaf.keys.length;
                        state = reusedArray !== undefined ? 1 : 0;
                        continue;
                    case 3:
                        return { done: true, value: undefined };
                }
            }
        });
    }
    /* Used by entries() and entriesReversed() to prepare to start iterating.
     * It develops a "node queue" for each non-leaf level of the tree.
     * Levels are numbered "bottom-up" so that level 0 is a list of leaf
     * nodes from a low-level non-leaf node. The queue at a given level L
     * consists of nodequeue[L] which is the children of a BNodeInternal,
     * and nodeindex[L], the current index within that child list, such
     * such that nodequeue[L-1] === nodequeue[L][nodeindex[L]].children.
     * (However inside this function the order is reversed.)
     */
    findPath(key) {
        var nextnode = this._root;
        var nodequeue, nodeindex;
        if (nextnode.isLeaf) {
            (nodequeue = EmptyArray), (nodeindex = EmptyArray); // avoid allocations
        }
        else {
            (nodequeue = []), (nodeindex = []);
            for (var d = 0; !nextnode.isLeaf; d++) {
                nodequeue[d] = nextnode.children;
                nodeindex[d] =
                    key === undefined ? 0 : nextnode.indexOf(key, 0, this._compare);
                if (nodeindex[d] >= nodequeue[d].length)
                    return; // first key > maxKey()
                nextnode = nodequeue[d][nodeindex[d]];
            }
            nodequeue.reverse();
            nodeindex.reverse();
        }
        return { nodequeue, nodeindex, leaf: nextnode };
    }
    /**
     * Computes the differences between `this` and `other`.
     * For efficiency, the diff is returned via invocations of supplied handlers.
     * The computation is optimized for the case in which the two trees have large amounts
     * of shared data (obtained by calling the `clone` or `with` APIs) and will avoid
     * any iteration of shared state.
     * The handlers can cause computation to early exit by returning {break: R}.
     * Neither of the collections should be changed during the comparison process (in your callbacks), as this method assumes they will not be mutated.
     * @param other The tree to compute a diff against.
     * @param onlyThis Callback invoked for all keys only present in `this`.
     * @param onlyOther Callback invoked for all keys only present in `other`.
     * @param different Callback invoked for all keys with differing values.
     */
    diffAgainst(other, onlyThis, onlyOther, different) {
        if (other._compare !== this._compare) {
            throw new Error("Tree comparators are not the same.");
        }
        if (this.isEmpty || other.isEmpty) {
            if (this.isEmpty && other.isEmpty)
                return undefined;
            // If one tree is empty, everything will be an onlyThis/onlyOther.
            if (this.isEmpty)
                return onlyOther === undefined
                    ? undefined
                    : BTree.stepToEnd(BTree.makeDiffCursor(other), onlyOther);
            return onlyThis === undefined
                ? undefined
                : BTree.stepToEnd(BTree.makeDiffCursor(this), onlyThis);
        }
        // Cursor-based diff algorithm is as follows:
        // - Until neither cursor has navigated to the end of the tree, do the following:
        //  - If the `this` cursor is "behind" the `other` cursor (strictly <, via compare), advance it.
        //  - Otherwise, advance the `other` cursor.
        //  - Any time a cursor is stepped, perform the following:
        //    - If either cursor points to a key/value pair:
        //      - If thisCursor === otherCursor and the values differ, it is a Different.
        //      - If thisCursor > otherCursor and otherCursor is at a key/value pair, it is an OnlyOther.
        //      - If thisCursor < otherCursor and thisCursor is at a key/value pair, it is an OnlyThis as long as the most recent
        //        cursor step was *not* otherCursor advancing from a tie. The extra condition avoids erroneous OnlyOther calls
        //        that would occur due to otherCursor being the "leader".
        //    - Otherwise, if both cursors point to nodes, compare them. If they are equal by reference (shared), skip
        //      both cursors to the next node in the walk.
        // - Once one cursor has finished stepping, any remaining steps (if any) are taken and key/value pairs are logged
        //   as OnlyOther (if otherCursor is stepping) or OnlyThis (if thisCursor is stepping).
        // This algorithm gives the critical guarantee that all locations (both nodes and key/value pairs) in both trees that
        // are identical by value (and possibly by reference) will be visited *at the same time* by the cursors.
        // This removes the possibility of emitting incorrect diffs, as well as allowing for skipping shared nodes.
        const { _compare } = this;
        const thisCursor = BTree.makeDiffCursor(this);
        const otherCursor = BTree.makeDiffCursor(other);
        // It doesn't matter how thisSteppedLast is initialized.
        // Step order is only used when either cursor is at a leaf, and cursors always start at a node.
        let thisSuccess = true, otherSuccess = true, prevCursorOrder = BTree.compare(thisCursor, otherCursor, _compare);
        while (thisSuccess && otherSuccess) {
            const cursorOrder = BTree.compare(thisCursor, otherCursor, _compare);
            const { leaf: thisLeaf, internalSpine: thisInternalSpine, levelIndices: thisLevelIndices, } = thisCursor;
            const { leaf: otherLeaf, internalSpine: otherInternalSpine, levelIndices: otherLevelIndices, } = otherCursor;
            if (thisLeaf || otherLeaf) {
                // If the cursors were at the same location last step, then there is no work to be done.
                if (prevCursorOrder !== 0) {
                    if (cursorOrder === 0) {
                        if (thisLeaf && otherLeaf && different) {
                            // Equal keys, check for modifications
                            const valThis = thisLeaf.values[thisLevelIndices[thisLevelIndices.length - 1]];
                            const valOther = otherLeaf.values[otherLevelIndices[otherLevelIndices.length - 1]];
                            if (!Object.is(valThis, valOther)) {
                                const result = different(thisCursor.currentKey, valThis, valOther);
                                if (result && result.break)
                                    return result.break;
                            }
                        }
                    }
                    else if (cursorOrder > 0) {
                        // If this is the case, we know that either:
                        // 1. otherCursor stepped last from a starting position that trailed thisCursor, and is still behind, or
                        // 2. thisCursor stepped last and leapfrogged otherCursor
                        // Either of these cases is an "only other"
                        if (otherLeaf && onlyOther) {
                            const otherVal = otherLeaf.values[otherLevelIndices[otherLevelIndices.length - 1]];
                            const result = onlyOther(otherCursor.currentKey, otherVal);
                            if (result && result.break)
                                return result.break;
                        }
                    }
                    else if (onlyThis) {
                        if (thisLeaf && prevCursorOrder !== 0) {
                            const valThis = thisLeaf.values[thisLevelIndices[thisLevelIndices.length - 1]];
                            const result = onlyThis(thisCursor.currentKey, valThis);
                            if (result && result.break)
                                return result.break;
                        }
                    }
                }
            }
            else if (!thisLeaf && !otherLeaf && cursorOrder === 0) {
                const lastThis = thisInternalSpine.length - 1;
                const lastOther = otherInternalSpine.length - 1;
                const nodeThis = thisInternalSpine[lastThis][thisLevelIndices[lastThis]];
                const nodeOther = otherInternalSpine[lastOther][otherLevelIndices[lastOther]];
                if (nodeOther === nodeThis) {
                    prevCursorOrder = 0;
                    thisSuccess = BTree.step(thisCursor, true);
                    otherSuccess = BTree.step(otherCursor, true);
                    continue;
                }
            }
            prevCursorOrder = cursorOrder;
            if (cursorOrder < 0) {
                thisSuccess = BTree.step(thisCursor);
            }
            else {
                otherSuccess = BTree.step(otherCursor);
            }
        }
        if (thisSuccess && onlyThis)
            return BTree.finishCursorWalk(thisCursor, otherCursor, _compare, onlyThis);
        if (otherSuccess && onlyOther)
            return BTree.finishCursorWalk(otherCursor, thisCursor, _compare, onlyOther);
    }
    ///////////////////////////////////////////////////////////////////////////
    // Helper methods for diffAgainst /////////////////////////////////////////
    static finishCursorWalk(cursor, cursorFinished, compareKeys, callback) {
        const compared = BTree.compare(cursor, cursorFinished, compareKeys);
        if (compared === 0) {
            if (!BTree.step(cursor))
                return undefined;
        }
        else if (compared < 0) {
            check(false, "cursor walk terminated early");
        }
        return BTree.stepToEnd(cursor, callback);
    }
    static stepToEnd(cursor, callback) {
        let canStep = true;
        while (canStep) {
            const { leaf, levelIndices, currentKey } = cursor;
            if (leaf) {
                const value = leaf.values[levelIndices[levelIndices.length - 1]];
                const result = callback(currentKey, value);
                if (result && result.break)
                    return result.break;
            }
            canStep = BTree.step(cursor);
        }
        return undefined;
    }
    static makeDiffCursor(tree) {
        const { _root, height } = tree;
        return {
            height: height,
            internalSpine: [[_root]],
            levelIndices: [0],
            leaf: undefined,
            currentKey: _root.maxKey(),
        };
    }
    /**
     * Advances the cursor to the next step in the walk of its tree.
     * Cursors are walked backwards in sort order, as this allows them to leverage maxKey() in order to be compared in O(1).
     * @param cursor The cursor to step
     * @param stepToNode If true, the cursor will be advanced to the next node (skipping values)
     * @returns true if the step was completed and false if the step would have caused the cursor to move beyond the end of the tree.
     */
    static step(cursor, stepToNode) {
        const { internalSpine, levelIndices, leaf } = cursor;
        if (stepToNode === true || leaf) {
            const levelsLength = levelIndices.length;
            // Step to the next node only if:
            // - We are explicitly directed to via stepToNode, or
            // - There are no key/value pairs left to step to in this leaf
            if (stepToNode === true || levelIndices[levelsLength - 1] === 0) {
                const spineLength = internalSpine.length;
                // Root is leaf
                if (spineLength === 0)
                    return false;
                // Walk back up the tree until we find a new subtree to descend into
                const nodeLevelIndex = spineLength - 1;
                let levelIndexWalkBack = nodeLevelIndex;
                while (levelIndexWalkBack >= 0) {
                    if (levelIndices[levelIndexWalkBack] > 0) {
                        if (levelIndexWalkBack < levelsLength - 1) {
                            // Remove leaf state from cursor
                            cursor.leaf = undefined;
                            levelIndices.pop();
                        }
                        // If we walked upwards past any internal node, slice them out
                        if (levelIndexWalkBack < nodeLevelIndex)
                            cursor.internalSpine = internalSpine.slice(0, levelIndexWalkBack + 1);
                        // Move to new internal node
                        cursor.currentKey = internalSpine[levelIndexWalkBack][--levelIndices[levelIndexWalkBack]].maxKey();
                        return true;
                    }
                    levelIndexWalkBack--;
                }
                // Cursor is in the far left leaf of the tree, no more nodes to enumerate
                return false;
            }
            else {
                // Move to new leaf value
                const valueIndex = --levelIndices[levelsLength - 1];
                cursor.currentKey = leaf.keys[valueIndex];
                return true;
            }
        }
        else {
            // Cursor does not point to a value in a leaf, so move downwards
            const nextLevel = internalSpine.length;
            const currentLevel = nextLevel - 1;
            const node = internalSpine[currentLevel][levelIndices[currentLevel]];
            if (node.isLeaf) {
                // Entering into a leaf. Set the cursor to point at the last key/value pair.
                cursor.leaf = node;
                const valueIndex = (levelIndices[nextLevel] = node.values.length - 1);
                cursor.currentKey = node.keys[valueIndex];
            }
            else {
                const children = node.children;
                internalSpine[nextLevel] = children;
                const childIndex = children.length - 1;
                levelIndices[nextLevel] = childIndex;
                cursor.currentKey = children[childIndex].maxKey();
            }
            return true;
        }
    }
    /**
     * Compares the two cursors. Returns a value indicating which cursor is ahead in a walk.
     * Note that cursors are advanced in reverse sorting order.
     */
    static compare(cursorA, cursorB, compareKeys) {
        const { height: heightA, currentKey: currentKeyA, levelIndices: levelIndicesA, } = cursorA;
        const { height: heightB, currentKey: currentKeyB, levelIndices: levelIndicesB, } = cursorB;
        // Reverse the comparison order, as cursors are advanced in reverse sorting order
        const keyComparison = compareKeys(currentKeyB, currentKeyA);
        if (keyComparison !== 0) {
            return keyComparison;
        }
        // Normalize depth values relative to the shortest tree.
        // This ensures that concurrent cursor walks of trees of differing heights can reliably land on shared nodes at the same time.
        // To accomplish this, a cursor that is on an internal node at depth D1 with maxKey X is considered "behind" a cursor on an
        // internal node at depth D2 with maxKey Y, when D1 < D2. Thus, always walking the cursor that is "behind" will allow the cursor
        // at shallower depth (but equal maxKey) to "catch up" and land on shared nodes.
        const heightMin = heightA < heightB ? heightA : heightB;
        const depthANormalized = levelIndicesA.length - (heightA - heightMin);
        const depthBNormalized = levelIndicesB.length - (heightB - heightMin);
        return depthANormalized - depthBNormalized;
    }
    // End of helper methods for diffAgainst //////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    /** Returns a new iterator for iterating the keys of each pair in ascending order.
     *  @param firstKey: Minimum key to include in the output. */
    keys(firstKey) {
        var it = this.entries(firstKey, ReusedArray);
        return iterator(() => {
            var n = it.next();
            if (n.value)
                n.value = n.value[0];
            return n;
        });
    }
    /** Returns a new iterator for iterating the values of each pair in order by key.
     *  @param firstKey: Minimum key whose associated value is included in the output. */
    values(firstKey) {
        var it = this.entries(firstKey, ReusedArray);
        return iterator(() => {
            var n = it.next();
            if (n.value)
                n.value = n.value[1];
            return n;
        });
    }
    /////////////////////////////////////////////////////////////////////////////
    // Additional methods ///////////////////////////////////////////////////////
    /** Returns the maximum number of children/values before nodes will split. */
    get maxNodeSize() {
        return this._maxNodeSize;
    }
    /** Gets the lowest key in the tree. Complexity: O(log size) */
    minKey() {
        return this._root.minKey();
    }
    /** Gets the highest key in the tree. Complexity: O(1) */
    maxKey() {
        return this._root.maxKey();
    }
    /** Quickly clones the tree by marking the root node as shared.
     *  Both copies remain editable. When you modify either copy, any
     *  nodes that are shared (or potentially shared) between the two
     *  copies are cloned so that the changes do not affect other copies.
     *  This is known as copy-on-write behavior, or "lazy copying". */
    clone() {
        this._root.isShared = true;
        var result = new BTree(undefined, this._compare, this._maxNodeSize);
        result._root = this._root;
        result._size = this._size;
        return result;
    }
    /** Performs a greedy clone, immediately duplicating any nodes that are
     *  not currently marked as shared, in order to avoid marking any nodes
     *  as shared.
     *  @param force Clone all nodes, even shared ones.
     */
    greedyClone(force) {
        var result = new BTree(undefined, this._compare, this._maxNodeSize);
        result._root = this._root.greedyClone(force);
        result._size = this._size;
        return result;
    }
    /** Gets an array filled with the contents of the tree, sorted by key */
    toArray(maxLength = 0x7fffffff) {
        let min = this.minKey(), max = this.maxKey();
        if (min !== undefined)
            return this.getRange(min, max, true, maxLength);
        return [];
    }
    /** Gets an array of all keys, sorted */
    keysArray() {
        var results = [];
        this._root.forRange(this.minKey(), this.maxKey(), true, false, this, 0, (k, v) => {
            results.push(k);
        });
        return results;
    }
    /** Gets an array of all values, sorted by key */
    valuesArray() {
        var results = [];
        this._root.forRange(this.minKey(), this.maxKey(), true, false, this, 0, (k, v) => {
            results.push(v);
        });
        return results;
    }
    /** Gets a string representing the tree's data based on toArray(). */
    toString() {
        return this.toArray().toString();
    }
    /** Stores a key-value pair only if the key doesn't already exist in the tree.
     * @returns true if a new key was added
     */
    setIfNotPresent(key, value) {
        return this.set(key, value, false);
    }
    /** Returns the next pair whose key is larger than the specified key (or undefined if there is none).
     * If key === undefined, this function returns the lowest pair.
     * @param key The key to search for.
     * @param reusedArray Optional array used repeatedly to store key-value pairs, to
     * avoid creating a new array on every iteration.
     */
    nextHigherPair(key, reusedArray) {
        reusedArray = reusedArray || [];
        if (key === undefined) {
            return this._root.minPair(reusedArray);
        }
        return this._root.getPairOrNextHigher(key, this._compare, false, reusedArray);
    }
    /** Returns the next key larger than the specified key, or undefined if there is none.
     *  Also, nextHigherKey(undefined) returns the lowest key.
     */
    nextHigherKey(key) {
        var p = this.nextHigherPair(key, ReusedArray);
        return p && p[0];
    }
    /** Returns the next pair whose key is smaller than the specified key (or undefined if there is none).
     *  If key === undefined, this function returns the highest pair.
     * @param key The key to search for.
     * @param reusedArray Optional array used repeatedly to store key-value pairs, to
     *        avoid creating a new array each time you call this method.
     */
    nextLowerPair(key, reusedArray) {
        reusedArray = reusedArray || [];
        if (key === undefined) {
            return this._root.maxPair(reusedArray);
        }
        return this._root.getPairOrNextLower(key, this._compare, false, reusedArray);
    }
    /** Returns the next key smaller than the specified key, or undefined if there is none.
     *  Also, nextLowerKey(undefined) returns the highest key.
     */
    nextLowerKey(key) {
        var p = this.nextLowerPair(key, ReusedArray);
        return p && p[0];
    }
    /** Returns the key-value pair associated with the supplied key if it exists
     *  or the pair associated with the next lower pair otherwise. If there is no
     *  next lower pair, undefined is returned.
     * @param key The key to search for.
     * @param reusedArray Optional array used repeatedly to store key-value pairs, to
     *        avoid creating a new array each time you call this method.
     * */
    getPairOrNextLower(key, reusedArray) {
        return this._root.getPairOrNextLower(key, this._compare, true, reusedArray || []);
    }
    /** Returns the key-value pair associated with the supplied key if it exists
     *  or the pair associated with the next lower pair otherwise. If there is no
     *  next lower pair, undefined is returned.
     * @param key The key to search for.
     * @param reusedArray Optional array used repeatedly to store key-value pairs, to
     *        avoid creating a new array each time you call this method.
     * */
    getPairOrNextHigher(key, reusedArray) {
        return this._root.getPairOrNextHigher(key, this._compare, true, reusedArray || []);
    }
    /** Edits the value associated with a key in the tree, if it already exists.
     * @returns true if the key existed, false if not.
     */
    changeIfPresent(key, value) {
        return this.editRange(key, key, true, (k, v) => ({ value })) !== 0;
    }
    /**
     * Builds an array of pairs from the specified range of keys, sorted by key.
     * Each returned pair is also an array: pair[0] is the key, pair[1] is the value.
     * @param low The first key in the array will be greater than or equal to `low`.
     * @param high This method returns when a key larger than this is reached.
     * @param includeHigh If the `high` key is present, its pair will be included
     *        in the output if and only if this parameter is true. Note: if the
     *        `low` key is present, it is always included in the output.
     * @param maxLength Length limit. getRange will stop scanning the tree when
     *                  the array reaches this size.
     * @description Computational complexity: O(result.length + log size)
     */
    getRange(low, high, includeHigh, maxLength = 0x3ffffff) {
        var results = [];
        this._root.forRange(low, high, includeHigh, false, this, 0, (k, v) => {
            results.push([k, v]);
            return results.length > maxLength ? Break : undefined;
        });
        return results;
    }
    /** Adds all pairs from a list of key-value pairs.
     * @param pairs Pairs to add to this tree. If there are duplicate keys,
     *        later pairs currently overwrite earlier ones (e.g. [[0,1],[0,7]]
     *        associates 0 with 7.)
     * @param overwrite Whether to overwrite pairs that already exist (if false,
     *        pairs[i] is ignored when the key pairs[i][0] already exists.)
     * @returns The number of pairs added to the collection.
     * @description Computational complexity: O(pairs.length * log(size + pairs.length))
     */
    setPairs(pairs, overwrite) {
        var added = 0;
        for (var i = 0; i < pairs.length; i++)
            if (this.set(pairs[i][0], pairs[i][1], overwrite))
                added++;
        return added;
    }
    /**
     * Scans the specified range of keys, in ascending order by key.
     * Note: the callback `onFound` must not insert or remove items in the
     * collection. Doing so may cause incorrect data to be sent to the
     * callback afterward.
     * @param low The first key scanned will be greater than or equal to `low`.
     * @param high Scanning stops when a key larger than this is reached.
     * @param includeHigh If the `high` key is present, `onFound` is called for
     *        that final pair if and only if this parameter is true.
     * @param onFound A function that is called for each key-value pair. This
     *        function can return {break:R} to stop early with result R.
     * @param initialCounter Initial third argument of onFound. This value
     *        increases by one each time `onFound` is called. Default: 0
     * @returns The number of values found, or R if the callback returned
     *        `{break:R}` to stop early.
     * @description Computational complexity: O(number of items scanned + log size)
     */
    forRange(low, high, includeHigh, onFound, initialCounter) {
        var r = this._root.forRange(low, high, includeHigh, false, this, initialCounter || 0, onFound);
        return typeof r === "number" ? r : r.break;
    }
    /**
     * Scans and potentially modifies values for a subsequence of keys.
     * Note: the callback `onFound` should ideally be a pure function.
     *   Specifically, it must not insert items, call clone(), or change
     *   the collection except via return value; out-of-band editing may
     *   cause an exception or may cause incorrect data to be sent to
     *   the callback (duplicate or missed items). It must not cause a
     *   clone() of the collection, otherwise the clone could be modified
     *   by changes requested by the callback.
     * @param low The first key scanned will be greater than or equal to `low`.
     * @param high Scanning stops when a key larger than this is reached.
     * @param includeHigh If the `high` key is present, `onFound` is called for
     *        that final pair if and only if this parameter is true.
     * @param onFound A function that is called for each key-value pair. This
     *        function can return `{value:v}` to change the value associated
     *        with the current key, `{delete:true}` to delete the current pair,
     *        `{break:R}` to stop early with result R, or it can return nothing
     *        (undefined or {}) to cause no effect and continue iterating.
     *        `{break:R}` can be combined with one of the other two commands.
     *        The third argument `counter` is the number of items iterated
     *        previously; it equals 0 when `onFound` is called the first time.
     * @returns The number of values scanned, or R if the callback returned
     *        `{break:R}` to stop early.
     * @description
     *   Computational complexity: O(number of items scanned + log size)
     *   Note: if the tree has been cloned with clone(), any shared
     *   nodes are copied before `onFound` is called. This takes O(n) time
     *   where n is proportional to the amount of shared data scanned.
     */
    editRange(low, high, includeHigh, onFound, initialCounter) {
        var root = this._root;
        if (root.isShared)
            this._root = root = root.clone();
        try {
            var r = root.forRange(low, high, includeHigh, true, this, initialCounter || 0, onFound);
            return typeof r === "number" ? r : r.break;
        }
        finally {
            while (root.keys.length <= 1 && !root.isLeaf)
                this._root = root =
                    root.keys.length === 0
                        ? EmptyLeaf
                        : root.children[0];
        }
    }
    /** Same as `editRange` except that the callback is called for all pairs. */
    editAll(onFound, initialCounter) {
        return this.editRange(this.minKey(), this.maxKey(), true, onFound, initialCounter);
    }
    /**
     * Removes a range of key-value pairs from the B+ tree.
     * @param low The first key scanned will be greater than or equal to `low`.
     * @param high Scanning stops when a key larger than this is reached.
     * @param includeHigh Specifies whether the `high` key, if present, is deleted.
     * @returns The number of key-value pairs that were deleted.
     * @description Computational complexity: O(log size + number of items deleted)
     */
    deleteRange(low, high, includeHigh) {
        return this.editRange(low, high, includeHigh, DeleteRange);
    }
    /** Deletes a series of keys from the collection. */
    deleteKeys(keys) {
        for (var i = 0, r = 0; i < keys.length; i++)
            if (this.delete(keys[i]))
                r++;
        return r;
    }
    /** Gets the height of the tree: the number of internal nodes between the
     *  BTree object and its leaf nodes (zero if there are no internal nodes). */
    get height() {
        let node = this._root;
        let height = -1;
        while (node) {
            height++;
            node = node.isLeaf
                ? undefined
                : node.children[0];
        }
        return height;
    }
    /** Makes the object read-only to ensure it is not accidentally modified.
     *  Freezing does not have to be permanent; unfreeze() reverses the effect.
     *  This is accomplished by replacing mutator functions with a function
     *  that throws an Error. Compared to using a property (e.g. this.isFrozen)
     *  this implementation gives better performance in non-frozen BTrees.
     */
    freeze() {
        var t = this;
        // Note: all other mutators ultimately call set() or editRange()
        //       so we don't need to override those others.
        t.clear = t.set = t.editRange = function () {
            throw new Error("Attempted to modify a frozen BTree");
        };
    }
    /** Ensures mutations are allowed, reversing the effect of freeze(). */
    unfreeze() {
        // @ts-ignore "The operand of a 'delete' operator must be optional."
        //            (wrong: delete does not affect the prototype.)
        delete this.clear;
        // @ts-ignore
        delete this.set;
        // @ts-ignore
        delete this.editRange;
    }
    /** Returns true if the tree appears to be frozen. */
    get isFrozen() {
        return this.hasOwnProperty("editRange");
    }
    /** Scans the tree for signs of serious bugs (e.g. this.size doesn't match
     *  number of elements, internal nodes not caching max element properly...)
     *  Computational complexity: O(number of nodes), i.e. O(size). This method
     *  skips the most expensive test - whether all keys are sorted - but it
     *  does check that maxKey() of the children of internal nodes are sorted. */
    checkValid() {
        var size = this._root.checkValid(0, this, 0);
        check(size === this.size, "size mismatch: counted ", size, "but stored", this.size);
    }
}
if (Symbol && Symbol.iterator)
    // iterator is equivalent to entries()
    BTree.prototype[Symbol.iterator] = BTree.prototype.entries;
BTree.prototype.where = BTree.prototype.filter;
BTree.prototype.setRange = BTree.prototype.setPairs;
BTree.prototype.add = BTree.prototype.set;
function iterator(next = () => ({ done: true, value: undefined })) {
    var result = { next };
    if (Symbol && Symbol.iterator)
        result[Symbol.iterator] = function () {
            return this;
        };
    return result;
}
/** Leaf node / base class. **************************************************/
class BNode {
    constructor(keys = [], values) {
        this.keys = keys;
        this.values = values || undefVals;
        this.isShared = undefined;
    }
    get isLeaf() {
        return this.children === undefined;
    }
    ///////////////////////////////////////////////////////////////////////////
    // Shared methods /////////////////////////////////////////////////////////
    maxKey() {
        return this.keys[this.keys.length - 1];
    }
    // If key not found, returns i^failXor where i is the insertion index.
    // Callers that don't care whether there was a match will set failXor=0.
    indexOf(key, failXor, cmp) {
        const keys = this.keys;
        var lo = 0, hi = keys.length, mid = hi >> 1;
        while (lo < hi) {
            var c = cmp(keys[mid], key);
            if (c < 0)
                lo = mid + 1;
            else if (c > 0)
                // key < keys[mid]
                hi = mid;
            else if (c === 0)
                return mid;
            else {
                // c is NaN or otherwise invalid
                if (key === key)
                    // at least the search key is not NaN
                    return keys.length;
                else
                    throw new Error("BTree: NaN was used as a key");
            }
            mid = (lo + hi) >> 1;
        }
        return mid ^ failXor;
        // Unrolled version: benchmarks show same speed, not worth using
        /*var i = 1, c: number = 0, sum = 0;
        if (keys.length >= 4) {
          i = 3;
          if (keys.length >= 8) {
            i = 7;
            if (keys.length >= 16) {
              i = 15;
              if (keys.length >= 32) {
                i = 31;
                if (keys.length >= 64) {
                  i = 127;
                  i += (c = i < keys.length ? cmp(keys[i], key) : 1) < 0 ? 64 : -64;
                  sum += c;
                  i += (c = i < keys.length ? cmp(keys[i], key) : 1) < 0 ? 32 : -32;
                  sum += c;
                }
                i += (c = i < keys.length ? cmp(keys[i], key) : 1) < 0 ? 16 : -16;
                sum += c;
              }
              i += (c = i < keys.length ? cmp(keys[i], key) : 1) < 0 ? 8 : -8;
              sum += c;
            }
            i += (c = i < keys.length ? cmp(keys[i], key) : 1) < 0 ? 4 : -4;
            sum += c;
          }
          i += (c = i < keys.length ? cmp(keys[i], key) : 1) < 0 ? 2 : -2;
          sum += c;
        }
        i += (c = i < keys.length ? cmp(keys[i], key) : 1) < 0 ? 1 : -1;
        c = i < keys.length ? cmp(keys[i], key) : 1;
        sum += c;
        if (c < 0) {
          ++i;
          c = i < keys.length ? cmp(keys[i], key) : 1;
          sum += c;
        }
        if (sum !== sum) {
          if (key === key) // at least the search key is not NaN
            return keys.length ^ failXor;
          else
            throw new Error("BTree: NaN was used as a key");
        }
        return c === 0 ? i : i ^ failXor;*/
    }
    /////////////////////////////////////////////////////////////////////////////
    // Leaf Node: misc //////////////////////////////////////////////////////////
    minKey() {
        return this.keys[0];
    }
    minPair(reusedArray) {
        if (this.keys.length === 0)
            return undefined;
        reusedArray[0] = this.keys[0];
        reusedArray[1] = this.values[0];
        return reusedArray;
    }
    maxPair(reusedArray) {
        if (this.keys.length === 0)
            return undefined;
        const lastIndex = this.keys.length - 1;
        reusedArray[0] = this.keys[lastIndex];
        reusedArray[1] = this.values[lastIndex];
        return reusedArray;
    }
    clone() {
        var v = this.values;
        return new BNode(this.keys.slice(0), v === undefVals ? v : v.slice(0));
    }
    greedyClone(force) {
        return this.isShared && !force ? this : this.clone();
    }
    get(key, defaultValue, tree) {
        var i = this.indexOf(key, -1, tree._compare);
        return i < 0 ? defaultValue : this.values[i];
    }
    getPairOrNextLower(key, compare, inclusive, reusedArray) {
        var i = this.indexOf(key, -1, compare);
        const indexOrLower = i < 0 ? ~i - 1 : inclusive ? i : i - 1;
        if (indexOrLower >= 0) {
            reusedArray[0] = this.keys[indexOrLower];
            reusedArray[1] = this.values[indexOrLower];
            return reusedArray;
        }
        return undefined;
    }
    getPairOrNextHigher(key, compare, inclusive, reusedArray) {
        var i = this.indexOf(key, -1, compare);
        const indexOrLower = i < 0 ? ~i : inclusive ? i : i + 1;
        const keys = this.keys;
        if (indexOrLower < keys.length) {
            reusedArray[0] = keys[indexOrLower];
            reusedArray[1] = this.values[indexOrLower];
            return reusedArray;
        }
        return undefined;
    }
    checkValid(depth, tree, baseIndex) {
        var kL = this.keys.length, vL = this.values.length;
        check(this.values === undefVals ? kL <= vL : kL === vL, "keys/values length mismatch: depth", depth, "with lengths", kL, vL, "and baseIndex", baseIndex);
        // Note: we don't check for "node too small" because sometimes a node
        // can legitimately have size 1. This occurs if there is a batch
        // deletion, leaving a node of size 1, and the siblings are full so
        // it can't be merged with adjacent nodes. However, the parent will
        // verify that the average node size is at least half of the maximum.
        check(depth == 0 || kL > 0, "empty leaf at depth", depth, "and baseIndex", baseIndex);
        return kL;
    }
    /////////////////////////////////////////////////////////////////////////////
    // Leaf Node: set & node splitting //////////////////////////////////////////
    set(key, value, overwrite, tree) {
        var i = this.indexOf(key, -1, tree._compare);
        if (i < 0) {
            // key does not exist yet
            i = ~i;
            tree._size++;
            if (this.keys.length < tree._maxNodeSize) {
                return this.insertInLeaf(i, key, value, tree);
            }
            else {
                // This leaf node is full and must split
                var newRightSibling = this.splitOffRightSide(), target = this;
                if (i > this.keys.length) {
                    i -= this.keys.length;
                    target = newRightSibling;
                }
                target.insertInLeaf(i, key, value, tree);
                return newRightSibling;
            }
        }
        else {
            // Key already exists
            if (overwrite !== false) {
                if (value !== undefined)
                    this.reifyValues();
                // usually this is a no-op, but some users may wish to edit the key
                this.keys[i] = key;
                this.values[i] = value;
            }
            return false;
        }
    }
    reifyValues() {
        if (this.values === undefVals)
            return (this.values = this.values.slice(0, this.keys.length));
        return this.values;
    }
    insertInLeaf(i, key, value, tree) {
        this.keys.splice(i, 0, key);
        if (this.values === undefVals) {
            while (undefVals.length < tree._maxNodeSize)
                undefVals.push(undefined);
            if (value === undefined) {
                return true;
            }
            else {
                this.values = undefVals.slice(0, this.keys.length - 1);
            }
        }
        this.values.splice(i, 0, value);
        return true;
    }
    takeFromRight(rhs) {
        // Reminder: parent node must update its copy of key for this node
        // assert: neither node is shared
        // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)
        var v = this.values;
        if (rhs.values === undefVals) {
            if (v !== undefVals)
                v.push(undefined);
        }
        else {
            v = this.reifyValues();
            v.push(rhs.values.shift());
        }
        this.keys.push(rhs.keys.shift());
    }
    takeFromLeft(lhs) {
        // Reminder: parent node must update its copy of key for this node
        // assert: neither node is shared
        // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)
        var v = this.values;
        if (lhs.values === undefVals) {
            if (v !== undefVals)
                v.unshift(undefined);
        }
        else {
            v = this.reifyValues();
            v.unshift(lhs.values.pop());
        }
        this.keys.unshift(lhs.keys.pop());
    }
    splitOffRightSide() {
        // Reminder: parent node must update its copy of key for this node
        var half = this.keys.length >> 1, keys = this.keys.splice(half);
        var values = this.values === undefVals ? undefVals : this.values.splice(half);
        return new BNode(keys, values);
    }
    /////////////////////////////////////////////////////////////////////////////
    // Leaf Node: scanning & deletions //////////////////////////////////////////
    forRange(low, high, includeHigh, editMode, tree, count, onFound) {
        var cmp = tree._compare;
        var iLow, iHigh;
        if (high === low) {
            if (!includeHigh)
                return count;
            iHigh = (iLow = this.indexOf(low, -1, cmp)) + 1;
            if (iLow < 0)
                return count;
        }
        else {
            iLow = this.indexOf(low, 0, cmp);
            iHigh = this.indexOf(high, -1, cmp);
            if (iHigh < 0)
                iHigh = ~iHigh;
            else if (includeHigh === true)
                iHigh++;
        }
        var keys = this.keys, values = this.values;
        if (onFound !== undefined) {
            for (var i = iLow; i < iHigh; i++) {
                var key = keys[i];
                var result = onFound(key, values[i], count++);
                if (result !== undefined) {
                    if (editMode === true) {
                        if (key !== keys[i] || this.isShared === true)
                            throw new Error("BTree illegally changed or cloned in editRange");
                        if (result.delete) {
                            this.keys.splice(i, 1);
                            if (this.values !== undefVals)
                                this.values.splice(i, 1);
                            tree._size--;
                            i--;
                            iHigh--;
                        }
                        else if (result.hasOwnProperty("value")) {
                            values[i] = result.value;
                        }
                    }
                    if (result.break !== undefined)
                        return result;
                }
            }
        }
        else
            count += iHigh - iLow;
        return count;
    }
    /** Adds entire contents of right-hand sibling (rhs is left unchanged) */
    mergeSibling(rhs, _) {
        this.keys.push.apply(this.keys, rhs.keys);
        if (this.values === undefVals) {
            if (rhs.values === undefVals)
                return;
            this.values = this.values.slice(0, this.keys.length);
        }
        this.values.push.apply(this.values, rhs.reifyValues());
    }
}
/** Internal node (non-leaf node) ********************************************/
class BNodeInternal extends BNode {
    constructor(children, keys) {
        if (!keys) {
            keys = [];
            for (var i = 0; i < children.length; i++)
                keys[i] = children[i].maxKey();
        }
        super(keys);
        this.children = children;
    }
    clone() {
        var children = this.children.slice(0);
        for (var i = 0; i < children.length; i++)
            children[i].isShared = true;
        return new BNodeInternal(children, this.keys.slice(0));
    }
    greedyClone(force) {
        if (this.isShared && !force)
            return this;
        var nu = new BNodeInternal(this.children.slice(0), this.keys.slice(0));
        for (var i = 0; i < nu.children.length; i++)
            nu.children[i] = nu.children[i].greedyClone();
        return nu;
    }
    minKey() {
        return this.children[0].minKey();
    }
    minPair(reusedArray) {
        return this.children[0].minPair(reusedArray);
    }
    maxPair(reusedArray) {
        return this.children[this.children.length - 1].maxPair(reusedArray);
    }
    get(key, defaultValue, tree) {
        var i = this.indexOf(key, 0, tree._compare), children = this.children;
        return i < children.length
            ? children[i].get(key, defaultValue, tree)
            : undefined;
    }
    getPairOrNextLower(key, compare, inclusive, reusedArray) {
        var i = this.indexOf(key, 0, compare), children = this.children;
        if (i >= children.length)
            return this.maxPair(reusedArray);
        const result = children[i].getPairOrNextLower(key, compare, inclusive, reusedArray);
        if (result === undefined && i > 0) {
            return children[i - 1].maxPair(reusedArray);
        }
        return result;
    }
    getPairOrNextHigher(key, compare, inclusive, reusedArray) {
        var i = this.indexOf(key, 0, compare), children = this.children, length = children.length;
        if (i >= length)
            return undefined;
        const result = children[i].getPairOrNextHigher(key, compare, inclusive, reusedArray);
        if (result === undefined && i < length - 1) {
            return children[i + 1].minPair(reusedArray);
        }
        return result;
    }
    checkValid(depth, tree, baseIndex) {
        let kL = this.keys.length, cL = this.children.length;
        check(kL === cL, "keys/children length mismatch: depth", depth, "lengths", kL, cL, "baseIndex", baseIndex);
        check(kL > 1 || depth > 0, "internal node has length", kL, "at depth", depth, "baseIndex", baseIndex);
        let size = 0, c = this.children, k = this.keys, childSize = 0;
        for (var i = 0; i < cL; i++) {
            size += c[i].checkValid(depth + 1, tree, baseIndex + size);
            childSize += c[i].keys.length;
            check(size >= childSize, "wtf", baseIndex); // no way this will ever fail
            check(i === 0 || c[i - 1].constructor === c[i].constructor, "type mismatch, baseIndex:", baseIndex);
            if (c[i].maxKey() != k[i])
                check(false, "keys[", i, "] =", k[i], "is wrong, should be ", c[i].maxKey(), "at depth", depth, "baseIndex", baseIndex);
            if (!(i === 0 || tree._compare(k[i - 1], k[i]) < 0))
                check(false, "sort violation at depth", depth, "index", i, "keys", k[i - 1], k[i]);
        }
        // 2020/08: BTree doesn't always avoid grossly undersized nodes,
        // but AFAIK such nodes are pretty harmless, so accept them.
        let toofew = childSize === 0; // childSize < (tree.maxNodeSize >> 1)*cL;
        if (toofew || childSize > tree.maxNodeSize * cL)
            check(false, toofew ? "too few" : "too many", "children (", childSize, size, ") at depth", depth, "maxNodeSize:", tree.maxNodeSize, "children.length:", cL, "baseIndex:", baseIndex);
        return size;
    }
    /////////////////////////////////////////////////////////////////////////////
    // Internal Node: set & node splitting //////////////////////////////////////
    set(key, value, overwrite, tree) {
        var c = this.children, max = tree._maxNodeSize, cmp = tree._compare;
        var i = Math.min(this.indexOf(key, 0, cmp), c.length - 1), child = c[i];
        if (child.isShared)
            c[i] = child = child.clone();
        if (child.keys.length >= max) {
            // child is full; inserting anything else will cause a split.
            // Shifting an item to the left or right sibling may avoid a split.
            // We can do a shift if the adjacent node is not full and if the
            // current key can still be placed in the same node after the shift.
            var other;
            if (i > 0 &&
                (other = c[i - 1]).keys.length < max &&
                cmp(child.keys[0], key) < 0) {
                if (other.isShared)
                    c[i - 1] = other = other.clone();
                other.takeFromRight(child);
                this.keys[i - 1] = other.maxKey();
            }
            else if ((other = c[i + 1]) !== undefined &&
                other.keys.length < max &&
                cmp(child.maxKey(), key) < 0) {
                if (other.isShared)
                    c[i + 1] = other = other.clone();
                other.takeFromLeft(child);
                this.keys[i] = c[i].maxKey();
            }
        }
        var result = child.set(key, value, overwrite, tree);
        if (result === false)
            return false;
        this.keys[i] = child.maxKey();
        if (result === true)
            return true;
        // The child has split and `result` is a new right child... does it fit?
        if (this.keys.length < max) {
            // yes
            this.insert(i + 1, result);
            return true;
        }
        else {
            // no, we must split also
            var newRightSibling = this.splitOffRightSide(), target = this;
            if (cmp(result.maxKey(), this.maxKey()) > 0) {
                target = newRightSibling;
                i -= this.keys.length;
            }
            target.insert(i + 1, result);
            return newRightSibling;
        }
    }
    insert(i, child) {
        this.children.splice(i, 0, child);
        this.keys.splice(i, 0, child.maxKey());
    }
    splitOffRightSide() {
        var half = this.children.length >> 1;
        return new BNodeInternal(this.children.splice(half), this.keys.splice(half));
    }
    takeFromRight(rhs) {
        // Reminder: parent node must update its copy of key for this node
        // assert: neither node is shared
        // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)
        this.keys.push(rhs.keys.shift());
        this.children.push(rhs.children.shift());
    }
    takeFromLeft(lhs) {
        // Reminder: parent node must update its copy of key for this node
        // assert: neither node is shared
        // assert rhs.keys.length > (maxNodeSize/2 && this.keys.length<maxNodeSize)
        this.keys.unshift(lhs.keys.pop());
        this.children.unshift(lhs.children.pop());
    }
    /////////////////////////////////////////////////////////////////////////////
    // Internal Node: scanning & deletions //////////////////////////////////////
    // Note: `count` is the next value of the third argument to `onFound`.
    //       A leaf node's `forRange` function returns a new value for this counter,
    //       unless the operation is to stop early.
    forRange(low, high, includeHigh, editMode, tree, count, onFound) {
        var cmp = tree._compare;
        var keys = this.keys, children = this.children;
        var iLow = this.indexOf(low, 0, cmp), i = iLow;
        var iHigh = Math.min(high === low ? iLow : this.indexOf(high, 0, cmp), keys.length - 1);
        if (!editMode) {
            // Simple case
            for (; i <= iHigh; i++) {
                var result = children[i].forRange(low, high, includeHigh, editMode, tree, count, onFound);
                if (typeof result !== "number")
                    return result;
                count = result;
            }
        }
        else if (i <= iHigh) {
            try {
                for (; i <= iHigh; i++) {
                    if (children[i].isShared)
                        children[i] = children[i].clone();
                    var result = children[i].forRange(low, high, includeHigh, editMode, tree, count, onFound);
                    // Note: if children[i] is empty then keys[i]=undefined.
                    //       This is an invalid state, but it is fixed below.
                    keys[i] = children[i].maxKey();
                    if (typeof result !== "number")
                        return result;
                    count = result;
                }
            }
            finally {
                // Deletions may have occurred, so look for opportunities to merge nodes.
                var half = tree._maxNodeSize >> 1;
                if (iLow > 0)
                    iLow--;
                for (i = iHigh; i >= iLow; i--) {
                    if (children[i].keys.length <= half) {
                        if (children[i].keys.length !== 0) {
                            this.tryMerge(i, tree._maxNodeSize);
                        }
                        else {
                            // child is empty! delete it!
                            keys.splice(i, 1);
                            children.splice(i, 1);
                        }
                    }
                }
                if (children.length !== 0 && children[0].keys.length === 0)
                    check(false, "emptiness bug");
            }
        }
        return count;
    }
    /** Merges child i with child i+1 if their combined size is not too large */
    tryMerge(i, maxSize) {
        var children = this.children;
        if (i >= 0 && i + 1 < children.length) {
            if (children[i].keys.length + children[i + 1].keys.length <= maxSize) {
                if (children[i].isShared)
                    // cloned already UNLESS i is outside scan range
                    children[i] = children[i].clone();
                children[i].mergeSibling(children[i + 1], maxSize);
                children.splice(i + 1, 1);
                this.keys.splice(i + 1, 1);
                this.keys[i] = children[i].maxKey();
                return true;
            }
        }
        return false;
    }
    mergeSibling(rhs, maxNodeSize) {
        // assert !this.isShared;
        var oldLength = this.keys.length;
        this.keys.push.apply(this.keys, rhs.keys);
        this.children.push.apply(this.children, rhs.children);
        // If our children are themselves almost empty due to a mass-delete,
        // they may need to be merged too (but only the oldLength-1 and its
        // right sibling should need this).
        this.tryMerge(oldLength - 1, maxNodeSize);
    }
}
// Optimization: this array of `undefined`s is used instead of a normal
// array of values in nodes where `undefined` is the only value.
// Its length is extended to max node size on first use; since it can
// be shared between trees with different maximums, its length can only
// increase, never decrease. Its type should be undefined[] but strangely
// TypeScript won't allow the comparison V[] === undefined[]. To prevent
// users from making this array too large, BTree has a maximum node size.
//
// FAQ: undefVals[i] is already undefined, so why increase the array size?
// Reading outside the bounds of an array is relatively slow because it
// has the side effect of scanning the prototype chain.
var undefVals = [];
const Delete = { delete: true }, DeleteRange = () => Delete;
const Break = { break: true };
const EmptyLeaf = (function () {
    var n = new BNode();
    n.isShared = true;
    return n;
})();
const EmptyArray = [];
const ReusedArray = []; // assumed thread-local
function check(fact, ...args) {
    if (!fact) {
        args.unshift("B+ tree"); // at beginning of message
        throw new Error(args.join(" "));
    }
}
/** A BTree frozen in the empty state. */
(() => {
    let t = new BTree();
    t.freeze();
    return t;
})();

/*
 Copyright 2017 Jeremy Scheff
 Copyright 2019 Florian Dold

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
*/
function getIndexKeys(value, keyPath, multiEntry) {
    if (multiEntry && Array.isArray(keyPath)) {
        const keys = [];
        for (const subkeyPath of keyPath) {
            const key = extractKey(subkeyPath, value);
            try {
                const k = valueToKey(key);
                keys.push(k);
            }
            catch (_a) {
                // Ignore invalid subkeys
            }
        }
        return keys;
    }
    else if (typeof keyPath === "string" || Array.isArray(keyPath)) {
        let key = extractKey(keyPath, value);
        if (key == null) {
            return [];
        }
        return [valueToKey(key)];
    }
    else {
        throw Error(`unsupported key path: ${typeof keyPath}`);
    }
}

/*
 Copyright 2019 Florian Dold

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 or implied. See the License for the specific language governing
 permissions and limitations under the License.
 */
var TransactionLevel;
(function (TransactionLevel) {
    TransactionLevel[TransactionLevel["None"] = 0] = "None";
    TransactionLevel[TransactionLevel["Read"] = 1] = "Read";
    TransactionLevel[TransactionLevel["Write"] = 2] = "Write";
    TransactionLevel[TransactionLevel["VersionChange"] = 3] = "VersionChange";
})(TransactionLevel || (TransactionLevel = {}));
class AsyncCondition {
    constructor() {
        const op = openPromise();
        this._waitPromise = op.promise;
        this._resolveWaitPromise = op.resolve;
    }
    wait() {
        return this._waitPromise;
    }
    trigger() {
        this._resolveWaitPromise();
        const op = openPromise();
        this._waitPromise = op.promise;
        this._resolveWaitPromise = op.resolve;
    }
}
function nextStoreKey(forward, data, k) {
    if (k === undefined || k === null) {
        return undefined;
    }
    const res = forward ? data.nextHigherPair(k) : data.nextLowerPair(k);
    if (!res) {
        return undefined;
    }
    return res[1].primaryKey;
}
function nextKey(forward, tree, key) {
    if (key != null) {
        return forward ? tree.nextHigherKey(key) : tree.nextLowerKey(key);
    }
    return forward ? tree.minKey() : tree.maxKey();
}
/**
 * Return the key that is furthest in
 * the direction indicated by the 'forward' flag.
 */
function furthestKey(forward, key1, key2) {
    if (key1 === undefined) {
        return key2;
    }
    if (key2 === undefined) {
        return key1;
    }
    const cmpResult = compareKeys(key1, key2);
    if (cmpResult === 0) {
        // Same result
        return key1;
    }
    if (forward && cmpResult === 1) {
        return key1;
    }
    if (forward && cmpResult === -1) {
        return key2;
    }
    if (!forward && cmpResult === 1) {
        return key2;
    }
    if (!forward && cmpResult === -1) {
        return key1;
    }
}
/**
 * Primitive in-memory backend.
 *
 * @public
 */
class MemoryBackend {
    constructor() {
        this.databases = {};
        this.connectionIdCounter = 1;
        this.transactionIdCounter = 1;
        /**
         * Connections by connection cookie.
         */
        this.connections = {};
        /**
         * Connections by transaction (!!) cookie.  In this implementation,
         * at most one transaction can run at the same time per connection.
         */
        this.connectionsByTransaction = {};
        /**
         * Condition that is triggered whenever a client disconnects.
         */
        this.disconnectCond = new AsyncCondition();
        /**
         * Condition that is triggered whenever a transaction finishes.
         */
        this.transactionDoneCond = new AsyncCondition();
        this.enableTracing = false;
        this.trackStats = true;
        this.accessStats = {
            readTransactions: 0,
            writeTransactions: 0,
            readsPerStore: {},
            readsPerIndex: {},
            readItemsPerIndex: {},
            readItemsPerStore: {},
            writesPerStore: {},
        };
    }
    /**
     * Load the data in this IndexedDB backend from a dump in JSON format.
     *
     * Must be called before any connections to the database backend have
     * been made.
     */
    importDump(dataJson) {
        if (this.transactionIdCounter != 1 || this.connectionIdCounter != 1) {
            throw Error("data must be imported before first transaction or connection");
        }
        // FIXME: validate!
        const data = structuredRevive(dataJson);
        if (typeof data !== "object") {
            throw Error("db dump corrupt");
        }
        this.databases = {};
        for (const dbName of Object.keys(data.databases)) {
            const schema = data.databases[dbName].schema;
            if (typeof schema !== "object") {
                throw Error("DB dump corrupt");
            }
            const objectStores = {};
            for (const objectStoreName of Object.keys(data.databases[dbName].objectStores)) {
                const storeSchema = schema.objectStores[objectStoreName];
                const dumpedObjectStore = data.databases[dbName].objectStores[objectStoreName];
                const pairs = dumpedObjectStore.records.map((r) => {
                    return structuredClone([r.primaryKey, r]);
                });
                const objectStoreData = new BTree(pairs, compareKeys);
                const objectStore = {
                    deleted: false,
                    modifiedData: undefined,
                    modifiedName: undefined,
                    modifiedKeyGenerator: undefined,
                    originalData: objectStoreData,
                    originalName: objectStoreName,
                    originalKeyGenerator: dumpedObjectStore.keyGenerator,
                    committedIndexes: {},
                    modifiedIndexes: {},
                };
                objectStores[objectStoreName] = objectStore;
                for (const indexName in storeSchema.indexes) {
                    const indexSchema = storeSchema.indexes[indexName];
                    const newIndex = {
                        deleted: false,
                        modifiedData: undefined,
                        modifiedName: undefined,
                        originalData: new BTree([], compareKeys),
                        originalName: indexName,
                    };
                    objectStore.committedIndexes[indexName] = newIndex;
                    objectStoreData.forEach((v, k) => {
                        try {
                            this.insertIntoIndex(newIndex, k, v.value, indexSchema);
                        }
                        catch (e) {
                            if (e instanceof DataError) {
                                // We don't propagate this error here.
                                return;
                            }
                            throw e;
                        }
                    });
                }
            }
            const db = {
                deleted: false,
                committedObjectStores: objectStores,
                committedSchema: structuredClone(schema),
                connectionCookies: [],
                txLevel: TransactionLevel.None,
                txRestrictObjectStores: undefined,
            };
            this.databases[dbName] = db;
        }
    }
    makeObjectStoreMap(database) {
        let map = {};
        for (let objectStoreName in database.committedObjectStores) {
            const store = database.committedObjectStores[objectStoreName];
            const entry = {
                store,
                indexMap: Object.assign({}, store.committedIndexes),
            };
            map[objectStoreName] = entry;
        }
        return map;
    }
    /**
     * Export the contents of the database to JSON.
     *
     * Only exports data that has been committed.
     */
    exportDump() {
        this.enableTracing && console.log("exporting dump");
        const dbDumps = {};
        for (const dbName of Object.keys(this.databases)) {
            const db = this.databases[dbName];
            const objectStores = {};
            for (const objectStoreName of Object.keys(db.committedObjectStores)) {
                const objectStore = db.committedObjectStores[objectStoreName];
                const objectStoreRecords = [];
                objectStore.originalData.forEach((v) => {
                    objectStoreRecords.push(structuredClone(v));
                });
                objectStores[objectStoreName] = {
                    name: objectStoreName,
                    records: objectStoreRecords,
                    keyGenerator: objectStore.originalKeyGenerator,
                };
            }
            const dbDump = {
                objectStores,
                schema: structuredClone(this.databases[dbName].committedSchema),
            };
            dbDumps[dbName] = dbDump;
        }
        return structuredEncapsulate({ databases: dbDumps });
    }
    getDatabases() {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.enableTracing) {
                console.log("TRACING: getDatabase");
            }
            const dbList = [];
            for (const name in this.databases) {
                dbList.push({
                    name,
                    version: this.databases[name].committedSchema.databaseVersion,
                });
            }
            return dbList;
        });
    }
    deleteDatabase(name) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.enableTracing) {
                console.log(`TRACING: deleteDatabase(${name})`);
            }
            const myDb = this.databases[name];
            if (!myDb) {
                throw Error("db not found");
            }
            if (myDb.committedSchema.databaseName !== name) {
                throw Error("name does not match");
            }
            while (myDb.txLevel !== TransactionLevel.None) {
                yield this.transactionDoneCond.wait();
            }
            myDb.deleted = true;
            delete this.databases[name];
        });
    }
    connectDatabase(name) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.enableTracing) {
                console.log(`TRACING: connectDatabase(${name})`);
            }
            const connectionId = this.connectionIdCounter++;
            const connectionCookie = `connection-${connectionId}`;
            let database = this.databases[name];
            if (!database) {
                const schema = {
                    databaseName: name,
                    databaseVersion: 0,
                    objectStores: {},
                };
                database = {
                    committedSchema: schema,
                    deleted: false,
                    committedObjectStores: {},
                    txLevel: TransactionLevel.None,
                    connectionCookies: [],
                    txRestrictObjectStores: undefined,
                };
                this.databases[name] = database;
            }
            if (database.connectionCookies.includes(connectionCookie)) {
                throw Error("already connected");
            }
            database.connectionCookies.push(connectionCookie);
            const myConn = {
                dbName: name,
                objectStoreMap: this.makeObjectStoreMap(database),
                modifiedSchema: structuredClone(database.committedSchema),
            };
            this.connections[connectionCookie] = myConn;
            return { connectionCookie };
        });
    }
    beginTransaction(conn, objectStores, mode) {
        return __awaiter(this, void 0, void 0, function* () {
            const transactionCookie = `tx-${this.transactionIdCounter++}`;
            if (this.enableTracing) {
                console.log(`TRACING: beginTransaction ${transactionCookie}`);
            }
            const myConn = this.connections[conn.connectionCookie];
            if (!myConn) {
                throw Error("connection not found");
            }
            const myDb = this.databases[myConn.dbName];
            if (!myDb) {
                throw Error("db not found");
            }
            while (myDb.txLevel !== TransactionLevel.None) {
                if (this.enableTracing) {
                    console.log(`TRACING: beginTransaction -- waiting for others to close`);
                }
                yield this.transactionDoneCond.wait();
            }
            if (mode === "readonly") {
                myDb.txLevel = TransactionLevel.Read;
            }
            else if (mode === "readwrite") {
                myDb.txLevel = TransactionLevel.Write;
            }
            else {
                throw Error("unsupported transaction mode");
            }
            if (this.trackStats) {
                if (mode === "readonly") {
                    this.accessStats.readTransactions++;
                }
                else if (mode === "readwrite") {
                    this.accessStats.writeTransactions++;
                }
            }
            myDb.txRestrictObjectStores = [...objectStores];
            this.connectionsByTransaction[transactionCookie] = myConn;
            return { transactionCookie };
        });
    }
    enterVersionChange(conn, newVersion) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.enableTracing) {
                console.log(`TRACING: enterVersionChange`);
            }
            const transactionCookie = `tx-vc-${this.transactionIdCounter++}`;
            const myConn = this.connections[conn.connectionCookie];
            if (!myConn) {
                throw Error("connection not found");
            }
            const myDb = this.databases[myConn.dbName];
            if (!myDb) {
                throw Error("db not found");
            }
            while (myDb.txLevel !== TransactionLevel.None) {
                yield this.transactionDoneCond.wait();
            }
            myDb.txLevel = TransactionLevel.VersionChange;
            myDb.txOwnerConnectionCookie = conn.connectionCookie;
            myDb.txOwnerTransactionCookie = transactionCookie;
            myDb.txRestrictObjectStores = undefined;
            this.connectionsByTransaction[transactionCookie] = myConn;
            myConn.modifiedSchema.databaseVersion = newVersion;
            return { transactionCookie };
        });
    }
    close(conn) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.enableTracing) {
                console.log(`TRACING: close (${conn.connectionCookie})`);
            }
            const myConn = this.connections[conn.connectionCookie];
            if (!myConn) {
                throw Error("connection not found - already closed?");
            }
            const myDb = this.databases[myConn.dbName];
            if (myDb) {
                // FIXME: what if we're still in a transaction?
                myDb.connectionCookies = myDb.connectionCookies.filter((x) => x != conn.connectionCookie);
            }
            delete this.connections[conn.connectionCookie];
            this.disconnectCond.trigger();
        });
    }
    requireConnection(dbConn) {
        const myConn = this.connections[dbConn.connectionCookie];
        if (!myConn) {
            throw Error(`unknown connection (${dbConn.connectionCookie})`);
        }
        return myConn;
    }
    requireConnectionFromTransaction(btx) {
        const myConn = this.connectionsByTransaction[btx.transactionCookie];
        if (!myConn) {
            throw Error(`unknown transaction (${btx.transactionCookie})`);
        }
        return myConn;
    }
    getSchema(dbConn) {
        if (this.enableTracing) {
            console.log(`TRACING: getSchema`);
        }
        const myConn = this.requireConnection(dbConn);
        const db = this.databases[myConn.dbName];
        if (!db) {
            throw Error("db not found");
        }
        return db.committedSchema;
    }
    getCurrentTransactionSchema(btx) {
        const myConn = this.requireConnectionFromTransaction(btx);
        const db = this.databases[myConn.dbName];
        if (!db) {
            throw Error("db not found");
        }
        return myConn.modifiedSchema;
    }
    getInitialTransactionSchema(btx) {
        const myConn = this.requireConnectionFromTransaction(btx);
        const db = this.databases[myConn.dbName];
        if (!db) {
            throw Error("db not found");
        }
        return db.committedSchema;
    }
    renameIndex(btx, objectStoreName, oldName, newName) {
        if (this.enableTracing) {
            console.log(`TRACING: renameIndex(?, ${oldName}, ${newName})`);
        }
        const myConn = this.requireConnectionFromTransaction(btx);
        const db = this.databases[myConn.dbName];
        if (!db) {
            throw Error("db not found");
        }
        if (db.txLevel < TransactionLevel.VersionChange) {
            throw Error("only allowed in versionchange transaction");
        }
        let schema = myConn.modifiedSchema;
        if (!schema) {
            throw Error();
        }
        const indexesSchema = schema.objectStores[objectStoreName].indexes;
        if (indexesSchema[newName]) {
            throw new Error("new index name already used");
        }
        if (!indexesSchema) {
            throw new Error("new index name already used");
        }
        const index = myConn.objectStoreMap[objectStoreName].indexMap[oldName];
        if (!index) {
            throw Error("old index missing in connection's index map");
        }
        indexesSchema[newName] = indexesSchema[newName];
        delete indexesSchema[oldName];
        myConn.objectStoreMap[objectStoreName].indexMap[newName] = index;
        delete myConn.objectStoreMap[objectStoreName].indexMap[oldName];
        index.modifiedName = newName;
    }
    deleteIndex(btx, objectStoreName, indexName) {
        if (this.enableTracing) {
            console.log(`TRACING: deleteIndex(${indexName})`);
        }
        const myConn = this.requireConnectionFromTransaction(btx);
        const db = this.databases[myConn.dbName];
        if (!db) {
            throw Error("db not found");
        }
        if (db.txLevel < TransactionLevel.VersionChange) {
            throw Error("only allowed in versionchange transaction");
        }
        let schema = myConn.modifiedSchema;
        if (!schema) {
            throw Error();
        }
        if (!schema.objectStores[objectStoreName].indexes[indexName]) {
            throw new Error("index does not exist");
        }
        const index = myConn.objectStoreMap[objectStoreName].indexMap[indexName];
        if (!index) {
            throw Error("old index missing in connection's index map");
        }
        index.deleted = true;
        delete schema.objectStores[objectStoreName].indexes[indexName];
        delete myConn.objectStoreMap[objectStoreName].indexMap[indexName];
    }
    deleteObjectStore(btx, name) {
        if (this.enableTracing) {
            console.log(`TRACING: deleteObjectStore(${name}) in ${btx.transactionCookie}`);
        }
        const myConn = this.requireConnectionFromTransaction(btx);
        const db = this.databases[myConn.dbName];
        if (!db) {
            throw Error("db not found");
        }
        if (db.txLevel < TransactionLevel.VersionChange) {
            throw Error("only allowed in versionchange transaction");
        }
        const schema = myConn.modifiedSchema;
        if (!schema) {
            throw Error();
        }
        const objectStoreProperties = schema.objectStores[name];
        if (!objectStoreProperties) {
            throw Error("object store not found");
        }
        const objectStoreMapEntry = myConn.objectStoreMap[name];
        if (!objectStoreMapEntry) {
            throw Error("object store not found in map");
        }
        const indexNames = Object.keys(objectStoreProperties.indexes);
        for (const indexName of indexNames) {
            this.deleteIndex(btx, name, indexName);
        }
        objectStoreMapEntry.store.deleted = true;
        delete myConn.objectStoreMap[name];
        delete schema.objectStores[name];
    }
    renameObjectStore(btx, oldName, newName) {
        if (this.enableTracing) {
            console.log(`TRACING: renameObjectStore(?, ${oldName}, ${newName})`);
        }
        const myConn = this.requireConnectionFromTransaction(btx);
        const db = this.databases[myConn.dbName];
        if (!db) {
            throw Error("db not found");
        }
        if (db.txLevel < TransactionLevel.VersionChange) {
            throw Error("only allowed in versionchange transaction");
        }
        const schema = myConn.modifiedSchema;
        if (!schema) {
            throw Error();
        }
        if (!schema.objectStores[oldName]) {
            throw Error("object store not found");
        }
        if (schema.objectStores[newName]) {
            throw Error("new object store already exists");
        }
        const objectStoreMapEntry = myConn.objectStoreMap[oldName];
        if (!objectStoreMapEntry) {
            throw Error("object store not found in map");
        }
        objectStoreMapEntry.store.modifiedName = newName;
        schema.objectStores[newName] = schema.objectStores[oldName];
        delete schema.objectStores[oldName];
        delete myConn.objectStoreMap[oldName];
        myConn.objectStoreMap[newName] = objectStoreMapEntry;
    }
    createObjectStore(btx, name, keyPath, autoIncrement) {
        if (this.enableTracing) {
            console.log(`TRACING: createObjectStore(${btx.transactionCookie}, ${name})`);
        }
        const myConn = this.requireConnectionFromTransaction(btx);
        const db = this.databases[myConn.dbName];
        if (!db) {
            throw Error("db not found");
        }
        if (db.txLevel < TransactionLevel.VersionChange) {
            throw Error("only allowed in versionchange transaction");
        }
        const newObjectStore = {
            deleted: false,
            modifiedName: undefined,
            originalName: name,
            modifiedData: undefined,
            originalData: new BTree([], compareKeys),
            modifiedKeyGenerator: undefined,
            originalKeyGenerator: 1,
            committedIndexes: {},
            modifiedIndexes: {},
        };
        const schema = myConn.modifiedSchema;
        if (!schema) {
            throw Error("no schema for versionchange tx");
        }
        schema.objectStores[name] = {
            autoIncrement,
            keyPath,
            indexes: {},
        };
        myConn.objectStoreMap[name] = { store: newObjectStore, indexMap: {} };
    }
    createIndex(btx, indexName, objectStoreName, keyPath, multiEntry, unique) {
        if (this.enableTracing) {
            console.log(`TRACING: createIndex(${indexName})`);
        }
        const myConn = this.requireConnectionFromTransaction(btx);
        const db = this.databases[myConn.dbName];
        if (!db) {
            throw Error("db not found");
        }
        if (db.txLevel < TransactionLevel.VersionChange) {
            throw Error("only allowed in versionchange transaction");
        }
        const indexProperties = {
            keyPath,
            multiEntry,
            unique,
        };
        const newIndex = {
            deleted: false,
            modifiedData: undefined,
            modifiedName: undefined,
            originalData: new BTree([], compareKeys),
            originalName: indexName,
        };
        myConn.objectStoreMap[objectStoreName].indexMap[indexName] = newIndex;
        const schema = myConn.modifiedSchema;
        if (!schema) {
            throw Error("no schema in versionchange tx");
        }
        const objectStoreProperties = schema.objectStores[objectStoreName];
        if (!objectStoreProperties) {
            throw Error("object store not found");
        }
        objectStoreProperties.indexes[indexName] = indexProperties;
        const objectStoreMapEntry = myConn.objectStoreMap[objectStoreName];
        if (!objectStoreMapEntry) {
            throw Error("object store does not exist");
        }
        const storeData = objectStoreMapEntry.store.modifiedData ||
            objectStoreMapEntry.store.originalData;
        storeData.forEach((v, k) => {
            try {
                this.insertIntoIndex(newIndex, k, v.value, indexProperties);
            }
            catch (e) {
                if (e instanceof DataError) {
                    // We don't propagate this error here.
                    return;
                }
                throw e;
            }
        });
    }
    clearObjectStore(btx, objectStoreName) {
        return __awaiter(this, void 0, void 0, function* () {
            const myConn = this.requireConnectionFromTransaction(btx);
            const db = this.databases[myConn.dbName];
            if (!db) {
                throw Error("db not found");
            }
            if (db.txLevel < TransactionLevel.Write) {
                throw Error("only allowed in write transaction");
            }
            if (db.txRestrictObjectStores &&
                !db.txRestrictObjectStores.includes(objectStoreName)) {
                throw Error(`Not allowed to access store '${objectStoreName}', transaction is over ${JSON.stringify(db.txRestrictObjectStores)}`);
            }
            const schema = myConn.modifiedSchema;
            const objectStoreMapEntry = myConn.objectStoreMap[objectStoreName];
            objectStoreMapEntry.store.modifiedData = new BTree([], compareKeys);
            for (const indexName of Object.keys(schema.objectStores[objectStoreName].indexes)) {
                const index = myConn.objectStoreMap[objectStoreName].indexMap[indexName];
                if (!index) {
                    throw Error("index referenced by object store does not exist");
                }
                index.modifiedData = new BTree([], compareKeys);
            }
        });
    }
    deleteRecord(btx, objectStoreName, range) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.enableTracing) {
                console.log(`TRACING: deleteRecord from store ${objectStoreName}`);
            }
            const myConn = this.requireConnectionFromTransaction(btx);
            const db = this.databases[myConn.dbName];
            if (!db) {
                throw Error("db not found");
            }
            if (db.txLevel < TransactionLevel.Write) {
                throw Error("only allowed in write transaction");
            }
            if (db.txRestrictObjectStores &&
                !db.txRestrictObjectStores.includes(objectStoreName)) {
                throw Error(`Not allowed to access store '${objectStoreName}', transaction is over ${JSON.stringify(db.txRestrictObjectStores)}`);
            }
            if (typeof range !== "object") {
                throw Error("deleteRecord got invalid range (must be object)");
            }
            if (!("lowerOpen" in range)) {
                throw Error("deleteRecord got invalid range (sanity check failed, 'lowerOpen' missing)");
            }
            const schema = myConn.modifiedSchema;
            const objectStoreMapEntry = myConn.objectStoreMap[objectStoreName];
            if (!objectStoreMapEntry.store.modifiedData) {
                objectStoreMapEntry.store.modifiedData =
                    objectStoreMapEntry.store.originalData;
            }
            let modifiedData = objectStoreMapEntry.store.modifiedData;
            let currKey;
            if (range.lower === undefined || range.lower === null) {
                currKey = modifiedData.minKey();
            }
            else {
                currKey = range.lower;
                // We have a range with an lowerOpen lower bound, so don't start
                // deleting the lower bound.  Instead start with the next higher key.
                if (range.lowerOpen && currKey !== undefined) {
                    currKey = modifiedData.nextHigherKey(currKey);
                }
            }
            if (currKey === undefined) {
                throw Error("invariant violated");
            }
            // make sure that currKey is either undefined or pointing to an
            // existing object.
            let firstValue = modifiedData.get(currKey);
            if (!firstValue) {
                if (currKey !== undefined) {
                    currKey = modifiedData.nextHigherKey(currKey);
                }
            }
            // loop invariant: (currKey is undefined) or (currKey is a valid key)
            while (true) {
                if (currKey === undefined) {
                    // nothing more to delete!
                    break;
                }
                if (range.upper !== null && range.upper !== undefined) {
                    if (range.upperOpen && compareKeys(currKey, range.upper) === 0) {
                        // We have a range that's upperOpen, so stop before we delete the upper bound.
                        break;
                    }
                    if (!range.upperOpen && compareKeys(currKey, range.upper) > 0) {
                        // The upper range is inclusive, only stop if we're after the upper range.
                        break;
                    }
                }
                const storeEntry = modifiedData.get(currKey);
                if (!storeEntry) {
                    throw Error("assertion failed");
                }
                for (const indexName of Object.keys(schema.objectStores[objectStoreName].indexes)) {
                    const index = myConn.objectStoreMap[objectStoreName].indexMap[indexName];
                    if (!index) {
                        throw Error("index referenced by object store does not exist");
                    }
                    this.enableTracing &&
                        console.log(`deleting from index ${indexName} for object store ${objectStoreName}`);
                    const indexProperties = schema.objectStores[objectStoreName].indexes[indexName];
                    this.deleteFromIndex(index, storeEntry.primaryKey, storeEntry.value, indexProperties);
                }
                modifiedData = modifiedData.without(currKey);
                currKey = modifiedData.nextHigherKey(currKey);
            }
            objectStoreMapEntry.store.modifiedData = modifiedData;
        });
    }
    deleteFromIndex(index, primaryKey, value, indexProperties) {
        if (this.enableTracing) {
            console.log(`deleteFromIndex(${index.modifiedName || index.originalName})`);
        }
        if (value === undefined || value === null) {
            throw Error("cannot delete null/undefined value from index");
        }
        let indexData = index.modifiedData || index.originalData;
        const indexKeys = getIndexKeys(value, indexProperties.keyPath, indexProperties.multiEntry);
        for (const indexKey of indexKeys) {
            const existingIndexRecord = indexData.get(indexKey);
            if (!existingIndexRecord) {
                throw Error("db inconsistent: expected index entry missing");
            }
            const newPrimaryKeys = existingIndexRecord.primaryKeys.without(primaryKey);
            if (newPrimaryKeys.size === 0) {
                index.modifiedData = indexData.without(indexKey);
            }
            else {
                const newIndexRecord = {
                    indexKey,
                    primaryKeys: newPrimaryKeys,
                };
                index.modifiedData = indexData.with(indexKey, newIndexRecord, true);
            }
        }
    }
    getRecords(btx, req) {
        var _a, _b, _c, _d;
        return __awaiter(this, void 0, void 0, function* () {
            if (this.enableTracing) {
                console.log(`TRACING: getRecords`);
                console.log("query", req);
            }
            const myConn = this.requireConnectionFromTransaction(btx);
            const db = this.databases[myConn.dbName];
            if (!db) {
                throw Error("db not found");
            }
            if (db.txLevel < TransactionLevel.Read) {
                throw Error("only allowed while running a transaction");
            }
            if (db.txRestrictObjectStores &&
                !db.txRestrictObjectStores.includes(req.objectStoreName)) {
                throw Error(`Not allowed to access store '${req.objectStoreName}', transaction is over ${JSON.stringify(db.txRestrictObjectStores)}`);
            }
            const objectStoreMapEntry = myConn.objectStoreMap[req.objectStoreName];
            if (!objectStoreMapEntry) {
                throw Error("object store not found");
            }
            let range;
            if (req.range == null || req.range === undefined) {
                range = new BridgeIDBKeyRange(undefined, undefined, true, true);
            }
            else {
                range = req.range;
            }
            if (typeof range !== "object") {
                throw Error("getRecords was given an invalid range (sanity check failed, not an object)");
            }
            if (!("lowerOpen" in range)) {
                throw Error("getRecords was given an invalid range (sanity check failed, lowerOpen missing)");
            }
            const forward = req.direction === "next" || req.direction === "nextunique";
            const unique = req.direction === "prevunique" || req.direction === "nextunique";
            const storeData = objectStoreMapEntry.store.modifiedData ||
                objectStoreMapEntry.store.originalData;
            const haveIndex = req.indexName !== undefined;
            let resp;
            if (haveIndex) {
                const index = myConn.objectStoreMap[req.objectStoreName].indexMap[req.indexName];
                const indexData = index.modifiedData || index.originalData;
                resp = getIndexRecords({
                    forward,
                    indexData,
                    storeData,
                    limit: req.limit,
                    unique,
                    range,
                    resultLevel: req.resultLevel,
                    advanceIndexKey: req.advanceIndexKey,
                    advancePrimaryKey: req.advancePrimaryKey,
                    lastIndexPosition: req.lastIndexPosition,
                    lastObjectStorePosition: req.lastObjectStorePosition,
                });
                if (this.trackStats) {
                    const k = `${req.objectStoreName}.${req.indexName}`;
                    this.accessStats.readsPerIndex[k] =
                        ((_a = this.accessStats.readsPerIndex[k]) !== null && _a !== void 0 ? _a : 0) + 1;
                    this.accessStats.readItemsPerIndex[k] =
                        ((_b = this.accessStats.readItemsPerIndex[k]) !== null && _b !== void 0 ? _b : 0) + resp.count;
                }
            }
            else {
                if (req.advanceIndexKey !== undefined) {
                    throw Error("unsupported request");
                }
                resp = getObjectStoreRecords({
                    forward,
                    storeData,
                    limit: req.limit,
                    range,
                    resultLevel: req.resultLevel,
                    advancePrimaryKey: req.advancePrimaryKey,
                    lastIndexPosition: req.lastIndexPosition,
                    lastObjectStorePosition: req.lastObjectStorePosition,
                });
                if (this.trackStats) {
                    const k = `${req.objectStoreName}`;
                    this.accessStats.readsPerStore[k] =
                        ((_c = this.accessStats.readsPerStore[k]) !== null && _c !== void 0 ? _c : 0) + 1;
                    this.accessStats.readItemsPerStore[k] =
                        ((_d = this.accessStats.readItemsPerStore[k]) !== null && _d !== void 0 ? _d : 0) + resp.count;
                }
            }
            if (this.enableTracing) {
                console.log(`TRACING: getRecords got ${resp.count} results`);
            }
            return resp;
        });
    }
    storeRecord(btx, storeReq) {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            if (this.enableTracing) {
                console.log(`TRACING: storeRecord`);
                console.log(`key ${storeReq.key}, record ${JSON.stringify(structuredEncapsulate(storeReq.value))}`);
            }
            const myConn = this.requireConnectionFromTransaction(btx);
            const db = this.databases[myConn.dbName];
            if (!db) {
                throw Error("db not found");
            }
            if (db.txLevel < TransactionLevel.Write) {
                throw Error("store operation only allowed while running a transaction");
            }
            if (db.txRestrictObjectStores &&
                !db.txRestrictObjectStores.includes(storeReq.objectStoreName)) {
                throw Error(`Not allowed to access store '${storeReq.objectStoreName}', transaction is over ${JSON.stringify(db.txRestrictObjectStores)}`);
            }
            if (this.trackStats) {
                this.accessStats.writesPerStore[storeReq.objectStoreName] =
                    ((_a = this.accessStats.writesPerStore[storeReq.objectStoreName]) !== null && _a !== void 0 ? _a : 0) + 1;
            }
            const schema = myConn.modifiedSchema;
            const objectStoreMapEntry = myConn.objectStoreMap[storeReq.objectStoreName];
            if (!objectStoreMapEntry.store.modifiedData) {
                objectStoreMapEntry.store.modifiedData =
                    objectStoreMapEntry.store.originalData;
            }
            const modifiedData = objectStoreMapEntry.store.modifiedData;
            let key;
            let value;
            if (storeReq.storeLevel === StoreLevel.UpdateExisting) {
                if (storeReq.key === null || storeReq.key === undefined) {
                    throw Error("invalid update request (key not given)");
                }
                if (!objectStoreMapEntry.store.modifiedData.has(storeReq.key)) {
                    throw Error("invalid update request (record does not exist)");
                }
                key = storeReq.key;
                value = storeReq.value;
            }
            else {
                const keygen = objectStoreMapEntry.store.modifiedKeyGenerator ||
                    objectStoreMapEntry.store.originalKeyGenerator;
                const autoIncrement = schema.objectStores[storeReq.objectStoreName].autoIncrement;
                const keyPath = schema.objectStores[storeReq.objectStoreName].keyPath;
                if (keyPath !== null &&
                    keyPath !== undefined &&
                    storeReq.key !== undefined) {
                    // If in-line keys are used, a key can't be explicitly specified.
                    throw new DataError();
                }
                let storeKeyResult;
                try {
                    storeKeyResult = makeStoreKeyValue(storeReq.value, storeReq.key, keygen, autoIncrement, keyPath);
                }
                catch (e) {
                    if (e instanceof DataError) {
                        const kp = JSON.stringify(keyPath);
                        const n = storeReq.objectStoreName;
                        const m = `Could not extract key from value, objectStore=${n}, keyPath=${kp}, value=${JSON.stringify(storeReq.value)}`;
                        if (this.enableTracing) {
                            console.error(e);
                            console.error("value was:", storeReq.value);
                            console.error("key was:", storeReq.key);
                        }
                        throw new DataError(m);
                    }
                    else {
                        throw e;
                    }
                }
                key = storeKeyResult.key;
                value = storeKeyResult.value;
                objectStoreMapEntry.store.modifiedKeyGenerator =
                    storeKeyResult.updatedKeyGenerator;
                const hasKey = modifiedData.has(key);
                if (hasKey && storeReq.storeLevel !== StoreLevel.AllowOverwrite) {
                    throw new ConstraintError("refusing to overwrite");
                }
            }
            const oldStoreRecord = modifiedData.get(key);
            const newObjectStoreRecord = {
                // FIXME: We should serialize the key here, not just clone it.
                primaryKey: structuredClone(key),
                value: structuredClone(value),
            };
            objectStoreMapEntry.store.modifiedData = modifiedData.with(key, newObjectStoreRecord, true);
            for (const indexName of Object.keys(schema.objectStores[storeReq.objectStoreName].indexes)) {
                const index = myConn.objectStoreMap[storeReq.objectStoreName].indexMap[indexName];
                if (!index) {
                    throw Error("index referenced by object store does not exist");
                }
                const indexProperties = schema.objectStores[storeReq.objectStoreName].indexes[indexName];
                // Remove old index entry first!
                if (oldStoreRecord) {
                    this.deleteFromIndex(index, key, oldStoreRecord.value, indexProperties);
                }
                try {
                    this.insertIntoIndex(index, key, value, indexProperties);
                }
                catch (e) {
                    if (e instanceof DataError) ;
                    else {
                        throw e;
                    }
                }
            }
            return { key };
        });
    }
    insertIntoIndex(index, primaryKey, value, indexProperties) {
        if (this.enableTracing) {
            console.log(`insertIntoIndex(${index.modifiedName || index.originalName})`);
        }
        let indexData = index.modifiedData || index.originalData;
        let indexKeys;
        try {
            indexKeys = getIndexKeys(value, indexProperties.keyPath, indexProperties.multiEntry);
        }
        catch (e) {
            if (e instanceof DataError) {
                const n = index.modifiedName || index.originalName;
                const p = JSON.stringify(indexProperties.keyPath);
                const m = `Failed to extract index keys from index ${n} for keyPath ${p}.`;
                if (this.enableTracing) {
                    console.error(m);
                    console.error("value was", value);
                }
                throw new DataError(m);
            }
            else {
                throw e;
            }
        }
        for (const indexKey of indexKeys) {
            const existingRecord = indexData.get(indexKey);
            if (existingRecord) {
                if (indexProperties.unique) {
                    throw new ConstraintError();
                }
                else {
                    const newIndexRecord = {
                        indexKey: indexKey,
                        primaryKeys: existingRecord.primaryKeys.with(primaryKey),
                    };
                    index.modifiedData = indexData.with(indexKey, newIndexRecord, true);
                }
            }
            else {
                const primaryKeys = new BTree([[primaryKey, undefined]], compareKeys);
                const newIndexRecord = {
                    indexKey: indexKey,
                    primaryKeys,
                };
                index.modifiedData = indexData.with(indexKey, newIndexRecord, true);
            }
        }
    }
    rollback(btx) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.enableTracing) {
                console.log(`TRACING: rollback`);
            }
            const myConn = this.connectionsByTransaction[btx.transactionCookie];
            if (!myConn) {
                throw Error("unknown transaction");
            }
            const db = this.databases[myConn.dbName];
            if (!db) {
                throw Error("db not found");
            }
            if (db.txLevel < TransactionLevel.Read) {
                throw Error("rollback is only allowed while running a transaction");
            }
            db.txLevel = TransactionLevel.None;
            db.txRestrictObjectStores = undefined;
            myConn.modifiedSchema = structuredClone(db.committedSchema);
            myConn.objectStoreMap = this.makeObjectStoreMap(db);
            for (const objectStoreName in db.committedObjectStores) {
                const objectStore = db.committedObjectStores[objectStoreName];
                objectStore.deleted = false;
                objectStore.modifiedData = undefined;
                objectStore.modifiedName = undefined;
                objectStore.modifiedKeyGenerator = undefined;
                objectStore.modifiedIndexes = {};
                for (const indexName of Object.keys(db.committedSchema.objectStores[objectStoreName].indexes)) {
                    const index = objectStore.committedIndexes[indexName];
                    index.deleted = false;
                    index.modifiedData = undefined;
                    index.modifiedName = undefined;
                }
            }
            delete this.connectionsByTransaction[btx.transactionCookie];
            this.transactionDoneCond.trigger();
        });
    }
    commit(btx) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.enableTracing) {
                console.log(`TRACING: commit`);
            }
            const myConn = this.requireConnectionFromTransaction(btx);
            const db = this.databases[myConn.dbName];
            if (!db) {
                throw Error("db not found");
            }
            const txLevel = db.txLevel;
            if (txLevel < TransactionLevel.Read) {
                throw Error("only allowed while running a transaction");
            }
            db.committedSchema = structuredClone(myConn.modifiedSchema);
            db.txLevel = TransactionLevel.None;
            db.txRestrictObjectStores = undefined;
            db.committedObjectStores = {};
            db.committedObjectStores = {};
            for (const objectStoreName in myConn.objectStoreMap) {
                const objectStoreMapEntry = myConn.objectStoreMap[objectStoreName];
                const store = objectStoreMapEntry.store;
                store.deleted = false;
                store.originalData = store.modifiedData || store.originalData;
                store.originalName = store.modifiedName || store.originalName;
                store.modifiedIndexes = {};
                if (store.modifiedKeyGenerator !== undefined) {
                    store.originalKeyGenerator = store.modifiedKeyGenerator;
                }
                db.committedObjectStores[objectStoreName] = store;
                for (const indexName in objectStoreMapEntry.indexMap) {
                    const index = objectStoreMapEntry.indexMap[indexName];
                    index.deleted = false;
                    index.originalData = index.modifiedData || index.originalData;
                    index.originalName = index.modifiedName || index.originalName;
                    store.committedIndexes[indexName] = index;
                }
            }
            myConn.objectStoreMap = this.makeObjectStoreMap(db);
            delete this.connectionsByTransaction[btx.transactionCookie];
            this.transactionDoneCond.trigger();
            if (this.afterCommitCallback && txLevel >= TransactionLevel.Write) {
                yield this.afterCommitCallback();
            }
        });
    }
}
function getIndexRecords(req) {
    let numResults = 0;
    const indexKeys = [];
    const primaryKeys = [];
    const values = [];
    const { unique, range, forward, indexData } = req;
    function nextIndexEntry(prevPos) {
        const res = forward
            ? indexData.nextHigherPair(prevPos)
            : indexData.nextLowerPair(prevPos);
        return res ? res[1] : undefined;
    }
    function packResult() {
        // Collect the values based on the primary keys,
        // if requested.
        if (req.resultLevel === ResultLevel.Full) {
            for (let i = 0; i < numResults; i++) {
                const result = req.storeData.get(primaryKeys[i]);
                if (!result) {
                    console.error("invariant violated during read");
                    console.error("request was", req);
                    throw Error("invariant violated during read");
                }
                values.push(structuredClone(result.value));
            }
        }
        return {
            count: numResults,
            indexKeys: req.resultLevel >= ResultLevel.OnlyKeys ? indexKeys : undefined,
            primaryKeys: req.resultLevel >= ResultLevel.OnlyKeys ? primaryKeys : undefined,
            values: req.resultLevel >= ResultLevel.Full ? values : undefined,
        };
    }
    let firstIndexPos = req.lastIndexPosition;
    {
        const rangeStart = forward ? range.lower : range.upper;
        const dataStart = forward ? indexData.minKey() : indexData.maxKey();
        firstIndexPos = furthestKey(forward, firstIndexPos, rangeStart);
        firstIndexPos = furthestKey(forward, firstIndexPos, dataStart);
    }
    if (firstIndexPos == null) {
        return packResult();
    }
    let objectStorePos = undefined;
    let indexEntry = undefined;
    // Now we align at indexPos and after objectStorePos
    indexEntry = indexData.get(firstIndexPos);
    if (!indexEntry) {
        // We're not aligned to an index key, go to next index entry
        indexEntry = nextIndexEntry(firstIndexPos);
        if (!indexEntry) {
            return packResult();
        }
        objectStorePos = nextKey(true, indexEntry.primaryKeys, undefined);
    }
    else if (req.lastIndexPosition != null &&
        compareKeys(req.lastIndexPosition, indexEntry.indexKey) !== 0) {
        // We're already past the desired lastIndexPosition, don't use
        // lastObjectStorePosition.
        objectStorePos = nextKey(true, indexEntry.primaryKeys, undefined);
    }
    else {
        objectStorePos = nextKey(true, indexEntry.primaryKeys, req.lastObjectStorePosition);
    }
    // Now skip lower/upper bound of open ranges
    if (forward &&
        range.lowerOpen &&
        range.lower != null &&
        compareKeys(range.lower, indexEntry.indexKey) === 0) {
        indexEntry = nextIndexEntry(indexEntry.indexKey);
        if (!indexEntry) {
            return packResult();
        }
        objectStorePos = indexEntry.primaryKeys.minKey();
    }
    if (!forward &&
        range.upperOpen &&
        range.upper != null &&
        compareKeys(range.upper, indexEntry.indexKey) === 0) {
        indexEntry = nextIndexEntry(indexEntry.indexKey);
        if (!indexEntry) {
            return packResult();
        }
        objectStorePos = indexEntry.primaryKeys.minKey();
    }
    // If requested, return only unique results
    if (unique &&
        req.lastIndexPosition != null &&
        compareKeys(indexEntry.indexKey, req.lastIndexPosition) === 0) {
        indexEntry = nextIndexEntry(indexEntry.indexKey);
        if (!indexEntry) {
            return packResult();
        }
        objectStorePos = indexEntry.primaryKeys.minKey();
    }
    if (req.advanceIndexKey != null) {
        const ik = furthestKey(forward, indexEntry.indexKey, req.advanceIndexKey);
        indexEntry = indexData.get(ik);
        if (!indexEntry) {
            indexEntry = nextIndexEntry(ik);
        }
        if (!indexEntry) {
            return packResult();
        }
    }
    // Use advancePrimaryKey if necessary
    if (req.advanceIndexKey != null &&
        req.advancePrimaryKey &&
        compareKeys(indexEntry.indexKey, req.advanceIndexKey) == 0) {
        if (objectStorePos == null ||
            compareKeys(req.advancePrimaryKey, objectStorePos) > 0) {
            objectStorePos = nextKey(true, indexEntry.primaryKeys, req.advancePrimaryKey);
        }
    }
    while (1) {
        if (req.limit != 0 && numResults == req.limit) {
            break;
        }
        if (!range.includes(indexEntry.indexKey)) {
            break;
        }
        if (indexEntry === undefined) {
            break;
        }
        if (objectStorePos == null) {
            // We don't have any more records with the current index key.
            indexEntry = nextIndexEntry(indexEntry.indexKey);
            if (!indexEntry) {
                return packResult();
            }
            objectStorePos = indexEntry.primaryKeys.minKey();
            continue;
        }
        indexKeys.push(structuredClone(indexEntry.indexKey));
        primaryKeys.push(structuredClone(objectStorePos));
        numResults++;
        if (unique) {
            objectStorePos = undefined;
        }
        else {
            objectStorePos = indexEntry.primaryKeys.nextHigherKey(objectStorePos);
        }
    }
    return packResult();
}
function getObjectStoreRecords(req) {
    let numResults = 0;
    const indexKeys = [];
    const primaryKeys = [];
    const values = [];
    const { storeData, range, forward } = req;
    function packResult() {
        return {
            count: numResults,
            indexKeys: req.resultLevel >= ResultLevel.OnlyKeys ? indexKeys : undefined,
            primaryKeys: req.resultLevel >= ResultLevel.OnlyKeys ? primaryKeys : undefined,
            values: req.resultLevel >= ResultLevel.Full ? values : undefined,
        };
    }
    const rangeStart = forward ? range.lower : range.upper;
    const dataStart = forward ? storeData.minKey() : storeData.maxKey();
    let storePos = req.lastObjectStorePosition;
    storePos = furthestKey(forward, storePos, rangeStart);
    storePos = furthestKey(forward, storePos, dataStart);
    storePos = furthestKey(forward, storePos, req.advancePrimaryKey);
    if (storePos != null) {
        // Advance store position if we are either still at the last returned
        // store key, or if we are currently not on a key.
        const storeEntry = storeData.get(storePos);
        if (!storeEntry ||
            (req.lastObjectStorePosition != null &&
                compareKeys(req.lastObjectStorePosition, storePos) === 0)) {
            storePos = forward
                ? storeData.nextHigherKey(storePos)
                : storeData.nextLowerKey(storePos);
        }
    }
    else {
        storePos = forward ? storeData.minKey() : storeData.maxKey();
    }
    if (storePos != null &&
        forward &&
        range.lowerOpen &&
        range.lower != null &&
        compareKeys(range.lower, storePos) === 0) {
        storePos = storeData.nextHigherKey(storePos);
    }
    if (storePos != null &&
        !forward &&
        range.upperOpen &&
        range.upper != null &&
        compareKeys(range.upper, storePos) === 0) {
        storePos = storeData.nextLowerKey(storePos);
    }
    while (1) {
        if (req.limit != 0 && numResults == req.limit) {
            break;
        }
        if (storePos === null || storePos === undefined) {
            break;
        }
        if (!range.includes(storePos)) {
            break;
        }
        const res = storeData.get(storePos);
        if (res === undefined) {
            break;
        }
        if (req.resultLevel >= ResultLevel.OnlyKeys) {
            primaryKeys.push(structuredClone(storePos));
        }
        if (req.resultLevel >= ResultLevel.Full) {
            values.push(structuredClone(res.value));
        }
        numResults++;
        storePos = nextStoreKey(forward, storeData, storePos);
    }
    return packResult();
}

var _a;
// globalThis polyfill, see https://mathiasbynens.be/notes/globalthis
(function () {
    if (typeof globalThis === "object")
        return;
    Object.defineProperty(Object.prototype, "__magic__", {
        get: function () {
            return this;
        },
        configurable: true, // This makes it possible to `delete` the getter later.
    });
    // @ts-ignore: polyfill magic
    __magic__.globalThis = __magic__; // lolwat
    // @ts-ignore: polyfill magic
    delete Object.prototype.__magic__;
})();
/**
 * Global indexeddb objects, either from the native or bridge-idb
 * implementation, depending on what is available in
 * the global environment.
 */
const GlobalIDB = {
    KeyRange: (_a = globalThis.IDBKeyRange) !== null && _a !== void 0 ? _a : BridgeIDBKeyRange,
};
/**
 * Populate the global name space such that the given IndexedDB factory is made
 * available globally.
 *
 * @public
 */
function shimIndexedDB(factory) {
    // @ts-ignore: shimming
    const g = globalThis;
    g.indexedDB = factory;
    g.IDBCursor = BridgeIDBCursor;
    g.IDBKeyRange = BridgeIDBKeyRange;
    g.IDBDatabase = BridgeIDBDatabase;
    g.IDBFactory = BridgeIDBFactory;
    g.IDBIndex = BridgeIDBIndex;
    g.IDBKeyRange = BridgeIDBKeyRange;
    g.IDBObjectStore = BridgeIDBObjectStore;
    g.IDBOpenDBRequest = BridgeIDBOpenDBRequest;
    g.IDBRequest = BridgeIDBRequest;
    g.IDBTransaction = BridgeIDBTransaction;
    g.IDBVersionChangeEvent = BridgeIDBVersionChangeEvent;
}

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const EXCHANGE_COINS_LOCK = "exchange-coins-lock";

/*
 This file is part of GNU Taler
 (C) 2021 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
new Logger("coinSelection.ts");
/**
 * Account for the fees of spending a coin.
 */
function tallyFees(tally, wireFeesPerExchange, wireFeeAmortization, exchangeBaseUrl, feeDeposit) {
    var _a;
    const currency = tally.amountPayRemaining.currency;
    let amountWireFeeLimitRemaining = tally.amountWireFeeLimitRemaining;
    let amountDepositFeeLimitRemaining = tally.amountDepositFeeLimitRemaining;
    let customerDepositFees = tally.customerDepositFees;
    let customerWireFees = tally.customerWireFees;
    let amountPayRemaining = tally.amountPayRemaining;
    const wireFeeCoveredForExchange = new Set(tally.wireFeeCoveredForExchange);
    if (!tally.wireFeeCoveredForExchange.has(exchangeBaseUrl)) {
        const wf = (_a = wireFeesPerExchange[exchangeBaseUrl]) !== null && _a !== void 0 ? _a : Amounts.getZero(currency);
        const wfForgiven = Amounts.min(amountWireFeeLimitRemaining, wf);
        amountWireFeeLimitRemaining = Amounts.sub(amountWireFeeLimitRemaining, wfForgiven).amount;
        // The remaining, amortized amount needs to be paid by the
        // wallet or covered by the deposit fee allowance.
        let wfRemaining = Amounts.divide(Amounts.sub(wf, wfForgiven).amount, wireFeeAmortization);
        // This is the amount forgiven via the deposit fee allowance.
        const wfDepositForgiven = Amounts.min(amountDepositFeeLimitRemaining, wfRemaining);
        amountDepositFeeLimitRemaining = Amounts.sub(amountDepositFeeLimitRemaining, wfDepositForgiven).amount;
        wfRemaining = Amounts.sub(wfRemaining, wfDepositForgiven).amount;
        customerWireFees = Amounts.add(customerWireFees, wfRemaining).amount;
        amountPayRemaining = Amounts.add(amountPayRemaining, wfRemaining).amount;
        wireFeeCoveredForExchange.add(exchangeBaseUrl);
    }
    const dfForgiven = Amounts.min(feeDeposit, amountDepositFeeLimitRemaining);
    amountDepositFeeLimitRemaining = Amounts.sub(amountDepositFeeLimitRemaining, dfForgiven).amount;
    // How much does the user spend on deposit fees for this coin?
    const dfRemaining = Amounts.sub(feeDeposit, dfForgiven).amount;
    customerDepositFees = Amounts.add(customerDepositFees, dfRemaining).amount;
    amountPayRemaining = Amounts.add(amountPayRemaining, dfRemaining).amount;
    return {
        amountDepositFeeLimitRemaining,
        amountPayRemaining,
        amountWireFeeLimitRemaining,
        customerDepositFees,
        customerWireFees,
        wireFeeCoveredForExchange,
    };
}

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$l = new Logger("exchanges.ts");
function denominationRecordFromKeys(exchangeBaseUrl, exchangeMasterPub, listIssueDate, denomIn) {
    let denomPub;
    denomPub = denomIn.denom_pub;
    const denomPubHash = encodeCrock(hashDenomPub(denomPub));
    const value = Amounts.parseOrThrow(denomIn.value);
    const d = {
        denomPub,
        denomPubHash,
        exchangeBaseUrl,
        exchangeMasterPub,
        fees: {
            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,
        isRevoked: false,
        masterSig: denomIn.master_sig,
        stampExpireDeposit: denomIn.stamp_expire_deposit,
        stampExpireLegal: denomIn.stamp_expire_legal,
        stampExpireWithdraw: denomIn.stamp_expire_withdraw,
        stampStart: denomIn.stamp_start,
        verificationStatus: DenominationVerificationStatus.Unverified,
        amountFrac: value.fraction,
        amountVal: value.value,
        currency: value.currency,
        listIssueDate,
    };
    return d;
}
function getExchangeRequestTimeout() {
    return Duration.fromSpec({
        seconds: 5,
    });
}
async function downloadExchangeWithTermsOfService(exchangeBaseUrl, http, timeout, contentType) {
    const reqUrl = new URL$1("terms", exchangeBaseUrl);
    const headers = {
        Accept: contentType,
    };
    const resp = await http.get(reqUrl.href, {
        headers,
        timeout,
    });
    const tosText = await readSuccessResponseTextOrThrow(resp);
    const tosEtag = resp.headers.get("etag") || "unknown";
    const tosContentType = resp.headers.get("content-type") || "text/plain";
    return { tosText, tosEtag, tosContentType };
}
/**
 * Get exchange details from the database.
 */
async function getExchangeDetails(tx, exchangeBaseUrl) {
    const r = await tx.exchanges.get(exchangeBaseUrl);
    if (!r) {
        return;
    }
    const dp = r.detailsPointer;
    if (!dp) {
        return;
    }
    const { currency, masterPublicKey } = dp;
    return await tx.exchangeDetails.get([r.baseUrl, currency, masterPublicKey]);
}
getExchangeDetails.makeContext = (db) => db.mktx((x) => [x.exchanges, x.exchangeDetails]);
async function updateExchangeTermsOfService(ws, exchangeBaseUrl, tos) {
    await ws.db
        .mktx((x) => [x.exchanges, x.exchangeDetails])
        .runReadWrite(async (tx) => {
        const d = await getExchangeDetails(tx, exchangeBaseUrl);
        if (d) {
            d.termsOfServiceText = tos.tosText;
            d.termsOfServiceContentType = tos.tosContentType;
            d.termsOfServiceLastEtag = tos.tosEtag;
            await tx.exchangeDetails.put(d);
        }
    });
}
async function acceptExchangeTermsOfService(ws, exchangeBaseUrl, etag) {
    await ws.db
        .mktx((x) => [x.exchanges, x.exchangeDetails])
        .runReadWrite(async (tx) => {
        const d = await getExchangeDetails(tx, exchangeBaseUrl);
        if (d) {
            d.termsOfServiceAcceptedEtag = etag;
            await tx.exchangeDetails.put(d);
        }
    });
}
async function validateWireInfo(ws, versionCurrent, wireInfo, masterPublicKey) {
    for (const a of wireInfo.accounts) {
        logger$l.trace("validating exchange acct");
        let isValid = false;
        if (ws.insecureTrustExchange) {
            isValid = true;
        }
        else {
            const { valid: v } = await ws.cryptoApi.isValidWireAccount({
                masterPub: masterPublicKey,
                paytoUri: a.payto_uri,
                sig: a.master_sig,
                versionCurrent,
            });
            isValid = v;
        }
        if (!isValid) {
            throw Error("exchange acct signature invalid");
        }
    }
    const feesForType = {};
    for (const wireMethod of Object.keys(wireInfo.fees)) {
        const feeList = [];
        for (const x of wireInfo.fees[wireMethod]) {
            const startStamp = x.start_date;
            const endStamp = x.end_date;
            const fee = {
                closingFee: Amounts.parseOrThrow(x.closing_fee),
                endStamp,
                sig: x.sig,
                startStamp,
                wireFee: Amounts.parseOrThrow(x.wire_fee),
                wadFee: Amounts.parseOrThrow(x.wad_fee),
            };
            let isValid = false;
            if (ws.insecureTrustExchange) {
                isValid = true;
            }
            else {
                const { valid: v } = await ws.cryptoApi.isValidWireFee({
                    masterPub: masterPublicKey,
                    type: wireMethod,
                    wf: fee,
                });
                isValid = v;
            }
            if (!isValid) {
                throw Error("exchange wire fee signature invalid");
            }
            feeList.push(fee);
        }
        feesForType[wireMethod] = feeList;
    }
    return {
        accounts: wireInfo.accounts,
        feesForType,
    };
}
/**
 * Fetch wire information for an exchange.
 *
 * @param exchangeBaseUrl Exchange base URL, assumed to be already normalized.
 */
async function downloadExchangeWireInfo(exchangeBaseUrl, http, timeout) {
    const reqUrl = new URL$1("wire", exchangeBaseUrl);
    const resp = await http.get(reqUrl.href, {
        timeout,
    });
    const wireInfo = await readSuccessResponseJsonOrThrow(resp, codecForExchangeWireJson());
    return wireInfo;
}
async function provideExchangeRecord(ws, baseUrl, now) {
    return await ws.db
        .mktx((x) => [x.exchanges, x.exchangeDetails])
        .runReadWrite(async (tx) => {
        let exchange = await tx.exchanges.get(baseUrl);
        if (!exchange) {
            const r = {
                permanent: true,
                baseUrl: baseUrl,
                detailsPointer: undefined,
                lastUpdate: undefined,
                nextUpdate: AbsoluteTime.toTimestamp(now),
                nextRefreshCheck: AbsoluteTime.toTimestamp(now),
            };
            await tx.exchanges.put(r);
            exchange = r;
        }
        const exchangeDetails = await getExchangeDetails(tx, baseUrl);
        return { exchange, exchangeDetails };
    });
}
/**
 * Download and validate an exchange's /keys data.
 */
async function downloadExchangeKeysInfo(baseUrl, http, timeout) {
    var _a;
    const keysUrl = new URL$1("keys", baseUrl);
    const resp = await http.get(keysUrl.href, {
        timeout,
    });
    const exchangeKeysJsonUnchecked = await readSuccessResponseJsonOrThrow(resp, codecForExchangeKeysJson());
    if (exchangeKeysJsonUnchecked.denoms.length === 0) {
        throw TalerError.fromDetail(TalerErrorCode.WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT, {
            exchangeBaseUrl: baseUrl,
        }, "exchange doesn't offer any denominations");
    }
    const protocolVersion = exchangeKeysJsonUnchecked.version;
    const versionRes = LibtoolVersion.compare(WALLET_EXCHANGE_PROTOCOL_VERSION, protocolVersion);
    if ((versionRes === null || versionRes === void 0 ? void 0 : versionRes.compatible) != true) {
        throw TalerError.fromDetail(TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE, {
            exchangeProtocolVersion: protocolVersion,
            walletProtocolVersion: WALLET_EXCHANGE_PROTOCOL_VERSION,
        }, "exchange protocol version not compatible with wallet");
    }
    const currency = Amounts.parseOrThrow(exchangeKeysJsonUnchecked.denoms[0].value).currency.toUpperCase();
    return {
        masterPublicKey: exchangeKeysJsonUnchecked.master_public_key,
        currency,
        auditors: exchangeKeysJsonUnchecked.auditors,
        currentDenominations: exchangeKeysJsonUnchecked.denoms.map((d) => denominationRecordFromKeys(baseUrl, exchangeKeysJsonUnchecked.master_public_key, exchangeKeysJsonUnchecked.list_issue_date, d)),
        protocolVersion: exchangeKeysJsonUnchecked.version,
        signingKeys: exchangeKeysJsonUnchecked.signkeys,
        reserveClosingDelay: exchangeKeysJsonUnchecked.reserve_closing_delay,
        expiry: AbsoluteTime.toTimestamp(getExpiry(resp, {
            minDuration: durationFromSpec({ hours: 1 }),
        })),
        recoup: (_a = exchangeKeysJsonUnchecked.recoup) !== null && _a !== void 0 ? _a : [],
        listIssueDate: exchangeKeysJsonUnchecked.list_issue_date,
    };
}
async function downloadTosFromAcceptedFormat(ws, baseUrl, timeout, acceptedFormat) {
    let tosFound;
    //Remove this when exchange supports multiple content-type in accept header
    if (acceptedFormat)
        for (const format of acceptedFormat) {
            const resp = await downloadExchangeWithTermsOfService(baseUrl, ws.http, timeout, format);
            if (resp.tosContentType === format) {
                tosFound = resp;
                break;
            }
        }
    if (tosFound !== undefined)
        return tosFound;
    // If none of the specified format was found try text/plain
    return await downloadExchangeWithTermsOfService(baseUrl, ws.http, timeout, "text/plain");
}
async function updateExchangeFromUrl(ws, baseUrl, options = {}) {
    return runOperationHandlerForResult(await updateExchangeFromUrlHandler(ws, baseUrl, options));
}
/**
 * Update or add exchange DB entry by fetching the /keys and /wire information.
 * Optionally link the reserve entry to the new or existing
 * exchange entry in then DB.
 */
async function updateExchangeFromUrlHandler(ws, baseUrl, options = {}) {
    var _a;
    const forceNow = (_a = options.forceNow) !== null && _a !== void 0 ? _a : false;
    logger$l.info(`updating exchange info for ${baseUrl}, forced: ${forceNow}`);
    const now = AbsoluteTime.now();
    baseUrl = canonicalizeBaseUrl(baseUrl);
    const { exchange, exchangeDetails } = await provideExchangeRecord(ws, baseUrl, now);
    if (!forceNow &&
        exchangeDetails !== undefined &&
        !AbsoluteTime.isExpired(AbsoluteTime.fromTimestamp(exchange.nextUpdate))) {
        logger$l.info("using existing exchange info");
        return {
            type: OperationAttemptResultType.Finished,
            result: { exchange, exchangeDetails },
        };
    }
    logger$l.info("updating exchange /keys info");
    const timeout = getExchangeRequestTimeout();
    const keysInfo = await downloadExchangeKeysInfo(baseUrl, ws.http, timeout);
    logger$l.info("updating exchange /wire info");
    const wireInfoDownload = await downloadExchangeWireInfo(baseUrl, ws.http, timeout);
    logger$l.info("validating exchange /wire info");
    const version = LibtoolVersion.parseVersion(keysInfo.protocolVersion);
    if (!version) {
        // Should have been validated earlier.
        throw Error("unexpected invalid version");
    }
    const wireInfo = await validateWireInfo(ws, version.current, wireInfoDownload, keysInfo.masterPublicKey);
    logger$l.info("finished validating exchange /wire info");
    const tosDownload = await downloadTosFromAcceptedFormat(ws, baseUrl, timeout, ["text/plain"]);
    const tosHasBeenAccepted = (exchangeDetails === null || exchangeDetails === void 0 ? void 0 : exchangeDetails.termsOfServiceAcceptedEtag) === tosDownload.tosEtag;
    let recoupGroupId;
    logger$l.trace("updating exchange info in database");
    const updated = await ws.db
        .mktx((x) => [
        x.exchanges,
        x.exchangeDetails,
        x.denominations,
        x.coins,
        x.refreshGroups,
        x.recoupGroups,
    ])
        .runReadWrite(async (tx) => {
        const r = await tx.exchanges.get(baseUrl);
        if (!r) {
            logger$l.warn(`exchange ${baseUrl} no longer present`);
            return;
        }
        let details = await getExchangeDetails(tx, r.baseUrl);
        // FIXME: validate signing keys and merge with old set
        details = {
            auditors: keysInfo.auditors,
            currency: keysInfo.currency,
            masterPublicKey: keysInfo.masterPublicKey,
            protocolVersion: keysInfo.protocolVersion,
            signingKeys: keysInfo.signingKeys,
            reserveClosingDelay: keysInfo.reserveClosingDelay,
            exchangeBaseUrl: r.baseUrl,
            wireInfo,
            termsOfServiceText: tosDownload.tosText,
            termsOfServiceAcceptedEtag: tosHasBeenAccepted
                ? tosDownload.tosEtag
                : undefined,
            termsOfServiceContentType: tosDownload.tosContentType,
            termsOfServiceLastEtag: tosDownload.tosEtag,
            termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp.now(),
        };
        // FIXME: only update if pointer got updated
        r.lastUpdate = TalerProtocolTimestamp.now();
        r.nextUpdate = keysInfo.expiry;
        // New denominations might be available.
        r.nextRefreshCheck = TalerProtocolTimestamp.now();
        r.detailsPointer = {
            currency: details.currency,
            masterPublicKey: details.masterPublicKey,
            // FIXME: only change if pointer really changed
            updateClock: TalerProtocolTimestamp.now(),
            protocolVersionRange: keysInfo.protocolVersion,
        };
        await tx.exchanges.put(r);
        await tx.exchangeDetails.put(details);
        logger$l.info("updating denominations in database");
        const currentDenomSet = new Set(keysInfo.currentDenominations.map((x) => x.denomPubHash));
        for (const currentDenom of keysInfo.currentDenominations) {
            const oldDenom = await tx.denominations.get([
                baseUrl,
                currentDenom.denomPubHash,
            ]);
            if (oldDenom) ;
            else {
                await tx.denominations.put(currentDenom);
            }
        }
        // Update list issue date for all denominations,
        // and mark non-offered denominations as such.
        await tx.denominations.indexes.byExchangeBaseUrl
            .iter(r.baseUrl)
            .forEachAsync(async (x) => {
            if (!currentDenomSet.has(x.denomPubHash)) {
                // FIXME: Here, an auditor report should be created, unless
                // the denomination is really legally expired.
                if (x.isOffered) {
                    x.isOffered = false;
                    logger$l.info(`setting denomination ${x.denomPubHash} to offered=false`);
                }
            }
            else {
                x.listIssueDate = keysInfo.listIssueDate;
                if (!x.isOffered) {
                    x.isOffered = true;
                    logger$l.info(`setting denomination ${x.denomPubHash} to offered=true`);
                }
            }
            await tx.denominations.put(x);
        });
        logger$l.trace("done updating denominations in database");
        // Handle recoup
        const recoupDenomList = keysInfo.recoup;
        const newlyRevokedCoinPubs = [];
        logger$l.trace("recoup list from exchange", recoupDenomList);
        for (const recoupInfo of recoupDenomList) {
            const oldDenom = await tx.denominations.get([
                r.baseUrl,
                recoupInfo.h_denom_pub,
            ]);
            if (!oldDenom) {
                // We never even knew about the revoked denomination, all good.
                continue;
            }
            if (oldDenom.isRevoked) {
                // We already marked the denomination as revoked,
                // this implies we revoked all coins
                logger$l.trace("denom already revoked");
                continue;
            }
            logger$l.info("revoking denom", recoupInfo.h_denom_pub);
            oldDenom.isRevoked = true;
            await tx.denominations.put(oldDenom);
            const affectedCoins = await tx.coins.indexes.byDenomPubHash
                .iter(recoupInfo.h_denom_pub)
                .toArray();
            for (const ac of affectedCoins) {
                newlyRevokedCoinPubs.push(ac.coinPub);
            }
        }
        if (newlyRevokedCoinPubs.length != 0) {
            logger$l.info("recouping coins", newlyRevokedCoinPubs);
            recoupGroupId = await ws.recoupOps.createRecoupGroup(ws, tx, exchange.baseUrl, newlyRevokedCoinPubs);
        }
        return {
            exchange: r,
            exchangeDetails: details,
        };
    });
    if (recoupGroupId) {
        // Asynchronously start recoup.  This doesn't need to finish
        // for the exchange update to be considered finished.
        ws.recoupOps.processRecoupGroup(ws, recoupGroupId).catch((e) => {
            logger$l.error("error while recouping coins:", e);
        });
    }
    if (!updated) {
        throw Error("something went wrong with updating the exchange");
    }
    logger$l.trace("done updating exchange info in database");
    ws.notify({
        type: NotificationType.ExchangeAdded,
    });
    return {
        type: OperationAttemptResultType.Finished,
        result: {
            exchange: updated.exchange,
            exchangeDetails: updated.exchangeDetails,
        },
    };
}
async function getExchangePaytoUri(ws, exchangeBaseUrl, supportedTargetTypes) {
    var _a;
    // We do the update here, since the exchange might not even exist
    // yet in our database.
    const details = await getExchangeDetails
        .makeContext(ws.db)
        .runReadOnly(async (tx) => {
        return getExchangeDetails(tx, exchangeBaseUrl);
    });
    const accounts = (_a = details === null || details === void 0 ? void 0 : details.wireInfo.accounts) !== null && _a !== void 0 ? _a : [];
    for (const account of accounts) {
        const res = parsePaytoUri(account.payto_uri);
        if (!res) {
            continue;
        }
        if (supportedTargetTypes.includes(res.targetType)) {
            return account.payto_uri;
        }
    }
    throw Error("no matching exchange account found");
}
/**
 * Check if and how an exchange is trusted and/or audited.
 */
async function getExchangeTrust(ws, exchangeInfo) {
    let isTrusted = false;
    let isAudited = false;
    return await ws.db
        .mktx((x) => [
        x.exchanges,
        x.exchangeDetails,
        x.exchangeTrust,
        x.auditorTrust,
    ])
        .runReadOnly(async (tx) => {
        const exchangeDetails = await getExchangeDetails(tx, exchangeInfo.baseUrl);
        if (!exchangeDetails) {
            throw Error(`exchange ${exchangeInfo.baseUrl} details not available`);
        }
        const exchangeTrustRecord = await tx.exchangeTrust.indexes.byExchangeMasterPub.get(exchangeDetails.masterPublicKey);
        if (exchangeTrustRecord &&
            exchangeTrustRecord.uids.length > 0 &&
            exchangeTrustRecord.currency === exchangeDetails.currency) {
            isTrusted = true;
        }
        for (const auditor of exchangeDetails.auditors) {
            const auditorTrustRecord = await tx.auditorTrust.indexes.byAuditorPub.get(auditor.auditor_pub);
            if (auditorTrustRecord && auditorTrustRecord.uids.length > 0) {
                isAudited = true;
                break;
            }
        }
        return { isTrusted, isAudited };
    });
}

/*
 This file is part of GNU Taler
 (C) 2021 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * Logger.
 */
const logger$k = new Logger("deposits.ts");
/**
 * @see {processDepositGroup}
 */
async function processDepositGroup(ws, depositGroupId, options = {}) {
    var _a, _b;
    const depositGroup = await ws.db
        .mktx((x) => [x.depositGroups])
        .runReadOnly(async (tx) => {
        return tx.depositGroups.get(depositGroupId);
    });
    if (!depositGroup) {
        logger$k.warn(`deposit group ${depositGroupId} not found`);
        return OperationAttemptResult.finishedEmpty();
    }
    if (depositGroup.timestampFinished) {
        logger$k.trace(`deposit group ${depositGroupId} already finished`);
        return OperationAttemptResult.finishedEmpty();
    }
    const contractData = extractContractData(depositGroup.contractTermsRaw, depositGroup.contractTermsHash, "");
    // Check for cancellation before expensive operations.
    (_a = options.cancellationToken) === null || _a === void 0 ? void 0 : _a.throwIfCancelled();
    const depositPermissions = await generateDepositPermissions(ws, depositGroup.payCoinSelection, contractData);
    for (let i = 0; i < depositPermissions.length; i++) {
        if (depositGroup.depositedPerCoin[i]) {
            continue;
        }
        const perm = depositPermissions[i];
        let requestBody;
        requestBody = {
            contribution: Amounts.stringify(perm.contribution),
            merchant_payto_uri: depositGroup.wire.payto_uri,
            wire_salt: depositGroup.wire.salt,
            h_contract_terms: depositGroup.contractTermsHash,
            ub_sig: perm.ub_sig,
            timestamp: depositGroup.contractTermsRaw.timestamp,
            wire_transfer_deadline: depositGroup.contractTermsRaw.wire_transfer_deadline,
            refund_deadline: depositGroup.contractTermsRaw.refund_deadline,
            coin_sig: perm.coin_sig,
            denom_pub_hash: perm.h_denom,
            merchant_pub: depositGroup.merchantPub,
        };
        // Check for cancellation before making network request.
        (_b = options.cancellationToken) === null || _b === void 0 ? void 0 : _b.throwIfCancelled();
        const url = new URL$1(`coins/${perm.coin_pub}/deposit`, perm.exchange_url);
        logger$k.info(`depositing to ${url}`);
        const httpResp = await ws.http.postJson(url.href, requestBody, {
            cancellationToken: options.cancellationToken,
        });
        await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
        await ws.db
            .mktx((x) => [x.depositGroups])
            .runReadWrite(async (tx) => {
            const dg = await tx.depositGroups.get(depositGroupId);
            if (!dg) {
                return;
            }
            dg.depositedPerCoin[i] = true;
            await tx.depositGroups.put(dg);
        });
    }
    await ws.db
        .mktx((x) => [x.depositGroups])
        .runReadWrite(async (tx) => {
        const dg = await tx.depositGroups.get(depositGroupId);
        if (!dg) {
            return;
        }
        let allDeposited = true;
        for (const d of depositGroup.depositedPerCoin) {
            if (!d) {
                allDeposited = false;
            }
        }
        if (allDeposited) {
            dg.timestampFinished = TalerProtocolTimestamp.now();
            dg.operationStatus = OperationStatus.Finished;
            await tx.depositGroups.put(dg);
        }
    });
    return OperationAttemptResult.finishedEmpty();
}
async function trackDepositGroup(ws, req) {
    const responses = [];
    const depositGroup = await ws.db
        .mktx((x) => [x.depositGroups])
        .runReadOnly(async (tx) => {
        return tx.depositGroups.get(req.depositGroupId);
    });
    if (!depositGroup) {
        throw Error("deposit group not found");
    }
    const contractData = extractContractData(depositGroup.contractTermsRaw, depositGroup.contractTermsHash, "");
    const depositPermissions = await generateDepositPermissions(ws, depositGroup.payCoinSelection, contractData);
    const wireHash = depositGroup.contractTermsRaw.h_wire;
    for (const dp of depositPermissions) {
        const url = new URL$1(`deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${dp.coin_pub}`, dp.exchange_url);
        const sigResp = await ws.cryptoApi.signTrackTransaction({
            coinPub: dp.coin_pub,
            contractTermsHash: depositGroup.contractTermsHash,
            merchantPriv: depositGroup.merchantPriv,
            merchantPub: depositGroup.merchantPub,
            wireHash,
        });
        url.searchParams.set("merchant_sig", sigResp.sig);
        const httpResp = await ws.http.get(url.href);
        const body = await httpResp.json();
        responses.push({
            body,
            status: httpResp.status,
        });
    }
    return {
        responses,
    };
}
async function getFeeForDeposit(ws, req) {
    const p = parsePaytoUri(req.depositPaytoUri);
    if (!p) {
        throw Error("invalid payto URI");
    }
    const amount = Amounts.parseOrThrow(req.amount);
    const exchangeInfos = [];
    await ws.db
        .mktx((x) => [x.exchanges, x.exchangeDetails])
        .runReadOnly(async (tx) => {
        const allExchanges = await tx.exchanges.iter().toArray();
        for (const e of allExchanges) {
            const details = await getExchangeDetails(tx, e.baseUrl);
            if (!details || amount.currency !== details.currency) {
                continue;
            }
            exchangeInfos.push({
                master_pub: details.masterPublicKey,
                url: e.baseUrl,
            });
        }
    });
    const payCoinSel = await selectPayCoinsNew(ws, {
        auditors: [],
        exchanges: Object.values(exchangeInfos).map((v) => ({
            exchangeBaseUrl: v.url,
            exchangePub: v.master_pub,
        })),
        wireMethod: p.targetType,
        contractTermsAmount: Amounts.parseOrThrow(req.amount),
        depositFeeLimit: Amounts.parseOrThrow(req.amount),
        wireFeeAmortization: 1,
        wireFeeLimit: Amounts.parseOrThrow(req.amount),
        prevPayCoins: [],
    });
    if (!payCoinSel) {
        throw Error("insufficient funds");
    }
    return await getTotalFeesForDepositAmount(ws, p.targetType, amount, payCoinSel);
}
async function prepareDepositGroup(ws, req) {
    var _a;
    const p = parsePaytoUri(req.depositPaytoUri);
    if (!p) {
        throw Error("invalid payto URI");
    }
    const amount = Amounts.parseOrThrow(req.amount);
    const exchangeInfos = [];
    await ws.db
        .mktx((x) => [x.exchanges, x.exchangeDetails])
        .runReadOnly(async (tx) => {
        const allExchanges = await tx.exchanges.iter().toArray();
        for (const e of allExchanges) {
            const details = await getExchangeDetails(tx, e.baseUrl);
            if (!details || amount.currency !== details.currency) {
                continue;
            }
            exchangeInfos.push({
                master_pub: details.masterPublicKey,
                url: e.baseUrl,
            });
        }
    });
    const now = AbsoluteTime.now();
    const nowRounded = AbsoluteTime.toTimestamp(now);
    const contractTerms = {
        auditors: [],
        exchanges: exchangeInfos,
        amount: req.amount,
        max_fee: Amounts.stringify(amount),
        max_wire_fee: Amounts.stringify(amount),
        wire_method: p.targetType,
        timestamp: nowRounded,
        merchant_base_url: "",
        summary: "",
        nonce: "",
        wire_transfer_deadline: nowRounded,
        order_id: "",
        h_wire: "",
        pay_deadline: AbsoluteTime.toTimestamp(AbsoluteTime.addDuration(now, durationFromSpec({ hours: 1 }))),
        merchant: {
            name: "(wallet)",
        },
        merchant_pub: "",
        refund_deadline: TalerProtocolTimestamp.zero(),
    };
    const { h: contractTermsHash } = await ws.cryptoApi.hashString({
        str: canonicalJson(contractTerms),
    });
    const contractData = extractContractData(contractTerms, contractTermsHash, "");
    const payCoinSel = await selectPayCoinsNew(ws, {
        auditors: contractData.allowedAuditors,
        exchanges: contractData.allowedExchanges,
        wireMethod: contractData.wireMethod,
        contractTermsAmount: contractData.amount,
        depositFeeLimit: contractData.maxDepositFee,
        wireFeeAmortization: (_a = contractData.wireFeeAmortization) !== null && _a !== void 0 ? _a : 1,
        wireFeeLimit: contractData.maxWireFee,
        prevPayCoins: [],
    });
    if (!payCoinSel) {
        throw Error("insufficient funds");
    }
    const totalDepositCost = await getTotalPaymentCost(ws, payCoinSel);
    const effectiveDepositAmount = await getEffectiveDepositAmount(ws, p.targetType, payCoinSel);
    return { totalDepositCost, effectiveDepositAmount };
}
async function createDepositGroup(ws, req) {
    var _a;
    const p = parsePaytoUri(req.depositPaytoUri);
    if (!p) {
        throw Error("invalid payto URI");
    }
    const amount = Amounts.parseOrThrow(req.amount);
    const exchangeInfos = [];
    await ws.db
        .mktx((x) => [x.exchanges, x.exchangeDetails])
        .runReadOnly(async (tx) => {
        const allExchanges = await tx.exchanges.iter().toArray();
        for (const e of allExchanges) {
            const details = await getExchangeDetails(tx, e.baseUrl);
            if (!details || amount.currency !== details.currency) {
                continue;
            }
            exchangeInfos.push({
                master_pub: details.masterPublicKey,
                url: e.baseUrl,
            });
        }
    });
    const now = AbsoluteTime.now();
    const nowRounded = AbsoluteTime.toTimestamp(now);
    const noncePair = await ws.cryptoApi.createEddsaKeypair({});
    const merchantPair = await ws.cryptoApi.createEddsaKeypair({});
    const wireSalt = encodeCrock(getRandomBytes(16));
    const wireHash = hashWire(req.depositPaytoUri, wireSalt);
    const contractTerms = {
        auditors: [],
        exchanges: exchangeInfos,
        amount: req.amount,
        max_fee: Amounts.stringify(amount),
        max_wire_fee: Amounts.stringify(amount),
        wire_method: p.targetType,
        timestamp: nowRounded,
        merchant_base_url: "",
        summary: "",
        nonce: noncePair.pub,
        wire_transfer_deadline: nowRounded,
        order_id: "",
        h_wire: wireHash,
        pay_deadline: AbsoluteTime.toTimestamp(AbsoluteTime.addDuration(now, durationFromSpec({ hours: 1 }))),
        merchant: {
            name: "(wallet)",
        },
        merchant_pub: merchantPair.pub,
        refund_deadline: TalerProtocolTimestamp.zero(),
    };
    const { h: contractTermsHash } = await ws.cryptoApi.hashString({
        str: canonicalJson(contractTerms),
    });
    const contractData = extractContractData(contractTerms, contractTermsHash, "");
    const payCoinSel = await selectPayCoinsNew(ws, {
        auditors: contractData.allowedAuditors,
        exchanges: contractData.allowedExchanges,
        wireMethod: contractData.wireMethod,
        contractTermsAmount: contractData.amount,
        depositFeeLimit: contractData.maxDepositFee,
        wireFeeAmortization: (_a = contractData.wireFeeAmortization) !== null && _a !== void 0 ? _a : 1,
        wireFeeLimit: contractData.maxWireFee,
        prevPayCoins: [],
    });
    if (!payCoinSel) {
        throw Error("insufficient funds");
    }
    const totalDepositCost = await getTotalPaymentCost(ws, payCoinSel);
    const depositGroupId = encodeCrock(getRandomBytes(32));
    const effectiveDepositAmount = await getEffectiveDepositAmount(ws, p.targetType, payCoinSel);
    const depositGroup = {
        contractTermsHash,
        contractTermsRaw: contractTerms,
        depositGroupId,
        noncePriv: noncePair.priv,
        noncePub: noncePair.pub,
        timestampCreated: AbsoluteTime.toTimestamp(now),
        timestampFinished: undefined,
        payCoinSelection: payCoinSel,
        payCoinSelectionUid: encodeCrock(getRandomBytes(32)),
        depositedPerCoin: payCoinSel.coinPubs.map(() => false),
        merchantPriv: merchantPair.priv,
        merchantPub: merchantPair.pub,
        totalPayCost: totalDepositCost,
        effectiveDepositAmount,
        wire: {
            payto_uri: req.depositPaytoUri,
            salt: wireSalt,
        },
        operationStatus: OperationStatus.Pending,
    };
    await ws.db
        .mktx((x) => [
        x.depositGroups,
        x.coins,
        x.recoupGroups,
        x.denominations,
        x.refreshGroups,
        x.coinAvailability,
    ])
        .runReadWrite(async (tx) => {
        await spendCoins(ws, tx, {
            allocationId: `deposit-group:${depositGroup.depositGroupId}`,
            coinPubs: payCoinSel.coinPubs,
            contributions: payCoinSel.coinContributions,
            refreshReason: RefreshReason.PayDeposit,
        });
        await tx.depositGroups.put(depositGroup);
    });
    return {
        depositGroupId: depositGroupId,
        transactionId: makeEventId(TransactionType.Deposit, depositGroupId),
    };
}
/**
 * Get the amount that will be deposited on the merchant's bank
 * account, not considering aggregation.
 */
async function getEffectiveDepositAmount(ws, wireType, pcs) {
    const amt = [];
    const fees = [];
    const exchangeSet = new Set();
    await ws.db
        .mktx((x) => [x.coins, x.denominations, x.exchanges, x.exchangeDetails])
        .runReadOnly(async (tx) => {
        var _a;
        for (let i = 0; i < pcs.coinPubs.length; i++) {
            const coin = await tx.coins.get(pcs.coinPubs[i]);
            if (!coin) {
                throw Error("can't calculate deposit amount, coin not found");
            }
            const denom = await ws.getDenomInfo(ws, tx, coin.exchangeBaseUrl, coin.denomPubHash);
            if (!denom) {
                throw Error("can't find denomination to calculate deposit amount");
            }
            amt.push(pcs.coinContributions[i]);
            fees.push(denom.feeDeposit);
            exchangeSet.add(coin.exchangeBaseUrl);
        }
        for (const exchangeUrl of exchangeSet.values()) {
            const exchangeDetails = await getExchangeDetails(tx, exchangeUrl);
            if (!exchangeDetails) {
                continue;
            }
            // FIXME/NOTE: the line below _likely_ throws exception
            // about "find method not found on undefined" when the wireType
            // is not supported by the Exchange.
            const fee = (_a = exchangeDetails.wireInfo.feesForType[wireType].find((x) => {
                return AbsoluteTime.isBetween(AbsoluteTime.now(), AbsoluteTime.fromTimestamp(x.startStamp), AbsoluteTime.fromTimestamp(x.endStamp));
            })) === null || _a === void 0 ? void 0 : _a.wireFee;
            if (fee) {
                fees.push(fee);
            }
        }
    });
    return Amounts.sub(Amounts.sum(amt).amount, Amounts.sum(fees).amount).amount;
}
/**
 * Get the fee amount that will be charged when trying to deposit the
 * specified amount using the selected coins and the wire method.
 */
async function getTotalFeesForDepositAmount(ws, wireType, total, pcs) {
    const wireFee = [];
    const coinFee = [];
    const refreshFee = [];
    const exchangeSet = new Set();
    await ws.db
        .mktx((x) => [x.coins, x.denominations, x.exchanges, x.exchangeDetails])
        .runReadOnly(async (tx) => {
        var _a, _b;
        for (let i = 0; i < pcs.coinPubs.length; i++) {
            const coin = await tx.coins.get(pcs.coinPubs[i]);
            if (!coin) {
                throw Error("can't calculate deposit amount, coin not found");
            }
            const denom = await ws.getDenomInfo(ws, tx, coin.exchangeBaseUrl, coin.denomPubHash);
            if (!denom) {
                throw Error("can't find denomination to calculate deposit amount");
            }
            coinFee.push(denom.feeDeposit);
            exchangeSet.add(coin.exchangeBaseUrl);
            const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
                .iter(coin.exchangeBaseUrl)
                .filter((x) => Amounts.isSameCurrency(DenominationRecord.getValue(x), pcs.coinContributions[i]));
            const amountLeft = Amounts.sub(denom.value, pcs.coinContributions[i]).amount;
            const refreshCost = getTotalRefreshCost(allDenoms, denom, amountLeft);
            refreshFee.push(refreshCost);
        }
        for (const exchangeUrl of exchangeSet.values()) {
            const exchangeDetails = await getExchangeDetails(tx, exchangeUrl);
            if (!exchangeDetails) {
                continue;
            }
            const fee = (_b = (_a = exchangeDetails.wireInfo.feesForType[wireType]) === null || _a === void 0 ? void 0 : _a.find((x) => {
                return AbsoluteTime.isBetween(AbsoluteTime.now(), AbsoluteTime.fromTimestamp(x.startStamp), AbsoluteTime.fromTimestamp(x.endStamp));
            })) === null || _b === void 0 ? void 0 : _b.wireFee;
            if (fee) {
                wireFee.push(fee);
            }
        }
    });
    return {
        coin: Amounts.sumOrZero(total.currency, coinFee).amount,
        wire: Amounts.sumOrZero(total.currency, wireFee).amount,
        refresh: Amounts.sumOrZero(total.currency, refreshFee).amount,
    };
}

/*
 This file is part of GNU Taler
 (C) 2019 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$j = new Logger("operations/tip.ts");
async function prepareTip(ws, talerTipUri) {
    const res = parseTipUri(talerTipUri);
    if (!res) {
        throw Error("invalid taler://tip URI");
    }
    let tipRecord = await ws.db
        .mktx((x) => [x.tips])
        .runReadOnly(async (tx) => {
        return tx.tips.indexes.byMerchantTipIdAndBaseUrl.get([
            res.merchantTipId,
            res.merchantBaseUrl,
        ]);
    });
    if (!tipRecord) {
        const tipStatusUrl = new URL$1(`tips/${res.merchantTipId}`, res.merchantBaseUrl);
        logger$j.trace("checking tip status from", tipStatusUrl.href);
        const merchantResp = await ws.http.get(tipStatusUrl.href);
        const tipPickupStatus = await readSuccessResponseJsonOrThrow(merchantResp, codecForTipPickupGetResponse());
        logger$j.trace(`status ${j2s(tipPickupStatus)}`);
        const amount = Amounts.parseOrThrow(tipPickupStatus.tip_amount);
        logger$j.trace("new tip, creating tip record");
        await updateExchangeFromUrl(ws, tipPickupStatus.exchange_url);
        //FIXME: is this needed? withdrawDetails is not used
        // * if the intention is to update the exchange information in the database
        //   maybe we can use another name. `get` seems like a pure-function
        await getExchangeWithdrawalInfo(ws, tipPickupStatus.exchange_url, amount);
        const walletTipId = encodeCrock(getRandomBytes(32));
        await updateWithdrawalDenoms(ws, tipPickupStatus.exchange_url);
        const denoms = await getCandidateWithdrawalDenoms(ws, tipPickupStatus.exchange_url);
        const selectedDenoms = selectWithdrawalDenominations(amount, denoms);
        const secretSeed = encodeCrock(getRandomBytes(64));
        const denomSelUid = encodeCrock(getRandomBytes(32));
        const newTipRecord = {
            walletTipId: walletTipId,
            acceptedTimestamp: undefined,
            tipAmountRaw: amount,
            tipExpiration: tipPickupStatus.expiration,
            exchangeBaseUrl: tipPickupStatus.exchange_url,
            merchantBaseUrl: res.merchantBaseUrl,
            createdTimestamp: TalerProtocolTimestamp.now(),
            merchantTipId: res.merchantTipId,
            tipAmountEffective: selectedDenoms.totalCoinValue,
            denomsSel: selectedDenoms,
            pickedUpTimestamp: undefined,
            secretSeed,
            denomSelUid,
        };
        await ws.db
            .mktx((x) => [x.tips])
            .runReadWrite(async (tx) => {
            await tx.tips.put(newTipRecord);
        });
        tipRecord = newTipRecord;
    }
    const tipStatus = {
        accepted: !!tipRecord && !!tipRecord.acceptedTimestamp,
        tipAmountRaw: Amounts.stringify(tipRecord.tipAmountRaw),
        exchangeBaseUrl: tipRecord.exchangeBaseUrl,
        merchantBaseUrl: tipRecord.merchantBaseUrl,
        expirationTimestamp: tipRecord.tipExpiration,
        tipAmountEffective: Amounts.stringify(tipRecord.tipAmountEffective),
        walletTipId: tipRecord.walletTipId,
    };
    return tipStatus;
}
async function processTip(ws, walletTipId, options = {}) {
    const tipRecord = await ws.db
        .mktx((x) => [x.tips])
        .runReadOnly(async (tx) => {
        return tx.tips.get(walletTipId);
    });
    if (!tipRecord) {
        return {
            type: OperationAttemptResultType.Finished,
            result: undefined,
        };
    }
    if (tipRecord.pickedUpTimestamp) {
        logger$j.warn("tip already picked up");
        return {
            type: OperationAttemptResultType.Finished,
            result: undefined,
        };
    }
    const denomsForWithdraw = tipRecord.denomsSel;
    const planchets = [];
    // Planchets in the form that the merchant expects
    const planchetsDetail = [];
    const denomForPlanchet = [];
    for (const dh of denomsForWithdraw.selectedDenoms) {
        const denom = await ws.db
            .mktx((x) => [x.denominations])
            .runReadOnly(async (tx) => {
            return tx.denominations.get([
                tipRecord.exchangeBaseUrl,
                dh.denomPubHash,
            ]);
        });
        checkDbInvariant(!!denom, "denomination should be in database");
        for (let i = 0; i < dh.count; i++) {
            const deriveReq = {
                denomPub: denom.denomPub,
                planchetIndex: planchets.length,
                secretSeed: tipRecord.secretSeed,
            };
            logger$j.trace(`deriving tip planchet: ${j2s(deriveReq)}`);
            const p = await ws.cryptoApi.createTipPlanchet(deriveReq);
            logger$j.trace(`derive result: ${j2s(p)}`);
            denomForPlanchet[planchets.length] = denom;
            planchets.push(p);
            planchetsDetail.push({
                coin_ev: p.coinEv,
                denom_pub_hash: denom.denomPubHash,
            });
        }
    }
    const tipStatusUrl = new URL$1(`tips/${tipRecord.merchantTipId}/pickup`, tipRecord.merchantBaseUrl);
    const req = { planchets: planchetsDetail };
    logger$j.trace(`sending tip request: ${j2s(req)}`);
    const merchantResp = await ws.http.postJson(tipStatusUrl.href, req);
    logger$j.trace(`got tip response, status ${merchantResp.status}`);
    // FIXME: Why do we do this?
    if ((merchantResp.status >= 500 && merchantResp.status <= 599) ||
        merchantResp.status === 424) {
        logger$j.trace(`got transient tip error`);
        // FIXME: wrap in another error code that indicates a transient error
        return {
            type: OperationAttemptResultType.Error,
            errorDetail: makeErrorDetail(TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, getHttpResponseErrorDetails(merchantResp), "tip pickup failed (transient)"),
        };
    }
    let blindedSigs = [];
    const response = await readSuccessResponseJsonOrThrow(merchantResp, codecForMerchantTipResponseV2());
    blindedSigs = response.blind_sigs.map((x) => x.blind_sig);
    if (blindedSigs.length !== planchets.length) {
        throw Error("number of tip responses does not match requested planchets");
    }
    const newCoinRecords = [];
    for (let i = 0; i < blindedSigs.length; i++) {
        const blindedSig = blindedSigs[i];
        const denom = denomForPlanchet[i];
        checkLogicInvariant(!!denom);
        const planchet = planchets[i];
        checkLogicInvariant(!!planchet);
        if (denom.denomPub.cipher !== DenomKeyType.Rsa) {
            throw Error("unsupported cipher");
        }
        if (blindedSig.cipher !== DenomKeyType.Rsa) {
            throw Error("unsupported cipher");
        }
        const denomSigRsa = await ws.cryptoApi.rsaUnblind({
            bk: planchet.blindingKey,
            blindedSig: blindedSig.blinded_rsa_signature,
            pk: denom.denomPub.rsa_public_key,
        });
        const isValid = await ws.cryptoApi.rsaVerify({
            hm: planchet.coinPub,
            pk: denom.denomPub.rsa_public_key,
            sig: denomSigRsa.sig,
        });
        if (!isValid) {
            return {
                type: OperationAttemptResultType.Error,
                errorDetail: makeErrorDetail(TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID, {}, "invalid signature from the exchange (via merchant tip) after unblinding"),
            };
        }
        newCoinRecords.push({
            blindingKey: planchet.blindingKey,
            coinPriv: planchet.coinPriv,
            coinPub: planchet.coinPub,
            coinSource: {
                type: CoinSourceType.Tip,
                coinIndex: i,
                walletTipId: walletTipId,
            },
            currentAmount: DenominationRecord.getValue(denom),
            denomPubHash: denom.denomPubHash,
            denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa.sig },
            exchangeBaseUrl: tipRecord.exchangeBaseUrl,
            status: CoinStatus.Fresh,
            coinEvHash: planchet.coinEvHash,
            maxAge: AgeRestriction.AGE_UNRESTRICTED,
            ageCommitmentProof: planchet.ageCommitmentProof,
            allocation: undefined,
        });
    }
    await ws.db
        .mktx((x) => [x.coins, x.coinAvailability, x.denominations, x.tips])
        .runReadWrite(async (tx) => {
        const tr = await tx.tips.get(walletTipId);
        if (!tr) {
            return;
        }
        if (tr.pickedUpTimestamp) {
            return;
        }
        tr.pickedUpTimestamp = TalerProtocolTimestamp.now();
        await tx.tips.put(tr);
        for (const cr of newCoinRecords) {
            await makeCoinAvailable(ws, tx, cr);
        }
    });
    return {
        type: OperationAttemptResultType.Finished,
        result: undefined,
    };
}
async function acceptTip(ws, tipId) {
    const found = await ws.db
        .mktx((x) => [x.tips])
        .runReadWrite(async (tx) => {
        const tipRecord = await tx.tips.get(tipId);
        if (!tipRecord) {
            logger$j.error("tip not found");
            return false;
        }
        tipRecord.acceptedTimestamp = TalerProtocolTimestamp.now();
        await tx.tips.put(tipRecord);
        return true;
    });
    if (found) {
        await processTip(ws, tipId);
    }
    return {
        transactionId: makeEventId(TransactionType.Tip, tipId),
    };
}

/*
 This file is part of GNU Taler
 (C) 2019 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$i = new Logger("taler-wallet-core:transactions.ts");
var TombstoneTag;
(function (TombstoneTag) {
    TombstoneTag["DeleteWithdrawalGroup"] = "delete-withdrawal-group";
    TombstoneTag["DeleteReserve"] = "delete-reserve";
    TombstoneTag["DeletePayment"] = "delete-payment";
    TombstoneTag["DeleteTip"] = "delete-tip";
    TombstoneTag["DeleteRefreshGroup"] = "delete-refresh-group";
    TombstoneTag["DeleteDepositGroup"] = "delete-deposit-group";
    TombstoneTag["DeleteRefund"] = "delete-refund";
    TombstoneTag["DeletePeerPullDebit"] = "delete-peer-pull-debit";
    TombstoneTag["DeletePeerPushDebit"] = "delete-peer-push-debit";
})(TombstoneTag || (TombstoneTag = {}));
/**
 * Create an event ID from the type and the primary key for the event.
 */
function makeEventId(type, ...args) {
    return type + ":" + args.map((x) => encodeURIComponent(x)).join(":");
}
function shouldSkipCurrency(transactionsRequest, currency) {
    if (!(transactionsRequest === null || transactionsRequest === void 0 ? void 0 : transactionsRequest.currency)) {
        return false;
    }
    return transactionsRequest.currency.toLowerCase() !== currency.toLowerCase();
}
function shouldSkipSearch(transactionsRequest, fields) {
    if (!(transactionsRequest === null || transactionsRequest === void 0 ? void 0 : transactionsRequest.search)) {
        return false;
    }
    const needle = transactionsRequest.search.trim();
    for (const f of fields) {
        if (f.indexOf(needle) >= 0) {
            return false;
        }
    }
    return true;
}
/**
 * Fallback order of transactions that have the same timestamp.
 */
const txOrder = {
    [TransactionType.Withdrawal]: 1,
    [TransactionType.Tip]: 2,
    [TransactionType.Payment]: 3,
    [TransactionType.PeerPullCredit]: 4,
    [TransactionType.PeerPullDebit]: 5,
    [TransactionType.PeerPushCredit]: 6,
    [TransactionType.PeerPushDebit]: 7,
    [TransactionType.Refund]: 8,
    [TransactionType.Deposit]: 9,
    [TransactionType.Refresh]: 10,
    [TransactionType.Tip]: 11,
};
async function getTransactionById(ws, req) {
    const [typeStr, ...rest] = req.transactionId.split(":");
    const type = typeStr;
    if (type === TransactionType.Withdrawal ||
        type === TransactionType.PeerPullCredit ||
        type === TransactionType.PeerPushCredit) {
        const withdrawalGroupId = rest[0];
        return await ws.db
            .mktx((x) => [
            x.withdrawalGroups,
            x.exchangeDetails,
            x.exchanges,
            x.operationRetries,
        ])
            .runReadWrite(async (tx) => {
            const withdrawalGroupRecord = await tx.withdrawalGroups.get(withdrawalGroupId);
            if (!withdrawalGroupRecord)
                throw Error("not found");
            const opId = RetryTags.forWithdrawal(withdrawalGroupRecord);
            const ort = await tx.operationRetries.get(opId);
            if (withdrawalGroupRecord.wgInfo.withdrawalType ===
                "bank-integrated" /* WithdrawalRecordType.BankIntegrated */) {
                return buildTransactionForBankIntegratedWithdraw(withdrawalGroupRecord, ort);
            }
            if (withdrawalGroupRecord.wgInfo.withdrawalType ===
                "peer-pull-credit" /* WithdrawalRecordType.PeerPullCredit */) {
                return buildTransactionForPullPaymentCredit(withdrawalGroupRecord, ort);
            }
            if (withdrawalGroupRecord.wgInfo.withdrawalType ===
                "peer-push-credit" /* WithdrawalRecordType.PeerPushCredit */) {
                return buildTransactionForPushPaymentCredit(withdrawalGroupRecord, ort);
            }
            const exchangeDetails = await getExchangeDetails(tx, withdrawalGroupRecord.exchangeBaseUrl);
            if (!exchangeDetails)
                throw Error("not exchange details");
            return buildTransactionForManualWithdraw(withdrawalGroupRecord, exchangeDetails, ort);
        });
    }
    else if (type === TransactionType.Payment) {
        const proposalId = rest[0];
        return await ws.db
            .mktx((x) => [x.purchases, x.tombstones, x.operationRetries])
            .runReadWrite(async (tx) => {
            const purchase = await tx.purchases.get(proposalId);
            if (!purchase)
                throw Error("not found");
            const filteredRefunds = await Promise.all(Object.values(purchase.refunds).map(async (r) => {
                const t = await tx.tombstones.get(makeEventId(TombstoneTag.DeleteRefund, purchase.proposalId, `${r.executionTime.t_s}`));
                if (!t)
                    return r;
                return undefined;
            }));
            const cleanRefunds = filteredRefunds.filter((x) => !!x);
            const contractData = purchase.download.contractData;
            const refunds = mergeRefundByExecutionTime(cleanRefunds, Amounts.getZero(contractData.amount.currency));
            const payOpId = RetryTags.forPay(purchase);
            const refundQueryOpId = RetryTags.forRefundQuery(purchase);
            const payRetryRecord = await tx.operationRetries.get(payOpId);
            const refundQueryRetryRecord = await tx.operationRetries.get(refundQueryOpId);
            const err = payRetryRecord !== undefined
                ? payRetryRecord
                : refundQueryRetryRecord;
            return buildTransactionForPurchase(purchase, refunds, err);
        });
    }
    else if (type === TransactionType.Refresh) {
        rest[0];
        throw Error(`no tx for refresh`);
    }
    else if (type === TransactionType.Tip) {
        const tipId = rest[0];
        return await ws.db
            .mktx((x) => [x.tips, x.operationRetries])
            .runReadWrite(async (tx) => {
            const tipRecord = await tx.tips.get(tipId);
            if (!tipRecord)
                throw Error("not found");
            const retries = await tx.operationRetries.get(RetryTags.forTipPickup(tipRecord));
            return buildTransactionForTip(tipRecord, retries);
        });
    }
    else if (type === TransactionType.Deposit) {
        const depositGroupId = rest[0];
        return await ws.db
            .mktx((x) => [x.depositGroups, x.operationRetries])
            .runReadWrite(async (tx) => {
            const depositRecord = await tx.depositGroups.get(depositGroupId);
            if (!depositRecord)
                throw Error("not found");
            const retries = await tx.operationRetries.get(RetryTags.forDeposit(depositRecord));
            return buildTransactionForDeposit(depositRecord, retries);
        });
    }
    else if (type === TransactionType.Refund) {
        const proposalId = rest[0];
        const executionTimeStr = rest[1];
        return await ws.db
            .mktx((x) => [x.operationRetries, x.purchases, x.tombstones])
            .runReadWrite(async (tx) => {
            const purchase = await tx.purchases.get(proposalId);
            if (!purchase)
                throw Error("not found");
            const theRefund = Object.values(purchase.refunds).find((r) => `${r.executionTime.t_s}` === executionTimeStr);
            if (!theRefund)
                throw Error("not found");
            const t = await tx.tombstones.get(makeEventId(TombstoneTag.DeleteRefund, purchase.proposalId, executionTimeStr));
            if (t)
                throw Error("deleted");
            const contractData = purchase.download.contractData;
            const refunds = mergeRefundByExecutionTime([theRefund], Amounts.getZero(contractData.amount.currency));
            const refundQueryOpId = RetryTags.forRefundQuery(purchase);
            const refundQueryRetryRecord = await tx.operationRetries.get(refundQueryOpId);
            return buildTransactionForRefund(purchase, refunds[0], refundQueryRetryRecord);
        });
    }
    else if (type === TransactionType.PeerPullDebit) {
        const peerPullPaymentIncomingId = rest[0];
        return await ws.db
            .mktx((x) => [x.peerPullPaymentIncoming])
            .runReadWrite(async (tx) => {
            const debit = await tx.peerPullPaymentIncoming.get(peerPullPaymentIncomingId);
            if (!debit)
                throw Error("not found");
            return buildTransactionForPullPaymentDebit(debit);
        });
    }
    else if (type === TransactionType.PeerPushDebit) {
        const pursePub = rest[0];
        return await ws.db
            .mktx((x) => [x.peerPushPaymentInitiations])
            .runReadWrite(async (tx) => {
            const debit = await tx.peerPushPaymentInitiations.get(pursePub);
            if (!debit)
                throw Error("not found");
            return buildTransactionForPushPaymentDebit(debit);
        });
    }
    else {
        const unknownTxType = type;
        throw Error(`can't delete a '${unknownTxType}' transaction`);
    }
}
function buildTransactionForPushPaymentDebit(pi, ort) {
    return Object.assign({ type: TransactionType.PeerPushDebit, amountEffective: pi.amount, amountRaw: pi.amount, exchangeBaseUrl: pi.exchangeBaseUrl, info: {
            expiration: pi.contractTerms.purse_expiration,
            summary: pi.contractTerms.summary,
        }, frozen: false, pending: !pi.purseCreated, timestamp: pi.timestampCreated, talerUri: constructPayPushUri({
            exchangeBaseUrl: pi.exchangeBaseUrl,
            contractPriv: pi.contractPriv,
        }), transactionId: makeEventId(TransactionType.PeerPushDebit, pi.pursePub) }, ((ort === null || ort === void 0 ? void 0 : ort.lastError) ? { error: ort.lastError } : {}));
}
function buildTransactionForPullPaymentDebit(pi, ort) {
    return Object.assign({ type: TransactionType.PeerPullDebit, amountEffective: Amounts.stringify(pi.contractTerms.amount), amountRaw: Amounts.stringify(pi.contractTerms.amount), exchangeBaseUrl: pi.exchangeBaseUrl, frozen: false, pending: false, info: {
            expiration: pi.contractTerms.purse_expiration,
            summary: pi.contractTerms.summary,
        }, timestamp: pi.timestampCreated, transactionId: makeEventId(TransactionType.PeerPullDebit, pi.peerPullPaymentIncomingId) }, ((ort === null || ort === void 0 ? void 0 : ort.lastError) ? { error: ort.lastError } : {}));
}
function buildTransactionForPullPaymentCredit(wsr, ort) {
    if (wsr.wgInfo.withdrawalType !== "peer-pull-credit" /* WithdrawalRecordType.PeerPullCredit */)
        throw Error("");
    return Object.assign({ type: TransactionType.PeerPullCredit, amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount), exchangeBaseUrl: wsr.exchangeBaseUrl, pending: !wsr.timestampFinish, timestamp: wsr.timestampStart, info: {
            expiration: wsr.wgInfo.contractTerms.purse_expiration,
            summary: wsr.wgInfo.contractTerms.summary,
        }, talerUri: constructPayPullUri({
            exchangeBaseUrl: wsr.exchangeBaseUrl,
            contractPriv: wsr.wgInfo.contractPriv,
        }), transactionId: makeEventId(TransactionType.PeerPullCredit, wsr.withdrawalGroupId), frozen: false }, ((ort === null || ort === void 0 ? void 0 : ort.lastError) ? { error: ort.lastError } : {}));
}
function buildTransactionForPushPaymentCredit(wsr, ort) {
    if (wsr.wgInfo.withdrawalType !== "peer-push-credit" /* WithdrawalRecordType.PeerPushCredit */)
        throw Error("");
    return Object.assign({ type: TransactionType.PeerPushCredit, amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount), exchangeBaseUrl: wsr.exchangeBaseUrl, info: {
            expiration: wsr.wgInfo.contractTerms.purse_expiration,
            summary: wsr.wgInfo.contractTerms.summary,
        }, pending: !wsr.timestampFinish, timestamp: wsr.timestampStart, transactionId: makeEventId(TransactionType.PeerPushCredit, wsr.withdrawalGroupId), frozen: false }, ((ort === null || ort === void 0 ? void 0 : ort.lastError) ? { error: ort.lastError } : {}));
}
function buildTransactionForBankIntegratedWithdraw(wsr, ort) {
    if (wsr.wgInfo.withdrawalType !== "bank-integrated" /* WithdrawalRecordType.BankIntegrated */)
        throw Error("");
    return Object.assign({ type: TransactionType.Withdrawal, amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount), withdrawalDetails: {
            type: WithdrawalType.TalerBankIntegrationApi,
            confirmed: wsr.wgInfo.bankInfo.timestampBankConfirmed ? true : false,
            reservePub: wsr.reservePub,
            bankConfirmationUrl: wsr.wgInfo.bankInfo.confirmUrl,
        }, exchangeBaseUrl: wsr.exchangeBaseUrl, pending: !wsr.timestampFinish, timestamp: wsr.timestampStart, transactionId: makeEventId(TransactionType.Withdrawal, wsr.withdrawalGroupId), frozen: false }, ((ort === null || ort === void 0 ? void 0 : ort.lastError) ? { error: ort.lastError } : {}));
}
function buildTransactionForManualWithdraw(wsr, exchangeDetails, ort) {
    var _a, _b;
    if (wsr.wgInfo.withdrawalType !== "bank-manual" /* WithdrawalRecordType.BankManual */)
        throw Error("");
    return Object.assign({ type: TransactionType.Withdrawal, amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount), withdrawalDetails: {
            type: WithdrawalType.ManualTransfer,
            reservePub: wsr.reservePub,
            exchangePaytoUris: (_b = (_a = exchangeDetails.wireInfo) === null || _a === void 0 ? void 0 : _a.accounts.map((x) => addPaytoQueryParams(x.payto_uri, { subject: wsr.reservePub }))) !== null && _b !== void 0 ? _b : [],
        }, exchangeBaseUrl: wsr.exchangeBaseUrl, pending: !wsr.timestampFinish, timestamp: wsr.timestampStart, transactionId: makeEventId(TransactionType.Withdrawal, wsr.withdrawalGroupId), frozen: false }, ((ort === null || ort === void 0 ? void 0 : ort.lastError) ? { error: ort.lastError } : {}));
}
function buildTransactionForDeposit(dg, ort) {
    return Object.assign({ type: TransactionType.Deposit, amountRaw: Amounts.stringify(dg.effectiveDepositAmount), amountEffective: Amounts.stringify(dg.totalPayCost), pending: !dg.timestampFinished, frozen: false, timestamp: dg.timestampCreated, targetPaytoUri: dg.wire.payto_uri, transactionId: makeEventId(TransactionType.Deposit, dg.depositGroupId), depositGroupId: dg.depositGroupId }, ((ort === null || ort === void 0 ? void 0 : ort.lastError) ? { error: ort.lastError } : {}));
}
function buildTransactionForTip(tipRecord, ort) {
    if (!tipRecord.acceptedTimestamp)
        throw Error("");
    return Object.assign({ type: TransactionType.Tip, amountEffective: Amounts.stringify(tipRecord.tipAmountEffective), amountRaw: Amounts.stringify(tipRecord.tipAmountRaw), pending: !tipRecord.pickedUpTimestamp, frozen: false, timestamp: tipRecord.acceptedTimestamp, transactionId: makeEventId(TransactionType.Tip, tipRecord.walletTipId), merchantBaseUrl: tipRecord.merchantBaseUrl }, ((ort === null || ort === void 0 ? void 0 : ort.lastError) ? { error: ort.lastError } : {}));
}
function mergeRefundByExecutionTime(rs, zero) {
    const refundByExecTime = rs.reduce((prev, refund) => {
        const key = `${refund.executionTime.t_s}`;
        //refunds counts if applied
        const effective = refund.type === RefundState.Applied
            ? Amounts.sub(refund.refundAmount, refund.refundFee, refund.totalRefreshCostBound).amount
            : zero;
        const raw = refund.type === RefundState.Applied ? refund.refundAmount : zero;
        const v = prev.get(key);
        if (!v) {
            prev.set(key, {
                executionTime: refund.executionTime,
                amountAppliedEffective: effective,
                amountAppliedRaw: raw,
                firstTimestamp: refund.obtainedTime,
            });
        }
        else {
            //v.executionTime is the same
            v.amountAppliedEffective = Amounts.add(v.amountAppliedEffective, effective).amount;
            v.amountAppliedRaw = Amounts.add(v.amountAppliedRaw).amount;
            v.firstTimestamp = TalerProtocolTimestamp.min(v.firstTimestamp, refund.obtainedTime);
        }
        return prev;
    }, new Map());
    return Array.from(refundByExecTime.values());
}
function buildTransactionForRefund(purchaseRecord, refundInfo, ort) {
    const contractData = purchaseRecord.download.contractData;
    const info = {
        merchant: contractData.merchant,
        orderId: contractData.orderId,
        products: contractData.products,
        summary: contractData.summary,
        summary_i18n: contractData.summaryI18n,
        contractTermsHash: contractData.contractTermsHash,
    };
    if (contractData.fulfillmentUrl !== "") {
        info.fulfillmentUrl = contractData.fulfillmentUrl;
    }
    return Object.assign({ type: TransactionType.Refund, info, refundedTransactionId: makeEventId(TransactionType.Payment, purchaseRecord.proposalId), transactionId: makeEventId(TransactionType.Refund, purchaseRecord.proposalId, `${refundInfo.executionTime.t_s}`), timestamp: refundInfo.firstTimestamp, amountEffective: Amounts.stringify(refundInfo.amountAppliedEffective), amountRaw: Amounts.stringify(refundInfo.amountAppliedRaw), refundPending: purchaseRecord.refundAwaiting === undefined
            ? undefined
            : Amounts.stringify(purchaseRecord.refundAwaiting), pending: false, frozen: false }, ((ort === null || ort === void 0 ? void 0 : ort.lastError) ? { error: ort.lastError } : {}));
}
function buildTransactionForPurchase(purchaseRecord, refundsInfo, ort) {
    var _a;
    const contractData = purchaseRecord.download.contractData;
    const zero = Amounts.getZero(contractData.amount.currency);
    const info = {
        merchant: contractData.merchant,
        orderId: contractData.orderId,
        products: contractData.products,
        summary: contractData.summary,
        summary_i18n: contractData.summaryI18n,
        contractTermsHash: contractData.contractTermsHash,
    };
    if (contractData.fulfillmentUrl !== "") {
        info.fulfillmentUrl = contractData.fulfillmentUrl;
    }
    const totalRefund = refundsInfo.reduce((prev, cur) => {
        return {
            raw: Amounts.add(prev.raw, cur.amountAppliedRaw).amount,
            effective: Amounts.add(prev.effective, cur.amountAppliedEffective)
                .amount,
        };
    }, {
        raw: zero,
        effective: zero,
    });
    const refunds = refundsInfo.map((r) => ({
        amountEffective: Amounts.stringify(r.amountAppliedEffective),
        amountRaw: Amounts.stringify(r.amountAppliedRaw),
        timestamp: r.executionTime,
        transactionId: makeEventId(TransactionType.Refund, purchaseRecord.proposalId, `${r.executionTime.t_s}`),
    }));
    return Object.assign({ type: TransactionType.Payment, amountRaw: Amounts.stringify(contractData.amount), amountEffective: Amounts.stringify(purchaseRecord.totalPayCost), totalRefundRaw: Amounts.stringify(totalRefund.raw), totalRefundEffective: Amounts.stringify(totalRefund.effective), refundPending: purchaseRecord.refundAwaiting === undefined
            ? undefined
            : Amounts.stringify(purchaseRecord.refundAwaiting), status: purchaseRecord.timestampFirstSuccessfulPay
            ? PaymentStatus.Paid
            : PaymentStatus.Accepted, pending: !purchaseRecord.timestampFirstSuccessfulPay &&
            purchaseRecord.abortStatus === AbortStatus.None, refunds, timestamp: purchaseRecord.timestampAccept, transactionId: makeEventId(TransactionType.Payment, purchaseRecord.proposalId), proposalId: purchaseRecord.proposalId, info, frozen: (_a = purchaseRecord.payFrozen) !== null && _a !== void 0 ? _a : false }, ((ort === null || ort === void 0 ? void 0 : ort.lastError) ? { error: ort.lastError } : {}));
}
/**
 * Retrieve the full event history for this wallet.
 */
async function getTransactions(ws, transactionsRequest) {
    const transactions = [];
    await ws.db
        .mktx((x) => [
        x.coins,
        x.denominations,
        x.depositGroups,
        x.exchangeDetails,
        x.exchanges,
        x.operationRetries,
        x.peerPullPaymentIncoming,
        x.peerPushPaymentInitiations,
        x.planchets,
        x.proposals,
        x.purchases,
        x.recoupGroups,
        x.tips,
        x.tombstones,
        x.withdrawalGroups,
    ])
        .runReadOnly(async (tx) => {
        tx.peerPushPaymentInitiations.iter().forEachAsync(async (pi) => {
            const amount = Amounts.parseOrThrow(pi.amount);
            if (shouldSkipCurrency(transactionsRequest, amount.currency)) {
                return;
            }
            if (shouldSkipSearch(transactionsRequest, [])) {
                return;
            }
            transactions.push(buildTransactionForPushPaymentDebit(pi));
        });
        tx.peerPullPaymentIncoming.iter().forEachAsync(async (pi) => {
            const amount = Amounts.parseOrThrow(pi.contractTerms.amount);
            if (shouldSkipCurrency(transactionsRequest, amount.currency)) {
                return;
            }
            if (shouldSkipSearch(transactionsRequest, [])) {
                return;
            }
            if (!pi.accepted) {
                return;
            }
            transactions.push(buildTransactionForPullPaymentDebit(pi));
        });
        tx.withdrawalGroups.iter().forEachAsync(async (wsr) => {
            if (shouldSkipCurrency(transactionsRequest, wsr.rawWithdrawalAmount.currency)) {
                return;
            }
            if (shouldSkipSearch(transactionsRequest, [])) {
                return;
            }
            const opId = RetryTags.forWithdrawal(wsr);
            const ort = await tx.operationRetries.get(opId);
            if (wsr.wgInfo.withdrawalType === "peer-pull-credit" /* WithdrawalRecordType.PeerPullCredit */) {
                transactions.push(buildTransactionForPullPaymentCredit(wsr, ort));
                return;
            }
            else if (wsr.wgInfo.withdrawalType === "peer-push-credit" /* WithdrawalRecordType.PeerPushCredit */) {
                transactions.push(buildTransactionForPushPaymentCredit(wsr, ort));
                return;
            }
            else if (wsr.wgInfo.withdrawalType === "bank-integrated" /* WithdrawalRecordType.BankIntegrated */) {
                transactions.push(buildTransactionForBankIntegratedWithdraw(wsr, ort));
            }
            else {
                const exchangeDetails = await getExchangeDetails(tx, wsr.exchangeBaseUrl);
                if (!exchangeDetails) {
                    // FIXME: report somehow
                    return;
                }
                transactions.push(buildTransactionForManualWithdraw(wsr, exchangeDetails, ort));
            }
        });
        tx.depositGroups.iter().forEachAsync(async (dg) => {
            const amount = Amounts.parseOrThrow(dg.contractTermsRaw.amount);
            if (shouldSkipCurrency(transactionsRequest, amount.currency)) {
                return;
            }
            const opId = RetryTags.forDeposit(dg);
            const retryRecord = await tx.operationRetries.get(opId);
            transactions.push(buildTransactionForDeposit(dg, retryRecord));
        });
        tx.purchases.iter().forEachAsync(async (pr) => {
            if (shouldSkipCurrency(transactionsRequest, pr.download.contractData.amount.currency)) {
                return;
            }
            const contractData = pr.download.contractData;
            if (shouldSkipSearch(transactionsRequest, [contractData.summary])) {
                return;
            }
            const proposal = await tx.proposals.get(pr.proposalId);
            if (!proposal) {
                return;
            }
            const filteredRefunds = await Promise.all(Object.values(pr.refunds).map(async (r) => {
                const t = await tx.tombstones.get(makeEventId(TombstoneTag.DeleteRefund, pr.proposalId, `${r.executionTime.t_s}`));
                if (!t)
                    return r;
                return undefined;
            }));
            const cleanRefunds = filteredRefunds.filter((x) => !!x);
            const refunds = mergeRefundByExecutionTime(cleanRefunds, Amounts.getZero(contractData.amount.currency));
            refunds.forEach(async (refundInfo) => {
                const refundQueryOpId = RetryTags.forRefundQuery(pr);
                const refundQueryRetryRecord = await tx.operationRetries.get(refundQueryOpId);
                transactions.push(buildTransactionForRefund(pr, refundInfo, refundQueryRetryRecord));
            });
            const payOpId = RetryTags.forPay(pr);
            const refundQueryOpId = RetryTags.forRefundQuery(pr);
            const payRetryRecord = await tx.operationRetries.get(payOpId);
            const refundQueryRetryRecord = await tx.operationRetries.get(refundQueryOpId);
            const err = payRetryRecord !== undefined
                ? payRetryRecord
                : refundQueryRetryRecord;
            transactions.push(buildTransactionForPurchase(pr, refunds, err));
        });
        tx.tips.iter().forEachAsync(async (tipRecord) => {
            if (shouldSkipCurrency(transactionsRequest, tipRecord.tipAmountRaw.currency)) {
                return;
            }
            if (!tipRecord.acceptedTimestamp) {
                return;
            }
            const opId = RetryTags.forTipPickup(tipRecord);
            const retryRecord = await tx.operationRetries.get(opId);
            transactions.push(buildTransactionForTip(tipRecord, retryRecord));
        });
    });
    const txPending = transactions.filter((x) => x.pending);
    const txNotPending = transactions.filter((x) => !x.pending);
    const txCmp = (h1, h2) => {
        const tsCmp = AbsoluteTime.cmp(AbsoluteTime.fromTimestamp(h1.timestamp), AbsoluteTime.fromTimestamp(h2.timestamp));
        if (tsCmp === 0) {
            return Math.sign(txOrder[h1.type] - txOrder[h2.type]);
        }
        return tsCmp;
    };
    txPending.sort(txCmp);
    txNotPending.sort(txCmp);
    return { transactions: [...txNotPending, ...txPending] };
}
/**
 * Immediately retry the underlying operation
 * of a transaction.
 */
async function retryTransaction(ws, transactionId) {
    logger$i.info(`retrying transaction ${transactionId}`);
    const [type, ...rest] = transactionId.split(":");
    switch (type) {
        case TransactionType.Deposit: {
            const depositGroupId = rest[0];
            processDepositGroup(ws, depositGroupId, {
                forceNow: true,
            });
            break;
        }
        case TransactionType.Withdrawal: {
            const withdrawalGroupId = rest[0];
            await processWithdrawalGroup(ws, withdrawalGroupId, { forceNow: true });
            break;
        }
        case TransactionType.Payment: {
            const proposalId = rest[0];
            await processPurchasePay(ws, proposalId, { forceNow: true });
            break;
        }
        case TransactionType.Tip: {
            const walletTipId = rest[0];
            await processTip(ws, walletTipId, { forceNow: true });
            break;
        }
        case TransactionType.Refresh: {
            const refreshGroupId = rest[0];
            await processRefreshGroup(ws, refreshGroupId, { forceNow: true });
            break;
        }
    }
}
/**
 * Permanently delete a transaction based on the transaction ID.
 */
async function deleteTransaction(ws, transactionId) {
    const [typeStr, ...rest] = transactionId.split(":");
    const type = typeStr;
    if (type === TransactionType.Withdrawal ||
        type === TransactionType.PeerPullCredit ||
        type === TransactionType.PeerPushCredit) {
        const withdrawalGroupId = rest[0];
        await ws.db
            .mktx((x) => [x.withdrawalGroups, x.tombstones])
            .runReadWrite(async (tx) => {
            const withdrawalGroupRecord = await tx.withdrawalGroups.get(withdrawalGroupId);
            if (withdrawalGroupRecord) {
                await tx.withdrawalGroups.delete(withdrawalGroupId);
                await tx.tombstones.put({
                    id: TombstoneTag.DeleteWithdrawalGroup + ":" + withdrawalGroupId,
                });
                return;
            }
        });
    }
    else if (type === TransactionType.Payment) {
        const proposalId = rest[0];
        await ws.db
            .mktx((x) => [x.proposals, x.purchases, x.tombstones])
            .runReadWrite(async (tx) => {
            let found = false;
            const proposal = await tx.proposals.get(proposalId);
            if (proposal) {
                found = true;
                await tx.proposals.delete(proposalId);
            }
            const purchase = await tx.purchases.get(proposalId);
            if (purchase) {
                found = true;
                await tx.purchases.delete(proposalId);
            }
            if (found) {
                await tx.tombstones.put({
                    id: TombstoneTag.DeletePayment + ":" + proposalId,
                });
            }
        });
    }
    else if (type === TransactionType.Refresh) {
        const refreshGroupId = rest[0];
        await ws.db
            .mktx((x) => [x.refreshGroups, x.tombstones])
            .runReadWrite(async (tx) => {
            const rg = await tx.refreshGroups.get(refreshGroupId);
            if (rg) {
                await tx.refreshGroups.delete(refreshGroupId);
                await tx.tombstones.put({
                    id: TombstoneTag.DeleteRefreshGroup + ":" + refreshGroupId,
                });
            }
        });
    }
    else if (type === TransactionType.Tip) {
        const tipId = rest[0];
        await ws.db
            .mktx((x) => [x.tips, x.tombstones])
            .runReadWrite(async (tx) => {
            const tipRecord = await tx.tips.get(tipId);
            if (tipRecord) {
                await tx.tips.delete(tipId);
                await tx.tombstones.put({
                    id: TombstoneTag.DeleteTip + ":" + tipId,
                });
            }
        });
    }
    else if (type === TransactionType.Deposit) {
        const depositGroupId = rest[0];
        await ws.db
            .mktx((x) => [x.depositGroups, x.tombstones])
            .runReadWrite(async (tx) => {
            const tipRecord = await tx.depositGroups.get(depositGroupId);
            if (tipRecord) {
                await tx.depositGroups.delete(depositGroupId);
                await tx.tombstones.put({
                    id: TombstoneTag.DeleteDepositGroup + ":" + depositGroupId,
                });
            }
        });
    }
    else if (type === TransactionType.Refund) {
        const proposalId = rest[0];
        const executionTimeStr = rest[1];
        await ws.db
            .mktx((x) => [x.proposals, x.purchases, x.tombstones])
            .runReadWrite(async (tx) => {
            const purchase = await tx.purchases.get(proposalId);
            if (purchase) {
                // This should just influence the history view,
                // but won't delete any actual refund information.
                await tx.tombstones.put({
                    id: makeEventId(TombstoneTag.DeleteRefund, proposalId, executionTimeStr),
                });
            }
        });
    }
    else if (type === TransactionType.PeerPullDebit) {
        const peerPullPaymentIncomingId = rest[0];
        await ws.db
            .mktx((x) => [x.peerPullPaymentIncoming, x.tombstones])
            .runReadWrite(async (tx) => {
            const debit = await tx.peerPullPaymentIncoming.get(peerPullPaymentIncomingId);
            if (debit) {
                await tx.peerPullPaymentIncoming.delete(peerPullPaymentIncomingId);
                await tx.tombstones.put({
                    id: makeEventId(TombstoneTag.DeletePeerPullDebit, peerPullPaymentIncomingId),
                });
            }
        });
    }
    else if (type === TransactionType.PeerPushDebit) {
        const pursePub = rest[0];
        await ws.db
            .mktx((x) => [x.peerPushPaymentInitiations, x.tombstones])
            .runReadWrite(async (tx) => {
            const debit = await tx.peerPushPaymentInitiations.get(pursePub);
            if (debit) {
                await tx.peerPushPaymentInitiations.delete(pursePub);
                await tx.tombstones.put({
                    id: makeEventId(TombstoneTag.DeletePeerPushDebit, pursePub),
                });
            }
        });
    }
    else {
        const unknownTxType = type;
        throw Error(`can't delete a '${unknownTxType}' transaction`);
    }
}

/*
 This file is part of GNU Taler
 (C) 2019-2021 Taler Systems SA

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * Logger for this file.
 */
const logger$h = new Logger("operations/withdraw.ts");
/**
 * Check if a denom is withdrawable based on the expiration time,
 * revocation and offered state.
 */
function isWithdrawableDenom(d) {
    const now = AbsoluteTime.now();
    const start = AbsoluteTime.fromTimestamp(d.stampStart);
    const withdrawExpire = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw);
    const started = AbsoluteTime.cmp(now, start) >= 0;
    let lastPossibleWithdraw;
    {
        lastPossibleWithdraw = AbsoluteTime.subtractDuraction(withdrawExpire, durationFromSpec({ minutes: 5 }));
    }
    const remaining = Duration.getRemaining(lastPossibleWithdraw, now);
    const stillOkay = remaining.d_ms !== 0;
    return started && stillOkay && !d.isRevoked && d.isOffered;
}
/**
 * Get a list of denominations (with repetitions possible)
 * whose total value is as close as possible to the available
 * amount, but never larger.
 */
function selectWithdrawalDenominations(amountAvailable, denoms) {
    let remaining = Amounts.copy(amountAvailable);
    const selectedDenoms = [];
    let totalCoinValue = Amounts.getZero(amountAvailable.currency);
    let totalWithdrawCost = Amounts.getZero(amountAvailable.currency);
    denoms = denoms.filter(isWithdrawableDenom);
    denoms.sort((d1, d2) => Amounts.cmp(DenominationRecord.getValue(d2), DenominationRecord.getValue(d1)));
    for (const d of denoms) {
        let count = 0;
        const cost = Amounts.add(DenominationRecord.getValue(d), d.fees.feeWithdraw).amount;
        for (;;) {
            if (Amounts.cmp(remaining, cost) < 0) {
                break;
            }
            remaining = Amounts.sub(remaining, cost).amount;
            count++;
        }
        if (count > 0) {
            totalCoinValue = Amounts.add(totalCoinValue, Amounts.mult(DenominationRecord.getValue(d), count).amount).amount;
            totalWithdrawCost = Amounts.add(totalWithdrawCost, Amounts.mult(cost, count).amount).amount;
            selectedDenoms.push({
                count,
                denomPubHash: d.denomPubHash,
            });
        }
        if (Amounts.isZero(remaining)) {
            break;
        }
    }
    if (logger$h.shouldLogTrace()) {
        logger$h.trace(`selected withdrawal denoms for ${Amounts.stringify(totalCoinValue)}`);
        for (const sd of selectedDenoms) {
            logger$h.trace(`denom_pub_hash=${sd.denomPubHash}, count=${sd.count}`);
        }
        logger$h.trace("(end of withdrawal denom list)");
    }
    return {
        selectedDenoms,
        totalCoinValue,
        totalWithdrawCost,
    };
}
function selectForcedWithdrawalDenominations(amountAvailable, denoms, forcedDenomSel) {
    const selectedDenoms = [];
    let totalCoinValue = Amounts.getZero(amountAvailable.currency);
    let totalWithdrawCost = Amounts.getZero(amountAvailable.currency);
    denoms = denoms.filter(isWithdrawableDenom);
    denoms.sort((d1, d2) => Amounts.cmp(DenominationRecord.getValue(d2), DenominationRecord.getValue(d1)));
    for (const fds of forcedDenomSel.denoms) {
        const count = fds.count;
        const denom = denoms.find((x) => {
            return Amounts.cmp(DenominationRecord.getValue(x), fds.value) == 0;
        });
        if (!denom) {
            throw Error(`unable to find denom for forced selection (value ${fds.value})`);
        }
        const cost = Amounts.add(DenominationRecord.getValue(denom), denom.fees.feeWithdraw).amount;
        totalCoinValue = Amounts.add(totalCoinValue, Amounts.mult(DenominationRecord.getValue(denom), count).amount).amount;
        totalWithdrawCost = Amounts.add(totalWithdrawCost, Amounts.mult(cost, count).amount).amount;
        selectedDenoms.push({
            count,
            denomPubHash: denom.denomPubHash,
        });
    }
    return {
        selectedDenoms,
        totalCoinValue,
        totalWithdrawCost,
    };
}
/**
 * Get information about a withdrawal from
 * a taler://withdraw URI by asking the bank.
 *
 * FIXME: Move into bank client.
 */
async function getBankWithdrawalInfo(http, talerWithdrawUri) {
    const uriResult = parseWithdrawUri(talerWithdrawUri);
    if (!uriResult) {
        throw Error(`can't parse URL ${talerWithdrawUri}`);
    }
    const configReqUrl = new URL$1("config", uriResult.bankIntegrationApiBaseUrl);
    const configResp = await http.get(configReqUrl.href);
    const config = await readSuccessResponseJsonOrThrow(configResp, codecForTalerConfigResponse());
    const versionRes = LibtoolVersion.compare(WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, config.version);
    if ((versionRes === null || versionRes === void 0 ? void 0 : versionRes.compatible) != true) {
        throw TalerError.fromDetail(TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE, {
            exchangeProtocolVersion: config.version,
            walletProtocolVersion: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
        }, "bank integration protocol version not compatible with wallet");
    }
    const reqUrl = new URL$1(`withdrawal-operation/${uriResult.withdrawalOperationId}`, uriResult.bankIntegrationApiBaseUrl);
    logger$h.info(`bank withdrawal status URL: ${reqUrl.href}}`);
    const resp = await http.get(reqUrl.href);
    const status = await readSuccessResponseJsonOrThrow(resp, codecForWithdrawOperationStatusResponse());
    logger$h.info(`bank withdrawal operation status: ${j2s(status)}`);
    return {
        amount: Amounts.parseOrThrow(status.amount),
        confirmTransferUrl: status.confirm_transfer_url,
        selectionDone: status.selection_done,
        senderWire: status.sender_wire,
        suggestedExchange: status.suggested_exchange,
        transferDone: status.transfer_done,
        wireTypes: status.wire_types,
    };
}
/**
 * Return denominations that can potentially used for a withdrawal.
 */
async function getCandidateWithdrawalDenoms(ws, exchangeBaseUrl) {
    return await ws.db
        .mktx((x) => [x.denominations])
        .runReadOnly(async (tx) => {
        const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl.getAll(exchangeBaseUrl);
        return allDenoms.filter(isWithdrawableDenom);
    });
}
/**
 * Generate a planchet for a coin index in a withdrawal group.
 * Does not actually withdraw the coin yet.
 *
 * Split up so that we can parallelize the crypto, but serialize
 * the exchange requests per reserve.
 */
async function processPlanchetGenerate(ws, withdrawalGroup, coinIdx) {
    var _a;
    let planchet = await ws.db
        .mktx((x) => [x.planchets])
        .runReadOnly(async (tx) => {
        return tx.planchets.indexes.byGroupAndIndex.get([
            withdrawalGroup.withdrawalGroupId,
            coinIdx,
        ]);
    });
    if (planchet) {
        return;
    }
    let ci = 0;
    let maybeDenomPubHash;
    for (let di = 0; di < withdrawalGroup.denomsSel.selectedDenoms.length; di++) {
        const d = withdrawalGroup.denomsSel.selectedDenoms[di];
        if (coinIdx >= ci && coinIdx < ci + d.count) {
            maybeDenomPubHash = d.denomPubHash;
            break;
        }
        ci += d.count;
    }
    if (!maybeDenomPubHash) {
        throw Error("invariant violated");
    }
    const denomPubHash = maybeDenomPubHash;
    const denom = await ws.db
        .mktx((x) => [x.denominations])
        .runReadOnly(async (tx) => {
        return ws.getDenomInfo(ws, tx, withdrawalGroup.exchangeBaseUrl, denomPubHash);
    });
    checkDbInvariant(!!denom);
    const r = await ws.cryptoApi.createPlanchet({
        denomPub: denom.denomPub,
        feeWithdraw: denom.feeWithdraw,
        reservePriv: withdrawalGroup.reservePriv,
        reservePub: withdrawalGroup.reservePub,
        value: denom.value,
        coinIndex: coinIdx,
        secretSeed: withdrawalGroup.secretSeed,
        restrictAge: withdrawalGroup.restrictAge,
    });
    const newPlanchet = {
        blindingKey: r.blindingKey,
        coinEv: r.coinEv,
        coinEvHash: r.coinEvHash,
        coinIdx,
        coinPriv: r.coinPriv,
        coinPub: r.coinPub,
        denomPubHash: r.denomPubHash,
        reservePub: r.reservePub,
        withdrawalDone: false,
        withdrawSig: r.withdrawSig,
        withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
        maxAge: (_a = withdrawalGroup.restrictAge) !== null && _a !== void 0 ? _a : AgeRestriction.AGE_UNRESTRICTED,
        ageCommitmentProof: r.ageCommitmentProof,
        lastError: undefined,
    };
    await ws.db
        .mktx((x) => [x.planchets])
        .runReadWrite(async (tx) => {
        const p = await tx.planchets.indexes.byGroupAndIndex.get([
            withdrawalGroup.withdrawalGroupId,
            coinIdx,
        ]);
        if (p) {
            planchet = p;
            return;
        }
        await tx.planchets.put(newPlanchet);
        planchet = newPlanchet;
    });
}
/**
 * Send the withdrawal request for a generated planchet to the exchange.
 *
 * The verification of the response is done asynchronously to enable parallelism.
 */
async function processPlanchetExchangeRequest(ws, withdrawalGroup, coinIdx) {
    logger$h.info(`processing planchet exchange request ${withdrawalGroup.withdrawalGroupId}/${coinIdx}`);
    const d = await ws.db
        .mktx((x) => [
        x.withdrawalGroups,
        x.planchets,
        x.exchanges,
        x.denominations,
    ])
        .runReadOnly(async (tx) => {
        let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
            withdrawalGroup.withdrawalGroupId,
            coinIdx,
        ]);
        if (!planchet) {
            return;
        }
        if (planchet.withdrawalDone) {
            logger$h.warn("processPlanchet: planchet already withdrawn");
            return;
        }
        const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl);
        if (!exchange) {
            logger$h.error("db inconsistent: exchange for planchet not found");
            return;
        }
        const denom = await ws.getDenomInfo(ws, tx, withdrawalGroup.exchangeBaseUrl, planchet.denomPubHash);
        if (!denom) {
            logger$h.error("db inconsistent: denom for planchet not found");
            return;
        }
        logger$h.trace(`processing planchet #${coinIdx} in withdrawal ${withdrawalGroup.withdrawalGroupId}`);
        const reqBody = {
            denom_pub_hash: planchet.denomPubHash,
            reserve_sig: planchet.withdrawSig,
            coin_ev: planchet.coinEv,
        };
        const reqUrl = new URL$1(`reserves/${planchet.reservePub}/withdraw`, exchange.baseUrl).href;
        return { reqUrl, reqBody };
    });
    if (!d) {
        return;
    }
    const { reqUrl, reqBody } = d;
    try {
        const resp = await ws.http.postJson(reqUrl, reqBody);
        const r = await readSuccessResponseJsonOrThrow(resp, codecForWithdrawResponse());
        return r;
    }
    catch (e) {
        const errDetail = getErrorDetailFromException(e);
        logger$h.trace("withdrawal request failed", e);
        logger$h.trace(e);
        await ws.db
            .mktx((x) => [x.planchets])
            .runReadWrite(async (tx) => {
            let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
                withdrawalGroup.withdrawalGroupId,
                coinIdx,
            ]);
            if (!planchet) {
                return;
            }
            planchet.lastError = errDetail;
            await tx.planchets.put(planchet);
        });
        return;
    }
}
/**
 * Send the withdrawal request for a generated planchet to the exchange.
 *
 * The verification of the response is done asynchronously to enable parallelism.
 */
async function processPlanchetExchangeBatchRequest(ws, withdrawalGroup) {
    logger$h.info(`processing planchet exchange batch request ${withdrawalGroup.withdrawalGroupId}`);
    const numTotalCoins = withdrawalGroup.denomsSel.selectedDenoms
        .map((x) => x.count)
        .reduce((a, b) => a + b);
    const d = await ws.db
        .mktx((x) => [
        x.withdrawalGroups,
        x.planchets,
        x.exchanges,
        x.denominations,
    ])
        .runReadOnly(async (tx) => {
        const reqBody = {
            planchets: [],
        };
        const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl);
        if (!exchange) {
            logger$h.error("db inconsistent: exchange for planchet not found");
            return;
        }
        for (let coinIdx = 0; coinIdx < numTotalCoins; coinIdx++) {
            let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
                withdrawalGroup.withdrawalGroupId,
                coinIdx,
            ]);
            if (!planchet) {
                return;
            }
            if (planchet.withdrawalDone) {
                logger$h.warn("processPlanchet: planchet already withdrawn");
                return;
            }
            const denom = await ws.getDenomInfo(ws, tx, withdrawalGroup.exchangeBaseUrl, planchet.denomPubHash);
            if (!denom) {
                logger$h.error("db inconsistent: denom for planchet not found");
                return;
            }
            const planchetReq = {
                denom_pub_hash: planchet.denomPubHash,
                reserve_sig: planchet.withdrawSig,
                coin_ev: planchet.coinEv,
            };
            reqBody.planchets.push(planchetReq);
        }
        return reqBody;
    });
    if (!d) {
        return;
    }
    const reqUrl = new URL$1(`reserves/${withdrawalGroup.reservePub}/batch-withdraw`, withdrawalGroup.exchangeBaseUrl).href;
    const resp = await ws.http.postJson(reqUrl, d);
    const r = await readSuccessResponseJsonOrThrow(resp, codecForWithdrawBatchResponse());
    return r;
}
async function processPlanchetVerifyAndStoreCoin(ws, withdrawalGroup, coinIdx, resp) {
    const d = await ws.db
        .mktx((x) => [x.withdrawalGroups, x.planchets, x.denominations])
        .runReadOnly(async (tx) => {
        let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
            withdrawalGroup.withdrawalGroupId,
            coinIdx,
        ]);
        if (!planchet) {
            return;
        }
        if (planchet.withdrawalDone) {
            logger$h.warn("processPlanchet: planchet already withdrawn");
            return;
        }
        const denomInfo = await ws.getDenomInfo(ws, tx, withdrawalGroup.exchangeBaseUrl, planchet.denomPubHash);
        if (!denomInfo) {
            return;
        }
        return {
            planchet,
            denomInfo,
            exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl,
        };
    });
    if (!d) {
        return;
    }
    const { planchet, denomInfo } = d;
    const planchetDenomPub = denomInfo.denomPub;
    if (planchetDenomPub.cipher !== DenomKeyType.Rsa) {
        throw Error(`cipher (${planchetDenomPub.cipher}) not supported`);
    }
    let evSig = resp.ev_sig;
    if (!(evSig.cipher === DenomKeyType.Rsa)) {
        throw Error("unsupported cipher");
    }
    const denomSigRsa = await ws.cryptoApi.rsaUnblind({
        bk: planchet.blindingKey,
        blindedSig: evSig.blinded_rsa_signature,
        pk: planchetDenomPub.rsa_public_key,
    });
    const isValid = await ws.cryptoApi.rsaVerify({
        hm: planchet.coinPub,
        pk: planchetDenomPub.rsa_public_key,
        sig: denomSigRsa.sig,
    });
    if (!isValid) {
        await ws.db
            .mktx((x) => [x.planchets])
            .runReadWrite(async (tx) => {
            let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
                withdrawalGroup.withdrawalGroupId,
                coinIdx,
            ]);
            if (!planchet) {
                return;
            }
            planchet.lastError = makeErrorDetail(TalerErrorCode.WALLET_EXCHANGE_COIN_SIGNATURE_INVALID, {}, "invalid signature from the exchange after unblinding");
            await tx.planchets.put(planchet);
        });
        return;
    }
    let denomSig;
    if (planchetDenomPub.cipher === DenomKeyType.Rsa) {
        denomSig = {
            cipher: planchetDenomPub.cipher,
            rsa_signature: denomSigRsa.sig,
        };
    }
    else {
        throw Error("unsupported cipher");
    }
    const coin = {
        blindingKey: planchet.blindingKey,
        coinPriv: planchet.coinPriv,
        coinPub: planchet.coinPub,
        currentAmount: denomInfo.value,
        denomPubHash: planchet.denomPubHash,
        denomSig,
        coinEvHash: planchet.coinEvHash,
        exchangeBaseUrl: d.exchangeBaseUrl,
        status: CoinStatus.Fresh,
        coinSource: {
            type: CoinSourceType.Withdraw,
            coinIndex: coinIdx,
            reservePub: planchet.reservePub,
            withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
        },
        maxAge: planchet.maxAge,
        ageCommitmentProof: planchet.ageCommitmentProof,
        allocation: undefined,
    };
    const planchetCoinPub = planchet.coinPub;
    // Check if this is the first time that the whole
    // withdrawal succeeded.  If so, mark the withdrawal
    // group as finished.
    const firstSuccess = await ws.db
        .mktx((x) => [
        x.coins,
        x.denominations,
        x.coinAvailability,
        x.withdrawalGroups,
        x.planchets,
    ])
        .runReadWrite(async (tx) => {
        const p = await tx.planchets.get(planchetCoinPub);
        if (!p || p.withdrawalDone) {
            return false;
        }
        p.withdrawalDone = true;
        await tx.planchets.put(p);
        await makeCoinAvailable(ws, tx, coin);
        return true;
    });
    if (firstSuccess) {
        ws.notify({
            type: NotificationType.CoinWithdrawn,
        });
    }
}
/**
 * Make sure that denominations that currently can be used for withdrawal
 * are validated, and the result of validation is stored in the database.
 */
async function updateWithdrawalDenoms(ws, exchangeBaseUrl) {
    logger$h.trace(`updating denominations used for withdrawal for ${exchangeBaseUrl}`);
    const exchangeDetails = await ws.db
        .mktx((x) => [x.exchanges, x.exchangeDetails])
        .runReadOnly(async (tx) => {
        return ws.exchangeOps.getExchangeDetails(tx, exchangeBaseUrl);
    });
    if (!exchangeDetails) {
        logger$h.error("exchange details not available");
        throw Error(`exchange ${exchangeBaseUrl} details not available`);
    }
    // First do a pass where the validity of candidate denominations
    // is checked and the result is stored in the database.
    logger$h.trace("getting candidate denominations");
    const denominations = await getCandidateWithdrawalDenoms(ws, exchangeBaseUrl);
    logger$h.trace(`got ${denominations.length} candidate denominations`);
    const batchSize = 500;
    let current = 0;
    while (current < denominations.length) {
        const updatedDenominations = [];
        // Do a batch of batchSize
        for (let batchIdx = 0; batchIdx < batchSize && current < denominations.length; batchIdx++, current++) {
            const denom = denominations[current];
            if (denom.verificationStatus === DenominationVerificationStatus.Unverified) {
                logger$h.trace(`Validating denomination (${current + 1}/${denominations.length}) signature of ${denom.denomPubHash}`);
                let valid = false;
                if (ws.insecureTrustExchange) {
                    valid = true;
                }
                else {
                    const res = await ws.cryptoApi.isValidDenom({
                        denom,
                        masterPub: exchangeDetails.masterPublicKey,
                    });
                    valid = res.valid;
                }
                logger$h.trace(`Done validating ${denom.denomPubHash}`);
                if (!valid) {
                    logger$h.warn(`Signature check for denomination h=${denom.denomPubHash} failed`);
                    denom.verificationStatus = DenominationVerificationStatus.VerifiedBad;
                }
                else {
                    denom.verificationStatus =
                        DenominationVerificationStatus.VerifiedGood;
                }
                updatedDenominations.push(denom);
            }
        }
        if (updatedDenominations.length > 0) {
            logger$h.trace("writing denomination batch to db");
            await ws.db
                .mktx((x) => [x.denominations])
                .runReadWrite(async (tx) => {
                for (let i = 0; i < updatedDenominations.length; i++) {
                    const denom = updatedDenominations[i];
                    await tx.denominations.put(denom);
                }
            });
            logger$h.trace("done with DB write");
        }
    }
}
/**
 * Update the information about a reserve that is stored in the wallet
 * by querying the reserve's exchange.
 *
 * If the reserve have funds that are not allocated in a withdrawal group yet
 * and are big enough to withdraw with available denominations,
 * create a new withdrawal group for the remaining amount.
 */
async function queryReserve(ws, withdrawalGroupId, cancellationToken) {
    const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, {
        withdrawalGroupId,
    });
    checkDbInvariant(!!withdrawalGroup);
    if (withdrawalGroup.status !== WithdrawalGroupStatus.QueryingStatus) {
        return { ready: true };
    }
    const reservePub = withdrawalGroup.reservePub;
    const reserveUrl = new URL$1(`reserves/${reservePub}`, withdrawalGroup.exchangeBaseUrl);
    reserveUrl.searchParams.set("timeout_ms", "30000");
    logger$h.info(`querying reserve status via ${reserveUrl}`);
    const resp = await ws.http.get(reserveUrl.href, {
        timeout: getReserveRequestTimeout(),
        cancellationToken,
    });
    const result = await readSuccessResponseJsonOrErrorCode(resp, codecForReserveStatus());
    if (result.isError) {
        if (resp.status === 404 &&
            result.talerErrorResponse.code ===
                TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN) {
            ws.notify({
                type: NotificationType.ReserveNotYetFound,
                reservePub,
            });
            return { ready: false };
        }
        else {
            throwUnexpectedRequestError(resp, result.talerErrorResponse);
        }
    }
    logger$h.trace(`got reserve status ${j2s(result.response)}`);
    await ws.db
        .mktx((x) => [x.withdrawalGroups])
        .runReadWrite(async (tx) => {
        const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
        if (!wg) {
            logger$h.warn(`withdrawal group ${withdrawalGroupId} not found`);
            return;
        }
        wg.status = WithdrawalGroupStatus.Ready;
        await tx.withdrawalGroups.put(wg);
    });
    return { ready: true };
}
var BankStatusResultCode;
(function (BankStatusResultCode) {
    BankStatusResultCode["Done"] = "done";
    BankStatusResultCode["Waiting"] = "waiting";
    BankStatusResultCode["Aborted"] = "aborted";
})(BankStatusResultCode || (BankStatusResultCode = {}));
async function processWithdrawalGroup(ws, withdrawalGroupId, options = {}) {
    logger$h.trace("processing withdrawal group", withdrawalGroupId);
    const withdrawalGroup = await ws.db
        .mktx((x) => [x.withdrawalGroups])
        .runReadOnly(async (tx) => {
        return tx.withdrawalGroups.get(withdrawalGroupId);
    });
    if (!withdrawalGroup) {
        throw Error(`withdrawal group ${withdrawalGroupId} not found`);
    }
    const retryTag = RetryTags.forWithdrawal(withdrawalGroup);
    // We're already running!
    if (ws.activeLongpoll[retryTag]) {
        logger$h.info("withdrawal group already in long-polling, returning!");
        return {
            type: OperationAttemptResultType.Longpoll,
        };
    }
    switch (withdrawalGroup.status) {
        case WithdrawalGroupStatus.RegisteringBank:
            await processReserveBankStatus(ws, withdrawalGroupId);
            return await processWithdrawalGroup(ws, withdrawalGroupId, {
                forceNow: true,
            });
        case WithdrawalGroupStatus.QueryingStatus: {
            const doQueryAsync = async () => {
                if (ws.stopped) {
                    logger$h.info("not long-polling reserve, wallet already stopped");
                    await storeOperationPending(ws, retryTag);
                    return;
                }
                const cts = CancellationToken.create();
                let res = undefined;
                try {
                    ws.activeLongpoll[retryTag] = {
                        cancel: () => {
                            logger$h.info("cancel of reserve longpoll requested");
                            cts.cancel();
                        },
                    };
                    res = await queryReserve(ws, withdrawalGroupId, cts.token);
                }
                catch (e) {
                    await storeOperationError(ws, retryTag, getErrorDetailFromException(e));
                    return;
                }
                delete ws.activeLongpoll[retryTag];
                logger$h.info(`active longpoll keys (2) ${Object.keys(ws.activeLongpoll)}`);
                if (!res.ready) {
                    await storeOperationPending(ws, retryTag);
                }
                ws.latch.trigger();
            };
            doQueryAsync();
            logger$h.info("returning early from withdrawal for long-polling in background");
            return {
                type: OperationAttemptResultType.Longpoll,
            };
        }
        case WithdrawalGroupStatus.WaitConfirmBank: {
            const res = await processReserveBankStatus(ws, withdrawalGroupId);
            switch (res.status) {
                case BankStatusResultCode.Aborted:
                case BankStatusResultCode.Done:
                    return {
                        type: OperationAttemptResultType.Finished,
                        result: undefined,
                    };
                case BankStatusResultCode.Waiting: {
                    return {
                        type: OperationAttemptResultType.Pending,
                        result: undefined,
                    };
                }
            }
            break;
        }
        case WithdrawalGroupStatus.BankAborted: {
            // FIXME
            return {
                type: OperationAttemptResultType.Pending,
                result: undefined,
            };
        }
        case WithdrawalGroupStatus.Finished:
            // We can try to withdraw, nothing needs to be done with the reserve.
            break;
        case WithdrawalGroupStatus.Ready:
            // Continue with the actual withdrawal!
            break;
        default:
            throw new InvariantViolatedError(`unknown reserve record status: ${withdrawalGroup.status}`);
    }
    await ws.exchangeOps.updateExchangeFromUrl(ws, withdrawalGroup.exchangeBaseUrl);
    if (withdrawalGroup.denomsSel.selectedDenoms.length === 0) {
        logger$h.warn("Finishing empty withdrawal group (no denoms)");
        await ws.db
            .mktx((x) => [x.withdrawalGroups])
            .runReadWrite(async (tx) => {
            const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
            if (!wg) {
                return;
            }
            wg.status = WithdrawalGroupStatus.Finished;
            wg.timestampFinish = TalerProtocolTimestamp.now();
            await tx.withdrawalGroups.put(wg);
        });
        return {
            type: OperationAttemptResultType.Finished,
            result: undefined,
        };
    }
    const numTotalCoins = withdrawalGroup.denomsSel.selectedDenoms
        .map((x) => x.count)
        .reduce((a, b) => a + b);
    let work = [];
    for (let i = 0; i < numTotalCoins; i++) {
        work.push(processPlanchetGenerate(ws, withdrawalGroup, i));
    }
    // Generate coins concurrently (parallelism only happens in the crypto API workers)
    await Promise.all(work);
    work = [];
    if (ws.batchWithdrawal) {
        const resp = await processPlanchetExchangeBatchRequest(ws, withdrawalGroup);
        if (!resp) {
            throw Error("unable to do batch withdrawal");
        }
        for (let coinIdx = 0; coinIdx < numTotalCoins; coinIdx++) {
            work.push(processPlanchetVerifyAndStoreCoin(ws, withdrawalGroup, coinIdx, resp.ev_sigs[coinIdx]));
        }
    }
    else {
        for (let coinIdx = 0; coinIdx < numTotalCoins; coinIdx++) {
            const resp = await processPlanchetExchangeRequest(ws, withdrawalGroup, coinIdx);
            if (!resp) {
                continue;
            }
            work.push(processPlanchetVerifyAndStoreCoin(ws, withdrawalGroup, coinIdx, resp));
        }
    }
    await Promise.all(work);
    let numFinished = 0;
    let finishedForFirstTime = false;
    let errorsPerCoin = {};
    await ws.db
        .mktx((x) => [x.coins, x.withdrawalGroups, x.planchets])
        .runReadWrite(async (tx) => {
        const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
        if (!wg) {
            return;
        }
        await tx.planchets.indexes.byGroup
            .iter(withdrawalGroupId)
            .forEach((x) => {
            if (x.withdrawalDone) {
                numFinished++;
            }
            if (x.lastError) {
                errorsPerCoin[x.coinIdx] = x.lastError;
            }
        });
        logger$h.info(`now withdrawn ${numFinished} of ${numTotalCoins} coins`);
        if (wg.timestampFinish === undefined && numFinished === numTotalCoins) {
            finishedForFirstTime = true;
            wg.timestampFinish = TalerProtocolTimestamp.now();
            wg.status = WithdrawalGroupStatus.Finished;
        }
        await tx.withdrawalGroups.put(wg);
    });
    if (numFinished != numTotalCoins) {
        throw TalerError.fromDetail(TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE, {
            errorsPerCoin,
        }, `withdrawal did not finish (${numFinished} / ${numTotalCoins} coins withdrawn)`);
    }
    if (finishedForFirstTime) {
        ws.notify({
            type: NotificationType.WithdrawGroupFinished,
            reservePub: withdrawalGroup.reservePub,
        });
    }
    return {
        type: OperationAttemptResultType.Finished,
        result: undefined,
    };
}
const AGE_MASK_GROUPS = "8:10:12:14:16:18"
    .split(":")
    .map((n) => parseInt(n, 10));
async function getExchangeWithdrawalInfo(ws, exchangeBaseUrl, instructedAmount, ageRestricted) {
    const { exchange, exchangeDetails } = await ws.exchangeOps.updateExchangeFromUrl(ws, exchangeBaseUrl);
    await updateWithdrawalDenoms(ws, exchangeBaseUrl);
    const denoms = await getCandidateWithdrawalDenoms(ws, exchangeBaseUrl);
    const selectedDenoms = selectWithdrawalDenominations(instructedAmount, denoms);
    if (selectedDenoms.selectedDenoms.length === 0) {
        throw Error(`unable to withdraw from ${exchangeBaseUrl}, can't select denominations for instructed amount (${Amounts.stringify(instructedAmount)}`);
    }
    const exchangeWireAccounts = [];
    for (const account of exchangeDetails.wireInfo.accounts) {
        exchangeWireAccounts.push(account.payto_uri);
    }
    const { isTrusted, isAudited } = await ws.exchangeOps.getExchangeTrust(ws, exchange);
    let hasDenomWithAgeRestriction = false;
    let earliestDepositExpiration;
    for (let i = 0; i < selectedDenoms.selectedDenoms.length; i++) {
        const ds = selectedDenoms.selectedDenoms[i];
        // FIXME: Do in one transaction!
        const denom = await ws.db
            .mktx((x) => [x.denominations])
            .runReadOnly(async (tx) => {
            return ws.getDenomInfo(ws, tx, exchangeBaseUrl, ds.denomPubHash);
        });
        checkDbInvariant(!!denom);
        hasDenomWithAgeRestriction =
            hasDenomWithAgeRestriction || denom.denomPub.age_mask > 0;
        const expireDeposit = denom.stampExpireDeposit;
        if (!earliestDepositExpiration) {
            earliestDepositExpiration = expireDeposit;
            continue;
        }
        if (AbsoluteTime.cmp(AbsoluteTime.fromTimestamp(expireDeposit), AbsoluteTime.fromTimestamp(earliestDepositExpiration)) < 0) {
            earliestDepositExpiration = expireDeposit;
        }
    }
    checkLogicInvariant(!!earliestDepositExpiration);
    const possibleDenoms = await ws.db
        .mktx((x) => [x.denominations])
        .runReadOnly(async (tx) => {
        const ds = await tx.denominations.indexes.byExchangeBaseUrl.getAll(exchangeBaseUrl);
        return ds.filter((x) => x.isOffered);
    });
    let versionMatch;
    if (exchangeDetails.protocolVersion) {
        versionMatch = LibtoolVersion.compare(WALLET_EXCHANGE_PROTOCOL_VERSION, exchangeDetails.protocolVersion);
        if (versionMatch &&
            !versionMatch.compatible &&
            versionMatch.currentCmp === -1) {
            logger$h.warn(`wallet's support for exchange protocol version ${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` +
                `(exchange has ${exchangeDetails.protocolVersion}), checking for updates`);
        }
    }
    let tosAccepted = false;
    if (exchangeDetails.termsOfServiceLastEtag) {
        if (exchangeDetails.termsOfServiceAcceptedEtag ===
            exchangeDetails.termsOfServiceLastEtag) {
            tosAccepted = true;
        }
    }
    const ret = {
        earliestDepositExpiration,
        exchangeInfo: exchange,
        exchangeDetails,
        exchangeWireAccounts,
        exchangeVersion: exchangeDetails.protocolVersion || "unknown",
        isAudited,
        isTrusted,
        numOfferedDenoms: possibleDenoms.length,
        selectedDenoms,
        // FIXME: delete this field / replace by something we can display to the user
        trustedAuditorPubs: [],
        versionMatch,
        walletVersion: WALLET_EXCHANGE_PROTOCOL_VERSION,
        termsOfServiceAccepted: tosAccepted,
        withdrawalAmountEffective: Amounts.stringify(selectedDenoms.totalCoinValue),
        withdrawalAmountRaw: Amounts.stringify(instructedAmount),
        // TODO: remove hardcoding, this should be calculated from the denominations info
        // force enabled for testing
        ageRestrictionOptions: hasDenomWithAgeRestriction
            ? AGE_MASK_GROUPS
            : undefined,
    };
    return ret;
}
/**
 * Get more information about a taler://withdraw URI.
 *
 * As side effects, the bank (via the bank integration API) is queried
 * and the exchange suggested by the bank is permanently added
 * to the wallet's list of known exchanges.
 */
async function getWithdrawalDetailsForUri(ws, talerWithdrawUri, opts = {}) {
    logger$h.trace(`getting withdrawal details for URI ${talerWithdrawUri}`);
    const info = await getBankWithdrawalInfo(ws.http, talerWithdrawUri);
    logger$h.trace(`got bank info`);
    if (info.suggestedExchange) {
        // FIXME: right now the exchange gets permanently added,
        // we might want to only temporarily add it.
        try {
            await ws.exchangeOps.updateExchangeFromUrl(ws, info.suggestedExchange);
        }
        catch (e) {
            // We still continued if it failed, as other exchanges might be available.
            // We don't want to fail if the bank-suggested exchange is broken/offline.
            logger$h.trace(`querying bank-suggested exchange (${info.suggestedExchange}) failed`);
        }
    }
    // Extract information about possible exchanges for the withdrawal
    // operation from the database.
    const exchanges = [];
    await ws.db
        .mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
        .runReadOnly(async (tx) => {
        const exchangeRecords = await tx.exchanges.iter().toArray();
        for (const r of exchangeRecords) {
            const details = await ws.exchangeOps.getExchangeDetails(tx, r.baseUrl);
            const denominations = await tx.denominations.indexes.byExchangeBaseUrl
                .iter(r.baseUrl)
                .toArray();
            if (details && denominations) {
                exchanges.push({
                    exchangeBaseUrl: details.exchangeBaseUrl,
                    currency: details.currency,
                    tos: {
                        acceptedVersion: details.termsOfServiceAcceptedEtag,
                        currentVersion: details.termsOfServiceLastEtag,
                        contentType: details.termsOfServiceContentType,
                        content: details.termsOfServiceText,
                    },
                    paytoUris: details.wireInfo.accounts.map((x) => x.payto_uri),
                });
            }
        }
    });
    return {
        amount: Amounts.stringify(info.amount),
        defaultExchangeBaseUrl: info.suggestedExchange,
        possibleExchanges: exchanges,
    };
}
/**
 * Get payto URIs that can be used to fund a withdrawal operation.
 */
async function getFundingPaytoUris(tx, withdrawalGroupId) {
    var _a, _b;
    const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId);
    checkDbInvariant(!!withdrawalGroup);
    const exchangeDetails = await getExchangeDetails(tx, withdrawalGroup.exchangeBaseUrl);
    if (!exchangeDetails) {
        logger$h.error(`exchange ${withdrawalGroup.exchangeBaseUrl} not found`);
        return [];
    }
    const plainPaytoUris = (_b = (_a = exchangeDetails.wireInfo) === null || _a === void 0 ? void 0 : _a.accounts.map((x) => x.payto_uri)) !== null && _b !== void 0 ? _b : [];
    if (!plainPaytoUris) {
        logger$h.error(`exchange ${withdrawalGroup.exchangeBaseUrl} has no wire info`);
        return [];
    }
    return plainPaytoUris.map((x) => addPaytoQueryParams(x, {
        amount: Amounts.stringify(withdrawalGroup.instructedAmount),
        message: `Taler Withdrawal ${withdrawalGroup.reservePub}`,
    }));
}
async function getWithdrawalGroupRecordTx(db, req) {
    return await db
        .mktx((x) => [x.withdrawalGroups])
        .runReadOnly(async (tx) => {
        return tx.withdrawalGroups.get(req.withdrawalGroupId);
    });
}
function getReserveRequestTimeout(r) {
    return { d_ms: 60000 };
}
function getBankStatusUrl(talerWithdrawUri) {
    const uriResult = parseWithdrawUri(talerWithdrawUri);
    if (!uriResult) {
        throw Error(`can't parse withdrawal URL ${talerWithdrawUri}`);
    }
    const url = new URL$1(`withdrawal-operation/${uriResult.withdrawalOperationId}`, uriResult.bankIntegrationApiBaseUrl);
    return url.href;
}
async function registerReserveWithBank(ws, withdrawalGroupId) {
    const withdrawalGroup = await ws.db
        .mktx((x) => [x.withdrawalGroups])
        .runReadOnly(async (tx) => {
        return await tx.withdrawalGroups.get(withdrawalGroupId);
    });
    switch (withdrawalGroup === null || withdrawalGroup === void 0 ? void 0 : withdrawalGroup.status) {
        case WithdrawalGroupStatus.WaitConfirmBank:
        case WithdrawalGroupStatus.RegisteringBank:
            break;
        default:
            return;
    }
    if (withdrawalGroup.wgInfo.withdrawalType != "bank-integrated" /* WithdrawalRecordType.BankIntegrated */) {
        throw Error();
    }
    const bankInfo = withdrawalGroup.wgInfo.bankInfo;
    if (!bankInfo) {
        return;
    }
    const bankStatusUrl = getBankStatusUrl(bankInfo.talerWithdrawUri);
    const reqBody = {
        reserve_pub: withdrawalGroup.reservePub,
        selected_exchange: bankInfo.exchangePaytoUri,
    };
    logger$h.info(`registering reserve with bank: ${j2s(reqBody)}`);
    const httpResp = await ws.http.postJson(bankStatusUrl, reqBody, {
        timeout: getReserveRequestTimeout(),
    });
    await readSuccessResponseJsonOrThrow(httpResp, codecForBankWithdrawalOperationPostResponse());
    await ws.db
        .mktx((x) => [x.withdrawalGroups])
        .runReadWrite(async (tx) => {
        const r = await tx.withdrawalGroups.get(withdrawalGroupId);
        if (!r) {
            return;
        }
        switch (r.status) {
            case WithdrawalGroupStatus.RegisteringBank:
            case WithdrawalGroupStatus.WaitConfirmBank:
                break;
            default:
                return;
        }
        if (r.wgInfo.withdrawalType !== "bank-integrated" /* WithdrawalRecordType.BankIntegrated */) {
            throw Error("invariant failed");
        }
        r.wgInfo.bankInfo.timestampReserveInfoPosted = AbsoluteTime.toTimestamp(AbsoluteTime.now());
        r.status = WithdrawalGroupStatus.WaitConfirmBank;
        await tx.withdrawalGroups.put(r);
    });
    ws.notify({ type: NotificationType.ReserveRegisteredWithBank });
}
async function processReserveBankStatus(ws, withdrawalGroupId) {
    const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, {
        withdrawalGroupId,
    });
    switch (withdrawalGroup === null || withdrawalGroup === void 0 ? void 0 : withdrawalGroup.status) {
        case WithdrawalGroupStatus.WaitConfirmBank:
        case WithdrawalGroupStatus.RegisteringBank:
            break;
        default:
            return {
                status: BankStatusResultCode.Done,
            };
    }
    if (withdrawalGroup.wgInfo.withdrawalType != "bank-integrated" /* WithdrawalRecordType.BankIntegrated */) {
        throw Error("wrong withdrawal record type");
    }
    const bankInfo = withdrawalGroup.wgInfo.bankInfo;
    if (!bankInfo) {
        return {
            status: BankStatusResultCode.Done,
        };
    }
    const bankStatusUrl = getBankStatusUrl(bankInfo.talerWithdrawUri);
    const statusResp = await ws.http.get(bankStatusUrl, {
        timeout: getReserveRequestTimeout(),
    });
    const status = await readSuccessResponseJsonOrThrow(statusResp, codecForWithdrawOperationStatusResponse());
    if (status.aborted) {
        logger$h.info("bank aborted the withdrawal");
        await ws.db
            .mktx((x) => [x.withdrawalGroups])
            .runReadWrite(async (tx) => {
            const r = await tx.withdrawalGroups.get(withdrawalGroupId);
            if (!r) {
                return;
            }
            switch (r.status) {
                case WithdrawalGroupStatus.RegisteringBank:
                case WithdrawalGroupStatus.WaitConfirmBank:
                    break;
                default:
                    return;
            }
            if (r.wgInfo.withdrawalType !== "bank-integrated" /* WithdrawalRecordType.BankIntegrated */) {
                throw Error("invariant failed");
            }
            const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
            r.wgInfo.bankInfo.timestampBankConfirmed = now;
            r.status = WithdrawalGroupStatus.BankAborted;
            await tx.withdrawalGroups.put(r);
        });
        return {
            status: BankStatusResultCode.Aborted,
        };
    }
    // Bank still needs to know our reserve info
    if (!status.selection_done) {
        await registerReserveWithBank(ws, withdrawalGroupId);
        return await processReserveBankStatus(ws, withdrawalGroupId);
    }
    // FIXME: Why do we do this?!
    if (withdrawalGroup.status === WithdrawalGroupStatus.RegisteringBank) {
        await registerReserveWithBank(ws, withdrawalGroupId);
        return await processReserveBankStatus(ws, withdrawalGroupId);
    }
    await ws.db
        .mktx((x) => [x.withdrawalGroups])
        .runReadWrite(async (tx) => {
        const r = await tx.withdrawalGroups.get(withdrawalGroupId);
        if (!r) {
            return;
        }
        // Re-check reserve status within transaction
        switch (r.status) {
            case WithdrawalGroupStatus.RegisteringBank:
            case WithdrawalGroupStatus.WaitConfirmBank:
                break;
            default:
                return;
        }
        if (r.wgInfo.withdrawalType !== "bank-integrated" /* WithdrawalRecordType.BankIntegrated */) {
            throw Error("invariant failed");
        }
        if (status.transfer_done) {
            logger$h.info("withdrawal: transfer confirmed by bank.");
            const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
            r.wgInfo.bankInfo.timestampBankConfirmed = now;
            r.status = WithdrawalGroupStatus.QueryingStatus;
        }
        else {
            logger$h.info("withdrawal: transfer not yet confirmed by bank");
            r.wgInfo.bankInfo.confirmUrl = status.confirm_transfer_url;
            r.senderWire = status.sender_wire;
        }
        await tx.withdrawalGroups.put(r);
    });
    return {
        status: BankStatusResultCode.Done,
    };
}
async function internalCreateWithdrawalGroup(ws, args) {
    var _a;
    const reserveKeyPair = (_a = args.reserveKeyPair) !== null && _a !== void 0 ? _a : (await ws.cryptoApi.createEddsaKeypair({}));
    const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
    const secretSeed = encodeCrock(getRandomBytes(32));
    const canonExchange = canonicalizeBaseUrl(args.exchangeBaseUrl);
    const withdrawalGroupId = encodeCrock(getRandomBytes(32));
    const amount = args.amount;
    await updateWithdrawalDenoms(ws, canonExchange);
    const denoms = await getCandidateWithdrawalDenoms(ws, canonExchange);
    let initialDenomSel;
    const denomSelUid = encodeCrock(getRandomBytes(16));
    if (args.forcedDenomSel) {
        logger$h.warn("using forced denom selection");
        initialDenomSel = selectForcedWithdrawalDenominations(amount, denoms, args.forcedDenomSel);
    }
    else {
        initialDenomSel = selectWithdrawalDenominations(amount, denoms);
    }
    const withdrawalGroup = {
        denomSelUid,
        denomsSel: initialDenomSel,
        exchangeBaseUrl: canonExchange,
        instructedAmount: amount,
        timestampStart: now,
        rawWithdrawalAmount: initialDenomSel.totalWithdrawCost,
        secretSeed,
        reservePriv: reserveKeyPair.priv,
        reservePub: reserveKeyPair.pub,
        status: args.reserveStatus,
        withdrawalGroupId,
        restrictAge: args.restrictAge,
        senderWire: undefined,
        timestampFinish: undefined,
        wgInfo: args.wgInfo,
    };
    const exchangeInfo = await updateExchangeFromUrl(ws, canonExchange);
    const exchangeDetails = exchangeInfo.exchangeDetails;
    if (!exchangeDetails) {
        logger$h.trace(exchangeDetails);
        throw Error("exchange not updated");
    }
    const { isAudited, isTrusted } = await getExchangeTrust(ws, exchangeInfo.exchange);
    await ws.db
        .mktx((x) => [
        x.withdrawalGroups,
        x.reserves,
        x.exchanges,
        x.exchangeDetails,
        x.exchangeTrust,
    ])
        .runReadWrite(async (tx) => {
        await tx.withdrawalGroups.add(withdrawalGroup);
        await tx.reserves.put({
            reservePub: withdrawalGroup.reservePub,
            reservePriv: withdrawalGroup.reservePriv,
        });
        if (!isAudited && !isTrusted) {
            await tx.exchangeTrust.put({
                currency: amount.currency,
                exchangeBaseUrl: canonExchange,
                exchangeMasterPub: exchangeDetails.masterPublicKey,
                uids: [encodeCrock(getRandomBytes(32))],
            });
        }
    });
    return withdrawalGroup;
}
async function acceptWithdrawalFromUri(ws, req) {
    const existingWithdrawalGroup = await ws.db
        .mktx((x) => [x.withdrawalGroups])
        .runReadOnly(async (tx) => {
        return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(req.talerWithdrawUri);
    });
    if (existingWithdrawalGroup) {
        let url;
        if (existingWithdrawalGroup.wgInfo.withdrawalType ===
            "bank-integrated" /* WithdrawalRecordType.BankIntegrated */) {
            url = existingWithdrawalGroup.wgInfo.bankInfo.confirmUrl;
        }
        return {
            reservePub: existingWithdrawalGroup.reservePub,
            confirmTransferUrl: url,
            transactionId: makeEventId(TransactionType.Withdrawal, existingWithdrawalGroup.withdrawalGroupId),
        };
    }
    await updateExchangeFromUrl(ws, req.selectedExchange);
    const withdrawInfo = await getBankWithdrawalInfo(ws.http, req.talerWithdrawUri);
    const exchangePaytoUri = await getExchangePaytoUri(ws, req.selectedExchange, withdrawInfo.wireTypes);
    const withdrawalGroup = await internalCreateWithdrawalGroup(ws, {
        amount: withdrawInfo.amount,
        exchangeBaseUrl: req.selectedExchange,
        wgInfo: {
            withdrawalType: "bank-integrated" /* WithdrawalRecordType.BankIntegrated */,
            bankInfo: {
                exchangePaytoUri,
                talerWithdrawUri: req.talerWithdrawUri,
                confirmUrl: withdrawInfo.confirmTransferUrl,
                timestampBankConfirmed: undefined,
                timestampReserveInfoPosted: undefined,
            },
        },
        restrictAge: req.restrictAge,
        forcedDenomSel: req.forcedDenomSel,
        reserveStatus: WithdrawalGroupStatus.RegisteringBank,
    });
    const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
    // We do this here, as the reserve should be registered before we return,
    // so that we can redirect the user to the bank's status page.
    await processReserveBankStatus(ws, withdrawalGroupId);
    const processedWithdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, {
        withdrawalGroupId,
    });
    if ((processedWithdrawalGroup === null || processedWithdrawalGroup === void 0 ? void 0 : processedWithdrawalGroup.status) === WithdrawalGroupStatus.BankAborted) {
        throw TalerError.fromDetail(TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK, {});
    }
    // Start withdrawal in the background.
    await processWithdrawalGroup(ws, withdrawalGroupId, { forceNow: true }).catch((err) => {
        logger$h.error("Processing withdrawal (after creation) failed:", err);
    });
    return {
        reservePub: withdrawalGroup.reservePub,
        confirmTransferUrl: withdrawInfo.confirmTransferUrl,
        transactionId: makeEventId(TransactionType.Withdrawal, withdrawalGroupId),
    };
}
/**
 * Create a manual withdrawal operation.
 *
 * Adds the corresponding exchange as a trusted exchange if it is neither
 * audited nor trusted already.
 *
 * Asynchronously starts the withdrawal.
 */
async function createManualWithdrawal(ws, req) {
    const withdrawalGroup = await internalCreateWithdrawalGroup(ws, {
        amount: Amounts.jsonifyAmount(req.amount),
        wgInfo: {
            withdrawalType: "bank-manual" /* WithdrawalRecordType.BankManual */,
        },
        exchangeBaseUrl: req.exchangeBaseUrl,
        forcedDenomSel: req.forcedDenomSel,
        restrictAge: req.restrictAge,
        reserveStatus: WithdrawalGroupStatus.QueryingStatus,
    });
    const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
    const exchangePaytoUris = await ws.db
        .mktx((x) => [
        x.withdrawalGroups,
        x.exchanges,
        x.exchangeDetails,
        x.exchangeTrust,
    ])
        .runReadWrite(async (tx) => {
        return await getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId);
    });
    // Start withdrawal in the background (do not await!)
    // FIXME: We could also interrupt the task look if it is waiting and
    // rely on retry handling to re-process the withdrawal group.
    runOperationWithErrorReporting(ws, RetryTags.forWithdrawal(withdrawalGroup), async () => {
        return await processWithdrawalGroup(ws, withdrawalGroupId, {
            forceNow: true,
        });
    });
    return {
        reservePub: withdrawalGroup.reservePub,
        exchangePaytoUris: exchangePaytoUris,
        transactionId: makeEventId(TransactionType.Withdrawal, withdrawalGroupId),
    };
}

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$g = new Logger("refresh.ts");
/**
 * 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 denomMap = Object.fromEntries(denoms.map((x) => [x.denomPubHash, x]));
    const withdrawDenoms = selectWithdrawalDenominations(withdrawAmount, denoms);
    const resultingAmount = Amounts.add(Amounts.getZero(withdrawAmount.currency), ...withdrawDenoms.selectedDenoms.map((d) => Amounts.mult(DenominationRecord.getValue(denomMap[d.denomPubHash]), d.count).amount)).amount;
    const totalCost = Amounts.sub(amountLeft, resultingAmount).amount;
    logger$g.trace(`total refresh cost for ${amountToPretty(amountLeft)} is ${amountToPretty(totalCost)}`);
    return totalCost;
}
function updateGroupStatus(rg) {
    const allDone = fnutil.all(rg.statusPerCoin, (x) => x === RefreshCoinStatus.Finished || x === RefreshCoinStatus.Frozen);
    const anyFrozen = fnutil.any(rg.statusPerCoin, (x) => x === RefreshCoinStatus.Frozen);
    if (allDone) {
        if (anyFrozen) {
            rg.frozen = true;
        }
        else {
            rg.timestampFinished = AbsoluteTime.toTimestamp(AbsoluteTime.now());
            rg.operationStatus = OperationStatus.Finished;
        }
    }
}
/**
 * Create a refresh session for one particular coin inside a refresh group.
 */
async function refreshCreateSession(ws, refreshGroupId, coinIndex) {
    logger$g.trace(`creating refresh session for coin ${coinIndex} in refresh group ${refreshGroupId}`);
    const d = await ws.db
        .mktx((x) => [x.refreshGroups, x.coins])
        .runReadWrite(async (tx) => {
        const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
        if (!refreshGroup) {
            return;
        }
        if (refreshGroup.statusPerCoin[coinIndex] === RefreshCoinStatus.Finished) {
            return;
        }
        const existingRefreshSession = refreshGroup.refreshSessionPerCoin[coinIndex];
        if (existingRefreshSession) {
            return;
        }
        const oldCoinPub = refreshGroup.oldCoinPubs[coinIndex];
        const coin = await tx.coins.get(oldCoinPub);
        if (!coin) {
            throw Error("Can't refresh, coin not found");
        }
        return { refreshGroup, coin };
    });
    if (!d) {
        return;
    }
    const { refreshGroup, coin } = d;
    const { exchange } = await updateExchangeFromUrl(ws, coin.exchangeBaseUrl);
    if (!exchange) {
        throw Error("db inconsistent: exchange of coin not found");
    }
    // FIXME: use helper functions from withdraw.ts
    // to update and filter withdrawable denoms.
    const { availableAmount, availableDenoms } = await ws.db
        .mktx((x) => [x.denominations])
        .runReadOnly(async (tx) => {
        const oldDenom = await ws.getDenomInfo(ws, tx, exchange.baseUrl, coin.denomPubHash);
        if (!oldDenom) {
            throw Error("db inconsistent: denomination for coin not found");
        }
        // FIXME: use an index here, based on the withdrawal expiration time.
        const availableDenoms = await tx.denominations.indexes.byExchangeBaseUrl
            .iter(exchange.baseUrl)
            .toArray();
        const availableAmount = Amounts.sub(refreshGroup.inputPerCoin[coinIndex], oldDenom.feeRefresh).amount;
        return { availableAmount, availableDenoms };
    });
    const newCoinDenoms = selectWithdrawalDenominations(availableAmount, availableDenoms);
    if (newCoinDenoms.selectedDenoms.length === 0) {
        logger$g.trace(`not refreshing, available amount ${amountToPretty(availableAmount)} too small`);
        await ws.db
            .mktx((x) => [x.coins, x.refreshGroups])
            .runReadWrite(async (tx) => {
            const rg = await tx.refreshGroups.get(refreshGroupId);
            if (!rg) {
                return;
            }
            rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Finished;
            updateGroupStatus(rg);
            await tx.refreshGroups.put(rg);
        });
        ws.notify({ type: NotificationType.RefreshUnwarranted });
        return;
    }
    const sessionSecretSeed = encodeCrock(getRandomBytes(64));
    // Store refresh session for this coin in the database.
    await ws.db
        .mktx((x) => [x.refreshGroups, x.coins])
        .runReadWrite(async (tx) => {
        const rg = await tx.refreshGroups.get(refreshGroupId);
        if (!rg) {
            return;
        }
        if (rg.refreshSessionPerCoin[coinIndex]) {
            return;
        }
        rg.refreshSessionPerCoin[coinIndex] = {
            norevealIndex: undefined,
            sessionSecretSeed: sessionSecretSeed,
            newDenoms: newCoinDenoms.selectedDenoms.map((x) => ({
                count: x.count,
                denomPubHash: x.denomPubHash,
            })),
            amountRefreshOutput: newCoinDenoms.totalCoinValue,
        };
        await tx.refreshGroups.put(rg);
    });
    logger$g.info(`created refresh session for coin #${coinIndex} in ${refreshGroupId}`);
    ws.notify({ type: NotificationType.RefreshStarted });
}
function getRefreshRequestTimeout(rg) {
    return Duration.fromSpec({
        seconds: 5,
    });
}
async function refreshMelt(ws, refreshGroupId, coinIndex) {
    const d = await ws.db
        .mktx((x) => [x.refreshGroups, x.coins, x.denominations])
        .runReadWrite(async (tx) => {
        const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
        if (!refreshGroup) {
            return;
        }
        const refreshSession = refreshGroup.refreshSessionPerCoin[coinIndex];
        if (!refreshSession) {
            return;
        }
        if (refreshSession.norevealIndex !== undefined) {
            return;
        }
        const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]);
        checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
        const oldDenom = await ws.getDenomInfo(ws, tx, oldCoin.exchangeBaseUrl, oldCoin.denomPubHash);
        checkDbInvariant(!!oldDenom, "denomination for melted coin doesn't exist");
        const newCoinDenoms = [];
        for (const dh of refreshSession.newDenoms) {
            const newDenom = await ws.getDenomInfo(ws, tx, oldCoin.exchangeBaseUrl, dh.denomPubHash);
            checkDbInvariant(!!newDenom, "new denomination for refresh not in database");
            newCoinDenoms.push({
                count: dh.count,
                denomPub: newDenom.denomPub,
                denomPubHash: newDenom.denomPubHash,
                feeWithdraw: newDenom.feeWithdraw,
                value: newDenom.value,
            });
        }
        return { newCoinDenoms, oldCoin, oldDenom, refreshGroup, refreshSession };
    });
    if (!d) {
        return;
    }
    const { newCoinDenoms, oldCoin, oldDenom, refreshGroup, refreshSession } = d;
    let exchangeProtocolVersion;
    switch (d.oldDenom.denomPub.cipher) {
        case DenomKeyType.Rsa: {
            exchangeProtocolVersion = ExchangeProtocolVersion.V12;
            break;
        }
        default:
            throw Error("unsupported key type");
    }
    const derived = await ws.cryptoApi.deriveRefreshSession({
        exchangeProtocolVersion,
        kappa: 3,
        meltCoinDenomPubHash: oldCoin.denomPubHash,
        meltCoinPriv: oldCoin.coinPriv,
        meltCoinPub: oldCoin.coinPub,
        feeRefresh: oldDenom.feeRefresh,
        meltCoinMaxAge: oldCoin.maxAge,
        meltCoinAgeCommitmentProof: oldCoin.ageCommitmentProof,
        newCoinDenoms,
        sessionSecretSeed: refreshSession.sessionSecretSeed,
    });
    const reqUrl = new URL$1(`coins/${oldCoin.coinPub}/melt`, oldCoin.exchangeBaseUrl);
    let maybeAch;
    if (oldCoin.ageCommitmentProof) {
        maybeAch = AgeRestriction.hashCommitment(oldCoin.ageCommitmentProof.commitment);
    }
    const meltReqBody = {
        coin_pub: oldCoin.coinPub,
        confirm_sig: derived.confirmSig,
        denom_pub_hash: oldCoin.denomPubHash,
        denom_sig: oldCoin.denomSig,
        rc: derived.hash,
        value_with_fee: Amounts.stringify(derived.meltValueWithFee),
        age_commitment_hash: maybeAch,
    };
    const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => {
        return await ws.http.postJson(reqUrl.href, meltReqBody, {
            timeout: getRefreshRequestTimeout(),
        });
    });
    if (resp.status === HttpStatusCode.NotFound) {
        const errDetails = await readUnexpectedResponseDetails(resp);
        await ws.db
            .mktx((x) => [x.refreshGroups])
            .runReadWrite(async (tx) => {
            const rg = await tx.refreshGroups.get(refreshGroupId);
            if (!rg) {
                return;
            }
            if (rg.timestampFinished) {
                return;
            }
            if (rg.statusPerCoin[coinIndex] !== RefreshCoinStatus.Pending) {
                return;
            }
            rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Frozen;
            rg.lastErrorPerCoin[coinIndex] = errDetails;
            updateGroupStatus(rg);
            await tx.refreshGroups.put(rg);
        });
        return;
    }
    const meltResponse = await readSuccessResponseJsonOrThrow(resp, codecForExchangeMeltResponse());
    const norevealIndex = meltResponse.noreveal_index;
    refreshSession.norevealIndex = norevealIndex;
    await ws.db
        .mktx((x) => [x.refreshGroups])
        .runReadWrite(async (tx) => {
        const rg = await tx.refreshGroups.get(refreshGroupId);
        if (!rg) {
            return;
        }
        if (rg.timestampFinished) {
            return;
        }
        const rs = rg.refreshSessionPerCoin[coinIndex];
        if (!rs) {
            return;
        }
        if (rs.norevealIndex !== undefined) {
            return;
        }
        rs.norevealIndex = norevealIndex;
        await tx.refreshGroups.put(rg);
    });
    ws.notify({
        type: NotificationType.RefreshMelted,
    });
}
async function assembleRefreshRevealRequest(args) {
    var _a;
    const { derived, norevealIndex, cryptoApi, oldCoinPriv, oldCoinPub, newDenoms, } = args;
    const privs = Array.from(derived.transferPrivs);
    privs.splice(norevealIndex, 1);
    const planchets = derived.planchetsForGammas[norevealIndex];
    if (!planchets) {
        throw Error("refresh index error");
    }
    const newDenomsFlat = [];
    const linkSigs = [];
    for (let i = 0; i < newDenoms.length; i++) {
        const dsel = newDenoms[i];
        for (let j = 0; j < dsel.count; j++) {
            const newCoinIndex = linkSigs.length;
            const linkSig = await cryptoApi.signCoinLink({
                coinEv: planchets[newCoinIndex].coinEv,
                newDenomHash: dsel.denomPubHash,
                oldCoinPriv: oldCoinPriv,
                oldCoinPub: oldCoinPub,
                transferPub: derived.transferPubs[norevealIndex],
            });
            linkSigs.push(linkSig.sig);
            newDenomsFlat.push(dsel.denomPubHash);
        }
    }
    const req = {
        coin_evs: planchets.map((x) => x.coinEv),
        new_denoms_h: newDenomsFlat,
        transfer_privs: privs,
        transfer_pub: derived.transferPubs[norevealIndex],
        link_sigs: linkSigs,
        old_age_commitment: (_a = args.oldAgeCommitment) === null || _a === void 0 ? void 0 : _a.publicKeys,
    };
    return req;
}
async function refreshReveal(ws, refreshGroupId, coinIndex) {
    var _a;
    logger$g.info(`doing refresh reveal for ${refreshGroupId} (old coin ${coinIndex})`);
    const d = await ws.db
        .mktx((x) => [x.refreshGroups, x.coins, x.denominations])
        .runReadOnly(async (tx) => {
        const refreshGroup = await tx.refreshGroups.get(refreshGroupId);
        if (!refreshGroup) {
            return;
        }
        const refreshSession = refreshGroup.refreshSessionPerCoin[coinIndex];
        if (!refreshSession) {
            return;
        }
        const norevealIndex = refreshSession.norevealIndex;
        if (norevealIndex === undefined) {
            throw Error("can't reveal without melting first");
        }
        const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]);
        checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
        const oldDenom = await ws.getDenomInfo(ws, tx, oldCoin.exchangeBaseUrl, oldCoin.denomPubHash);
        checkDbInvariant(!!oldDenom, "denomination for melted coin doesn't exist");
        const newCoinDenoms = [];
        for (const dh of refreshSession.newDenoms) {
            const newDenom = await ws.getDenomInfo(ws, tx, oldCoin.exchangeBaseUrl, dh.denomPubHash);
            checkDbInvariant(!!newDenom, "new denomination for refresh not in database");
            newCoinDenoms.push({
                count: dh.count,
                denomPub: newDenom.denomPub,
                denomPubHash: newDenom.denomPubHash,
                feeWithdraw: newDenom.feeWithdraw,
                value: newDenom.value,
            });
        }
        return {
            oldCoin,
            oldDenom,
            newCoinDenoms,
            refreshSession,
            refreshGroup,
            norevealIndex,
        };
    });
    if (!d) {
        return;
    }
    const { oldCoin, oldDenom, newCoinDenoms, refreshSession, refreshGroup, norevealIndex, } = d;
    let exchangeProtocolVersion;
    switch (d.oldDenom.denomPub.cipher) {
        case DenomKeyType.Rsa: {
            exchangeProtocolVersion = ExchangeProtocolVersion.V12;
            break;
        }
        default:
            throw Error("unsupported key type");
    }
    const derived = await ws.cryptoApi.deriveRefreshSession({
        exchangeProtocolVersion,
        kappa: 3,
        meltCoinDenomPubHash: oldCoin.denomPubHash,
        meltCoinPriv: oldCoin.coinPriv,
        meltCoinPub: oldCoin.coinPub,
        feeRefresh: oldDenom.feeRefresh,
        newCoinDenoms,
        meltCoinMaxAge: oldCoin.maxAge,
        meltCoinAgeCommitmentProof: oldCoin.ageCommitmentProof,
        sessionSecretSeed: refreshSession.sessionSecretSeed,
    });
    const reqUrl = new URL$1(`refreshes/${derived.hash}/reveal`, oldCoin.exchangeBaseUrl);
    const req = await assembleRefreshRevealRequest({
        cryptoApi: ws.cryptoApi,
        derived,
        newDenoms: newCoinDenoms,
        norevealIndex: norevealIndex,
        oldCoinPriv: oldCoin.coinPriv,
        oldCoinPub: oldCoin.coinPub,
        oldAgeCommitment: (_a = oldCoin.ageCommitmentProof) === null || _a === void 0 ? void 0 : _a.commitment,
    });
    const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => {
        return await ws.http.postJson(reqUrl.href, req, {
            timeout: getRefreshRequestTimeout(),
        });
    });
    const reveal = await readSuccessResponseJsonOrThrow(resp, codecForExchangeRevealResponse());
    const coins = [];
    for (let i = 0; i < refreshSession.newDenoms.length; i++) {
        const ncd = newCoinDenoms[i];
        for (let j = 0; j < refreshSession.newDenoms[i].count; j++) {
            const newCoinIndex = coins.length;
            const pc = derived.planchetsForGammas[norevealIndex][newCoinIndex];
            if (ncd.denomPub.cipher !== DenomKeyType.Rsa) {
                throw Error("cipher unsupported");
            }
            const evSig = reveal.ev_sigs[newCoinIndex].ev_sig;
            const denomSig = await ws.cryptoApi.unblindDenominationSignature({
                planchet: {
                    blindingKey: pc.blindingKey,
                    denomPub: ncd.denomPub,
                },
                evSig,
            });
            const coin = {
                blindingKey: pc.blindingKey,
                coinPriv: pc.coinPriv,
                coinPub: pc.coinPub,
                currentAmount: ncd.value,
                denomPubHash: ncd.denomPubHash,
                denomSig,
                exchangeBaseUrl: oldCoin.exchangeBaseUrl,
                status: CoinStatus.Fresh,
                coinSource: {
                    type: CoinSourceType.Refresh,
                    oldCoinPub: refreshGroup.oldCoinPubs[coinIndex],
                },
                coinEvHash: pc.coinEvHash,
                maxAge: pc.maxAge,
                ageCommitmentProof: pc.ageCommitmentProof,
                allocation: undefined,
            };
            coins.push(coin);
        }
    }
    await ws.db
        .mktx((x) => [
        x.coins,
        x.denominations,
        x.coinAvailability,
        x.refreshGroups,
    ])
        .runReadWrite(async (tx) => {
        const rg = await tx.refreshGroups.get(refreshGroupId);
        if (!rg) {
            logger$g.warn("no refresh session found");
            return;
        }
        const rs = rg.refreshSessionPerCoin[coinIndex];
        if (!rs) {
            return;
        }
        rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Finished;
        updateGroupStatus(rg);
        for (const coin of coins) {
            await makeCoinAvailable(ws, tx, coin);
        }
        await tx.refreshGroups.put(rg);
    });
    logger$g.trace("refresh finished (end of reveal)");
    ws.notify({
        type: NotificationType.RefreshRevealed,
    });
}
async function processRefreshGroup(ws, refreshGroupId, options = {}) {
    logger$g.info(`processing refresh group ${refreshGroupId}`);
    const refreshGroup = await ws.db
        .mktx((x) => [x.refreshGroups])
        .runReadOnly(async (tx) => tx.refreshGroups.get(refreshGroupId));
    if (!refreshGroup) {
        return {
            type: OperationAttemptResultType.Finished,
            result: undefined,
        };
    }
    if (refreshGroup.timestampFinished) {
        return {
            type: OperationAttemptResultType.Finished,
            result: undefined,
        };
    }
    // Process refresh sessions of the group in parallel.
    logger$g.trace("processing refresh sessions for old coins");
    const ps = refreshGroup.oldCoinPubs.map((x, i) => processRefreshSession(ws, refreshGroupId, i).catch((x) => {
        if (x instanceof CryptoApiStoppedError) {
            logger$g.info("crypto API stopped while processing refresh group, probably the wallet is currently shutting down.");
        }
        else if (x instanceof TalerError) {
            logger$g.warn("process refresh session got exception (TalerError)");
            logger$g.warn(`exc ${x}`);
            logger$g.warn(`exc stack ${x.stack}`);
            logger$g.warn(`error detail: ${j2s(x.errorDetail)}`);
        }
        else {
            logger$g.warn("process refresh session got exception");
            logger$g.warn(`exc ${x}`);
            logger$g.warn(`exc stack ${x.stack}`);
        }
    }));
    try {
        logger$g.trace("waiting for refreshes");
        await Promise.all(ps);
        logger$g.trace("refresh finished");
    }
    catch (e) {
        logger$g.warn("process refresh sessions got exception");
        logger$g.warn(`exception: ${e}`);
    }
    return {
        type: OperationAttemptResultType.Finished,
        result: undefined,
    };
}
async function processRefreshSession(ws, refreshGroupId, coinIndex) {
    logger$g.info(`processing refresh session for coin ${coinIndex} of group ${refreshGroupId}`);
    let refreshGroup = await ws.db
        .mktx((x) => [x.refreshGroups])
        .runReadOnly(async (tx) => {
        return tx.refreshGroups.get(refreshGroupId);
    });
    if (!refreshGroup) {
        return;
    }
    if (refreshGroup.statusPerCoin[coinIndex] === RefreshCoinStatus.Finished) {
        return;
    }
    if (!refreshGroup.refreshSessionPerCoin[coinIndex]) {
        await refreshCreateSession(ws, refreshGroupId, coinIndex);
        refreshGroup = await ws.db
            .mktx((x) => [x.refreshGroups])
            .runReadOnly(async (tx) => {
            return tx.refreshGroups.get(refreshGroupId);
        });
        if (!refreshGroup) {
            return;
        }
    }
    const refreshSession = refreshGroup.refreshSessionPerCoin[coinIndex];
    if (!refreshSession) {
        if (refreshGroup.statusPerCoin[coinIndex] !== RefreshCoinStatus.Finished) {
            throw Error("BUG: refresh session was not created and coin not marked as finished");
        }
        return;
    }
    if (refreshSession.norevealIndex === undefined) {
        await refreshMelt(ws, refreshGroupId, coinIndex);
    }
    await refreshReveal(ws, refreshGroupId, coinIndex);
}
/**
 * Create a refresh group for a list of coins.
 *
 * Refreshes the remaining amount on the coin, effectively capturing the remaining
 * value in the refresh group.
 *
 * The caller must ensure that
 * the remaining amount was updated correctly before the coin was deposited or
 * credited.
 *
 * The caller must also ensure that the coins that should be refreshed exist
 * in the current database transaction.
 */
async function createRefreshGroup(ws, tx, oldCoinPubs, reason) {
    const refreshGroupId = encodeCrock(getRandomBytes(32));
    const inputPerCoin = [];
    const estimatedOutputPerCoin = [];
    const denomsPerExchange = {};
    const getDenoms = async (exchangeBaseUrl) => {
        if (denomsPerExchange[exchangeBaseUrl]) {
            return denomsPerExchange[exchangeBaseUrl];
        }
        const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
            .iter(exchangeBaseUrl)
            .filter((x) => {
            return isWithdrawableDenom(x);
        });
        denomsPerExchange[exchangeBaseUrl] = allDenoms;
        return allDenoms;
    };
    for (const ocp of oldCoinPubs) {
        const coin = await tx.coins.get(ocp.coinPub);
        checkDbInvariant(!!coin, "coin must be in database");
        const denom = await ws.getDenomInfo(ws, tx, coin.exchangeBaseUrl, coin.denomPubHash);
        checkDbInvariant(!!denom, "denomination for existing coin must be in database");
        switch (coin.status) {
            case CoinStatus.Dormant:
                break;
            case CoinStatus.Fresh: {
                coin.status = CoinStatus.Dormant;
                const coinAv = await tx.coinAvailability.get([
                    coin.exchangeBaseUrl,
                    coin.denomPubHash,
                    coin.maxAge,
                ]);
                checkDbInvariant(!!coinAv);
                checkDbInvariant(coinAv.freshCoinCount > 0);
                coinAv.freshCoinCount--;
                await tx.coinAvailability.put(coinAv);
                break;
            }
            case CoinStatus.FreshSuspended: {
                // For suspended coins, we don't have to adjust coin
                // availability, as they are not counted as available.
                coin.status = CoinStatus.Dormant;
                break;
            }
            default:
                assertUnreachable(coin.status);
        }
        const refreshAmount = coin.currentAmount;
        inputPerCoin.push(refreshAmount);
        coin.currentAmount = Amounts.getZero(refreshAmount.currency);
        await tx.coins.put(coin);
        const denoms = await getDenoms(coin.exchangeBaseUrl);
        const cost = getTotalRefreshCost(denoms, denom, refreshAmount);
        const output = Amounts.sub(refreshAmount, cost).amount;
        estimatedOutputPerCoin.push(output);
    }
    const refreshGroup = {
        operationStatus: OperationStatus.Pending,
        timestampFinished: undefined,
        statusPerCoin: oldCoinPubs.map(() => RefreshCoinStatus.Pending),
        lastErrorPerCoin: {},
        oldCoinPubs: oldCoinPubs.map((x) => x.coinPub),
        reason,
        refreshGroupId,
        refreshSessionPerCoin: oldCoinPubs.map(() => undefined),
        inputPerCoin,
        estimatedOutputPerCoin,
        timestampCreated: TalerProtocolTimestamp.now(),
    };
    if (oldCoinPubs.length == 0) {
        logger$g.warn("created refresh group with zero coins");
        refreshGroup.timestampFinished = TalerProtocolTimestamp.now();
        refreshGroup.operationStatus = OperationStatus.Finished;
    }
    await tx.refreshGroups.put(refreshGroup);
    logger$g.info(`created refresh group ${refreshGroupId}`);
    processRefreshGroup(ws, refreshGroupId).catch((e) => {
        if (e instanceof CryptoApiStoppedError) {
            return;
        }
        logger$g.warn(`processing refresh group ${refreshGroupId} failed: ${e}`);
    });
    return {
        refreshGroupId,
    };
}
/**
 * Timestamp after which the wallet would do the next check for an auto-refresh.
 */
function getAutoRefreshCheckThreshold(d) {
    const expireWithdraw = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw);
    const expireDeposit = AbsoluteTime.fromTimestamp(d.stampExpireDeposit);
    const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit);
    const deltaDiv = durationMul(delta, 0.75);
    return AbsoluteTime.addDuration(expireWithdraw, deltaDiv);
}
/**
 * Timestamp after which the wallet would do an auto-refresh.
 */
function getAutoRefreshExecuteThreshold(d) {
    const expireWithdraw = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw);
    const expireDeposit = AbsoluteTime.fromTimestamp(d.stampExpireDeposit);
    const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit);
    const deltaDiv = durationMul(delta, 0.5);
    return AbsoluteTime.addDuration(expireWithdraw, deltaDiv);
}
async function autoRefresh(ws, exchangeBaseUrl) {
    logger$g.info(`doing auto-refresh check for '${exchangeBaseUrl}'`);
    // We must make sure that the exchange is up-to-date so that
    // can refresh into new denominations.
    await updateExchangeFromUrl(ws, exchangeBaseUrl, {
        forceNow: true,
    });
    let minCheckThreshold = AbsoluteTime.addDuration(AbsoluteTime.now(), durationFromSpec({ days: 1 }));
    await ws.db
        .mktx((x) => [
        x.coins,
        x.denominations,
        x.coinAvailability,
        x.refreshGroups,
        x.exchanges,
    ])
        .runReadWrite(async (tx) => {
        const exchange = await tx.exchanges.get(exchangeBaseUrl);
        if (!exchange) {
            return;
        }
        const coins = await tx.coins.indexes.byBaseUrl
            .iter(exchangeBaseUrl)
            .toArray();
        const refreshCoins = [];
        for (const coin of coins) {
            if (coin.status !== CoinStatus.Fresh) {
                continue;
            }
            const denom = await tx.denominations.get([
                exchangeBaseUrl,
                coin.denomPubHash,
            ]);
            if (!denom) {
                logger$g.warn("denomination not in database");
                continue;
            }
            const executeThreshold = getAutoRefreshExecuteThreshold(denom);
            if (AbsoluteTime.isExpired(executeThreshold)) {
                refreshCoins.push(coin);
            }
            else {
                const checkThreshold = getAutoRefreshCheckThreshold(denom);
                minCheckThreshold = AbsoluteTime.min(minCheckThreshold, checkThreshold);
            }
        }
        if (refreshCoins.length > 0) {
            const res = await createRefreshGroup(ws, tx, refreshCoins, RefreshReason.Scheduled);
            logger$g.info(`created refresh group for auto-refresh (${res.refreshGroupId})`);
        }
        logger$g.info(`current wallet time: ${AbsoluteTime.toIsoString(AbsoluteTime.now())}`);
        logger$g.info(`next refresh check at ${AbsoluteTime.toIsoString(minCheckThreshold)}`);
        exchange.nextRefreshCheck = AbsoluteTime.toTimestamp(minCheckThreshold);
        await tx.exchanges.put(exchange);
    });
    return OperationAttemptResult.finishedEmpty();
}

/*
 This file is part of GNU Taler
 (C) 2019-2022 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * Logger.
 */
const logger$f = new Logger("pay.ts");
/**
 * Compute the total cost of a payment to the customer.
 *
 * This includes the amount taken by the merchant, fees (wire/deposit) contributed
 * by the customer, refreshing fees, fees for withdraw-after-refresh and "trimmings"
 * of coins that are too small to spend.
 */
async function getTotalPaymentCost(ws, pcs) {
    return ws.db
        .mktx((x) => [x.coins, x.denominations])
        .runReadOnly(async (tx) => {
        const costs = [];
        for (let i = 0; i < pcs.coinPubs.length; i++) {
            const coin = await tx.coins.get(pcs.coinPubs[i]);
            if (!coin) {
                throw Error("can't calculate payment cost, coin not found");
            }
            const denom = await tx.denominations.get([
                coin.exchangeBaseUrl,
                coin.denomPubHash,
            ]);
            if (!denom) {
                throw Error("can't calculate payment cost, denomination for coin not found");
            }
            const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
                .iter(coin.exchangeBaseUrl)
                .filter((x) => Amounts.isSameCurrency(DenominationRecord.getValue(x), pcs.coinContributions[i]));
            const amountLeft = Amounts.sub(DenominationRecord.getValue(denom), pcs.coinContributions[i]).amount;
            const refreshCost = getTotalRefreshCost(allDenoms, DenominationRecord.toDenomInfo(denom), amountLeft);
            costs.push(pcs.coinContributions[i]);
            costs.push(refreshCost);
        }
        const zero = Amounts.getZero(pcs.paymentAmount.currency);
        return Amounts.sum([zero, ...costs]).amount;
    });
}
/**
 * Record all information that is necessary to
 * pay for a proposal in the wallet's database.
 */
async function recordConfirmPay(ws, proposal, coinSelection, coinDepositPermissions, sessionIdOverride) {
    const d = proposal.download;
    if (!d) {
        throw Error("proposal is in invalid state");
    }
    let sessionId;
    if (sessionIdOverride) {
        sessionId = sessionIdOverride;
    }
    else {
        sessionId = proposal.downloadSessionId;
    }
    logger$f.trace(`recording payment on ${proposal.orderId} with session ID ${sessionId}`);
    const payCostInfo = await getTotalPaymentCost(ws, coinSelection);
    const t = {
        abortStatus: AbortStatus.None,
        download: d,
        lastSessionId: sessionId,
        payCoinSelection: coinSelection,
        payCoinSelectionUid: encodeCrock(getRandomBytes(32)),
        totalPayCost: payCostInfo,
        coinDepositPermissions,
        timestampAccept: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
        timestampLastRefundStatus: undefined,
        proposalId: proposal.proposalId,
        refundQueryRequested: false,
        timestampFirstSuccessfulPay: undefined,
        autoRefundDeadline: undefined,
        refundAwaiting: undefined,
        paymentSubmitPending: true,
        refunds: {},
        merchantPaySig: undefined,
        noncePriv: proposal.noncePriv,
        noncePub: proposal.noncePub,
    };
    await ws.db
        .mktx((x) => [
        x.proposals,
        x.purchases,
        x.coins,
        x.refreshGroups,
        x.denominations,
        x.coinAvailability,
    ])
        .runReadWrite(async (tx) => {
        const p = await tx.proposals.get(proposal.proposalId);
        if (p) {
            p.proposalStatus = ProposalStatus.Accepted;
            await tx.proposals.put(p);
        }
        await tx.purchases.put(t);
        await spendCoins(ws, tx, {
            allocationId: `proposal:${t.proposalId}`,
            coinPubs: coinSelection.coinPubs,
            contributions: coinSelection.coinContributions,
            refreshReason: RefreshReason.PayMerchant,
        });
    });
    ws.notify({
        type: NotificationType.ProposalAccepted,
        proposalId: proposal.proposalId,
    });
    return t;
}
async function failProposalPermanently(ws, proposalId, err) {
    await ws.db
        .mktx((x) => [x.proposals])
        .runReadWrite(async (tx) => {
        const p = await tx.proposals.get(proposalId);
        if (!p) {
            return;
        }
        p.proposalStatus = ProposalStatus.PermanentlyFailed;
        await tx.proposals.put(p);
    });
}
function getProposalRequestTimeout(retryInfo) {
    return Duration.clamp({
        lower: Duration.fromSpec({ seconds: 1 }),
        upper: Duration.fromSpec({ seconds: 60 }),
        value: retryInfo ? RetryInfo.getDuration(retryInfo) : Duration.fromSpec({}),
    });
}
function getPayRequestTimeout(purchase) {
    return Duration.multiply({ d_ms: 15000 }, 1 + purchase.payCoinSelection.coinPubs.length / 5);
}
function extractContractData(parsedContractTerms, contractTermsHash, merchantSig) {
    var _a;
    const amount = Amounts.parseOrThrow(parsedContractTerms.amount);
    let maxWireFee;
    if (parsedContractTerms.max_wire_fee) {
        maxWireFee = Amounts.parseOrThrow(parsedContractTerms.max_wire_fee);
    }
    else {
        maxWireFee = Amounts.getZero(amount.currency);
    }
    return {
        amount,
        contractTermsHash: contractTermsHash,
        fulfillmentUrl: (_a = parsedContractTerms.fulfillment_url) !== null && _a !== void 0 ? _a : "",
        merchantBaseUrl: parsedContractTerms.merchant_base_url,
        merchantPub: parsedContractTerms.merchant_pub,
        merchantSig,
        orderId: parsedContractTerms.order_id,
        summary: parsedContractTerms.summary,
        autoRefund: parsedContractTerms.auto_refund,
        maxWireFee,
        payDeadline: parsedContractTerms.pay_deadline,
        refundDeadline: parsedContractTerms.refund_deadline,
        wireFeeAmortization: parsedContractTerms.wire_fee_amortization || 1,
        allowedAuditors: parsedContractTerms.auditors.map((x) => ({
            auditorBaseUrl: x.url,
            auditorPub: x.auditor_pub,
        })),
        allowedExchanges: parsedContractTerms.exchanges.map((x) => ({
            exchangeBaseUrl: x.url,
            exchangePub: x.master_pub,
        })),
        timestamp: parsedContractTerms.timestamp,
        wireMethod: parsedContractTerms.wire_method,
        wireInfoHash: parsedContractTerms.h_wire,
        maxDepositFee: Amounts.parseOrThrow(parsedContractTerms.max_fee),
        merchant: parsedContractTerms.merchant,
        products: parsedContractTerms.products,
        summaryI18n: parsedContractTerms.summary_i18n,
        minimumAge: parsedContractTerms.minimum_age,
        deliveryDate: parsedContractTerms.delivery_date,
        deliveryLocation: parsedContractTerms.delivery_location,
    };
}
async function processDownloadProposal(ws, proposalId, options = {}) {
    const proposal = await ws.db
        .mktx((x) => [x.proposals])
        .runReadOnly(async (tx) => {
        return await tx.proposals.get(proposalId);
    });
    if (!proposal) {
        return {
            type: OperationAttemptResultType.Finished,
            result: undefined,
        };
    }
    if (proposal.proposalStatus != ProposalStatus.Downloading) {
        return {
            type: OperationAttemptResultType.Finished,
            result: undefined,
        };
    }
    const orderClaimUrl = new URL$1(`orders/${proposal.orderId}/claim`, proposal.merchantBaseUrl).href;
    logger$f.trace("downloading contract from '" + orderClaimUrl + "'");
    const requestBody = {
        nonce: proposal.noncePub,
    };
    if (proposal.claimToken) {
        requestBody.token = proposal.claimToken;
    }
    const opId = RetryTags.forProposalClaim(proposal);
    const retryRecord = await ws.db
        .mktx((x) => [x.operationRetries])
        .runReadOnly(async (tx) => {
        return tx.operationRetries.get(opId);
    });
    // FIXME: Do this in the background using the new return value
    const httpResponse = await ws.http.postJson(orderClaimUrl, requestBody, {
        timeout: getProposalRequestTimeout(retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.retryInfo),
    });
    const r = await readSuccessResponseJsonOrErrorCode(httpResponse, codecForProposal());
    if (r.isError) {
        switch (r.talerErrorResponse.code) {
            case TalerErrorCode.MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED:
                throw TalerError.fromDetail(TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED, {
                    orderId: proposal.orderId,
                    claimUrl: orderClaimUrl,
                }, "order already claimed (likely by other wallet)");
            default:
                throwUnexpectedRequestError(httpResponse, r.talerErrorResponse);
        }
    }
    const proposalResp = r.response;
    // The proposalResp contains the contract terms as raw JSON,
    // as the coded to parse them doesn't necessarily round-trip.
    // We need this raw JSON to compute the contract terms hash.
    // FIXME: Do better error handling, check if the
    // contract terms have all their forgettable information still
    // present.  The wallet should never accept contract terms
    // with missing information from the merchant.
    const isWellFormed = ContractTermsUtil.validateForgettable(proposalResp.contract_terms);
    if (!isWellFormed) {
        logger$f.trace(`malformed contract terms: ${j2s(proposalResp.contract_terms)}`);
        const err = makeErrorDetail(TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED, {}, "validation for well-formedness failed");
        await failProposalPermanently(ws, proposalId);
        throw makePendingOperationFailedError(err, TransactionType.Payment, proposalId);
    }
    const contractTermsHash = ContractTermsUtil.hashContractTerms(proposalResp.contract_terms);
    logger$f.info(`Contract terms hash: ${contractTermsHash}`);
    let parsedContractTerms;
    try {
        parsedContractTerms = codecForContractTerms().decode(proposalResp.contract_terms);
    }
    catch (e) {
        const err = makeErrorDetail(TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED, {}, `schema validation failed: ${e}`);
        await failProposalPermanently(ws, proposalId);
        throw makePendingOperationFailedError(err, TransactionType.Payment, proposalId);
    }
    const sigValid = await ws.cryptoApi.isValidContractTermsSignature({
        contractTermsHash,
        merchantPub: parsedContractTerms.merchant_pub,
        sig: proposalResp.sig,
    });
    if (!sigValid) {
        const err = makeErrorDetail(TalerErrorCode.WALLET_CONTRACT_TERMS_SIGNATURE_INVALID, {
            merchantPub: parsedContractTerms.merchant_pub,
            orderId: parsedContractTerms.order_id,
        }, "merchant's signature on contract terms is invalid");
        await failProposalPermanently(ws, proposalId);
        throw makePendingOperationFailedError(err, TransactionType.Payment, proposalId);
    }
    const fulfillmentUrl = parsedContractTerms.fulfillment_url;
    const baseUrlForDownload = proposal.merchantBaseUrl;
    const baseUrlFromContractTerms = parsedContractTerms.merchant_base_url;
    if (baseUrlForDownload !== baseUrlFromContractTerms) {
        const err = makeErrorDetail(TalerErrorCode.WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH, {
            baseUrlForDownload,
            baseUrlFromContractTerms,
        }, "merchant base URL mismatch");
        await failProposalPermanently(ws, proposalId);
        throw makePendingOperationFailedError(err, TransactionType.Payment, proposalId);
    }
    const contractData = extractContractData(parsedContractTerms, contractTermsHash, proposalResp.sig);
    logger$f.trace(`extracted contract data: ${j2s(contractData)}`);
    await ws.db
        .mktx((x) => [x.purchases, x.proposals])
        .runReadWrite(async (tx) => {
        const p = await tx.proposals.get(proposalId);
        if (!p) {
            return;
        }
        if (p.proposalStatus !== ProposalStatus.Downloading) {
            return;
        }
        p.download = {
            contractData,
            contractTermsRaw: proposalResp.contract_terms,
        };
        if (fulfillmentUrl &&
            (fulfillmentUrl.startsWith("http://") ||
                fulfillmentUrl.startsWith("https://"))) {
            const differentPurchase = await tx.purchases.indexes.byFulfillmentUrl.get(fulfillmentUrl);
            if (differentPurchase) {
                logger$f.warn("repurchase detected");
                p.proposalStatus = ProposalStatus.Repurchase;
                p.repurchaseProposalId = differentPurchase.proposalId;
                await tx.proposals.put(p);
                return;
            }
        }
        p.proposalStatus = ProposalStatus.Proposed;
        await tx.proposals.put(p);
    });
    ws.notify({
        type: NotificationType.ProposalDownloaded,
        proposalId: proposal.proposalId,
    });
    return {
        type: OperationAttemptResultType.Finished,
        result: undefined,
    };
}
/**
 * Download a proposal and store it in the database.
 * Returns an id for it to retrieve it later.
 *
 * @param sessionId Current session ID, if the proposal is being
 *  downloaded in the context of a session ID.
 */
async function startDownloadProposal(ws, merchantBaseUrl, orderId, sessionId, claimToken, noncePriv) {
    const oldProposal = await ws.db
        .mktx((x) => [x.proposals])
        .runReadOnly(async (tx) => {
        return tx.proposals.indexes.byUrlAndOrderId.get([
            merchantBaseUrl,
            orderId,
        ]);
    });
    /* If we have already claimed this proposal with the same sessionId
     * nonce and claim token, reuse it. */
    if (oldProposal &&
        oldProposal.downloadSessionId === sessionId &&
        (!noncePriv || oldProposal.noncePriv === noncePriv) &&
        oldProposal.claimToken === claimToken) {
        await processDownloadProposal(ws, oldProposal.proposalId);
        return oldProposal.proposalId;
    }
    let noncePair;
    if (noncePriv) {
        noncePair = {
            priv: noncePriv,
            pub: (await ws.cryptoApi.eddsaGetPublic({ priv: noncePriv })).pub,
        };
    }
    else {
        noncePair = await ws.cryptoApi.createEddsaKeypair({});
    }
    const { priv, pub } = noncePair;
    const proposalId = encodeCrock(getRandomBytes(32));
    const proposalRecord = {
        download: undefined,
        noncePriv: priv,
        noncePub: pub,
        claimToken,
        timestamp: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
        merchantBaseUrl,
        orderId,
        proposalId: proposalId,
        proposalStatus: ProposalStatus.Downloading,
        repurchaseProposalId: undefined,
        downloadSessionId: sessionId,
    };
    await ws.db
        .mktx((x) => [x.proposals])
        .runReadWrite(async (tx) => {
        const existingRecord = await tx.proposals.indexes.byUrlAndOrderId.get([
            merchantBaseUrl,
            orderId,
        ]);
        if (existingRecord) {
            // Created concurrently
            return;
        }
        await tx.proposals.put(proposalRecord);
    });
    await processDownloadProposal(ws, proposalId);
    return proposalId;
}
async function storeFirstPaySuccess(ws, proposalId, sessionId, paySig) {
    const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
    await ws.db
        .mktx((x) => [x.purchases])
        .runReadWrite(async (tx) => {
        const purchase = await tx.purchases.get(proposalId);
        if (!purchase) {
            logger$f.warn("purchase does not exist anymore");
            return;
        }
        const isFirst = purchase.timestampFirstSuccessfulPay === undefined;
        if (!isFirst) {
            logger$f.warn("payment success already stored");
            return;
        }
        purchase.timestampFirstSuccessfulPay = now;
        purchase.paymentSubmitPending = false;
        purchase.lastSessionId = sessionId;
        purchase.merchantPaySig = paySig;
        const protoAr = purchase.download.contractData.autoRefund;
        if (protoAr) {
            const ar = Duration.fromTalerProtocolDuration(protoAr);
            logger$f.info("auto_refund present");
            purchase.refundQueryRequested = true;
            purchase.autoRefundDeadline = AbsoluteTime.toTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), ar));
        }
        await tx.purchases.put(purchase);
    });
}
async function storePayReplaySuccess(ws, proposalId, sessionId) {
    await ws.db
        .mktx((x) => [x.purchases])
        .runReadWrite(async (tx) => {
        const purchase = await tx.purchases.get(proposalId);
        if (!purchase) {
            logger$f.warn("purchase does not exist anymore");
            return;
        }
        const isFirst = purchase.timestampFirstSuccessfulPay === undefined;
        if (isFirst) {
            throw Error("invalid payment state");
        }
        purchase.paymentSubmitPending = false;
        purchase.lastSessionId = sessionId;
        await tx.purchases.put(purchase);
    });
}
/**
 * Handle a 409 Conflict response from the merchant.
 *
 * We do this by going through the coin history provided by the exchange and
 * (1) verifying the signatures from the exchange
 * (2) adjusting the remaining coin value and refreshing it
 * (3) re-do coin selection with the bad coin removed
 */
async function handleInsufficientFunds(ws, proposalId, err) {
    var _a;
    logger$f.trace("handling insufficient funds, trying to re-select coins");
    const proposal = await ws.db
        .mktx((x) => [x.purchases])
        .runReadOnly(async (tx) => {
        return tx.purchases.get(proposalId);
    });
    if (!proposal) {
        return;
    }
    logger$f.trace(`got error details: ${j2s(err)}`);
    const exchangeReply = err.exchange_reply;
    if (exchangeReply.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS) {
        // FIXME: set as failed
        if (logger$f.shouldLogTrace()) {
            logger$f.trace("got exchange error reply (see below)");
            logger$f.trace(j2s(exchangeReply));
        }
        throw Error(`unable to handle /pay error response (${exchangeReply.code})`);
    }
    const brokenCoinPub = exchangeReply.coin_pub;
    logger$f.trace(`excluded broken coin pub=${brokenCoinPub}`);
    if (!brokenCoinPub) {
        throw new TalerProtocolViolationError();
    }
    const { contractData } = proposal.download;
    const prevPayCoins = [];
    await ws.db
        .mktx((x) => [x.coins, x.denominations])
        .runReadOnly(async (tx) => {
        for (let i = 0; i < proposal.payCoinSelection.coinPubs.length; i++) {
            const coinPub = proposal.payCoinSelection.coinPubs[i];
            if (coinPub === brokenCoinPub) {
                continue;
            }
            const contrib = proposal.payCoinSelection.coinContributions[i];
            const coin = await tx.coins.get(coinPub);
            if (!coin) {
                continue;
            }
            const denom = await tx.denominations.get([
                coin.exchangeBaseUrl,
                coin.denomPubHash,
            ]);
            if (!denom) {
                continue;
            }
            prevPayCoins.push({
                coinPub,
                contribution: contrib,
                exchangeBaseUrl: coin.exchangeBaseUrl,
                feeDeposit: denom.fees.feeDeposit,
            });
        }
    });
    const res = await selectPayCoinsNew(ws, {
        auditors: contractData.allowedAuditors,
        exchanges: contractData.allowedExchanges,
        wireMethod: contractData.wireMethod,
        contractTermsAmount: contractData.amount,
        depositFeeLimit: contractData.maxDepositFee,
        wireFeeAmortization: (_a = contractData.wireFeeAmortization) !== null && _a !== void 0 ? _a : 1,
        wireFeeLimit: contractData.maxWireFee,
        prevPayCoins,
        requiredMinimumAge: contractData.minimumAge,
    });
    if (!res) {
        logger$f.trace("insufficient funds for coin re-selection");
        return;
    }
    logger$f.trace("re-selected coins");
    await ws.db
        .mktx((x) => [
        x.purchases,
        x.coins,
        x.coinAvailability,
        x.denominations,
        x.refreshGroups,
    ])
        .runReadWrite(async (tx) => {
        const p = await tx.purchases.get(proposalId);
        if (!p) {
            return;
        }
        p.payCoinSelection = res;
        p.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
        p.coinDepositPermissions = undefined;
        await tx.purchases.put(p);
        await spendCoins(ws, tx, {
            allocationId: `proposal:${p.proposalId}`,
            coinPubs: p.payCoinSelection.coinPubs,
            contributions: p.payCoinSelection.coinContributions,
            refreshReason: RefreshReason.PayMerchant,
        });
    });
}
async function unblockBackup(ws, proposalId) {
    await ws.db
        .mktx((x) => [x.backupProviders])
        .runReadWrite(async (tx) => {
        await tx.backupProviders.indexes.byPaymentProposalId
            .iter(proposalId)
            .forEachAsync(async (bp) => {
            if (bp.state.tag === BackupProviderStateTag.Retrying) {
                bp.state = {
                    tag: BackupProviderStateTag.Ready,
                    nextBackupTimestamp: TalerProtocolTimestamp.now(),
                };
            }
        });
    });
}
async function selectCandidates(ws, req) {
    return await ws.db
        .mktx((x) => [
        x.exchanges,
        x.exchangeDetails,
        x.denominations,
        x.coinAvailability,
    ])
        .runReadOnly(async (tx) => {
        var _a;
        const denoms = [];
        const exchanges = await tx.exchanges.iter().toArray();
        const wfPerExchange = {};
        for (const exchange of exchanges) {
            const exchangeDetails = await getExchangeDetails(tx, exchange.baseUrl);
            if ((exchangeDetails === null || exchangeDetails === void 0 ? void 0 : exchangeDetails.currency) !== req.contractTermsAmount.currency) {
                continue;
            }
            let wireMethodSupported = false;
            for (const acc of exchangeDetails.wireInfo.accounts) {
                const pp = parsePaytoUri(acc.payto_uri);
                checkLogicInvariant(!!pp);
                if (pp.targetType === req.wireMethod) {
                    wireMethodSupported = true;
                    break;
                }
            }
            if (!wireMethodSupported) {
                break;
            }
            exchangeDetails.wireInfo.accounts;
            let accepted = false;
            for (const allowedExchange of req.exchanges) {
                if (allowedExchange.exchangePub === exchangeDetails.masterPublicKey) {
                    accepted = true;
                    break;
                }
            }
            for (const allowedAuditor of req.auditors) {
                for (const providedAuditor of exchangeDetails.auditors) {
                    if (allowedAuditor.auditorPub === providedAuditor.auditor_pub) {
                        accepted = true;
                        break;
                    }
                }
            }
            if (!accepted) {
                continue;
            }
            let ageLower = 0;
            let ageUpper = AgeRestriction.AGE_UNRESTRICTED;
            if (req.requiredMinimumAge) {
                ageLower = req.requiredMinimumAge;
            }
            const myExchangeDenoms = await tx.coinAvailability.indexes.byExchangeAgeAvailability.getAll(GlobalIDB.KeyRange.bound([exchangeDetails.exchangeBaseUrl, ageLower, 1], [
                exchangeDetails.exchangeBaseUrl,
                ageUpper,
                Number.MAX_SAFE_INTEGER,
            ]));
            // FIXME: Check that the individual denomination is audited!
            // FIXME: Should we exclude denominations that are
            // not spendable anymore?
            for (const denomAvail of myExchangeDenoms) {
                const denom = await tx.denominations.get([
                    denomAvail.exchangeBaseUrl,
                    denomAvail.denomPubHash,
                ]);
                checkDbInvariant(!!denom);
                if (denom.isRevoked || !denom.isOffered) {
                    continue;
                }
                denoms.push(Object.assign(Object.assign({}, DenominationRecord.toDenomInfo(denom)), { numAvailable: (_a = denomAvail.freshCoinCount) !== null && _a !== void 0 ? _a : 0, maxAge: denomAvail.maxAge }));
            }
        }
        // Sort by available amount (descending),  deposit fee (ascending) and
        // denomPub (ascending) if deposit fee is the same
        // (to guarantee deterministic results)
        denoms.sort((o1, o2) => -Amounts.cmp(o1.value, o2.value) ||
            Amounts.cmp(o1.feeDeposit, o2.feeDeposit) ||
            strcmp(o1.denomPubHash, o2.denomPubHash));
        return [denoms, wfPerExchange];
    });
}
function makeAvailabilityKey(exchangeBaseUrl, denomPubHash, maxAge) {
    return `${denomPubHash};${maxAge};${exchangeBaseUrl}`;
}
function selectGreedy(req, candidateDenoms, wireFeesPerExchange, tally) {
    const { wireFeeAmortization } = req;
    const selectedDenom = {};
    for (const aci of candidateDenoms) {
        const contributions = [];
        for (let i = 0; i < aci.numAvailable; i++) {
            // Don't use this coin if depositing it is more expensive than
            // the amount it would give the merchant.
            if (Amounts.cmp(aci.feeDeposit, aci.value) > 0) {
                continue;
            }
            if (Amounts.isZero(tally.amountPayRemaining)) {
                // We have spent enough!
                break;
            }
            tally = tallyFees(tally, wireFeesPerExchange, wireFeeAmortization, aci.exchangeBaseUrl, aci.feeDeposit);
            let coinSpend = Amounts.max(Amounts.min(tally.amountPayRemaining, aci.value), aci.feeDeposit);
            tally.amountPayRemaining = Amounts.sub(tally.amountPayRemaining, coinSpend).amount;
            contributions.push(coinSpend);
        }
        if (contributions.length) {
            const avKey = makeAvailabilityKey(aci.exchangeBaseUrl, aci.denomPubHash, aci.maxAge);
            let sd = selectedDenom[avKey];
            if (!sd) {
                sd = {
                    contributions: [],
                    denomPubHash: aci.denomPubHash,
                    exchangeBaseUrl: aci.exchangeBaseUrl,
                    maxAge: aci.maxAge,
                };
            }
            sd.contributions.push(...contributions);
            selectedDenom[avKey] = sd;
        }
        if (Amounts.isZero(tally.amountPayRemaining)) {
            return selectedDenom;
        }
    }
    return undefined;
}
function selectForced(req, candidateDenoms) {
    const selectedDenom = {};
    const forcedSelection = req.forcedSelection;
    checkLogicInvariant(!!forcedSelection);
    for (const forcedCoin of forcedSelection.coins) {
        let found = false;
        for (const aci of candidateDenoms) {
            if (aci.numAvailable <= 0) {
                continue;
            }
            if (Amounts.cmp(aci.value, forcedCoin.value) === 0) {
                aci.numAvailable--;
                const avKey = makeAvailabilityKey(aci.exchangeBaseUrl, aci.denomPubHash, aci.maxAge);
                let sd = selectedDenom[avKey];
                if (!sd) {
                    sd = {
                        contributions: [],
                        denomPubHash: aci.denomPubHash,
                        exchangeBaseUrl: aci.exchangeBaseUrl,
                        maxAge: aci.maxAge,
                    };
                }
                sd.contributions.push(Amounts.parseOrThrow(forcedCoin.value));
                selectedDenom[avKey] = sd;
                found = true;
                break;
            }
        }
        if (!found) {
            throw Error("can't find coin for forced coin selection");
        }
    }
    return selectedDenom;
}
/**
 * Given a list of candidate coins, select coins to spend under the merchant's
 * constraints.
 *
 * The prevPayCoins can be specified to "repair" a coin selection
 * by adding additional coins, after a broken (e.g. double-spent) coin
 * has been removed from the selection.
 *
 * This function is only exported for the sake of unit tests.
 */
async function selectPayCoinsNew(ws, req) {
    var _a;
    const { contractTermsAmount, depositFeeLimit, wireFeeLimit, wireFeeAmortization, } = req;
    const [candidateDenoms, wireFeesPerExchange] = await selectCandidates(ws, req);
    // logger.trace(`candidate denoms: ${j2s(candidateDenoms)}`);
    const coinPubs = [];
    const coinContributions = [];
    const currency = contractTermsAmount.currency;
    let tally = {
        amountPayRemaining: contractTermsAmount,
        amountWireFeeLimitRemaining: wireFeeLimit,
        amountDepositFeeLimitRemaining: depositFeeLimit,
        customerDepositFees: Amounts.getZero(currency),
        customerWireFees: Amounts.getZero(currency),
        wireFeeCoveredForExchange: new Set(),
    };
    const prevPayCoins = (_a = req.prevPayCoins) !== null && _a !== void 0 ? _a : [];
    // Look at existing pay coin selection and tally up
    for (const prev of prevPayCoins) {
        tally = tallyFees(tally, wireFeesPerExchange, wireFeeAmortization, prev.exchangeBaseUrl, prev.feeDeposit);
        tally.amountPayRemaining = Amounts.sub(tally.amountPayRemaining, prev.contribution).amount;
        coinPubs.push(prev.coinPub);
        coinContributions.push(prev.contribution);
    }
    let selectedDenom;
    if (req.forcedSelection) {
        selectedDenom = selectForced(req, candidateDenoms);
    }
    else {
        // FIXME:  Here, we should select coins in a smarter way.
        // Instead of always spending the next-largest coin,
        // we should try to find the smallest coin that covers the
        // amount.
        selectedDenom = selectGreedy(req, candidateDenoms, wireFeesPerExchange, tally);
    }
    if (!selectedDenom) {
        return undefined;
    }
    const finalSel = selectedDenom;
    logger$f.trace(`coin selection request ${j2s(req)}`);
    logger$f.trace(`selected coins (via denoms) for payment: ${j2s(finalSel)}`);
    await ws.db
        .mktx((x) => [x.coins, x.denominations])
        .runReadOnly(async (tx) => {
        for (const dph of Object.keys(finalSel)) {
            const selInfo = finalSel[dph];
            const numRequested = selInfo.contributions.length;
            const query = [
                selInfo.exchangeBaseUrl,
                selInfo.denomPubHash,
                selInfo.maxAge,
                CoinStatus.Fresh,
            ];
            logger$f.info(`query: ${j2s(query)}`);
            const coins = await tx.coins.indexes.byExchangeDenomPubHashAndAgeAndStatus.getAll(query, numRequested);
            if (coins.length != numRequested) {
                throw Error(`coin selection failed (not available anymore, got only ${coins.length}/${numRequested})`);
            }
            coinPubs.push(...coins.map((x) => x.coinPub));
            coinContributions.push(...selInfo.contributions);
        }
    });
    return {
        paymentAmount: contractTermsAmount,
        coinContributions,
        coinPubs,
        customerDepositFees: tally.customerDepositFees,
        customerWireFees: tally.customerWireFees,
    };
}
async function checkPaymentByProposalId(ws, proposalId, sessionId) {
    var _a;
    let proposal = await ws.db
        .mktx((x) => [x.proposals])
        .runReadOnly(async (tx) => {
        return tx.proposals.get(proposalId);
    });
    if (!proposal) {
        throw Error(`could not get proposal ${proposalId}`);
    }
    if (proposal.proposalStatus === ProposalStatus.Repurchase) {
        const existingProposalId = proposal.repurchaseProposalId;
        if (!existingProposalId) {
            throw Error("invalid proposal state");
        }
        logger$f.trace("using existing purchase for same product");
        proposal = await ws.db
            .mktx((x) => [x.proposals])
            .runReadOnly(async (tx) => {
            return tx.proposals.get(existingProposalId);
        });
        if (!proposal) {
            throw Error("existing proposal is in wrong state");
        }
    }
    const d = proposal.download;
    if (!d) {
        logger$f.error("bad proposal", proposal);
        throw Error("proposal is in invalid state");
    }
    const contractData = d.contractData;
    const merchantSig = d.contractData.merchantSig;
    if (!merchantSig) {
        throw Error("BUG: proposal is in invalid state");
    }
    proposalId = proposal.proposalId;
    // First check if we already paid for it.
    const purchase = await ws.db
        .mktx((x) => [x.purchases])
        .runReadOnly(async (tx) => {
        return tx.purchases.get(proposalId);
    });
    if (!purchase) {
        // If not already paid, check if we could pay for it.
        const res = await selectPayCoinsNew(ws, {
            auditors: contractData.allowedAuditors,
            exchanges: contractData.allowedExchanges,
            contractTermsAmount: contractData.amount,
            depositFeeLimit: contractData.maxDepositFee,
            wireFeeAmortization: (_a = contractData.wireFeeAmortization) !== null && _a !== void 0 ? _a : 1,
            wireFeeLimit: contractData.maxWireFee,
            prevPayCoins: [],
            requiredMinimumAge: contractData.minimumAge,
            wireMethod: contractData.wireMethod,
        });
        if (!res) {
            logger$f.info("not allowing payment, insufficient coins");
            return {
                status: PreparePayResultType.InsufficientBalance,
                contractTerms: d.contractTermsRaw,
                proposalId: proposal.proposalId,
                noncePriv: proposal.noncePriv,
                amountRaw: Amounts.stringify(d.contractData.amount),
            };
        }
        const totalCost = await getTotalPaymentCost(ws, res);
        logger$f.trace("costInfo", totalCost);
        logger$f.trace("coinsForPayment", res);
        return {
            status: PreparePayResultType.PaymentPossible,
            contractTerms: d.contractTermsRaw,
            proposalId: proposal.proposalId,
            noncePriv: proposal.noncePriv,
            amountEffective: Amounts.stringify(totalCost),
            amountRaw: Amounts.stringify(res.paymentAmount),
            contractTermsHash: d.contractData.contractTermsHash,
        };
    }
    if (purchase.lastSessionId !== sessionId) {
        logger$f.trace("automatically re-submitting payment with different session ID");
        await ws.db
            .mktx((x) => [x.purchases])
            .runReadWrite(async (tx) => {
            const p = await tx.purchases.get(proposalId);
            if (!p) {
                return;
            }
            p.lastSessionId = sessionId;
            p.paymentSubmitPending = true;
            await tx.purchases.put(p);
        });
        const r = await processPurchasePay(ws, proposalId, { forceNow: true });
        if (r.type !== OperationAttemptResultType.Finished) {
            // FIXME: This does not surface the original error
            throw Error("submitting pay failed");
        }
        return {
            status: PreparePayResultType.AlreadyConfirmed,
            contractTerms: purchase.download.contractTermsRaw,
            contractTermsHash: purchase.download.contractData.contractTermsHash,
            paid: true,
            amountRaw: Amounts.stringify(purchase.download.contractData.amount),
            amountEffective: Amounts.stringify(purchase.totalPayCost),
            proposalId,
        };
    }
    else if (!purchase.timestampFirstSuccessfulPay) {
        return {
            status: PreparePayResultType.AlreadyConfirmed,
            contractTerms: purchase.download.contractTermsRaw,
            contractTermsHash: purchase.download.contractData.contractTermsHash,
            paid: false,
            amountRaw: Amounts.stringify(purchase.download.contractData.amount),
            amountEffective: Amounts.stringify(purchase.totalPayCost),
            proposalId,
        };
    }
    else {
        const paid = !purchase.paymentSubmitPending;
        return Object.assign(Object.assign({ status: PreparePayResultType.AlreadyConfirmed, contractTerms: purchase.download.contractTermsRaw, contractTermsHash: purchase.download.contractData.contractTermsHash, paid, amountRaw: Amounts.stringify(purchase.download.contractData.amount), amountEffective: Amounts.stringify(purchase.totalPayCost) }, (paid ? { nextUrl: purchase.download.contractData.orderId } : {})), { proposalId });
    }
}
async function getContractTermsDetails(ws, proposalId) {
    const proposal = await ws.db
        .mktx((x) => [x.proposals])
        .runReadOnly(async (tx) => {
        return tx.proposals.get(proposalId);
    });
    if (!proposal) {
        throw Error(`proposal with id ${proposalId} not found`);
    }
    if (!proposal.download || !proposal.download.contractData) {
        throw Error("proposal is in invalid state");
    }
    return proposal.download.contractData;
}
/**
 * Check if a payment for the given taler://pay/ URI is possible.
 *
 * If the payment is possible, the signature are already generated but not
 * yet send to the merchant.
 */
async function preparePayForUri(ws, talerPayUri) {
    const uriResult = parsePayUri(talerPayUri);
    if (!uriResult) {
        throw TalerError.fromDetail(TalerErrorCode.WALLET_INVALID_TALER_PAY_URI, {
            talerPayUri,
        }, `invalid taler://pay URI (${talerPayUri})`);
    }
    let proposalId = await startDownloadProposal(ws, uriResult.merchantBaseUrl, uriResult.orderId, uriResult.sessionId, uriResult.claimToken, uriResult.noncePriv);
    return checkPaymentByProposalId(ws, proposalId, uriResult.sessionId);
}
/**
 * Generate deposit permissions for a purchase.
 *
 * Accesses the database and the crypto worker.
 */
async function generateDepositPermissions(ws, payCoinSel, contractData) {
    const depositPermissions = [];
    const coinWithDenom = [];
    await ws.db
        .mktx((x) => [x.coins, x.denominations])
        .runReadOnly(async (tx) => {
        for (let i = 0; i < payCoinSel.coinPubs.length; i++) {
            const coin = await tx.coins.get(payCoinSel.coinPubs[i]);
            if (!coin) {
                throw Error("can't pay, allocated coin not found anymore");
            }
            const denom = await tx.denominations.get([
                coin.exchangeBaseUrl,
                coin.denomPubHash,
            ]);
            if (!denom) {
                throw Error("can't pay, denomination of allocated coin not found anymore");
            }
            coinWithDenom.push({ coin, denom });
        }
    });
    for (let i = 0; i < payCoinSel.coinPubs.length; i++) {
        const { coin, denom } = coinWithDenom[i];
        let wireInfoHash;
        wireInfoHash = contractData.wireInfoHash;
        logger$f.trace(`signing deposit permission for coin with ageRestriction=${j2s(coin.ageCommitmentProof)}`);
        const dp = await ws.cryptoApi.signDepositPermission({
            coinPriv: coin.coinPriv,
            coinPub: coin.coinPub,
            contractTermsHash: contractData.contractTermsHash,
            denomPubHash: coin.denomPubHash,
            denomKeyType: denom.denomPub.cipher,
            denomSig: coin.denomSig,
            exchangeBaseUrl: coin.exchangeBaseUrl,
            feeDeposit: denom.fees.feeDeposit,
            merchantPub: contractData.merchantPub,
            refundDeadline: contractData.refundDeadline,
            spendAmount: payCoinSel.coinContributions[i],
            timestamp: contractData.timestamp,
            wireInfoHash,
            ageCommitmentProof: coin.ageCommitmentProof,
            requiredMinimumAge: contractData.minimumAge,
        });
        depositPermissions.push(dp);
    }
    return depositPermissions;
}
/**
 * Run the operation handler for a payment
 * and return the result as a {@link ConfirmPayResult}.
 */
async function runPayForConfirmPay(ws, proposalId) {
    var _a;
    const res = await processPurchasePay(ws, proposalId, { forceNow: true });
    switch (res.type) {
        case OperationAttemptResultType.Finished: {
            const purchase = await ws.db
                .mktx((x) => [x.purchases])
                .runReadOnly(async (tx) => {
                return tx.purchases.get(proposalId);
            });
            if (!(purchase === null || purchase === void 0 ? void 0 : purchase.download)) {
                throw Error("purchase record not available anymore");
            }
            return {
                type: ConfirmPayResultType.Done,
                contractTerms: purchase.download.contractTermsRaw,
                transactionId: makeEventId(TransactionType.Payment, proposalId),
            };
        }
        case OperationAttemptResultType.Error: {
            // We hide transient errors from the caller.
            const opRetry = await ws.db
                .mktx((x) => [x.operationRetries])
                .runReadOnly(async (tx) => tx.operationRetries.get(RetryTags.byPaymentProposalId(proposalId)));
            const maxRetry = 3;
            const numRetry = (_a = opRetry === null || opRetry === void 0 ? void 0 : opRetry.retryInfo.retryCounter) !== null && _a !== void 0 ? _a : 0;
            if (res.errorDetail.code ===
                TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR &&
                numRetry < maxRetry) {
                // Pretend the operation is pending instead of reporting
                // an error, but only up to maxRetry attempts.
                await storeOperationPending(ws, RetryTags.byPaymentProposalId(proposalId));
                return {
                    type: ConfirmPayResultType.Pending,
                    lastError: opRetry === null || opRetry === void 0 ? void 0 : opRetry.lastError,
                    transactionId: makeEventId(TransactionType.Payment, proposalId),
                };
            }
            else {
                // FIXME: allocate error code!
                await storeOperationError(ws, RetryTags.byPaymentProposalId(proposalId), res.errorDetail);
                throw Error("payment failed");
            }
        }
        case OperationAttemptResultType.Pending:
            await storeOperationPending(ws, `${PendingTaskType.Pay}:${proposalId}`);
            return {
                type: ConfirmPayResultType.Pending,
                transactionId: makeEventId(TransactionType.Payment, proposalId),
                lastError: undefined,
            };
        case OperationAttemptResultType.Longpoll:
            throw Error("unexpected processPurchasePay result (longpoll)");
        default:
            assertUnreachable();
    }
}
/**
 * Confirm payment for a proposal previously claimed by the wallet.
 */
async function confirmPay(ws, proposalId, sessionIdOverride, forcedCoinSel) {
    var _a;
    logger$f.trace(`executing confirmPay with proposalId ${proposalId} and sessionIdOverride ${sessionIdOverride}`);
    const proposal = await ws.db
        .mktx((x) => [x.proposals])
        .runReadOnly(async (tx) => {
        return tx.proposals.get(proposalId);
    });
    if (!proposal) {
        throw Error(`proposal with id ${proposalId} not found`);
    }
    const d = proposal.download;
    if (!d) {
        throw Error("proposal is in invalid state");
    }
    const existingPurchase = await ws.db
        .mktx((x) => [x.purchases])
        .runReadWrite(async (tx) => {
        const purchase = await tx.purchases.get(proposalId);
        if (purchase &&
            sessionIdOverride !== undefined &&
            sessionIdOverride != purchase.lastSessionId) {
            logger$f.trace(`changing session ID to ${sessionIdOverride}`);
            purchase.lastSessionId = sessionIdOverride;
            purchase.paymentSubmitPending = true;
            await tx.purchases.put(purchase);
        }
        return purchase;
    });
    if (existingPurchase) {
        logger$f.trace("confirmPay: submitting payment for existing purchase");
        return runPayForConfirmPay(ws, proposalId);
    }
    logger$f.trace("confirmPay: purchase record does not exist yet");
    const contractData = d.contractData;
    let res = undefined;
    res = await selectPayCoinsNew(ws, {
        auditors: contractData.allowedAuditors,
        exchanges: contractData.allowedExchanges,
        wireMethod: contractData.wireMethod,
        contractTermsAmount: contractData.amount,
        depositFeeLimit: contractData.maxDepositFee,
        wireFeeAmortization: (_a = contractData.wireFeeAmortization) !== null && _a !== void 0 ? _a : 1,
        wireFeeLimit: contractData.maxWireFee,
        prevPayCoins: [],
        requiredMinimumAge: contractData.minimumAge,
        forcedSelection: forcedCoinSel,
    });
    logger$f.trace("coin selection result", res);
    if (!res) {
        // Should not happen, since checkPay should be called first
        // FIXME: Actually, this should be handled gracefully,
        // and the status should be stored in the DB.
        logger$f.warn("not confirming payment, insufficient coins");
        throw Error("insufficient balance");
    }
    const depositPermissions = await generateDepositPermissions(ws, res, d.contractData);
    await recordConfirmPay(ws, proposal, res, depositPermissions, sessionIdOverride);
    return runPayForConfirmPay(ws, proposalId);
}
async function processPurchasePay(ws, proposalId, options = {}) {
    const purchase = await ws.db
        .mktx((x) => [x.purchases])
        .runReadOnly(async (tx) => {
        return tx.purchases.get(proposalId);
    });
    if (!purchase) {
        return {
            type: OperationAttemptResultType.Error,
            errorDetail: {
                // FIXME: allocate more specific error code
                code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
                hint: `trying to pay for purchase that is not in the database`,
                proposalId: proposalId,
            },
        };
    }
    if (!purchase.paymentSubmitPending) {
        OperationAttemptResult.finishedEmpty();
    }
    logger$f.trace(`processing purchase pay ${proposalId}`);
    const sessionId = purchase.lastSessionId;
    logger$f.trace("paying with session ID", sessionId);
    if (!purchase.merchantPaySig) {
        const payUrl = new URL$1(`orders/${purchase.download.contractData.orderId}/pay`, purchase.download.contractData.merchantBaseUrl).href;
        let depositPermissions;
        if (purchase.coinDepositPermissions) {
            depositPermissions = purchase.coinDepositPermissions;
        }
        else {
            // FIXME: also cache!
            depositPermissions = await generateDepositPermissions(ws, purchase.payCoinSelection, purchase.download.contractData);
        }
        const reqBody = {
            coins: depositPermissions,
            session_id: purchase.lastSessionId,
        };
        logger$f.trace("making pay request ... ", JSON.stringify(reqBody, undefined, 2));
        const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], () => ws.http.postJson(payUrl, reqBody, {
            timeout: getPayRequestTimeout(purchase),
        }));
        logger$f.trace(`got resp ${JSON.stringify(resp)}`);
        if (resp.status >= 500 && resp.status <= 599) {
            const errDetails = await readUnexpectedResponseDetails(resp);
            return {
                type: OperationAttemptResultType.Error,
                errorDetail: makeErrorDetail(TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR, {
                    requestError: errDetails,
                }),
            };
        }
        if (resp.status === HttpStatusCode.BadRequest) {
            const errDetails = await readUnexpectedResponseDetails(resp);
            logger$f.warn("unexpected 400 response for /pay");
            logger$f.warn(j2s(errDetails));
            await ws.db
                .mktx((x) => [x.purchases])
                .runReadWrite(async (tx) => {
                const purch = await tx.purchases.get(proposalId);
                if (!purch) {
                    return;
                }
                purch.payFrozen = true;
                await tx.purchases.put(purch);
            });
            throw makePendingOperationFailedError(errDetails, TransactionType.Payment, proposalId);
        }
        if (resp.status === HttpStatusCode.Conflict) {
            const err = await readTalerErrorResponse(resp);
            if (err.code ===
                TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS) {
                // Do this in the background, as it might take some time
                handleInsufficientFunds(ws, proposalId, err).catch(async (e) => {
                    console.log("handling insufficient funds failed");
                    await scheduleRetry(ws, RetryTags.forPay(purchase), {
                        code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
                        message: "unexpected exception",
                        hint: "unexpected exception",
                        details: {
                            exception: e.toString(),
                        },
                    });
                });
                return {
                    type: OperationAttemptResultType.Pending,
                    result: undefined,
                };
            }
        }
        const merchantResp = await readSuccessResponseJsonOrThrow(resp, codecForMerchantPayResponse());
        logger$f.trace("got success from pay URL", merchantResp);
        const merchantPub = purchase.download.contractData.merchantPub;
        const { valid } = await ws.cryptoApi.isValidPaymentSignature({
            contractHash: purchase.download.contractData.contractTermsHash,
            merchantPub,
            sig: merchantResp.sig,
        });
        if (!valid) {
            logger$f.error("merchant payment signature invalid");
            // FIXME: properly display error
            throw Error("merchant payment signature invalid");
        }
        await storeFirstPaySuccess(ws, proposalId, sessionId, merchantResp.sig);
        await unblockBackup(ws, proposalId);
    }
    else {
        const payAgainUrl = new URL$1(`orders/${purchase.download.contractData.orderId}/paid`, purchase.download.contractData.merchantBaseUrl).href;
        const reqBody = {
            sig: purchase.merchantPaySig,
            h_contract: purchase.download.contractData.contractTermsHash,
            session_id: sessionId !== null && sessionId !== void 0 ? sessionId : "",
        };
        const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], () => ws.http.postJson(payAgainUrl, reqBody));
        if (resp.status !== 204) {
            throw TalerError.fromDetail(TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, getHttpResponseErrorDetails(resp), "/paid failed");
        }
        await storePayReplaySuccess(ws, proposalId, sessionId);
        await unblockBackup(ws, proposalId);
    }
    ws.notify({
        type: NotificationType.PayOperationSuccess,
        proposalId: purchase.proposalId,
    });
    return OperationAttemptResult.finishedEmpty();
}

/*
 This file is part of GNU Taler
 (C) 2020 Taler Systems SA

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$e = new Logger("operations/backup/import.ts");
function checkBackupInvariant(b, m) {
    if (!b) {
        if (m) {
            throw Error(`BUG: backup invariant failed (${m})`);
        }
        else {
            throw Error("BUG: backup invariant failed");
        }
    }
}
/**
 * Re-compute information about the coin selection for a payment.
 */
async function recoverPayCoinSelection(tx, contractData, backupPurchase) {
    const coinPubs = backupPurchase.pay_coins.map((x) => x.coin_pub);
    const coinContributions = backupPurchase.pay_coins.map((x) => Amounts.parseOrThrow(x.contribution));
    const coveredExchanges = new Set();
    let totalWireFee = Amounts.getZero(contractData.amount.currency);
    let totalDepositFees = Amounts.getZero(contractData.amount.currency);
    for (const coinPub of coinPubs) {
        const coinRecord = await tx.coins.get(coinPub);
        checkBackupInvariant(!!coinRecord);
        const denom = await tx.denominations.get([
            coinRecord.exchangeBaseUrl,
            coinRecord.denomPubHash,
        ]);
        checkBackupInvariant(!!denom);
        totalDepositFees = Amounts.add(totalDepositFees, denom.fees.feeDeposit).amount;
        if (!coveredExchanges.has(coinRecord.exchangeBaseUrl)) {
            const exchangeDetails = await getExchangeDetails(tx, coinRecord.exchangeBaseUrl);
            checkBackupInvariant(!!exchangeDetails);
            let wireFee;
            const feesForType = exchangeDetails.wireInfo.feesForType;
            checkBackupInvariant(!!feesForType);
            for (const fee of feesForType[contractData.wireMethod] || []) {
                if (fee.startStamp <= contractData.timestamp &&
                    fee.endStamp >= contractData.timestamp) {
                    wireFee = fee.wireFee;
                    break;
                }
            }
            if (wireFee) {
                totalWireFee = Amounts.add(totalWireFee, wireFee).amount;
            }
            coveredExchanges.add(coinRecord.exchangeBaseUrl);
        }
    }
    let customerWireFee;
    const amortizedWireFee = Amounts.divide(totalWireFee, contractData.wireFeeAmortization);
    if (Amounts.cmp(contractData.maxWireFee, amortizedWireFee) < 0) {
        customerWireFee = amortizedWireFee;
    }
    else {
        customerWireFee = Amounts.getZero(contractData.amount.currency);
    }
    const customerDepositFees = Amounts.sub(totalDepositFees, contractData.maxDepositFee).amount;
    return {
        coinPubs,
        coinContributions,
        paymentAmount: contractData.amount,
        customerWireFees: customerWireFee,
        customerDepositFees,
    };
}
async function getDenomSelStateFromBackup(tx, exchangeBaseUrl, sel) {
    const d0 = await tx.denominations.get([
        exchangeBaseUrl,
        sel[0].denom_pub_hash,
    ]);
    checkBackupInvariant(!!d0);
    const selectedDenoms = [];
    let totalCoinValue = Amounts.getZero(d0.currency);
    let totalWithdrawCost = Amounts.getZero(d0.currency);
    for (const s of sel) {
        const d = await tx.denominations.get([exchangeBaseUrl, s.denom_pub_hash]);
        checkBackupInvariant(!!d);
        totalCoinValue = Amounts.add(totalCoinValue, DenominationRecord.getValue(d)).amount;
        totalWithdrawCost = Amounts.add(totalWithdrawCost, DenominationRecord.getValue(d), d.fees.feeWithdraw).amount;
    }
    return {
        selectedDenoms,
        totalCoinValue,
        totalWithdrawCost,
    };
}
async function importCoin(ws, tx, cryptoComp, args) {
    const { backupCoin, exchangeBaseUrl, denomPubHash } = args;
    const compCoin = cryptoComp.coinPrivToCompletedCoin[backupCoin.coin_priv];
    checkLogicInvariant(!!compCoin);
    const existingCoin = await tx.coins.get(compCoin.coinPub);
    if (!existingCoin) {
        let coinSource;
        switch (backupCoin.coin_source.type) {
            case BackupCoinSourceType.Refresh:
                coinSource = {
                    type: CoinSourceType.Refresh,
                    oldCoinPub: backupCoin.coin_source.old_coin_pub,
                };
                break;
            case BackupCoinSourceType.Tip:
                coinSource = {
                    type: CoinSourceType.Tip,
                    coinIndex: backupCoin.coin_source.coin_index,
                    walletTipId: backupCoin.coin_source.wallet_tip_id,
                };
                break;
            case BackupCoinSourceType.Withdraw:
                coinSource = {
                    type: CoinSourceType.Withdraw,
                    coinIndex: backupCoin.coin_source.coin_index,
                    reservePub: backupCoin.coin_source.reserve_pub,
                    withdrawalGroupId: backupCoin.coin_source.withdrawal_group_id,
                };
                break;
        }
        const coinRecord = {
            blindingKey: backupCoin.blinding_key,
            coinEvHash: compCoin.coinEvHash,
            coinPriv: backupCoin.coin_priv,
            currentAmount: Amounts.parseOrThrow(backupCoin.current_amount),
            denomSig: backupCoin.denom_sig,
            coinPub: compCoin.coinPub,
            exchangeBaseUrl,
            denomPubHash,
            status: backupCoin.fresh ? CoinStatus.Fresh : CoinStatus.Dormant,
            coinSource,
            // FIXME!
            maxAge: AgeRestriction.AGE_UNRESTRICTED,
            // FIXME!
            ageCommitmentProof: undefined,
            // FIXME!
            allocation: undefined,
        };
        if (coinRecord.status === CoinStatus.Fresh) {
            await makeCoinAvailable(ws, tx, coinRecord);
        }
        else {
            await tx.coins.put(coinRecord);
        }
    }
}
async function importBackup(ws, backupBlobArg, cryptoComp) {
    await provideBackupState(ws);
    logger$e.info(`importing backup ${j2s(backupBlobArg)}`);
    return ws.db
        .mktx((x) => [
        x.config,
        x.exchangeDetails,
        x.exchanges,
        x.coins,
        x.coinAvailability,
        x.denominations,
        x.purchases,
        x.proposals,
        x.refreshGroups,
        x.backupProviders,
        x.tips,
        x.recoupGroups,
        x.withdrawalGroups,
        x.tombstones,
        x.depositGroups,
    ])
        .runReadWrite(async (tx) => {
        var _a, _b, _c;
        var _d, _e;
        // FIXME: validate schema!
        const backupBlob = backupBlobArg;
        // FIXME: validate version
        for (const tombstone of backupBlob.tombstones) {
            await tx.tombstones.put({
                id: tombstone,
            });
        }
        const tombstoneSet = new Set((await tx.tombstones.iter().toArray()).map((x) => x.id));
        // FIXME:  Validate that the "details pointer" is correct
        for (const backupExchange of backupBlob.exchanges) {
            const existingExchange = await tx.exchanges.get(backupExchange.base_url);
            if (existingExchange) {
                continue;
            }
            await tx.exchanges.put({
                baseUrl: backupExchange.base_url,
                detailsPointer: {
                    currency: backupExchange.currency,
                    masterPublicKey: backupExchange.master_public_key,
                    updateClock: backupExchange.update_clock,
                    protocolVersionRange: backupExchange.protocol_version_range,
                },
                permanent: true,
                lastUpdate: undefined,
                nextUpdate: TalerProtocolTimestamp.now(),
                nextRefreshCheck: TalerProtocolTimestamp.now(),
            });
        }
        for (const backupExchangeDetails of backupBlob.exchange_details) {
            const existingExchangeDetails = await tx.exchangeDetails.get([
                backupExchangeDetails.base_url,
                backupExchangeDetails.currency,
                backupExchangeDetails.master_public_key,
            ]);
            if (!existingExchangeDetails) {
                const wireInfo = {
                    accounts: backupExchangeDetails.accounts.map((x) => ({
                        master_sig: x.master_sig,
                        payto_uri: x.payto_uri,
                    })),
                    feesForType: {},
                };
                for (const fee of backupExchangeDetails.wire_fees) {
                    const w = ((_a = (_d = wireInfo.feesForType)[_e = fee.wire_type]) !== null && _a !== void 0 ? _a : (_d[_e] = []));
                    w.push({
                        closingFee: Amounts.parseOrThrow(fee.closing_fee),
                        endStamp: fee.end_stamp,
                        sig: fee.sig,
                        startStamp: fee.start_stamp,
                        wireFee: Amounts.parseOrThrow(fee.wire_fee),
                        wadFee: Amounts.parseOrThrow(fee.wad_fee),
                    });
                }
                await tx.exchangeDetails.put({
                    exchangeBaseUrl: backupExchangeDetails.base_url,
                    termsOfServiceAcceptedEtag: backupExchangeDetails.tos_accepted_etag,
                    termsOfServiceText: undefined,
                    termsOfServiceLastEtag: undefined,
                    termsOfServiceContentType: undefined,
                    termsOfServiceAcceptedTimestamp: backupExchangeDetails.tos_accepted_timestamp,
                    wireInfo,
                    currency: backupExchangeDetails.currency,
                    auditors: backupExchangeDetails.auditors.map((x) => ({
                        auditor_pub: x.auditor_pub,
                        auditor_url: x.auditor_url,
                        denomination_keys: x.denomination_keys,
                    })),
                    masterPublicKey: backupExchangeDetails.master_public_key,
                    protocolVersion: backupExchangeDetails.protocol_version,
                    reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
                    signingKeys: backupExchangeDetails.signing_keys.map((x) => ({
                        key: x.key,
                        master_sig: x.master_sig,
                        stamp_end: x.stamp_end,
                        stamp_expire: x.stamp_expire,
                        stamp_start: x.stamp_start,
                    })),
                });
            }
            for (const backupDenomination of backupExchangeDetails.denominations) {
                if (backupDenomination.denom_pub.cipher !== DenomKeyType.Rsa) {
                    throw Error("unsupported cipher");
                }
                const denomPubHash = cryptoComp.rsaDenomPubToHash[backupDenomination.denom_pub.rsa_public_key];
                checkLogicInvariant(!!denomPubHash);
                const existingDenom = await tx.denominations.get([
                    backupExchangeDetails.base_url,
                    denomPubHash,
                ]);
                if (!existingDenom) {
                    const value = Amounts.parseOrThrow(backupDenomination.value);
                    await tx.denominations.put({
                        denomPub: backupDenomination.denom_pub,
                        denomPubHash: denomPubHash,
                        exchangeBaseUrl: backupExchangeDetails.base_url,
                        exchangeMasterPub: backupExchangeDetails.master_public_key,
                        fees: {
                            feeDeposit: Amounts.parseOrThrow(backupDenomination.fee_deposit),
                            feeRefresh: Amounts.parseOrThrow(backupDenomination.fee_refresh),
                            feeRefund: Amounts.parseOrThrow(backupDenomination.fee_refund),
                            feeWithdraw: Amounts.parseOrThrow(backupDenomination.fee_withdraw),
                        },
                        isOffered: backupDenomination.is_offered,
                        isRevoked: backupDenomination.is_revoked,
                        masterSig: backupDenomination.master_sig,
                        stampExpireDeposit: backupDenomination.stamp_expire_deposit,
                        stampExpireLegal: backupDenomination.stamp_expire_legal,
                        stampExpireWithdraw: backupDenomination.stamp_expire_withdraw,
                        stampStart: backupDenomination.stamp_start,
                        verificationStatus: DenominationVerificationStatus.VerifiedGood,
                        currency: value.currency,
                        amountFrac: value.fraction,
                        amountVal: value.value,
                        listIssueDate: backupDenomination.list_issue_date,
                    });
                }
                for (const backupCoin of backupDenomination.coins) {
                    await importCoin(ws, tx, cryptoComp, {
                        backupCoin,
                        denomPubHash,
                        exchangeBaseUrl: backupExchangeDetails.base_url,
                    });
                }
            }
            for (const backupWg of backupBlob.withdrawal_groups) {
                const reservePub = cryptoComp.reservePrivToPub[backupWg.reserve_priv];
                checkLogicInvariant(!!reservePub);
                const ts = makeEventId(TombstoneTag.DeleteReserve, reservePub);
                if (tombstoneSet.has(ts)) {
                    continue;
                }
                const existingWg = await tx.withdrawalGroups.get(backupWg.withdrawal_group_id);
                if (existingWg) {
                    continue;
                }
                let wgInfo;
                switch (backupWg.info.type) {
                    case BackupWgType.BankIntegrated:
                        wgInfo = {
                            withdrawalType: "bank-integrated" /* WithdrawalRecordType.BankIntegrated */,
                            bankInfo: {
                                exchangePaytoUri: backupWg.info.exchange_payto_uri,
                                talerWithdrawUri: backupWg.info.taler_withdraw_uri,
                                confirmUrl: backupWg.info.confirm_url,
                                timestampBankConfirmed: backupWg.info.timestamp_bank_confirmed,
                                timestampReserveInfoPosted: backupWg.info.timestamp_reserve_info_posted,
                            },
                        };
                        break;
                    case BackupWgType.BankManual:
                        wgInfo = {
                            withdrawalType: "bank-manual" /* WithdrawalRecordType.BankManual */,
                        };
                        break;
                    case BackupWgType.PeerPullCredit:
                        wgInfo = {
                            withdrawalType: "peer-pull-credit" /* WithdrawalRecordType.PeerPullCredit */,
                            contractTerms: backupWg.info.contract_terms,
                            contractPriv: backupWg.info.contract_priv,
                        };
                        break;
                    case BackupWgType.PeerPushCredit:
                        wgInfo = {
                            withdrawalType: "peer-push-credit" /* WithdrawalRecordType.PeerPushCredit */,
                            contractTerms: backupWg.info.contract_terms,
                        };
                        break;
                    case BackupWgType.Recoup:
                        wgInfo = {
                            withdrawalType: "recoup" /* WithdrawalRecordType.Recoup */,
                        };
                        break;
                    default:
                        assertUnreachable(backupWg.info);
                }
                await tx.withdrawalGroups.put({
                    withdrawalGroupId: backupWg.withdrawal_group_id,
                    exchangeBaseUrl: backupWg.exchange_base_url,
                    instructedAmount: Amounts.parseOrThrow(backupWg.instructed_amount),
                    secretSeed: backupWg.secret_seed,
                    denomsSel: await getDenomSelStateFromBackup(tx, backupWg.exchange_base_url, backupWg.selected_denoms),
                    denomSelUid: backupWg.selected_denoms_uid,
                    rawWithdrawalAmount: Amounts.parseOrThrow(backupWg.raw_withdrawal_amount),
                    reservePriv: backupWg.reserve_priv,
                    reservePub,
                    status: backupWg.timestamp_finish
                        ? WithdrawalGroupStatus.Finished
                        : WithdrawalGroupStatus.QueryingStatus,
                    timestampStart: backupWg.timestamp_created,
                    wgInfo,
                    restrictAge: backupWg.restrict_age,
                    senderWire: undefined,
                    timestampFinish: backupWg.timestamp_finish,
                });
            }
        }
        for (const backupProposal of backupBlob.proposals) {
            const ts = makeEventId(TombstoneTag.DeletePayment, backupProposal.proposal_id);
            if (tombstoneSet.has(ts)) {
                continue;
            }
            const existingProposal = await tx.proposals.get(backupProposal.proposal_id);
            if (!existingProposal) {
                let download;
                let proposalStatus;
                switch (backupProposal.proposal_status) {
                    case BackupProposalStatus.Proposed:
                        if (backupProposal.contract_terms_raw) {
                            proposalStatus = ProposalStatus.Proposed;
                        }
                        else {
                            proposalStatus = ProposalStatus.Downloading;
                        }
                        break;
                    case BackupProposalStatus.Refused:
                        proposalStatus = ProposalStatus.Refused;
                        break;
                    case BackupProposalStatus.Repurchase:
                        proposalStatus = ProposalStatus.Repurchase;
                        break;
                    case BackupProposalStatus.PermanentlyFailed:
                        proposalStatus = ProposalStatus.PermanentlyFailed;
                        break;
                }
                if (backupProposal.contract_terms_raw) {
                    checkDbInvariant(!!backupProposal.merchant_sig);
                    const parsedContractTerms = codecForContractTerms().decode(backupProposal.contract_terms_raw);
                    const amount = Amounts.parseOrThrow(parsedContractTerms.amount);
                    const contractTermsHash = cryptoComp.proposalIdToContractTermsHash[backupProposal.proposal_id];
                    let maxWireFee;
                    if (parsedContractTerms.max_wire_fee) {
                        maxWireFee = Amounts.parseOrThrow(parsedContractTerms.max_wire_fee);
                    }
                    else {
                        maxWireFee = Amounts.getZero(amount.currency);
                    }
                    download = {
                        contractData: {
                            amount,
                            contractTermsHash: contractTermsHash,
                            fulfillmentUrl: (_b = parsedContractTerms.fulfillment_url) !== null && _b !== void 0 ? _b : "",
                            merchantBaseUrl: parsedContractTerms.merchant_base_url,
                            merchantPub: parsedContractTerms.merchant_pub,
                            merchantSig: backupProposal.merchant_sig,
                            orderId: parsedContractTerms.order_id,
                            summary: parsedContractTerms.summary,
                            autoRefund: parsedContractTerms.auto_refund,
                            maxWireFee,
                            payDeadline: parsedContractTerms.pay_deadline,
                            refundDeadline: parsedContractTerms.refund_deadline,
                            wireFeeAmortization: parsedContractTerms.wire_fee_amortization || 1,
                            allowedAuditors: parsedContractTerms.auditors.map((x) => ({
                                auditorBaseUrl: x.url,
                                auditorPub: x.auditor_pub,
                            })),
                            allowedExchanges: parsedContractTerms.exchanges.map((x) => ({
                                exchangeBaseUrl: x.url,
                                exchangePub: x.master_pub,
                            })),
                            timestamp: parsedContractTerms.timestamp,
                            wireMethod: parsedContractTerms.wire_method,
                            wireInfoHash: parsedContractTerms.h_wire,
                            maxDepositFee: Amounts.parseOrThrow(parsedContractTerms.max_fee),
                            merchant: parsedContractTerms.merchant,
                            products: parsedContractTerms.products,
                            summaryI18n: parsedContractTerms.summary_i18n,
                            deliveryDate: parsedContractTerms.delivery_date,
                            deliveryLocation: parsedContractTerms.delivery_location,
                        },
                        contractTermsRaw: backupProposal.contract_terms_raw,
                    };
                }
                await tx.proposals.put({
                    claimToken: backupProposal.claim_token,
                    merchantBaseUrl: backupProposal.merchant_base_url,
                    timestamp: backupProposal.timestamp,
                    orderId: backupProposal.order_id,
                    noncePriv: backupProposal.nonce_priv,
                    noncePub: cryptoComp.proposalNoncePrivToPub[backupProposal.nonce_priv],
                    proposalId: backupProposal.proposal_id,
                    repurchaseProposalId: backupProposal.repurchase_proposal_id,
                    download,
                    proposalStatus,
                    // FIXME!
                    downloadSessionId: undefined,
                });
            }
        }
        for (const backupPurchase of backupBlob.purchases) {
            const ts = makeEventId(TombstoneTag.DeletePayment, backupPurchase.proposal_id);
            if (tombstoneSet.has(ts)) {
                continue;
            }
            const existingPurchase = await tx.purchases.get(backupPurchase.proposal_id);
            if (!existingPurchase) {
                const refunds = {};
                for (const backupRefund of backupPurchase.refunds) {
                    const key = `${backupRefund.coin_pub}-${backupRefund.rtransaction_id}`;
                    const coin = await tx.coins.get(backupRefund.coin_pub);
                    checkBackupInvariant(!!coin);
                    const denom = await tx.denominations.get([
                        coin.exchangeBaseUrl,
                        coin.denomPubHash,
                    ]);
                    checkBackupInvariant(!!denom);
                    const common = {
                        coinPub: backupRefund.coin_pub,
                        executionTime: backupRefund.execution_time,
                        obtainedTime: backupRefund.obtained_time,
                        refundAmount: Amounts.parseOrThrow(backupRefund.refund_amount),
                        refundFee: denom.fees.feeRefund,
                        rtransactionId: backupRefund.rtransaction_id,
                        totalRefreshCostBound: Amounts.parseOrThrow(backupRefund.total_refresh_cost_bound),
                    };
                    switch (backupRefund.type) {
                        case BackupRefundState.Applied:
                            refunds[key] = Object.assign({ type: RefundState.Applied }, common);
                            break;
                        case BackupRefundState.Failed:
                            refunds[key] = Object.assign({ type: RefundState.Failed }, common);
                            break;
                        case BackupRefundState.Pending:
                            refunds[key] = Object.assign({ type: RefundState.Pending }, common);
                            break;
                    }
                }
                let abortStatus;
                switch (backupPurchase.abort_status) {
                    case "abort-finished":
                        abortStatus = AbortStatus.AbortFinished;
                        break;
                    case "abort-refund":
                        abortStatus = AbortStatus.AbortRefund;
                        break;
                    case undefined:
                        abortStatus = AbortStatus.None;
                        break;
                    default:
                        logger$e.warn(`got backup purchase abort_status ${j2s(backupPurchase.abort_status)}`);
                        throw Error("not reachable");
                }
                const parsedContractTerms = codecForContractTerms().decode(backupPurchase.contract_terms_raw);
                const amount = Amounts.parseOrThrow(parsedContractTerms.amount);
                const contractTermsHash = cryptoComp.proposalIdToContractTermsHash[backupPurchase.proposal_id];
                let maxWireFee;
                if (parsedContractTerms.max_wire_fee) {
                    maxWireFee = Amounts.parseOrThrow(parsedContractTerms.max_wire_fee);
                }
                else {
                    maxWireFee = Amounts.getZero(amount.currency);
                }
                const download = {
                    contractData: {
                        amount,
                        contractTermsHash: contractTermsHash,
                        fulfillmentUrl: (_c = parsedContractTerms.fulfillment_url) !== null && _c !== void 0 ? _c : "",
                        merchantBaseUrl: parsedContractTerms.merchant_base_url,
                        merchantPub: parsedContractTerms.merchant_pub,
                        merchantSig: backupPurchase.merchant_sig,
                        orderId: parsedContractTerms.order_id,
                        summary: parsedContractTerms.summary,
                        autoRefund: parsedContractTerms.auto_refund,
                        maxWireFee,
                        payDeadline: parsedContractTerms.pay_deadline,
                        refundDeadline: parsedContractTerms.refund_deadline,
                        wireFeeAmortization: parsedContractTerms.wire_fee_amortization || 1,
                        allowedAuditors: parsedContractTerms.auditors.map((x) => ({
                            auditorBaseUrl: x.url,
                            auditorPub: x.auditor_pub,
                        })),
                        allowedExchanges: parsedContractTerms.exchanges.map((x) => ({
                            exchangeBaseUrl: x.url,
                            exchangePub: x.master_pub,
                        })),
                        timestamp: parsedContractTerms.timestamp,
                        wireMethod: parsedContractTerms.wire_method,
                        wireInfoHash: parsedContractTerms.h_wire,
                        maxDepositFee: Amounts.parseOrThrow(parsedContractTerms.max_fee),
                        merchant: parsedContractTerms.merchant,
                        products: parsedContractTerms.products,
                        summaryI18n: parsedContractTerms.summary_i18n,
                        deliveryDate: parsedContractTerms.delivery_date,
                        deliveryLocation: parsedContractTerms.delivery_location,
                    },
                    contractTermsRaw: backupPurchase.contract_terms_raw,
                };
                await tx.purchases.put({
                    proposalId: backupPurchase.proposal_id,
                    noncePriv: backupPurchase.nonce_priv,
                    noncePub: cryptoComp.proposalNoncePrivToPub[backupPurchase.nonce_priv],
                    autoRefundDeadline: TalerProtocolTimestamp.never(),
                    refundAwaiting: undefined,
                    timestampAccept: backupPurchase.timestamp_accept,
                    timestampFirstSuccessfulPay: backupPurchase.timestamp_first_successful_pay,
                    timestampLastRefundStatus: undefined,
                    merchantPaySig: backupPurchase.merchant_pay_sig,
                    lastSessionId: undefined,
                    abortStatus,
                    download,
                    paymentSubmitPending: !backupPurchase.timestamp_first_successful_pay,
                    refundQueryRequested: false,
                    payCoinSelection: await recoverPayCoinSelection(tx, download.contractData, backupPurchase),
                    coinDepositPermissions: undefined,
                    totalPayCost: Amounts.parseOrThrow(backupPurchase.total_pay_cost),
                    refunds,
                    payCoinSelectionUid: backupPurchase.pay_coins_uid,
                });
            }
        }
        for (const backupRefreshGroup of backupBlob.refresh_groups) {
            const ts = makeEventId(TombstoneTag.DeleteRefreshGroup, backupRefreshGroup.refresh_group_id);
            if (tombstoneSet.has(ts)) {
                continue;
            }
            const existingRg = await tx.refreshGroups.get(backupRefreshGroup.refresh_group_id);
            if (!existingRg) {
                let reason;
                switch (backupRefreshGroup.reason) {
                    case BackupRefreshReason.AbortPay:
                        reason = RefreshReason.AbortPay;
                        break;
                    case BackupRefreshReason.BackupRestored:
                        reason = RefreshReason.BackupRestored;
                        break;
                    case BackupRefreshReason.Manual:
                        reason = RefreshReason.Manual;
                        break;
                    case BackupRefreshReason.Pay:
                        reason = RefreshReason.PayMerchant;
                        break;
                    case BackupRefreshReason.Recoup:
                        reason = RefreshReason.Recoup;
                        break;
                    case BackupRefreshReason.Refund:
                        reason = RefreshReason.Refund;
                        break;
                    case BackupRefreshReason.Scheduled:
                        reason = RefreshReason.Scheduled;
                        break;
                }
                const refreshSessionPerCoin = [];
                for (const oldCoin of backupRefreshGroup.old_coins) {
                    const c = await tx.coins.get(oldCoin.coin_pub);
                    checkBackupInvariant(!!c);
                    if (oldCoin.refresh_session) {
                        const denomSel = await getDenomSelStateFromBackup(tx, c.exchangeBaseUrl, oldCoin.refresh_session.new_denoms);
                        refreshSessionPerCoin.push({
                            sessionSecretSeed: oldCoin.refresh_session.session_secret_seed,
                            norevealIndex: oldCoin.refresh_session.noreveal_index,
                            newDenoms: oldCoin.refresh_session.new_denoms.map((x) => ({
                                count: x.count,
                                denomPubHash: x.denom_pub_hash,
                            })),
                            amountRefreshOutput: denomSel.totalCoinValue,
                        });
                    }
                    else {
                        refreshSessionPerCoin.push(undefined);
                    }
                }
                await tx.refreshGroups.put({
                    timestampFinished: backupRefreshGroup.timestamp_finish,
                    timestampCreated: backupRefreshGroup.timestamp_created,
                    refreshGroupId: backupRefreshGroup.refresh_group_id,
                    reason,
                    lastErrorPerCoin: {},
                    oldCoinPubs: backupRefreshGroup.old_coins.map((x) => x.coin_pub),
                    statusPerCoin: backupRefreshGroup.old_coins.map((x) => x.finished
                        ? RefreshCoinStatus.Finished
                        : RefreshCoinStatus.Pending),
                    operationStatus: backupRefreshGroup.timestamp_finish
                        ? OperationStatus.Finished
                        : OperationStatus.Pending,
                    inputPerCoin: backupRefreshGroup.old_coins.map((x) => Amounts.parseOrThrow(x.input_amount)),
                    estimatedOutputPerCoin: backupRefreshGroup.old_coins.map((x) => Amounts.parseOrThrow(x.estimated_output_amount)),
                    refreshSessionPerCoin,
                });
            }
        }
        for (const backupTip of backupBlob.tips) {
            const ts = makeEventId(TombstoneTag.DeleteTip, backupTip.wallet_tip_id);
            if (tombstoneSet.has(ts)) {
                continue;
            }
            const existingTip = await tx.tips.get(backupTip.wallet_tip_id);
            if (!existingTip) {
                const denomsSel = await getDenomSelStateFromBackup(tx, backupTip.exchange_base_url, backupTip.selected_denoms);
                await tx.tips.put({
                    acceptedTimestamp: backupTip.timestamp_accepted,
                    createdTimestamp: backupTip.timestamp_created,
                    denomsSel,
                    exchangeBaseUrl: backupTip.exchange_base_url,
                    merchantBaseUrl: backupTip.exchange_base_url,
                    merchantTipId: backupTip.merchant_tip_id,
                    pickedUpTimestamp: backupTip.timestamp_finished,
                    secretSeed: backupTip.secret_seed,
                    tipAmountEffective: denomsSel.totalCoinValue,
                    tipAmountRaw: Amounts.parseOrThrow(backupTip.tip_amount_raw),
                    tipExpiration: backupTip.timestamp_expiration,
                    walletTipId: backupTip.wallet_tip_id,
                    denomSelUid: backupTip.selected_denoms_uid,
                });
            }
        }
        // We now process tombstones.
        // The import code above should already prevent
        // importing things that are tombstoned,
        // but we do tombstone processing last just to be sure.
        for (const tombstone of tombstoneSet) {
            const [type, ...rest] = tombstone.split(":");
            if (type === TombstoneTag.DeleteDepositGroup) {
                await tx.depositGroups.delete(rest[0]);
            }
            else if (type === TombstoneTag.DeletePayment) {
                await tx.purchases.delete(rest[0]);
                await tx.proposals.delete(rest[0]);
            }
            else if (type === TombstoneTag.DeleteRefreshGroup) {
                await tx.refreshGroups.delete(rest[0]);
            }
            else if (type === TombstoneTag.DeleteRefund) ;
            else if (type === TombstoneTag.DeleteTip) {
                await tx.tips.delete(rest[0]);
            }
            else if (type === TombstoneTag.DeleteWithdrawalGroup) {
                await tx.withdrawalGroups.delete(rest[0]);
            }
            else {
                logger$e.warn(`unable to process tombstone of type '${type}'`);
            }
        }
    });
}

/*
 This file is part of GNU Taler
 (C) 2020 Taler Systems SA

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$d = new Logger("operations/backup.ts");
function concatArrays(xs) {
    let len = 0;
    for (const x of xs) {
        len += x.byteLength;
    }
    const out = new Uint8Array(len);
    let offset = 0;
    for (const x of xs) {
        out.set(x, offset);
        offset += x.length;
    }
    return out;
}
const magic = "TLRWBK01";
/**
 * Encrypt the backup.
 *
 * Blob format:
 * Magic "TLRWBK01" (8 bytes)
 * Nonce (24 bytes)
 * Compressed JSON blob (rest)
 */
async function encryptBackup(config, blob) {
    const chunks = [];
    chunks.push(stringToBytes(magic));
    const nonceStr = config.lastBackupNonce;
    checkLogicInvariant(!!nonceStr);
    const nonce = decodeCrock(nonceStr).slice(0, 24);
    chunks.push(nonce);
    const backupJsonContent = canonicalJson(blob);
    logger$d.trace("backup JSON size", backupJsonContent.length);
    const compressedContent = gzipSync(stringToBytes(backupJsonContent), {
        mtime: 0,
    });
    const secret = deriveBlobSecret(config);
    const encrypted = secretbox(compressedContent, nonce.slice(0, 24), secret);
    chunks.push(encrypted);
    return concatArrays(chunks);
}
/**
 * Compute cryptographic values for a backup blob.
 *
 * FIXME: Take data that we already know from the DB.
 * FIXME: Move computations into crypto worker.
 */
async function computeBackupCryptoData(cryptoApi, backupContent) {
    const cryptoData = {
        coinPrivToCompletedCoin: {},
        rsaDenomPubToHash: {},
        proposalIdToContractTermsHash: {},
        proposalNoncePrivToPub: {},
        reservePrivToPub: {},
    };
    for (const backupExchangeDetails of backupContent.exchange_details) {
        for (const backupDenom of backupExchangeDetails.denominations) {
            if (backupDenom.denom_pub.cipher !== DenomKeyType.Rsa) {
                throw Error("unsupported cipher");
            }
            for (const backupCoin of backupDenom.coins) {
                const coinPub = encodeCrock(eddsaGetPublic(decodeCrock(backupCoin.coin_priv)));
                const blindedCoin = rsaBlind(hash(decodeCrock(backupCoin.coin_priv)), decodeCrock(backupCoin.blinding_key), decodeCrock(backupDenom.denom_pub.rsa_public_key));
                cryptoData.coinPrivToCompletedCoin[backupCoin.coin_priv] = {
                    coinEvHash: encodeCrock(hash(blindedCoin)),
                    coinPub,
                };
            }
            cryptoData.rsaDenomPubToHash[backupDenom.denom_pub.rsa_public_key] =
                encodeCrock(hashDenomPub(backupDenom.denom_pub));
        }
    }
    for (const backupWg of backupContent.withdrawal_groups) {
        cryptoData.reservePrivToPub[backupWg.reserve_priv] = encodeCrock(eddsaGetPublic(decodeCrock(backupWg.reserve_priv)));
    }
    for (const prop of backupContent.proposals) {
        const { h: contractTermsHash } = await cryptoApi.hashString({
            str: canonicalJson(prop.contract_terms_raw),
        });
        const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(prop.nonce_priv)));
        cryptoData.proposalNoncePrivToPub[prop.nonce_priv] = noncePub;
        cryptoData.proposalIdToContractTermsHash[prop.proposal_id] =
            contractTermsHash;
    }
    for (const purch of backupContent.purchases) {
        const { h: contractTermsHash } = await cryptoApi.hashString({
            str: canonicalJson(purch.contract_terms_raw),
        });
        const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(purch.nonce_priv)));
        cryptoData.proposalNoncePrivToPub[purch.nonce_priv] = noncePub;
        cryptoData.proposalIdToContractTermsHash[purch.proposal_id] =
            contractTermsHash;
    }
    return cryptoData;
}
function deriveAccountKeyPair(bc, providerUrl) {
    const privateKey = kdf(32, decodeCrock(bc.walletRootPriv), stringToBytes("taler-sync-account-key-salt"), stringToBytes(providerUrl));
    return {
        eddsaPriv: privateKey,
        eddsaPub: eddsaGetPublic(privateKey),
    };
}
function deriveBlobSecret(bc) {
    return kdf(32, decodeCrock(bc.walletRootPriv), stringToBytes("taler-sync-blob-secret-salt"), stringToBytes("taler-sync-blob-secret-info"));
}
function getNextBackupTimestamp() {
    // FIXME:  Randomize!
    return AbsoluteTime.toTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), durationFromSpec({ minutes: 5 })));
}
async function runBackupCycleForProvider(ws, args) {
    const provider = await ws.db
        .mktx((x) => [x.backupProviders])
        .runReadOnly(async (tx) => {
        return tx.backupProviders.get(args.backupProviderBaseUrl);
    });
    if (!provider) {
        logger$d.warn("provider disappeared");
        return {
            type: OperationAttemptResultType.Finished,
            result: undefined,
        };
    }
    const backupJson = await exportBackup(ws);
    const backupConfig = await provideBackupState(ws);
    const encBackup = await encryptBackup(backupConfig, backupJson);
    const currentBackupHash = hash(encBackup);
    const accountKeyPair = deriveAccountKeyPair(backupConfig, provider.baseUrl);
    const newHash = encodeCrock(currentBackupHash);
    const oldHash = provider.lastBackupHash;
    logger$d.trace(`trying to upload backup to ${provider.baseUrl}`);
    logger$d.trace(`old hash ${oldHash}, new hash ${newHash}`);
    const syncSigResp = await ws.cryptoApi.makeSyncSignature({
        newHash: encodeCrock(currentBackupHash),
        oldHash: provider.lastBackupHash,
        accountPriv: encodeCrock(accountKeyPair.eddsaPriv),
    });
    logger$d.trace(`sync signature is ${syncSigResp}`);
    const accountBackupUrl = new URL$1(`/backups/${encodeCrock(accountKeyPair.eddsaPub)}`, provider.baseUrl);
    const resp = await ws.http.fetch(accountBackupUrl.href, {
        method: "POST",
        body: encBackup,
        headers: Object.assign({ "content-type": "application/octet-stream", "sync-signature": syncSigResp.sig, "if-none-match": newHash }, (provider.lastBackupHash
            ? {
                "if-match": provider.lastBackupHash,
            }
            : {})),
    });
    logger$d.trace(`sync response status: ${resp.status}`);
    if (resp.status === HttpStatusCode.NotModified) {
        await ws.db
            .mktx((x) => [x.backupProviders])
            .runReadWrite(async (tx) => {
            const prov = await tx.backupProviders.get(provider.baseUrl);
            if (!prov) {
                return;
            }
            prov.lastBackupCycleTimestamp = TalerProtocolTimestamp.now();
            prov.state = {
                tag: BackupProviderStateTag.Ready,
                nextBackupTimestamp: getNextBackupTimestamp(),
            };
            await tx.backupProviders.put(prov);
        });
        return {
            type: OperationAttemptResultType.Finished,
            result: undefined,
        };
    }
    if (resp.status === HttpStatusCode.PaymentRequired) {
        logger$d.trace("payment required for backup");
        logger$d.trace(`headers: ${j2s(resp.headers)}`);
        const talerUri = resp.headers.get("taler");
        if (!talerUri) {
            throw Error("no taler URI available to pay provider");
        }
        const res = await preparePayForUri(ws, talerUri);
        let proposalId = res.proposalId;
        let doPay = false;
        switch (res.status) {
            case PreparePayResultType.InsufficientBalance:
                // FIXME: record in provider state!
                logger$d.warn("insufficient balance to pay for backup provider");
                proposalId = res.proposalId;
                break;
            case PreparePayResultType.PaymentPossible:
                doPay = true;
                break;
            case PreparePayResultType.AlreadyConfirmed:
                break;
        }
        // FIXME: check if the provider is overcharging us!
        await ws.db
            .mktx((x) => [x.backupProviders, x.operationRetries])
            .runReadWrite(async (tx) => {
            const provRec = await tx.backupProviders.get(provider.baseUrl);
            checkDbInvariant(!!provRec);
            const ids = new Set(provRec.paymentProposalIds);
            ids.add(proposalId);
            provRec.paymentProposalIds = Array.from(ids).sort();
            provRec.currentPaymentProposalId = proposalId;
            // FIXME: allocate error code for this!
            await tx.backupProviders.put(provRec);
            const opId = RetryTags.forBackup(provRec);
            await scheduleRetryInTx(ws, tx, opId);
        });
        if (doPay) {
            const confirmRes = await confirmPay(ws, proposalId);
            switch (confirmRes.type) {
                case ConfirmPayResultType.Pending:
                    logger$d.warn("payment not yet finished yet");
                    break;
            }
        }
        if (args.retryAfterPayment) {
            return await runBackupCycleForProvider(ws, Object.assign(Object.assign({}, args), { retryAfterPayment: false }));
        }
        return {
            type: OperationAttemptResultType.Pending,
            result: undefined,
        };
    }
    if (resp.status === HttpStatusCode.NoContent) {
        await ws.db
            .mktx((x) => [x.backupProviders])
            .runReadWrite(async (tx) => {
            const prov = await tx.backupProviders.get(provider.baseUrl);
            if (!prov) {
                return;
            }
            prov.lastBackupHash = encodeCrock(currentBackupHash);
            prov.lastBackupCycleTimestamp = TalerProtocolTimestamp.now();
            prov.state = {
                tag: BackupProviderStateTag.Ready,
                nextBackupTimestamp: getNextBackupTimestamp(),
            };
            await tx.backupProviders.put(prov);
        });
        return {
            type: OperationAttemptResultType.Finished,
            result: undefined,
        };
    }
    if (resp.status === HttpStatusCode.Conflict) {
        logger$d.info("conflicting backup found");
        const backupEnc = new Uint8Array(await resp.bytes());
        const backupConfig = await provideBackupState(ws);
        const blob = await decryptBackup(backupConfig, backupEnc);
        const cryptoData = await computeBackupCryptoData(ws.cryptoApi, blob);
        await importBackup(ws, blob, cryptoData);
        await ws.db
            .mktx((x) => [x.backupProviders, x.operationRetries])
            .runReadWrite(async (tx) => {
            const prov = await tx.backupProviders.get(provider.baseUrl);
            if (!prov) {
                logger$d.warn("backup provider not found anymore");
                return;
            }
            prov.lastBackupHash = encodeCrock(hash(backupEnc));
            // FIXME: Allocate error code for this situation?
            // FIXME: Add operation retry record!
            const opId = RetryTags.forBackup(prov);
            await scheduleRetryInTx(ws, tx, opId);
            prov.state = {
                tag: BackupProviderStateTag.Retrying,
            };
            await tx.backupProviders.put(prov);
        });
        logger$d.info("processed existing backup");
        // Now upload our own, merged backup.
        return await runBackupCycleForProvider(ws, Object.assign(Object.assign({}, args), { retryAfterPayment: false }));
    }
    // Some other response that we did not expect!
    logger$d.error("parsing error response");
    const err = await readTalerErrorResponse(resp);
    logger$d.error(`got error response from backup provider: ${j2s(err)}`);
    return {
        type: OperationAttemptResultType.Error,
        errorDetail: err,
    };
}
async function processBackupForProvider(ws, backupProviderBaseUrl) {
    const provider = await ws.db
        .mktx((x) => [x.backupProviders])
        .runReadOnly(async (tx) => {
        return await tx.backupProviders.get(backupProviderBaseUrl);
    });
    if (!provider) {
        throw Error("unknown backup provider");
    }
    logger$d.info(`running backup for provider ${backupProviderBaseUrl}`);
    return await runBackupCycleForProvider(ws, {
        backupProviderBaseUrl: provider.baseUrl,
        retryAfterPayment: true,
    });
}
const codecForRemoveBackupProvider = () => buildCodecForObject()
    .property("provider", codecForString())
    .build("RemoveBackupProviderRequest");
async function removeBackupProvider(ws, req) {
    await ws.db
        .mktx((x) => [x.backupProviders])
        .runReadWrite(async (tx) => {
        await tx.backupProviders.delete(req.provider);
    });
}
const codecForRunBackupCycle = () => buildCodecForObject()
    .property("providers", codecOptional(codecForList(codecForString())))
    .build("RunBackupCycleRequest");
/**
 * Do one backup cycle that consists of:
 * 1. Exporting a backup and try to upload it.
 *    Stop if this step succeeds.
 * 2. Download, verify and import backups from connected sync accounts.
 * 3. Upload the updated backup blob.
 */
async function runBackupCycle(ws, req) {
    const providers = await ws.db
        .mktx((x) => [x.backupProviders])
        .runReadOnly(async (tx) => {
        if (req.providers) {
            const rs = await Promise.all(req.providers.map((id) => tx.backupProviders.get(id)));
            return rs.filter(notEmpty);
        }
        return await tx.backupProviders.iter().toArray();
    });
    for (const provider of providers) {
        await runBackupCycleForProvider(ws, {
            backupProviderBaseUrl: provider.baseUrl,
            retryAfterPayment: true,
        });
    }
}
const codecForSyncTermsOfServiceResponse = () => buildCodecForObject()
    .property("storage_limit_in_megabytes", codecForNumber())
    .property("annual_fee", codecForAmountString())
    .property("version", codecForString())
    .build("SyncTermsOfServiceResponse");
const codecForAddBackupProviderRequest = () => buildCodecForObject()
    .property("backupProviderBaseUrl", codecForString())
    .property("name", codecForString())
    .property("activate", codecOptional(codecForBoolean()))
    .build("AddBackupProviderRequest");
async function addBackupProvider(ws, req) {
    logger$d.info(`adding backup provider ${j2s(req)}`);
    await provideBackupState(ws);
    const canonUrl = canonicalizeBaseUrl(req.backupProviderBaseUrl);
    await ws.db
        .mktx((x) => [x.backupProviders])
        .runReadWrite(async (tx) => {
        const oldProv = await tx.backupProviders.get(canonUrl);
        if (oldProv) {
            logger$d.info("old backup provider found");
            if (req.activate) {
                oldProv.state = {
                    tag: BackupProviderStateTag.Ready,
                    nextBackupTimestamp: TalerProtocolTimestamp.now(),
                };
                logger$d.info("setting existing backup provider to active");
                await tx.backupProviders.put(oldProv);
            }
            return;
        }
    });
    const termsUrl = new URL$1("config", canonUrl);
    const resp = await ws.http.get(termsUrl.href);
    const terms = await readSuccessResponseJsonOrThrow(resp, codecForSyncTermsOfServiceResponse());
    await ws.db
        .mktx((x) => [x.backupProviders])
        .runReadWrite(async (tx) => {
        let state;
        if (req.activate) {
            state = {
                tag: BackupProviderStateTag.Ready,
                nextBackupTimestamp: TalerProtocolTimestamp.now(),
            };
        }
        else {
            state = {
                tag: BackupProviderStateTag.Provisional,
            };
        }
        await tx.backupProviders.put({
            state,
            name: req.name,
            terms: {
                annualFee: terms.annual_fee,
                storageLimitInMegabytes: terms.storage_limit_in_megabytes,
                supportedProtocolVersion: terms.version,
            },
            paymentProposalIds: [],
            baseUrl: canonUrl,
            uids: [encodeCrock(getRandomBytes(32))],
        });
    });
}
async function importBackupPlain(ws, blob) {
    // FIXME: parse
    const backup = blob;
    const cryptoData = await computeBackupCryptoData(ws.cryptoApi, backup);
    await importBackup(ws, blob, cryptoData);
}
var ProviderPaymentType;
(function (ProviderPaymentType) {
    ProviderPaymentType["Unpaid"] = "unpaid";
    ProviderPaymentType["Pending"] = "pending";
    ProviderPaymentType["InsufficientBalance"] = "insufficient-balance";
    ProviderPaymentType["Paid"] = "paid";
    ProviderPaymentType["TermsChanged"] = "terms-changed";
})(ProviderPaymentType || (ProviderPaymentType = {}));
async function getProviderPaymentInfo(ws, provider) {
    if (!provider.currentPaymentProposalId) {
        return {
            type: ProviderPaymentType.Unpaid,
        };
    }
    const status = await checkPaymentByProposalId(ws, provider.currentPaymentProposalId);
    if (status.status === PreparePayResultType.InsufficientBalance) {
        return {
            type: ProviderPaymentType.InsufficientBalance,
        };
    }
    if (status.status === PreparePayResultType.PaymentPossible) {
        return {
            type: ProviderPaymentType.Pending,
        };
    }
    if (status.status === PreparePayResultType.AlreadyConfirmed) {
        if (status.paid) {
            return {
                type: ProviderPaymentType.Paid,
                paidUntil: AbsoluteTime.addDuration(AbsoluteTime.fromTimestamp(status.contractTerms.timestamp), durationFromSpec({ years: 1 })),
            };
        }
        else {
            return {
                type: ProviderPaymentType.Pending,
            };
        }
    }
    throw Error("not reached");
}
/**
 * Get information about the current state of wallet backups.
 */
async function getBackupInfo(ws) {
    var _a;
    const backupConfig = await provideBackupState(ws);
    const providerRecords = await ws.db
        .mktx((x) => [x.backupProviders, x.operationRetries])
        .runReadOnly(async (tx) => {
        return await tx.backupProviders.iter().mapAsync(async (bp) => {
            const opId = RetryTags.forBackup(bp);
            const retryRecord = await tx.operationRetries.get(opId);
            return {
                provider: bp,
                retryRecord,
            };
        });
    });
    const providers = [];
    for (const x of providerRecords) {
        providers.push({
            active: x.provider.state.tag !== BackupProviderStateTag.Provisional,
            syncProviderBaseUrl: x.provider.baseUrl,
            lastSuccessfulBackupTimestamp: x.provider.lastBackupCycleTimestamp,
            paymentProposalIds: x.provider.paymentProposalIds,
            lastError: x.provider.state.tag === BackupProviderStateTag.Retrying
                ? (_a = x.retryRecord) === null || _a === void 0 ? void 0 : _a.lastError
                : undefined,
            paymentStatus: await getProviderPaymentInfo(ws, x.provider),
            terms: x.provider.terms,
            name: x.provider.name,
        });
    }
    return {
        deviceId: backupConfig.deviceId,
        walletRootPub: backupConfig.walletRootPub,
        providers,
    };
}
/**
 * Get backup recovery information, including the wallet's
 * private key.
 */
async function getBackupRecovery(ws) {
    const bs = await provideBackupState(ws);
    const providers = await ws.db
        .mktx((x) => [x.backupProviders])
        .runReadOnly(async (tx) => {
        return await tx.backupProviders.iter().toArray();
    });
    return {
        providers: providers
            .filter((x) => x.state.tag !== BackupProviderStateTag.Provisional)
            .map((x) => {
            return {
                url: x.baseUrl,
            };
        }),
        walletRootPriv: bs.walletRootPriv,
    };
}
async function backupRecoveryTheirs(ws, br) {
    await ws.db
        .mktx((x) => [x.config, x.backupProviders])
        .runReadWrite(async (tx) => {
        let backupStateEntry = await tx.config.get(WALLET_BACKUP_STATE_KEY);
        checkDbInvariant(!!backupStateEntry);
        checkDbInvariant(backupStateEntry.key === WALLET_BACKUP_STATE_KEY);
        backupStateEntry.value.lastBackupNonce = undefined;
        backupStateEntry.value.lastBackupTimestamp = undefined;
        backupStateEntry.value.lastBackupCheckTimestamp = undefined;
        backupStateEntry.value.lastBackupPlainHash = undefined;
        backupStateEntry.value.walletRootPriv = br.walletRootPriv;
        backupStateEntry.value.walletRootPub = encodeCrock(eddsaGetPublic(decodeCrock(br.walletRootPriv)));
        await tx.config.put(backupStateEntry);
        for (const prov of br.providers) {
            const existingProv = await tx.backupProviders.get(prov.url);
            if (!existingProv) {
                await tx.backupProviders.put({
                    baseUrl: prov.url,
                    name: "not-defined",
                    paymentProposalIds: [],
                    state: {
                        tag: BackupProviderStateTag.Ready,
                        nextBackupTimestamp: TalerProtocolTimestamp.now(),
                    },
                    uids: [encodeCrock(getRandomBytes(32))],
                });
            }
        }
        const providers = await tx.backupProviders.iter().toArray();
        for (const prov of providers) {
            prov.lastBackupCycleTimestamp = undefined;
            prov.lastBackupHash = undefined;
            await tx.backupProviders.put(prov);
        }
    });
}
async function backupRecoveryOurs(ws, br) {
    throw Error("not implemented");
}
async function loadBackupRecovery(ws, br) {
    const bs = await provideBackupState(ws);
    const providers = await ws.db
        .mktx((x) => [x.backupProviders])
        .runReadOnly(async (tx) => {
        return await tx.backupProviders.iter().toArray();
    });
    let strategy = br.strategy;
    if (br.recovery.walletRootPriv != bs.walletRootPriv &&
        providers.length > 0 &&
        !strategy) {
        throw Error("recovery load strategy must be specified for wallet with existing providers");
    }
    else if (!strategy) {
        // Default to using the new key if we don't have providers yet.
        strategy = RecoveryMergeStrategy.Theirs;
    }
    if (strategy === RecoveryMergeStrategy.Theirs) {
        return backupRecoveryTheirs(ws, br.recovery);
    }
    else {
        return backupRecoveryOurs(ws, br.recovery);
    }
}
async function decryptBackup(backupConfig, data) {
    const rMagic = bytesToString(data.slice(0, 8));
    if (rMagic != magic) {
        throw Error("invalid backup file (magic tag mismatch)");
    }
    const nonce = data.slice(8, 8 + 24);
    const box = data.slice(8 + 24);
    const secret = deriveBlobSecret(backupConfig);
    const dataCompressed = secretbox_open(box, nonce, secret);
    if (!dataCompressed) {
        throw Error("decryption failed");
    }
    return JSON.parse(bytesToString(gunzipSync(dataCompressed)));
}

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$c = new Logger("operations/balance.ts");
/**
 * Get balance information.
 */
async function getBalancesInsideTransaction(ws, tx) {
    const balanceStore = {};
    /**
     * Add amount to a balance field, both for
     * the slicing by exchange and currency.
     */
    const initBalance = (currency) => {
        const b = balanceStore[currency];
        if (!b) {
            balanceStore[currency] = {
                available: Amounts.getZero(currency),
                pendingIncoming: Amounts.getZero(currency),
                pendingOutgoing: Amounts.getZero(currency),
            };
        }
        return balanceStore[currency];
    };
    await tx.coins.iter().forEach((c) => {
        // Only count fresh coins, as dormant coins will
        // already be in a refresh session.
        if (c.status === CoinStatus.Fresh) {
            const b = initBalance(c.currentAmount.currency);
            b.available = Amounts.add(b.available, c.currentAmount).amount;
        }
    });
    await tx.refreshGroups.iter().forEach((r) => {
        // Don't count finished refreshes, since the refresh already resulted
        // in coins being added to the wallet.
        if (r.timestampFinished) {
            return;
        }
        for (let i = 0; i < r.oldCoinPubs.length; i++) {
            const session = r.refreshSessionPerCoin[i];
            if (session) {
                const b = initBalance(session.amountRefreshOutput.currency);
                // We are always assuming the refresh will succeed, thus we
                // report the output as available balance.
                b.available = Amounts.add(b.available, session.amountRefreshOutput).amount;
            }
            else {
                const b = initBalance(r.inputPerCoin[i].currency);
                b.available = Amounts.add(b.available, r.estimatedOutputPerCoin[i]).amount;
            }
        }
    });
    await tx.withdrawalGroups.iter().forEach((wds) => {
        if (wds.timestampFinish) {
            return;
        }
        const b = initBalance(wds.denomsSel.totalWithdrawCost.currency);
        b.pendingIncoming = Amounts.add(b.pendingIncoming, wds.denomsSel.totalCoinValue).amount;
    });
    const balancesResponse = {
        balances: [],
    };
    Object.keys(balanceStore)
        .sort()
        .forEach((c) => {
        const v = balanceStore[c];
        balancesResponse.balances.push({
            available: Amounts.stringify(v.available),
            pendingIncoming: Amounts.stringify(v.pendingIncoming),
            pendingOutgoing: Amounts.stringify(v.pendingOutgoing),
            hasPendingTransactions: false,
            requiresUserInput: false,
        });
    });
    return balancesResponse;
}
/**
 * Get detailed balance information, sliced by exchange and by currency.
 */
async function getBalances(ws) {
    logger$c.trace("starting to compute balance");
    const wbal = await ws.db
        .mktx((x) => [x.coins, x.refreshGroups, x.purchases, x.withdrawalGroups])
        .runReadOnly(async (tx) => {
        return getBalancesInsideTransaction(ws, tx);
    });
    logger$c.trace("finished computing wallet balance");
    return wbal;
}

/*
 This file is part of GNU Taler
 (C) 2021 Taler Systems S.A..

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$b = new Logger("taler-wallet-core:merchants.ts");
async function getMerchantInfo(ws, merchantBaseUrl) {
    const canonBaseUrl = canonicalizeBaseUrl(merchantBaseUrl);
    const existingInfo = ws.merchantInfoCache[canonBaseUrl];
    if (existingInfo) {
        return existingInfo;
    }
    const configUrl = new URL$1("config", canonBaseUrl);
    const resp = await ws.http.get(configUrl.href);
    const configResp = await readSuccessResponseJsonOrThrow(resp, codecForMerchantConfigResponse());
    logger$b.info(`merchant "${canonBaseUrl}" reports protocol ${configResp.version}"`);
    const parsedVersion = LibtoolVersion.parseVersion(configResp.version);
    if (!parsedVersion) {
        throw Error("invalid merchant version");
    }
    const merchantInfo = {
        protocolVersionCurrent: parsedVersion.current,
    };
    ws.merchantInfoCache[canonBaseUrl] = merchantInfo;
    return merchantInfo;
}

/*
 This file is part of GNU Taler
 (C) 2022 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$a = new Logger("operations/peer-to-peer.ts");
async function selectPeerCoins(ws, tx, instructedAmount) {
    var _a;
    const exchanges = await tx.exchanges.iter().toArray();
    for (const exch of exchanges) {
        if (((_a = exch.detailsPointer) === null || _a === void 0 ? void 0 : _a.currency) !== instructedAmount.currency) {
            continue;
        }
        const coins = (await tx.coins.indexes.byBaseUrl.getAll(exch.baseUrl)).filter((x) => x.status === CoinStatus.Fresh);
        const coinInfos = [];
        for (const coin of coins) {
            const denom = await ws.getDenomInfo(ws, tx, coin.exchangeBaseUrl, coin.denomPubHash);
            if (!denom) {
                throw Error("denom not found");
            }
            coinInfos.push({
                coinPub: coin.coinPub,
                feeDeposit: denom.feeDeposit,
                value: denom.value,
                denomPubHash: denom.denomPubHash,
                coinPriv: coin.coinPriv,
                denomSig: coin.denomSig,
                maxAge: coin.maxAge,
                ageCommitmentProof: coin.ageCommitmentProof,
            });
        }
        if (coinInfos.length === 0) {
            continue;
        }
        coinInfos.sort((o1, o2) => -Amounts.cmp(o1.value, o2.value) ||
            strcmp(o1.denomPubHash, o2.denomPubHash));
        let amountAcc = Amounts.getZero(instructedAmount.currency);
        let depositFeesAcc = Amounts.getZero(instructedAmount.currency);
        const resCoins = [];
        for (const coin of coinInfos) {
            if (Amounts.cmp(amountAcc, instructedAmount) >= 0) {
                const res = {
                    exchangeBaseUrl: exch.baseUrl,
                    coins: resCoins,
                    depositFees: depositFeesAcc,
                };
                return res;
            }
            const gap = Amounts.add(coin.feeDeposit, Amounts.sub(instructedAmount, amountAcc).amount).amount;
            const contrib = Amounts.min(gap, coin.value);
            amountAcc = Amounts.add(amountAcc, Amounts.sub(contrib, coin.feeDeposit).amount).amount;
            depositFeesAcc = Amounts.add(depositFeesAcc, coin.feeDeposit).amount;
            resCoins.push({
                coinPriv: coin.coinPriv,
                coinPub: coin.coinPub,
                contribution: Amounts.stringify(contrib),
                denomPubHash: coin.denomPubHash,
                denomSig: coin.denomSig,
                ageCommitmentProof: coin.ageCommitmentProof,
            });
        }
        continue;
    }
    return undefined;
}
async function initiatePeerToPeerPush(ws, req) {
    const instructedAmount = Amounts.parseOrThrow(req.amount);
    const pursePair = await ws.cryptoApi.createEddsaKeypair({});
    const mergePair = await ws.cryptoApi.createEddsaKeypair({});
    const purseExpiration = AbsoluteTime.toTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), Duration.fromSpec({ days: 2 })));
    const contractTerms = Object.assign(Object.assign({}, req.partialContractTerms), { purse_expiration: purseExpiration, amount: req.amount });
    const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
    const econtractResp = await ws.cryptoApi.encryptContractForMerge({
        contractTerms,
        mergePriv: mergePair.priv,
        pursePriv: pursePair.priv,
        pursePub: pursePair.pub,
    });
    const coinSelRes = await ws.db
        .mktx((x) => [
        x.exchanges,
        x.coins,
        x.coinAvailability,
        x.denominations,
        x.refreshGroups,
        x.peerPullPaymentInitiations,
        x.peerPushPaymentInitiations,
    ])
        .runReadWrite(async (tx) => {
        const sel = await selectPeerCoins(ws, tx, instructedAmount);
        if (!sel) {
            return undefined;
        }
        await spendCoins(ws, tx, {
            allocationId: `peer-push:${pursePair.pub}`,
            coinPubs: sel.coins.map((x) => x.coinPub),
            contributions: sel.coins.map((x) => Amounts.parseOrThrow(x.contribution)),
            refreshReason: RefreshReason.PayPeerPush,
        });
        await tx.peerPushPaymentInitiations.add({
            amount: Amounts.stringify(instructedAmount),
            contractPriv: econtractResp.contractPriv,
            contractTerms,
            exchangeBaseUrl: sel.exchangeBaseUrl,
            mergePriv: mergePair.priv,
            mergePub: mergePair.pub,
            // FIXME: only set this later!
            purseCreated: true,
            purseExpiration: purseExpiration,
            pursePriv: pursePair.priv,
            pursePub: pursePair.pub,
            timestampCreated: TalerProtocolTimestamp.now(),
        });
        return sel;
    });
    logger$a.info(`selected p2p coins (push): ${j2s(coinSelRes)}`);
    if (!coinSelRes) {
        throw Error("insufficient balance");
    }
    const purseSigResp = await ws.cryptoApi.signPurseCreation({
        hContractTerms,
        mergePub: mergePair.pub,
        minAge: 0,
        purseAmount: Amounts.stringify(instructedAmount),
        purseExpiration,
        pursePriv: pursePair.priv,
    });
    const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
        exchangeBaseUrl: coinSelRes.exchangeBaseUrl,
        pursePub: pursePair.pub,
        coins: coinSelRes.coins,
    });
    const createPurseUrl = new URL(`purses/${pursePair.pub}/create`, coinSelRes.exchangeBaseUrl);
    const httpResp = await ws.http.postJson(createPurseUrl.href, {
        amount: Amounts.stringify(instructedAmount),
        merge_pub: mergePair.pub,
        purse_sig: purseSigResp.sig,
        h_contract_terms: hContractTerms,
        purse_expiration: purseExpiration,
        deposits: depositSigsResp.deposits,
        min_age: 0,
        econtract: econtractResp.econtract,
    });
    const resp = await httpResp.json();
    logger$a.info(`resp: ${j2s(resp)}`);
    if (httpResp.status !== 200) {
        throw Error("got error response from exchange");
    }
    return {
        contractPriv: econtractResp.contractPriv,
        mergePriv: mergePair.priv,
        pursePub: pursePair.pub,
        exchangeBaseUrl: coinSelRes.exchangeBaseUrl,
        talerUri: constructPayPushUri({
            exchangeBaseUrl: coinSelRes.exchangeBaseUrl,
            contractPriv: econtractResp.contractPriv,
        }),
        transactionId: makeEventId(TransactionType.PeerPushDebit, pursePair.pub),
    };
}
const codecForExchangePurseStatus = () => buildCodecForObject()
    .property("balance", codecForAmountString())
    .build("ExchangePurseStatus");
async function checkPeerPushPayment(ws, req) {
    // FIXME: Check if existing record exists!
    const uri = parsePayPushUri(req.talerUri);
    if (!uri) {
        throw Error("got invalid taler://pay-push URI");
    }
    const exchangeBaseUrl = uri.exchangeBaseUrl;
    await updateExchangeFromUrl(ws, exchangeBaseUrl);
    const contractPriv = uri.contractPriv;
    const contractPub = encodeCrock(eddsaGetPublic(decodeCrock(contractPriv)));
    const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl);
    const contractHttpResp = await ws.http.get(getContractUrl.href);
    const contractResp = await readSuccessResponseJsonOrThrow(contractHttpResp, codecForExchangeGetContractResponse());
    const pursePub = contractResp.purse_pub;
    const dec = await ws.cryptoApi.decryptContractForMerge({
        ciphertext: contractResp.econtract,
        contractPriv: contractPriv,
        pursePub: pursePub,
    });
    const getPurseUrl = new URL(`purses/${pursePub}/deposit`, exchangeBaseUrl);
    const purseHttpResp = await ws.http.get(getPurseUrl.href);
    const purseStatus = await readSuccessResponseJsonOrThrow(purseHttpResp, codecForExchangePurseStatus());
    const peerPushPaymentIncomingId = encodeCrock(getRandomBytes(32));
    await ws.db
        .mktx((x) => [x.peerPushPaymentIncoming])
        .runReadWrite(async (tx) => {
        await tx.peerPushPaymentIncoming.add({
            peerPushPaymentIncomingId,
            contractPriv: contractPriv,
            exchangeBaseUrl: exchangeBaseUrl,
            mergePriv: dec.mergePriv,
            pursePub: pursePub,
            timestamp: TalerProtocolTimestamp.now(),
            contractTerms: dec.contractTerms,
        });
    });
    return {
        amount: purseStatus.balance,
        contractTerms: dec.contractTerms,
        peerPushPaymentIncomingId,
    };
}
function talerPaytoFromExchangeReserve(exchangeBaseUrl, reservePub) {
    const url = new URL(exchangeBaseUrl);
    let proto;
    if (url.protocol === "http:") {
        proto = "taler-reserve-http";
    }
    else if (url.protocol === "https:") {
        proto = "taler-reserve";
    }
    else {
        throw Error(`unsupported exchange base URL protocol (${url.protocol})`);
    }
    let path = url.pathname;
    if (!path.endsWith("/")) {
        path = path + "/";
    }
    return `payto://${proto}/${url.host}${url.pathname}${reservePub}`;
}
async function getMergeReserveInfo(ws, req) {
    // We have to eagerly create the key pair outside of the transaction,
    // due to the async crypto API.
    const newReservePair = await ws.cryptoApi.createEddsaKeypair({});
    const mergeReserveInfo = await ws.db
        .mktx((x) => [x.exchanges, x.withdrawalGroups])
        .runReadWrite(async (tx) => {
        const ex = await tx.exchanges.get(req.exchangeBaseUrl);
        checkDbInvariant(!!ex);
        if (ex.currentMergeReserveInfo) {
            return ex.currentMergeReserveInfo;
        }
        await tx.exchanges.put(ex);
        ex.currentMergeReserveInfo = {
            reservePriv: newReservePair.priv,
            reservePub: newReservePair.pub,
        };
        return ex.currentMergeReserveInfo;
    });
    return mergeReserveInfo;
}
async function acceptPeerPushPayment(ws, req) {
    const peerInc = await ws.db
        .mktx((x) => [x.peerPushPaymentIncoming])
        .runReadOnly(async (tx) => {
        return tx.peerPushPaymentIncoming.get(req.peerPushPaymentIncomingId);
    });
    if (!peerInc) {
        throw Error(`can't accept unknown incoming p2p push payment (${req.peerPushPaymentIncomingId})`);
    }
    await updateExchangeFromUrl(ws, peerInc.exchangeBaseUrl);
    const amount = Amounts.parseOrThrow(peerInc.contractTerms.amount);
    const mergeReserveInfo = await getMergeReserveInfo(ws, {
        exchangeBaseUrl: peerInc.exchangeBaseUrl,
    });
    const mergeTimestamp = TalerProtocolTimestamp.now();
    const reservePayto = talerPaytoFromExchangeReserve(peerInc.exchangeBaseUrl, mergeReserveInfo.reservePub);
    const sigRes = await ws.cryptoApi.signPurseMerge({
        contractTermsHash: ContractTermsUtil.hashContractTerms(peerInc.contractTerms),
        flags: 1 /* WalletAccountMergeFlags.MergeFullyPaidPurse */,
        mergePriv: peerInc.mergePriv,
        mergeTimestamp: mergeTimestamp,
        purseAmount: Amounts.stringify(amount),
        purseExpiration: peerInc.contractTerms.purse_expiration,
        purseFee: Amounts.stringify(Amounts.getZero(amount.currency)),
        pursePub: peerInc.pursePub,
        reservePayto,
        reservePriv: mergeReserveInfo.reservePriv,
    });
    const mergePurseUrl = new URL(`purses/${peerInc.pursePub}/merge`, peerInc.exchangeBaseUrl);
    const mergeReq = {
        payto_uri: reservePayto,
        merge_timestamp: mergeTimestamp,
        merge_sig: sigRes.mergeSig,
        reserve_sig: sigRes.accountSig,
    };
    const mergeHttpReq = await ws.http.postJson(mergePurseUrl.href, mergeReq);
    logger$a.info(`merge request: ${j2s(mergeReq)}`);
    const res = await readSuccessResponseJsonOrThrow(mergeHttpReq, codecForAny());
    logger$a.info(`merge response: ${j2s(res)}`);
    const wg = await internalCreateWithdrawalGroup(ws, {
        amount,
        wgInfo: {
            withdrawalType: "peer-push-credit" /* WithdrawalRecordType.PeerPushCredit */,
            contractTerms: peerInc.contractTerms,
        },
        exchangeBaseUrl: peerInc.exchangeBaseUrl,
        reserveStatus: WithdrawalGroupStatus.QueryingStatus,
        reserveKeyPair: {
            priv: mergeReserveInfo.reservePriv,
            pub: mergeReserveInfo.reservePub,
        },
    });
    return {
        transactionId: makeEventId(TransactionType.PeerPushCredit, wg.withdrawalGroupId),
    };
}
/**
 * FIXME: Bad name!
 */
async function acceptPeerPullPayment(ws, req) {
    const peerPullInc = await ws.db
        .mktx((x) => [x.peerPullPaymentIncoming])
        .runReadOnly(async (tx) => {
        return tx.peerPullPaymentIncoming.get(req.peerPullPaymentIncomingId);
    });
    if (!peerPullInc) {
        throw Error(`can't accept unknown incoming p2p pull payment (${req.peerPullPaymentIncomingId})`);
    }
    const instructedAmount = Amounts.parseOrThrow(peerPullInc.contractTerms.amount);
    const coinSelRes = await ws.db
        .mktx((x) => [
        x.exchanges,
        x.coins,
        x.denominations,
        x.refreshGroups,
        x.peerPullPaymentIncoming,
        x.coinAvailability,
    ])
        .runReadWrite(async (tx) => {
        const sel = await selectPeerCoins(ws, tx, instructedAmount);
        if (!sel) {
            return undefined;
        }
        await spendCoins(ws, tx, {
            allocationId: `peer-pull:${req.peerPullPaymentIncomingId}`,
            coinPubs: sel.coins.map((x) => x.coinPub),
            contributions: sel.coins.map((x) => Amounts.parseOrThrow(x.contribution)),
            refreshReason: RefreshReason.PayPeerPull,
        });
        const pi = await tx.peerPullPaymentIncoming.get(req.peerPullPaymentIncomingId);
        if (!pi) {
            throw Error();
        }
        pi.accepted = true;
        await tx.peerPullPaymentIncoming.put(pi);
        return sel;
    });
    logger$a.info(`selected p2p coins (pull): ${j2s(coinSelRes)}`);
    if (!coinSelRes) {
        throw Error("insufficient balance");
    }
    const pursePub = peerPullInc.pursePub;
    const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
        exchangeBaseUrl: coinSelRes.exchangeBaseUrl,
        pursePub,
        coins: coinSelRes.coins,
    });
    const purseDepositUrl = new URL(`purses/${pursePub}/deposit`, coinSelRes.exchangeBaseUrl);
    const depositPayload = {
        deposits: depositSigsResp.deposits,
    };
    const httpResp = await ws.http.postJson(purseDepositUrl.href, depositPayload);
    const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
    logger$a.trace(`purse deposit response: ${j2s(resp)}`);
    return {
        transactionId: makeEventId(TransactionType.PeerPullDebit, req.peerPullPaymentIncomingId),
    };
}
async function checkPeerPullPayment(ws, req) {
    const uri = parsePayPullUri(req.talerUri);
    if (!uri) {
        throw Error("got invalid taler://pay-push URI");
    }
    const exchangeBaseUrl = uri.exchangeBaseUrl;
    const contractPriv = uri.contractPriv;
    const contractPub = encodeCrock(eddsaGetPublic(decodeCrock(contractPriv)));
    const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl);
    const contractHttpResp = await ws.http.get(getContractUrl.href);
    const contractResp = await readSuccessResponseJsonOrThrow(contractHttpResp, codecForExchangeGetContractResponse());
    const pursePub = contractResp.purse_pub;
    const dec = await ws.cryptoApi.decryptContractForDeposit({
        ciphertext: contractResp.econtract,
        contractPriv: contractPriv,
        pursePub: pursePub,
    });
    const getPurseUrl = new URL(`purses/${pursePub}/merge`, exchangeBaseUrl);
    const purseHttpResp = await ws.http.get(getPurseUrl.href);
    const purseStatus = await readSuccessResponseJsonOrThrow(purseHttpResp, codecForExchangePurseStatus());
    const peerPullPaymentIncomingId = encodeCrock(getRandomBytes(32));
    await ws.db
        .mktx((x) => [x.peerPullPaymentIncoming])
        .runReadWrite(async (tx) => {
        await tx.peerPullPaymentIncoming.add({
            peerPullPaymentIncomingId,
            contractPriv: contractPriv,
            exchangeBaseUrl: exchangeBaseUrl,
            pursePub: pursePub,
            timestampCreated: TalerProtocolTimestamp.now(),
            contractTerms: dec.contractTerms,
            paid: false,
            accepted: false,
        });
    });
    return {
        amount: purseStatus.balance,
        contractTerms: dec.contractTerms,
        peerPullPaymentIncomingId,
    };
}
/**
 * Initiate a peer pull payment.
 */
async function initiatePeerRequestForPay(ws, req) {
    await updateExchangeFromUrl(ws, req.exchangeBaseUrl);
    const mergeReserveInfo = await getMergeReserveInfo(ws, {
        exchangeBaseUrl: req.exchangeBaseUrl,
    });
    const mergeTimestamp = TalerProtocolTimestamp.now();
    const pursePair = await ws.cryptoApi.createEddsaKeypair({});
    const mergePair = await ws.cryptoApi.createEddsaKeypair({});
    const purseExpiration = AbsoluteTime.toTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), Duration.fromSpec({ days: 2 })));
    const reservePayto = talerPaytoFromExchangeReserve(req.exchangeBaseUrl, mergeReserveInfo.reservePub);
    const contractTerms = Object.assign(Object.assign({}, req.partialContractTerms), { amount: req.amount, purse_expiration: purseExpiration });
    const econtractResp = await ws.cryptoApi.encryptContractForDeposit({
        contractTerms,
        pursePriv: pursePair.priv,
        pursePub: pursePair.pub,
    });
    const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
    const purseFee = Amounts.stringify(Amounts.getZero(Amounts.parseOrThrow(req.amount).currency));
    const sigRes = await ws.cryptoApi.signReservePurseCreate({
        contractTermsHash: hContractTerms,
        flags: 3 /* WalletAccountMergeFlags.CreateWithPurseFee */,
        mergePriv: mergePair.priv,
        mergeTimestamp: mergeTimestamp,
        purseAmount: req.amount,
        purseExpiration: purseExpiration,
        purseFee: purseFee,
        pursePriv: pursePair.priv,
        pursePub: pursePair.pub,
        reservePayto,
        reservePriv: mergeReserveInfo.reservePriv,
    });
    await ws.db
        .mktx((x) => [x.peerPullPaymentInitiations])
        .runReadWrite(async (tx) => {
        await tx.peerPullPaymentInitiations.put({
            amount: req.amount,
            contractTerms,
            exchangeBaseUrl: req.exchangeBaseUrl,
            pursePriv: pursePair.priv,
            pursePub: pursePair.pub,
        });
    });
    const reservePurseReqBody = {
        merge_sig: sigRes.mergeSig,
        merge_timestamp: mergeTimestamp,
        h_contract_terms: hContractTerms,
        merge_pub: mergePair.pub,
        min_age: 0,
        purse_expiration: purseExpiration,
        purse_fee: purseFee,
        purse_pub: pursePair.pub,
        purse_sig: sigRes.purseSig,
        purse_value: req.amount,
        reserve_sig: sigRes.accountSig,
        econtract: econtractResp.econtract,
    };
    logger$a.info(`reserve purse request: ${j2s(reservePurseReqBody)}`);
    const reservePurseMergeUrl = new URL(`reserves/${mergeReserveInfo.reservePub}/purse`, req.exchangeBaseUrl);
    const httpResp = await ws.http.postJson(reservePurseMergeUrl.href, reservePurseReqBody);
    const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
    logger$a.info(`reserve merge response: ${j2s(resp)}`);
    const wg = await internalCreateWithdrawalGroup(ws, {
        amount: Amounts.parseOrThrow(req.amount),
        wgInfo: {
            withdrawalType: "peer-pull-credit" /* WithdrawalRecordType.PeerPullCredit */,
            contractTerms,
            contractPriv: econtractResp.contractPriv,
        },
        exchangeBaseUrl: req.exchangeBaseUrl,
        reserveStatus: WithdrawalGroupStatus.QueryingStatus,
        reserveKeyPair: {
            priv: mergeReserveInfo.reservePriv,
            pub: mergeReserveInfo.reservePub,
        },
    });
    return {
        talerUri: constructPayPullUri({
            exchangeBaseUrl: req.exchangeBaseUrl,
            contractPriv: econtractResp.contractPriv,
        }),
        transactionId: makeEventId(TransactionType.PeerPullCredit, wg.withdrawalGroupId),
    };
}

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
async function gatherExchangePending(tx, now, resp) {
    await tx.exchanges.iter().forEachAsync(async (exch) => {
        var _a;
        const opTag = RetryTags.forExchangeUpdate(exch);
        let opr = await tx.operationRetries.get(opTag);
        resp.pendingOperations.push({
            type: PendingTaskType.ExchangeUpdate,
            id: opTag,
            givesLifeness: false,
            timestampDue: (_a = opr === null || opr === void 0 ? void 0 : opr.retryInfo.nextRetry) !== null && _a !== void 0 ? _a : AbsoluteTime.fromTimestamp(exch.nextUpdate),
            exchangeBaseUrl: exch.baseUrl,
            lastError: opr === null || opr === void 0 ? void 0 : opr.lastError,
        });
        // We only schedule a check for auto-refresh if the exchange update
        // was successful.
        if (!(opr === null || opr === void 0 ? void 0 : opr.lastError)) {
            resp.pendingOperations.push({
                type: PendingTaskType.ExchangeCheckRefresh,
                id: RetryTags.forExchangeCheckRefresh(exch),
                timestampDue: AbsoluteTime.fromTimestamp(exch.nextRefreshCheck),
                givesLifeness: false,
                exchangeBaseUrl: exch.baseUrl,
            });
        }
    });
}
async function gatherRefreshPending(tx, now, resp) {
    var _a;
    const refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll(OperationStatus.Pending);
    for (const r of refreshGroups) {
        if (r.timestampFinished) {
            return;
        }
        if (r.frozen) {
            return;
        }
        const opId = RetryTags.forRefresh(r);
        const retryRecord = await tx.operationRetries.get(opId);
        resp.pendingOperations.push({
            type: PendingTaskType.Refresh,
            id: opId,
            givesLifeness: true,
            timestampDue: (_a = retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.retryInfo.nextRetry) !== null && _a !== void 0 ? _a : AbsoluteTime.now(),
            refreshGroupId: r.refreshGroupId,
            finishedPerCoin: r.statusPerCoin.map((x) => x === RefreshCoinStatus.Finished),
            retryInfo: retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.retryInfo,
        });
    }
}
async function gatherWithdrawalPending(tx, now, resp) {
    var _a, _b;
    const wsrs = await tx.withdrawalGroups.indexes.byStatus.getAll(GlobalIDB.KeyRange.bound(OperationStatusRange.ACTIVE_START, OperationStatusRange.ACTIVE_END));
    for (const wsr of wsrs) {
        if (wsr.timestampFinish) {
            return;
        }
        const opTag = RetryTags.forWithdrawal(wsr);
        let opr = await tx.operationRetries.get(opTag);
        const now = AbsoluteTime.now();
        if (!opr) {
            opr = {
                id: opTag,
                retryInfo: {
                    firstTry: now,
                    nextRetry: now,
                    retryCounter: 0,
                },
            };
        }
        resp.pendingOperations.push({
            type: PendingTaskType.Withdraw,
            id: opTag,
            givesLifeness: true,
            timestampDue: (_b = (_a = opr.retryInfo) === null || _a === void 0 ? void 0 : _a.nextRetry) !== null && _b !== void 0 ? _b : AbsoluteTime.now(),
            withdrawalGroupId: wsr.withdrawalGroupId,
            lastError: opr.lastError,
            retryInfo: opr.retryInfo,
        });
    }
}
async function gatherProposalPending(tx, now, resp) {
    await tx.proposals.iter().forEachAsync(async (proposal) => {
        var _a, _b;
        if (proposal.proposalStatus == ProposalStatus.Proposed) ;
        else if (proposal.proposalStatus == ProposalStatus.Downloading) {
            const opId = RetryTags.forProposalClaim(proposal);
            const retryRecord = await tx.operationRetries.get(opId);
            const timestampDue = (_b = (_a = retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.retryInfo) === null || _a === void 0 ? void 0 : _a.nextRetry) !== null && _b !== void 0 ? _b : AbsoluteTime.now();
            resp.pendingOperations.push({
                type: PendingTaskType.ProposalDownload,
                id: opId,
                givesLifeness: true,
                timestampDue,
                merchantBaseUrl: proposal.merchantBaseUrl,
                orderId: proposal.orderId,
                proposalId: proposal.proposalId,
                proposalTimestamp: proposal.timestamp,
                lastError: retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.lastError,
                retryInfo: retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.retryInfo,
            });
        }
    });
}
async function gatherDepositPending(tx, now, resp) {
    var _a;
    const dgs = await tx.depositGroups.indexes.byStatus.getAll(OperationStatus.Pending);
    for (const dg of dgs) {
        if (dg.timestampFinished) {
            return;
        }
        const opId = RetryTags.forDeposit(dg);
        const retryRecord = await tx.operationRetries.get(opId);
        const timestampDue = (_a = retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.retryInfo.nextRetry) !== null && _a !== void 0 ? _a : AbsoluteTime.now();
        resp.pendingOperations.push({
            type: PendingTaskType.Deposit,
            id: opId,
            givesLifeness: true,
            timestampDue,
            depositGroupId: dg.depositGroupId,
            lastError: retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.lastError,
            retryInfo: retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.retryInfo,
        });
    }
}
async function gatherTipPending(tx, now, resp) {
    await tx.tips.iter().forEachAsync(async (tip) => {
        var _a;
        // FIXME: The tip record needs a proper status field!
        if (tip.pickedUpTimestamp) {
            return;
        }
        const opId = RetryTags.forTipPickup(tip);
        const retryRecord = await tx.operationRetries.get(opId);
        if (tip.acceptedTimestamp) {
            resp.pendingOperations.push({
                type: PendingTaskType.TipPickup,
                id: opId,
                givesLifeness: true,
                timestampDue: (_a = retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.retryInfo.nextRetry) !== null && _a !== void 0 ? _a : AbsoluteTime.now(),
                merchantBaseUrl: tip.merchantBaseUrl,
                tipId: tip.walletTipId,
                merchantTipId: tip.merchantTipId,
            });
        }
    });
}
async function gatherPurchasePending(tx, now, resp) {
    // FIXME: Only iter purchases with some "active" flag!
    await tx.purchases.iter().forEachAsync(async (pr) => {
        var _a, _b;
        if (pr.paymentSubmitPending &&
            pr.abortStatus === AbortStatus.None &&
            !pr.payFrozen) {
            const payOpId = RetryTags.forPay(pr);
            const payRetryRecord = await tx.operationRetries.get(payOpId);
            const timestampDue = (_a = payRetryRecord === null || payRetryRecord === void 0 ? void 0 : payRetryRecord.retryInfo.nextRetry) !== null && _a !== void 0 ? _a : AbsoluteTime.now();
            resp.pendingOperations.push({
                type: PendingTaskType.Pay,
                id: payOpId,
                givesLifeness: true,
                timestampDue,
                isReplay: false,
                proposalId: pr.proposalId,
                retryInfo: payRetryRecord === null || payRetryRecord === void 0 ? void 0 : payRetryRecord.retryInfo,
                lastError: payRetryRecord === null || payRetryRecord === void 0 ? void 0 : payRetryRecord.lastError,
            });
        }
        if (pr.refundQueryRequested) {
            const refundQueryOpId = RetryTags.forRefundQuery(pr);
            const refundQueryRetryRecord = await tx.operationRetries.get(refundQueryOpId);
            resp.pendingOperations.push({
                type: PendingTaskType.RefundQuery,
                id: refundQueryOpId,
                givesLifeness: true,
                timestampDue: (_b = refundQueryRetryRecord === null || refundQueryRetryRecord === void 0 ? void 0 : refundQueryRetryRecord.retryInfo.nextRetry) !== null && _b !== void 0 ? _b : AbsoluteTime.now(),
                proposalId: pr.proposalId,
                retryInfo: refundQueryRetryRecord === null || refundQueryRetryRecord === void 0 ? void 0 : refundQueryRetryRecord.retryInfo,
                lastError: refundQueryRetryRecord === null || refundQueryRetryRecord === void 0 ? void 0 : refundQueryRetryRecord.lastError,
            });
        }
    });
}
async function gatherRecoupPending(tx, now, resp) {
    await tx.recoupGroups.iter().forEachAsync(async (rg) => {
        var _a;
        if (rg.timestampFinished) {
            return;
        }
        const opId = RetryTags.forRecoup(rg);
        const retryRecord = await tx.operationRetries.get(opId);
        resp.pendingOperations.push({
            type: PendingTaskType.Recoup,
            id: opId,
            givesLifeness: true,
            timestampDue: (_a = retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.retryInfo.nextRetry) !== null && _a !== void 0 ? _a : AbsoluteTime.now(),
            recoupGroupId: rg.recoupGroupId,
            retryInfo: retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.retryInfo,
            lastError: retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.lastError,
        });
    });
}
async function gatherBackupPending(tx, now, resp) {
    await tx.backupProviders.iter().forEachAsync(async (bp) => {
        var _a, _b;
        const opId = RetryTags.forBackup(bp);
        const retryRecord = await tx.operationRetries.get(opId);
        if (bp.state.tag === BackupProviderStateTag.Ready) {
            resp.pendingOperations.push({
                type: PendingTaskType.Backup,
                id: opId,
                givesLifeness: false,
                timestampDue: AbsoluteTime.fromTimestamp(bp.state.nextBackupTimestamp),
                backupProviderBaseUrl: bp.baseUrl,
                lastError: undefined,
            });
        }
        else if (bp.state.tag === BackupProviderStateTag.Retrying) {
            resp.pendingOperations.push({
                type: PendingTaskType.Backup,
                id: opId,
                givesLifeness: false,
                timestampDue: (_b = (_a = retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.retryInfo) === null || _a === void 0 ? void 0 : _a.nextRetry) !== null && _b !== void 0 ? _b : AbsoluteTime.now(),
                backupProviderBaseUrl: bp.baseUrl,
                retryInfo: retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.retryInfo,
                lastError: retryRecord === null || retryRecord === void 0 ? void 0 : retryRecord.lastError,
            });
        }
    });
}
async function getPendingOperations(ws) {
    const now = AbsoluteTime.now();
    return await ws.db
        .mktx((x) => [
        x.backupProviders,
        x.exchanges,
        x.exchangeDetails,
        x.refreshGroups,
        x.coins,
        x.withdrawalGroups,
        x.proposals,
        x.tips,
        x.purchases,
        x.planchets,
        x.depositGroups,
        x.recoupGroups,
        x.operationRetries,
    ])
        .runReadWrite(async (tx) => {
        const resp = {
            pendingOperations: [],
        };
        await gatherExchangePending(tx, now, resp);
        await gatherRefreshPending(tx, now, resp);
        await gatherWithdrawalPending(tx, now, resp);
        await gatherProposalPending(tx, now, resp);
        await gatherDepositPending(tx, now, resp);
        await gatherTipPending(tx, now, resp);
        await gatherPurchasePending(tx, now, resp);
        await gatherRecoupPending(tx, now, resp);
        await gatherBackupPending(tx, now, resp);
        return resp;
    });
}

/*
 This file is part of GNU Taler
 (C) 2019-2020 Taler Systems SA

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$9 = new Logger("operations/recoup.ts");
/**
 * Store a recoup group record in the database after marking
 * a coin in the group as finished.
 */
async function putGroupAsFinished(ws, tx, recoupGroup, coinIdx) {
    logger$9.trace(`setting coin ${coinIdx} of ${recoupGroup.coinPubs.length} as finished`);
    if (recoupGroup.timestampFinished) {
        return;
    }
    recoupGroup.recoupFinishedPerCoin[coinIdx] = true;
    await tx.recoupGroups.put(recoupGroup);
}
async function recoupTipCoin(ws, recoupGroupId, coinIdx, coin) {
    // We can't really recoup a coin we got via tipping.
    // Thus we just put the coin to sleep.
    // FIXME: somehow report this to the user
    await ws.db
        .mktx((stores) => [
        stores.recoupGroups,
        stores.denominations,
        stores.refreshGroups,
        stores.coins,
    ])
        .runReadWrite(async (tx) => {
        const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
        if (!recoupGroup) {
            return;
        }
        if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
            return;
        }
        await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
    });
}
async function recoupWithdrawCoin(ws, recoupGroupId, coinIdx, coin, cs) {
    const reservePub = cs.reservePub;
    const denomInfo = await ws.db
        .mktx((x) => [x.denominations])
        .runReadOnly(async (tx) => {
        const denomInfo = await ws.getDenomInfo(ws, tx, coin.exchangeBaseUrl, coin.denomPubHash);
        return denomInfo;
    });
    if (!denomInfo) {
        // FIXME:  We should at least emit some pending operation / warning for this?
        return;
    }
    ws.notify({
        type: NotificationType.RecoupStarted,
    });
    const recoupRequest = await ws.cryptoApi.createRecoupRequest({
        blindingKey: coin.blindingKey,
        coinPriv: coin.coinPriv,
        coinPub: coin.coinPub,
        denomPub: denomInfo.denomPub,
        denomPubHash: coin.denomPubHash,
        denomSig: coin.denomSig,
    });
    const reqUrl = new URL$1(`/coins/${coin.coinPub}/recoup`, coin.exchangeBaseUrl);
    logger$9.trace(`requesting recoup via ${reqUrl.href}`);
    const resp = await ws.http.postJson(reqUrl.href, recoupRequest);
    const recoupConfirmation = await readSuccessResponseJsonOrThrow(resp, codecForRecoupConfirmation());
    logger$9.trace(`got recoup confirmation ${j2s(recoupConfirmation)}`);
    if (recoupConfirmation.reserve_pub !== reservePub) {
        throw Error(`Coin's reserve doesn't match reserve on recoup`);
    }
    // FIXME: verify that our expectations about the amount match
    await ws.db
        .mktx((x) => [x.coins, x.denominations, x.recoupGroups, x.refreshGroups])
        .runReadWrite(async (tx) => {
        const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
        if (!recoupGroup) {
            return;
        }
        if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
            return;
        }
        const updatedCoin = await tx.coins.get(coin.coinPub);
        if (!updatedCoin) {
            return;
        }
        updatedCoin.status = CoinStatus.Dormant;
        const currency = updatedCoin.currentAmount.currency;
        updatedCoin.currentAmount = Amounts.getZero(currency);
        await tx.coins.put(updatedCoin);
        await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
    });
    ws.notify({
        type: NotificationType.RecoupFinished,
    });
}
async function recoupRefreshCoin(ws, recoupGroupId, coinIdx, coin, cs) {
    const d = await ws.db
        .mktx((x) => [x.coins, x.denominations])
        .runReadOnly(async (tx) => {
        const denomInfo = await ws.getDenomInfo(ws, tx, coin.exchangeBaseUrl, coin.denomPubHash);
        if (!denomInfo) {
            return;
        }
        return { denomInfo };
    });
    if (!d) {
        // FIXME:  We should at least emit some pending operation / warning for this?
        return;
    }
    ws.notify({
        type: NotificationType.RecoupStarted,
    });
    const recoupRequest = await ws.cryptoApi.createRecoupRefreshRequest({
        blindingKey: coin.blindingKey,
        coinPriv: coin.coinPriv,
        coinPub: coin.coinPub,
        denomPub: d.denomInfo.denomPub,
        denomPubHash: coin.denomPubHash,
        denomSig: coin.denomSig,
    });
    const reqUrl = new URL$1(`/coins/${coin.coinPub}/recoup-refresh`, coin.exchangeBaseUrl);
    logger$9.trace(`making recoup request for ${coin.coinPub}`);
    const resp = await ws.http.postJson(reqUrl.href, recoupRequest);
    const recoupConfirmation = await readSuccessResponseJsonOrThrow(resp, codecForRecoupConfirmation());
    if (recoupConfirmation.old_coin_pub != cs.oldCoinPub) {
        throw Error(`Coin's oldCoinPub doesn't match reserve on recoup`);
    }
    await ws.db
        .mktx((x) => [x.coins, x.denominations, x.recoupGroups, x.refreshGroups])
        .runReadWrite(async (tx) => {
        const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
        if (!recoupGroup) {
            return;
        }
        if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
            return;
        }
        const oldCoin = await tx.coins.get(cs.oldCoinPub);
        const revokedCoin = await tx.coins.get(coin.coinPub);
        if (!revokedCoin) {
            logger$9.warn("revoked coin for recoup not found");
            return;
        }
        if (!oldCoin) {
            logger$9.warn("refresh old coin for recoup not found");
            return;
        }
        revokedCoin.status = CoinStatus.Dormant;
        oldCoin.currentAmount = Amounts.add(oldCoin.currentAmount, recoupGroup.oldAmountPerCoin[coinIdx]).amount;
        logger$9.trace("recoup: setting old coin amount to", Amounts.stringify(oldCoin.currentAmount));
        recoupGroup.scheduleRefreshCoins.push(oldCoin.coinPub);
        await tx.coins.put(revokedCoin);
        await tx.coins.put(oldCoin);
        await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
    });
}
async function processRecoupGroup(ws, recoupGroupId, options = {}) {
    await runOperationHandlerForResult(await processRecoupGroupHandler(ws, recoupGroupId, options));
    return;
}
async function processRecoupGroupHandler(ws, recoupGroupId, options = {}) {
    var _a;
    (_a = options.forceNow) !== null && _a !== void 0 ? _a : false;
    let recoupGroup = await ws.db
        .mktx((x) => [x.recoupGroups])
        .runReadOnly(async (tx) => {
        return tx.recoupGroups.get(recoupGroupId);
    });
    if (!recoupGroup) {
        return OperationAttemptResult.finishedEmpty();
    }
    if (recoupGroup.timestampFinished) {
        logger$9.trace("recoup group finished");
        return OperationAttemptResult.finishedEmpty();
    }
    const ps = recoupGroup.coinPubs.map(async (x, i) => {
        try {
            await processRecoup(ws, recoupGroupId, i);
        }
        catch (e) {
            logger$9.warn(`processRecoup failed: ${e}`);
            throw e;
        }
    });
    await Promise.all(ps);
    recoupGroup = await ws.db
        .mktx((x) => [x.recoupGroups])
        .runReadOnly(async (tx) => {
        return tx.recoupGroups.get(recoupGroupId);
    });
    if (!recoupGroup) {
        return OperationAttemptResult.finishedEmpty();
    }
    for (const b of recoupGroup.recoupFinishedPerCoin) {
        if (!b) {
            return OperationAttemptResult.finishedEmpty();
        }
    }
    logger$9.info("all recoups of recoup group are finished");
    const reserveSet = new Set();
    const reservePrivMap = {};
    for (let i = 0; i < recoupGroup.coinPubs.length; i++) {
        const coinPub = recoupGroup.coinPubs[i];
        await ws.db
            .mktx((x) => [x.coins, x.reserves])
            .runReadOnly(async (tx) => {
            const coin = await tx.coins.get(coinPub);
            if (!coin) {
                throw Error(`Coin ${coinPub} not found, can't request recoup`);
            }
            if (coin.coinSource.type === CoinSourceType.Withdraw) {
                const reserve = await tx.reserves.indexes.byReservePub.get(coin.coinSource.reservePub);
                if (!reserve) {
                    return;
                }
                reserveSet.add(coin.coinSource.reservePub);
                reservePrivMap[coin.coinSource.reservePub] = reserve.reservePriv;
            }
        });
    }
    for (const reservePub of reserveSet) {
        const reserveUrl = new URL$1(`reserves/${reservePub}`, recoupGroup.exchangeBaseUrl);
        logger$9.info(`querying reserve status for recoup via ${reserveUrl}`);
        const resp = await ws.http.get(reserveUrl.href);
        const result = await readSuccessResponseJsonOrThrow(resp, codecForReserveStatus());
        await internalCreateWithdrawalGroup(ws, {
            amount: Amounts.parseOrThrow(result.balance),
            exchangeBaseUrl: recoupGroup.exchangeBaseUrl,
            reserveStatus: WithdrawalGroupStatus.QueryingStatus,
            reserveKeyPair: {
                pub: reservePub,
                priv: reservePrivMap[reservePub],
            },
            wgInfo: {
                withdrawalType: "recoup" /* WithdrawalRecordType.Recoup */,
            },
        });
    }
    await ws.db
        .mktx((x) => [
        x.recoupGroups,
        x.coinAvailability,
        x.denominations,
        x.refreshGroups,
        x.coins,
    ])
        .runReadWrite(async (tx) => {
        const rg2 = await tx.recoupGroups.get(recoupGroupId);
        if (!rg2) {
            return;
        }
        rg2.timestampFinished = TalerProtocolTimestamp.now();
        if (rg2.scheduleRefreshCoins.length > 0) {
            const refreshGroupId = await createRefreshGroup(ws, tx, rg2.scheduleRefreshCoins.map((x) => ({ coinPub: x })), RefreshReason.Recoup);
            processRefreshGroup(ws, refreshGroupId.refreshGroupId).catch((e) => {
                logger$9.error(`error while refreshing after recoup ${e}`);
            });
        }
        await tx.recoupGroups.put(rg2);
    });
    return OperationAttemptResult.finishedEmpty();
}
async function createRecoupGroup(ws, tx, exchangeBaseUrl, coinPubs) {
    const recoupGroupId = encodeCrock(getRandomBytes(32));
    const recoupGroup = {
        recoupGroupId,
        exchangeBaseUrl: exchangeBaseUrl,
        coinPubs: coinPubs,
        timestampFinished: undefined,
        timestampStarted: TalerProtocolTimestamp.now(),
        recoupFinishedPerCoin: coinPubs.map(() => false),
        // Will be populated later
        oldAmountPerCoin: [],
        scheduleRefreshCoins: [],
    };
    for (let coinIdx = 0; coinIdx < coinPubs.length; coinIdx++) {
        const coinPub = coinPubs[coinIdx];
        const coin = await tx.coins.get(coinPub);
        if (!coin) {
            await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
            continue;
        }
        if (Amounts.isZero(coin.currentAmount)) {
            await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
            continue;
        }
        recoupGroup.oldAmountPerCoin[coinIdx] = coin.currentAmount;
        coin.currentAmount = Amounts.getZero(coin.currentAmount.currency);
        await tx.coins.put(coin);
    }
    await tx.recoupGroups.put(recoupGroup);
    return recoupGroupId;
}
/**
 * Run the recoup protocol for a single coin in a recoup group.
 */
async function processRecoup(ws, recoupGroupId, coinIdx) {
    const coin = await ws.db
        .mktx((x) => [x.recoupGroups, x.coins])
        .runReadOnly(async (tx) => {
        const recoupGroup = await tx.recoupGroups.get(recoupGroupId);
        if (!recoupGroup) {
            return;
        }
        if (recoupGroup.timestampFinished) {
            return;
        }
        if (recoupGroup.recoupFinishedPerCoin[coinIdx]) {
            return;
        }
        const coinPub = recoupGroup.coinPubs[coinIdx];
        const coin = await tx.coins.get(coinPub);
        if (!coin) {
            throw Error(`Coin ${coinPub} not found, can't request recoup`);
        }
        return coin;
    });
    if (!coin) {
        return;
    }
    const cs = coin.coinSource;
    switch (cs.type) {
        case CoinSourceType.Tip:
            return recoupTipCoin(ws, recoupGroupId, coinIdx);
        case CoinSourceType.Refresh:
            return recoupRefreshCoin(ws, recoupGroupId, coinIdx, coin, cs);
        case CoinSourceType.Withdraw:
            return recoupWithdrawCoin(ws, recoupGroupId, coinIdx, coin, cs);
        default:
            throw Error("unknown coin source type");
    }
}

/*
 This file is part of GNU Taler
 (C) 2019-2019 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$8 = new Logger("refund.ts");
async function prepareRefund(ws, talerRefundUri) {
    const parseResult = parseRefundUri(talerRefundUri);
    logger$8.trace("preparing refund offer", parseResult);
    if (!parseResult) {
        throw Error("invalid refund URI");
    }
    const purchase = await ws.db
        .mktx((x) => [x.purchases])
        .runReadOnly(async (tx) => {
        return tx.purchases.indexes.byMerchantUrlAndOrderId.get([
            parseResult.merchantBaseUrl,
            parseResult.orderId,
        ]);
    });
    if (!purchase) {
        throw Error(`no purchase for the taler://refund/ URI (${talerRefundUri}) was found`);
    }
    const awaiting = await queryAndSaveAwaitingRefund(ws, purchase);
    const summary = calculateRefundSummary(purchase);
    const proposalId = purchase.proposalId;
    const { contractData: c } = purchase.download;
    return {
        proposalId,
        effectivePaid: Amounts.stringify(summary.amountEffectivePaid),
        gone: Amounts.stringify(summary.amountRefundGone),
        granted: Amounts.stringify(summary.amountRefundGranted),
        pending: summary.pendingAtExchange,
        awaiting: Amounts.stringify(awaiting),
        info: {
            contractTermsHash: c.contractTermsHash,
            merchant: c.merchant,
            orderId: c.orderId,
            products: c.products,
            summary: c.summary,
            fulfillmentMessage: c.fulfillmentMessage,
            summary_i18n: c.summaryI18n,
            fulfillmentMessage_i18n: c.fulfillmentMessageI18n,
        },
    };
}
function getRefundKey(d) {
    return `${d.coin_pub}-${d.rtransaction_id}`;
}
async function applySuccessfulRefund(tx, p, refreshCoinsMap, r) {
    // FIXME: check signature before storing it as valid!
    const refundKey = getRefundKey(r);
    const coin = await tx.coins.get(r.coin_pub);
    if (!coin) {
        logger$8.warn("coin not found, can't apply refund");
        return;
    }
    const denom = await tx.denominations.get([
        coin.exchangeBaseUrl,
        coin.denomPubHash,
    ]);
    if (!denom) {
        throw Error("inconsistent database");
    }
    refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub };
    const refundAmount = Amounts.parseOrThrow(r.refund_amount);
    const refundFee = denom.fees.feeRefund;
    coin.status = CoinStatus.Dormant;
    coin.currentAmount = Amounts.add(coin.currentAmount, refundAmount).amount;
    coin.currentAmount = Amounts.sub(coin.currentAmount, refundFee).amount;
    logger$8.trace(`coin amount after is ${Amounts.stringify(coin.currentAmount)}`);
    await tx.coins.put(coin);
    const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
        .iter(coin.exchangeBaseUrl)
        .toArray();
    const amountLeft = Amounts.sub(Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount))
        .amount, denom.fees.feeRefund).amount;
    const totalRefreshCostBound = getTotalRefreshCost(allDenoms, DenominationRecord.toDenomInfo(denom), amountLeft);
    p.refunds[refundKey] = {
        type: RefundState.Applied,
        obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
        executionTime: r.execution_time,
        refundAmount: Amounts.parseOrThrow(r.refund_amount),
        refundFee: denom.fees.feeRefund,
        totalRefreshCostBound,
        coinPub: r.coin_pub,
        rtransactionId: r.rtransaction_id,
    };
}
async function storePendingRefund(tx, p, r) {
    const refundKey = getRefundKey(r);
    const coin = await tx.coins.get(r.coin_pub);
    if (!coin) {
        logger$8.warn("coin not found, can't apply refund");
        return;
    }
    const denom = await tx.denominations.get([
        coin.exchangeBaseUrl,
        coin.denomPubHash,
    ]);
    if (!denom) {
        throw Error("inconsistent database");
    }
    const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
        .iter(coin.exchangeBaseUrl)
        .toArray();
    const amountLeft = Amounts.sub(Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount))
        .amount, denom.fees.feeRefund).amount;
    const totalRefreshCostBound = getTotalRefreshCost(allDenoms, DenominationRecord.toDenomInfo(denom), amountLeft);
    p.refunds[refundKey] = {
        type: RefundState.Pending,
        obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
        executionTime: r.execution_time,
        refundAmount: Amounts.parseOrThrow(r.refund_amount),
        refundFee: denom.fees.feeRefund,
        totalRefreshCostBound,
        coinPub: r.coin_pub,
        rtransactionId: r.rtransaction_id,
    };
}
async function storeFailedRefund(tx, p, refreshCoinsMap, r) {
    const refundKey = getRefundKey(r);
    const coin = await tx.coins.get(r.coin_pub);
    if (!coin) {
        logger$8.warn("coin not found, can't apply refund");
        return;
    }
    const denom = await tx.denominations.get([
        coin.exchangeBaseUrl,
        coin.denomPubHash,
    ]);
    if (!denom) {
        throw Error("inconsistent database");
    }
    const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
        .iter(coin.exchangeBaseUrl)
        .toArray();
    const amountLeft = Amounts.sub(Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount))
        .amount, denom.fees.feeRefund).amount;
    const totalRefreshCostBound = getTotalRefreshCost(allDenoms, DenominationRecord.toDenomInfo(denom), amountLeft);
    p.refunds[refundKey] = {
        type: RefundState.Failed,
        obtainedTime: TalerProtocolTimestamp.now(),
        executionTime: r.execution_time,
        refundAmount: Amounts.parseOrThrow(r.refund_amount),
        refundFee: denom.fees.feeRefund,
        totalRefreshCostBound,
        coinPub: r.coin_pub,
        rtransactionId: r.rtransaction_id,
    };
    if (p.abortStatus === AbortStatus.AbortRefund) {
        // Refund failed because the merchant didn't even try to deposit
        // the coin yet, so we try to refresh.
        if (r.exchange_code === TalerErrorCode.EXCHANGE_REFUND_DEPOSIT_NOT_FOUND) {
            const coin = await tx.coins.get(r.coin_pub);
            if (!coin) {
                logger$8.warn("coin not found, can't apply refund");
                return;
            }
            const denom = await tx.denominations.get([
                coin.exchangeBaseUrl,
                coin.denomPubHash,
            ]);
            if (!denom) {
                logger$8.warn("denomination for coin missing");
                return;
            }
            let contrib;
            for (let i = 0; i < p.payCoinSelection.coinPubs.length; i++) {
                if (p.payCoinSelection.coinPubs[i] === r.coin_pub) {
                    contrib = p.payCoinSelection.coinContributions[i];
                }
            }
            if (contrib) {
                coin.currentAmount = Amounts.add(coin.currentAmount, contrib).amount;
                coin.currentAmount = Amounts.sub(coin.currentAmount, denom.fees.feeRefund).amount;
            }
            refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub };
            await tx.coins.put(coin);
        }
    }
}
async function acceptRefunds(ws, proposalId, refunds, reason) {
    logger$8.trace("handling refunds", refunds);
    const now = TalerProtocolTimestamp.now();
    await ws.db
        .mktx((x) => [
        x.purchases,
        x.coins,
        x.coinAvailability,
        x.denominations,
        x.refreshGroups,
    ])
        .runReadWrite(async (tx) => {
        const p = await tx.purchases.get(proposalId);
        if (!p) {
            logger$8.error("purchase not found, not adding refunds");
            return;
        }
        const refreshCoinsMap = {};
        for (const refundStatus of refunds) {
            const refundKey = getRefundKey(refundStatus);
            const existingRefundInfo = p.refunds[refundKey];
            const isPermanentFailure = refundStatus.type === "failure" &&
                refundStatus.exchange_status >= 400 &&
                refundStatus.exchange_status < 500;
            // Already failed.
            if ((existingRefundInfo === null || existingRefundInfo === void 0 ? void 0 : existingRefundInfo.type) === RefundState.Failed) {
                continue;
            }
            // Already applied.
            if ((existingRefundInfo === null || existingRefundInfo === void 0 ? void 0 : existingRefundInfo.type) === RefundState.Applied) {
                continue;
            }
            // Still pending.
            if (refundStatus.type === "failure" &&
                !isPermanentFailure &&
                (existingRefundInfo === null || existingRefundInfo === void 0 ? void 0 : existingRefundInfo.type) === RefundState.Pending) {
                continue;
            }
            // Invariant: (!existingRefundInfo) || (existingRefundInfo === Pending)
            if (refundStatus.type === "success") {
                await applySuccessfulRefund(tx, p, refreshCoinsMap, refundStatus);
            }
            else if (isPermanentFailure) {
                await storeFailedRefund(tx, p, refreshCoinsMap, refundStatus);
            }
            else {
                await storePendingRefund(tx, p, refundStatus);
            }
        }
        const refreshCoinsPubs = Object.values(refreshCoinsMap);
        if (refreshCoinsPubs.length > 0) {
            await createRefreshGroup(ws, tx, refreshCoinsPubs, RefreshReason.Refund);
        }
        // Are we done with querying yet, or do we need to do another round
        // after a retry delay?
        let queryDone = true;
        if (p.timestampFirstSuccessfulPay &&
            p.autoRefundDeadline &&
            AbsoluteTime.cmp(AbsoluteTime.fromTimestamp(p.autoRefundDeadline), AbsoluteTime.fromTimestamp(now)) > 0) {
            queryDone = false;
        }
        let numPendingRefunds = 0;
        for (const ri of Object.values(p.refunds)) {
            switch (ri.type) {
                case RefundState.Pending:
                    numPendingRefunds++;
                    break;
            }
        }
        if (numPendingRefunds > 0) {
            queryDone = false;
        }
        if (queryDone) {
            p.timestampLastRefundStatus = now;
            p.refundQueryRequested = false;
            if (p.abortStatus === AbortStatus.AbortRefund) {
                p.abortStatus = AbortStatus.AbortFinished;
            }
            logger$8.trace("refund query done");
        }
        else {
            // No error, but we need to try again!
            p.timestampLastRefundStatus = now;
            logger$8.trace("refund query not done");
        }
        await tx.purchases.put(p);
    });
    ws.notify({
        type: NotificationType.RefundQueried,
    });
}
function calculateRefundSummary(p) {
    let amountRefundGranted = Amounts.getZero(p.download.contractData.amount.currency);
    let amountRefundGone = Amounts.getZero(p.download.contractData.amount.currency);
    let pendingAtExchange = false;
    Object.keys(p.refunds).forEach((rk) => {
        const refund = p.refunds[rk];
        if (refund.type === RefundState.Pending) {
            pendingAtExchange = true;
        }
        if (refund.type === RefundState.Applied ||
            refund.type === RefundState.Pending) {
            amountRefundGranted = Amounts.add(amountRefundGranted, Amounts.sub(refund.refundAmount, refund.refundFee, refund.totalRefreshCostBound).amount).amount;
        }
        else {
            amountRefundGone = Amounts.add(amountRefundGone, refund.refundAmount).amount;
        }
    });
    return {
        amountEffectivePaid: p.totalPayCost,
        amountRefundGone,
        amountRefundGranted,
        pendingAtExchange,
    };
}
/**
 * Accept a refund, return the contract hash for the contract
 * that was involved in the refund.
 */
async function applyRefund(ws, talerRefundUri) {
    const parseResult = parseRefundUri(talerRefundUri);
    logger$8.trace("applying refund", parseResult);
    if (!parseResult) {
        throw Error("invalid refund URI");
    }
    const purchase = await ws.db
        .mktx((x) => [x.purchases])
        .runReadOnly(async (tx) => {
        return tx.purchases.indexes.byMerchantUrlAndOrderId.get([
            parseResult.merchantBaseUrl,
            parseResult.orderId,
        ]);
    });
    if (!purchase) {
        throw Error(`no purchase for the taler://refund/ URI (${talerRefundUri}) was found`);
    }
    return applyRefundFromPurchaseId(ws, purchase.proposalId);
}
async function applyRefundFromPurchaseId(ws, proposalId) {
    logger$8.trace("applying refund for purchase", proposalId);
    logger$8.info("processing purchase for refund");
    const success = await ws.db
        .mktx((x) => [x.purchases])
        .runReadWrite(async (tx) => {
        const p = await tx.purchases.get(proposalId);
        if (!p) {
            logger$8.error("no purchase found for refund URL");
            return false;
        }
        p.refundQueryRequested = true;
        await tx.purchases.put(p);
        return true;
    });
    if (success) {
        ws.notify({
            type: NotificationType.RefundStarted,
        });
        await processPurchaseQueryRefund(ws, proposalId, {
            forceNow: true,
            waitForAutoRefund: false,
        });
    }
    const purchase = await ws.db
        .mktx((x) => [x.purchases])
        .runReadOnly(async (tx) => {
        return tx.purchases.get(proposalId);
    });
    if (!purchase) {
        throw Error("purchase no longer exists");
    }
    const summary = calculateRefundSummary(purchase);
    return {
        contractTermsHash: purchase.download.contractData.contractTermsHash,
        proposalId: purchase.proposalId,
        transactionId: makeEventId(TransactionType.Payment, proposalId),
        amountEffectivePaid: Amounts.stringify(summary.amountEffectivePaid),
        amountRefundGone: Amounts.stringify(summary.amountRefundGone),
        amountRefundGranted: Amounts.stringify(summary.amountRefundGranted),
        pendingAtExchange: summary.pendingAtExchange,
        info: {
            contractTermsHash: purchase.download.contractData.contractTermsHash,
            merchant: purchase.download.contractData.merchant,
            orderId: purchase.download.contractData.orderId,
            products: purchase.download.contractData.products,
            summary: purchase.download.contractData.summary,
            fulfillmentMessage: purchase.download.contractData.fulfillmentMessage,
            summary_i18n: purchase.download.contractData.summaryI18n,
            fulfillmentMessage_i18n: purchase.download.contractData.fulfillmentMessageI18n,
        },
    };
}
async function queryAndSaveAwaitingRefund(ws, purchase, waitForAutoRefund) {
    const requestUrl = new URL$1(`orders/${purchase.download.contractData.orderId}`, purchase.download.contractData.merchantBaseUrl);
    requestUrl.searchParams.set("h_contract", purchase.download.contractData.contractTermsHash);
    // Long-poll for one second
    if (waitForAutoRefund) {
        requestUrl.searchParams.set("timeout_ms", "1000");
        requestUrl.searchParams.set("await_refund_obtained", "yes");
        logger$8.trace("making long-polling request for auto-refund");
    }
    const resp = await ws.http.get(requestUrl.href);
    const orderStatus = await readSuccessResponseJsonOrThrow(resp, codecForMerchantOrderStatusPaid());
    if (!orderStatus.refunded) {
        // Wait for retry ...
        return Amounts.getZero(purchase.totalPayCost.currency);
    }
    const refundAwaiting = Amounts.sub(Amounts.parseOrThrow(orderStatus.refund_amount), Amounts.parseOrThrow(orderStatus.refund_taken)).amount;
    if (purchase.refundAwaiting === undefined ||
        Amounts.cmp(refundAwaiting, purchase.refundAwaiting) !== 0) {
        await ws.db
            .mktx((x) => [x.purchases])
            .runReadWrite(async (tx) => {
            const p = await tx.purchases.get(purchase.proposalId);
            if (!p) {
                logger$8.warn("purchase does not exist anymore");
                return;
            }
            p.refundAwaiting = refundAwaiting;
            await tx.purchases.put(p);
        });
    }
    return refundAwaiting;
}
async function processPurchaseQueryRefund(ws, proposalId, options = {}) {
    var _a;
    const waitForAutoRefund = (_a = options.waitForAutoRefund) !== null && _a !== void 0 ? _a : false;
    const purchase = await ws.db
        .mktx((x) => [x.purchases])
        .runReadOnly(async (tx) => {
        return tx.purchases.get(proposalId);
    });
    if (!purchase) {
        return OperationAttemptResult.finishedEmpty();
    }
    if (!purchase.refundQueryRequested) {
        return OperationAttemptResult.finishedEmpty();
    }
    if (purchase.timestampFirstSuccessfulPay) {
        if (!purchase.autoRefundDeadline ||
            !AbsoluteTime.isExpired(AbsoluteTime.fromTimestamp(purchase.autoRefundDeadline))) {
            const awaitingAmount = await queryAndSaveAwaitingRefund(ws, purchase, waitForAutoRefund);
            if (Amounts.isZero(awaitingAmount)) {
                return OperationAttemptResult.finishedEmpty();
            }
        }
        const requestUrl = new URL$1(`orders/${purchase.download.contractData.orderId}/refund`, purchase.download.contractData.merchantBaseUrl);
        logger$8.trace(`making refund request to ${requestUrl.href}`);
        const request = await ws.http.postJson(requestUrl.href, {
            h_contract: purchase.download.contractData.contractTermsHash,
        });
        const refundResponse = await readSuccessResponseJsonOrThrow(request, codecForMerchantOrderRefundPickupResponse());
        await acceptRefunds(ws, proposalId, refundResponse.refunds, RefundReason.NormalRefund);
    }
    else if (purchase.abortStatus === AbortStatus.AbortRefund) {
        const requestUrl = new URL$1(`orders/${purchase.download.contractData.orderId}/abort`, purchase.download.contractData.merchantBaseUrl);
        const abortingCoins = [];
        await ws.db
            .mktx((x) => [x.coins])
            .runReadOnly(async (tx) => {
            for (let i = 0; i < purchase.payCoinSelection.coinPubs.length; i++) {
                const coinPub = purchase.payCoinSelection.coinPubs[i];
                const coin = await tx.coins.get(coinPub);
                checkDbInvariant(!!coin, "expected coin to be present");
                abortingCoins.push({
                    coin_pub: coinPub,
                    contribution: Amounts.stringify(purchase.payCoinSelection.coinContributions[i]),
                    exchange_url: coin.exchangeBaseUrl,
                });
            }
        });
        const abortReq = {
            h_contract: purchase.download.contractData.contractTermsHash,
            coins: abortingCoins,
        };
        logger$8.trace(`making order abort request to ${requestUrl.href}`);
        const request = await ws.http.postJson(requestUrl.href, abortReq);
        const abortResp = await readSuccessResponseJsonOrThrow(request, codecForAbortResponse());
        const refunds = [];
        if (abortResp.refunds.length != abortingCoins.length) {
            // FIXME: define error code!
            throw Error("invalid order abort response");
        }
        for (let i = 0; i < abortResp.refunds.length; i++) {
            const r = abortResp.refunds[i];
            refunds.push(Object.assign(Object.assign({}, r), { coin_pub: purchase.payCoinSelection.coinPubs[i], refund_amount: Amounts.stringify(purchase.payCoinSelection.coinContributions[i]), rtransaction_id: 0, execution_time: AbsoluteTime.toTimestamp(AbsoluteTime.addDuration(AbsoluteTime.fromTimestamp(purchase.download.contractData.timestamp), Duration.fromSpec({ seconds: 1 }))) }));
        }
        await acceptRefunds(ws, proposalId, refunds, RefundReason.AbortRefund);
    }
    return OperationAttemptResult.finishedEmpty();
}
async function abortFailedPayWithRefund(ws, proposalId) {
    await ws.db
        .mktx((x) => [x.purchases])
        .runReadWrite(async (tx) => {
        const purchase = await tx.purchases.get(proposalId);
        if (!purchase) {
            throw Error("purchase not found");
        }
        if (purchase.timestampFirstSuccessfulPay) {
            // No point in aborting it.  We don't even report an error.
            logger$8.warn(`tried to abort successful payment`);
            return;
        }
        if (purchase.abortStatus !== AbortStatus.None) {
            return;
        }
        purchase.refundQueryRequested = true;
        purchase.paymentSubmitPending = false;
        purchase.abortStatus = AbortStatus.AbortRefund;
        await tx.purchases.put(purchase);
    });
    processPurchaseQueryRefund(ws, proposalId, {
        forceNow: true,
    }).catch((e) => {
        logger$8.trace(`error during refund processing after abort pay: ${e}`);
    });
}

/*
 This file is part of GNU Taler
 (C) 2020 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$7 = new Logger("operations/testing.ts");
/**
 * Generate a random alphanumeric ID.  Does *not* use cryptographically
 * secure randomness.
 */
function makeId$1(length) {
    let result = "";
    const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for (let i = 0; i < length; i++) {
        result += characters.charAt(Math.floor(Math.random() * characters.length));
    }
    return result;
}
/**
 * Helper function to generate the "Authorization" HTTP header.
 */
function makeBasicAuthHeader$1(username, password) {
    const auth = `${username}:${password}`;
    const authEncoded = Buffer.from(auth).toString("base64");
    return `Basic ${authEncoded}`;
}
async function withdrawTestBalance(ws, req) {
    var _a;
    const amount = req.amount;
    const exchangeBaseUrl = req.exchangeBaseUrl;
    const bankAccessApiBaseUrl = (_a = req.bankAccessApiBaseUrl) !== null && _a !== void 0 ? _a : req.bankBaseUrl;
    logger$7.trace(`Registered bank user, bank access base url ${bankAccessApiBaseUrl}`);
    const bankUser = await registerRandomBankUser(ws.http, bankAccessApiBaseUrl);
    logger$7.trace(`Registered bank user ${JSON.stringify(bankUser)}`);
    const wresp = await createDemoBankWithdrawalUri(ws.http, bankAccessApiBaseUrl, bankUser, amount);
    await acceptWithdrawalFromUri(ws, {
        talerWithdrawUri: wresp.taler_withdraw_uri,
        selectedExchange: exchangeBaseUrl,
        forcedDenomSel: req.forcedDenomSel,
    });
    await confirmBankWithdrawalUri(ws.http, bankAccessApiBaseUrl, bankUser, wresp.withdrawal_id);
}
function getMerchantAuthHeader(m) {
    if (m.authToken) {
        return {
            Authorization: `Bearer ${m.authToken}`,
        };
    }
    return {};
}
/**
 * Use the testing API of a demobank to create a taler://withdraw URI
 * that the wallet can then use to make a withdrawal.
 */
async function createDemoBankWithdrawalUri(http, bankAccessApiBaseUrl, bankUser, amount) {
    const reqUrl = new URL$1(`accounts/${bankUser.username}/withdrawals`, bankAccessApiBaseUrl).href;
    const resp = await http.postJson(reqUrl, {
        amount,
    }, {
        headers: {
            Authorization: makeBasicAuthHeader$1(bankUser.username, bankUser.password),
        },
    });
    const respJson = await readSuccessResponseJsonOrThrow(resp, codecForAny());
    return respJson;
}
async function confirmBankWithdrawalUri(http, bankAccessApiBaseUrl, bankUser, withdrawalId) {
    const reqUrl = new URL$1(`accounts/${bankUser.username}/withdrawals/${withdrawalId}/confirm`, bankAccessApiBaseUrl).href;
    const resp = await http.postJson(reqUrl, {}, {
        headers: {
            Authorization: makeBasicAuthHeader$1(bankUser.username, bankUser.password),
        },
    });
    await readSuccessResponseJsonOrThrow(resp, codecForAny());
    return;
}
async function registerRandomBankUser(http, bankAccessApiBaseUrl) {
    const reqUrl = new URL$1("testing/register", bankAccessApiBaseUrl).href;
    const randId = makeId$1(8);
    const bankUser = {
        // euFin doesn't allow resource names to have upper case letters.
        username: `testuser-${randId.toLowerCase()}`,
        password: `testpw-${randId}`,
    };
    const resp = await http.postJson(reqUrl, bankUser);
    await checkSuccessResponseOrThrow(resp);
    return bankUser;
}
async function refund(http, merchantBackend, orderId, reason, refundAmount) {
    const reqUrl = new URL$1(`private/orders/${orderId}/refund`, merchantBackend.baseUrl);
    const refundReq = {
        order_id: orderId,
        reason,
        refund: refundAmount,
    };
    const resp = await http.postJson(reqUrl.href, refundReq, {
        headers: getMerchantAuthHeader(merchantBackend),
    });
    const r = await readSuccessResponseJsonOrThrow(resp, codecForAny());
    const refundUri = r.taler_refund_uri;
    if (!refundUri) {
        throw Error("no refund URI in response");
    }
    return refundUri;
}
async function createOrder(http, merchantBackend, amount, summary, fulfillmentUrl) {
    const t = Math.floor(new Date().getTime() / 1000) + 15 * 60;
    const reqUrl = new URL$1("private/orders", merchantBackend.baseUrl).href;
    const orderReq = {
        order: {
            amount,
            summary,
            fulfillment_url: fulfillmentUrl,
            refund_deadline: { t_s: t },
            wire_transfer_deadline: { t_s: t },
        },
    };
    const resp = await http.postJson(reqUrl, orderReq, {
        headers: getMerchantAuthHeader(merchantBackend),
    });
    const r = await readSuccessResponseJsonOrThrow(resp, codecForAny());
    const orderId = r.order_id;
    if (!orderId) {
        throw Error("no order id in response");
    }
    return { orderId };
}
async function checkPayment(http, merchantBackend, orderId) {
    const reqUrl = new URL$1(`private/orders/${orderId}`, merchantBackend.baseUrl);
    reqUrl.searchParams.set("order_id", orderId);
    const resp = await http.get(reqUrl.href, {
        headers: getMerchantAuthHeader(merchantBackend),
    });
    return readSuccessResponseJsonOrThrow(resp, codecForCheckPaymentResponse());
}
async function makePayment(ws, merchant, amount, summary) {
    const orderResp = await createOrder(ws.http, merchant, amount, summary, "taler://fulfillment-success/thx");
    logger$7.trace("created order with orderId", orderResp.orderId);
    let paymentStatus = await checkPayment(ws.http, merchant, orderResp.orderId);
    logger$7.trace("payment status", paymentStatus);
    const talerPayUri = paymentStatus.taler_pay_uri;
    if (!talerPayUri) {
        throw Error("no taler://pay/ URI in payment response");
    }
    const preparePayResult = await preparePayForUri(ws, talerPayUri);
    logger$7.trace("prepare pay result", preparePayResult);
    if (preparePayResult.status != "payment-possible") {
        throw Error("payment not possible");
    }
    const confirmPayResult = await confirmPay(ws, preparePayResult.proposalId, undefined);
    logger$7.trace("confirmPayResult", confirmPayResult);
    paymentStatus = await checkPayment(ws.http, merchant, orderResp.orderId);
    logger$7.trace("payment status after wallet payment:", paymentStatus);
    if (paymentStatus.order_status !== "paid") {
        throw Error("payment did not succeed");
    }
    return {
        orderId: orderResp.orderId,
    };
}
async function runIntegrationTest(ws, args) {
    var _a, _b;
    logger$7.info("running test with arguments", args);
    const parsedSpendAmount = Amounts.parseOrThrow(args.amountToSpend);
    const currency = parsedSpendAmount.currency;
    logger$7.info("withdrawing test balance");
    await withdrawTestBalance(ws, {
        amount: args.amountToWithdraw,
        bankBaseUrl: args.bankBaseUrl,
        bankAccessApiBaseUrl: (_a = args.bankAccessApiBaseUrl) !== null && _a !== void 0 ? _a : args.bankBaseUrl,
        exchangeBaseUrl: args.exchangeBaseUrl,
    });
    await ws.runUntilDone();
    logger$7.info("done withdrawing test balance");
    const balance = await getBalances(ws);
    logger$7.trace(JSON.stringify(balance, null, 2));
    const myMerchant = {
        baseUrl: args.merchantBaseUrl,
        authToken: args.merchantAuthToken,
    };
    await makePayment(ws, myMerchant, args.amountToSpend, "hello world");
    // Wait until the refresh is done
    await ws.runUntilDone();
    logger$7.trace("withdrawing test balance for refund");
    const withdrawAmountTwo = Amounts.parseOrThrow(`${currency}:18`);
    const spendAmountTwo = Amounts.parseOrThrow(`${currency}:7`);
    const refundAmount = Amounts.parseOrThrow(`${currency}:6`);
    const spendAmountThree = Amounts.parseOrThrow(`${currency}:3`);
    await withdrawTestBalance(ws, {
        amount: Amounts.stringify(withdrawAmountTwo),
        bankBaseUrl: args.bankBaseUrl,
        bankAccessApiBaseUrl: (_b = args.bankAccessApiBaseUrl) !== null && _b !== void 0 ? _b : args.bankBaseUrl,
        exchangeBaseUrl: args.exchangeBaseUrl,
    });
    // Wait until the withdraw is done
    await ws.runUntilDone();
    const { orderId: refundOrderId } = await makePayment(ws, myMerchant, Amounts.stringify(spendAmountTwo), "order that will be refunded");
    const refundUri = await refund(ws.http, myMerchant, refundOrderId, "test refund", Amounts.stringify(refundAmount));
    logger$7.trace("refund URI", refundUri);
    await applyRefund(ws, refundUri);
    logger$7.trace("integration test: applied refund");
    // Wait until the refund is done
    await ws.runUntilDone();
    logger$7.trace("integration test: making payment after refund");
    await makePayment(ws, myMerchant, Amounts.stringify(spendAmountThree), "payment after refund");
    logger$7.trace("integration test: make payment done");
    await ws.runUntilDone();
    logger$7.trace("integration test: all done!");
}
async function testPay(ws, args) {
    logger$7.trace("creating order");
    const merchant = {
        authToken: args.merchantAuthToken,
        baseUrl: args.merchantBaseUrl,
    };
    const orderResp = await createOrder(ws.http, merchant, args.amount, args.summary, "taler://fulfillment-success/thank+you");
    logger$7.trace("created new order with order ID", orderResp.orderId);
    const checkPayResp = await checkPayment(ws.http, merchant, orderResp.orderId);
    const talerPayUri = checkPayResp.taler_pay_uri;
    if (!talerPayUri) {
        console.error("fatal: no taler pay URI received from backend");
        process.exit(1);
    }
    logger$7.trace("taler pay URI:", talerPayUri);
    const result = await preparePayForUri(ws, talerPayUri);
    if (result.status !== PreparePayResultType.PaymentPossible) {
        throw Error(`unexpected prepare pay status: ${result.status}`);
    }
    const r = await confirmPay(ws, result.proposalId, undefined, args.forcedCoinSel);
    if (r.type != ConfirmPayResultType.Done) {
        throw Error("payment not done");
    }
    const purchase = await ws.db
        .mktx((x) => [x.purchases])
        .runReadOnly(async (tx) => {
        return tx.purchases.get(result.proposalId);
    });
    checkLogicInvariant(!!purchase);
    return {
        payCoinSelection: purchase.payCoinSelection,
    };
}

/*
 This file is part of GNU Taler
 (C) 2021 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * Given a list of denominations with the same value and same period of time:
 * return the one that will be used.
 * The best denomination is the one that will minimize the fee cost.
 *
 * @param list denominations of same value
 * @returns
 */
function selectBestForOverlappingDenominations(list) {
    let minDeposit = undefined;
    //TODO: improve denomination selection, this is a trivial implementation
    list.forEach((e) => {
        if (minDeposit === undefined) {
            minDeposit = e;
            return;
        }
        if (Amounts.cmp(minDeposit.feeDeposit, e.feeDeposit) > -1) {
            minDeposit = e;
        }
    });
    return minDeposit;
}
/**
 * Create a usage timeline with the denominations given.
 *
 * If there are multiple denominations that can be used, the list will
 * contain the one that minimize the fee cost. @see selectBestForOverlappingDenominations
 *
 * @param list list of denominations
 * @param periodProp property of element of the list that will be used as end of the usage period
 * @param feeProp property of the element of the list that will be used as fee reference
 * @returns  list of @type {FeeDescription} sorted by usage period
 */
function createDenominationTimeline(list, periodProp, feeProp) {
    const points = list
        .reduce((ps, denom) => {
        //exclude denoms with bad configuration
        if (denom.stampStart.t_s >= denom[periodProp].t_s) {
            throw Error(`denom ${denom.denomPubHash} has start after the end`);
            // return ps;
        }
        ps.push({
            type: "start",
            moment: AbsoluteTime.fromTimestamp(denom.stampStart),
            denom,
        });
        ps.push({
            type: "end",
            moment: AbsoluteTime.fromTimestamp(denom[periodProp]),
            denom,
        });
        return ps;
    }, [])
        .sort((a, b) => {
        const v = Amounts.cmp(a.denom.value, b.denom.value);
        if (v != 0)
            return v;
        const t = AbsoluteTime.cmp(a.moment, b.moment);
        if (t != 0)
            return t;
        if (a.type === b.type)
            return 0;
        return a.type === "start" ? 1 : -1;
    });
    const activeAtTheSameTime = [];
    return points.reduce((result, cursor, idx) => {
        const hash = cursor.denom.denomPubHash;
        if (!hash)
            throw Error(`denomination without hash ${JSON.stringify(cursor.denom, undefined, 2)}`);
        let prev = result.length > 0 ? result[result.length - 1] : undefined;
        const prevHasSameValue = prev && Amounts.cmp(prev.value, cursor.denom.value) === 0;
        if (prev) {
            if (prevHasSameValue) {
                prev.until = cursor.moment;
                if (prev.from.t_ms === prev.until.t_ms) {
                    result.pop();
                    prev = result[result.length - 1];
                }
            }
            else {
                // the last end adds a gap that we have to remove
                result.pop();
            }
        }
        //update the activeAtTheSameTime list
        if (cursor.type === "end") {
            const loc = activeAtTheSameTime.findIndex((v) => v.denomPubHash === hash);
            if (loc === -1) {
                throw Error(`denomination ${hash} has an end but no start`);
            }
            activeAtTheSameTime.splice(loc, 1);
        }
        else if (cursor.type === "start") {
            activeAtTheSameTime.push(cursor.denom);
        }
        else {
            const exhaustiveCheck = cursor.type;
            throw new Error(`not TimePoint defined for type: ${exhaustiveCheck}`);
        }
        if (idx == points.length - 1) {
            //this is the last element in the list, prevent adding
            //a gap in the end
            if (cursor.type !== "end") {
                throw Error(`denomination ${hash} starts after ending or doesn't have an ending`);
            }
            if (activeAtTheSameTime.length > 0) {
                throw Error(`there are ${activeAtTheSameTime.length} denominations without ending`);
            }
            return result;
        }
        const current = selectBestForOverlappingDenominations(activeAtTheSameTime);
        if (current) {
            if (prev === undefined || //is the first
                !prev.fee || //is a gap
                Amounts.cmp(prev.fee, current[feeProp]) !== 0 // prev has the same fee
            ) {
                result.push({
                    value: cursor.denom.value,
                    from: cursor.moment,
                    until: AbsoluteTime.never(),
                    fee: current[feeProp],
                });
            }
            else {
                prev.until = cursor.moment;
            }
        }
        else {
            result.push({
                value: cursor.denom.value,
                from: cursor.moment,
                until: AbsoluteTime.never(), //not yet known
            });
        }
        return result;
    }, []);
}

/*
 This file is part of GNU Taler
 (C) 2015-2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const builtinAuditors = [
    {
        currency: "KUDOS",
        auditorPub: "BW9DC48PHQY4NH011SHHX36DZZ3Q22Y6X7FZ1VD1CMZ2PTFZ6PN0",
        auditorBaseUrl: "https://auditor.demo.taler.net/",
        uids: ["5P25XF8TVQP9AW6VYGY2KV47WT5Y3ZXFSJAA570GJPX5SVJXKBVG"],
    },
];
const builtinExchanges = ["https://exchange.demo.taler.net/"];
const logger$6 = new Logger("wallet.ts");
async function getWithdrawalDetailsForAmount(ws, exchangeBaseUrl, amount, restrictAge) {
    const wi = await getExchangeWithdrawalInfo(ws, exchangeBaseUrl, amount);
    const paytoUris = wi.exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri);
    if (!paytoUris) {
        throw Error("exchange is in invalid state");
    }
    return {
        amountRaw: Amounts.stringify(amount),
        amountEffective: Amounts.stringify(wi.selectedDenoms.totalCoinValue),
        paytoUris,
        tosAccepted: wi.termsOfServiceAccepted,
    };
}
/**
 * Call the right handler for a pending operation without doing
 * any special error handling.
 */
async function callOperationHandler(ws, pending, forceNow = false) {
    switch (pending.type) {
        case PendingTaskType.ExchangeUpdate:
            return await updateExchangeFromUrlHandler(ws, pending.exchangeBaseUrl, {
                forceNow,
            });
        case PendingTaskType.Refresh:
            return await processRefreshGroup(ws, pending.refreshGroupId, {
                forceNow,
            });
        case PendingTaskType.Withdraw:
            return await processWithdrawalGroup(ws, pending.withdrawalGroupId, {
                forceNow,
            });
        case PendingTaskType.ProposalDownload:
            return await processDownloadProposal(ws, pending.proposalId, {
                forceNow,
            });
        case PendingTaskType.TipPickup:
            return await processTip(ws, pending.tipId, { forceNow });
        case PendingTaskType.Pay:
            return await processPurchasePay(ws, pending.proposalId, { forceNow });
        case PendingTaskType.RefundQuery:
            return await processPurchaseQueryRefund(ws, pending.proposalId, {
                forceNow,
            });
        case PendingTaskType.Recoup:
            return await processRecoupGroupHandler(ws, pending.recoupGroupId, {
                forceNow,
            });
        case PendingTaskType.ExchangeCheckRefresh:
            return await autoRefresh(ws, pending.exchangeBaseUrl);
        case PendingTaskType.Deposit: {
            return await processDepositGroup(ws, pending.depositGroupId, {
                forceNow,
            });
        }
        case PendingTaskType.Backup:
            return await processBackupForProvider(ws, pending.backupProviderBaseUrl);
        default:
            return assertUnreachable();
    }
}
async function storeOperationError(ws, pendingTaskId, e) {
    await ws.db
        .mktx((x) => [x.operationRetries])
        .runReadWrite(async (tx) => {
        let retryRecord = await tx.operationRetries.get(pendingTaskId);
        if (!retryRecord) {
            retryRecord = {
                id: pendingTaskId,
                lastError: e,
                retryInfo: RetryInfo.reset(),
            };
        }
        else {
            retryRecord.lastError = e;
            retryRecord.retryInfo = RetryInfo.increment(retryRecord.retryInfo);
        }
        await tx.operationRetries.put(retryRecord);
    });
}
async function storeOperationFinished(ws, pendingTaskId) {
    await ws.db
        .mktx((x) => [x.operationRetries])
        .runReadWrite(async (tx) => {
        await tx.operationRetries.delete(pendingTaskId);
    });
}
async function storeOperationPending(ws, pendingTaskId) {
    await ws.db
        .mktx((x) => [x.operationRetries])
        .runReadWrite(async (tx) => {
        let retryRecord = await tx.operationRetries.get(pendingTaskId);
        if (!retryRecord) {
            retryRecord = {
                id: pendingTaskId,
                retryInfo: RetryInfo.reset(),
            };
        }
        else {
            delete retryRecord.lastError;
            retryRecord.retryInfo = RetryInfo.increment(retryRecord.retryInfo);
        }
        await tx.operationRetries.put(retryRecord);
    });
}
async function runOperationWithErrorReporting(ws, opId, f) {
    let maybeError;
    try {
        const resp = await f();
        switch (resp.type) {
            case OperationAttemptResultType.Error:
                return await storeOperationError(ws, opId, resp.errorDetail);
            case OperationAttemptResultType.Finished:
                return await storeOperationFinished(ws, opId);
            case OperationAttemptResultType.Pending:
                return await storeOperationPending(ws, opId);
            case OperationAttemptResultType.Longpoll:
                break;
        }
    }
    catch (e) {
        if (e instanceof TalerError) {
            logger$6.warn("operation processed resulted in error");
            logger$6.warn(`error was: ${j2s(e.errorDetail)}`);
            maybeError = e.errorDetail;
            return await storeOperationError(ws, opId, maybeError);
        }
        else if (e instanceof Error) {
            // This is a bug, as we expect pending operations to always
            // do their own error handling and only throw WALLET_PENDING_OPERATION_FAILED
            // or return something.
            logger$6.error(`Uncaught exception: ${e.message}`);
            logger$6.error(`Stack: ${e.stack}`);
            maybeError = makeErrorDetail(TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, {
                stack: e.stack,
            }, `unexpected exception (message: ${e.message})`);
            return await storeOperationError(ws, opId, maybeError);
        }
        else {
            logger$6.error("Uncaught exception, value is not even an error.");
            maybeError = makeErrorDetail(TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, {}, `unexpected exception (not even an error)`);
            return await storeOperationError(ws, opId, maybeError);
        }
    }
}
/**
 * Process pending operations.
 */
async function runPending(ws, forceNow = false) {
    const pendingOpsResponse = await getPendingOperations(ws);
    for (const p of pendingOpsResponse.pendingOperations) {
        if (!forceNow && !AbsoluteTime.isExpired(p.timestampDue)) {
            continue;
        }
        await runOperationWithErrorReporting(ws, p.id, async () => {
            logger$6.trace(`running pending ${JSON.stringify(p, undefined, 2)}`);
            return await callOperationHandler(ws, p, forceNow);
        });
    }
}
/**
 * Main retry loop of the wallet.
 *
 * Looks up pending operations from the wallet, runs them, repeat.
 */
async function runTaskLoop(ws, opts = {}) {
    logger$6.info(`running task loop opts=${j2s(opts)}`);
    let retriesExceeded = false;
    for (let iteration = 0; !ws.stopped; iteration++) {
        const pending = await getPendingOperations(ws);
        logger$6.trace(`pending operations: ${j2s(pending)}`);
        let numGivingLiveness = 0;
        let numDue = 0;
        let minDue = AbsoluteTime.never();
        for (const p of pending.pendingOperations) {
            const maxRetries = opts.maxRetries;
            if (maxRetries && p.retryInfo && p.retryInfo.retryCounter > maxRetries) {
                retriesExceeded = true;
                logger$6.warn(`skipping, as ${maxRetries} retries are exceeded in an operation of type ${p.type}`);
                continue;
            }
            minDue = AbsoluteTime.min(minDue, p.timestampDue);
            if (AbsoluteTime.isExpired(p.timestampDue)) {
                numDue++;
            }
            if (p.givesLifeness) {
                numGivingLiveness++;
            }
        }
        if (opts.stopWhenDone && numGivingLiveness === 0 && iteration !== 0) {
            logger$6.warn(`stopping, as no pending operations have lifeness`);
            return {
                retriesExceeded,
            };
        }
        // Make sure that we run tasks that don't give lifeness at least
        // one time.
        if (iteration !== 0 && numDue === 0) {
            // We've executed pending, due operations at least one.
            // Now we don't have any more operations available,
            // and need to wait.
            // Wait for at most 5 seconds to the next check.
            const dt = durationMin(durationFromSpec({
                seconds: 5,
            }), Duration.getRemaining(minDue));
            logger$6.trace(`waiting for at most ${dt.d_ms} ms`);
            const timeout = ws.timerGroup.resolveAfter(dt);
            ws.notify({
                type: NotificationType.WaitingForRetry,
                numGivingLiveness,
                numPending: pending.pendingOperations.length,
            });
            // Wait until either the timeout, or we are notified (via the latch)
            // that more work might be available.
            await Promise.race([timeout, ws.latch.wait()]);
        }
        else {
            logger$6.trace(`running ${pending.pendingOperations.length} pending operations`);
            for (const p of pending.pendingOperations) {
                if (!AbsoluteTime.isExpired(p.timestampDue)) {
                    continue;
                }
                await runOperationWithErrorReporting(ws, p.id, async () => {
                    logger$6.trace(`running pending ${JSON.stringify(p, undefined, 2)}`);
                    return await callOperationHandler(ws, p);
                });
                ws.notify({
                    type: NotificationType.PendingOperationProcessed,
                });
            }
        }
    }
    logger$6.trace("exiting wallet retry loop");
    return {
        retriesExceeded,
    };
}
/**
 * Insert the hard-coded defaults for exchanges, coins and
 * auditors into the database, unless these defaults have
 * already been applied.
 */
async function fillDefaults(ws) {
    await ws.db
        .mktx((x) => [x.config, x.auditorTrust])
        .runReadWrite(async (tx) => {
        let applied = false;
        await tx.config.iter().forEach((x) => {
            if (x.key == "currencyDefaultsApplied" && x.value == true) {
                applied = true;
            }
        });
        if (!applied) {
            for (const c of builtinAuditors) {
                await tx.auditorTrust.put(c);
            }
        }
        // FIXME: make sure exchanges are added transactionally to
        // DB in first-time default application
    });
    for (const url of builtinExchanges) {
        try {
            await updateExchangeFromUrl(ws, url, { forceNow: true });
        }
        catch (e) {
            logger$6.warn(`could not update builtin exchange ${url} during wallet initialization`);
        }
    }
}
async function getExchangeTos(ws, exchangeBaseUrl, acceptedFormat) {
    // FIXME: download ToS in acceptable format if passed!
    const { exchangeDetails } = await updateExchangeFromUrl(ws, exchangeBaseUrl);
    const content = exchangeDetails.termsOfServiceText;
    const currentEtag = exchangeDetails.termsOfServiceLastEtag;
    const contentType = exchangeDetails.termsOfServiceContentType;
    if (content === undefined ||
        currentEtag === undefined ||
        contentType === undefined) {
        throw Error("exchange is in invalid state");
    }
    if (acceptedFormat &&
        acceptedFormat.findIndex((f) => f === contentType) !== -1) {
        return {
            acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
            currentEtag,
            content,
            contentType,
        };
    }
    const tosDownload = await downloadTosFromAcceptedFormat(ws, exchangeBaseUrl, getExchangeRequestTimeout(), acceptedFormat);
    if (tosDownload.tosContentType === contentType) {
        return {
            acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
            currentEtag,
            content,
            contentType,
        };
    }
    await updateExchangeTermsOfService(ws, exchangeBaseUrl, tosDownload);
    return {
        acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
        currentEtag: tosDownload.tosEtag,
        content: tosDownload.tosText,
        contentType: tosDownload.tosContentType,
    };
}
/**
 * List bank accounts known to the wallet from
 * previous withdrawals.
 */
async function listKnownBankAccounts(ws, currency) {
    const accounts = [];
    await ws.db
        .mktx((x) => [x.bankAccounts])
        .runReadOnly(async (tx) => {
        const knownAccounts = await tx.bankAccounts.iter().toArray();
        for (const r of knownAccounts) {
            if (currency && currency !== r.currency) {
                continue;
            }
            const payto = parsePaytoUri(r.uri);
            if (payto) {
                accounts.push({
                    uri: payto,
                    alias: r.alias,
                    kyc_completed: r.kycCompleted,
                    currency: r.currency,
                });
            }
        }
    });
    return { accounts };
}
/**
 */
async function addKnownBankAccounts(ws, payto, alias, currency) {
    await ws.db
        .mktx((x) => [x.bankAccounts])
        .runReadWrite(async (tx) => {
        tx.bankAccounts.put({
            uri: payto,
            alias: alias,
            currency: currency,
            kycCompleted: false,
        });
    });
    return;
}
/**
 */
async function forgetKnownBankAccounts(ws, payto) {
    await ws.db
        .mktx((x) => [x.bankAccounts])
        .runReadWrite(async (tx) => {
        const account = await tx.bankAccounts.get(payto);
        if (!account) {
            throw Error(`account not found: ${payto}`);
        }
        tx.bankAccounts.delete(account.uri);
    });
    return;
}
async function getExchanges(ws) {
    const exchanges = [];
    await ws.db
        .mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
        .runReadOnly(async (tx) => {
        const exchangeRecords = await tx.exchanges.iter().toArray();
        for (const r of exchangeRecords) {
            const dp = r.detailsPointer;
            if (!dp) {
                continue;
            }
            const { currency } = dp;
            const exchangeDetails = await getExchangeDetails(tx, r.baseUrl);
            if (!exchangeDetails) {
                continue;
            }
            const denominations = await tx.denominations.indexes.byExchangeBaseUrl
                .iter(r.baseUrl)
                .toArray();
            if (!denominations) {
                continue;
            }
            exchanges.push({
                exchangeBaseUrl: r.baseUrl,
                currency,
                tos: {
                    acceptedVersion: exchangeDetails.termsOfServiceAcceptedEtag,
                    currentVersion: exchangeDetails.termsOfServiceLastEtag,
                    contentType: exchangeDetails.termsOfServiceContentType,
                    content: exchangeDetails.termsOfServiceText,
                },
                paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
            });
        }
    });
    return { exchanges };
}
async function getExchangeDetailedInfo(ws, exchangeBaseurl) {
    //TODO: should we use the forceUpdate parameter?
    const exchange = await ws.db
        .mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
        .runReadOnly(async (tx) => {
        const ex = await tx.exchanges.get(exchangeBaseurl);
        const dp = ex === null || ex === void 0 ? void 0 : ex.detailsPointer;
        if (!dp) {
            return;
        }
        const { currency } = dp;
        const exchangeDetails = await getExchangeDetails(tx, ex.baseUrl);
        if (!exchangeDetails) {
            return;
        }
        const denominationRecords = await tx.denominations.indexes.byExchangeBaseUrl
            .iter(ex.baseUrl)
            .toArray();
        if (!denominationRecords) {
            return;
        }
        const denominations = denominationRecords.map((x) => DenominationRecord.toDenomInfo(x));
        return {
            info: {
                exchangeBaseUrl: ex.baseUrl,
                currency,
                tos: {
                    acceptedVersion: exchangeDetails.termsOfServiceAcceptedEtag,
                    currentVersion: exchangeDetails.termsOfServiceLastEtag,
                    contentType: exchangeDetails.termsOfServiceContentType,
                    content: exchangeDetails.termsOfServiceText,
                },
                paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
                auditors: exchangeDetails.auditors,
                wireInfo: exchangeDetails.wireInfo,
            },
            denominations,
        };
    });
    if (!exchange) {
        throw Error(`exchange with base url "${exchangeBaseurl}" not found`);
    }
    const feesDescription = {
        deposit: createDenominationTimeline(exchange.denominations, "stampExpireDeposit", "feeDeposit"),
        refresh: createDenominationTimeline(exchange.denominations, "stampExpireWithdraw", "feeRefresh"),
        refund: createDenominationTimeline(exchange.denominations, "stampExpireWithdraw", "feeRefund"),
        withdraw: createDenominationTimeline(exchange.denominations, "stampExpireWithdraw", "feeWithdraw"),
    };
    return Object.assign(Object.assign({}, exchange.info), { feesDescription });
}
async function makeCoinAvailable(ws, tx, coinRecord) {
    checkLogicInvariant(coinRecord.status === CoinStatus.Fresh);
    const existingCoin = await tx.coins.get(coinRecord.coinPub);
    if (existingCoin) {
        return;
    }
    const denom = await tx.denominations.get([
        coinRecord.exchangeBaseUrl,
        coinRecord.denomPubHash,
    ]);
    checkDbInvariant(!!denom);
    const ageRestriction = coinRecord.maxAge;
    let car = await tx.coinAvailability.get([
        coinRecord.exchangeBaseUrl,
        coinRecord.denomPubHash,
        ageRestriction,
    ]);
    if (!car) {
        car = {
            maxAge: ageRestriction,
            amountFrac: denom.amountFrac,
            amountVal: denom.amountVal,
            currency: denom.currency,
            denomPubHash: denom.denomPubHash,
            exchangeBaseUrl: denom.exchangeBaseUrl,
            freshCoinCount: 0,
        };
    }
    car.freshCoinCount++;
    await tx.coins.put(coinRecord);
    await tx.coinAvailability.put(car);
}
async function spendCoins(ws, tx, csi) {
    for (let i = 0; i < csi.coinPubs.length; i++) {
        const coin = await tx.coins.get(csi.coinPubs[i]);
        if (!coin) {
            throw Error("coin allocated for payment doesn't exist anymore");
        }
        const coinAvailability = await tx.coinAvailability.get([
            coin.exchangeBaseUrl,
            coin.denomPubHash,
            coin.maxAge,
        ]);
        checkDbInvariant(!!coinAvailability);
        const contrib = csi.contributions[i];
        if (coin.status !== CoinStatus.Fresh) {
            const alloc = coin.allocation;
            if (!alloc) {
                continue;
            }
            if (alloc.id !== csi.allocationId) {
                // FIXME: assign error code
                throw Error("conflicting coin allocation (id)");
            }
            if (0 !== Amounts.cmp(alloc.amount, contrib)) {
                // FIXME: assign error code
                throw Error("conflicting coin allocation (contrib)");
            }
            continue;
        }
        coin.status = CoinStatus.Dormant;
        coin.allocation = {
            id: csi.allocationId,
            amount: Amounts.stringify(contrib),
        };
        const remaining = Amounts.sub(coin.currentAmount, contrib);
        if (remaining.saturated) {
            throw Error("not enough remaining balance on coin for payment");
        }
        coin.currentAmount = remaining.amount;
        checkDbInvariant(!!coinAvailability);
        if (coinAvailability.freshCoinCount === 0) {
            throw Error(`invalid coin count ${coinAvailability.freshCoinCount} in DB`);
        }
        coinAvailability.freshCoinCount--;
        await tx.coins.put(coin);
        await tx.coinAvailability.put(coinAvailability);
    }
    const refreshCoinPubs = csi.coinPubs.map((x) => ({
        coinPub: x,
    }));
    await createRefreshGroup(ws, tx, refreshCoinPubs, RefreshReason.PayMerchant);
}
async function setCoinSuspended(ws, coinPub, suspended) {
    await ws.db
        .mktx((x) => [x.coins, x.coinAvailability])
        .runReadWrite(async (tx) => {
        const c = await tx.coins.get(coinPub);
        if (!c) {
            logger$6.warn(`coin ${coinPub} not found, won't suspend`);
            return;
        }
        const coinAvailability = await tx.coinAvailability.get([
            c.exchangeBaseUrl,
            c.denomPubHash,
            c.maxAge,
        ]);
        checkDbInvariant(!!coinAvailability);
        if (suspended) {
            if (c.status !== CoinStatus.Fresh) {
                return;
            }
            if (coinAvailability.freshCoinCount === 0) {
                throw Error(`invalid coin count ${coinAvailability.freshCoinCount} in DB`);
            }
            coinAvailability.freshCoinCount--;
            c.status = CoinStatus.FreshSuspended;
        }
        else {
            if (c.status == CoinStatus.Dormant) {
                return;
            }
            coinAvailability.freshCoinCount++;
            c.status = CoinStatus.Fresh;
        }
        await tx.coins.put(c);
        await tx.coinAvailability.put(coinAvailability);
    });
}
/**
 * Dump the public information of coins we have in an easy-to-process format.
 */
async function dumpCoins(ws) {
    const coinsJson = { coins: [] };
    logger$6.info("dumping coins");
    await ws.db
        .mktx((x) => [x.coins, x.denominations, x.withdrawalGroups])
        .runReadOnly(async (tx) => {
        const coins = await tx.coins.iter().toArray();
        for (const c of coins) {
            const denom = await tx.denominations.get([
                c.exchangeBaseUrl,
                c.denomPubHash,
            ]);
            if (!denom) {
                console.error("no denom session found for coin");
                continue;
            }
            const cs = c.coinSource;
            let refreshParentCoinPub;
            if (cs.type == CoinSourceType.Refresh) {
                refreshParentCoinPub = cs.oldCoinPub;
            }
            let withdrawalReservePub;
            if (cs.type == CoinSourceType.Withdraw) {
                const ws = await tx.withdrawalGroups.get(cs.withdrawalGroupId);
                if (!ws) {
                    console.error("no withdrawal session found for coin");
                    continue;
                }
                withdrawalReservePub = ws.reservePub;
            }
            const denomInfo = await ws.getDenomInfo(ws, tx, c.exchangeBaseUrl, c.denomPubHash);
            if (!denomInfo) {
                console.error("no denomination found for coin");
                continue;
            }
            coinsJson.coins.push({
                coin_pub: c.coinPub,
                denom_pub: denomInfo.denomPub,
                denom_pub_hash: c.denomPubHash,
                denom_value: Amounts.stringify({
                    value: denom.amountVal,
                    currency: denom.currency,
                    fraction: denom.amountFrac,
                }),
                exchange_base_url: c.exchangeBaseUrl,
                refresh_parent_coin_pub: refreshParentCoinPub,
                remaining_value: Amounts.stringify(c.currentAmount),
                withdrawal_reserve_pub: withdrawalReservePub,
                coin_suspended: c.status === CoinStatus.FreshSuspended,
                ageCommitmentProof: c.ageCommitmentProof,
            });
        }
    });
    return coinsJson;
}
/**
 * Get an API client from an internal wallet state object.
 */
async function getClientFromWalletState(ws) {
    let id = 0;
    const client = {
        async call(op, payload) {
            const res = await handleCoreApiRequest(ws, op, `${id++}`, payload);
            switch (res.type) {
                case "error":
                    throw TalerError.fromUncheckedDetail(res.error);
                case "response":
                    return res.result;
            }
        },
    };
    return client;
}
const VERSION$1 = typeof __VERSION__ !== "undefined" ? __VERSION__ : "dev";
const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
/**
 * Implementation of the "wallet-core" API.
 */
async function dispatchRequestInternal(ws, operation, payload) {
    if (!ws.initCalled && operation !== "initWallet") {
        throw Error(`wallet must be initialized before running operation ${operation}`);
    }
    // FIXME: Can we make this more type-safe by using the request/response type
    // definitions we already have?
    switch (operation) {
        case "initWallet": {
            logger$6.info("initializing wallet");
            ws.initCalled = true;
            if (typeof payload === "object" && payload.skipDefaults) {
                logger$6.info("skipping defaults");
            }
            else {
                logger$6.info("filling defaults");
                await fillDefaults(ws);
            }
            return {};
        }
        case "withdrawTestkudos": {
            await withdrawTestBalance(ws, {
                amount: "TESTKUDOS:10",
                bankBaseUrl: "https://bank.test.taler.net/",
                bankAccessApiBaseUrl: "https://bank.test.taler.net/",
                exchangeBaseUrl: "https://exchange.test.taler.net/",
            });
            return {};
        }
        case "withdrawTestBalance": {
            const req = codecForWithdrawTestBalance().decode(payload);
            await withdrawTestBalance(ws, req);
            return {};
        }
        case "runIntegrationTest": {
            const req = codecForIntegrationTestArgs().decode(payload);
            await runIntegrationTest(ws, req);
            return {};
        }
        case "testPay": {
            const req = codecForTestPayArgs().decode(payload);
            return await testPay(ws, req);
        }
        case "getTransactions": {
            const req = codecForTransactionsRequest().decode(payload);
            return await getTransactions(ws, req);
        }
        case "getTransactionById": {
            const req = codecForTransactionByIdRequest().decode(payload);
            return await getTransactionById(ws, req);
        }
        case "addExchange": {
            const req = codecForAddExchangeRequest().decode(payload);
            await updateExchangeFromUrl(ws, req.exchangeBaseUrl, {
                forceNow: req.forceUpdate,
            });
            return {};
        }
        case "listExchanges": {
            return await getExchanges(ws);
        }
        case "getExchangeDetailedInfo": {
            const req = codecForAddExchangeRequest().decode(payload);
            return await getExchangeDetailedInfo(ws, req.exchangeBaseUrl);
        }
        case "listKnownBankAccounts": {
            const req = codecForListKnownBankAccounts().decode(payload);
            return await listKnownBankAccounts(ws, req.currency);
        }
        case "addKnownBankAccounts": {
            const req = codecForAddKnownBankAccounts().decode(payload);
            await addKnownBankAccounts(ws, req.payto, req.alias, req.currency);
            return {};
        }
        case "forgetKnownBankAccounts": {
            const req = codecForForgetKnownBankAccounts().decode(payload);
            await forgetKnownBankAccounts(ws, req.payto);
            return {};
        }
        case "getWithdrawalDetailsForUri": {
            const req = codecForGetWithdrawalDetailsForUri().decode(payload);
            return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri);
        }
        case "getExchangeWithdrawalInfo": {
            const req = codecForGetExchangeWithdrawalInfo().decode(payload);
            return await getExchangeWithdrawalInfo(ws, req.exchangeBaseUrl, req.amount, req.ageRestricted);
        }
        case "acceptManualWithdrawal": {
            const req = codecForAcceptManualWithdrawalRequet().decode(payload);
            const res = await createManualWithdrawal(ws, {
                amount: Amounts.parseOrThrow(req.amount),
                exchangeBaseUrl: req.exchangeBaseUrl,
                restrictAge: req.restrictAge,
            });
            return res;
        }
        case "getWithdrawalDetailsForAmount": {
            const req = codecForGetWithdrawalDetailsForAmountRequest().decode(payload);
            return await getWithdrawalDetailsForAmount(ws, req.exchangeBaseUrl, Amounts.parseOrThrow(req.amount), req.restrictAge);
        }
        case "getBalances": {
            return await getBalances(ws);
        }
        case "getPendingOperations": {
            return await getPendingOperations(ws);
        }
        case "setExchangeTosAccepted": {
            const req = codecForAcceptExchangeTosRequest().decode(payload);
            await acceptExchangeTermsOfService(ws, req.exchangeBaseUrl, req.etag);
            return {};
        }
        case "applyRefund": {
            const req = codecForApplyRefundRequest().decode(payload);
            return await applyRefund(ws, req.talerRefundUri);
        }
        case "applyRefundFromPurchaseId": {
            const req = codecForApplyRefundFromPurchaseIdRequest().decode(payload);
            return await applyRefundFromPurchaseId(ws, req.purchaseId);
        }
        case "acceptBankIntegratedWithdrawal": {
            const req = codecForAcceptBankIntegratedWithdrawalRequest().decode(payload);
            return await acceptWithdrawalFromUri(ws, {
                selectedExchange: req.exchangeBaseUrl,
                talerWithdrawUri: req.talerWithdrawUri,
                forcedDenomSel: req.forcedDenomSel,
                restrictAge: req.restrictAge,
            });
        }
        case "getExchangeTos": {
            const req = codecForGetExchangeTosRequest().decode(payload);
            return getExchangeTos(ws, req.exchangeBaseUrl, req.acceptedFormat);
        }
        case "getContractTermsDetails": {
            const req = codecForGetContractTermsDetails().decode(payload);
            return getContractTermsDetails(ws, req.proposalId);
        }
        case "retryPendingNow": {
            await runPending(ws, true);
            return {};
        }
        // FIXME: Deprecate one of the aliases!
        case "preparePayForUri":
        case "preparePay": {
            const req = codecForPreparePayRequest().decode(payload);
            return await preparePayForUri(ws, req.talerPayUri);
        }
        case "confirmPay": {
            const req = codecForConfirmPayRequest().decode(payload);
            return await confirmPay(ws, req.proposalId, req.sessionId);
        }
        case "abortFailedPayWithRefund": {
            const req = codecForAbortPayWithRefundRequest().decode(payload);
            await abortFailedPayWithRefund(ws, req.proposalId);
            return {};
        }
        case "dumpCoins": {
            return await dumpCoins(ws);
        }
        case "setCoinSuspended": {
            const req = codecForSetCoinSuspendedRequest().decode(payload);
            await setCoinSuspended(ws, req.coinPub, req.suspended);
            return {};
        }
        case "forceRefresh": {
            const req = codecForForceRefreshRequest().decode(payload);
            const coinPubs = req.coinPubList.map((x) => ({ coinPub: x }));
            const refreshGroupId = await ws.db
                .mktx((x) => [
                x.refreshGroups,
                x.coinAvailability,
                x.denominations,
                x.coins,
            ])
                .runReadWrite(async (tx) => {
                return await createRefreshGroup(ws, tx, coinPubs, RefreshReason.Manual);
            });
            processRefreshGroup(ws, refreshGroupId.refreshGroupId, {
                forceNow: true,
            }).catch((x) => {
                logger$6.error(x);
            });
            return {
                refreshGroupId,
            };
        }
        case "prepareTip": {
            const req = codecForPrepareTipRequest().decode(payload);
            return await prepareTip(ws, req.talerTipUri);
        }
        case "prepareRefund": {
            const req = codecForPrepareRefundRequest().decode(payload);
            return await prepareRefund(ws, req.talerRefundUri);
        }
        case "acceptTip": {
            const req = codecForAcceptTipRequest().decode(payload);
            return await acceptTip(ws, req.walletTipId);
        }
        case "exportBackupPlain": {
            return exportBackup(ws);
        }
        case "addBackupProvider": {
            const req = codecForAddBackupProviderRequest().decode(payload);
            await addBackupProvider(ws, req);
            return {};
        }
        case "runBackupCycle": {
            const req = codecForRunBackupCycle().decode(payload);
            await runBackupCycle(ws, req);
            return {};
        }
        case "removeBackupProvider": {
            const req = codecForRemoveBackupProvider().decode(payload);
            await removeBackupProvider(ws, req);
            return {};
        }
        case "exportBackupRecovery": {
            const resp = await getBackupRecovery(ws);
            return resp;
        }
        case "importBackupRecovery": {
            const req = codecForAny().decode(payload);
            await loadBackupRecovery(ws, req);
            return {};
        }
        case "getBackupInfo": {
            const resp = await getBackupInfo(ws);
            return resp;
        }
        case "getFeeForDeposit": {
            const req = codecForGetFeeForDeposit().decode(payload);
            return await getFeeForDeposit(ws, req);
        }
        case "prepareDeposit": {
            const req = codecForPrepareDepositRequest().decode(payload);
            return await prepareDepositGroup(ws, req);
        }
        case "createDepositGroup": {
            const req = codecForCreateDepositGroupRequest().decode(payload);
            return await createDepositGroup(ws, req);
        }
        case "trackDepositGroup": {
            const req = codecForTrackDepositGroupRequest().decode(payload);
            return trackDepositGroup(ws, req);
        }
        case "deleteTransaction": {
            const req = codecForDeleteTransactionRequest().decode(payload);
            await deleteTransaction(ws, req.transactionId);
            return {};
        }
        case "retryTransaction": {
            const req = codecForRetryTransactionRequest().decode(payload);
            await retryTransaction(ws, req.transactionId);
            return {};
        }
        case "setWalletDeviceId": {
            const req = codecForSetWalletDeviceIdRequest().decode(payload);
            await setWalletDeviceId(ws, req.walletDeviceId);
            return {};
        }
        case "listCurrencies": {
            return await ws.db
                .mktx((x) => [x.auditorTrust, x.exchangeTrust])
                .runReadOnly(async (tx) => {
                const trustedAuditors = await tx.auditorTrust.iter().toArray();
                const trustedExchanges = await tx.exchangeTrust.iter().toArray();
                return {
                    trustedAuditors: trustedAuditors.map((x) => ({
                        currency: x.currency,
                        auditorBaseUrl: x.auditorBaseUrl,
                        auditorPub: x.auditorPub,
                    })),
                    trustedExchanges: trustedExchanges.map((x) => ({
                        currency: x.currency,
                        exchangeBaseUrl: x.exchangeBaseUrl,
                        exchangeMasterPub: x.exchangeMasterPub,
                    })),
                };
            });
        }
        case "withdrawFakebank": {
            const req = codecForWithdrawFakebankRequest().decode(payload);
            const amount = Amounts.parseOrThrow(req.amount);
            const details = await getWithdrawalDetailsForAmount(ws, req.exchange, amount);
            const wres = await createManualWithdrawal(ws, {
                amount: amount,
                exchangeBaseUrl: req.exchange,
            });
            const paytoUri = details.paytoUris[0];
            const pt = parsePaytoUri(paytoUri);
            if (!pt) {
                throw Error("failed to parse payto URI");
            }
            const components = pt.targetPath.split("/");
            const creditorAcct = components[components.length - 1];
            logger$6.info(`making testbank transfer to '${creditorAcct}'`);
            const fbReq = await ws.http.postJson(new URL$1(`${creditorAcct}/admin/add-incoming`, req.bank).href, {
                amount: Amounts.stringify(amount),
                reserve_pub: wres.reservePub,
                debit_account: "payto://x-taler-bank/localhost/testdebtor",
            });
            const fbResp = await readSuccessResponseJsonOrThrow(fbReq, codecForAny());
            logger$6.info(`started fakebank withdrawal: ${j2s(fbResp)}`);
            return {};
        }
        case "clearDb":
            await clearDatabase(ws.db.idbHandle());
            return {};
        case "recycle": {
            const backup = await exportBackup(ws);
            await clearDatabase(ws.db.idbHandle());
            await importBackupPlain(ws, backup);
            return {};
        }
        case "exportDb": {
            const dbDump = await exportDb(ws.db.idbHandle());
            return dbDump;
        }
        case "importDb": {
            const req = codecForImportDbRequest().decode(payload);
            await importDb(ws.db.idbHandle(), req.dump);
            return [];
        }
        case "initiatePeerPushPayment": {
            const req = codecForInitiatePeerPushPaymentRequest().decode(payload);
            return await initiatePeerToPeerPush(ws, req);
        }
        case "checkPeerPushPayment": {
            const req = codecForCheckPeerPushPaymentRequest().decode(payload);
            return await checkPeerPushPayment(ws, req);
        }
        case "acceptPeerPushPayment": {
            const req = codecForAcceptPeerPushPaymentRequest().decode(payload);
            return await acceptPeerPushPayment(ws, req);
        }
        case "initiatePeerPullPayment": {
            const req = codecForInitiatePeerPullPaymentRequest().decode(payload);
            return await initiatePeerRequestForPay(ws, req);
        }
        case "checkPeerPullPayment": {
            const req = codecForCheckPeerPullPaymentRequest().decode(payload);
            return await checkPeerPullPayment(ws, req);
        }
        case "acceptPeerPullPayment": {
            const req = codecForAcceptPeerPullPaymentRequest().decode(payload);
            return await acceptPeerPullPayment(ws, req);
        }
        case "getVersion": {
            const version = {
                hash: GIT_HASH,
                version: VERSION$1,
                exchange: WALLET_EXCHANGE_PROTOCOL_VERSION,
                merchant: WALLET_MERCHANT_PROTOCOL_VERSION,
                bank: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
            };
            return version;
        }
    }
    throw TalerError.fromDetail(TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN, {
        operation,
    }, "unknown operation");
}
/**
 * Handle a request to the wallet-core API.
 */
async function handleCoreApiRequest(ws, operation, id, payload) {
    try {
        const result = await dispatchRequestInternal(ws, operation, payload);
        return {
            type: "response",
            operation,
            id,
            result,
        };
    }
    catch (e) {
        const err = getErrorDetailFromException(e);
        logger$6.info(`finished wallet core request with error: ${j2s(err)}`);
        return {
            type: "error",
            operation,
            id,
            error: err,
        };
    }
}
/**
 * Public handle to a running wallet.
 */
class Wallet {
    constructor(db, http, timer, cryptoWorkerFactory) {
        this.ws = new InternalWalletStateImpl(db, http, timer, cryptoWorkerFactory);
    }
    get client() {
        if (!this._client) {
            throw Error();
        }
        return this._client;
    }
    /**
     * Trust the exchange, do not validate signatures.
     * Only used to benchmark the exchange.
     */
    setInsecureTrustExchange() {
        this.ws.insecureTrustExchange = true;
    }
    setBatchWithdrawal(enable) {
        this.ws.batchWithdrawal = enable;
    }
    static async create(db, http, timer, cryptoWorkerFactory) {
        const w = new Wallet(db, http, timer, cryptoWorkerFactory);
        w._client = await getClientFromWalletState(w.ws);
        return w;
    }
    addNotificationListener(f) {
        return this.ws.addNotificationListener(f);
    }
    stop() {
        this.ws.stop();
    }
    runPending(forceNow = false) {
        return runPending(this.ws, forceNow);
    }
    runTaskLoop(opts) {
        return runTaskLoop(this.ws, opts);
    }
    handleCoreApiRequest(operation, id, payload) {
        return handleCoreApiRequest(this.ws, operation, id, payload);
    }
}
/**
 * Internal state of the wallet.
 *
 * This ties together all the operation implementations.
 */
class InternalWalletStateImpl {
    constructor(
    // FIXME: Make this a getter and make
    // the actual value nullable.
    // Check if we are in a DB migration / garbage collection
    // and throw an error in that case.
    db, http, timer, cryptoWorkerFactory) {
        this.db = db;
        this.http = http;
        this.timer = timer;
        /**
         * @see {@link InternalWalletState.activeLongpoll}
         */
        this.activeLongpoll = {};
        this.merchantInfoCache = {};
        this.insecureTrustExchange = false;
        this.batchWithdrawal = false;
        this.latch = new AsyncCondition$1();
        this.stopped = false;
        this.listeners = [];
        this.initCalled = false;
        this.exchangeOps = {
            getExchangeDetails,
            getExchangeTrust,
            updateExchangeFromUrl,
        };
        this.recoupOps = {
            createRecoupGroup,
            processRecoupGroup,
        };
        this.merchantOps = {
            getMerchantInfo,
        };
        // FIXME: Use an LRU cache here.
        this.denomCache = {};
        /**
         * Promises that are waiting for a particular resource.
         */
        this.resourceWaiters = {};
        /**
         * Resources that are currently locked.
         */
        this.resourceLocks = new Set();
        this.cryptoDispatcher = new CryptoDispatcher(cryptoWorkerFactory);
        this.cryptoApi = this.cryptoDispatcher.cryptoApi;
        this.timerGroup = new TimerGroup(timer);
    }
    async getDenomInfo(ws, tx, exchangeBaseUrl, denomPubHash) {
        const key = `${exchangeBaseUrl}:${denomPubHash}`;
        const cached = this.denomCache[key];
        if (cached) {
            return cached;
        }
        const d = await tx.denominations.get([exchangeBaseUrl, denomPubHash]);
        if (d) {
            return DenominationRecord.toDenomInfo(d);
        }
        return undefined;
    }
    notify(n) {
        logger$6.trace("Notification", n);
        for (const l of this.listeners) {
            const nc = JSON.parse(JSON.stringify(n));
            setTimeout(() => {
                l(nc);
            }, 0);
        }
    }
    addNotificationListener(f) {
        this.listeners.push(f);
    }
    /**
     * Stop ongoing processing.
     */
    stop() {
        logger$6.info("stopping (at internal wallet state)");
        this.stopped = true;
        this.timerGroup.stopCurrentAndFutureTimers();
        this.cryptoDispatcher.stop();
        for (const key of Object.keys(this.activeLongpoll)) {
            logger$6.info(`cancelling active longpoll ${key}`);
            this.activeLongpoll[key].cancel();
        }
    }
    async runUntilDone(req = {}) {
        await runTaskLoop(this, Object.assign(Object.assign({}, req), { stopWhenDone: true }));
    }
    /**
     * Run an async function after acquiring a list of locks, identified
     * by string tokens.
     */
    async runSequentialized(tokens, f) {
        var _a;
        // Make sure locks are always acquired in the same order
        tokens = [...tokens].sort();
        for (const token of tokens) {
            if (this.resourceLocks.has(token)) {
                const p = openPromise$1();
                let waitList = this.resourceWaiters[token];
                if (!waitList) {
                    waitList = this.resourceWaiters[token] = [];
                }
                waitList.push(p);
                await p.promise;
            }
            this.resourceLocks.add(token);
        }
        try {
            logger$6.trace(`begin exclusive execution on ${JSON.stringify(tokens)}`);
            const result = await f();
            logger$6.trace(`end exclusive execution on ${JSON.stringify(tokens)}`);
            return result;
        }
        finally {
            for (const token of tokens) {
                this.resourceLocks.delete(token);
                let waiter = ((_a = this.resourceWaiters[token]) !== null && _a !== void 0 ? _a : []).shift();
                if (waiter) {
                    waiter.resolve();
                }
            }
        }
    }
}

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
new Logger("synchronousWorker.ts");

/*
 This file is part of GNU Taler
 (C) 2022 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$5 = new Logger("bank-api-client.ts");
var CreditDebitIndicator;
(function (CreditDebitIndicator) {
    CreditDebitIndicator["Credit"] = "credit";
    CreditDebitIndicator["Debit"] = "debit";
})(CreditDebitIndicator || (CreditDebitIndicator = {}));
/**
 * Helper function to generate the "Authorization" HTTP header.
 */
function makeBasicAuthHeader(username, password) {
    const auth = `${username}:${password}`;
    const authEncoded = Buffer.from(auth).toString("base64");
    return `Basic ${authEncoded}`;
}
const codecForWithdrawalOperationInfo = () => buildCodecForObject()
    .property("withdrawal_id", codecForString())
    .property("taler_withdraw_uri", codecForString())
    .build("WithdrawalOperationInfo");
var BankApi;
(function (BankApi) {
    // FIXME: Move to BankAccessApi?!
    async function registerAccount(bank, username, password) {
        const url = new URL("testing/register", bank.bankAccessApiBaseUrl);
        const resp = await bank.http.postJson(url.href, { username, password });
        let paytoUri = `payto://x-taler-bank/localhost/${username}`;
        if (resp.status !== 200 && resp.status !== 202 && resp.status !== 204) {
            logger$5.error(`${j2s(await resp.json())}`);
            throw TalerError.fromDetail(TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, {
                httpStatusCode: resp.status,
            });
        }
        try {
            // Pybank has no body, thus this might throw.
            const respJson = await resp.json();
            // LibEuFin demobank returns payto URI in response
            if (respJson.paytoUri) {
                paytoUri = respJson.paytoUri;
            }
        }
        catch (e) {
            // Do nothing
        }
        return {
            password,
            username,
            accountPaytoUri: paytoUri,
        };
    }
    BankApi.registerAccount = registerAccount;
    // FIXME: Move to BankAccessApi?!
    async function createRandomBankUser(bank) {
        const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
        const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase();
        return await registerAccount(bank, username, password);
    }
    BankApi.createRandomBankUser = createRandomBankUser;
    async function adminAddIncoming(bank, params) {
        let maybeBaseUrl = bank.baseUrl;
        let url = new URL(`taler-wire-gateway/${params.exchangeBankAccount.accountName}/admin/add-incoming`, maybeBaseUrl);
        await bank.http.postJson(url.href, {
            amount: params.amount,
            reserve_pub: params.reservePub,
            debit_account: params.debitAccountPayto,
        }, {
            headers: {
                Authorization: makeBasicAuthHeader(params.exchangeBankAccount.accountName, params.exchangeBankAccount.accountPassword),
            },
        });
    }
    BankApi.adminAddIncoming = adminAddIncoming;
    async function confirmWithdrawalOperation(bank, bankUser, wopi) {
        const url = new URL(`accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/confirm`, bank.bankAccessApiBaseUrl);
        logger$5.info(`confirming withdrawal operation via ${url.href}`);
        const resp = await bank.http.postJson(url.href, {}, {
            headers: {
                Authorization: makeBasicAuthHeader(bankUser.username, bankUser.password),
            },
        });
        logger$5.info(`response status ${resp.status}`);
        await readSuccessResponseJsonOrThrow(resp, codecForAny());
        // FIXME: We don't check the status here!
    }
    BankApi.confirmWithdrawalOperation = confirmWithdrawalOperation;
    async function abortWithdrawalOperation(bank, bankUser, wopi) {
        const url = new URL(`accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/abort`, bank.bankAccessApiBaseUrl);
        const resp = await bank.http.postJson(url.href, {}, {
            headers: {
                Authorization: makeBasicAuthHeader(bankUser.username, bankUser.password),
            },
        });
        await readSuccessResponseJsonOrThrow(resp, codecForAny());
    }
    BankApi.abortWithdrawalOperation = abortWithdrawalOperation;
})(BankApi || (BankApi = {}));
var BankAccessApi;
(function (BankAccessApi) {
    async function getAccountBalance(bank, bankUser) {
        const url = new URL(`accounts/${bankUser.username}`, bank.bankAccessApiBaseUrl);
        const resp = await bank.http.get(url.href, {
            headers: {
                Authorization: makeBasicAuthHeader(bankUser.username, bankUser.password),
            },
        });
        return await resp.json();
    }
    BankAccessApi.getAccountBalance = getAccountBalance;
    async function createWithdrawalOperation(bank, bankUser, amount) {
        const url = new URL(`accounts/${bankUser.username}/withdrawals`, bank.bankAccessApiBaseUrl);
        const resp = await bank.http.postJson(url.href, {
            amount,
        }, {
            headers: {
                Authorization: makeBasicAuthHeader(bankUser.username, bankUser.password),
            },
        });
        return readSuccessResponseJsonOrThrow(resp, codecForWithdrawalOperationInfo());
    }
    BankAccessApi.createWithdrawalOperation = createWithdrawalOperation;
})(BankAccessApi || (BankAccessApi = {}));

/*
 This file is part of GNU Taler
 (C) 2021 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
new Logger("dbless.ts");

var axios$2 = {exports: {}};

var axios$1 = {exports: {}};

var bind$2 = 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);
  };
};

var bind$1 = bind$2;

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

var toString = Object.prototype.toString;

// eslint-disable-next-line func-names
var kindOf = (function(cache) {
  // eslint-disable-next-line func-names
  return function(thing) {
    var str = toString.call(thing);
    return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
  };
})(Object.create(null));

function kindOfTest(type) {
  type = type.toLowerCase();
  return function isKindOf(thing) {
    return kindOf(thing) === type;
  };
}

/**
 * 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 Array.isArray(val);
}

/**
 * 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 a Buffer
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Buffer, otherwise false
 */
function isBuffer(val) {
  return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor)
    && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val);
}

/**
 * Determine if a value is an ArrayBuffer
 *
 * @function
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an ArrayBuffer, otherwise false
 */
var isArrayBuffer = kindOfTest('ArrayBuffer');


/**
 * 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) && (isArrayBuffer(val.buffer));
  }
  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 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 plain Object
 *
 * @param {Object} val The value to test
 * @return {boolean} True if value is a plain Object, otherwise false
 */
function isPlainObject(val) {
  if (kindOf(val) !== 'object') {
    return false;
  }

  var prototype = Object.getPrototypeOf(val);
  return prototype === null || prototype === Object.prototype;
}

/**
 * Determine if a value is a Date
 *
 * @function
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Date, otherwise false
 */
var isDate = kindOfTest('Date');

/**
 * Determine if a value is a File
 *
 * @function
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a File, otherwise false
 */
var isFile = kindOfTest('File');

/**
 * Determine if a value is a Blob
 *
 * @function
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Blob, otherwise false
 */
var isBlob = kindOfTest('Blob');

/**
 * Determine if a value is a FileList
 *
 * @function
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a File, otherwise false
 */
var isFileList = kindOfTest('FileList');

/**
 * 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 FormData
 *
 * @param {Object} thing The value to test
 * @returns {boolean} True if value is an FormData, otherwise false
 */
function isFormData(thing) {
  var pattern = '[object FormData]';
  return thing && (
    (typeof FormData === 'function' && thing instanceof FormData) ||
    toString.call(thing) === pattern ||
    (isFunction(thing.toString) && thing.toString() === pattern)
  );
}

/**
 * Determine if a value is a URLSearchParams object
 * @function
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a URLSearchParams object, otherwise false
 */
var isURLSearchParams = kindOfTest('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.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
}

/**
 * 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'
 * nativescript
 *  navigator.product -> 'NativeScript' or 'NS'
 */
function isStandardBrowserEnv() {
  if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' ||
                                           navigator.product === 'NativeScript' ||
                                           navigator.product === 'NS')) {
    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') {
    /*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 (isPlainObject(result[key]) && isPlainObject(val)) {
      result[key] = merge(result[key], val);
    } else if (isPlainObject(val)) {
      result[key] = merge({}, val);
    } else if (isArray(val)) {
      result[key] = val.slice();
    } 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$1(val, thisArg);
    } else {
      a[key] = val;
    }
  });
  return a;
}

/**
 * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
 *
 * @param {string} content with BOM
 * @return {string} content value without BOM
 */
function stripBOM(content) {
  if (content.charCodeAt(0) === 0xFEFF) {
    content = content.slice(1);
  }
  return content;
}

/**
 * Inherit the prototype methods from one constructor into another
 * @param {function} constructor
 * @param {function} superConstructor
 * @param {object} [props]
 * @param {object} [descriptors]
 */

function inherits(constructor, superConstructor, props, descriptors) {
  constructor.prototype = Object.create(superConstructor.prototype, descriptors);
  constructor.prototype.constructor = constructor;
  props && Object.assign(constructor.prototype, props);
}

/**
 * Resolve object with deep prototype chain to a flat object
 * @param {Object} sourceObj source object
 * @param {Object} [destObj]
 * @param {Function} [filter]
 * @returns {Object}
 */

function toFlatObject(sourceObj, destObj, filter) {
  var props;
  var i;
  var prop;
  var merged = {};

  destObj = destObj || {};

  do {
    props = Object.getOwnPropertyNames(sourceObj);
    i = props.length;
    while (i-- > 0) {
      prop = props[i];
      if (!merged[prop]) {
        destObj[prop] = sourceObj[prop];
        merged[prop] = true;
      }
    }
    sourceObj = Object.getPrototypeOf(sourceObj);
  } while (sourceObj && (!filter || filter(sourceObj, destObj)) && sourceObj !== Object.prototype);

  return destObj;
}

/*
 * determines whether a string ends with the characters of a specified string
 * @param {String} str
 * @param {String} searchString
 * @param {Number} [position= 0]
 * @returns {boolean}
 */
function endsWith(str, searchString, position) {
  str = String(str);
  if (position === undefined || position > str.length) {
    position = str.length;
  }
  position -= searchString.length;
  var lastIndex = str.indexOf(searchString, position);
  return lastIndex !== -1 && lastIndex === position;
}


/**
 * Returns new array from array like object
 * @param {*} [thing]
 * @returns {Array}
 */
function toArray(thing) {
  if (!thing) return null;
  var i = thing.length;
  if (isUndefined(i)) return null;
  var arr = new Array(i);
  while (i-- > 0) {
    arr[i] = thing[i];
  }
  return arr;
}

// eslint-disable-next-line func-names
var isTypedArray = (function(TypedArray) {
  // eslint-disable-next-line func-names
  return function(thing) {
    return TypedArray && thing instanceof TypedArray;
  };
})(typeof Uint8Array !== 'undefined' && Object.getPrototypeOf(Uint8Array));

var utils$9 = {
  isArray: isArray,
  isArrayBuffer: isArrayBuffer,
  isBuffer: isBuffer,
  isFormData: isFormData,
  isArrayBufferView: isArrayBufferView,
  isString: isString,
  isNumber: isNumber,
  isObject: isObject,
  isPlainObject: isPlainObject,
  isUndefined: isUndefined,
  isDate: isDate,
  isFile: isFile,
  isBlob: isBlob,
  isFunction: isFunction,
  isStream: isStream,
  isURLSearchParams: isURLSearchParams,
  isStandardBrowserEnv: isStandardBrowserEnv,
  forEach: forEach,
  merge: merge,
  extend: extend,
  trim: trim,
  stripBOM: stripBOM,
  inherits: inherits,
  toFlatObject: toFlatObject,
  kindOf: kindOf,
  kindOfTest: kindOfTest,
  endsWith: endsWith,
  toArray: toArray,
  isTypedArray: isTypedArray,
  isFileList: isFileList
};

var utils$8 = utils$9;

function encode(val) {
  return encodeURIComponent(val).
    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
 */
var buildURL$1 = function buildURL(url, params, paramsSerializer) {
  /*eslint no-param-reassign:0*/
  if (!params) {
    return url;
  }

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

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

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

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

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

  if (serializedParams) {
    var hashmarkIndex = url.indexOf('#');
    if (hashmarkIndex !== -1) {
      url = url.slice(0, hashmarkIndex);
    }

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

  return url;
};

var utils$7 = utils$9;

function InterceptorManager$1() {
  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$1.prototype.use = function use(fulfilled, rejected, options) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
    synchronous: options ? options.synchronous : false,
    runWhen: options ? options.runWhen : null
  });
  return this.handlers.length - 1;
};

/**
 * Remove an interceptor from the stack
 *
 * @param {Number} id The ID that was returned by `use`
 */
InterceptorManager$1.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$1.prototype.forEach = function forEach(fn) {
  utils$7.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

var InterceptorManager_1 = InterceptorManager$1;

var utils$6 = utils$9;

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

var AxiosError_1;
var hasRequiredAxiosError;

function requireAxiosError () {
	if (hasRequiredAxiosError) return AxiosError_1;
	hasRequiredAxiosError = 1;

	var utils = utils$9;

	/**
	 * Create an Error with the specified message, config, error code, request and response.
	 *
	 * @param {string} message The error message.
	 * @param {string} [code] The error code (for example, 'ECONNABORTED').
	 * @param {Object} [config] The config.
	 * @param {Object} [request] The request.
	 * @param {Object} [response] The response.
	 * @returns {Error} The created error.
	 */
	function AxiosError(message, code, config, request, response) {
	  Error.call(this);
	  this.message = message;
	  this.name = 'AxiosError';
	  code && (this.code = code);
	  config && (this.config = config);
	  request && (this.request = request);
	  response && (this.response = response);
	}

	utils.inherits(AxiosError, Error, {
	  toJSON: function toJSON() {
	    return {
	      // Standard
	      message: this.message,
	      name: this.name,
	      // Microsoft
	      description: this.description,
	      number: this.number,
	      // Mozilla
	      fileName: this.fileName,
	      lineNumber: this.lineNumber,
	      columnNumber: this.columnNumber,
	      stack: this.stack,
	      // Axios
	      config: this.config,
	      code: this.code,
	      status: this.response && this.response.status ? this.response.status : null
	    };
	  }
	});

	var prototype = AxiosError.prototype;
	var descriptors = {};

	[
	  'ERR_BAD_OPTION_VALUE',
	  'ERR_BAD_OPTION',
	  'ECONNABORTED',
	  'ETIMEDOUT',
	  'ERR_NETWORK',
	  'ERR_FR_TOO_MANY_REDIRECTS',
	  'ERR_DEPRECATED',
	  'ERR_BAD_RESPONSE',
	  'ERR_BAD_REQUEST',
	  'ERR_CANCELED'
	// eslint-disable-next-line func-names
	].forEach(function(code) {
	  descriptors[code] = {value: code};
	});

	Object.defineProperties(AxiosError, descriptors);
	Object.defineProperty(prototype, 'isAxiosError', {value: true});

	// eslint-disable-next-line func-names
	AxiosError.from = function(error, code, config, request, response, customProps) {
	  var axiosError = Object.create(prototype);

	  utils.toFlatObject(error, axiosError, function filter(obj) {
	    return obj !== Error.prototype;
	  });

	  AxiosError.call(axiosError, error.message, code, config, request, response);

	  axiosError.name = error.name;

	  customProps && Object.assign(axiosError, customProps);

	  return axiosError;
	};

	AxiosError_1 = AxiosError;
	return AxiosError_1;
}

var transitional = {
  silentJSONParsing: true,
  forcedJSONParsing: true,
  clarifyTimeoutError: false
};

var toFormData_1;
var hasRequiredToFormData;

function requireToFormData () {
	if (hasRequiredToFormData) return toFormData_1;
	hasRequiredToFormData = 1;

	var utils = utils$9;

	/**
	 * Convert a data object to FormData
	 * @param {Object} obj
	 * @param {?Object} [formData]
	 * @returns {Object}
	 **/

	function toFormData(obj, formData) {
	  // eslint-disable-next-line no-param-reassign
	  formData = formData || new FormData();

	  var stack = [];

	  function convertValue(value) {
	    if (value === null) return '';

	    if (utils.isDate(value)) {
	      return value.toISOString();
	    }

	    if (utils.isArrayBuffer(value) || utils.isTypedArray(value)) {
	      return typeof Blob === 'function' ? new Blob([value]) : Buffer.from(value);
	    }

	    return value;
	  }

	  function build(data, parentKey) {
	    if (utils.isPlainObject(data) || utils.isArray(data)) {
	      if (stack.indexOf(data) !== -1) {
	        throw Error('Circular reference detected in ' + parentKey);
	      }

	      stack.push(data);

	      utils.forEach(data, function each(value, key) {
	        if (utils.isUndefined(value)) return;
	        var fullKey = parentKey ? parentKey + '.' + key : key;
	        var arr;

	        if (value && !parentKey && typeof value === 'object') {
	          if (utils.endsWith(key, '{}')) {
	            // eslint-disable-next-line no-param-reassign
	            value = JSON.stringify(value);
	          } else if (utils.endsWith(key, '[]') && (arr = utils.toArray(value))) {
	            // eslint-disable-next-line func-names
	            arr.forEach(function(el) {
	              !utils.isUndefined(el) && formData.append(fullKey, convertValue(el));
	            });
	            return;
	          }
	        }

	        build(value, fullKey);
	      });

	      stack.pop();
	    } else {
	      formData.append(parentKey, convertValue(data));
	    }
	  }

	  build(obj);

	  return formData;
	}

	toFormData_1 = toFormData;
	return toFormData_1;
}

var settle;
var hasRequiredSettle;

function requireSettle () {
	if (hasRequiredSettle) return settle;
	hasRequiredSettle = 1;

	var AxiosError = requireAxiosError();

	/**
	 * 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.
	 */
	settle = function settle(resolve, reject, response) {
	  var validateStatus = response.config.validateStatus;
	  if (!response.status || !validateStatus || validateStatus(response.status)) {
	    resolve(response);
	  } else {
	    reject(new AxiosError(
	      'Request failed with status code ' + response.status,
	      [AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4],
	      response.config,
	      response.request,
	      response
	    ));
	  }
	};
	return settle;
}

var cookies;
var hasRequiredCookies;

function requireCookies () {
	if (hasRequiredCookies) return cookies;
	hasRequiredCookies = 1;

	var utils = utils$9;

	cookies = (
	  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() {}
	      };
	    })()
	);
	return cookies;
}

/**
 * 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
 */
var isAbsoluteURL$1 = 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);
};

/**
 * 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
 */
var combineURLs$1 = function combineURLs(baseURL, relativeURL) {
  return relativeURL
    ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
    : baseURL;
};

var isAbsoluteURL = isAbsoluteURL$1;
var combineURLs = combineURLs$1;

/**
 * Creates a new URL by combining the baseURL with the requestedURL,
 * only when the requestedURL is not already an absolute URL.
 * If the requestURL is absolute, this function returns the requestedURL untouched.
 *
 * @param {string} baseURL The base URL
 * @param {string} requestedURL Absolute or relative URL to combine
 * @returns {string} The combined full path
 */
var buildFullPath$1 = function buildFullPath(baseURL, requestedURL) {
  if (baseURL && !isAbsoluteURL(requestedURL)) {
    return combineURLs(baseURL, requestedURL);
  }
  return requestedURL;
};

var parseHeaders;
var hasRequiredParseHeaders;

function requireParseHeaders () {
	if (hasRequiredParseHeaders) return parseHeaders;
	hasRequiredParseHeaders = 1;

	var utils = utils$9;

	// Headers whose duplicates are ignored by node
	// c.f. https://nodejs.org/api/http.html#http_message_headers
	var ignoreDuplicateOf = [
	  'age', 'authorization', 'content-length', 'content-type', 'etag',
	  'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since',
	  'last-modified', 'location', 'max-forwards', 'proxy-authorization',
	  'referer', 'retry-after', 'user-agent'
	];

	/**
	 * 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
	 */
	parseHeaders = 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) {
	      if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) {
	        return;
	      }
	      if (key === 'set-cookie') {
	        parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]);
	      } else {
	        parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
	      }
	    }
	  });

	  return parsed;
	};
	return parseHeaders;
}

var isURLSameOrigin;
var hasRequiredIsURLSameOrigin;

function requireIsURLSameOrigin () {
	if (hasRequiredIsURLSameOrigin) return isURLSameOrigin;
	hasRequiredIsURLSameOrigin = 1;

	var utils = utils$9;

	isURLSameOrigin = (
	  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;
	      };
	    })()
	);
	return isURLSameOrigin;
}

var CanceledError_1;
var hasRequiredCanceledError;

function requireCanceledError () {
	if (hasRequiredCanceledError) return CanceledError_1;
	hasRequiredCanceledError = 1;

	var AxiosError = requireAxiosError();
	var utils = utils$9;

	/**
	 * A `CanceledError` is an object that is thrown when an operation is canceled.
	 *
	 * @class
	 * @param {string=} message The message.
	 */
	function CanceledError(message) {
	  // eslint-disable-next-line no-eq-null,eqeqeq
	  AxiosError.call(this, message == null ? 'canceled' : message, AxiosError.ERR_CANCELED);
	  this.name = 'CanceledError';
	}

	utils.inherits(CanceledError, AxiosError, {
	  __CANCEL__: true
	});

	CanceledError_1 = CanceledError;
	return CanceledError_1;
}

var parseProtocol;
var hasRequiredParseProtocol;

function requireParseProtocol () {
	if (hasRequiredParseProtocol) return parseProtocol;
	hasRequiredParseProtocol = 1;

	parseProtocol = function parseProtocol(url) {
	  var match = /^([-+\w]{1,25})(:?\/\/|:)/.exec(url);
	  return match && match[1] || '';
	};
	return parseProtocol;
}

var xhr;
var hasRequiredXhr;

function requireXhr () {
	if (hasRequiredXhr) return xhr;
	hasRequiredXhr = 1;

	var utils = utils$9;
	var settle = requireSettle();
	var cookies = requireCookies();
	var buildURL = buildURL$1;
	var buildFullPath = buildFullPath$1;
	var parseHeaders = requireParseHeaders();
	var isURLSameOrigin = requireIsURLSameOrigin();
	var transitionalDefaults = transitional;
	var AxiosError = requireAxiosError();
	var CanceledError = requireCanceledError();
	var parseProtocol = requireParseProtocol();

	xhr = function xhrAdapter(config) {
	  return new Promise(function dispatchXhrRequest(resolve, reject) {
	    var requestData = config.data;
	    var requestHeaders = config.headers;
	    var responseType = config.responseType;
	    var onCanceled;
	    function done() {
	      if (config.cancelToken) {
	        config.cancelToken.unsubscribe(onCanceled);
	      }

	      if (config.signal) {
	        config.signal.removeEventListener('abort', onCanceled);
	      }
	    }

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

	    var request = new XMLHttpRequest();

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

	    var fullPath = buildFullPath(config.baseURL, config.url);

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

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

	    function onloadend() {
	      if (!request) {
	        return;
	      }
	      // Prepare the response
	      var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
	      var responseData = !responseType || responseType === 'text' ||  responseType === 'json' ?
	        request.responseText : request.response;
	      var response = {
	        data: responseData,
	        status: request.status,
	        statusText: request.statusText,
	        headers: responseHeaders,
	        config: config,
	        request: request
	      };

	      settle(function _resolve(value) {
	        resolve(value);
	        done();
	      }, function _reject(err) {
	        reject(err);
	        done();
	      }, response);

	      // Clean up request
	      request = null;
	    }

	    if ('onloadend' in request) {
	      // Use onloadend if available
	      request.onloadend = onloadend;
	    } else {
	      // Listen for ready state to emulate onloadend
	      request.onreadystatechange = function handleLoad() {
	        if (!request || request.readyState !== 4) {
	          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;
	        }
	        // readystate handler is calling before onerror or ontimeout handlers,
	        // so we should call onloadend on the next 'tick'
	        setTimeout(onloadend);
	      };
	    }

	    // Handle browser request cancellation (as opposed to a manual cancellation)
	    request.onabort = function handleAbort() {
	      if (!request) {
	        return;
	      }

	      reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));

	      // 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(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request, request));

	      // Clean up request
	      request = null;
	    };

	    // Handle timeout
	    request.ontimeout = function handleTimeout() {
	      var timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
	      var transitional = config.transitional || transitionalDefaults;
	      if (config.timeoutErrorMessage) {
	        timeoutErrorMessage = config.timeoutErrorMessage;
	      }
	      reject(new AxiosError(
	        timeoutErrorMessage,
	        transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
	        config,
	        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()) {
	      // Add xsrf header
	      var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && 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 (!utils.isUndefined(config.withCredentials)) {
	      request.withCredentials = !!config.withCredentials;
	    }

	    // Add responseType to request if needed
	    if (responseType && responseType !== 'json') {
	      request.responseType = config.responseType;
	    }

	    // 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 || config.signal) {
	      // Handle cancellation
	      // eslint-disable-next-line func-names
	      onCanceled = function(cancel) {
	        if (!request) {
	          return;
	        }
	        reject(!cancel || (cancel && cancel.type) ? new CanceledError() : cancel);
	        request.abort();
	        request = null;
	      };

	      config.cancelToken && config.cancelToken.subscribe(onCanceled);
	      if (config.signal) {
	        config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
	      }
	    }

	    if (!requestData) {
	      requestData = null;
	    }

	    var protocol = parseProtocol(fullPath);

	    if (protocol && [ 'http', 'https', 'file' ].indexOf(protocol) === -1) {
	      reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
	      return;
	    }


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

var followRedirects = {exports: {}};

var src = {exports: {}};

var browser = {exports: {}};

/**
 * Helpers.
 */

var ms;
var hasRequiredMs;

function requireMs () {
	if (hasRequiredMs) return ms;
	hasRequiredMs = 1;
	var s = 1000;
	var m = s * 60;
	var h = m * 60;
	var d = h * 24;
	var w = d * 7;
	var y = d * 365.25;

	/**
	 * Parse or format the given `val`.
	 *
	 * Options:
	 *
	 *  - `long` verbose formatting [false]
	 *
	 * @param {String|Number} val
	 * @param {Object} [options]
	 * @throws {Error} throw an error if val is not a non-empty string or a number
	 * @return {String|Number}
	 * @api public
	 */

	ms = function(val, options) {
	  options = options || {};
	  var type = typeof val;
	  if (type === 'string' && val.length > 0) {
	    return parse(val);
	  } else if (type === 'number' && isFinite(val)) {
	    return options.long ? fmtLong(val) : fmtShort(val);
	  }
	  throw new Error(
	    'val is not a non-empty string or a valid number. val=' +
	      JSON.stringify(val)
	  );
	};

	/**
	 * Parse the given `str` and return milliseconds.
	 *
	 * @param {String} str
	 * @return {Number}
	 * @api private
	 */

	function parse(str) {
	  str = String(str);
	  if (str.length > 100) {
	    return;
	  }
	  var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
	    str
	  );
	  if (!match) {
	    return;
	  }
	  var n = parseFloat(match[1]);
	  var type = (match[2] || 'ms').toLowerCase();
	  switch (type) {
	    case 'years':
	    case 'year':
	    case 'yrs':
	    case 'yr':
	    case 'y':
	      return n * y;
	    case 'weeks':
	    case 'week':
	    case 'w':
	      return n * w;
	    case 'days':
	    case 'day':
	    case 'd':
	      return n * d;
	    case 'hours':
	    case 'hour':
	    case 'hrs':
	    case 'hr':
	    case 'h':
	      return n * h;
	    case 'minutes':
	    case 'minute':
	    case 'mins':
	    case 'min':
	    case 'm':
	      return n * m;
	    case 'seconds':
	    case 'second':
	    case 'secs':
	    case 'sec':
	    case 's':
	      return n * s;
	    case 'milliseconds':
	    case 'millisecond':
	    case 'msecs':
	    case 'msec':
	    case 'ms':
	      return n;
	    default:
	      return undefined;
	  }
	}

	/**
	 * Short format for `ms`.
	 *
	 * @param {Number} ms
	 * @return {String}
	 * @api private
	 */

	function fmtShort(ms) {
	  var msAbs = Math.abs(ms);
	  if (msAbs >= d) {
	    return Math.round(ms / d) + 'd';
	  }
	  if (msAbs >= h) {
	    return Math.round(ms / h) + 'h';
	  }
	  if (msAbs >= m) {
	    return Math.round(ms / m) + 'm';
	  }
	  if (msAbs >= s) {
	    return Math.round(ms / s) + 's';
	  }
	  return ms + 'ms';
	}

	/**
	 * Long format for `ms`.
	 *
	 * @param {Number} ms
	 * @return {String}
	 * @api private
	 */

	function fmtLong(ms) {
	  var msAbs = Math.abs(ms);
	  if (msAbs >= d) {
	    return plural(ms, msAbs, d, 'day');
	  }
	  if (msAbs >= h) {
	    return plural(ms, msAbs, h, 'hour');
	  }
	  if (msAbs >= m) {
	    return plural(ms, msAbs, m, 'minute');
	  }
	  if (msAbs >= s) {
	    return plural(ms, msAbs, s, 'second');
	  }
	  return ms + ' ms';
	}

	/**
	 * Pluralization helper.
	 */

	function plural(ms, msAbs, n, name) {
	  var isPlural = msAbs >= n * 1.5;
	  return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : '');
	}
	return ms;
}

var common;
var hasRequiredCommon;

function requireCommon () {
	if (hasRequiredCommon) return common;
	hasRequiredCommon = 1;
	/**
	 * This is the common logic for both the Node.js and web browser
	 * implementations of `debug()`.
	 */

	function setup(env) {
		createDebug.debug = createDebug;
		createDebug.default = createDebug;
		createDebug.coerce = coerce;
		createDebug.disable = disable;
		createDebug.enable = enable;
		createDebug.enabled = enabled;
		createDebug.humanize = requireMs();
		createDebug.destroy = destroy;

		Object.keys(env).forEach(key => {
			createDebug[key] = env[key];
		});

		/**
		* The currently active debug mode names, and names to skip.
		*/

		createDebug.names = [];
		createDebug.skips = [];

		/**
		* Map of special "%n" handling functions, for the debug "format" argument.
		*
		* Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
		*/
		createDebug.formatters = {};

		/**
		* Selects a color for a debug namespace
		* @param {String} namespace The namespace string for the debug instance to be colored
		* @return {Number|String} An ANSI color code for the given namespace
		* @api private
		*/
		function selectColor(namespace) {
			let hash = 0;

			for (let i = 0; i < namespace.length; i++) {
				hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
				hash |= 0; // Convert to 32bit integer
			}

			return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
		}
		createDebug.selectColor = selectColor;

		/**
		* Create a debugger with the given `namespace`.
		*
		* @param {String} namespace
		* @return {Function}
		* @api public
		*/
		function createDebug(namespace) {
			let prevTime;
			let enableOverride = null;
			let namespacesCache;
			let enabledCache;

			function debug(...args) {
				// Disabled?
				if (!debug.enabled) {
					return;
				}

				const self = debug;

				// Set `diff` timestamp
				const curr = Number(new Date());
				const ms = curr - (prevTime || curr);
				self.diff = ms;
				self.prev = prevTime;
				self.curr = curr;
				prevTime = curr;

				args[0] = createDebug.coerce(args[0]);

				if (typeof args[0] !== 'string') {
					// Anything else let's inspect with %O
					args.unshift('%O');
				}

				// Apply any `formatters` transformations
				let index = 0;
				args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
					// If we encounter an escaped % then don't increase the array index
					if (match === '%%') {
						return '%';
					}
					index++;
					const formatter = createDebug.formatters[format];
					if (typeof formatter === 'function') {
						const val = args[index];
						match = formatter.call(self, val);

						// Now we need to remove `args[index]` since it's inlined in the `format`
						args.splice(index, 1);
						index--;
					}
					return match;
				});

				// Apply env-specific formatting (colors, etc.)
				createDebug.formatArgs.call(self, args);

				const logFn = self.log || createDebug.log;
				logFn.apply(self, args);
			}

			debug.namespace = namespace;
			debug.useColors = createDebug.useColors();
			debug.color = createDebug.selectColor(namespace);
			debug.extend = extend;
			debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release.

			Object.defineProperty(debug, 'enabled', {
				enumerable: true,
				configurable: false,
				get: () => {
					if (enableOverride !== null) {
						return enableOverride;
					}
					if (namespacesCache !== createDebug.namespaces) {
						namespacesCache = createDebug.namespaces;
						enabledCache = createDebug.enabled(namespace);
					}

					return enabledCache;
				},
				set: v => {
					enableOverride = v;
				}
			});

			// Env-specific initialization logic for debug instances
			if (typeof createDebug.init === 'function') {
				createDebug.init(debug);
			}

			return debug;
		}

		function extend(namespace, delimiter) {
			const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
			newDebug.log = this.log;
			return newDebug;
		}

		/**
		* Enables a debug mode by namespaces. This can include modes
		* separated by a colon and wildcards.
		*
		* @param {String} namespaces
		* @api public
		*/
		function enable(namespaces) {
			createDebug.save(namespaces);
			createDebug.namespaces = namespaces;

			createDebug.names = [];
			createDebug.skips = [];

			let i;
			const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
			const len = split.length;

			for (i = 0; i < len; i++) {
				if (!split[i]) {
					// ignore empty strings
					continue;
				}

				namespaces = split[i].replace(/\*/g, '.*?');

				if (namespaces[0] === '-') {
					createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
				} else {
					createDebug.names.push(new RegExp('^' + namespaces + '$'));
				}
			}
		}

		/**
		* Disable debug output.
		*
		* @return {String} namespaces
		* @api public
		*/
		function disable() {
			const namespaces = [
				...createDebug.names.map(toNamespace),
				...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace)
			].join(',');
			createDebug.enable('');
			return namespaces;
		}

		/**
		* Returns true if the given mode name is enabled, false otherwise.
		*
		* @param {String} name
		* @return {Boolean}
		* @api public
		*/
		function enabled(name) {
			if (name[name.length - 1] === '*') {
				return true;
			}

			let i;
			let len;

			for (i = 0, len = createDebug.skips.length; i < len; i++) {
				if (createDebug.skips[i].test(name)) {
					return false;
				}
			}

			for (i = 0, len = createDebug.names.length; i < len; i++) {
				if (createDebug.names[i].test(name)) {
					return true;
				}
			}

			return false;
		}

		/**
		* Convert regexp to namespace
		*
		* @param {RegExp} regxep
		* @return {String} namespace
		* @api private
		*/
		function toNamespace(regexp) {
			return regexp.toString()
				.substring(2, regexp.toString().length - 2)
				.replace(/\.\*\?$/, '*');
		}

		/**
		* Coerce `val`.
		*
		* @param {Mixed} val
		* @return {Mixed}
		* @api private
		*/
		function coerce(val) {
			if (val instanceof Error) {
				return val.stack || val.message;
			}
			return val;
		}

		/**
		* XXX DO NOT USE. This is a temporary stub function.
		* XXX It WILL be removed in the next major release.
		*/
		function destroy() {
			console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
		}

		createDebug.enable(createDebug.load());

		return createDebug;
	}

	common = setup;
	return common;
}

/* eslint-env browser */

var hasRequiredBrowser;

function requireBrowser () {
	if (hasRequiredBrowser) return browser.exports;
	hasRequiredBrowser = 1;
	(function (module, exports) {
		/**
		 * This is the web browser implementation of `debug()`.
		 */

		exports.formatArgs = formatArgs;
		exports.save = save;
		exports.load = load;
		exports.useColors = useColors;
		exports.storage = localstorage();
		exports.destroy = (() => {
			let warned = false;

			return () => {
				if (!warned) {
					warned = true;
					console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
				}
			};
		})();

		/**
		 * Colors.
		 */

		exports.colors = [
			'#0000CC',
			'#0000FF',
			'#0033CC',
			'#0033FF',
			'#0066CC',
			'#0066FF',
			'#0099CC',
			'#0099FF',
			'#00CC00',
			'#00CC33',
			'#00CC66',
			'#00CC99',
			'#00CCCC',
			'#00CCFF',
			'#3300CC',
			'#3300FF',
			'#3333CC',
			'#3333FF',
			'#3366CC',
			'#3366FF',
			'#3399CC',
			'#3399FF',
			'#33CC00',
			'#33CC33',
			'#33CC66',
			'#33CC99',
			'#33CCCC',
			'#33CCFF',
			'#6600CC',
			'#6600FF',
			'#6633CC',
			'#6633FF',
			'#66CC00',
			'#66CC33',
			'#9900CC',
			'#9900FF',
			'#9933CC',
			'#9933FF',
			'#99CC00',
			'#99CC33',
			'#CC0000',
			'#CC0033',
			'#CC0066',
			'#CC0099',
			'#CC00CC',
			'#CC00FF',
			'#CC3300',
			'#CC3333',
			'#CC3366',
			'#CC3399',
			'#CC33CC',
			'#CC33FF',
			'#CC6600',
			'#CC6633',
			'#CC9900',
			'#CC9933',
			'#CCCC00',
			'#CCCC33',
			'#FF0000',
			'#FF0033',
			'#FF0066',
			'#FF0099',
			'#FF00CC',
			'#FF00FF',
			'#FF3300',
			'#FF3333',
			'#FF3366',
			'#FF3399',
			'#FF33CC',
			'#FF33FF',
			'#FF6600',
			'#FF6633',
			'#FF9900',
			'#FF9933',
			'#FFCC00',
			'#FFCC33'
		];

		/**
		 * Currently only WebKit-based Web Inspectors, Firefox >= v31,
		 * and the Firebug extension (any Firefox version) are known
		 * to support "%c" CSS customizations.
		 *
		 * TODO: add a `localStorage` variable to explicitly enable/disable colors
		 */

		// eslint-disable-next-line complexity
		function useColors() {
			// NB: In an Electron preload script, document will be defined but not fully
			// initialized. Since we know we're in Chrome, we'll just detect this case
			// explicitly
			if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {
				return true;
			}

			// Internet Explorer and Edge do not support colors.
			if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
				return false;
			}

			// Is webkit? http://stackoverflow.com/a/16459606/376773
			// document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
			return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||
				// Is firebug? http://stackoverflow.com/a/398120/376773
				(typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
				// Is firefox >= v31?
				// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
				(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) ||
				// Double check webkit in userAgent just in case we are in a worker
				(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
		}

		/**
		 * Colorize log arguments if enabled.
		 *
		 * @api public
		 */

		function formatArgs(args) {
			args[0] = (this.useColors ? '%c' : '') +
				this.namespace +
				(this.useColors ? ' %c' : ' ') +
				args[0] +
				(this.useColors ? '%c ' : ' ') +
				'+' + module.exports.humanize(this.diff);

			if (!this.useColors) {
				return;
			}

			const c = 'color: ' + this.color;
			args.splice(1, 0, c, 'color: inherit');

			// The final "%c" is somewhat tricky, because there could be other
			// arguments passed either before or after the %c, so we need to
			// figure out the correct index to insert the CSS into
			let index = 0;
			let lastC = 0;
			args[0].replace(/%[a-zA-Z%]/g, match => {
				if (match === '%%') {
					return;
				}
				index++;
				if (match === '%c') {
					// We only are interested in the *last* %c
					// (the user may have provided their own)
					lastC = index;
				}
			});

			args.splice(lastC, 0, c);
		}

		/**
		 * Invokes `console.debug()` when available.
		 * No-op when `console.debug` is not a "function".
		 * If `console.debug` is not available, falls back
		 * to `console.log`.
		 *
		 * @api public
		 */
		exports.log = console.debug || console.log || (() => {});

		/**
		 * Save `namespaces`.
		 *
		 * @param {String} namespaces
		 * @api private
		 */
		function save(namespaces) {
			try {
				if (namespaces) {
					exports.storage.setItem('debug', namespaces);
				} else {
					exports.storage.removeItem('debug');
				}
			} catch (error) {
				// Swallow
				// XXX (@Qix-) should we be logging these?
			}
		}

		/**
		 * Load `namespaces`.
		 *
		 * @return {String} returns the previously persisted debug modes
		 * @api private
		 */
		function load() {
			let r;
			try {
				r = exports.storage.getItem('debug');
			} catch (error) {
				// Swallow
				// XXX (@Qix-) should we be logging these?
			}

			// If debug isn't set in LS, and we're in Electron, try to load $DEBUG
			if (!r && typeof process !== 'undefined' && 'env' in process) {
				r = process.env.DEBUG;
			}

			return r;
		}

		/**
		 * Localstorage attempts to return the localstorage.
		 *
		 * This is necessary because safari throws
		 * when a user disables cookies/localstorage
		 * and you attempt to access it.
		 *
		 * @return {LocalStorage}
		 * @api private
		 */

		function localstorage() {
			try {
				// TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context
				// The Browser also has localStorage in the global context.
				return localStorage;
			} catch (error) {
				// Swallow
				// XXX (@Qix-) should we be logging these?
			}
		}

		module.exports = requireCommon()(exports);

		const {formatters} = module.exports;

		/**
		 * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
		 */

		formatters.j = function (v) {
			try {
				return JSON.stringify(v);
			} catch (error) {
				return '[UnexpectedJSONParseError]: ' + error.message;
			}
		};
} (browser, browser.exports));
	return browser.exports;
}

var node = {exports: {}};

var hasFlag;
var hasRequiredHasFlag;

function requireHasFlag () {
	if (hasRequiredHasFlag) return hasFlag;
	hasRequiredHasFlag = 1;

	hasFlag = (flag, argv = process.argv) => {
		const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--');
		const position = argv.indexOf(prefix + flag);
		const terminatorPosition = argv.indexOf('--');
		return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
	};
	return hasFlag;
}

var supportsColor_1;
var hasRequiredSupportsColor;

function requireSupportsColor () {
	if (hasRequiredSupportsColor) return supportsColor_1;
	hasRequiredSupportsColor = 1;
	const os = require$$0__default["default"];
	const tty = require$$1__default["default"];
	const hasFlag = requireHasFlag();

	const {env} = process;

	let flagForceColor;
	if (hasFlag('no-color') ||
		hasFlag('no-colors') ||
		hasFlag('color=false') ||
		hasFlag('color=never')) {
		flagForceColor = 0;
	} else if (hasFlag('color') ||
		hasFlag('colors') ||
		hasFlag('color=true') ||
		hasFlag('color=always')) {
		flagForceColor = 1;
	}

	function envForceColor() {
		if ('FORCE_COLOR' in env) {
			if (env.FORCE_COLOR === 'true') {
				return 1;
			}

			if (env.FORCE_COLOR === 'false') {
				return 0;
			}

			return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
		}
	}

	function translateLevel(level) {
		if (level === 0) {
			return false;
		}

		return {
			level,
			hasBasic: true,
			has256: level >= 2,
			has16m: level >= 3
		};
	}

	function supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
		const noFlagForceColor = envForceColor();
		if (noFlagForceColor !== undefined) {
			flagForceColor = noFlagForceColor;
		}

		const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;

		if (forceColor === 0) {
			return 0;
		}

		if (sniffFlags) {
			if (hasFlag('color=16m') ||
				hasFlag('color=full') ||
				hasFlag('color=truecolor')) {
				return 3;
			}

			if (hasFlag('color=256')) {
				return 2;
			}
		}

		if (haveStream && !streamIsTTY && forceColor === undefined) {
			return 0;
		}

		const min = forceColor || 0;

		if (env.TERM === 'dumb') {
			return min;
		}

		if (process.platform === 'win32') {
			// Windows 10 build 10586 is the first Windows release that supports 256 colors.
			// Windows 10 build 14931 is the first release that supports 16m/TrueColor.
			const osRelease = os.release().split('.');
			if (
				Number(osRelease[0]) >= 10 &&
				Number(osRelease[2]) >= 10586
			) {
				return Number(osRelease[2]) >= 14931 ? 3 : 2;
			}

			return 1;
		}

		if ('CI' in env) {
			if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI', 'GITHUB_ACTIONS', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') {
				return 1;
			}

			return min;
		}

		if ('TEAMCITY_VERSION' in env) {
			return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
		}

		if (env.COLORTERM === 'truecolor') {
			return 3;
		}

		if ('TERM_PROGRAM' in env) {
			const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);

			switch (env.TERM_PROGRAM) {
				case 'iTerm.app':
					return version >= 3 ? 3 : 2;
				case 'Apple_Terminal':
					return 2;
				// No default
			}
		}

		if (/-256(color)?$/i.test(env.TERM)) {
			return 2;
		}

		if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
			return 1;
		}

		if ('COLORTERM' in env) {
			return 1;
		}

		return min;
	}

	function getSupportLevel(stream, options = {}) {
		const level = supportsColor(stream, {
			streamIsTTY: stream && stream.isTTY,
			...options
		});

		return translateLevel(level);
	}

	supportsColor_1 = {
		supportsColor: getSupportLevel,
		stdout: getSupportLevel({isTTY: tty.isatty(1)}),
		stderr: getSupportLevel({isTTY: tty.isatty(2)})
	};
	return supportsColor_1;
}

/**
 * Module dependencies.
 */

var hasRequiredNode;

function requireNode () {
	if (hasRequiredNode) return node.exports;
	hasRequiredNode = 1;
	(function (module, exports) {
		const tty = require$$1__default["default"];
		const util = require$$1__default$1["default"];

		/**
		 * This is the Node.js implementation of `debug()`.
		 */

		exports.init = init;
		exports.log = log;
		exports.formatArgs = formatArgs;
		exports.save = save;
		exports.load = load;
		exports.useColors = useColors;
		exports.destroy = util.deprecate(
			() => {},
			'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'
		);

		/**
		 * Colors.
		 */

		exports.colors = [6, 2, 3, 4, 5, 1];

		try {
			// Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json)
			// eslint-disable-next-line import/no-extraneous-dependencies
			const supportsColor = requireSupportsColor();

			if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
				exports.colors = [
					20,
					21,
					26,
					27,
					32,
					33,
					38,
					39,
					40,
					41,
					42,
					43,
					44,
					45,
					56,
					57,
					62,
					63,
					68,
					69,
					74,
					75,
					76,
					77,
					78,
					79,
					80,
					81,
					92,
					93,
					98,
					99,
					112,
					113,
					128,
					129,
					134,
					135,
					148,
					149,
					160,
					161,
					162,
					163,
					164,
					165,
					166,
					167,
					168,
					169,
					170,
					171,
					172,
					173,
					178,
					179,
					184,
					185,
					196,
					197,
					198,
					199,
					200,
					201,
					202,
					203,
					204,
					205,
					206,
					207,
					208,
					209,
					214,
					215,
					220,
					221
				];
			}
		} catch (error) {
			// Swallow - we only care if `supports-color` is available; it doesn't have to be.
		}

		/**
		 * Build up the default `inspectOpts` object from the environment variables.
		 *
		 *   $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js
		 */

		exports.inspectOpts = Object.keys(process.env).filter(key => {
			return /^debug_/i.test(key);
		}).reduce((obj, key) => {
			// Camel-case
			const prop = key
				.substring(6)
				.toLowerCase()
				.replace(/_([a-z])/g, (_, k) => {
					return k.toUpperCase();
				});

			// Coerce string value into JS value
			let val = process.env[key];
			if (/^(yes|on|true|enabled)$/i.test(val)) {
				val = true;
			} else if (/^(no|off|false|disabled)$/i.test(val)) {
				val = false;
			} else if (val === 'null') {
				val = null;
			} else {
				val = Number(val);
			}

			obj[prop] = val;
			return obj;
		}, {});

		/**
		 * Is stdout a TTY? Colored output is enabled when `true`.
		 */

		function useColors() {
			return 'colors' in exports.inspectOpts ?
				Boolean(exports.inspectOpts.colors) :
				tty.isatty(process.stderr.fd);
		}

		/**
		 * Adds ANSI color escape codes if enabled.
		 *
		 * @api public
		 */

		function formatArgs(args) {
			const {namespace: name, useColors} = this;

			if (useColors) {
				const c = this.color;
				const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c);
				const prefix = `  ${colorCode};1m${name} \u001B[0m`;

				args[0] = prefix + args[0].split('\n').join('\n' + prefix);
				args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m');
			} else {
				args[0] = getDate() + name + ' ' + args[0];
			}
		}

		function getDate() {
			if (exports.inspectOpts.hideDate) {
				return '';
			}
			return new Date().toISOString() + ' ';
		}

		/**
		 * Invokes `util.format()` with the specified arguments and writes to stderr.
		 */

		function log(...args) {
			return process.stderr.write(util.format(...args) + '\n');
		}

		/**
		 * Save `namespaces`.
		 *
		 * @param {String} namespaces
		 * @api private
		 */
		function save(namespaces) {
			if (namespaces) {
				process.env.DEBUG = namespaces;
			} else {
				// If you set a process.env field to null or undefined, it gets cast to the
				// string 'null' or 'undefined'. Just delete instead.
				delete process.env.DEBUG;
			}
		}

		/**
		 * Load `namespaces`.
		 *
		 * @return {String} returns the previously persisted debug modes
		 * @api private
		 */

		function load() {
			return process.env.DEBUG;
		}

		/**
		 * Init logic for `debug` instances.
		 *
		 * Create a new `inspectOpts` object in case `useColors` is set
		 * differently for a particular `debug` instance.
		 */

		function init(debug) {
			debug.inspectOpts = {};

			const keys = Object.keys(exports.inspectOpts);
			for (let i = 0; i < keys.length; i++) {
				debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
			}
		}

		module.exports = requireCommon()(exports);

		const {formatters} = module.exports;

		/**
		 * Map %o to `util.inspect()`, all on a single line.
		 */

		formatters.o = function (v) {
			this.inspectOpts.colors = this.useColors;
			return util.inspect(v, this.inspectOpts)
				.split('\n')
				.map(str => str.trim())
				.join(' ');
		};

		/**
		 * Map %O to `util.inspect()`, allowing multiple lines if needed.
		 */

		formatters.O = function (v) {
			this.inspectOpts.colors = this.useColors;
			return util.inspect(v, this.inspectOpts);
		};
} (node, node.exports));
	return node.exports;
}

/**
 * Detect Electron renderer / nwjs process, which is node, but we should
 * treat as a browser.
 */

var hasRequiredSrc;

function requireSrc () {
	if (hasRequiredSrc) return src.exports;
	hasRequiredSrc = 1;
	(function (module) {
		if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) {
			module.exports = requireBrowser();
		} else {
			module.exports = requireNode();
		}
} (src));
	return src.exports;
}

var debug_1;
var hasRequiredDebug;

function requireDebug () {
	if (hasRequiredDebug) return debug_1;
	hasRequiredDebug = 1;
	var debug;

	debug_1 = function () {
	  if (!debug) {
	    try {
	      /* eslint global-require: off */
	      debug = requireSrc()("follow-redirects");
	    }
	    catch (error) { /* */ }
	    if (typeof debug !== "function") {
	      debug = function () { /* */ };
	    }
	  }
	  debug.apply(null, arguments);
	};
	return debug_1;
}

var hasRequiredFollowRedirects;

function requireFollowRedirects () {
	if (hasRequiredFollowRedirects) return followRedirects.exports;
	hasRequiredFollowRedirects = 1;
	var url = require$$0__default$1["default"];
	var URL = url.URL;
	var http = require$$1__default$2["default"];
	var https = require$$2__default["default"];
	var Writable = require$$3__default["default"].Writable;
	var assert = require$$4__default["default"];
	var debug = requireDebug();

	// Create handlers that pass events from native requests
	var events = ["abort", "aborted", "connect", "error", "socket", "timeout"];
	var eventHandlers = Object.create(null);
	events.forEach(function (event) {
	  eventHandlers[event] = function (arg1, arg2, arg3) {
	    this._redirectable.emit(event, arg1, arg2, arg3);
	  };
	});

	var InvalidUrlError = createErrorType(
	  "ERR_INVALID_URL",
	  "Invalid URL",
	  TypeError
	);
	// Error types with codes
	var RedirectionError = createErrorType(
	  "ERR_FR_REDIRECTION_FAILURE",
	  "Redirected request failed"
	);
	var TooManyRedirectsError = createErrorType(
	  "ERR_FR_TOO_MANY_REDIRECTS",
	  "Maximum number of redirects exceeded"
	);
	var MaxBodyLengthExceededError = createErrorType(
	  "ERR_FR_MAX_BODY_LENGTH_EXCEEDED",
	  "Request body larger than maxBodyLength limit"
	);
	var WriteAfterEndError = createErrorType(
	  "ERR_STREAM_WRITE_AFTER_END",
	  "write after end"
	);

	// An HTTP(S) request that can be redirected
	function RedirectableRequest(options, responseCallback) {
	  // Initialize the request
	  Writable.call(this);
	  this._sanitizeOptions(options);
	  this._options = options;
	  this._ended = false;
	  this._ending = false;
	  this._redirectCount = 0;
	  this._redirects = [];
	  this._requestBodyLength = 0;
	  this._requestBodyBuffers = [];

	  // Attach a callback if passed
	  if (responseCallback) {
	    this.on("response", responseCallback);
	  }

	  // React to responses of native requests
	  var self = this;
	  this._onNativeResponse = function (response) {
	    self._processResponse(response);
	  };

	  // Perform the first request
	  this._performRequest();
	}
	RedirectableRequest.prototype = Object.create(Writable.prototype);

	RedirectableRequest.prototype.abort = function () {
	  abortRequest(this._currentRequest);
	  this.emit("abort");
	};

	// Writes buffered data to the current native request
	RedirectableRequest.prototype.write = function (data, encoding, callback) {
	  // Writing is not allowed if end has been called
	  if (this._ending) {
	    throw new WriteAfterEndError();
	  }

	  // Validate input and shift parameters if necessary
	  if (!isString(data) && !isBuffer(data)) {
	    throw new TypeError("data should be a string, Buffer or Uint8Array");
	  }
	  if (isFunction(encoding)) {
	    callback = encoding;
	    encoding = null;
	  }

	  // Ignore empty buffers, since writing them doesn't invoke the callback
	  // https://github.com/nodejs/node/issues/22066
	  if (data.length === 0) {
	    if (callback) {
	      callback();
	    }
	    return;
	  }
	  // Only write when we don't exceed the maximum body length
	  if (this._requestBodyLength + data.length <= this._options.maxBodyLength) {
	    this._requestBodyLength += data.length;
	    this._requestBodyBuffers.push({ data: data, encoding: encoding });
	    this._currentRequest.write(data, encoding, callback);
	  }
	  // Error when we exceed the maximum body length
	  else {
	    this.emit("error", new MaxBodyLengthExceededError());
	    this.abort();
	  }
	};

	// Ends the current native request
	RedirectableRequest.prototype.end = function (data, encoding, callback) {
	  // Shift parameters if necessary
	  if (isFunction(data)) {
	    callback = data;
	    data = encoding = null;
	  }
	  else if (isFunction(encoding)) {
	    callback = encoding;
	    encoding = null;
	  }

	  // Write data if needed and end
	  if (!data) {
	    this._ended = this._ending = true;
	    this._currentRequest.end(null, null, callback);
	  }
	  else {
	    var self = this;
	    var currentRequest = this._currentRequest;
	    this.write(data, encoding, function () {
	      self._ended = true;
	      currentRequest.end(null, null, callback);
	    });
	    this._ending = true;
	  }
	};

	// Sets a header value on the current native request
	RedirectableRequest.prototype.setHeader = function (name, value) {
	  this._options.headers[name] = value;
	  this._currentRequest.setHeader(name, value);
	};

	// Clears a header value on the current native request
	RedirectableRequest.prototype.removeHeader = function (name) {
	  delete this._options.headers[name];
	  this._currentRequest.removeHeader(name);
	};

	// Global timeout for all underlying requests
	RedirectableRequest.prototype.setTimeout = function (msecs, callback) {
	  var self = this;

	  // Destroys the socket on timeout
	  function destroyOnTimeout(socket) {
	    socket.setTimeout(msecs);
	    socket.removeListener("timeout", socket.destroy);
	    socket.addListener("timeout", socket.destroy);
	  }

	  // Sets up a timer to trigger a timeout event
	  function startTimer(socket) {
	    if (self._timeout) {
	      clearTimeout(self._timeout);
	    }
	    self._timeout = setTimeout(function () {
	      self.emit("timeout");
	      clearTimer();
	    }, msecs);
	    destroyOnTimeout(socket);
	  }

	  // Stops a timeout from triggering
	  function clearTimer() {
	    // Clear the timeout
	    if (self._timeout) {
	      clearTimeout(self._timeout);
	      self._timeout = null;
	    }

	    // Clean up all attached listeners
	    self.removeListener("abort", clearTimer);
	    self.removeListener("error", clearTimer);
	    self.removeListener("response", clearTimer);
	    if (callback) {
	      self.removeListener("timeout", callback);
	    }
	    if (!self.socket) {
	      self._currentRequest.removeListener("socket", startTimer);
	    }
	  }

	  // Attach callback if passed
	  if (callback) {
	    this.on("timeout", callback);
	  }

	  // Start the timer if or when the socket is opened
	  if (this.socket) {
	    startTimer(this.socket);
	  }
	  else {
	    this._currentRequest.once("socket", startTimer);
	  }

	  // Clean up on events
	  this.on("socket", destroyOnTimeout);
	  this.on("abort", clearTimer);
	  this.on("error", clearTimer);
	  this.on("response", clearTimer);

	  return this;
	};

	// Proxy all other public ClientRequest methods
	[
	  "flushHeaders", "getHeader",
	  "setNoDelay", "setSocketKeepAlive",
	].forEach(function (method) {
	  RedirectableRequest.prototype[method] = function (a, b) {
	    return this._currentRequest[method](a, b);
	  };
	});

	// Proxy all public ClientRequest properties
	["aborted", "connection", "socket"].forEach(function (property) {
	  Object.defineProperty(RedirectableRequest.prototype, property, {
	    get: function () { return this._currentRequest[property]; },
	  });
	});

	RedirectableRequest.prototype._sanitizeOptions = function (options) {
	  // Ensure headers are always present
	  if (!options.headers) {
	    options.headers = {};
	  }

	  // Since http.request treats host as an alias of hostname,
	  // but the url module interprets host as hostname plus port,
	  // eliminate the host property to avoid confusion.
	  if (options.host) {
	    // Use hostname if set, because it has precedence
	    if (!options.hostname) {
	      options.hostname = options.host;
	    }
	    delete options.host;
	  }

	  // Complete the URL object when necessary
	  if (!options.pathname && options.path) {
	    var searchPos = options.path.indexOf("?");
	    if (searchPos < 0) {
	      options.pathname = options.path;
	    }
	    else {
	      options.pathname = options.path.substring(0, searchPos);
	      options.search = options.path.substring(searchPos);
	    }
	  }
	};


	// Executes the next native request (initial or redirect)
	RedirectableRequest.prototype._performRequest = function () {
	  // Load the native protocol
	  var protocol = this._options.protocol;
	  var nativeProtocol = this._options.nativeProtocols[protocol];
	  if (!nativeProtocol) {
	    this.emit("error", new TypeError("Unsupported protocol " + protocol));
	    return;
	  }

	  // If specified, use the agent corresponding to the protocol
	  // (HTTP and HTTPS use different types of agents)
	  if (this._options.agents) {
	    var scheme = protocol.slice(0, -1);
	    this._options.agent = this._options.agents[scheme];
	  }

	  // Create the native request and set up its event handlers
	  var request = this._currentRequest =
	        nativeProtocol.request(this._options, this._onNativeResponse);
	  request._redirectable = this;
	  for (var event of events) {
	    request.on(event, eventHandlers[event]);
	  }

	  // RFC7230§5.3.1: When making a request directly to an origin server, […]
	  // a client MUST send only the absolute path […] as the request-target.
	  this._currentUrl = /^\//.test(this._options.path) ?
	    url.format(this._options) :
	    // When making a request to a proxy, […]
	    // a client MUST send the target URI in absolute-form […].
	    this._options.path;

	  // End a redirected request
	  // (The first request must be ended explicitly with RedirectableRequest#end)
	  if (this._isRedirect) {
	    // Write the request entity and end
	    var i = 0;
	    var self = this;
	    var buffers = this._requestBodyBuffers;
	    (function writeNext(error) {
	      // Only write if this request has not been redirected yet
	      /* istanbul ignore else */
	      if (request === self._currentRequest) {
	        // Report any write errors
	        /* istanbul ignore if */
	        if (error) {
	          self.emit("error", error);
	        }
	        // Write the next buffer if there are still left
	        else if (i < buffers.length) {
	          var buffer = buffers[i++];
	          /* istanbul ignore else */
	          if (!request.finished) {
	            request.write(buffer.data, buffer.encoding, writeNext);
	          }
	        }
	        // End the request if `end` has been called on us
	        else if (self._ended) {
	          request.end();
	        }
	      }
	    }());
	  }
	};

	// Processes a response from the current native request
	RedirectableRequest.prototype._processResponse = function (response) {
	  // Store the redirected response
	  var statusCode = response.statusCode;
	  if (this._options.trackRedirects) {
	    this._redirects.push({
	      url: this._currentUrl,
	      headers: response.headers,
	      statusCode: statusCode,
	    });
	  }

	  // RFC7231§6.4: The 3xx (Redirection) class of status code indicates
	  // that further action needs to be taken by the user agent in order to
	  // fulfill the request. If a Location header field is provided,
	  // the user agent MAY automatically redirect its request to the URI
	  // referenced by the Location field value,
	  // even if the specific status code is not understood.

	  // If the response is not a redirect; return it as-is
	  var location = response.headers.location;
	  if (!location || this._options.followRedirects === false ||
	      statusCode < 300 || statusCode >= 400) {
	    response.responseUrl = this._currentUrl;
	    response.redirects = this._redirects;
	    this.emit("response", response);

	    // Clean up
	    this._requestBodyBuffers = [];
	    return;
	  }

	  // The response is a redirect, so abort the current request
	  abortRequest(this._currentRequest);
	  // Discard the remainder of the response to avoid waiting for data
	  response.destroy();

	  // RFC7231§6.4: A client SHOULD detect and intervene
	  // in cyclical redirections (i.e., "infinite" redirection loops).
	  if (++this._redirectCount > this._options.maxRedirects) {
	    this.emit("error", new TooManyRedirectsError());
	    return;
	  }

	  // Store the request headers if applicable
	  var requestHeaders;
	  var beforeRedirect = this._options.beforeRedirect;
	  if (beforeRedirect) {
	    requestHeaders = Object.assign({
	      // The Host header was set by nativeProtocol.request
	      Host: response.req.getHeader("host"),
	    }, this._options.headers);
	  }

	  // RFC7231§6.4: Automatic redirection needs to done with
	  // care for methods not known to be safe, […]
	  // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change
	  // the request method from POST to GET for the subsequent request.
	  var method = this._options.method;
	  if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" ||
	      // RFC7231§6.4.4: The 303 (See Other) status code indicates that
	      // the server is redirecting the user agent to a different resource […]
	      // A user agent can perform a retrieval request targeting that URI
	      // (a GET or HEAD request if using HTTP) […]
	      (statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) {
	    this._options.method = "GET";
	    // Drop a possible entity and headers related to it
	    this._requestBodyBuffers = [];
	    removeMatchingHeaders(/^content-/i, this._options.headers);
	  }

	  // Drop the Host header, as the redirect might lead to a different host
	  var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers);

	  // If the redirect is relative, carry over the host of the last request
	  var currentUrlParts = url.parse(this._currentUrl);
	  var currentHost = currentHostHeader || currentUrlParts.host;
	  var currentUrl = /^\w+:/.test(location) ? this._currentUrl :
	    url.format(Object.assign(currentUrlParts, { host: currentHost }));

	  // Determine the URL of the redirection
	  var redirectUrl;
	  try {
	    redirectUrl = url.resolve(currentUrl, location);
	  }
	  catch (cause) {
	    this.emit("error", new RedirectionError({ cause: cause }));
	    return;
	  }

	  // Create the redirected request
	  debug("redirecting to", redirectUrl);
	  this._isRedirect = true;
	  var redirectUrlParts = url.parse(redirectUrl);
	  Object.assign(this._options, redirectUrlParts);

	  // Drop confidential headers when redirecting to a less secure protocol
	  // or to a different domain that is not a superdomain
	  if (redirectUrlParts.protocol !== currentUrlParts.protocol &&
	     redirectUrlParts.protocol !== "https:" ||
	     redirectUrlParts.host !== currentHost &&
	     !isSubdomain(redirectUrlParts.host, currentHost)) {
	    removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
	  }

	  // Evaluate the beforeRedirect callback
	  if (isFunction(beforeRedirect)) {
	    var responseDetails = {
	      headers: response.headers,
	      statusCode: statusCode,
	    };
	    var requestDetails = {
	      url: currentUrl,
	      method: method,
	      headers: requestHeaders,
	    };
	    try {
	      beforeRedirect(this._options, responseDetails, requestDetails);
	    }
	    catch (err) {
	      this.emit("error", err);
	      return;
	    }
	    this._sanitizeOptions(this._options);
	  }

	  // Perform the redirected request
	  try {
	    this._performRequest();
	  }
	  catch (cause) {
	    this.emit("error", new RedirectionError({ cause: cause }));
	  }
	};

	// Wraps the key/value object of protocols with redirect functionality
	function wrap(protocols) {
	  // Default settings
	  var exports = {
	    maxRedirects: 21,
	    maxBodyLength: 10 * 1024 * 1024,
	  };

	  // Wrap each protocol
	  var nativeProtocols = {};
	  Object.keys(protocols).forEach(function (scheme) {
	    var protocol = scheme + ":";
	    var nativeProtocol = nativeProtocols[protocol] = protocols[scheme];
	    var wrappedProtocol = exports[scheme] = Object.create(nativeProtocol);

	    // Executes a request, following redirects
	    function request(input, options, callback) {
	      // Parse parameters
	      if (isString(input)) {
	        var parsed;
	        try {
	          parsed = urlToOptions(new URL(input));
	        }
	        catch (err) {
	          /* istanbul ignore next */
	          parsed = url.parse(input);
	        }
	        if (!isString(parsed.protocol)) {
	          throw new InvalidUrlError({ input });
	        }
	        input = parsed;
	      }
	      else if (URL && (input instanceof URL)) {
	        input = urlToOptions(input);
	      }
	      else {
	        callback = options;
	        options = input;
	        input = { protocol: protocol };
	      }
	      if (isFunction(options)) {
	        callback = options;
	        options = null;
	      }

	      // Set defaults
	      options = Object.assign({
	        maxRedirects: exports.maxRedirects,
	        maxBodyLength: exports.maxBodyLength,
	      }, input, options);
	      options.nativeProtocols = nativeProtocols;
	      if (!isString(options.host) && !isString(options.hostname)) {
	        options.hostname = "::1";
	      }

	      assert.equal(options.protocol, protocol, "protocol mismatch");
	      debug("options", options);
	      return new RedirectableRequest(options, callback);
	    }

	    // Executes a GET request, following redirects
	    function get(input, options, callback) {
	      var wrappedRequest = wrappedProtocol.request(input, options, callback);
	      wrappedRequest.end();
	      return wrappedRequest;
	    }

	    // Expose the properties on the wrapped protocol
	    Object.defineProperties(wrappedProtocol, {
	      request: { value: request, configurable: true, enumerable: true, writable: true },
	      get: { value: get, configurable: true, enumerable: true, writable: true },
	    });
	  });
	  return exports;
	}

	/* istanbul ignore next */
	function noop() { /* empty */ }

	// from https://github.com/nodejs/node/blob/master/lib/internal/url.js
	function urlToOptions(urlObject) {
	  var options = {
	    protocol: urlObject.protocol,
	    hostname: urlObject.hostname.startsWith("[") ?
	      /* istanbul ignore next */
	      urlObject.hostname.slice(1, -1) :
	      urlObject.hostname,
	    hash: urlObject.hash,
	    search: urlObject.search,
	    pathname: urlObject.pathname,
	    path: urlObject.pathname + urlObject.search,
	    href: urlObject.href,
	  };
	  if (urlObject.port !== "") {
	    options.port = Number(urlObject.port);
	  }
	  return options;
	}

	function removeMatchingHeaders(regex, headers) {
	  var lastValue;
	  for (var header in headers) {
	    if (regex.test(header)) {
	      lastValue = headers[header];
	      delete headers[header];
	    }
	  }
	  return (lastValue === null || typeof lastValue === "undefined") ?
	    undefined : String(lastValue).trim();
	}

	function createErrorType(code, message, baseClass) {
	  // Create constructor
	  function CustomError(properties) {
	    Error.captureStackTrace(this, this.constructor);
	    Object.assign(this, properties || {});
	    this.code = code;
	    this.message = this.cause ? message + ": " + this.cause.message : message;
	  }

	  // Attach constructor and set default properties
	  CustomError.prototype = new (baseClass || Error)();
	  CustomError.prototype.constructor = CustomError;
	  CustomError.prototype.name = "Error [" + code + "]";
	  return CustomError;
	}

	function abortRequest(request) {
	  for (var event of events) {
	    request.removeListener(event, eventHandlers[event]);
	  }
	  request.on("error", noop);
	  request.abort();
	}

	function isSubdomain(subdomain, domain) {
	  assert(isString(subdomain) && isString(domain));
	  var dot = subdomain.length - domain.length - 1;
	  return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain);
	}

	function isString(value) {
	  return typeof value === "string" || value instanceof String;
	}

	function isFunction(value) {
	  return typeof value === "function";
	}

	function isBuffer(value) {
	  return typeof value === "object" && ("length" in value);
	}

	// Exports
	followRedirects.exports = wrap({ http: http, https: https });
	followRedirects.exports.wrap = wrap;
	return followRedirects.exports;
}

var data;
var hasRequiredData;

function requireData () {
	if (hasRequiredData) return data;
	hasRequiredData = 1;
	data = {
	  "version": "0.27.2"
	};
	return data;
}

var http_1;
var hasRequiredHttp;

function requireHttp () {
	if (hasRequiredHttp) return http_1;
	hasRequiredHttp = 1;

	var utils = utils$9;
	var settle = requireSettle();
	var buildFullPath = buildFullPath$1;
	var buildURL = buildURL$1;
	var http = require$$1__default$2["default"];
	var https = require$$2__default["default"];
	var httpFollow = requireFollowRedirects().http;
	var httpsFollow = requireFollowRedirects().https;
	var url = require$$0__default$1["default"];
	var zlib = require$$8__default["default"];
	var VERSION = requireData().version;
	var transitionalDefaults = transitional;
	var AxiosError = requireAxiosError();
	var CanceledError = requireCanceledError();

	var isHttps = /https:?/;

	var supportedProtocols = [ 'http:', 'https:', 'file:' ];

	/**
	 *
	 * @param {http.ClientRequestArgs} options
	 * @param {AxiosProxyConfig} proxy
	 * @param {string} location
	 */
	function setProxy(options, proxy, location) {
	  options.hostname = proxy.host;
	  options.host = proxy.host;
	  options.port = proxy.port;
	  options.path = location;

	  // Basic proxy authorization
	  if (proxy.auth) {
	    var base64 = Buffer.from(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64');
	    options.headers['Proxy-Authorization'] = 'Basic ' + base64;
	  }

	  // If a proxy is used, any redirects must also pass through the proxy
	  options.beforeRedirect = function beforeRedirect(redirection) {
	    redirection.headers.host = redirection.host;
	    setProxy(redirection, proxy, redirection.href);
	  };
	}

	/*eslint consistent-return:0*/
	http_1 = function httpAdapter(config) {
	  return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) {
	    var onCanceled;
	    function done() {
	      if (config.cancelToken) {
	        config.cancelToken.unsubscribe(onCanceled);
	      }

	      if (config.signal) {
	        config.signal.removeEventListener('abort', onCanceled);
	      }
	    }
	    var resolve = function resolve(value) {
	      done();
	      resolvePromise(value);
	    };
	    var rejected = false;
	    var reject = function reject(value) {
	      done();
	      rejected = true;
	      rejectPromise(value);
	    };
	    var data = config.data;
	    var headers = config.headers;
	    var headerNames = {};

	    Object.keys(headers).forEach(function storeLowerName(name) {
	      headerNames[name.toLowerCase()] = name;
	    });

	    // Set User-Agent (required by some servers)
	    // See https://github.com/axios/axios/issues/69
	    if ('user-agent' in headerNames) {
	      // User-Agent is specified; handle case where no UA header is desired
	      if (!headers[headerNames['user-agent']]) {
	        delete headers[headerNames['user-agent']];
	      }
	      // Otherwise, use specified value
	    } else {
	      // Only set header if it hasn't been set in config
	      headers['User-Agent'] = 'axios/' + VERSION;
	    }

	    // support for https://www.npmjs.com/package/form-data api
	    if (utils.isFormData(data) && utils.isFunction(data.getHeaders)) {
	      Object.assign(headers, data.getHeaders());
	    } else if (data && !utils.isStream(data)) {
	      if (Buffer.isBuffer(data)) ; else if (utils.isArrayBuffer(data)) {
	        data = Buffer.from(new Uint8Array(data));
	      } else if (utils.isString(data)) {
	        data = Buffer.from(data, 'utf-8');
	      } else {
	        return reject(new AxiosError(
	          'Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream',
	          AxiosError.ERR_BAD_REQUEST,
	          config
	        ));
	      }

	      if (config.maxBodyLength > -1 && data.length > config.maxBodyLength) {
	        return reject(new AxiosError(
	          'Request body larger than maxBodyLength limit',
	          AxiosError.ERR_BAD_REQUEST,
	          config
	        ));
	      }

	      // Add Content-Length header if data exists
	      if (!headerNames['content-length']) {
	        headers['Content-Length'] = data.length;
	      }
	    }

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

	    // Parse url
	    var fullPath = buildFullPath(config.baseURL, config.url);
	    var parsed = url.parse(fullPath);
	    var protocol = parsed.protocol || supportedProtocols[0];

	    if (supportedProtocols.indexOf(protocol) === -1) {
	      return reject(new AxiosError(
	        'Unsupported protocol ' + protocol,
	        AxiosError.ERR_BAD_REQUEST,
	        config
	      ));
	    }

	    if (!auth && parsed.auth) {
	      var urlAuth = parsed.auth.split(':');
	      var urlUsername = urlAuth[0] || '';
	      var urlPassword = urlAuth[1] || '';
	      auth = urlUsername + ':' + urlPassword;
	    }

	    if (auth && headerNames.authorization) {
	      delete headers[headerNames.authorization];
	    }

	    var isHttpsRequest = isHttps.test(protocol);
	    var agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;

	    try {
	      buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, '');
	    } catch (err) {
	      var customErr = new Error(err.message);
	      customErr.config = config;
	      customErr.url = config.url;
	      customErr.exists = true;
	      reject(customErr);
	    }

	    var options = {
	      path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''),
	      method: config.method.toUpperCase(),
	      headers: headers,
	      agent: agent,
	      agents: { http: config.httpAgent, https: config.httpsAgent },
	      auth: auth
	    };

	    if (config.socketPath) {
	      options.socketPath = config.socketPath;
	    } else {
	      options.hostname = parsed.hostname;
	      options.port = parsed.port;
	    }

	    var proxy = config.proxy;
	    if (!proxy && proxy !== false) {
	      var proxyEnv = protocol.slice(0, -1) + '_proxy';
	      var proxyUrl = process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()];
	      if (proxyUrl) {
	        var parsedProxyUrl = url.parse(proxyUrl);
	        var noProxyEnv = process.env.no_proxy || process.env.NO_PROXY;
	        var shouldProxy = true;

	        if (noProxyEnv) {
	          var noProxy = noProxyEnv.split(',').map(function trim(s) {
	            return s.trim();
	          });

	          shouldProxy = !noProxy.some(function proxyMatch(proxyElement) {
	            if (!proxyElement) {
	              return false;
	            }
	            if (proxyElement === '*') {
	              return true;
	            }
	            if (proxyElement[0] === '.' &&
	                parsed.hostname.substr(parsed.hostname.length - proxyElement.length) === proxyElement) {
	              return true;
	            }

	            return parsed.hostname === proxyElement;
	          });
	        }

	        if (shouldProxy) {
	          proxy = {
	            host: parsedProxyUrl.hostname,
	            port: parsedProxyUrl.port,
	            protocol: parsedProxyUrl.protocol
	          };

	          if (parsedProxyUrl.auth) {
	            var proxyUrlAuth = parsedProxyUrl.auth.split(':');
	            proxy.auth = {
	              username: proxyUrlAuth[0],
	              password: proxyUrlAuth[1]
	            };
	          }
	        }
	      }
	    }

	    if (proxy) {
	      options.headers.host = parsed.hostname + (parsed.port ? ':' + parsed.port : '');
	      setProxy(options, proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path);
	    }

	    var transport;
	    var isHttpsProxy = isHttpsRequest && (proxy ? isHttps.test(proxy.protocol) : true);
	    if (config.transport) {
	      transport = config.transport;
	    } else if (config.maxRedirects === 0) {
	      transport = isHttpsProxy ? https : http;
	    } else {
	      if (config.maxRedirects) {
	        options.maxRedirects = config.maxRedirects;
	      }
	      if (config.beforeRedirect) {
	        options.beforeRedirect = config.beforeRedirect;
	      }
	      transport = isHttpsProxy ? httpsFollow : httpFollow;
	    }

	    if (config.maxBodyLength > -1) {
	      options.maxBodyLength = config.maxBodyLength;
	    }

	    if (config.insecureHTTPParser) {
	      options.insecureHTTPParser = config.insecureHTTPParser;
	    }

	    // Create the request
	    var req = transport.request(options, function handleResponse(res) {
	      if (req.aborted) return;

	      // uncompress the response body transparently if required
	      var stream = res;

	      // return the last request in case of redirects
	      var lastRequest = res.req || req;


	      // if no content, is HEAD request or decompress disabled we should not decompress
	      if (res.statusCode !== 204 && lastRequest.method !== 'HEAD' && config.decompress !== false) {
	        switch (res.headers['content-encoding']) {
	        /*eslint default-case:0*/
	        case 'gzip':
	        case 'compress':
	        case 'deflate':
	        // add the unzipper to the body stream processing pipeline
	          stream = stream.pipe(zlib.createUnzip());

	          // remove the content-encoding in order to not confuse downstream operations
	          delete res.headers['content-encoding'];
	          break;
	        }
	      }

	      var response = {
	        status: res.statusCode,
	        statusText: res.statusMessage,
	        headers: res.headers,
	        config: config,
	        request: lastRequest
	      };

	      if (config.responseType === 'stream') {
	        response.data = stream;
	        settle(resolve, reject, response);
	      } else {
	        var responseBuffer = [];
	        var totalResponseBytes = 0;
	        stream.on('data', function handleStreamData(chunk) {
	          responseBuffer.push(chunk);
	          totalResponseBytes += chunk.length;

	          // make sure the content length is not over the maxContentLength if specified
	          if (config.maxContentLength > -1 && totalResponseBytes > config.maxContentLength) {
	            // stream.destoy() emit aborted event before calling reject() on Node.js v16
	            rejected = true;
	            stream.destroy();
	            reject(new AxiosError('maxContentLength size of ' + config.maxContentLength + ' exceeded',
	              AxiosError.ERR_BAD_RESPONSE, config, lastRequest));
	          }
	        });

	        stream.on('aborted', function handlerStreamAborted() {
	          if (rejected) {
	            return;
	          }
	          stream.destroy();
	          reject(new AxiosError(
	            'maxContentLength size of ' + config.maxContentLength + ' exceeded',
	            AxiosError.ERR_BAD_RESPONSE,
	            config,
	            lastRequest
	          ));
	        });

	        stream.on('error', function handleStreamError(err) {
	          if (req.aborted) return;
	          reject(AxiosError.from(err, null, config, lastRequest));
	        });

	        stream.on('end', function handleStreamEnd() {
	          try {
	            var responseData = responseBuffer.length === 1 ? responseBuffer[0] : Buffer.concat(responseBuffer);
	            if (config.responseType !== 'arraybuffer') {
	              responseData = responseData.toString(config.responseEncoding);
	              if (!config.responseEncoding || config.responseEncoding === 'utf8') {
	                responseData = utils.stripBOM(responseData);
	              }
	            }
	            response.data = responseData;
	          } catch (err) {
	            reject(AxiosError.from(err, null, config, response.request, response));
	          }
	          settle(resolve, reject, response);
	        });
	      }
	    });

	    // Handle errors
	    req.on('error', function handleRequestError(err) {
	      // @todo remove
	      // if (req.aborted && err.code !== AxiosError.ERR_FR_TOO_MANY_REDIRECTS) return;
	      reject(AxiosError.from(err, null, config, req));
	    });

	    // set tcp keep alive to prevent drop connection by peer
	    req.on('socket', function handleRequestSocket(socket) {
	      // default interval of sending ack packet is 1 minute
	      socket.setKeepAlive(true, 1000 * 60);
	    });

	    // Handle request timeout
	    if (config.timeout) {
	      // This is forcing a int timeout to avoid problems if the `req` interface doesn't handle other types.
	      var timeout = parseInt(config.timeout, 10);

	      if (isNaN(timeout)) {
	        reject(new AxiosError(
	          'error trying to parse `config.timeout` to int',
	          AxiosError.ERR_BAD_OPTION_VALUE,
	          config,
	          req
	        ));

	        return;
	      }

	      // Sometime, the response will be very slow, and does not respond, the connect event will be block by event loop system.
	      // And timer callback will be fired, and abort() will be invoked before connection, then get "socket hang up" and code ECONNRESET.
	      // At this time, if we have a large number of request, nodejs will hang up some socket on background. and the number will up and up.
	      // And then these socket which be hang up will devoring CPU little by little.
	      // ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect.
	      req.setTimeout(timeout, function handleRequestTimeout() {
	        req.abort();
	        var transitional = config.transitional || transitionalDefaults;
	        reject(new AxiosError(
	          'timeout of ' + timeout + 'ms exceeded',
	          transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
	          config,
	          req
	        ));
	      });
	    }

	    if (config.cancelToken || config.signal) {
	      // Handle cancellation
	      // eslint-disable-next-line func-names
	      onCanceled = function(cancel) {
	        if (req.aborted) return;

	        req.abort();
	        reject(!cancel || (cancel && cancel.type) ? new CanceledError() : cancel);
	      };

	      config.cancelToken && config.cancelToken.subscribe(onCanceled);
	      if (config.signal) {
	        config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
	      }
	    }


	    // Send the request
	    if (utils.isStream(data)) {
	      data.on('error', function handleStreamError(err) {
	        reject(AxiosError.from(err, config, null, req));
	      }).pipe(req);
	    } else {
	      req.end(data);
	    }
	  });
	};
	return http_1;
}

var FormData$1 = {exports: {}};

var delayed_stream;
var hasRequiredDelayed_stream;

function requireDelayed_stream () {
	if (hasRequiredDelayed_stream) return delayed_stream;
	hasRequiredDelayed_stream = 1;
	var Stream = require$$3__default["default"].Stream;
	var util = require$$1__default$1["default"];

	delayed_stream = DelayedStream;
	function DelayedStream() {
	  this.source = null;
	  this.dataSize = 0;
	  this.maxDataSize = 1024 * 1024;
	  this.pauseStream = true;

	  this._maxDataSizeExceeded = false;
	  this._released = false;
	  this._bufferedEvents = [];
	}
	util.inherits(DelayedStream, Stream);

	DelayedStream.create = function(source, options) {
	  var delayedStream = new this();

	  options = options || {};
	  for (var option in options) {
	    delayedStream[option] = options[option];
	  }

	  delayedStream.source = source;

	  var realEmit = source.emit;
	  source.emit = function() {
	    delayedStream._handleEmit(arguments);
	    return realEmit.apply(source, arguments);
	  };

	  source.on('error', function() {});
	  if (delayedStream.pauseStream) {
	    source.pause();
	  }

	  return delayedStream;
	};

	Object.defineProperty(DelayedStream.prototype, 'readable', {
	  configurable: true,
	  enumerable: true,
	  get: function() {
	    return this.source.readable;
	  }
	});

	DelayedStream.prototype.setEncoding = function() {
	  return this.source.setEncoding.apply(this.source, arguments);
	};

	DelayedStream.prototype.resume = function() {
	  if (!this._released) {
	    this.release();
	  }

	  this.source.resume();
	};

	DelayedStream.prototype.pause = function() {
	  this.source.pause();
	};

	DelayedStream.prototype.release = function() {
	  this._released = true;

	  this._bufferedEvents.forEach(function(args) {
	    this.emit.apply(this, args);
	  }.bind(this));
	  this._bufferedEvents = [];
	};

	DelayedStream.prototype.pipe = function() {
	  var r = Stream.prototype.pipe.apply(this, arguments);
	  this.resume();
	  return r;
	};

	DelayedStream.prototype._handleEmit = function(args) {
	  if (this._released) {
	    this.emit.apply(this, args);
	    return;
	  }

	  if (args[0] === 'data') {
	    this.dataSize += args[1].length;
	    this._checkIfMaxDataSizeExceeded();
	  }

	  this._bufferedEvents.push(args);
	};

	DelayedStream.prototype._checkIfMaxDataSizeExceeded = function() {
	  if (this._maxDataSizeExceeded) {
	    return;
	  }

	  if (this.dataSize <= this.maxDataSize) {
	    return;
	  }

	  this._maxDataSizeExceeded = true;
	  var message =
	    'DelayedStream#maxDataSize of ' + this.maxDataSize + ' bytes exceeded.';
	  this.emit('error', new Error(message));
	};
	return delayed_stream;
}

var combined_stream;
var hasRequiredCombined_stream;

function requireCombined_stream () {
	if (hasRequiredCombined_stream) return combined_stream;
	hasRequiredCombined_stream = 1;
	var util = require$$1__default$1["default"];
	var Stream = require$$3__default["default"].Stream;
	var DelayedStream = requireDelayed_stream();

	combined_stream = CombinedStream;
	function CombinedStream() {
	  this.writable = false;
	  this.readable = true;
	  this.dataSize = 0;
	  this.maxDataSize = 2 * 1024 * 1024;
	  this.pauseStreams = true;

	  this._released = false;
	  this._streams = [];
	  this._currentStream = null;
	  this._insideLoop = false;
	  this._pendingNext = false;
	}
	util.inherits(CombinedStream, Stream);

	CombinedStream.create = function(options) {
	  var combinedStream = new this();

	  options = options || {};
	  for (var option in options) {
	    combinedStream[option] = options[option];
	  }

	  return combinedStream;
	};

	CombinedStream.isStreamLike = function(stream) {
	  return (typeof stream !== 'function')
	    && (typeof stream !== 'string')
	    && (typeof stream !== 'boolean')
	    && (typeof stream !== 'number')
	    && (!Buffer.isBuffer(stream));
	};

	CombinedStream.prototype.append = function(stream) {
	  var isStreamLike = CombinedStream.isStreamLike(stream);

	  if (isStreamLike) {
	    if (!(stream instanceof DelayedStream)) {
	      var newStream = DelayedStream.create(stream, {
	        maxDataSize: Infinity,
	        pauseStream: this.pauseStreams,
	      });
	      stream.on('data', this._checkDataSize.bind(this));
	      stream = newStream;
	    }

	    this._handleErrors(stream);

	    if (this.pauseStreams) {
	      stream.pause();
	    }
	  }

	  this._streams.push(stream);
	  return this;
	};

	CombinedStream.prototype.pipe = function(dest, options) {
	  Stream.prototype.pipe.call(this, dest, options);
	  this.resume();
	  return dest;
	};

	CombinedStream.prototype._getNext = function() {
	  this._currentStream = null;

	  if (this._insideLoop) {
	    this._pendingNext = true;
	    return; // defer call
	  }

	  this._insideLoop = true;
	  try {
	    do {
	      this._pendingNext = false;
	      this._realGetNext();
	    } while (this._pendingNext);
	  } finally {
	    this._insideLoop = false;
	  }
	};

	CombinedStream.prototype._realGetNext = function() {
	  var stream = this._streams.shift();


	  if (typeof stream == 'undefined') {
	    this.end();
	    return;
	  }

	  if (typeof stream !== 'function') {
	    this._pipeNext(stream);
	    return;
	  }

	  var getStream = stream;
	  getStream(function(stream) {
	    var isStreamLike = CombinedStream.isStreamLike(stream);
	    if (isStreamLike) {
	      stream.on('data', this._checkDataSize.bind(this));
	      this._handleErrors(stream);
	    }

	    this._pipeNext(stream);
	  }.bind(this));
	};

	CombinedStream.prototype._pipeNext = function(stream) {
	  this._currentStream = stream;

	  var isStreamLike = CombinedStream.isStreamLike(stream);
	  if (isStreamLike) {
	    stream.on('end', this._getNext.bind(this));
	    stream.pipe(this, {end: false});
	    return;
	  }

	  var value = stream;
	  this.write(value);
	  this._getNext();
	};

	CombinedStream.prototype._handleErrors = function(stream) {
	  var self = this;
	  stream.on('error', function(err) {
	    self._emitError(err);
	  });
	};

	CombinedStream.prototype.write = function(data) {
	  this.emit('data', data);
	};

	CombinedStream.prototype.pause = function() {
	  if (!this.pauseStreams) {
	    return;
	  }

	  if(this.pauseStreams && this._currentStream && typeof(this._currentStream.pause) == 'function') this._currentStream.pause();
	  this.emit('pause');
	};

	CombinedStream.prototype.resume = function() {
	  if (!this._released) {
	    this._released = true;
	    this.writable = true;
	    this._getNext();
	  }

	  if(this.pauseStreams && this._currentStream && typeof(this._currentStream.resume) == 'function') this._currentStream.resume();
	  this.emit('resume');
	};

	CombinedStream.prototype.end = function() {
	  this._reset();
	  this.emit('end');
	};

	CombinedStream.prototype.destroy = function() {
	  this._reset();
	  this.emit('close');
	};

	CombinedStream.prototype._reset = function() {
	  this.writable = false;
	  this._streams = [];
	  this._currentStream = null;
	};

	CombinedStream.prototype._checkDataSize = function() {
	  this._updateDataSize();
	  if (this.dataSize <= this.maxDataSize) {
	    return;
	  }

	  var message =
	    'DelayedStream#maxDataSize of ' + this.maxDataSize + ' bytes exceeded.';
	  this._emitError(new Error(message));
	};

	CombinedStream.prototype._updateDataSize = function() {
	  this.dataSize = 0;

	  var self = this;
	  this._streams.forEach(function(stream) {
	    if (!stream.dataSize) {
	      return;
	    }

	    self.dataSize += stream.dataSize;
	  });

	  if (this._currentStream && this._currentStream.dataSize) {
	    this.dataSize += this._currentStream.dataSize;
	  }
	};

	CombinedStream.prototype._emitError = function(err) {
	  this._reset();
	  this.emit('error', err);
	};
	return combined_stream;
}

var mimeTypes = {};

var mimeDb = {exports: {}};

var require$$0 = {
	"application/1d-interleaved-parityfec": {
	source: "iana"
},
	"application/3gpdash-qoe-report+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/3gpp-ims+xml": {
	source: "iana",
	compressible: true
},
	"application/3gpphal+json": {
	source: "iana",
	compressible: true
},
	"application/3gpphalforms+json": {
	source: "iana",
	compressible: true
},
	"application/a2l": {
	source: "iana"
},
	"application/ace+cbor": {
	source: "iana"
},
	"application/activemessage": {
	source: "iana"
},
	"application/activity+json": {
	source: "iana",
	compressible: true
},
	"application/alto-costmap+json": {
	source: "iana",
	compressible: true
},
	"application/alto-costmapfilter+json": {
	source: "iana",
	compressible: true
},
	"application/alto-directory+json": {
	source: "iana",
	compressible: true
},
	"application/alto-endpointcost+json": {
	source: "iana",
	compressible: true
},
	"application/alto-endpointcostparams+json": {
	source: "iana",
	compressible: true
},
	"application/alto-endpointprop+json": {
	source: "iana",
	compressible: true
},
	"application/alto-endpointpropparams+json": {
	source: "iana",
	compressible: true
},
	"application/alto-error+json": {
	source: "iana",
	compressible: true
},
	"application/alto-networkmap+json": {
	source: "iana",
	compressible: true
},
	"application/alto-networkmapfilter+json": {
	source: "iana",
	compressible: true
},
	"application/alto-updatestreamcontrol+json": {
	source: "iana",
	compressible: true
},
	"application/alto-updatestreamparams+json": {
	source: "iana",
	compressible: true
},
	"application/aml": {
	source: "iana"
},
	"application/andrew-inset": {
	source: "iana",
	extensions: [
		"ez"
	]
},
	"application/applefile": {
	source: "iana"
},
	"application/applixware": {
	source: "apache",
	extensions: [
		"aw"
	]
},
	"application/at+jwt": {
	source: "iana"
},
	"application/atf": {
	source: "iana"
},
	"application/atfx": {
	source: "iana"
},
	"application/atom+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"atom"
	]
},
	"application/atomcat+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"atomcat"
	]
},
	"application/atomdeleted+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"atomdeleted"
	]
},
	"application/atomicmail": {
	source: "iana"
},
	"application/atomsvc+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"atomsvc"
	]
},
	"application/atsc-dwd+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"dwd"
	]
},
	"application/atsc-dynamic-event-message": {
	source: "iana"
},
	"application/atsc-held+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"held"
	]
},
	"application/atsc-rdt+json": {
	source: "iana",
	compressible: true
},
	"application/atsc-rsat+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"rsat"
	]
},
	"application/atxml": {
	source: "iana"
},
	"application/auth-policy+xml": {
	source: "iana",
	compressible: true
},
	"application/bacnet-xdd+zip": {
	source: "iana",
	compressible: false
},
	"application/batch-smtp": {
	source: "iana"
},
	"application/bdoc": {
	compressible: false,
	extensions: [
		"bdoc"
	]
},
	"application/beep+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/calendar+json": {
	source: "iana",
	compressible: true
},
	"application/calendar+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xcs"
	]
},
	"application/call-completion": {
	source: "iana"
},
	"application/cals-1840": {
	source: "iana"
},
	"application/captive+json": {
	source: "iana",
	compressible: true
},
	"application/cbor": {
	source: "iana"
},
	"application/cbor-seq": {
	source: "iana"
},
	"application/cccex": {
	source: "iana"
},
	"application/ccmp+xml": {
	source: "iana",
	compressible: true
},
	"application/ccxml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"ccxml"
	]
},
	"application/cdfx+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"cdfx"
	]
},
	"application/cdmi-capability": {
	source: "iana",
	extensions: [
		"cdmia"
	]
},
	"application/cdmi-container": {
	source: "iana",
	extensions: [
		"cdmic"
	]
},
	"application/cdmi-domain": {
	source: "iana",
	extensions: [
		"cdmid"
	]
},
	"application/cdmi-object": {
	source: "iana",
	extensions: [
		"cdmio"
	]
},
	"application/cdmi-queue": {
	source: "iana",
	extensions: [
		"cdmiq"
	]
},
	"application/cdni": {
	source: "iana"
},
	"application/cea": {
	source: "iana"
},
	"application/cea-2018+xml": {
	source: "iana",
	compressible: true
},
	"application/cellml+xml": {
	source: "iana",
	compressible: true
},
	"application/cfw": {
	source: "iana"
},
	"application/city+json": {
	source: "iana",
	compressible: true
},
	"application/clr": {
	source: "iana"
},
	"application/clue+xml": {
	source: "iana",
	compressible: true
},
	"application/clue_info+xml": {
	source: "iana",
	compressible: true
},
	"application/cms": {
	source: "iana"
},
	"application/cnrp+xml": {
	source: "iana",
	compressible: true
},
	"application/coap-group+json": {
	source: "iana",
	compressible: true
},
	"application/coap-payload": {
	source: "iana"
},
	"application/commonground": {
	source: "iana"
},
	"application/conference-info+xml": {
	source: "iana",
	compressible: true
},
	"application/cose": {
	source: "iana"
},
	"application/cose-key": {
	source: "iana"
},
	"application/cose-key-set": {
	source: "iana"
},
	"application/cpl+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"cpl"
	]
},
	"application/csrattrs": {
	source: "iana"
},
	"application/csta+xml": {
	source: "iana",
	compressible: true
},
	"application/cstadata+xml": {
	source: "iana",
	compressible: true
},
	"application/csvm+json": {
	source: "iana",
	compressible: true
},
	"application/cu-seeme": {
	source: "apache",
	extensions: [
		"cu"
	]
},
	"application/cwt": {
	source: "iana"
},
	"application/cybercash": {
	source: "iana"
},
	"application/dart": {
	compressible: true
},
	"application/dash+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"mpd"
	]
},
	"application/dash-patch+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"mpp"
	]
},
	"application/dashdelta": {
	source: "iana"
},
	"application/davmount+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"davmount"
	]
},
	"application/dca-rft": {
	source: "iana"
},
	"application/dcd": {
	source: "iana"
},
	"application/dec-dx": {
	source: "iana"
},
	"application/dialog-info+xml": {
	source: "iana",
	compressible: true
},
	"application/dicom": {
	source: "iana"
},
	"application/dicom+json": {
	source: "iana",
	compressible: true
},
	"application/dicom+xml": {
	source: "iana",
	compressible: true
},
	"application/dii": {
	source: "iana"
},
	"application/dit": {
	source: "iana"
},
	"application/dns": {
	source: "iana"
},
	"application/dns+json": {
	source: "iana",
	compressible: true
},
	"application/dns-message": {
	source: "iana"
},
	"application/docbook+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"dbk"
	]
},
	"application/dots+cbor": {
	source: "iana"
},
	"application/dskpp+xml": {
	source: "iana",
	compressible: true
},
	"application/dssc+der": {
	source: "iana",
	extensions: [
		"dssc"
	]
},
	"application/dssc+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xdssc"
	]
},
	"application/dvcs": {
	source: "iana"
},
	"application/ecmascript": {
	source: "iana",
	compressible: true,
	extensions: [
		"es",
		"ecma"
	]
},
	"application/edi-consent": {
	source: "iana"
},
	"application/edi-x12": {
	source: "iana",
	compressible: false
},
	"application/edifact": {
	source: "iana",
	compressible: false
},
	"application/efi": {
	source: "iana"
},
	"application/elm+json": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/elm+xml": {
	source: "iana",
	compressible: true
},
	"application/emergencycalldata.cap+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/emergencycalldata.comment+xml": {
	source: "iana",
	compressible: true
},
	"application/emergencycalldata.control+xml": {
	source: "iana",
	compressible: true
},
	"application/emergencycalldata.deviceinfo+xml": {
	source: "iana",
	compressible: true
},
	"application/emergencycalldata.ecall.msd": {
	source: "iana"
},
	"application/emergencycalldata.providerinfo+xml": {
	source: "iana",
	compressible: true
},
	"application/emergencycalldata.serviceinfo+xml": {
	source: "iana",
	compressible: true
},
	"application/emergencycalldata.subscriberinfo+xml": {
	source: "iana",
	compressible: true
},
	"application/emergencycalldata.veds+xml": {
	source: "iana",
	compressible: true
},
	"application/emma+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"emma"
	]
},
	"application/emotionml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"emotionml"
	]
},
	"application/encaprtp": {
	source: "iana"
},
	"application/epp+xml": {
	source: "iana",
	compressible: true
},
	"application/epub+zip": {
	source: "iana",
	compressible: false,
	extensions: [
		"epub"
	]
},
	"application/eshop": {
	source: "iana"
},
	"application/exi": {
	source: "iana",
	extensions: [
		"exi"
	]
},
	"application/expect-ct-report+json": {
	source: "iana",
	compressible: true
},
	"application/express": {
	source: "iana",
	extensions: [
		"exp"
	]
},
	"application/fastinfoset": {
	source: "iana"
},
	"application/fastsoap": {
	source: "iana"
},
	"application/fdt+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"fdt"
	]
},
	"application/fhir+json": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/fhir+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/fido.trusted-apps+json": {
	compressible: true
},
	"application/fits": {
	source: "iana"
},
	"application/flexfec": {
	source: "iana"
},
	"application/font-sfnt": {
	source: "iana"
},
	"application/font-tdpfr": {
	source: "iana",
	extensions: [
		"pfr"
	]
},
	"application/font-woff": {
	source: "iana",
	compressible: false
},
	"application/framework-attributes+xml": {
	source: "iana",
	compressible: true
},
	"application/geo+json": {
	source: "iana",
	compressible: true,
	extensions: [
		"geojson"
	]
},
	"application/geo+json-seq": {
	source: "iana"
},
	"application/geopackage+sqlite3": {
	source: "iana"
},
	"application/geoxacml+xml": {
	source: "iana",
	compressible: true
},
	"application/gltf-buffer": {
	source: "iana"
},
	"application/gml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"gml"
	]
},
	"application/gpx+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"gpx"
	]
},
	"application/gxf": {
	source: "apache",
	extensions: [
		"gxf"
	]
},
	"application/gzip": {
	source: "iana",
	compressible: false,
	extensions: [
		"gz"
	]
},
	"application/h224": {
	source: "iana"
},
	"application/held+xml": {
	source: "iana",
	compressible: true
},
	"application/hjson": {
	extensions: [
		"hjson"
	]
},
	"application/http": {
	source: "iana"
},
	"application/hyperstudio": {
	source: "iana",
	extensions: [
		"stk"
	]
},
	"application/ibe-key-request+xml": {
	source: "iana",
	compressible: true
},
	"application/ibe-pkg-reply+xml": {
	source: "iana",
	compressible: true
},
	"application/ibe-pp-data": {
	source: "iana"
},
	"application/iges": {
	source: "iana"
},
	"application/im-iscomposing+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/index": {
	source: "iana"
},
	"application/index.cmd": {
	source: "iana"
},
	"application/index.obj": {
	source: "iana"
},
	"application/index.response": {
	source: "iana"
},
	"application/index.vnd": {
	source: "iana"
},
	"application/inkml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"ink",
		"inkml"
	]
},
	"application/iotp": {
	source: "iana"
},
	"application/ipfix": {
	source: "iana",
	extensions: [
		"ipfix"
	]
},
	"application/ipp": {
	source: "iana"
},
	"application/isup": {
	source: "iana"
},
	"application/its+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"its"
	]
},
	"application/java-archive": {
	source: "apache",
	compressible: false,
	extensions: [
		"jar",
		"war",
		"ear"
	]
},
	"application/java-serialized-object": {
	source: "apache",
	compressible: false,
	extensions: [
		"ser"
	]
},
	"application/java-vm": {
	source: "apache",
	compressible: false,
	extensions: [
		"class"
	]
},
	"application/javascript": {
	source: "iana",
	charset: "UTF-8",
	compressible: true,
	extensions: [
		"js",
		"mjs"
	]
},
	"application/jf2feed+json": {
	source: "iana",
	compressible: true
},
	"application/jose": {
	source: "iana"
},
	"application/jose+json": {
	source: "iana",
	compressible: true
},
	"application/jrd+json": {
	source: "iana",
	compressible: true
},
	"application/jscalendar+json": {
	source: "iana",
	compressible: true
},
	"application/json": {
	source: "iana",
	charset: "UTF-8",
	compressible: true,
	extensions: [
		"json",
		"map"
	]
},
	"application/json-patch+json": {
	source: "iana",
	compressible: true
},
	"application/json-seq": {
	source: "iana"
},
	"application/json5": {
	extensions: [
		"json5"
	]
},
	"application/jsonml+json": {
	source: "apache",
	compressible: true,
	extensions: [
		"jsonml"
	]
},
	"application/jwk+json": {
	source: "iana",
	compressible: true
},
	"application/jwk-set+json": {
	source: "iana",
	compressible: true
},
	"application/jwt": {
	source: "iana"
},
	"application/kpml-request+xml": {
	source: "iana",
	compressible: true
},
	"application/kpml-response+xml": {
	source: "iana",
	compressible: true
},
	"application/ld+json": {
	source: "iana",
	compressible: true,
	extensions: [
		"jsonld"
	]
},
	"application/lgr+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"lgr"
	]
},
	"application/link-format": {
	source: "iana"
},
	"application/load-control+xml": {
	source: "iana",
	compressible: true
},
	"application/lost+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"lostxml"
	]
},
	"application/lostsync+xml": {
	source: "iana",
	compressible: true
},
	"application/lpf+zip": {
	source: "iana",
	compressible: false
},
	"application/lxf": {
	source: "iana"
},
	"application/mac-binhex40": {
	source: "iana",
	extensions: [
		"hqx"
	]
},
	"application/mac-compactpro": {
	source: "apache",
	extensions: [
		"cpt"
	]
},
	"application/macwriteii": {
	source: "iana"
},
	"application/mads+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"mads"
	]
},
	"application/manifest+json": {
	source: "iana",
	charset: "UTF-8",
	compressible: true,
	extensions: [
		"webmanifest"
	]
},
	"application/marc": {
	source: "iana",
	extensions: [
		"mrc"
	]
},
	"application/marcxml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"mrcx"
	]
},
	"application/mathematica": {
	source: "iana",
	extensions: [
		"ma",
		"nb",
		"mb"
	]
},
	"application/mathml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"mathml"
	]
},
	"application/mathml-content+xml": {
	source: "iana",
	compressible: true
},
	"application/mathml-presentation+xml": {
	source: "iana",
	compressible: true
},
	"application/mbms-associated-procedure-description+xml": {
	source: "iana",
	compressible: true
},
	"application/mbms-deregister+xml": {
	source: "iana",
	compressible: true
},
	"application/mbms-envelope+xml": {
	source: "iana",
	compressible: true
},
	"application/mbms-msk+xml": {
	source: "iana",
	compressible: true
},
	"application/mbms-msk-response+xml": {
	source: "iana",
	compressible: true
},
	"application/mbms-protection-description+xml": {
	source: "iana",
	compressible: true
},
	"application/mbms-reception-report+xml": {
	source: "iana",
	compressible: true
},
	"application/mbms-register+xml": {
	source: "iana",
	compressible: true
},
	"application/mbms-register-response+xml": {
	source: "iana",
	compressible: true
},
	"application/mbms-schedule+xml": {
	source: "iana",
	compressible: true
},
	"application/mbms-user-service-description+xml": {
	source: "iana",
	compressible: true
},
	"application/mbox": {
	source: "iana",
	extensions: [
		"mbox"
	]
},
	"application/media-policy-dataset+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"mpf"
	]
},
	"application/media_control+xml": {
	source: "iana",
	compressible: true
},
	"application/mediaservercontrol+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"mscml"
	]
},
	"application/merge-patch+json": {
	source: "iana",
	compressible: true
},
	"application/metalink+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"metalink"
	]
},
	"application/metalink4+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"meta4"
	]
},
	"application/mets+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"mets"
	]
},
	"application/mf4": {
	source: "iana"
},
	"application/mikey": {
	source: "iana"
},
	"application/mipc": {
	source: "iana"
},
	"application/missing-blocks+cbor-seq": {
	source: "iana"
},
	"application/mmt-aei+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"maei"
	]
},
	"application/mmt-usd+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"musd"
	]
},
	"application/mods+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"mods"
	]
},
	"application/moss-keys": {
	source: "iana"
},
	"application/moss-signature": {
	source: "iana"
},
	"application/mosskey-data": {
	source: "iana"
},
	"application/mosskey-request": {
	source: "iana"
},
	"application/mp21": {
	source: "iana",
	extensions: [
		"m21",
		"mp21"
	]
},
	"application/mp4": {
	source: "iana",
	extensions: [
		"mp4s",
		"m4p"
	]
},
	"application/mpeg4-generic": {
	source: "iana"
},
	"application/mpeg4-iod": {
	source: "iana"
},
	"application/mpeg4-iod-xmt": {
	source: "iana"
},
	"application/mrb-consumer+xml": {
	source: "iana",
	compressible: true
},
	"application/mrb-publish+xml": {
	source: "iana",
	compressible: true
},
	"application/msc-ivr+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/msc-mixer+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/msword": {
	source: "iana",
	compressible: false,
	extensions: [
		"doc",
		"dot"
	]
},
	"application/mud+json": {
	source: "iana",
	compressible: true
},
	"application/multipart-core": {
	source: "iana"
},
	"application/mxf": {
	source: "iana",
	extensions: [
		"mxf"
	]
},
	"application/n-quads": {
	source: "iana",
	extensions: [
		"nq"
	]
},
	"application/n-triples": {
	source: "iana",
	extensions: [
		"nt"
	]
},
	"application/nasdata": {
	source: "iana"
},
	"application/news-checkgroups": {
	source: "iana",
	charset: "US-ASCII"
},
	"application/news-groupinfo": {
	source: "iana",
	charset: "US-ASCII"
},
	"application/news-transmission": {
	source: "iana"
},
	"application/nlsml+xml": {
	source: "iana",
	compressible: true
},
	"application/node": {
	source: "iana",
	extensions: [
		"cjs"
	]
},
	"application/nss": {
	source: "iana"
},
	"application/oauth-authz-req+jwt": {
	source: "iana"
},
	"application/oblivious-dns-message": {
	source: "iana"
},
	"application/ocsp-request": {
	source: "iana"
},
	"application/ocsp-response": {
	source: "iana"
},
	"application/octet-stream": {
	source: "iana",
	compressible: false,
	extensions: [
		"bin",
		"dms",
		"lrf",
		"mar",
		"so",
		"dist",
		"distz",
		"pkg",
		"bpk",
		"dump",
		"elc",
		"deploy",
		"exe",
		"dll",
		"deb",
		"dmg",
		"iso",
		"img",
		"msi",
		"msp",
		"msm",
		"buffer"
	]
},
	"application/oda": {
	source: "iana",
	extensions: [
		"oda"
	]
},
	"application/odm+xml": {
	source: "iana",
	compressible: true
},
	"application/odx": {
	source: "iana"
},
	"application/oebps-package+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"opf"
	]
},
	"application/ogg": {
	source: "iana",
	compressible: false,
	extensions: [
		"ogx"
	]
},
	"application/omdoc+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"omdoc"
	]
},
	"application/onenote": {
	source: "apache",
	extensions: [
		"onetoc",
		"onetoc2",
		"onetmp",
		"onepkg"
	]
},
	"application/opc-nodeset+xml": {
	source: "iana",
	compressible: true
},
	"application/oscore": {
	source: "iana"
},
	"application/oxps": {
	source: "iana",
	extensions: [
		"oxps"
	]
},
	"application/p21": {
	source: "iana"
},
	"application/p21+zip": {
	source: "iana",
	compressible: false
},
	"application/p2p-overlay+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"relo"
	]
},
	"application/parityfec": {
	source: "iana"
},
	"application/passport": {
	source: "iana"
},
	"application/patch-ops-error+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xer"
	]
},
	"application/pdf": {
	source: "iana",
	compressible: false,
	extensions: [
		"pdf"
	]
},
	"application/pdx": {
	source: "iana"
},
	"application/pem-certificate-chain": {
	source: "iana"
},
	"application/pgp-encrypted": {
	source: "iana",
	compressible: false,
	extensions: [
		"pgp"
	]
},
	"application/pgp-keys": {
	source: "iana",
	extensions: [
		"asc"
	]
},
	"application/pgp-signature": {
	source: "iana",
	extensions: [
		"asc",
		"sig"
	]
},
	"application/pics-rules": {
	source: "apache",
	extensions: [
		"prf"
	]
},
	"application/pidf+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/pidf-diff+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/pkcs10": {
	source: "iana",
	extensions: [
		"p10"
	]
},
	"application/pkcs12": {
	source: "iana"
},
	"application/pkcs7-mime": {
	source: "iana",
	extensions: [
		"p7m",
		"p7c"
	]
},
	"application/pkcs7-signature": {
	source: "iana",
	extensions: [
		"p7s"
	]
},
	"application/pkcs8": {
	source: "iana",
	extensions: [
		"p8"
	]
},
	"application/pkcs8-encrypted": {
	source: "iana"
},
	"application/pkix-attr-cert": {
	source: "iana",
	extensions: [
		"ac"
	]
},
	"application/pkix-cert": {
	source: "iana",
	extensions: [
		"cer"
	]
},
	"application/pkix-crl": {
	source: "iana",
	extensions: [
		"crl"
	]
},
	"application/pkix-pkipath": {
	source: "iana",
	extensions: [
		"pkipath"
	]
},
	"application/pkixcmp": {
	source: "iana",
	extensions: [
		"pki"
	]
},
	"application/pls+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"pls"
	]
},
	"application/poc-settings+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/postscript": {
	source: "iana",
	compressible: true,
	extensions: [
		"ai",
		"eps",
		"ps"
	]
},
	"application/ppsp-tracker+json": {
	source: "iana",
	compressible: true
},
	"application/problem+json": {
	source: "iana",
	compressible: true
},
	"application/problem+xml": {
	source: "iana",
	compressible: true
},
	"application/provenance+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"provx"
	]
},
	"application/prs.alvestrand.titrax-sheet": {
	source: "iana"
},
	"application/prs.cww": {
	source: "iana",
	extensions: [
		"cww"
	]
},
	"application/prs.cyn": {
	source: "iana",
	charset: "7-BIT"
},
	"application/prs.hpub+zip": {
	source: "iana",
	compressible: false
},
	"application/prs.nprend": {
	source: "iana"
},
	"application/prs.plucker": {
	source: "iana"
},
	"application/prs.rdf-xml-crypt": {
	source: "iana"
},
	"application/prs.xsf+xml": {
	source: "iana",
	compressible: true
},
	"application/pskc+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"pskcxml"
	]
},
	"application/pvd+json": {
	source: "iana",
	compressible: true
},
	"application/qsig": {
	source: "iana"
},
	"application/raml+yaml": {
	compressible: true,
	extensions: [
		"raml"
	]
},
	"application/raptorfec": {
	source: "iana"
},
	"application/rdap+json": {
	source: "iana",
	compressible: true
},
	"application/rdf+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"rdf",
		"owl"
	]
},
	"application/reginfo+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"rif"
	]
},
	"application/relax-ng-compact-syntax": {
	source: "iana",
	extensions: [
		"rnc"
	]
},
	"application/remote-printing": {
	source: "iana"
},
	"application/reputon+json": {
	source: "iana",
	compressible: true
},
	"application/resource-lists+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"rl"
	]
},
	"application/resource-lists-diff+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"rld"
	]
},
	"application/rfc+xml": {
	source: "iana",
	compressible: true
},
	"application/riscos": {
	source: "iana"
},
	"application/rlmi+xml": {
	source: "iana",
	compressible: true
},
	"application/rls-services+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"rs"
	]
},
	"application/route-apd+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"rapd"
	]
},
	"application/route-s-tsid+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"sls"
	]
},
	"application/route-usd+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"rusd"
	]
},
	"application/rpki-ghostbusters": {
	source: "iana",
	extensions: [
		"gbr"
	]
},
	"application/rpki-manifest": {
	source: "iana",
	extensions: [
		"mft"
	]
},
	"application/rpki-publication": {
	source: "iana"
},
	"application/rpki-roa": {
	source: "iana",
	extensions: [
		"roa"
	]
},
	"application/rpki-updown": {
	source: "iana"
},
	"application/rsd+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"rsd"
	]
},
	"application/rss+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"rss"
	]
},
	"application/rtf": {
	source: "iana",
	compressible: true,
	extensions: [
		"rtf"
	]
},
	"application/rtploopback": {
	source: "iana"
},
	"application/rtx": {
	source: "iana"
},
	"application/samlassertion+xml": {
	source: "iana",
	compressible: true
},
	"application/samlmetadata+xml": {
	source: "iana",
	compressible: true
},
	"application/sarif+json": {
	source: "iana",
	compressible: true
},
	"application/sarif-external-properties+json": {
	source: "iana",
	compressible: true
},
	"application/sbe": {
	source: "iana"
},
	"application/sbml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"sbml"
	]
},
	"application/scaip+xml": {
	source: "iana",
	compressible: true
},
	"application/scim+json": {
	source: "iana",
	compressible: true
},
	"application/scvp-cv-request": {
	source: "iana",
	extensions: [
		"scq"
	]
},
	"application/scvp-cv-response": {
	source: "iana",
	extensions: [
		"scs"
	]
},
	"application/scvp-vp-request": {
	source: "iana",
	extensions: [
		"spq"
	]
},
	"application/scvp-vp-response": {
	source: "iana",
	extensions: [
		"spp"
	]
},
	"application/sdp": {
	source: "iana",
	extensions: [
		"sdp"
	]
},
	"application/secevent+jwt": {
	source: "iana"
},
	"application/senml+cbor": {
	source: "iana"
},
	"application/senml+json": {
	source: "iana",
	compressible: true
},
	"application/senml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"senmlx"
	]
},
	"application/senml-etch+cbor": {
	source: "iana"
},
	"application/senml-etch+json": {
	source: "iana",
	compressible: true
},
	"application/senml-exi": {
	source: "iana"
},
	"application/sensml+cbor": {
	source: "iana"
},
	"application/sensml+json": {
	source: "iana",
	compressible: true
},
	"application/sensml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"sensmlx"
	]
},
	"application/sensml-exi": {
	source: "iana"
},
	"application/sep+xml": {
	source: "iana",
	compressible: true
},
	"application/sep-exi": {
	source: "iana"
},
	"application/session-info": {
	source: "iana"
},
	"application/set-payment": {
	source: "iana"
},
	"application/set-payment-initiation": {
	source: "iana",
	extensions: [
		"setpay"
	]
},
	"application/set-registration": {
	source: "iana"
},
	"application/set-registration-initiation": {
	source: "iana",
	extensions: [
		"setreg"
	]
},
	"application/sgml": {
	source: "iana"
},
	"application/sgml-open-catalog": {
	source: "iana"
},
	"application/shf+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"shf"
	]
},
	"application/sieve": {
	source: "iana",
	extensions: [
		"siv",
		"sieve"
	]
},
	"application/simple-filter+xml": {
	source: "iana",
	compressible: true
},
	"application/simple-message-summary": {
	source: "iana"
},
	"application/simplesymbolcontainer": {
	source: "iana"
},
	"application/sipc": {
	source: "iana"
},
	"application/slate": {
	source: "iana"
},
	"application/smil": {
	source: "iana"
},
	"application/smil+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"smi",
		"smil"
	]
},
	"application/smpte336m": {
	source: "iana"
},
	"application/soap+fastinfoset": {
	source: "iana"
},
	"application/soap+xml": {
	source: "iana",
	compressible: true
},
	"application/sparql-query": {
	source: "iana",
	extensions: [
		"rq"
	]
},
	"application/sparql-results+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"srx"
	]
},
	"application/spdx+json": {
	source: "iana",
	compressible: true
},
	"application/spirits-event+xml": {
	source: "iana",
	compressible: true
},
	"application/sql": {
	source: "iana"
},
	"application/srgs": {
	source: "iana",
	extensions: [
		"gram"
	]
},
	"application/srgs+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"grxml"
	]
},
	"application/sru+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"sru"
	]
},
	"application/ssdl+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"ssdl"
	]
},
	"application/ssml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"ssml"
	]
},
	"application/stix+json": {
	source: "iana",
	compressible: true
},
	"application/swid+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"swidtag"
	]
},
	"application/tamp-apex-update": {
	source: "iana"
},
	"application/tamp-apex-update-confirm": {
	source: "iana"
},
	"application/tamp-community-update": {
	source: "iana"
},
	"application/tamp-community-update-confirm": {
	source: "iana"
},
	"application/tamp-error": {
	source: "iana"
},
	"application/tamp-sequence-adjust": {
	source: "iana"
},
	"application/tamp-sequence-adjust-confirm": {
	source: "iana"
},
	"application/tamp-status-query": {
	source: "iana"
},
	"application/tamp-status-response": {
	source: "iana"
},
	"application/tamp-update": {
	source: "iana"
},
	"application/tamp-update-confirm": {
	source: "iana"
},
	"application/tar": {
	compressible: true
},
	"application/taxii+json": {
	source: "iana",
	compressible: true
},
	"application/td+json": {
	source: "iana",
	compressible: true
},
	"application/tei+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"tei",
		"teicorpus"
	]
},
	"application/tetra_isi": {
	source: "iana"
},
	"application/thraud+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"tfi"
	]
},
	"application/timestamp-query": {
	source: "iana"
},
	"application/timestamp-reply": {
	source: "iana"
},
	"application/timestamped-data": {
	source: "iana",
	extensions: [
		"tsd"
	]
},
	"application/tlsrpt+gzip": {
	source: "iana"
},
	"application/tlsrpt+json": {
	source: "iana",
	compressible: true
},
	"application/tnauthlist": {
	source: "iana"
},
	"application/token-introspection+jwt": {
	source: "iana"
},
	"application/toml": {
	compressible: true,
	extensions: [
		"toml"
	]
},
	"application/trickle-ice-sdpfrag": {
	source: "iana"
},
	"application/trig": {
	source: "iana",
	extensions: [
		"trig"
	]
},
	"application/ttml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"ttml"
	]
},
	"application/tve-trigger": {
	source: "iana"
},
	"application/tzif": {
	source: "iana"
},
	"application/tzif-leap": {
	source: "iana"
},
	"application/ubjson": {
	compressible: false,
	extensions: [
		"ubj"
	]
},
	"application/ulpfec": {
	source: "iana"
},
	"application/urc-grpsheet+xml": {
	source: "iana",
	compressible: true
},
	"application/urc-ressheet+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"rsheet"
	]
},
	"application/urc-targetdesc+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"td"
	]
},
	"application/urc-uisocketdesc+xml": {
	source: "iana",
	compressible: true
},
	"application/vcard+json": {
	source: "iana",
	compressible: true
},
	"application/vcard+xml": {
	source: "iana",
	compressible: true
},
	"application/vemmi": {
	source: "iana"
},
	"application/vividence.scriptfile": {
	source: "apache"
},
	"application/vnd.1000minds.decision-model+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"1km"
	]
},
	"application/vnd.3gpp-prose+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp-prose-pc3ch+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp-v2x-local-service-information": {
	source: "iana"
},
	"application/vnd.3gpp.5gnas": {
	source: "iana"
},
	"application/vnd.3gpp.access-transfer-events+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.bsf+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.gmop+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.gtpc": {
	source: "iana"
},
	"application/vnd.3gpp.interworking-data": {
	source: "iana"
},
	"application/vnd.3gpp.lpp": {
	source: "iana"
},
	"application/vnd.3gpp.mc-signalling-ear": {
	source: "iana"
},
	"application/vnd.3gpp.mcdata-affiliation-command+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcdata-info+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcdata-payload": {
	source: "iana"
},
	"application/vnd.3gpp.mcdata-service-config+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcdata-signalling": {
	source: "iana"
},
	"application/vnd.3gpp.mcdata-ue-config+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcdata-user-profile+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcptt-affiliation-command+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcptt-floor-request+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcptt-info+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcptt-location-info+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcptt-mbms-usage-info+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcptt-service-config+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcptt-signed+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcptt-ue-config+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcptt-ue-init-config+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcptt-user-profile+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcvideo-affiliation-command+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcvideo-affiliation-info+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcvideo-info+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcvideo-location-info+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcvideo-mbms-usage-info+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcvideo-service-config+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcvideo-transmission-request+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcvideo-ue-config+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mcvideo-user-profile+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.mid-call+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.ngap": {
	source: "iana"
},
	"application/vnd.3gpp.pfcp": {
	source: "iana"
},
	"application/vnd.3gpp.pic-bw-large": {
	source: "iana",
	extensions: [
		"plb"
	]
},
	"application/vnd.3gpp.pic-bw-small": {
	source: "iana",
	extensions: [
		"psb"
	]
},
	"application/vnd.3gpp.pic-bw-var": {
	source: "iana",
	extensions: [
		"pvb"
	]
},
	"application/vnd.3gpp.s1ap": {
	source: "iana"
},
	"application/vnd.3gpp.sms": {
	source: "iana"
},
	"application/vnd.3gpp.sms+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.srvcc-ext+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.srvcc-info+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.state-and-event-info+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp.ussd+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp2.bcmcsinfo+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.3gpp2.sms": {
	source: "iana"
},
	"application/vnd.3gpp2.tcap": {
	source: "iana",
	extensions: [
		"tcap"
	]
},
	"application/vnd.3lightssoftware.imagescal": {
	source: "iana"
},
	"application/vnd.3m.post-it-notes": {
	source: "iana",
	extensions: [
		"pwn"
	]
},
	"application/vnd.accpac.simply.aso": {
	source: "iana",
	extensions: [
		"aso"
	]
},
	"application/vnd.accpac.simply.imp": {
	source: "iana",
	extensions: [
		"imp"
	]
},
	"application/vnd.acucobol": {
	source: "iana",
	extensions: [
		"acu"
	]
},
	"application/vnd.acucorp": {
	source: "iana",
	extensions: [
		"atc",
		"acutc"
	]
},
	"application/vnd.adobe.air-application-installer-package+zip": {
	source: "apache",
	compressible: false,
	extensions: [
		"air"
	]
},
	"application/vnd.adobe.flash.movie": {
	source: "iana"
},
	"application/vnd.adobe.formscentral.fcdt": {
	source: "iana",
	extensions: [
		"fcdt"
	]
},
	"application/vnd.adobe.fxp": {
	source: "iana",
	extensions: [
		"fxp",
		"fxpl"
	]
},
	"application/vnd.adobe.partial-upload": {
	source: "iana"
},
	"application/vnd.adobe.xdp+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xdp"
	]
},
	"application/vnd.adobe.xfdf": {
	source: "iana",
	extensions: [
		"xfdf"
	]
},
	"application/vnd.aether.imp": {
	source: "iana"
},
	"application/vnd.afpc.afplinedata": {
	source: "iana"
},
	"application/vnd.afpc.afplinedata-pagedef": {
	source: "iana"
},
	"application/vnd.afpc.cmoca-cmresource": {
	source: "iana"
},
	"application/vnd.afpc.foca-charset": {
	source: "iana"
},
	"application/vnd.afpc.foca-codedfont": {
	source: "iana"
},
	"application/vnd.afpc.foca-codepage": {
	source: "iana"
},
	"application/vnd.afpc.modca": {
	source: "iana"
},
	"application/vnd.afpc.modca-cmtable": {
	source: "iana"
},
	"application/vnd.afpc.modca-formdef": {
	source: "iana"
},
	"application/vnd.afpc.modca-mediummap": {
	source: "iana"
},
	"application/vnd.afpc.modca-objectcontainer": {
	source: "iana"
},
	"application/vnd.afpc.modca-overlay": {
	source: "iana"
},
	"application/vnd.afpc.modca-pagesegment": {
	source: "iana"
},
	"application/vnd.age": {
	source: "iana",
	extensions: [
		"age"
	]
},
	"application/vnd.ah-barcode": {
	source: "iana"
},
	"application/vnd.ahead.space": {
	source: "iana",
	extensions: [
		"ahead"
	]
},
	"application/vnd.airzip.filesecure.azf": {
	source: "iana",
	extensions: [
		"azf"
	]
},
	"application/vnd.airzip.filesecure.azs": {
	source: "iana",
	extensions: [
		"azs"
	]
},
	"application/vnd.amadeus+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.amazon.ebook": {
	source: "apache",
	extensions: [
		"azw"
	]
},
	"application/vnd.amazon.mobi8-ebook": {
	source: "iana"
},
	"application/vnd.americandynamics.acc": {
	source: "iana",
	extensions: [
		"acc"
	]
},
	"application/vnd.amiga.ami": {
	source: "iana",
	extensions: [
		"ami"
	]
},
	"application/vnd.amundsen.maze+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.android.ota": {
	source: "iana"
},
	"application/vnd.android.package-archive": {
	source: "apache",
	compressible: false,
	extensions: [
		"apk"
	]
},
	"application/vnd.anki": {
	source: "iana"
},
	"application/vnd.anser-web-certificate-issue-initiation": {
	source: "iana",
	extensions: [
		"cii"
	]
},
	"application/vnd.anser-web-funds-transfer-initiation": {
	source: "apache",
	extensions: [
		"fti"
	]
},
	"application/vnd.antix.game-component": {
	source: "iana",
	extensions: [
		"atx"
	]
},
	"application/vnd.apache.arrow.file": {
	source: "iana"
},
	"application/vnd.apache.arrow.stream": {
	source: "iana"
},
	"application/vnd.apache.thrift.binary": {
	source: "iana"
},
	"application/vnd.apache.thrift.compact": {
	source: "iana"
},
	"application/vnd.apache.thrift.json": {
	source: "iana"
},
	"application/vnd.api+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.aplextor.warrp+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.apothekende.reservation+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.apple.installer+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"mpkg"
	]
},
	"application/vnd.apple.keynote": {
	source: "iana",
	extensions: [
		"key"
	]
},
	"application/vnd.apple.mpegurl": {
	source: "iana",
	extensions: [
		"m3u8"
	]
},
	"application/vnd.apple.numbers": {
	source: "iana",
	extensions: [
		"numbers"
	]
},
	"application/vnd.apple.pages": {
	source: "iana",
	extensions: [
		"pages"
	]
},
	"application/vnd.apple.pkpass": {
	compressible: false,
	extensions: [
		"pkpass"
	]
},
	"application/vnd.arastra.swi": {
	source: "iana"
},
	"application/vnd.aristanetworks.swi": {
	source: "iana",
	extensions: [
		"swi"
	]
},
	"application/vnd.artisan+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.artsquare": {
	source: "iana"
},
	"application/vnd.astraea-software.iota": {
	source: "iana",
	extensions: [
		"iota"
	]
},
	"application/vnd.audiograph": {
	source: "iana",
	extensions: [
		"aep"
	]
},
	"application/vnd.autopackage": {
	source: "iana"
},
	"application/vnd.avalon+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.avistar+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.balsamiq.bmml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"bmml"
	]
},
	"application/vnd.balsamiq.bmpr": {
	source: "iana"
},
	"application/vnd.banana-accounting": {
	source: "iana"
},
	"application/vnd.bbf.usp.error": {
	source: "iana"
},
	"application/vnd.bbf.usp.msg": {
	source: "iana"
},
	"application/vnd.bbf.usp.msg+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.bekitzur-stech+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.bint.med-content": {
	source: "iana"
},
	"application/vnd.biopax.rdf+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.blink-idb-value-wrapper": {
	source: "iana"
},
	"application/vnd.blueice.multipass": {
	source: "iana",
	extensions: [
		"mpm"
	]
},
	"application/vnd.bluetooth.ep.oob": {
	source: "iana"
},
	"application/vnd.bluetooth.le.oob": {
	source: "iana"
},
	"application/vnd.bmi": {
	source: "iana",
	extensions: [
		"bmi"
	]
},
	"application/vnd.bpf": {
	source: "iana"
},
	"application/vnd.bpf3": {
	source: "iana"
},
	"application/vnd.businessobjects": {
	source: "iana",
	extensions: [
		"rep"
	]
},
	"application/vnd.byu.uapi+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.cab-jscript": {
	source: "iana"
},
	"application/vnd.canon-cpdl": {
	source: "iana"
},
	"application/vnd.canon-lips": {
	source: "iana"
},
	"application/vnd.capasystems-pg+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.cendio.thinlinc.clientconf": {
	source: "iana"
},
	"application/vnd.century-systems.tcp_stream": {
	source: "iana"
},
	"application/vnd.chemdraw+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"cdxml"
	]
},
	"application/vnd.chess-pgn": {
	source: "iana"
},
	"application/vnd.chipnuts.karaoke-mmd": {
	source: "iana",
	extensions: [
		"mmd"
	]
},
	"application/vnd.ciedi": {
	source: "iana"
},
	"application/vnd.cinderella": {
	source: "iana",
	extensions: [
		"cdy"
	]
},
	"application/vnd.cirpack.isdn-ext": {
	source: "iana"
},
	"application/vnd.citationstyles.style+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"csl"
	]
},
	"application/vnd.claymore": {
	source: "iana",
	extensions: [
		"cla"
	]
},
	"application/vnd.cloanto.rp9": {
	source: "iana",
	extensions: [
		"rp9"
	]
},
	"application/vnd.clonk.c4group": {
	source: "iana",
	extensions: [
		"c4g",
		"c4d",
		"c4f",
		"c4p",
		"c4u"
	]
},
	"application/vnd.cluetrust.cartomobile-config": {
	source: "iana",
	extensions: [
		"c11amc"
	]
},
	"application/vnd.cluetrust.cartomobile-config-pkg": {
	source: "iana",
	extensions: [
		"c11amz"
	]
},
	"application/vnd.coffeescript": {
	source: "iana"
},
	"application/vnd.collabio.xodocuments.document": {
	source: "iana"
},
	"application/vnd.collabio.xodocuments.document-template": {
	source: "iana"
},
	"application/vnd.collabio.xodocuments.presentation": {
	source: "iana"
},
	"application/vnd.collabio.xodocuments.presentation-template": {
	source: "iana"
},
	"application/vnd.collabio.xodocuments.spreadsheet": {
	source: "iana"
},
	"application/vnd.collabio.xodocuments.spreadsheet-template": {
	source: "iana"
},
	"application/vnd.collection+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.collection.doc+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.collection.next+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.comicbook+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.comicbook-rar": {
	source: "iana"
},
	"application/vnd.commerce-battelle": {
	source: "iana"
},
	"application/vnd.commonspace": {
	source: "iana",
	extensions: [
		"csp"
	]
},
	"application/vnd.contact.cmsg": {
	source: "iana",
	extensions: [
		"cdbcmsg"
	]
},
	"application/vnd.coreos.ignition+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.cosmocaller": {
	source: "iana",
	extensions: [
		"cmc"
	]
},
	"application/vnd.crick.clicker": {
	source: "iana",
	extensions: [
		"clkx"
	]
},
	"application/vnd.crick.clicker.keyboard": {
	source: "iana",
	extensions: [
		"clkk"
	]
},
	"application/vnd.crick.clicker.palette": {
	source: "iana",
	extensions: [
		"clkp"
	]
},
	"application/vnd.crick.clicker.template": {
	source: "iana",
	extensions: [
		"clkt"
	]
},
	"application/vnd.crick.clicker.wordbank": {
	source: "iana",
	extensions: [
		"clkw"
	]
},
	"application/vnd.criticaltools.wbs+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"wbs"
	]
},
	"application/vnd.cryptii.pipe+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.crypto-shade-file": {
	source: "iana"
},
	"application/vnd.cryptomator.encrypted": {
	source: "iana"
},
	"application/vnd.cryptomator.vault": {
	source: "iana"
},
	"application/vnd.ctc-posml": {
	source: "iana",
	extensions: [
		"pml"
	]
},
	"application/vnd.ctct.ws+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.cups-pdf": {
	source: "iana"
},
	"application/vnd.cups-postscript": {
	source: "iana"
},
	"application/vnd.cups-ppd": {
	source: "iana",
	extensions: [
		"ppd"
	]
},
	"application/vnd.cups-raster": {
	source: "iana"
},
	"application/vnd.cups-raw": {
	source: "iana"
},
	"application/vnd.curl": {
	source: "iana"
},
	"application/vnd.curl.car": {
	source: "apache",
	extensions: [
		"car"
	]
},
	"application/vnd.curl.pcurl": {
	source: "apache",
	extensions: [
		"pcurl"
	]
},
	"application/vnd.cyan.dean.root+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.cybank": {
	source: "iana"
},
	"application/vnd.cyclonedx+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.cyclonedx+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.d2l.coursepackage1p0+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.d3m-dataset": {
	source: "iana"
},
	"application/vnd.d3m-problem": {
	source: "iana"
},
	"application/vnd.dart": {
	source: "iana",
	compressible: true,
	extensions: [
		"dart"
	]
},
	"application/vnd.data-vision.rdz": {
	source: "iana",
	extensions: [
		"rdz"
	]
},
	"application/vnd.datapackage+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.dataresource+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.dbf": {
	source: "iana",
	extensions: [
		"dbf"
	]
},
	"application/vnd.debian.binary-package": {
	source: "iana"
},
	"application/vnd.dece.data": {
	source: "iana",
	extensions: [
		"uvf",
		"uvvf",
		"uvd",
		"uvvd"
	]
},
	"application/vnd.dece.ttml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"uvt",
		"uvvt"
	]
},
	"application/vnd.dece.unspecified": {
	source: "iana",
	extensions: [
		"uvx",
		"uvvx"
	]
},
	"application/vnd.dece.zip": {
	source: "iana",
	extensions: [
		"uvz",
		"uvvz"
	]
},
	"application/vnd.denovo.fcselayout-link": {
	source: "iana",
	extensions: [
		"fe_launch"
	]
},
	"application/vnd.desmume.movie": {
	source: "iana"
},
	"application/vnd.dir-bi.plate-dl-nosuffix": {
	source: "iana"
},
	"application/vnd.dm.delegation+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.dna": {
	source: "iana",
	extensions: [
		"dna"
	]
},
	"application/vnd.document+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.dolby.mlp": {
	source: "apache",
	extensions: [
		"mlp"
	]
},
	"application/vnd.dolby.mobile.1": {
	source: "iana"
},
	"application/vnd.dolby.mobile.2": {
	source: "iana"
},
	"application/vnd.doremir.scorecloud-binary-document": {
	source: "iana"
},
	"application/vnd.dpgraph": {
	source: "iana",
	extensions: [
		"dpg"
	]
},
	"application/vnd.dreamfactory": {
	source: "iana",
	extensions: [
		"dfac"
	]
},
	"application/vnd.drive+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.ds-keypoint": {
	source: "apache",
	extensions: [
		"kpxx"
	]
},
	"application/vnd.dtg.local": {
	source: "iana"
},
	"application/vnd.dtg.local.flash": {
	source: "iana"
},
	"application/vnd.dtg.local.html": {
	source: "iana"
},
	"application/vnd.dvb.ait": {
	source: "iana",
	extensions: [
		"ait"
	]
},
	"application/vnd.dvb.dvbisl+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.dvb.dvbj": {
	source: "iana"
},
	"application/vnd.dvb.esgcontainer": {
	source: "iana"
},
	"application/vnd.dvb.ipdcdftnotifaccess": {
	source: "iana"
},
	"application/vnd.dvb.ipdcesgaccess": {
	source: "iana"
},
	"application/vnd.dvb.ipdcesgaccess2": {
	source: "iana"
},
	"application/vnd.dvb.ipdcesgpdd": {
	source: "iana"
},
	"application/vnd.dvb.ipdcroaming": {
	source: "iana"
},
	"application/vnd.dvb.iptv.alfec-base": {
	source: "iana"
},
	"application/vnd.dvb.iptv.alfec-enhancement": {
	source: "iana"
},
	"application/vnd.dvb.notif-aggregate-root+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.dvb.notif-container+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.dvb.notif-generic+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.dvb.notif-ia-msglist+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.dvb.notif-ia-registration-request+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.dvb.notif-ia-registration-response+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.dvb.notif-init+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.dvb.pfr": {
	source: "iana"
},
	"application/vnd.dvb.service": {
	source: "iana",
	extensions: [
		"svc"
	]
},
	"application/vnd.dxr": {
	source: "iana"
},
	"application/vnd.dynageo": {
	source: "iana",
	extensions: [
		"geo"
	]
},
	"application/vnd.dzr": {
	source: "iana"
},
	"application/vnd.easykaraoke.cdgdownload": {
	source: "iana"
},
	"application/vnd.ecdis-update": {
	source: "iana"
},
	"application/vnd.ecip.rlp": {
	source: "iana"
},
	"application/vnd.eclipse.ditto+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.ecowin.chart": {
	source: "iana",
	extensions: [
		"mag"
	]
},
	"application/vnd.ecowin.filerequest": {
	source: "iana"
},
	"application/vnd.ecowin.fileupdate": {
	source: "iana"
},
	"application/vnd.ecowin.series": {
	source: "iana"
},
	"application/vnd.ecowin.seriesrequest": {
	source: "iana"
},
	"application/vnd.ecowin.seriesupdate": {
	source: "iana"
},
	"application/vnd.efi.img": {
	source: "iana"
},
	"application/vnd.efi.iso": {
	source: "iana"
},
	"application/vnd.emclient.accessrequest+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.enliven": {
	source: "iana",
	extensions: [
		"nml"
	]
},
	"application/vnd.enphase.envoy": {
	source: "iana"
},
	"application/vnd.eprints.data+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.epson.esf": {
	source: "iana",
	extensions: [
		"esf"
	]
},
	"application/vnd.epson.msf": {
	source: "iana",
	extensions: [
		"msf"
	]
},
	"application/vnd.epson.quickanime": {
	source: "iana",
	extensions: [
		"qam"
	]
},
	"application/vnd.epson.salt": {
	source: "iana",
	extensions: [
		"slt"
	]
},
	"application/vnd.epson.ssf": {
	source: "iana",
	extensions: [
		"ssf"
	]
},
	"application/vnd.ericsson.quickcall": {
	source: "iana"
},
	"application/vnd.espass-espass+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.eszigno3+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"es3",
		"et3"
	]
},
	"application/vnd.etsi.aoc+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.asic-e+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.etsi.asic-s+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.etsi.cug+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.iptvcommand+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.iptvdiscovery+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.iptvprofile+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.iptvsad-bc+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.iptvsad-cod+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.iptvsad-npvr+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.iptvservice+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.iptvsync+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.iptvueprofile+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.mcid+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.mheg5": {
	source: "iana"
},
	"application/vnd.etsi.overload-control-policy-dataset+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.pstn+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.sci+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.simservs+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.timestamp-token": {
	source: "iana"
},
	"application/vnd.etsi.tsl+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.etsi.tsl.der": {
	source: "iana"
},
	"application/vnd.eu.kasparian.car+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.eudora.data": {
	source: "iana"
},
	"application/vnd.evolv.ecig.profile": {
	source: "iana"
},
	"application/vnd.evolv.ecig.settings": {
	source: "iana"
},
	"application/vnd.evolv.ecig.theme": {
	source: "iana"
},
	"application/vnd.exstream-empower+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.exstream-package": {
	source: "iana"
},
	"application/vnd.ezpix-album": {
	source: "iana",
	extensions: [
		"ez2"
	]
},
	"application/vnd.ezpix-package": {
	source: "iana",
	extensions: [
		"ez3"
	]
},
	"application/vnd.f-secure.mobile": {
	source: "iana"
},
	"application/vnd.familysearch.gedcom+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.fastcopy-disk-image": {
	source: "iana"
},
	"application/vnd.fdf": {
	source: "iana",
	extensions: [
		"fdf"
	]
},
	"application/vnd.fdsn.mseed": {
	source: "iana",
	extensions: [
		"mseed"
	]
},
	"application/vnd.fdsn.seed": {
	source: "iana",
	extensions: [
		"seed",
		"dataless"
	]
},
	"application/vnd.ffsns": {
	source: "iana"
},
	"application/vnd.ficlab.flb+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.filmit.zfc": {
	source: "iana"
},
	"application/vnd.fints": {
	source: "iana"
},
	"application/vnd.firemonkeys.cloudcell": {
	source: "iana"
},
	"application/vnd.flographit": {
	source: "iana",
	extensions: [
		"gph"
	]
},
	"application/vnd.fluxtime.clip": {
	source: "iana",
	extensions: [
		"ftc"
	]
},
	"application/vnd.font-fontforge-sfd": {
	source: "iana"
},
	"application/vnd.framemaker": {
	source: "iana",
	extensions: [
		"fm",
		"frame",
		"maker",
		"book"
	]
},
	"application/vnd.frogans.fnc": {
	source: "iana",
	extensions: [
		"fnc"
	]
},
	"application/vnd.frogans.ltf": {
	source: "iana",
	extensions: [
		"ltf"
	]
},
	"application/vnd.fsc.weblaunch": {
	source: "iana",
	extensions: [
		"fsc"
	]
},
	"application/vnd.fujifilm.fb.docuworks": {
	source: "iana"
},
	"application/vnd.fujifilm.fb.docuworks.binder": {
	source: "iana"
},
	"application/vnd.fujifilm.fb.docuworks.container": {
	source: "iana"
},
	"application/vnd.fujifilm.fb.jfi+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.fujitsu.oasys": {
	source: "iana",
	extensions: [
		"oas"
	]
},
	"application/vnd.fujitsu.oasys2": {
	source: "iana",
	extensions: [
		"oa2"
	]
},
	"application/vnd.fujitsu.oasys3": {
	source: "iana",
	extensions: [
		"oa3"
	]
},
	"application/vnd.fujitsu.oasysgp": {
	source: "iana",
	extensions: [
		"fg5"
	]
},
	"application/vnd.fujitsu.oasysprs": {
	source: "iana",
	extensions: [
		"bh2"
	]
},
	"application/vnd.fujixerox.art-ex": {
	source: "iana"
},
	"application/vnd.fujixerox.art4": {
	source: "iana"
},
	"application/vnd.fujixerox.ddd": {
	source: "iana",
	extensions: [
		"ddd"
	]
},
	"application/vnd.fujixerox.docuworks": {
	source: "iana",
	extensions: [
		"xdw"
	]
},
	"application/vnd.fujixerox.docuworks.binder": {
	source: "iana",
	extensions: [
		"xbd"
	]
},
	"application/vnd.fujixerox.docuworks.container": {
	source: "iana"
},
	"application/vnd.fujixerox.hbpl": {
	source: "iana"
},
	"application/vnd.fut-misnet": {
	source: "iana"
},
	"application/vnd.futoin+cbor": {
	source: "iana"
},
	"application/vnd.futoin+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.fuzzysheet": {
	source: "iana",
	extensions: [
		"fzs"
	]
},
	"application/vnd.genomatix.tuxedo": {
	source: "iana",
	extensions: [
		"txd"
	]
},
	"application/vnd.gentics.grd+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.geo+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.geocube+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.geogebra.file": {
	source: "iana",
	extensions: [
		"ggb"
	]
},
	"application/vnd.geogebra.slides": {
	source: "iana"
},
	"application/vnd.geogebra.tool": {
	source: "iana",
	extensions: [
		"ggt"
	]
},
	"application/vnd.geometry-explorer": {
	source: "iana",
	extensions: [
		"gex",
		"gre"
	]
},
	"application/vnd.geonext": {
	source: "iana",
	extensions: [
		"gxt"
	]
},
	"application/vnd.geoplan": {
	source: "iana",
	extensions: [
		"g2w"
	]
},
	"application/vnd.geospace": {
	source: "iana",
	extensions: [
		"g3w"
	]
},
	"application/vnd.gerber": {
	source: "iana"
},
	"application/vnd.globalplatform.card-content-mgt": {
	source: "iana"
},
	"application/vnd.globalplatform.card-content-mgt-response": {
	source: "iana"
},
	"application/vnd.gmx": {
	source: "iana",
	extensions: [
		"gmx"
	]
},
	"application/vnd.google-apps.document": {
	compressible: false,
	extensions: [
		"gdoc"
	]
},
	"application/vnd.google-apps.presentation": {
	compressible: false,
	extensions: [
		"gslides"
	]
},
	"application/vnd.google-apps.spreadsheet": {
	compressible: false,
	extensions: [
		"gsheet"
	]
},
	"application/vnd.google-earth.kml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"kml"
	]
},
	"application/vnd.google-earth.kmz": {
	source: "iana",
	compressible: false,
	extensions: [
		"kmz"
	]
},
	"application/vnd.gov.sk.e-form+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.gov.sk.e-form+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.gov.sk.xmldatacontainer+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.grafeq": {
	source: "iana",
	extensions: [
		"gqf",
		"gqs"
	]
},
	"application/vnd.gridmp": {
	source: "iana"
},
	"application/vnd.groove-account": {
	source: "iana",
	extensions: [
		"gac"
	]
},
	"application/vnd.groove-help": {
	source: "iana",
	extensions: [
		"ghf"
	]
},
	"application/vnd.groove-identity-message": {
	source: "iana",
	extensions: [
		"gim"
	]
},
	"application/vnd.groove-injector": {
	source: "iana",
	extensions: [
		"grv"
	]
},
	"application/vnd.groove-tool-message": {
	source: "iana",
	extensions: [
		"gtm"
	]
},
	"application/vnd.groove-tool-template": {
	source: "iana",
	extensions: [
		"tpl"
	]
},
	"application/vnd.groove-vcard": {
	source: "iana",
	extensions: [
		"vcg"
	]
},
	"application/vnd.hal+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.hal+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"hal"
	]
},
	"application/vnd.handheld-entertainment+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"zmm"
	]
},
	"application/vnd.hbci": {
	source: "iana",
	extensions: [
		"hbci"
	]
},
	"application/vnd.hc+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.hcl-bireports": {
	source: "iana"
},
	"application/vnd.hdt": {
	source: "iana"
},
	"application/vnd.heroku+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.hhe.lesson-player": {
	source: "iana",
	extensions: [
		"les"
	]
},
	"application/vnd.hl7cda+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/vnd.hl7v2+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/vnd.hp-hpgl": {
	source: "iana",
	extensions: [
		"hpgl"
	]
},
	"application/vnd.hp-hpid": {
	source: "iana",
	extensions: [
		"hpid"
	]
},
	"application/vnd.hp-hps": {
	source: "iana",
	extensions: [
		"hps"
	]
},
	"application/vnd.hp-jlyt": {
	source: "iana",
	extensions: [
		"jlt"
	]
},
	"application/vnd.hp-pcl": {
	source: "iana",
	extensions: [
		"pcl"
	]
},
	"application/vnd.hp-pclxl": {
	source: "iana",
	extensions: [
		"pclxl"
	]
},
	"application/vnd.httphone": {
	source: "iana"
},
	"application/vnd.hydrostatix.sof-data": {
	source: "iana",
	extensions: [
		"sfd-hdstx"
	]
},
	"application/vnd.hyper+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.hyper-item+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.hyperdrive+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.hzn-3d-crossword": {
	source: "iana"
},
	"application/vnd.ibm.afplinedata": {
	source: "iana"
},
	"application/vnd.ibm.electronic-media": {
	source: "iana"
},
	"application/vnd.ibm.minipay": {
	source: "iana",
	extensions: [
		"mpy"
	]
},
	"application/vnd.ibm.modcap": {
	source: "iana",
	extensions: [
		"afp",
		"listafp",
		"list3820"
	]
},
	"application/vnd.ibm.rights-management": {
	source: "iana",
	extensions: [
		"irm"
	]
},
	"application/vnd.ibm.secure-container": {
	source: "iana",
	extensions: [
		"sc"
	]
},
	"application/vnd.iccprofile": {
	source: "iana",
	extensions: [
		"icc",
		"icm"
	]
},
	"application/vnd.ieee.1905": {
	source: "iana"
},
	"application/vnd.igloader": {
	source: "iana",
	extensions: [
		"igl"
	]
},
	"application/vnd.imagemeter.folder+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.imagemeter.image+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.immervision-ivp": {
	source: "iana",
	extensions: [
		"ivp"
	]
},
	"application/vnd.immervision-ivu": {
	source: "iana",
	extensions: [
		"ivu"
	]
},
	"application/vnd.ims.imsccv1p1": {
	source: "iana"
},
	"application/vnd.ims.imsccv1p2": {
	source: "iana"
},
	"application/vnd.ims.imsccv1p3": {
	source: "iana"
},
	"application/vnd.ims.lis.v2.result+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.ims.lti.v2.toolconsumerprofile+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.ims.lti.v2.toolproxy+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.ims.lti.v2.toolproxy.id+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.ims.lti.v2.toolsettings+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.ims.lti.v2.toolsettings.simple+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.informedcontrol.rms+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.informix-visionary": {
	source: "iana"
},
	"application/vnd.infotech.project": {
	source: "iana"
},
	"application/vnd.infotech.project+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.innopath.wamp.notification": {
	source: "iana"
},
	"application/vnd.insors.igm": {
	source: "iana",
	extensions: [
		"igm"
	]
},
	"application/vnd.intercon.formnet": {
	source: "iana",
	extensions: [
		"xpw",
		"xpx"
	]
},
	"application/vnd.intergeo": {
	source: "iana",
	extensions: [
		"i2g"
	]
},
	"application/vnd.intertrust.digibox": {
	source: "iana"
},
	"application/vnd.intertrust.nncp": {
	source: "iana"
},
	"application/vnd.intu.qbo": {
	source: "iana",
	extensions: [
		"qbo"
	]
},
	"application/vnd.intu.qfx": {
	source: "iana",
	extensions: [
		"qfx"
	]
},
	"application/vnd.iptc.g2.catalogitem+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.iptc.g2.conceptitem+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.iptc.g2.knowledgeitem+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.iptc.g2.newsitem+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.iptc.g2.newsmessage+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.iptc.g2.packageitem+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.iptc.g2.planningitem+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.ipunplugged.rcprofile": {
	source: "iana",
	extensions: [
		"rcprofile"
	]
},
	"application/vnd.irepository.package+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"irp"
	]
},
	"application/vnd.is-xpr": {
	source: "iana",
	extensions: [
		"xpr"
	]
},
	"application/vnd.isac.fcs": {
	source: "iana",
	extensions: [
		"fcs"
	]
},
	"application/vnd.iso11783-10+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.jam": {
	source: "iana",
	extensions: [
		"jam"
	]
},
	"application/vnd.japannet-directory-service": {
	source: "iana"
},
	"application/vnd.japannet-jpnstore-wakeup": {
	source: "iana"
},
	"application/vnd.japannet-payment-wakeup": {
	source: "iana"
},
	"application/vnd.japannet-registration": {
	source: "iana"
},
	"application/vnd.japannet-registration-wakeup": {
	source: "iana"
},
	"application/vnd.japannet-setstore-wakeup": {
	source: "iana"
},
	"application/vnd.japannet-verification": {
	source: "iana"
},
	"application/vnd.japannet-verification-wakeup": {
	source: "iana"
},
	"application/vnd.jcp.javame.midlet-rms": {
	source: "iana",
	extensions: [
		"rms"
	]
},
	"application/vnd.jisp": {
	source: "iana",
	extensions: [
		"jisp"
	]
},
	"application/vnd.joost.joda-archive": {
	source: "iana",
	extensions: [
		"joda"
	]
},
	"application/vnd.jsk.isdn-ngn": {
	source: "iana"
},
	"application/vnd.kahootz": {
	source: "iana",
	extensions: [
		"ktz",
		"ktr"
	]
},
	"application/vnd.kde.karbon": {
	source: "iana",
	extensions: [
		"karbon"
	]
},
	"application/vnd.kde.kchart": {
	source: "iana",
	extensions: [
		"chrt"
	]
},
	"application/vnd.kde.kformula": {
	source: "iana",
	extensions: [
		"kfo"
	]
},
	"application/vnd.kde.kivio": {
	source: "iana",
	extensions: [
		"flw"
	]
},
	"application/vnd.kde.kontour": {
	source: "iana",
	extensions: [
		"kon"
	]
},
	"application/vnd.kde.kpresenter": {
	source: "iana",
	extensions: [
		"kpr",
		"kpt"
	]
},
	"application/vnd.kde.kspread": {
	source: "iana",
	extensions: [
		"ksp"
	]
},
	"application/vnd.kde.kword": {
	source: "iana",
	extensions: [
		"kwd",
		"kwt"
	]
},
	"application/vnd.kenameaapp": {
	source: "iana",
	extensions: [
		"htke"
	]
},
	"application/vnd.kidspiration": {
	source: "iana",
	extensions: [
		"kia"
	]
},
	"application/vnd.kinar": {
	source: "iana",
	extensions: [
		"kne",
		"knp"
	]
},
	"application/vnd.koan": {
	source: "iana",
	extensions: [
		"skp",
		"skd",
		"skt",
		"skm"
	]
},
	"application/vnd.kodak-descriptor": {
	source: "iana",
	extensions: [
		"sse"
	]
},
	"application/vnd.las": {
	source: "iana"
},
	"application/vnd.las.las+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.las.las+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"lasxml"
	]
},
	"application/vnd.laszip": {
	source: "iana"
},
	"application/vnd.leap+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.liberty-request+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.llamagraphics.life-balance.desktop": {
	source: "iana",
	extensions: [
		"lbd"
	]
},
	"application/vnd.llamagraphics.life-balance.exchange+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"lbe"
	]
},
	"application/vnd.logipipe.circuit+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.loom": {
	source: "iana"
},
	"application/vnd.lotus-1-2-3": {
	source: "iana",
	extensions: [
		"123"
	]
},
	"application/vnd.lotus-approach": {
	source: "iana",
	extensions: [
		"apr"
	]
},
	"application/vnd.lotus-freelance": {
	source: "iana",
	extensions: [
		"pre"
	]
},
	"application/vnd.lotus-notes": {
	source: "iana",
	extensions: [
		"nsf"
	]
},
	"application/vnd.lotus-organizer": {
	source: "iana",
	extensions: [
		"org"
	]
},
	"application/vnd.lotus-screencam": {
	source: "iana",
	extensions: [
		"scm"
	]
},
	"application/vnd.lotus-wordpro": {
	source: "iana",
	extensions: [
		"lwp"
	]
},
	"application/vnd.macports.portpkg": {
	source: "iana",
	extensions: [
		"portpkg"
	]
},
	"application/vnd.mapbox-vector-tile": {
	source: "iana",
	extensions: [
		"mvt"
	]
},
	"application/vnd.marlin.drm.actiontoken+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.marlin.drm.conftoken+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.marlin.drm.license+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.marlin.drm.mdcf": {
	source: "iana"
},
	"application/vnd.mason+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.maxar.archive.3tz+zip": {
	source: "iana",
	compressible: false
},
	"application/vnd.maxmind.maxmind-db": {
	source: "iana"
},
	"application/vnd.mcd": {
	source: "iana",
	extensions: [
		"mcd"
	]
},
	"application/vnd.medcalcdata": {
	source: "iana",
	extensions: [
		"mc1"
	]
},
	"application/vnd.mediastation.cdkey": {
	source: "iana",
	extensions: [
		"cdkey"
	]
},
	"application/vnd.meridian-slingshot": {
	source: "iana"
},
	"application/vnd.mfer": {
	source: "iana",
	extensions: [
		"mwf"
	]
},
	"application/vnd.mfmp": {
	source: "iana",
	extensions: [
		"mfm"
	]
},
	"application/vnd.micro+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.micrografx.flo": {
	source: "iana",
	extensions: [
		"flo"
	]
},
	"application/vnd.micrografx.igx": {
	source: "iana",
	extensions: [
		"igx"
	]
},
	"application/vnd.microsoft.portable-executable": {
	source: "iana"
},
	"application/vnd.microsoft.windows.thumbnail-cache": {
	source: "iana"
},
	"application/vnd.miele+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.mif": {
	source: "iana",
	extensions: [
		"mif"
	]
},
	"application/vnd.minisoft-hp3000-save": {
	source: "iana"
},
	"application/vnd.mitsubishi.misty-guard.trustweb": {
	source: "iana"
},
	"application/vnd.mobius.daf": {
	source: "iana",
	extensions: [
		"daf"
	]
},
	"application/vnd.mobius.dis": {
	source: "iana",
	extensions: [
		"dis"
	]
},
	"application/vnd.mobius.mbk": {
	source: "iana",
	extensions: [
		"mbk"
	]
},
	"application/vnd.mobius.mqy": {
	source: "iana",
	extensions: [
		"mqy"
	]
},
	"application/vnd.mobius.msl": {
	source: "iana",
	extensions: [
		"msl"
	]
},
	"application/vnd.mobius.plc": {
	source: "iana",
	extensions: [
		"plc"
	]
},
	"application/vnd.mobius.txf": {
	source: "iana",
	extensions: [
		"txf"
	]
},
	"application/vnd.mophun.application": {
	source: "iana",
	extensions: [
		"mpn"
	]
},
	"application/vnd.mophun.certificate": {
	source: "iana",
	extensions: [
		"mpc"
	]
},
	"application/vnd.motorola.flexsuite": {
	source: "iana"
},
	"application/vnd.motorola.flexsuite.adsi": {
	source: "iana"
},
	"application/vnd.motorola.flexsuite.fis": {
	source: "iana"
},
	"application/vnd.motorola.flexsuite.gotap": {
	source: "iana"
},
	"application/vnd.motorola.flexsuite.kmr": {
	source: "iana"
},
	"application/vnd.motorola.flexsuite.ttc": {
	source: "iana"
},
	"application/vnd.motorola.flexsuite.wem": {
	source: "iana"
},
	"application/vnd.motorola.iprm": {
	source: "iana"
},
	"application/vnd.mozilla.xul+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xul"
	]
},
	"application/vnd.ms-3mfdocument": {
	source: "iana"
},
	"application/vnd.ms-artgalry": {
	source: "iana",
	extensions: [
		"cil"
	]
},
	"application/vnd.ms-asf": {
	source: "iana"
},
	"application/vnd.ms-cab-compressed": {
	source: "iana",
	extensions: [
		"cab"
	]
},
	"application/vnd.ms-color.iccprofile": {
	source: "apache"
},
	"application/vnd.ms-excel": {
	source: "iana",
	compressible: false,
	extensions: [
		"xls",
		"xlm",
		"xla",
		"xlc",
		"xlt",
		"xlw"
	]
},
	"application/vnd.ms-excel.addin.macroenabled.12": {
	source: "iana",
	extensions: [
		"xlam"
	]
},
	"application/vnd.ms-excel.sheet.binary.macroenabled.12": {
	source: "iana",
	extensions: [
		"xlsb"
	]
},
	"application/vnd.ms-excel.sheet.macroenabled.12": {
	source: "iana",
	extensions: [
		"xlsm"
	]
},
	"application/vnd.ms-excel.template.macroenabled.12": {
	source: "iana",
	extensions: [
		"xltm"
	]
},
	"application/vnd.ms-fontobject": {
	source: "iana",
	compressible: true,
	extensions: [
		"eot"
	]
},
	"application/vnd.ms-htmlhelp": {
	source: "iana",
	extensions: [
		"chm"
	]
},
	"application/vnd.ms-ims": {
	source: "iana",
	extensions: [
		"ims"
	]
},
	"application/vnd.ms-lrm": {
	source: "iana",
	extensions: [
		"lrm"
	]
},
	"application/vnd.ms-office.activex+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.ms-officetheme": {
	source: "iana",
	extensions: [
		"thmx"
	]
},
	"application/vnd.ms-opentype": {
	source: "apache",
	compressible: true
},
	"application/vnd.ms-outlook": {
	compressible: false,
	extensions: [
		"msg"
	]
},
	"application/vnd.ms-package.obfuscated-opentype": {
	source: "apache"
},
	"application/vnd.ms-pki.seccat": {
	source: "apache",
	extensions: [
		"cat"
	]
},
	"application/vnd.ms-pki.stl": {
	source: "apache",
	extensions: [
		"stl"
	]
},
	"application/vnd.ms-playready.initiator+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.ms-powerpoint": {
	source: "iana",
	compressible: false,
	extensions: [
		"ppt",
		"pps",
		"pot"
	]
},
	"application/vnd.ms-powerpoint.addin.macroenabled.12": {
	source: "iana",
	extensions: [
		"ppam"
	]
},
	"application/vnd.ms-powerpoint.presentation.macroenabled.12": {
	source: "iana",
	extensions: [
		"pptm"
	]
},
	"application/vnd.ms-powerpoint.slide.macroenabled.12": {
	source: "iana",
	extensions: [
		"sldm"
	]
},
	"application/vnd.ms-powerpoint.slideshow.macroenabled.12": {
	source: "iana",
	extensions: [
		"ppsm"
	]
},
	"application/vnd.ms-powerpoint.template.macroenabled.12": {
	source: "iana",
	extensions: [
		"potm"
	]
},
	"application/vnd.ms-printdevicecapabilities+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.ms-printing.printticket+xml": {
	source: "apache",
	compressible: true
},
	"application/vnd.ms-printschematicket+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.ms-project": {
	source: "iana",
	extensions: [
		"mpp",
		"mpt"
	]
},
	"application/vnd.ms-tnef": {
	source: "iana"
},
	"application/vnd.ms-windows.devicepairing": {
	source: "iana"
},
	"application/vnd.ms-windows.nwprinting.oob": {
	source: "iana"
},
	"application/vnd.ms-windows.printerpairing": {
	source: "iana"
},
	"application/vnd.ms-windows.wsd.oob": {
	source: "iana"
},
	"application/vnd.ms-wmdrm.lic-chlg-req": {
	source: "iana"
},
	"application/vnd.ms-wmdrm.lic-resp": {
	source: "iana"
},
	"application/vnd.ms-wmdrm.meter-chlg-req": {
	source: "iana"
},
	"application/vnd.ms-wmdrm.meter-resp": {
	source: "iana"
},
	"application/vnd.ms-word.document.macroenabled.12": {
	source: "iana",
	extensions: [
		"docm"
	]
},
	"application/vnd.ms-word.template.macroenabled.12": {
	source: "iana",
	extensions: [
		"dotm"
	]
},
	"application/vnd.ms-works": {
	source: "iana",
	extensions: [
		"wps",
		"wks",
		"wcm",
		"wdb"
	]
},
	"application/vnd.ms-wpl": {
	source: "iana",
	extensions: [
		"wpl"
	]
},
	"application/vnd.ms-xpsdocument": {
	source: "iana",
	compressible: false,
	extensions: [
		"xps"
	]
},
	"application/vnd.msa-disk-image": {
	source: "iana"
},
	"application/vnd.mseq": {
	source: "iana",
	extensions: [
		"mseq"
	]
},
	"application/vnd.msign": {
	source: "iana"
},
	"application/vnd.multiad.creator": {
	source: "iana"
},
	"application/vnd.multiad.creator.cif": {
	source: "iana"
},
	"application/vnd.music-niff": {
	source: "iana"
},
	"application/vnd.musician": {
	source: "iana",
	extensions: [
		"mus"
	]
},
	"application/vnd.muvee.style": {
	source: "iana",
	extensions: [
		"msty"
	]
},
	"application/vnd.mynfc": {
	source: "iana",
	extensions: [
		"taglet"
	]
},
	"application/vnd.nacamar.ybrid+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.ncd.control": {
	source: "iana"
},
	"application/vnd.ncd.reference": {
	source: "iana"
},
	"application/vnd.nearst.inv+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.nebumind.line": {
	source: "iana"
},
	"application/vnd.nervana": {
	source: "iana"
},
	"application/vnd.netfpx": {
	source: "iana"
},
	"application/vnd.neurolanguage.nlu": {
	source: "iana",
	extensions: [
		"nlu"
	]
},
	"application/vnd.nimn": {
	source: "iana"
},
	"application/vnd.nintendo.nitro.rom": {
	source: "iana"
},
	"application/vnd.nintendo.snes.rom": {
	source: "iana"
},
	"application/vnd.nitf": {
	source: "iana",
	extensions: [
		"ntf",
		"nitf"
	]
},
	"application/vnd.noblenet-directory": {
	source: "iana",
	extensions: [
		"nnd"
	]
},
	"application/vnd.noblenet-sealer": {
	source: "iana",
	extensions: [
		"nns"
	]
},
	"application/vnd.noblenet-web": {
	source: "iana",
	extensions: [
		"nnw"
	]
},
	"application/vnd.nokia.catalogs": {
	source: "iana"
},
	"application/vnd.nokia.conml+wbxml": {
	source: "iana"
},
	"application/vnd.nokia.conml+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.nokia.iptv.config+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.nokia.isds-radio-presets": {
	source: "iana"
},
	"application/vnd.nokia.landmark+wbxml": {
	source: "iana"
},
	"application/vnd.nokia.landmark+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.nokia.landmarkcollection+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.nokia.n-gage.ac+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"ac"
	]
},
	"application/vnd.nokia.n-gage.data": {
	source: "iana",
	extensions: [
		"ngdat"
	]
},
	"application/vnd.nokia.n-gage.symbian.install": {
	source: "iana",
	extensions: [
		"n-gage"
	]
},
	"application/vnd.nokia.ncd": {
	source: "iana"
},
	"application/vnd.nokia.pcd+wbxml": {
	source: "iana"
},
	"application/vnd.nokia.pcd+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.nokia.radio-preset": {
	source: "iana",
	extensions: [
		"rpst"
	]
},
	"application/vnd.nokia.radio-presets": {
	source: "iana",
	extensions: [
		"rpss"
	]
},
	"application/vnd.novadigm.edm": {
	source: "iana",
	extensions: [
		"edm"
	]
},
	"application/vnd.novadigm.edx": {
	source: "iana",
	extensions: [
		"edx"
	]
},
	"application/vnd.novadigm.ext": {
	source: "iana",
	extensions: [
		"ext"
	]
},
	"application/vnd.ntt-local.content-share": {
	source: "iana"
},
	"application/vnd.ntt-local.file-transfer": {
	source: "iana"
},
	"application/vnd.ntt-local.ogw_remote-access": {
	source: "iana"
},
	"application/vnd.ntt-local.sip-ta_remote": {
	source: "iana"
},
	"application/vnd.ntt-local.sip-ta_tcp_stream": {
	source: "iana"
},
	"application/vnd.oasis.opendocument.chart": {
	source: "iana",
	extensions: [
		"odc"
	]
},
	"application/vnd.oasis.opendocument.chart-template": {
	source: "iana",
	extensions: [
		"otc"
	]
},
	"application/vnd.oasis.opendocument.database": {
	source: "iana",
	extensions: [
		"odb"
	]
},
	"application/vnd.oasis.opendocument.formula": {
	source: "iana",
	extensions: [
		"odf"
	]
},
	"application/vnd.oasis.opendocument.formula-template": {
	source: "iana",
	extensions: [
		"odft"
	]
},
	"application/vnd.oasis.opendocument.graphics": {
	source: "iana",
	compressible: false,
	extensions: [
		"odg"
	]
},
	"application/vnd.oasis.opendocument.graphics-template": {
	source: "iana",
	extensions: [
		"otg"
	]
},
	"application/vnd.oasis.opendocument.image": {
	source: "iana",
	extensions: [
		"odi"
	]
},
	"application/vnd.oasis.opendocument.image-template": {
	source: "iana",
	extensions: [
		"oti"
	]
},
	"application/vnd.oasis.opendocument.presentation": {
	source: "iana",
	compressible: false,
	extensions: [
		"odp"
	]
},
	"application/vnd.oasis.opendocument.presentation-template": {
	source: "iana",
	extensions: [
		"otp"
	]
},
	"application/vnd.oasis.opendocument.spreadsheet": {
	source: "iana",
	compressible: false,
	extensions: [
		"ods"
	]
},
	"application/vnd.oasis.opendocument.spreadsheet-template": {
	source: "iana",
	extensions: [
		"ots"
	]
},
	"application/vnd.oasis.opendocument.text": {
	source: "iana",
	compressible: false,
	extensions: [
		"odt"
	]
},
	"application/vnd.oasis.opendocument.text-master": {
	source: "iana",
	extensions: [
		"odm"
	]
},
	"application/vnd.oasis.opendocument.text-template": {
	source: "iana",
	extensions: [
		"ott"
	]
},
	"application/vnd.oasis.opendocument.text-web": {
	source: "iana",
	extensions: [
		"oth"
	]
},
	"application/vnd.obn": {
	source: "iana"
},
	"application/vnd.ocf+cbor": {
	source: "iana"
},
	"application/vnd.oci.image.manifest.v1+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.oftn.l10n+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.oipf.contentaccessdownload+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oipf.contentaccessstreaming+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oipf.cspg-hexbinary": {
	source: "iana"
},
	"application/vnd.oipf.dae.svg+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oipf.dae.xhtml+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oipf.mippvcontrolmessage+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oipf.pae.gem": {
	source: "iana"
},
	"application/vnd.oipf.spdiscovery+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oipf.spdlist+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oipf.ueprofile+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oipf.userprofile+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.olpc-sugar": {
	source: "iana",
	extensions: [
		"xo"
	]
},
	"application/vnd.oma-scws-config": {
	source: "iana"
},
	"application/vnd.oma-scws-http-request": {
	source: "iana"
},
	"application/vnd.oma-scws-http-response": {
	source: "iana"
},
	"application/vnd.oma.bcast.associated-procedure-parameter+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.bcast.drm-trigger+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.bcast.imd+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.bcast.ltkm": {
	source: "iana"
},
	"application/vnd.oma.bcast.notification+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.bcast.provisioningtrigger": {
	source: "iana"
},
	"application/vnd.oma.bcast.sgboot": {
	source: "iana"
},
	"application/vnd.oma.bcast.sgdd+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.bcast.sgdu": {
	source: "iana"
},
	"application/vnd.oma.bcast.simple-symbol-container": {
	source: "iana"
},
	"application/vnd.oma.bcast.smartcard-trigger+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.bcast.sprov+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.bcast.stkm": {
	source: "iana"
},
	"application/vnd.oma.cab-address-book+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.cab-feature-handler+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.cab-pcc+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.cab-subs-invite+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.cab-user-prefs+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.dcd": {
	source: "iana"
},
	"application/vnd.oma.dcdc": {
	source: "iana"
},
	"application/vnd.oma.dd2+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"dd2"
	]
},
	"application/vnd.oma.drm.risd+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.group-usage-list+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.lwm2m+cbor": {
	source: "iana"
},
	"application/vnd.oma.lwm2m+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.lwm2m+tlv": {
	source: "iana"
},
	"application/vnd.oma.pal+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.poc.detailed-progress-report+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.poc.final-report+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.poc.groups+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.poc.invocation-descriptor+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.poc.optimized-progress-report+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.push": {
	source: "iana"
},
	"application/vnd.oma.scidm.messages+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oma.xcap-directory+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.omads-email+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/vnd.omads-file+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/vnd.omads-folder+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/vnd.omaloc-supl-init": {
	source: "iana"
},
	"application/vnd.onepager": {
	source: "iana"
},
	"application/vnd.onepagertamp": {
	source: "iana"
},
	"application/vnd.onepagertamx": {
	source: "iana"
},
	"application/vnd.onepagertat": {
	source: "iana"
},
	"application/vnd.onepagertatp": {
	source: "iana"
},
	"application/vnd.onepagertatx": {
	source: "iana"
},
	"application/vnd.openblox.game+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"obgx"
	]
},
	"application/vnd.openblox.game-binary": {
	source: "iana"
},
	"application/vnd.openeye.oeb": {
	source: "iana"
},
	"application/vnd.openofficeorg.extension": {
	source: "apache",
	extensions: [
		"oxt"
	]
},
	"application/vnd.openstreetmap.data+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"osm"
	]
},
	"application/vnd.opentimestamps.ots": {
	source: "iana"
},
	"application/vnd.openxmlformats-officedocument.custom-properties+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.customxmlproperties+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.drawing+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.drawingml.chart+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.drawingml.diagramcolors+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.drawingml.diagramdata+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.drawingml.diagramlayout+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.drawingml.diagramstyle+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.extended-properties+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.commentauthors+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.comments+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.handoutmaster+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.notesmaster+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.notesslide+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.presentation": {
	source: "iana",
	compressible: false,
	extensions: [
		"pptx"
	]
},
	"application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.presprops+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.slide": {
	source: "iana",
	extensions: [
		"sldx"
	]
},
	"application/vnd.openxmlformats-officedocument.presentationml.slide+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.slidelayout+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.slidemaster+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.slideshow": {
	source: "iana",
	extensions: [
		"ppsx"
	]
},
	"application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.slideupdateinfo+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.tablestyles+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.tags+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.template": {
	source: "iana",
	extensions: [
		"potx"
	]
},
	"application/vnd.openxmlformats-officedocument.presentationml.template.main+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.presentationml.viewprops+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.calcchain+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.externallink+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcachedefinition+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcacherecords+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.pivottable+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.querytable+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.revisionheaders+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.revisionlog+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedstrings+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
	source: "iana",
	compressible: false,
	extensions: [
		"xlsx"
	]
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.sheetmetadata+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.tablesinglecells+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.template": {
	source: "iana",
	extensions: [
		"xltx"
	]
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.usernames+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.volatiledependencies+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.theme+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.themeoverride+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.vmldrawing": {
	source: "iana"
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
	source: "iana",
	compressible: false,
	extensions: [
		"docx"
	]
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.fonttable+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.template": {
	source: "iana",
	extensions: [
		"dotx"
	]
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-officedocument.wordprocessingml.websettings+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-package.core-properties+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.openxmlformats-package.relationships+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oracle.resource+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.orange.indata": {
	source: "iana"
},
	"application/vnd.osa.netdeploy": {
	source: "iana"
},
	"application/vnd.osgeo.mapguide.package": {
	source: "iana",
	extensions: [
		"mgp"
	]
},
	"application/vnd.osgi.bundle": {
	source: "iana"
},
	"application/vnd.osgi.dp": {
	source: "iana",
	extensions: [
		"dp"
	]
},
	"application/vnd.osgi.subsystem": {
	source: "iana",
	extensions: [
		"esa"
	]
},
	"application/vnd.otps.ct-kip+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.oxli.countgraph": {
	source: "iana"
},
	"application/vnd.pagerduty+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.palm": {
	source: "iana",
	extensions: [
		"pdb",
		"pqa",
		"oprc"
	]
},
	"application/vnd.panoply": {
	source: "iana"
},
	"application/vnd.paos.xml": {
	source: "iana"
},
	"application/vnd.patentdive": {
	source: "iana"
},
	"application/vnd.patientecommsdoc": {
	source: "iana"
},
	"application/vnd.pawaafile": {
	source: "iana",
	extensions: [
		"paw"
	]
},
	"application/vnd.pcos": {
	source: "iana"
},
	"application/vnd.pg.format": {
	source: "iana",
	extensions: [
		"str"
	]
},
	"application/vnd.pg.osasli": {
	source: "iana",
	extensions: [
		"ei6"
	]
},
	"application/vnd.piaccess.application-licence": {
	source: "iana"
},
	"application/vnd.picsel": {
	source: "iana",
	extensions: [
		"efif"
	]
},
	"application/vnd.pmi.widget": {
	source: "iana",
	extensions: [
		"wg"
	]
},
	"application/vnd.poc.group-advertisement+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.pocketlearn": {
	source: "iana",
	extensions: [
		"plf"
	]
},
	"application/vnd.powerbuilder6": {
	source: "iana",
	extensions: [
		"pbd"
	]
},
	"application/vnd.powerbuilder6-s": {
	source: "iana"
},
	"application/vnd.powerbuilder7": {
	source: "iana"
},
	"application/vnd.powerbuilder7-s": {
	source: "iana"
},
	"application/vnd.powerbuilder75": {
	source: "iana"
},
	"application/vnd.powerbuilder75-s": {
	source: "iana"
},
	"application/vnd.preminet": {
	source: "iana"
},
	"application/vnd.previewsystems.box": {
	source: "iana",
	extensions: [
		"box"
	]
},
	"application/vnd.proteus.magazine": {
	source: "iana",
	extensions: [
		"mgz"
	]
},
	"application/vnd.psfs": {
	source: "iana"
},
	"application/vnd.publishare-delta-tree": {
	source: "iana",
	extensions: [
		"qps"
	]
},
	"application/vnd.pvi.ptid1": {
	source: "iana",
	extensions: [
		"ptid"
	]
},
	"application/vnd.pwg-multiplexed": {
	source: "iana"
},
	"application/vnd.pwg-xhtml-print+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.qualcomm.brew-app-res": {
	source: "iana"
},
	"application/vnd.quarantainenet": {
	source: "iana"
},
	"application/vnd.quark.quarkxpress": {
	source: "iana",
	extensions: [
		"qxd",
		"qxt",
		"qwd",
		"qwt",
		"qxl",
		"qxb"
	]
},
	"application/vnd.quobject-quoxdocument": {
	source: "iana"
},
	"application/vnd.radisys.moml+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml-audit+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml-audit-conf+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml-audit-conn+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml-audit-dialog+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml-audit-stream+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml-conf+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml-dialog+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml-dialog-base+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml-dialog-fax-detect+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml-dialog-fax-sendrecv+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml-dialog-group+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml-dialog-speech+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.radisys.msml-dialog-transform+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.rainstor.data": {
	source: "iana"
},
	"application/vnd.rapid": {
	source: "iana"
},
	"application/vnd.rar": {
	source: "iana",
	extensions: [
		"rar"
	]
},
	"application/vnd.realvnc.bed": {
	source: "iana",
	extensions: [
		"bed"
	]
},
	"application/vnd.recordare.musicxml": {
	source: "iana",
	extensions: [
		"mxl"
	]
},
	"application/vnd.recordare.musicxml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"musicxml"
	]
},
	"application/vnd.renlearn.rlprint": {
	source: "iana"
},
	"application/vnd.resilient.logic": {
	source: "iana"
},
	"application/vnd.restful+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.rig.cryptonote": {
	source: "iana",
	extensions: [
		"cryptonote"
	]
},
	"application/vnd.rim.cod": {
	source: "apache",
	extensions: [
		"cod"
	]
},
	"application/vnd.rn-realmedia": {
	source: "apache",
	extensions: [
		"rm"
	]
},
	"application/vnd.rn-realmedia-vbr": {
	source: "apache",
	extensions: [
		"rmvb"
	]
},
	"application/vnd.route66.link66+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"link66"
	]
},
	"application/vnd.rs-274x": {
	source: "iana"
},
	"application/vnd.ruckus.download": {
	source: "iana"
},
	"application/vnd.s3sms": {
	source: "iana"
},
	"application/vnd.sailingtracker.track": {
	source: "iana",
	extensions: [
		"st"
	]
},
	"application/vnd.sar": {
	source: "iana"
},
	"application/vnd.sbm.cid": {
	source: "iana"
},
	"application/vnd.sbm.mid2": {
	source: "iana"
},
	"application/vnd.scribus": {
	source: "iana"
},
	"application/vnd.sealed.3df": {
	source: "iana"
},
	"application/vnd.sealed.csf": {
	source: "iana"
},
	"application/vnd.sealed.doc": {
	source: "iana"
},
	"application/vnd.sealed.eml": {
	source: "iana"
},
	"application/vnd.sealed.mht": {
	source: "iana"
},
	"application/vnd.sealed.net": {
	source: "iana"
},
	"application/vnd.sealed.ppt": {
	source: "iana"
},
	"application/vnd.sealed.tiff": {
	source: "iana"
},
	"application/vnd.sealed.xls": {
	source: "iana"
},
	"application/vnd.sealedmedia.softseal.html": {
	source: "iana"
},
	"application/vnd.sealedmedia.softseal.pdf": {
	source: "iana"
},
	"application/vnd.seemail": {
	source: "iana",
	extensions: [
		"see"
	]
},
	"application/vnd.seis+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.sema": {
	source: "iana",
	extensions: [
		"sema"
	]
},
	"application/vnd.semd": {
	source: "iana",
	extensions: [
		"semd"
	]
},
	"application/vnd.semf": {
	source: "iana",
	extensions: [
		"semf"
	]
},
	"application/vnd.shade-save-file": {
	source: "iana"
},
	"application/vnd.shana.informed.formdata": {
	source: "iana",
	extensions: [
		"ifm"
	]
},
	"application/vnd.shana.informed.formtemplate": {
	source: "iana",
	extensions: [
		"itp"
	]
},
	"application/vnd.shana.informed.interchange": {
	source: "iana",
	extensions: [
		"iif"
	]
},
	"application/vnd.shana.informed.package": {
	source: "iana",
	extensions: [
		"ipk"
	]
},
	"application/vnd.shootproof+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.shopkick+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.shp": {
	source: "iana"
},
	"application/vnd.shx": {
	source: "iana"
},
	"application/vnd.sigrok.session": {
	source: "iana"
},
	"application/vnd.simtech-mindmapper": {
	source: "iana",
	extensions: [
		"twd",
		"twds"
	]
},
	"application/vnd.siren+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.smaf": {
	source: "iana",
	extensions: [
		"mmf"
	]
},
	"application/vnd.smart.notebook": {
	source: "iana"
},
	"application/vnd.smart.teacher": {
	source: "iana",
	extensions: [
		"teacher"
	]
},
	"application/vnd.snesdev-page-table": {
	source: "iana"
},
	"application/vnd.software602.filler.form+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"fo"
	]
},
	"application/vnd.software602.filler.form-xml-zip": {
	source: "iana"
},
	"application/vnd.solent.sdkm+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"sdkm",
		"sdkd"
	]
},
	"application/vnd.spotfire.dxp": {
	source: "iana",
	extensions: [
		"dxp"
	]
},
	"application/vnd.spotfire.sfs": {
	source: "iana",
	extensions: [
		"sfs"
	]
},
	"application/vnd.sqlite3": {
	source: "iana"
},
	"application/vnd.sss-cod": {
	source: "iana"
},
	"application/vnd.sss-dtf": {
	source: "iana"
},
	"application/vnd.sss-ntf": {
	source: "iana"
},
	"application/vnd.stardivision.calc": {
	source: "apache",
	extensions: [
		"sdc"
	]
},
	"application/vnd.stardivision.draw": {
	source: "apache",
	extensions: [
		"sda"
	]
},
	"application/vnd.stardivision.impress": {
	source: "apache",
	extensions: [
		"sdd"
	]
},
	"application/vnd.stardivision.math": {
	source: "apache",
	extensions: [
		"smf"
	]
},
	"application/vnd.stardivision.writer": {
	source: "apache",
	extensions: [
		"sdw",
		"vor"
	]
},
	"application/vnd.stardivision.writer-global": {
	source: "apache",
	extensions: [
		"sgl"
	]
},
	"application/vnd.stepmania.package": {
	source: "iana",
	extensions: [
		"smzip"
	]
},
	"application/vnd.stepmania.stepchart": {
	source: "iana",
	extensions: [
		"sm"
	]
},
	"application/vnd.street-stream": {
	source: "iana"
},
	"application/vnd.sun.wadl+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"wadl"
	]
},
	"application/vnd.sun.xml.calc": {
	source: "apache",
	extensions: [
		"sxc"
	]
},
	"application/vnd.sun.xml.calc.template": {
	source: "apache",
	extensions: [
		"stc"
	]
},
	"application/vnd.sun.xml.draw": {
	source: "apache",
	extensions: [
		"sxd"
	]
},
	"application/vnd.sun.xml.draw.template": {
	source: "apache",
	extensions: [
		"std"
	]
},
	"application/vnd.sun.xml.impress": {
	source: "apache",
	extensions: [
		"sxi"
	]
},
	"application/vnd.sun.xml.impress.template": {
	source: "apache",
	extensions: [
		"sti"
	]
},
	"application/vnd.sun.xml.math": {
	source: "apache",
	extensions: [
		"sxm"
	]
},
	"application/vnd.sun.xml.writer": {
	source: "apache",
	extensions: [
		"sxw"
	]
},
	"application/vnd.sun.xml.writer.global": {
	source: "apache",
	extensions: [
		"sxg"
	]
},
	"application/vnd.sun.xml.writer.template": {
	source: "apache",
	extensions: [
		"stw"
	]
},
	"application/vnd.sus-calendar": {
	source: "iana",
	extensions: [
		"sus",
		"susp"
	]
},
	"application/vnd.svd": {
	source: "iana",
	extensions: [
		"svd"
	]
},
	"application/vnd.swiftview-ics": {
	source: "iana"
},
	"application/vnd.sycle+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.syft+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.symbian.install": {
	source: "apache",
	extensions: [
		"sis",
		"sisx"
	]
},
	"application/vnd.syncml+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true,
	extensions: [
		"xsm"
	]
},
	"application/vnd.syncml.dm+wbxml": {
	source: "iana",
	charset: "UTF-8",
	extensions: [
		"bdm"
	]
},
	"application/vnd.syncml.dm+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true,
	extensions: [
		"xdm"
	]
},
	"application/vnd.syncml.dm.notification": {
	source: "iana"
},
	"application/vnd.syncml.dmddf+wbxml": {
	source: "iana"
},
	"application/vnd.syncml.dmddf+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true,
	extensions: [
		"ddf"
	]
},
	"application/vnd.syncml.dmtnds+wbxml": {
	source: "iana"
},
	"application/vnd.syncml.dmtnds+xml": {
	source: "iana",
	charset: "UTF-8",
	compressible: true
},
	"application/vnd.syncml.ds.notification": {
	source: "iana"
},
	"application/vnd.tableschema+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.tao.intent-module-archive": {
	source: "iana",
	extensions: [
		"tao"
	]
},
	"application/vnd.tcpdump.pcap": {
	source: "iana",
	extensions: [
		"pcap",
		"cap",
		"dmp"
	]
},
	"application/vnd.think-cell.ppttc+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.tmd.mediaflex.api+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.tml": {
	source: "iana"
},
	"application/vnd.tmobile-livetv": {
	source: "iana",
	extensions: [
		"tmo"
	]
},
	"application/vnd.tri.onesource": {
	source: "iana"
},
	"application/vnd.trid.tpt": {
	source: "iana",
	extensions: [
		"tpt"
	]
},
	"application/vnd.triscape.mxs": {
	source: "iana",
	extensions: [
		"mxs"
	]
},
	"application/vnd.trueapp": {
	source: "iana",
	extensions: [
		"tra"
	]
},
	"application/vnd.truedoc": {
	source: "iana"
},
	"application/vnd.ubisoft.webplayer": {
	source: "iana"
},
	"application/vnd.ufdl": {
	source: "iana",
	extensions: [
		"ufd",
		"ufdl"
	]
},
	"application/vnd.uiq.theme": {
	source: "iana",
	extensions: [
		"utz"
	]
},
	"application/vnd.umajin": {
	source: "iana",
	extensions: [
		"umj"
	]
},
	"application/vnd.unity": {
	source: "iana",
	extensions: [
		"unityweb"
	]
},
	"application/vnd.uoml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"uoml"
	]
},
	"application/vnd.uplanet.alert": {
	source: "iana"
},
	"application/vnd.uplanet.alert-wbxml": {
	source: "iana"
},
	"application/vnd.uplanet.bearer-choice": {
	source: "iana"
},
	"application/vnd.uplanet.bearer-choice-wbxml": {
	source: "iana"
},
	"application/vnd.uplanet.cacheop": {
	source: "iana"
},
	"application/vnd.uplanet.cacheop-wbxml": {
	source: "iana"
},
	"application/vnd.uplanet.channel": {
	source: "iana"
},
	"application/vnd.uplanet.channel-wbxml": {
	source: "iana"
},
	"application/vnd.uplanet.list": {
	source: "iana"
},
	"application/vnd.uplanet.list-wbxml": {
	source: "iana"
},
	"application/vnd.uplanet.listcmd": {
	source: "iana"
},
	"application/vnd.uplanet.listcmd-wbxml": {
	source: "iana"
},
	"application/vnd.uplanet.signal": {
	source: "iana"
},
	"application/vnd.uri-map": {
	source: "iana"
},
	"application/vnd.valve.source.material": {
	source: "iana"
},
	"application/vnd.vcx": {
	source: "iana",
	extensions: [
		"vcx"
	]
},
	"application/vnd.vd-study": {
	source: "iana"
},
	"application/vnd.vectorworks": {
	source: "iana"
},
	"application/vnd.vel+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.verimatrix.vcas": {
	source: "iana"
},
	"application/vnd.veritone.aion+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.veryant.thin": {
	source: "iana"
},
	"application/vnd.ves.encrypted": {
	source: "iana"
},
	"application/vnd.vidsoft.vidconference": {
	source: "iana"
},
	"application/vnd.visio": {
	source: "iana",
	extensions: [
		"vsd",
		"vst",
		"vss",
		"vsw"
	]
},
	"application/vnd.visionary": {
	source: "iana",
	extensions: [
		"vis"
	]
},
	"application/vnd.vividence.scriptfile": {
	source: "iana"
},
	"application/vnd.vsf": {
	source: "iana",
	extensions: [
		"vsf"
	]
},
	"application/vnd.wap.sic": {
	source: "iana"
},
	"application/vnd.wap.slc": {
	source: "iana"
},
	"application/vnd.wap.wbxml": {
	source: "iana",
	charset: "UTF-8",
	extensions: [
		"wbxml"
	]
},
	"application/vnd.wap.wmlc": {
	source: "iana",
	extensions: [
		"wmlc"
	]
},
	"application/vnd.wap.wmlscriptc": {
	source: "iana",
	extensions: [
		"wmlsc"
	]
},
	"application/vnd.webturbo": {
	source: "iana",
	extensions: [
		"wtb"
	]
},
	"application/vnd.wfa.dpp": {
	source: "iana"
},
	"application/vnd.wfa.p2p": {
	source: "iana"
},
	"application/vnd.wfa.wsc": {
	source: "iana"
},
	"application/vnd.windows.devicepairing": {
	source: "iana"
},
	"application/vnd.wmc": {
	source: "iana"
},
	"application/vnd.wmf.bootstrap": {
	source: "iana"
},
	"application/vnd.wolfram.mathematica": {
	source: "iana"
},
	"application/vnd.wolfram.mathematica.package": {
	source: "iana"
},
	"application/vnd.wolfram.player": {
	source: "iana",
	extensions: [
		"nbp"
	]
},
	"application/vnd.wordperfect": {
	source: "iana",
	extensions: [
		"wpd"
	]
},
	"application/vnd.wqd": {
	source: "iana",
	extensions: [
		"wqd"
	]
},
	"application/vnd.wrq-hp3000-labelled": {
	source: "iana"
},
	"application/vnd.wt.stf": {
	source: "iana",
	extensions: [
		"stf"
	]
},
	"application/vnd.wv.csp+wbxml": {
	source: "iana"
},
	"application/vnd.wv.csp+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.wv.ssp+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.xacml+json": {
	source: "iana",
	compressible: true
},
	"application/vnd.xara": {
	source: "iana",
	extensions: [
		"xar"
	]
},
	"application/vnd.xfdl": {
	source: "iana",
	extensions: [
		"xfdl"
	]
},
	"application/vnd.xfdl.webform": {
	source: "iana"
},
	"application/vnd.xmi+xml": {
	source: "iana",
	compressible: true
},
	"application/vnd.xmpie.cpkg": {
	source: "iana"
},
	"application/vnd.xmpie.dpkg": {
	source: "iana"
},
	"application/vnd.xmpie.plan": {
	source: "iana"
},
	"application/vnd.xmpie.ppkg": {
	source: "iana"
},
	"application/vnd.xmpie.xlim": {
	source: "iana"
},
	"application/vnd.yamaha.hv-dic": {
	source: "iana",
	extensions: [
		"hvd"
	]
},
	"application/vnd.yamaha.hv-script": {
	source: "iana",
	extensions: [
		"hvs"
	]
},
	"application/vnd.yamaha.hv-voice": {
	source: "iana",
	extensions: [
		"hvp"
	]
},
	"application/vnd.yamaha.openscoreformat": {
	source: "iana",
	extensions: [
		"osf"
	]
},
	"application/vnd.yamaha.openscoreformat.osfpvg+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"osfpvg"
	]
},
	"application/vnd.yamaha.remote-setup": {
	source: "iana"
},
	"application/vnd.yamaha.smaf-audio": {
	source: "iana",
	extensions: [
		"saf"
	]
},
	"application/vnd.yamaha.smaf-phrase": {
	source: "iana",
	extensions: [
		"spf"
	]
},
	"application/vnd.yamaha.through-ngn": {
	source: "iana"
},
	"application/vnd.yamaha.tunnel-udpencap": {
	source: "iana"
},
	"application/vnd.yaoweme": {
	source: "iana"
},
	"application/vnd.yellowriver-custom-menu": {
	source: "iana",
	extensions: [
		"cmp"
	]
},
	"application/vnd.youtube.yt": {
	source: "iana"
},
	"application/vnd.zul": {
	source: "iana",
	extensions: [
		"zir",
		"zirz"
	]
},
	"application/vnd.zzazz.deck+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"zaz"
	]
},
	"application/voicexml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"vxml"
	]
},
	"application/voucher-cms+json": {
	source: "iana",
	compressible: true
},
	"application/vq-rtcpxr": {
	source: "iana"
},
	"application/wasm": {
	source: "iana",
	compressible: true,
	extensions: [
		"wasm"
	]
},
	"application/watcherinfo+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"wif"
	]
},
	"application/webpush-options+json": {
	source: "iana",
	compressible: true
},
	"application/whoispp-query": {
	source: "iana"
},
	"application/whoispp-response": {
	source: "iana"
},
	"application/widget": {
	source: "iana",
	extensions: [
		"wgt"
	]
},
	"application/winhlp": {
	source: "apache",
	extensions: [
		"hlp"
	]
},
	"application/wita": {
	source: "iana"
},
	"application/wordperfect5.1": {
	source: "iana"
},
	"application/wsdl+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"wsdl"
	]
},
	"application/wspolicy+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"wspolicy"
	]
},
	"application/x-7z-compressed": {
	source: "apache",
	compressible: false,
	extensions: [
		"7z"
	]
},
	"application/x-abiword": {
	source: "apache",
	extensions: [
		"abw"
	]
},
	"application/x-ace-compressed": {
	source: "apache",
	extensions: [
		"ace"
	]
},
	"application/x-amf": {
	source: "apache"
},
	"application/x-apple-diskimage": {
	source: "apache",
	extensions: [
		"dmg"
	]
},
	"application/x-arj": {
	compressible: false,
	extensions: [
		"arj"
	]
},
	"application/x-authorware-bin": {
	source: "apache",
	extensions: [
		"aab",
		"x32",
		"u32",
		"vox"
	]
},
	"application/x-authorware-map": {
	source: "apache",
	extensions: [
		"aam"
	]
},
	"application/x-authorware-seg": {
	source: "apache",
	extensions: [
		"aas"
	]
},
	"application/x-bcpio": {
	source: "apache",
	extensions: [
		"bcpio"
	]
},
	"application/x-bdoc": {
	compressible: false,
	extensions: [
		"bdoc"
	]
},
	"application/x-bittorrent": {
	source: "apache",
	extensions: [
		"torrent"
	]
},
	"application/x-blorb": {
	source: "apache",
	extensions: [
		"blb",
		"blorb"
	]
},
	"application/x-bzip": {
	source: "apache",
	compressible: false,
	extensions: [
		"bz"
	]
},
	"application/x-bzip2": {
	source: "apache",
	compressible: false,
	extensions: [
		"bz2",
		"boz"
	]
},
	"application/x-cbr": {
	source: "apache",
	extensions: [
		"cbr",
		"cba",
		"cbt",
		"cbz",
		"cb7"
	]
},
	"application/x-cdlink": {
	source: "apache",
	extensions: [
		"vcd"
	]
},
	"application/x-cfs-compressed": {
	source: "apache",
	extensions: [
		"cfs"
	]
},
	"application/x-chat": {
	source: "apache",
	extensions: [
		"chat"
	]
},
	"application/x-chess-pgn": {
	source: "apache",
	extensions: [
		"pgn"
	]
},
	"application/x-chrome-extension": {
	extensions: [
		"crx"
	]
},
	"application/x-cocoa": {
	source: "nginx",
	extensions: [
		"cco"
	]
},
	"application/x-compress": {
	source: "apache"
},
	"application/x-conference": {
	source: "apache",
	extensions: [
		"nsc"
	]
},
	"application/x-cpio": {
	source: "apache",
	extensions: [
		"cpio"
	]
},
	"application/x-csh": {
	source: "apache",
	extensions: [
		"csh"
	]
},
	"application/x-deb": {
	compressible: false
},
	"application/x-debian-package": {
	source: "apache",
	extensions: [
		"deb",
		"udeb"
	]
},
	"application/x-dgc-compressed": {
	source: "apache",
	extensions: [
		"dgc"
	]
},
	"application/x-director": {
	source: "apache",
	extensions: [
		"dir",
		"dcr",
		"dxr",
		"cst",
		"cct",
		"cxt",
		"w3d",
		"fgd",
		"swa"
	]
},
	"application/x-doom": {
	source: "apache",
	extensions: [
		"wad"
	]
},
	"application/x-dtbncx+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"ncx"
	]
},
	"application/x-dtbook+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"dtb"
	]
},
	"application/x-dtbresource+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"res"
	]
},
	"application/x-dvi": {
	source: "apache",
	compressible: false,
	extensions: [
		"dvi"
	]
},
	"application/x-envoy": {
	source: "apache",
	extensions: [
		"evy"
	]
},
	"application/x-eva": {
	source: "apache",
	extensions: [
		"eva"
	]
},
	"application/x-font-bdf": {
	source: "apache",
	extensions: [
		"bdf"
	]
},
	"application/x-font-dos": {
	source: "apache"
},
	"application/x-font-framemaker": {
	source: "apache"
},
	"application/x-font-ghostscript": {
	source: "apache",
	extensions: [
		"gsf"
	]
},
	"application/x-font-libgrx": {
	source: "apache"
},
	"application/x-font-linux-psf": {
	source: "apache",
	extensions: [
		"psf"
	]
},
	"application/x-font-pcf": {
	source: "apache",
	extensions: [
		"pcf"
	]
},
	"application/x-font-snf": {
	source: "apache",
	extensions: [
		"snf"
	]
},
	"application/x-font-speedo": {
	source: "apache"
},
	"application/x-font-sunos-news": {
	source: "apache"
},
	"application/x-font-type1": {
	source: "apache",
	extensions: [
		"pfa",
		"pfb",
		"pfm",
		"afm"
	]
},
	"application/x-font-vfont": {
	source: "apache"
},
	"application/x-freearc": {
	source: "apache",
	extensions: [
		"arc"
	]
},
	"application/x-futuresplash": {
	source: "apache",
	extensions: [
		"spl"
	]
},
	"application/x-gca-compressed": {
	source: "apache",
	extensions: [
		"gca"
	]
},
	"application/x-glulx": {
	source: "apache",
	extensions: [
		"ulx"
	]
},
	"application/x-gnumeric": {
	source: "apache",
	extensions: [
		"gnumeric"
	]
},
	"application/x-gramps-xml": {
	source: "apache",
	extensions: [
		"gramps"
	]
},
	"application/x-gtar": {
	source: "apache",
	extensions: [
		"gtar"
	]
},
	"application/x-gzip": {
	source: "apache"
},
	"application/x-hdf": {
	source: "apache",
	extensions: [
		"hdf"
	]
},
	"application/x-httpd-php": {
	compressible: true,
	extensions: [
		"php"
	]
},
	"application/x-install-instructions": {
	source: "apache",
	extensions: [
		"install"
	]
},
	"application/x-iso9660-image": {
	source: "apache",
	extensions: [
		"iso"
	]
},
	"application/x-iwork-keynote-sffkey": {
	extensions: [
		"key"
	]
},
	"application/x-iwork-numbers-sffnumbers": {
	extensions: [
		"numbers"
	]
},
	"application/x-iwork-pages-sffpages": {
	extensions: [
		"pages"
	]
},
	"application/x-java-archive-diff": {
	source: "nginx",
	extensions: [
		"jardiff"
	]
},
	"application/x-java-jnlp-file": {
	source: "apache",
	compressible: false,
	extensions: [
		"jnlp"
	]
},
	"application/x-javascript": {
	compressible: true
},
	"application/x-keepass2": {
	extensions: [
		"kdbx"
	]
},
	"application/x-latex": {
	source: "apache",
	compressible: false,
	extensions: [
		"latex"
	]
},
	"application/x-lua-bytecode": {
	extensions: [
		"luac"
	]
},
	"application/x-lzh-compressed": {
	source: "apache",
	extensions: [
		"lzh",
		"lha"
	]
},
	"application/x-makeself": {
	source: "nginx",
	extensions: [
		"run"
	]
},
	"application/x-mie": {
	source: "apache",
	extensions: [
		"mie"
	]
},
	"application/x-mobipocket-ebook": {
	source: "apache",
	extensions: [
		"prc",
		"mobi"
	]
},
	"application/x-mpegurl": {
	compressible: false
},
	"application/x-ms-application": {
	source: "apache",
	extensions: [
		"application"
	]
},
	"application/x-ms-shortcut": {
	source: "apache",
	extensions: [
		"lnk"
	]
},
	"application/x-ms-wmd": {
	source: "apache",
	extensions: [
		"wmd"
	]
},
	"application/x-ms-wmz": {
	source: "apache",
	extensions: [
		"wmz"
	]
},
	"application/x-ms-xbap": {
	source: "apache",
	extensions: [
		"xbap"
	]
},
	"application/x-msaccess": {
	source: "apache",
	extensions: [
		"mdb"
	]
},
	"application/x-msbinder": {
	source: "apache",
	extensions: [
		"obd"
	]
},
	"application/x-mscardfile": {
	source: "apache",
	extensions: [
		"crd"
	]
},
	"application/x-msclip": {
	source: "apache",
	extensions: [
		"clp"
	]
},
	"application/x-msdos-program": {
	extensions: [
		"exe"
	]
},
	"application/x-msdownload": {
	source: "apache",
	extensions: [
		"exe",
		"dll",
		"com",
		"bat",
		"msi"
	]
},
	"application/x-msmediaview": {
	source: "apache",
	extensions: [
		"mvb",
		"m13",
		"m14"
	]
},
	"application/x-msmetafile": {
	source: "apache",
	extensions: [
		"wmf",
		"wmz",
		"emf",
		"emz"
	]
},
	"application/x-msmoney": {
	source: "apache",
	extensions: [
		"mny"
	]
},
	"application/x-mspublisher": {
	source: "apache",
	extensions: [
		"pub"
	]
},
	"application/x-msschedule": {
	source: "apache",
	extensions: [
		"scd"
	]
},
	"application/x-msterminal": {
	source: "apache",
	extensions: [
		"trm"
	]
},
	"application/x-mswrite": {
	source: "apache",
	extensions: [
		"wri"
	]
},
	"application/x-netcdf": {
	source: "apache",
	extensions: [
		"nc",
		"cdf"
	]
},
	"application/x-ns-proxy-autoconfig": {
	compressible: true,
	extensions: [
		"pac"
	]
},
	"application/x-nzb": {
	source: "apache",
	extensions: [
		"nzb"
	]
},
	"application/x-perl": {
	source: "nginx",
	extensions: [
		"pl",
		"pm"
	]
},
	"application/x-pilot": {
	source: "nginx",
	extensions: [
		"prc",
		"pdb"
	]
},
	"application/x-pkcs12": {
	source: "apache",
	compressible: false,
	extensions: [
		"p12",
		"pfx"
	]
},
	"application/x-pkcs7-certificates": {
	source: "apache",
	extensions: [
		"p7b",
		"spc"
	]
},
	"application/x-pkcs7-certreqresp": {
	source: "apache",
	extensions: [
		"p7r"
	]
},
	"application/x-pki-message": {
	source: "iana"
},
	"application/x-rar-compressed": {
	source: "apache",
	compressible: false,
	extensions: [
		"rar"
	]
},
	"application/x-redhat-package-manager": {
	source: "nginx",
	extensions: [
		"rpm"
	]
},
	"application/x-research-info-systems": {
	source: "apache",
	extensions: [
		"ris"
	]
},
	"application/x-sea": {
	source: "nginx",
	extensions: [
		"sea"
	]
},
	"application/x-sh": {
	source: "apache",
	compressible: true,
	extensions: [
		"sh"
	]
},
	"application/x-shar": {
	source: "apache",
	extensions: [
		"shar"
	]
},
	"application/x-shockwave-flash": {
	source: "apache",
	compressible: false,
	extensions: [
		"swf"
	]
},
	"application/x-silverlight-app": {
	source: "apache",
	extensions: [
		"xap"
	]
},
	"application/x-sql": {
	source: "apache",
	extensions: [
		"sql"
	]
},
	"application/x-stuffit": {
	source: "apache",
	compressible: false,
	extensions: [
		"sit"
	]
},
	"application/x-stuffitx": {
	source: "apache",
	extensions: [
		"sitx"
	]
},
	"application/x-subrip": {
	source: "apache",
	extensions: [
		"srt"
	]
},
	"application/x-sv4cpio": {
	source: "apache",
	extensions: [
		"sv4cpio"
	]
},
	"application/x-sv4crc": {
	source: "apache",
	extensions: [
		"sv4crc"
	]
},
	"application/x-t3vm-image": {
	source: "apache",
	extensions: [
		"t3"
	]
},
	"application/x-tads": {
	source: "apache",
	extensions: [
		"gam"
	]
},
	"application/x-tar": {
	source: "apache",
	compressible: true,
	extensions: [
		"tar"
	]
},
	"application/x-tcl": {
	source: "apache",
	extensions: [
		"tcl",
		"tk"
	]
},
	"application/x-tex": {
	source: "apache",
	extensions: [
		"tex"
	]
},
	"application/x-tex-tfm": {
	source: "apache",
	extensions: [
		"tfm"
	]
},
	"application/x-texinfo": {
	source: "apache",
	extensions: [
		"texinfo",
		"texi"
	]
},
	"application/x-tgif": {
	source: "apache",
	extensions: [
		"obj"
	]
},
	"application/x-ustar": {
	source: "apache",
	extensions: [
		"ustar"
	]
},
	"application/x-virtualbox-hdd": {
	compressible: true,
	extensions: [
		"hdd"
	]
},
	"application/x-virtualbox-ova": {
	compressible: true,
	extensions: [
		"ova"
	]
},
	"application/x-virtualbox-ovf": {
	compressible: true,
	extensions: [
		"ovf"
	]
},
	"application/x-virtualbox-vbox": {
	compressible: true,
	extensions: [
		"vbox"
	]
},
	"application/x-virtualbox-vbox-extpack": {
	compressible: false,
	extensions: [
		"vbox-extpack"
	]
},
	"application/x-virtualbox-vdi": {
	compressible: true,
	extensions: [
		"vdi"
	]
},
	"application/x-virtualbox-vhd": {
	compressible: true,
	extensions: [
		"vhd"
	]
},
	"application/x-virtualbox-vmdk": {
	compressible: true,
	extensions: [
		"vmdk"
	]
},
	"application/x-wais-source": {
	source: "apache",
	extensions: [
		"src"
	]
},
	"application/x-web-app-manifest+json": {
	compressible: true,
	extensions: [
		"webapp"
	]
},
	"application/x-www-form-urlencoded": {
	source: "iana",
	compressible: true
},
	"application/x-x509-ca-cert": {
	source: "iana",
	extensions: [
		"der",
		"crt",
		"pem"
	]
},
	"application/x-x509-ca-ra-cert": {
	source: "iana"
},
	"application/x-x509-next-ca-cert": {
	source: "iana"
},
	"application/x-xfig": {
	source: "apache",
	extensions: [
		"fig"
	]
},
	"application/x-xliff+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"xlf"
	]
},
	"application/x-xpinstall": {
	source: "apache",
	compressible: false,
	extensions: [
		"xpi"
	]
},
	"application/x-xz": {
	source: "apache",
	extensions: [
		"xz"
	]
},
	"application/x-zmachine": {
	source: "apache",
	extensions: [
		"z1",
		"z2",
		"z3",
		"z4",
		"z5",
		"z6",
		"z7",
		"z8"
	]
},
	"application/x400-bp": {
	source: "iana"
},
	"application/xacml+xml": {
	source: "iana",
	compressible: true
},
	"application/xaml+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"xaml"
	]
},
	"application/xcap-att+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xav"
	]
},
	"application/xcap-caps+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xca"
	]
},
	"application/xcap-diff+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xdf"
	]
},
	"application/xcap-el+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xel"
	]
},
	"application/xcap-error+xml": {
	source: "iana",
	compressible: true
},
	"application/xcap-ns+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xns"
	]
},
	"application/xcon-conference-info+xml": {
	source: "iana",
	compressible: true
},
	"application/xcon-conference-info-diff+xml": {
	source: "iana",
	compressible: true
},
	"application/xenc+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xenc"
	]
},
	"application/xhtml+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xhtml",
		"xht"
	]
},
	"application/xhtml-voice+xml": {
	source: "apache",
	compressible: true
},
	"application/xliff+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xlf"
	]
},
	"application/xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xml",
		"xsl",
		"xsd",
		"rng"
	]
},
	"application/xml-dtd": {
	source: "iana",
	compressible: true,
	extensions: [
		"dtd"
	]
},
	"application/xml-external-parsed-entity": {
	source: "iana"
},
	"application/xml-patch+xml": {
	source: "iana",
	compressible: true
},
	"application/xmpp+xml": {
	source: "iana",
	compressible: true
},
	"application/xop+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xop"
	]
},
	"application/xproc+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"xpl"
	]
},
	"application/xslt+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xsl",
		"xslt"
	]
},
	"application/xspf+xml": {
	source: "apache",
	compressible: true,
	extensions: [
		"xspf"
	]
},
	"application/xv+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"mxml",
		"xhvml",
		"xvml",
		"xvm"
	]
},
	"application/yang": {
	source: "iana",
	extensions: [
		"yang"
	]
},
	"application/yang-data+json": {
	source: "iana",
	compressible: true
},
	"application/yang-data+xml": {
	source: "iana",
	compressible: true
},
	"application/yang-patch+json": {
	source: "iana",
	compressible: true
},
	"application/yang-patch+xml": {
	source: "iana",
	compressible: true
},
	"application/yin+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"yin"
	]
},
	"application/zip": {
	source: "iana",
	compressible: false,
	extensions: [
		"zip"
	]
},
	"application/zlib": {
	source: "iana"
},
	"application/zstd": {
	source: "iana"
},
	"audio/1d-interleaved-parityfec": {
	source: "iana"
},
	"audio/32kadpcm": {
	source: "iana"
},
	"audio/3gpp": {
	source: "iana",
	compressible: false,
	extensions: [
		"3gpp"
	]
},
	"audio/3gpp2": {
	source: "iana"
},
	"audio/aac": {
	source: "iana"
},
	"audio/ac3": {
	source: "iana"
},
	"audio/adpcm": {
	source: "apache",
	extensions: [
		"adp"
	]
},
	"audio/amr": {
	source: "iana",
	extensions: [
		"amr"
	]
},
	"audio/amr-wb": {
	source: "iana"
},
	"audio/amr-wb+": {
	source: "iana"
},
	"audio/aptx": {
	source: "iana"
},
	"audio/asc": {
	source: "iana"
},
	"audio/atrac-advanced-lossless": {
	source: "iana"
},
	"audio/atrac-x": {
	source: "iana"
},
	"audio/atrac3": {
	source: "iana"
},
	"audio/basic": {
	source: "iana",
	compressible: false,
	extensions: [
		"au",
		"snd"
	]
},
	"audio/bv16": {
	source: "iana"
},
	"audio/bv32": {
	source: "iana"
},
	"audio/clearmode": {
	source: "iana"
},
	"audio/cn": {
	source: "iana"
},
	"audio/dat12": {
	source: "iana"
},
	"audio/dls": {
	source: "iana"
},
	"audio/dsr-es201108": {
	source: "iana"
},
	"audio/dsr-es202050": {
	source: "iana"
},
	"audio/dsr-es202211": {
	source: "iana"
},
	"audio/dsr-es202212": {
	source: "iana"
},
	"audio/dv": {
	source: "iana"
},
	"audio/dvi4": {
	source: "iana"
},
	"audio/eac3": {
	source: "iana"
},
	"audio/encaprtp": {
	source: "iana"
},
	"audio/evrc": {
	source: "iana"
},
	"audio/evrc-qcp": {
	source: "iana"
},
	"audio/evrc0": {
	source: "iana"
},
	"audio/evrc1": {
	source: "iana"
},
	"audio/evrcb": {
	source: "iana"
},
	"audio/evrcb0": {
	source: "iana"
},
	"audio/evrcb1": {
	source: "iana"
},
	"audio/evrcnw": {
	source: "iana"
},
	"audio/evrcnw0": {
	source: "iana"
},
	"audio/evrcnw1": {
	source: "iana"
},
	"audio/evrcwb": {
	source: "iana"
},
	"audio/evrcwb0": {
	source: "iana"
},
	"audio/evrcwb1": {
	source: "iana"
},
	"audio/evs": {
	source: "iana"
},
	"audio/flexfec": {
	source: "iana"
},
	"audio/fwdred": {
	source: "iana"
},
	"audio/g711-0": {
	source: "iana"
},
	"audio/g719": {
	source: "iana"
},
	"audio/g722": {
	source: "iana"
},
	"audio/g7221": {
	source: "iana"
},
	"audio/g723": {
	source: "iana"
},
	"audio/g726-16": {
	source: "iana"
},
	"audio/g726-24": {
	source: "iana"
},
	"audio/g726-32": {
	source: "iana"
},
	"audio/g726-40": {
	source: "iana"
},
	"audio/g728": {
	source: "iana"
},
	"audio/g729": {
	source: "iana"
},
	"audio/g7291": {
	source: "iana"
},
	"audio/g729d": {
	source: "iana"
},
	"audio/g729e": {
	source: "iana"
},
	"audio/gsm": {
	source: "iana"
},
	"audio/gsm-efr": {
	source: "iana"
},
	"audio/gsm-hr-08": {
	source: "iana"
},
	"audio/ilbc": {
	source: "iana"
},
	"audio/ip-mr_v2.5": {
	source: "iana"
},
	"audio/isac": {
	source: "apache"
},
	"audio/l16": {
	source: "iana"
},
	"audio/l20": {
	source: "iana"
},
	"audio/l24": {
	source: "iana",
	compressible: false
},
	"audio/l8": {
	source: "iana"
},
	"audio/lpc": {
	source: "iana"
},
	"audio/melp": {
	source: "iana"
},
	"audio/melp1200": {
	source: "iana"
},
	"audio/melp2400": {
	source: "iana"
},
	"audio/melp600": {
	source: "iana"
},
	"audio/mhas": {
	source: "iana"
},
	"audio/midi": {
	source: "apache",
	extensions: [
		"mid",
		"midi",
		"kar",
		"rmi"
	]
},
	"audio/mobile-xmf": {
	source: "iana",
	extensions: [
		"mxmf"
	]
},
	"audio/mp3": {
	compressible: false,
	extensions: [
		"mp3"
	]
},
	"audio/mp4": {
	source: "iana",
	compressible: false,
	extensions: [
		"m4a",
		"mp4a"
	]
},
	"audio/mp4a-latm": {
	source: "iana"
},
	"audio/mpa": {
	source: "iana"
},
	"audio/mpa-robust": {
	source: "iana"
},
	"audio/mpeg": {
	source: "iana",
	compressible: false,
	extensions: [
		"mpga",
		"mp2",
		"mp2a",
		"mp3",
		"m2a",
		"m3a"
	]
},
	"audio/mpeg4-generic": {
	source: "iana"
},
	"audio/musepack": {
	source: "apache"
},
	"audio/ogg": {
	source: "iana",
	compressible: false,
	extensions: [
		"oga",
		"ogg",
		"spx",
		"opus"
	]
},
	"audio/opus": {
	source: "iana"
},
	"audio/parityfec": {
	source: "iana"
},
	"audio/pcma": {
	source: "iana"
},
	"audio/pcma-wb": {
	source: "iana"
},
	"audio/pcmu": {
	source: "iana"
},
	"audio/pcmu-wb": {
	source: "iana"
},
	"audio/prs.sid": {
	source: "iana"
},
	"audio/qcelp": {
	source: "iana"
},
	"audio/raptorfec": {
	source: "iana"
},
	"audio/red": {
	source: "iana"
},
	"audio/rtp-enc-aescm128": {
	source: "iana"
},
	"audio/rtp-midi": {
	source: "iana"
},
	"audio/rtploopback": {
	source: "iana"
},
	"audio/rtx": {
	source: "iana"
},
	"audio/s3m": {
	source: "apache",
	extensions: [
		"s3m"
	]
},
	"audio/scip": {
	source: "iana"
},
	"audio/silk": {
	source: "apache",
	extensions: [
		"sil"
	]
},
	"audio/smv": {
	source: "iana"
},
	"audio/smv-qcp": {
	source: "iana"
},
	"audio/smv0": {
	source: "iana"
},
	"audio/sofa": {
	source: "iana"
},
	"audio/sp-midi": {
	source: "iana"
},
	"audio/speex": {
	source: "iana"
},
	"audio/t140c": {
	source: "iana"
},
	"audio/t38": {
	source: "iana"
},
	"audio/telephone-event": {
	source: "iana"
},
	"audio/tetra_acelp": {
	source: "iana"
},
	"audio/tetra_acelp_bb": {
	source: "iana"
},
	"audio/tone": {
	source: "iana"
},
	"audio/tsvcis": {
	source: "iana"
},
	"audio/uemclip": {
	source: "iana"
},
	"audio/ulpfec": {
	source: "iana"
},
	"audio/usac": {
	source: "iana"
},
	"audio/vdvi": {
	source: "iana"
},
	"audio/vmr-wb": {
	source: "iana"
},
	"audio/vnd.3gpp.iufp": {
	source: "iana"
},
	"audio/vnd.4sb": {
	source: "iana"
},
	"audio/vnd.audiokoz": {
	source: "iana"
},
	"audio/vnd.celp": {
	source: "iana"
},
	"audio/vnd.cisco.nse": {
	source: "iana"
},
	"audio/vnd.cmles.radio-events": {
	source: "iana"
},
	"audio/vnd.cns.anp1": {
	source: "iana"
},
	"audio/vnd.cns.inf1": {
	source: "iana"
},
	"audio/vnd.dece.audio": {
	source: "iana",
	extensions: [
		"uva",
		"uvva"
	]
},
	"audio/vnd.digital-winds": {
	source: "iana",
	extensions: [
		"eol"
	]
},
	"audio/vnd.dlna.adts": {
	source: "iana"
},
	"audio/vnd.dolby.heaac.1": {
	source: "iana"
},
	"audio/vnd.dolby.heaac.2": {
	source: "iana"
},
	"audio/vnd.dolby.mlp": {
	source: "iana"
},
	"audio/vnd.dolby.mps": {
	source: "iana"
},
	"audio/vnd.dolby.pl2": {
	source: "iana"
},
	"audio/vnd.dolby.pl2x": {
	source: "iana"
},
	"audio/vnd.dolby.pl2z": {
	source: "iana"
},
	"audio/vnd.dolby.pulse.1": {
	source: "iana"
},
	"audio/vnd.dra": {
	source: "iana",
	extensions: [
		"dra"
	]
},
	"audio/vnd.dts": {
	source: "iana",
	extensions: [
		"dts"
	]
},
	"audio/vnd.dts.hd": {
	source: "iana",
	extensions: [
		"dtshd"
	]
},
	"audio/vnd.dts.uhd": {
	source: "iana"
},
	"audio/vnd.dvb.file": {
	source: "iana"
},
	"audio/vnd.everad.plj": {
	source: "iana"
},
	"audio/vnd.hns.audio": {
	source: "iana"
},
	"audio/vnd.lucent.voice": {
	source: "iana",
	extensions: [
		"lvp"
	]
},
	"audio/vnd.ms-playready.media.pya": {
	source: "iana",
	extensions: [
		"pya"
	]
},
	"audio/vnd.nokia.mobile-xmf": {
	source: "iana"
},
	"audio/vnd.nortel.vbk": {
	source: "iana"
},
	"audio/vnd.nuera.ecelp4800": {
	source: "iana",
	extensions: [
		"ecelp4800"
	]
},
	"audio/vnd.nuera.ecelp7470": {
	source: "iana",
	extensions: [
		"ecelp7470"
	]
},
	"audio/vnd.nuera.ecelp9600": {
	source: "iana",
	extensions: [
		"ecelp9600"
	]
},
	"audio/vnd.octel.sbc": {
	source: "iana"
},
	"audio/vnd.presonus.multitrack": {
	source: "iana"
},
	"audio/vnd.qcelp": {
	source: "iana"
},
	"audio/vnd.rhetorex.32kadpcm": {
	source: "iana"
},
	"audio/vnd.rip": {
	source: "iana",
	extensions: [
		"rip"
	]
},
	"audio/vnd.rn-realaudio": {
	compressible: false
},
	"audio/vnd.sealedmedia.softseal.mpeg": {
	source: "iana"
},
	"audio/vnd.vmx.cvsd": {
	source: "iana"
},
	"audio/vnd.wave": {
	compressible: false
},
	"audio/vorbis": {
	source: "iana",
	compressible: false
},
	"audio/vorbis-config": {
	source: "iana"
},
	"audio/wav": {
	compressible: false,
	extensions: [
		"wav"
	]
},
	"audio/wave": {
	compressible: false,
	extensions: [
		"wav"
	]
},
	"audio/webm": {
	source: "apache",
	compressible: false,
	extensions: [
		"weba"
	]
},
	"audio/x-aac": {
	source: "apache",
	compressible: false,
	extensions: [
		"aac"
	]
},
	"audio/x-aiff": {
	source: "apache",
	extensions: [
		"aif",
		"aiff",
		"aifc"
	]
},
	"audio/x-caf": {
	source: "apache",
	compressible: false,
	extensions: [
		"caf"
	]
},
	"audio/x-flac": {
	source: "apache",
	extensions: [
		"flac"
	]
},
	"audio/x-m4a": {
	source: "nginx",
	extensions: [
		"m4a"
	]
},
	"audio/x-matroska": {
	source: "apache",
	extensions: [
		"mka"
	]
},
	"audio/x-mpegurl": {
	source: "apache",
	extensions: [
		"m3u"
	]
},
	"audio/x-ms-wax": {
	source: "apache",
	extensions: [
		"wax"
	]
},
	"audio/x-ms-wma": {
	source: "apache",
	extensions: [
		"wma"
	]
},
	"audio/x-pn-realaudio": {
	source: "apache",
	extensions: [
		"ram",
		"ra"
	]
},
	"audio/x-pn-realaudio-plugin": {
	source: "apache",
	extensions: [
		"rmp"
	]
},
	"audio/x-realaudio": {
	source: "nginx",
	extensions: [
		"ra"
	]
},
	"audio/x-tta": {
	source: "apache"
},
	"audio/x-wav": {
	source: "apache",
	extensions: [
		"wav"
	]
},
	"audio/xm": {
	source: "apache",
	extensions: [
		"xm"
	]
},
	"chemical/x-cdx": {
	source: "apache",
	extensions: [
		"cdx"
	]
},
	"chemical/x-cif": {
	source: "apache",
	extensions: [
		"cif"
	]
},
	"chemical/x-cmdf": {
	source: "apache",
	extensions: [
		"cmdf"
	]
},
	"chemical/x-cml": {
	source: "apache",
	extensions: [
		"cml"
	]
},
	"chemical/x-csml": {
	source: "apache",
	extensions: [
		"csml"
	]
},
	"chemical/x-pdb": {
	source: "apache"
},
	"chemical/x-xyz": {
	source: "apache",
	extensions: [
		"xyz"
	]
},
	"font/collection": {
	source: "iana",
	extensions: [
		"ttc"
	]
},
	"font/otf": {
	source: "iana",
	compressible: true,
	extensions: [
		"otf"
	]
},
	"font/sfnt": {
	source: "iana"
},
	"font/ttf": {
	source: "iana",
	compressible: true,
	extensions: [
		"ttf"
	]
},
	"font/woff": {
	source: "iana",
	extensions: [
		"woff"
	]
},
	"font/woff2": {
	source: "iana",
	extensions: [
		"woff2"
	]
},
	"image/aces": {
	source: "iana",
	extensions: [
		"exr"
	]
},
	"image/apng": {
	compressible: false,
	extensions: [
		"apng"
	]
},
	"image/avci": {
	source: "iana",
	extensions: [
		"avci"
	]
},
	"image/avcs": {
	source: "iana",
	extensions: [
		"avcs"
	]
},
	"image/avif": {
	source: "iana",
	compressible: false,
	extensions: [
		"avif"
	]
},
	"image/bmp": {
	source: "iana",
	compressible: true,
	extensions: [
		"bmp"
	]
},
	"image/cgm": {
	source: "iana",
	extensions: [
		"cgm"
	]
},
	"image/dicom-rle": {
	source: "iana",
	extensions: [
		"drle"
	]
},
	"image/emf": {
	source: "iana",
	extensions: [
		"emf"
	]
},
	"image/fits": {
	source: "iana",
	extensions: [
		"fits"
	]
},
	"image/g3fax": {
	source: "iana",
	extensions: [
		"g3"
	]
},
	"image/gif": {
	source: "iana",
	compressible: false,
	extensions: [
		"gif"
	]
},
	"image/heic": {
	source: "iana",
	extensions: [
		"heic"
	]
},
	"image/heic-sequence": {
	source: "iana",
	extensions: [
		"heics"
	]
},
	"image/heif": {
	source: "iana",
	extensions: [
		"heif"
	]
},
	"image/heif-sequence": {
	source: "iana",
	extensions: [
		"heifs"
	]
},
	"image/hej2k": {
	source: "iana",
	extensions: [
		"hej2"
	]
},
	"image/hsj2": {
	source: "iana",
	extensions: [
		"hsj2"
	]
},
	"image/ief": {
	source: "iana",
	extensions: [
		"ief"
	]
},
	"image/jls": {
	source: "iana",
	extensions: [
		"jls"
	]
},
	"image/jp2": {
	source: "iana",
	compressible: false,
	extensions: [
		"jp2",
		"jpg2"
	]
},
	"image/jpeg": {
	source: "iana",
	compressible: false,
	extensions: [
		"jpeg",
		"jpg",
		"jpe"
	]
},
	"image/jph": {
	source: "iana",
	extensions: [
		"jph"
	]
},
	"image/jphc": {
	source: "iana",
	extensions: [
		"jhc"
	]
},
	"image/jpm": {
	source: "iana",
	compressible: false,
	extensions: [
		"jpm"
	]
},
	"image/jpx": {
	source: "iana",
	compressible: false,
	extensions: [
		"jpx",
		"jpf"
	]
},
	"image/jxr": {
	source: "iana",
	extensions: [
		"jxr"
	]
},
	"image/jxra": {
	source: "iana",
	extensions: [
		"jxra"
	]
},
	"image/jxrs": {
	source: "iana",
	extensions: [
		"jxrs"
	]
},
	"image/jxs": {
	source: "iana",
	extensions: [
		"jxs"
	]
},
	"image/jxsc": {
	source: "iana",
	extensions: [
		"jxsc"
	]
},
	"image/jxsi": {
	source: "iana",
	extensions: [
		"jxsi"
	]
},
	"image/jxss": {
	source: "iana",
	extensions: [
		"jxss"
	]
},
	"image/ktx": {
	source: "iana",
	extensions: [
		"ktx"
	]
},
	"image/ktx2": {
	source: "iana",
	extensions: [
		"ktx2"
	]
},
	"image/naplps": {
	source: "iana"
},
	"image/pjpeg": {
	compressible: false
},
	"image/png": {
	source: "iana",
	compressible: false,
	extensions: [
		"png"
	]
},
	"image/prs.btif": {
	source: "iana",
	extensions: [
		"btif"
	]
},
	"image/prs.pti": {
	source: "iana",
	extensions: [
		"pti"
	]
},
	"image/pwg-raster": {
	source: "iana"
},
	"image/sgi": {
	source: "apache",
	extensions: [
		"sgi"
	]
},
	"image/svg+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"svg",
		"svgz"
	]
},
	"image/t38": {
	source: "iana",
	extensions: [
		"t38"
	]
},
	"image/tiff": {
	source: "iana",
	compressible: false,
	extensions: [
		"tif",
		"tiff"
	]
},
	"image/tiff-fx": {
	source: "iana",
	extensions: [
		"tfx"
	]
},
	"image/vnd.adobe.photoshop": {
	source: "iana",
	compressible: true,
	extensions: [
		"psd"
	]
},
	"image/vnd.airzip.accelerator.azv": {
	source: "iana",
	extensions: [
		"azv"
	]
},
	"image/vnd.cns.inf2": {
	source: "iana"
},
	"image/vnd.dece.graphic": {
	source: "iana",
	extensions: [
		"uvi",
		"uvvi",
		"uvg",
		"uvvg"
	]
},
	"image/vnd.djvu": {
	source: "iana",
	extensions: [
		"djvu",
		"djv"
	]
},
	"image/vnd.dvb.subtitle": {
	source: "iana",
	extensions: [
		"sub"
	]
},
	"image/vnd.dwg": {
	source: "iana",
	extensions: [
		"dwg"
	]
},
	"image/vnd.dxf": {
	source: "iana",
	extensions: [
		"dxf"
	]
},
	"image/vnd.fastbidsheet": {
	source: "iana",
	extensions: [
		"fbs"
	]
},
	"image/vnd.fpx": {
	source: "iana",
	extensions: [
		"fpx"
	]
},
	"image/vnd.fst": {
	source: "iana",
	extensions: [
		"fst"
	]
},
	"image/vnd.fujixerox.edmics-mmr": {
	source: "iana",
	extensions: [
		"mmr"
	]
},
	"image/vnd.fujixerox.edmics-rlc": {
	source: "iana",
	extensions: [
		"rlc"
	]
},
	"image/vnd.globalgraphics.pgb": {
	source: "iana"
},
	"image/vnd.microsoft.icon": {
	source: "iana",
	compressible: true,
	extensions: [
		"ico"
	]
},
	"image/vnd.mix": {
	source: "iana"
},
	"image/vnd.mozilla.apng": {
	source: "iana"
},
	"image/vnd.ms-dds": {
	compressible: true,
	extensions: [
		"dds"
	]
},
	"image/vnd.ms-modi": {
	source: "iana",
	extensions: [
		"mdi"
	]
},
	"image/vnd.ms-photo": {
	source: "apache",
	extensions: [
		"wdp"
	]
},
	"image/vnd.net-fpx": {
	source: "iana",
	extensions: [
		"npx"
	]
},
	"image/vnd.pco.b16": {
	source: "iana",
	extensions: [
		"b16"
	]
},
	"image/vnd.radiance": {
	source: "iana"
},
	"image/vnd.sealed.png": {
	source: "iana"
},
	"image/vnd.sealedmedia.softseal.gif": {
	source: "iana"
},
	"image/vnd.sealedmedia.softseal.jpg": {
	source: "iana"
},
	"image/vnd.svf": {
	source: "iana"
},
	"image/vnd.tencent.tap": {
	source: "iana",
	extensions: [
		"tap"
	]
},
	"image/vnd.valve.source.texture": {
	source: "iana",
	extensions: [
		"vtf"
	]
},
	"image/vnd.wap.wbmp": {
	source: "iana",
	extensions: [
		"wbmp"
	]
},
	"image/vnd.xiff": {
	source: "iana",
	extensions: [
		"xif"
	]
},
	"image/vnd.zbrush.pcx": {
	source: "iana",
	extensions: [
		"pcx"
	]
},
	"image/webp": {
	source: "apache",
	extensions: [
		"webp"
	]
},
	"image/wmf": {
	source: "iana",
	extensions: [
		"wmf"
	]
},
	"image/x-3ds": {
	source: "apache",
	extensions: [
		"3ds"
	]
},
	"image/x-cmu-raster": {
	source: "apache",
	extensions: [
		"ras"
	]
},
	"image/x-cmx": {
	source: "apache",
	extensions: [
		"cmx"
	]
},
	"image/x-freehand": {
	source: "apache",
	extensions: [
		"fh",
		"fhc",
		"fh4",
		"fh5",
		"fh7"
	]
},
	"image/x-icon": {
	source: "apache",
	compressible: true,
	extensions: [
		"ico"
	]
},
	"image/x-jng": {
	source: "nginx",
	extensions: [
		"jng"
	]
},
	"image/x-mrsid-image": {
	source: "apache",
	extensions: [
		"sid"
	]
},
	"image/x-ms-bmp": {
	source: "nginx",
	compressible: true,
	extensions: [
		"bmp"
	]
},
	"image/x-pcx": {
	source: "apache",
	extensions: [
		"pcx"
	]
},
	"image/x-pict": {
	source: "apache",
	extensions: [
		"pic",
		"pct"
	]
},
	"image/x-portable-anymap": {
	source: "apache",
	extensions: [
		"pnm"
	]
},
	"image/x-portable-bitmap": {
	source: "apache",
	extensions: [
		"pbm"
	]
},
	"image/x-portable-graymap": {
	source: "apache",
	extensions: [
		"pgm"
	]
},
	"image/x-portable-pixmap": {
	source: "apache",
	extensions: [
		"ppm"
	]
},
	"image/x-rgb": {
	source: "apache",
	extensions: [
		"rgb"
	]
},
	"image/x-tga": {
	source: "apache",
	extensions: [
		"tga"
	]
},
	"image/x-xbitmap": {
	source: "apache",
	extensions: [
		"xbm"
	]
},
	"image/x-xcf": {
	compressible: false
},
	"image/x-xpixmap": {
	source: "apache",
	extensions: [
		"xpm"
	]
},
	"image/x-xwindowdump": {
	source: "apache",
	extensions: [
		"xwd"
	]
},
	"message/cpim": {
	source: "iana"
},
	"message/delivery-status": {
	source: "iana"
},
	"message/disposition-notification": {
	source: "iana",
	extensions: [
		"disposition-notification"
	]
},
	"message/external-body": {
	source: "iana"
},
	"message/feedback-report": {
	source: "iana"
},
	"message/global": {
	source: "iana",
	extensions: [
		"u8msg"
	]
},
	"message/global-delivery-status": {
	source: "iana",
	extensions: [
		"u8dsn"
	]
},
	"message/global-disposition-notification": {
	source: "iana",
	extensions: [
		"u8mdn"
	]
},
	"message/global-headers": {
	source: "iana",
	extensions: [
		"u8hdr"
	]
},
	"message/http": {
	source: "iana",
	compressible: false
},
	"message/imdn+xml": {
	source: "iana",
	compressible: true
},
	"message/news": {
	source: "iana"
},
	"message/partial": {
	source: "iana",
	compressible: false
},
	"message/rfc822": {
	source: "iana",
	compressible: true,
	extensions: [
		"eml",
		"mime"
	]
},
	"message/s-http": {
	source: "iana"
},
	"message/sip": {
	source: "iana"
},
	"message/sipfrag": {
	source: "iana"
},
	"message/tracking-status": {
	source: "iana"
},
	"message/vnd.si.simp": {
	source: "iana"
},
	"message/vnd.wfa.wsc": {
	source: "iana",
	extensions: [
		"wsc"
	]
},
	"model/3mf": {
	source: "iana",
	extensions: [
		"3mf"
	]
},
	"model/e57": {
	source: "iana"
},
	"model/gltf+json": {
	source: "iana",
	compressible: true,
	extensions: [
		"gltf"
	]
},
	"model/gltf-binary": {
	source: "iana",
	compressible: true,
	extensions: [
		"glb"
	]
},
	"model/iges": {
	source: "iana",
	compressible: false,
	extensions: [
		"igs",
		"iges"
	]
},
	"model/mesh": {
	source: "iana",
	compressible: false,
	extensions: [
		"msh",
		"mesh",
		"silo"
	]
},
	"model/mtl": {
	source: "iana",
	extensions: [
		"mtl"
	]
},
	"model/obj": {
	source: "iana",
	extensions: [
		"obj"
	]
},
	"model/step": {
	source: "iana"
},
	"model/step+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"stpx"
	]
},
	"model/step+zip": {
	source: "iana",
	compressible: false,
	extensions: [
		"stpz"
	]
},
	"model/step-xml+zip": {
	source: "iana",
	compressible: false,
	extensions: [
		"stpxz"
	]
},
	"model/stl": {
	source: "iana",
	extensions: [
		"stl"
	]
},
	"model/vnd.collada+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"dae"
	]
},
	"model/vnd.dwf": {
	source: "iana",
	extensions: [
		"dwf"
	]
},
	"model/vnd.flatland.3dml": {
	source: "iana"
},
	"model/vnd.gdl": {
	source: "iana",
	extensions: [
		"gdl"
	]
},
	"model/vnd.gs-gdl": {
	source: "apache"
},
	"model/vnd.gs.gdl": {
	source: "iana"
},
	"model/vnd.gtw": {
	source: "iana",
	extensions: [
		"gtw"
	]
},
	"model/vnd.moml+xml": {
	source: "iana",
	compressible: true
},
	"model/vnd.mts": {
	source: "iana",
	extensions: [
		"mts"
	]
},
	"model/vnd.opengex": {
	source: "iana",
	extensions: [
		"ogex"
	]
},
	"model/vnd.parasolid.transmit.binary": {
	source: "iana",
	extensions: [
		"x_b"
	]
},
	"model/vnd.parasolid.transmit.text": {
	source: "iana",
	extensions: [
		"x_t"
	]
},
	"model/vnd.pytha.pyox": {
	source: "iana"
},
	"model/vnd.rosette.annotated-data-model": {
	source: "iana"
},
	"model/vnd.sap.vds": {
	source: "iana",
	extensions: [
		"vds"
	]
},
	"model/vnd.usdz+zip": {
	source: "iana",
	compressible: false,
	extensions: [
		"usdz"
	]
},
	"model/vnd.valve.source.compiled-map": {
	source: "iana",
	extensions: [
		"bsp"
	]
},
	"model/vnd.vtu": {
	source: "iana",
	extensions: [
		"vtu"
	]
},
	"model/vrml": {
	source: "iana",
	compressible: false,
	extensions: [
		"wrl",
		"vrml"
	]
},
	"model/x3d+binary": {
	source: "apache",
	compressible: false,
	extensions: [
		"x3db",
		"x3dbz"
	]
},
	"model/x3d+fastinfoset": {
	source: "iana",
	extensions: [
		"x3db"
	]
},
	"model/x3d+vrml": {
	source: "apache",
	compressible: false,
	extensions: [
		"x3dv",
		"x3dvz"
	]
},
	"model/x3d+xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"x3d",
		"x3dz"
	]
},
	"model/x3d-vrml": {
	source: "iana",
	extensions: [
		"x3dv"
	]
},
	"multipart/alternative": {
	source: "iana",
	compressible: false
},
	"multipart/appledouble": {
	source: "iana"
},
	"multipart/byteranges": {
	source: "iana"
},
	"multipart/digest": {
	source: "iana"
},
	"multipart/encrypted": {
	source: "iana",
	compressible: false
},
	"multipart/form-data": {
	source: "iana",
	compressible: false
},
	"multipart/header-set": {
	source: "iana"
},
	"multipart/mixed": {
	source: "iana"
},
	"multipart/multilingual": {
	source: "iana"
},
	"multipart/parallel": {
	source: "iana"
},
	"multipart/related": {
	source: "iana",
	compressible: false
},
	"multipart/report": {
	source: "iana"
},
	"multipart/signed": {
	source: "iana",
	compressible: false
},
	"multipart/vnd.bint.med-plus": {
	source: "iana"
},
	"multipart/voice-message": {
	source: "iana"
},
	"multipart/x-mixed-replace": {
	source: "iana"
},
	"text/1d-interleaved-parityfec": {
	source: "iana"
},
	"text/cache-manifest": {
	source: "iana",
	compressible: true,
	extensions: [
		"appcache",
		"manifest"
	]
},
	"text/calendar": {
	source: "iana",
	extensions: [
		"ics",
		"ifb"
	]
},
	"text/calender": {
	compressible: true
},
	"text/cmd": {
	compressible: true
},
	"text/coffeescript": {
	extensions: [
		"coffee",
		"litcoffee"
	]
},
	"text/cql": {
	source: "iana"
},
	"text/cql-expression": {
	source: "iana"
},
	"text/cql-identifier": {
	source: "iana"
},
	"text/css": {
	source: "iana",
	charset: "UTF-8",
	compressible: true,
	extensions: [
		"css"
	]
},
	"text/csv": {
	source: "iana",
	compressible: true,
	extensions: [
		"csv"
	]
},
	"text/csv-schema": {
	source: "iana"
},
	"text/directory": {
	source: "iana"
},
	"text/dns": {
	source: "iana"
},
	"text/ecmascript": {
	source: "iana"
},
	"text/encaprtp": {
	source: "iana"
},
	"text/enriched": {
	source: "iana"
},
	"text/fhirpath": {
	source: "iana"
},
	"text/flexfec": {
	source: "iana"
},
	"text/fwdred": {
	source: "iana"
},
	"text/gff3": {
	source: "iana"
},
	"text/grammar-ref-list": {
	source: "iana"
},
	"text/html": {
	source: "iana",
	compressible: true,
	extensions: [
		"html",
		"htm",
		"shtml"
	]
},
	"text/jade": {
	extensions: [
		"jade"
	]
},
	"text/javascript": {
	source: "iana",
	compressible: true
},
	"text/jcr-cnd": {
	source: "iana"
},
	"text/jsx": {
	compressible: true,
	extensions: [
		"jsx"
	]
},
	"text/less": {
	compressible: true,
	extensions: [
		"less"
	]
},
	"text/markdown": {
	source: "iana",
	compressible: true,
	extensions: [
		"markdown",
		"md"
	]
},
	"text/mathml": {
	source: "nginx",
	extensions: [
		"mml"
	]
},
	"text/mdx": {
	compressible: true,
	extensions: [
		"mdx"
	]
},
	"text/mizar": {
	source: "iana"
},
	"text/n3": {
	source: "iana",
	charset: "UTF-8",
	compressible: true,
	extensions: [
		"n3"
	]
},
	"text/parameters": {
	source: "iana",
	charset: "UTF-8"
},
	"text/parityfec": {
	source: "iana"
},
	"text/plain": {
	source: "iana",
	compressible: true,
	extensions: [
		"txt",
		"text",
		"conf",
		"def",
		"list",
		"log",
		"in",
		"ini"
	]
},
	"text/provenance-notation": {
	source: "iana",
	charset: "UTF-8"
},
	"text/prs.fallenstein.rst": {
	source: "iana"
},
	"text/prs.lines.tag": {
	source: "iana",
	extensions: [
		"dsc"
	]
},
	"text/prs.prop.logic": {
	source: "iana"
},
	"text/raptorfec": {
	source: "iana"
},
	"text/red": {
	source: "iana"
},
	"text/rfc822-headers": {
	source: "iana"
},
	"text/richtext": {
	source: "iana",
	compressible: true,
	extensions: [
		"rtx"
	]
},
	"text/rtf": {
	source: "iana",
	compressible: true,
	extensions: [
		"rtf"
	]
},
	"text/rtp-enc-aescm128": {
	source: "iana"
},
	"text/rtploopback": {
	source: "iana"
},
	"text/rtx": {
	source: "iana"
},
	"text/sgml": {
	source: "iana",
	extensions: [
		"sgml",
		"sgm"
	]
},
	"text/shaclc": {
	source: "iana"
},
	"text/shex": {
	source: "iana",
	extensions: [
		"shex"
	]
},
	"text/slim": {
	extensions: [
		"slim",
		"slm"
	]
},
	"text/spdx": {
	source: "iana",
	extensions: [
		"spdx"
	]
},
	"text/strings": {
	source: "iana"
},
	"text/stylus": {
	extensions: [
		"stylus",
		"styl"
	]
},
	"text/t140": {
	source: "iana"
},
	"text/tab-separated-values": {
	source: "iana",
	compressible: true,
	extensions: [
		"tsv"
	]
},
	"text/troff": {
	source: "iana",
	extensions: [
		"t",
		"tr",
		"roff",
		"man",
		"me",
		"ms"
	]
},
	"text/turtle": {
	source: "iana",
	charset: "UTF-8",
	extensions: [
		"ttl"
	]
},
	"text/ulpfec": {
	source: "iana"
},
	"text/uri-list": {
	source: "iana",
	compressible: true,
	extensions: [
		"uri",
		"uris",
		"urls"
	]
},
	"text/vcard": {
	source: "iana",
	compressible: true,
	extensions: [
		"vcard"
	]
},
	"text/vnd.a": {
	source: "iana"
},
	"text/vnd.abc": {
	source: "iana"
},
	"text/vnd.ascii-art": {
	source: "iana"
},
	"text/vnd.curl": {
	source: "iana",
	extensions: [
		"curl"
	]
},
	"text/vnd.curl.dcurl": {
	source: "apache",
	extensions: [
		"dcurl"
	]
},
	"text/vnd.curl.mcurl": {
	source: "apache",
	extensions: [
		"mcurl"
	]
},
	"text/vnd.curl.scurl": {
	source: "apache",
	extensions: [
		"scurl"
	]
},
	"text/vnd.debian.copyright": {
	source: "iana",
	charset: "UTF-8"
},
	"text/vnd.dmclientscript": {
	source: "iana"
},
	"text/vnd.dvb.subtitle": {
	source: "iana",
	extensions: [
		"sub"
	]
},
	"text/vnd.esmertec.theme-descriptor": {
	source: "iana",
	charset: "UTF-8"
},
	"text/vnd.familysearch.gedcom": {
	source: "iana",
	extensions: [
		"ged"
	]
},
	"text/vnd.ficlab.flt": {
	source: "iana"
},
	"text/vnd.fly": {
	source: "iana",
	extensions: [
		"fly"
	]
},
	"text/vnd.fmi.flexstor": {
	source: "iana",
	extensions: [
		"flx"
	]
},
	"text/vnd.gml": {
	source: "iana"
},
	"text/vnd.graphviz": {
	source: "iana",
	extensions: [
		"gv"
	]
},
	"text/vnd.hans": {
	source: "iana"
},
	"text/vnd.hgl": {
	source: "iana"
},
	"text/vnd.in3d.3dml": {
	source: "iana",
	extensions: [
		"3dml"
	]
},
	"text/vnd.in3d.spot": {
	source: "iana",
	extensions: [
		"spot"
	]
},
	"text/vnd.iptc.newsml": {
	source: "iana"
},
	"text/vnd.iptc.nitf": {
	source: "iana"
},
	"text/vnd.latex-z": {
	source: "iana"
},
	"text/vnd.motorola.reflex": {
	source: "iana"
},
	"text/vnd.ms-mediapackage": {
	source: "iana"
},
	"text/vnd.net2phone.commcenter.command": {
	source: "iana"
},
	"text/vnd.radisys.msml-basic-layout": {
	source: "iana"
},
	"text/vnd.senx.warpscript": {
	source: "iana"
},
	"text/vnd.si.uricatalogue": {
	source: "iana"
},
	"text/vnd.sosi": {
	source: "iana"
},
	"text/vnd.sun.j2me.app-descriptor": {
	source: "iana",
	charset: "UTF-8",
	extensions: [
		"jad"
	]
},
	"text/vnd.trolltech.linguist": {
	source: "iana",
	charset: "UTF-8"
},
	"text/vnd.wap.si": {
	source: "iana"
},
	"text/vnd.wap.sl": {
	source: "iana"
},
	"text/vnd.wap.wml": {
	source: "iana",
	extensions: [
		"wml"
	]
},
	"text/vnd.wap.wmlscript": {
	source: "iana",
	extensions: [
		"wmls"
	]
},
	"text/vtt": {
	source: "iana",
	charset: "UTF-8",
	compressible: true,
	extensions: [
		"vtt"
	]
},
	"text/x-asm": {
	source: "apache",
	extensions: [
		"s",
		"asm"
	]
},
	"text/x-c": {
	source: "apache",
	extensions: [
		"c",
		"cc",
		"cxx",
		"cpp",
		"h",
		"hh",
		"dic"
	]
},
	"text/x-component": {
	source: "nginx",
	extensions: [
		"htc"
	]
},
	"text/x-fortran": {
	source: "apache",
	extensions: [
		"f",
		"for",
		"f77",
		"f90"
	]
},
	"text/x-gwt-rpc": {
	compressible: true
},
	"text/x-handlebars-template": {
	extensions: [
		"hbs"
	]
},
	"text/x-java-source": {
	source: "apache",
	extensions: [
		"java"
	]
},
	"text/x-jquery-tmpl": {
	compressible: true
},
	"text/x-lua": {
	extensions: [
		"lua"
	]
},
	"text/x-markdown": {
	compressible: true,
	extensions: [
		"mkd"
	]
},
	"text/x-nfo": {
	source: "apache",
	extensions: [
		"nfo"
	]
},
	"text/x-opml": {
	source: "apache",
	extensions: [
		"opml"
	]
},
	"text/x-org": {
	compressible: true,
	extensions: [
		"org"
	]
},
	"text/x-pascal": {
	source: "apache",
	extensions: [
		"p",
		"pas"
	]
},
	"text/x-processing": {
	compressible: true,
	extensions: [
		"pde"
	]
},
	"text/x-sass": {
	extensions: [
		"sass"
	]
},
	"text/x-scss": {
	extensions: [
		"scss"
	]
},
	"text/x-setext": {
	source: "apache",
	extensions: [
		"etx"
	]
},
	"text/x-sfv": {
	source: "apache",
	extensions: [
		"sfv"
	]
},
	"text/x-suse-ymp": {
	compressible: true,
	extensions: [
		"ymp"
	]
},
	"text/x-uuencode": {
	source: "apache",
	extensions: [
		"uu"
	]
},
	"text/x-vcalendar": {
	source: "apache",
	extensions: [
		"vcs"
	]
},
	"text/x-vcard": {
	source: "apache",
	extensions: [
		"vcf"
	]
},
	"text/xml": {
	source: "iana",
	compressible: true,
	extensions: [
		"xml"
	]
},
	"text/xml-external-parsed-entity": {
	source: "iana"
},
	"text/yaml": {
	compressible: true,
	extensions: [
		"yaml",
		"yml"
	]
},
	"video/1d-interleaved-parityfec": {
	source: "iana"
},
	"video/3gpp": {
	source: "iana",
	extensions: [
		"3gp",
		"3gpp"
	]
},
	"video/3gpp-tt": {
	source: "iana"
},
	"video/3gpp2": {
	source: "iana",
	extensions: [
		"3g2"
	]
},
	"video/av1": {
	source: "iana"
},
	"video/bmpeg": {
	source: "iana"
},
	"video/bt656": {
	source: "iana"
},
	"video/celb": {
	source: "iana"
},
	"video/dv": {
	source: "iana"
},
	"video/encaprtp": {
	source: "iana"
},
	"video/ffv1": {
	source: "iana"
},
	"video/flexfec": {
	source: "iana"
},
	"video/h261": {
	source: "iana",
	extensions: [
		"h261"
	]
},
	"video/h263": {
	source: "iana",
	extensions: [
		"h263"
	]
},
	"video/h263-1998": {
	source: "iana"
},
	"video/h263-2000": {
	source: "iana"
},
	"video/h264": {
	source: "iana",
	extensions: [
		"h264"
	]
},
	"video/h264-rcdo": {
	source: "iana"
},
	"video/h264-svc": {
	source: "iana"
},
	"video/h265": {
	source: "iana"
},
	"video/iso.segment": {
	source: "iana",
	extensions: [
		"m4s"
	]
},
	"video/jpeg": {
	source: "iana",
	extensions: [
		"jpgv"
	]
},
	"video/jpeg2000": {
	source: "iana"
},
	"video/jpm": {
	source: "apache",
	extensions: [
		"jpm",
		"jpgm"
	]
},
	"video/jxsv": {
	source: "iana"
},
	"video/mj2": {
	source: "iana",
	extensions: [
		"mj2",
		"mjp2"
	]
},
	"video/mp1s": {
	source: "iana"
},
	"video/mp2p": {
	source: "iana"
},
	"video/mp2t": {
	source: "iana",
	extensions: [
		"ts"
	]
},
	"video/mp4": {
	source: "iana",
	compressible: false,
	extensions: [
		"mp4",
		"mp4v",
		"mpg4"
	]
},
	"video/mp4v-es": {
	source: "iana"
},
	"video/mpeg": {
	source: "iana",
	compressible: false,
	extensions: [
		"mpeg",
		"mpg",
		"mpe",
		"m1v",
		"m2v"
	]
},
	"video/mpeg4-generic": {
	source: "iana"
},
	"video/mpv": {
	source: "iana"
},
	"video/nv": {
	source: "iana"
},
	"video/ogg": {
	source: "iana",
	compressible: false,
	extensions: [
		"ogv"
	]
},
	"video/parityfec": {
	source: "iana"
},
	"video/pointer": {
	source: "iana"
},
	"video/quicktime": {
	source: "iana",
	compressible: false,
	extensions: [
		"qt",
		"mov"
	]
},
	"video/raptorfec": {
	source: "iana"
},
	"video/raw": {
	source: "iana"
},
	"video/rtp-enc-aescm128": {
	source: "iana"
},
	"video/rtploopback": {
	source: "iana"
},
	"video/rtx": {
	source: "iana"
},
	"video/scip": {
	source: "iana"
},
	"video/smpte291": {
	source: "iana"
},
	"video/smpte292m": {
	source: "iana"
},
	"video/ulpfec": {
	source: "iana"
},
	"video/vc1": {
	source: "iana"
},
	"video/vc2": {
	source: "iana"
},
	"video/vnd.cctv": {
	source: "iana"
},
	"video/vnd.dece.hd": {
	source: "iana",
	extensions: [
		"uvh",
		"uvvh"
	]
},
	"video/vnd.dece.mobile": {
	source: "iana",
	extensions: [
		"uvm",
		"uvvm"
	]
},
	"video/vnd.dece.mp4": {
	source: "iana"
},
	"video/vnd.dece.pd": {
	source: "iana",
	extensions: [
		"uvp",
		"uvvp"
	]
},
	"video/vnd.dece.sd": {
	source: "iana",
	extensions: [
		"uvs",
		"uvvs"
	]
},
	"video/vnd.dece.video": {
	source: "iana",
	extensions: [
		"uvv",
		"uvvv"
	]
},
	"video/vnd.directv.mpeg": {
	source: "iana"
},
	"video/vnd.directv.mpeg-tts": {
	source: "iana"
},
	"video/vnd.dlna.mpeg-tts": {
	source: "iana"
},
	"video/vnd.dvb.file": {
	source: "iana",
	extensions: [
		"dvb"
	]
},
	"video/vnd.fvt": {
	source: "iana",
	extensions: [
		"fvt"
	]
},
	"video/vnd.hns.video": {
	source: "iana"
},
	"video/vnd.iptvforum.1dparityfec-1010": {
	source: "iana"
},
	"video/vnd.iptvforum.1dparityfec-2005": {
	source: "iana"
},
	"video/vnd.iptvforum.2dparityfec-1010": {
	source: "iana"
},
	"video/vnd.iptvforum.2dparityfec-2005": {
	source: "iana"
},
	"video/vnd.iptvforum.ttsavc": {
	source: "iana"
},
	"video/vnd.iptvforum.ttsmpeg2": {
	source: "iana"
},
	"video/vnd.motorola.video": {
	source: "iana"
},
	"video/vnd.motorola.videop": {
	source: "iana"
},
	"video/vnd.mpegurl": {
	source: "iana",
	extensions: [
		"mxu",
		"m4u"
	]
},
	"video/vnd.ms-playready.media.pyv": {
	source: "iana",
	extensions: [
		"pyv"
	]
},
	"video/vnd.nokia.interleaved-multimedia": {
	source: "iana"
},
	"video/vnd.nokia.mp4vr": {
	source: "iana"
},
	"video/vnd.nokia.videovoip": {
	source: "iana"
},
	"video/vnd.objectvideo": {
	source: "iana"
},
	"video/vnd.radgamettools.bink": {
	source: "iana"
},
	"video/vnd.radgamettools.smacker": {
	source: "iana"
},
	"video/vnd.sealed.mpeg1": {
	source: "iana"
},
	"video/vnd.sealed.mpeg4": {
	source: "iana"
},
	"video/vnd.sealed.swf": {
	source: "iana"
},
	"video/vnd.sealedmedia.softseal.mov": {
	source: "iana"
},
	"video/vnd.uvvu.mp4": {
	source: "iana",
	extensions: [
		"uvu",
		"uvvu"
	]
},
	"video/vnd.vivo": {
	source: "iana",
	extensions: [
		"viv"
	]
},
	"video/vnd.youtube.yt": {
	source: "iana"
},
	"video/vp8": {
	source: "iana"
},
	"video/vp9": {
	source: "iana"
},
	"video/webm": {
	source: "apache",
	compressible: false,
	extensions: [
		"webm"
	]
},
	"video/x-f4v": {
	source: "apache",
	extensions: [
		"f4v"
	]
},
	"video/x-fli": {
	source: "apache",
	extensions: [
		"fli"
	]
},
	"video/x-flv": {
	source: "apache",
	compressible: false,
	extensions: [
		"flv"
	]
},
	"video/x-m4v": {
	source: "apache",
	extensions: [
		"m4v"
	]
},
	"video/x-matroska": {
	source: "apache",
	compressible: false,
	extensions: [
		"mkv",
		"mk3d",
		"mks"
	]
},
	"video/x-mng": {
	source: "apache",
	extensions: [
		"mng"
	]
},
	"video/x-ms-asf": {
	source: "apache",
	extensions: [
		"asf",
		"asx"
	]
},
	"video/x-ms-vob": {
	source: "apache",
	extensions: [
		"vob"
	]
},
	"video/x-ms-wm": {
	source: "apache",
	extensions: [
		"wm"
	]
},
	"video/x-ms-wmv": {
	source: "apache",
	compressible: false,
	extensions: [
		"wmv"
	]
},
	"video/x-ms-wmx": {
	source: "apache",
	extensions: [
		"wmx"
	]
},
	"video/x-ms-wvx": {
	source: "apache",
	extensions: [
		"wvx"
	]
},
	"video/x-msvideo": {
	source: "apache",
	extensions: [
		"avi"
	]
},
	"video/x-sgi-movie": {
	source: "apache",
	extensions: [
		"movie"
	]
},
	"video/x-smv": {
	source: "apache",
	extensions: [
		"smv"
	]
},
	"x-conference/x-cooltalk": {
	source: "apache",
	extensions: [
		"ice"
	]
},
	"x-shader/x-fragment": {
	compressible: true
},
	"x-shader/x-vertex": {
	compressible: true
}
};

/*!
 * mime-db
 * Copyright(c) 2014 Jonathan Ong
 * Copyright(c) 2015-2022 Douglas Christopher Wilson
 * MIT Licensed
 */

var hasRequiredMimeDb;

function requireMimeDb () {
	if (hasRequiredMimeDb) return mimeDb.exports;
	hasRequiredMimeDb = 1;
	(function (module) {
		/**
		 * Module exports.
		 */

		module.exports = require$$0;
} (mimeDb));
	return mimeDb.exports;
}

/*!
 * mime-types
 * Copyright(c) 2014 Jonathan Ong
 * Copyright(c) 2015 Douglas Christopher Wilson
 * MIT Licensed
 */

var hasRequiredMimeTypes;

function requireMimeTypes () {
	if (hasRequiredMimeTypes) return mimeTypes;
	hasRequiredMimeTypes = 1;
	(function (exports) {

		/**
		 * Module dependencies.
		 * @private
		 */

		var db = requireMimeDb();
		var extname = path__default["default"].extname;

		/**
		 * Module variables.
		 * @private
		 */

		var EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/;
		var TEXT_TYPE_REGEXP = /^text\//i;

		/**
		 * Module exports.
		 * @public
		 */

		exports.charset = charset;
		exports.charsets = { lookup: charset };
		exports.contentType = contentType;
		exports.extension = extension;
		exports.extensions = Object.create(null);
		exports.lookup = lookup;
		exports.types = Object.create(null);

		// Populate the extensions/types maps
		populateMaps(exports.extensions, exports.types);

		/**
		 * Get the default charset for a MIME type.
		 *
		 * @param {string} type
		 * @return {boolean|string}
		 */

		function charset (type) {
		  if (!type || typeof type !== 'string') {
		    return false
		  }

		  // TODO: use media-typer
		  var match = EXTRACT_TYPE_REGEXP.exec(type);
		  var mime = match && db[match[1].toLowerCase()];

		  if (mime && mime.charset) {
		    return mime.charset
		  }

		  // default text/* to utf-8
		  if (match && TEXT_TYPE_REGEXP.test(match[1])) {
		    return 'UTF-8'
		  }

		  return false
		}

		/**
		 * Create a full Content-Type header given a MIME type or extension.
		 *
		 * @param {string} str
		 * @return {boolean|string}
		 */

		function contentType (str) {
		  // TODO: should this even be in this module?
		  if (!str || typeof str !== 'string') {
		    return false
		  }

		  var mime = str.indexOf('/') === -1
		    ? exports.lookup(str)
		    : str;

		  if (!mime) {
		    return false
		  }

		  // TODO: use content-type or other module
		  if (mime.indexOf('charset') === -1) {
		    var charset = exports.charset(mime);
		    if (charset) mime += '; charset=' + charset.toLowerCase();
		  }

		  return mime
		}

		/**
		 * Get the default extension for a MIME type.
		 *
		 * @param {string} type
		 * @return {boolean|string}
		 */

		function extension (type) {
		  if (!type || typeof type !== 'string') {
		    return false
		  }

		  // TODO: use media-typer
		  var match = EXTRACT_TYPE_REGEXP.exec(type);

		  // get extensions
		  var exts = match && exports.extensions[match[1].toLowerCase()];

		  if (!exts || !exts.length) {
		    return false
		  }

		  return exts[0]
		}

		/**
		 * Lookup the MIME type for a file path/extension.
		 *
		 * @param {string} path
		 * @return {boolean|string}
		 */

		function lookup (path) {
		  if (!path || typeof path !== 'string') {
		    return false
		  }

		  // get the extension ("ext" or ".ext" or full path)
		  var extension = extname('x.' + path)
		    .toLowerCase()
		    .substr(1);

		  if (!extension) {
		    return false
		  }

		  return exports.types[extension] || false
		}

		/**
		 * Populate the extensions and types maps.
		 * @private
		 */

		function populateMaps (extensions, types) {
		  // source preference (least -> most)
		  var preference = ['nginx', 'apache', undefined, 'iana'];

		  Object.keys(db).forEach(function forEachMimeType (type) {
		    var mime = db[type];
		    var exts = mime.extensions;

		    if (!exts || !exts.length) {
		      return
		    }

		    // mime -> extensions
		    extensions[type] = exts;

		    // extension -> mime
		    for (var i = 0; i < exts.length; i++) {
		      var extension = exts[i];

		      if (types[extension]) {
		        var from = preference.indexOf(db[types[extension]].source);
		        var to = preference.indexOf(mime.source);

		        if (types[extension] !== 'application/octet-stream' &&
		          (from > to || (from === to && types[extension].substr(0, 12) === 'application/'))) {
		          // skip the remapping
		          continue
		        }
		      }

		      // set the extension -> mime
		      types[extension] = type;
		    }
		  });
		}
} (mimeTypes));
	return mimeTypes;
}

var defer_1;
var hasRequiredDefer;

function requireDefer () {
	if (hasRequiredDefer) return defer_1;
	hasRequiredDefer = 1;
	defer_1 = defer;

	/**
	 * Runs provided function on next iteration of the event loop
	 *
	 * @param {function} fn - function to run
	 */
	function defer(fn)
	{
	  var nextTick = typeof setImmediate == 'function'
	    ? setImmediate
	    : (
	      typeof process == 'object' && typeof process.nextTick == 'function'
	      ? process.nextTick
	      : null
	    );

	  if (nextTick)
	  {
	    nextTick(fn);
	  }
	  else
	  {
	    setTimeout(fn, 0);
	  }
	}
	return defer_1;
}

var async_1;
var hasRequiredAsync;

function requireAsync () {
	if (hasRequiredAsync) return async_1;
	hasRequiredAsync = 1;
	var defer = requireDefer();

	// API
	async_1 = async;

	/**
	 * Runs provided callback asynchronously
	 * even if callback itself is not
	 *
	 * @param   {function} callback - callback to invoke
	 * @returns {function} - augmented callback
	 */
	function async(callback)
	{
	  var isAsync = false;

	  // check if async happened
	  defer(function() { isAsync = true; });

	  return function async_callback(err, result)
	  {
	    if (isAsync)
	    {
	      callback(err, result);
	    }
	    else
	    {
	      defer(function nextTick_callback()
	      {
	        callback(err, result);
	      });
	    }
	  };
	}
	return async_1;
}

var abort_1;
var hasRequiredAbort;

function requireAbort () {
	if (hasRequiredAbort) return abort_1;
	hasRequiredAbort = 1;
	// API
	abort_1 = abort;

	/**
	 * Aborts leftover active jobs
	 *
	 * @param {object} state - current state object
	 */
	function abort(state)
	{
	  Object.keys(state.jobs).forEach(clean.bind(state));

	  // reset leftover jobs
	  state.jobs = {};
	}

	/**
	 * Cleans up leftover job by invoking abort function for the provided job id
	 *
	 * @this  state
	 * @param {string|number} key - job id to abort
	 */
	function clean(key)
	{
	  if (typeof this.jobs[key] == 'function')
	  {
	    this.jobs[key]();
	  }
	}
	return abort_1;
}

var iterate_1;
var hasRequiredIterate;

function requireIterate () {
	if (hasRequiredIterate) return iterate_1;
	hasRequiredIterate = 1;
	var async = requireAsync()
	  , abort = requireAbort()
	  ;

	// API
	iterate_1 = iterate;

	/**
	 * Iterates over each job object
	 *
	 * @param {array|object} list - array or object (named list) to iterate over
	 * @param {function} iterator - iterator to run
	 * @param {object} state - current job status
	 * @param {function} callback - invoked when all elements processed
	 */
	function iterate(list, iterator, state, callback)
	{
	  // store current index
	  var key = state['keyedList'] ? state['keyedList'][state.index] : state.index;

	  state.jobs[key] = runJob(iterator, key, list[key], function(error, output)
	  {
	    // don't repeat yourself
	    // skip secondary callbacks
	    if (!(key in state.jobs))
	    {
	      return;
	    }

	    // clean up jobs
	    delete state.jobs[key];

	    if (error)
	    {
	      // don't process rest of the results
	      // stop still active jobs
	      // and reset the list
	      abort(state);
	    }
	    else
	    {
	      state.results[key] = output;
	    }

	    // return salvaged results
	    callback(error, state.results);
	  });
	}

	/**
	 * Runs iterator over provided job element
	 *
	 * @param   {function} iterator - iterator to invoke
	 * @param   {string|number} key - key/index of the element in the list of jobs
	 * @param   {mixed} item - job description
	 * @param   {function} callback - invoked after iterator is done with the job
	 * @returns {function|mixed} - job abort function or something else
	 */
	function runJob(iterator, key, item, callback)
	{
	  var aborter;

	  // allow shortcut if iterator expects only two arguments
	  if (iterator.length == 2)
	  {
	    aborter = iterator(item, async(callback));
	  }
	  // otherwise go with full three arguments
	  else
	  {
	    aborter = iterator(item, key, async(callback));
	  }

	  return aborter;
	}
	return iterate_1;
}

var state_1;
var hasRequiredState;

function requireState () {
	if (hasRequiredState) return state_1;
	hasRequiredState = 1;
	// API
	state_1 = state;

	/**
	 * Creates initial state object
	 * for iteration over list
	 *
	 * @param   {array|object} list - list to iterate over
	 * @param   {function|null} sortMethod - function to use for keys sort,
	 *                                     or `null` to keep them as is
	 * @returns {object} - initial state object
	 */
	function state(list, sortMethod)
	{
	  var isNamedList = !Array.isArray(list)
	    , initState =
	    {
	      index    : 0,
	      keyedList: isNamedList || sortMethod ? Object.keys(list) : null,
	      jobs     : {},
	      results  : isNamedList ? {} : [],
	      size     : isNamedList ? Object.keys(list).length : list.length
	    }
	    ;

	  if (sortMethod)
	  {
	    // sort array keys based on it's values
	    // sort object's keys just on own merit
	    initState.keyedList.sort(isNamedList ? sortMethod : function(a, b)
	    {
	      return sortMethod(list[a], list[b]);
	    });
	  }

	  return initState;
	}
	return state_1;
}

var terminator_1;
var hasRequiredTerminator;

function requireTerminator () {
	if (hasRequiredTerminator) return terminator_1;
	hasRequiredTerminator = 1;
	var abort = requireAbort()
	  , async = requireAsync()
	  ;

	// API
	terminator_1 = terminator;

	/**
	 * Terminates jobs in the attached state context
	 *
	 * @this  AsyncKitState#
	 * @param {function} callback - final callback to invoke after termination
	 */
	function terminator(callback)
	{
	  if (!Object.keys(this.jobs).length)
	  {
	    return;
	  }

	  // fast forward iteration index
	  this.index = this.size;

	  // abort jobs
	  abort(this);

	  // send back results we have so far
	  async(callback)(null, this.results);
	}
	return terminator_1;
}

var parallel_1;
var hasRequiredParallel;

function requireParallel () {
	if (hasRequiredParallel) return parallel_1;
	hasRequiredParallel = 1;
	var iterate    = requireIterate()
	  , initState  = requireState()
	  , terminator = requireTerminator()
	  ;

	// Public API
	parallel_1 = parallel;

	/**
	 * Runs iterator over provided array elements in parallel
	 *
	 * @param   {array|object} list - array or object (named list) to iterate over
	 * @param   {function} iterator - iterator to run
	 * @param   {function} callback - invoked when all elements processed
	 * @returns {function} - jobs terminator
	 */
	function parallel(list, iterator, callback)
	{
	  var state = initState(list);

	  while (state.index < (state['keyedList'] || list).length)
	  {
	    iterate(list, iterator, state, function(error, result)
	    {
	      if (error)
	      {
	        callback(error, result);
	        return;
	      }

	      // looks like it's the last one
	      if (Object.keys(state.jobs).length === 0)
	      {
	        callback(null, state.results);
	        return;
	      }
	    });

	    state.index++;
	  }

	  return terminator.bind(state, callback);
	}
	return parallel_1;
}

var serialOrdered = {exports: {}};

var hasRequiredSerialOrdered;

function requireSerialOrdered () {
	if (hasRequiredSerialOrdered) return serialOrdered.exports;
	hasRequiredSerialOrdered = 1;
	var iterate    = requireIterate()
	  , initState  = requireState()
	  , terminator = requireTerminator()
	  ;

	// Public API
	serialOrdered.exports = serialOrdered$1;
	// sorting helpers
	serialOrdered.exports.ascending  = ascending;
	serialOrdered.exports.descending = descending;

	/**
	 * Runs iterator over provided sorted array elements in series
	 *
	 * @param   {array|object} list - array or object (named list) to iterate over
	 * @param   {function} iterator - iterator to run
	 * @param   {function} sortMethod - custom sort function
	 * @param   {function} callback - invoked when all elements processed
	 * @returns {function} - jobs terminator
	 */
	function serialOrdered$1(list, iterator, sortMethod, callback)
	{
	  var state = initState(list, sortMethod);

	  iterate(list, iterator, state, function iteratorHandler(error, result)
	  {
	    if (error)
	    {
	      callback(error, result);
	      return;
	    }

	    state.index++;

	    // are we there yet?
	    if (state.index < (state['keyedList'] || list).length)
	    {
	      iterate(list, iterator, state, iteratorHandler);
	      return;
	    }

	    // done here
	    callback(null, state.results);
	  });

	  return terminator.bind(state, callback);
	}

	/*
	 * -- Sort methods
	 */

	/**
	 * sort helper to sort array elements in ascending order
	 *
	 * @param   {mixed} a - an item to compare
	 * @param   {mixed} b - an item to compare
	 * @returns {number} - comparison result
	 */
	function ascending(a, b)
	{
	  return a < b ? -1 : a > b ? 1 : 0;
	}

	/**
	 * sort helper to sort array elements in descending order
	 *
	 * @param   {mixed} a - an item to compare
	 * @param   {mixed} b - an item to compare
	 * @returns {number} - comparison result
	 */
	function descending(a, b)
	{
	  return -1 * ascending(a, b);
	}
	return serialOrdered.exports;
}

var serial_1;
var hasRequiredSerial;

function requireSerial () {
	if (hasRequiredSerial) return serial_1;
	hasRequiredSerial = 1;
	var serialOrdered = requireSerialOrdered();

	// Public API
	serial_1 = serial;

	/**
	 * Runs iterator over provided array elements in series
	 *
	 * @param   {array|object} list - array or object (named list) to iterate over
	 * @param   {function} iterator - iterator to run
	 * @param   {function} callback - invoked when all elements processed
	 * @returns {function} - jobs terminator
	 */
	function serial(list, iterator, callback)
	{
	  return serialOrdered(list, iterator, null, callback);
	}
	return serial_1;
}

var asynckit;
var hasRequiredAsynckit;

function requireAsynckit () {
	if (hasRequiredAsynckit) return asynckit;
	hasRequiredAsynckit = 1;
	asynckit =
	{
	  parallel      : requireParallel(),
	  serial        : requireSerial(),
	  serialOrdered : requireSerialOrdered()
	};
	return asynckit;
}

var populate;
var hasRequiredPopulate;

function requirePopulate () {
	if (hasRequiredPopulate) return populate;
	hasRequiredPopulate = 1;
	// populates missing values
	populate = function(dst, src) {

	  Object.keys(src).forEach(function(prop)
	  {
	    dst[prop] = dst[prop] || src[prop];
	  });

	  return dst;
	};
	return populate;
}

var form_data;
var hasRequiredForm_data;

function requireForm_data () {
	if (hasRequiredForm_data) return form_data;
	hasRequiredForm_data = 1;
	var CombinedStream = requireCombined_stream();
	var util = require$$1__default$1["default"];
	var path = path__default["default"];
	var http = require$$1__default$2["default"];
	var https = require$$2__default["default"];
	var parseUrl = require$$0__default$1["default"].parse;
	var fs = fs__default["default"];
	var Stream = require$$3__default["default"].Stream;
	var mime = requireMimeTypes();
	var asynckit = requireAsynckit();
	var populate = requirePopulate();

	// Public API
	form_data = FormData;

	// make it a Stream
	util.inherits(FormData, CombinedStream);

	/**
	 * Create readable "multipart/form-data" streams.
	 * Can be used to submit forms
	 * and file uploads to other web applications.
	 *
	 * @constructor
	 * @param {Object} options - Properties to be added/overriden for FormData and CombinedStream
	 */
	function FormData(options) {
	  if (!(this instanceof FormData)) {
	    return new FormData(options);
	  }

	  this._overheadLength = 0;
	  this._valueLength = 0;
	  this._valuesToMeasure = [];

	  CombinedStream.call(this);

	  options = options || {};
	  for (var option in options) {
	    this[option] = options[option];
	  }
	}

	FormData.LINE_BREAK = '\r\n';
	FormData.DEFAULT_CONTENT_TYPE = 'application/octet-stream';

	FormData.prototype.append = function(field, value, options) {

	  options = options || {};

	  // allow filename as single option
	  if (typeof options == 'string') {
	    options = {filename: options};
	  }

	  var append = CombinedStream.prototype.append.bind(this);

	  // all that streamy business can't handle numbers
	  if (typeof value == 'number') {
	    value = '' + value;
	  }

	  // https://github.com/felixge/node-form-data/issues/38
	  if (util.isArray(value)) {
	    // Please convert your array into string
	    // the way web server expects it
	    this._error(new Error('Arrays are not supported.'));
	    return;
	  }

	  var header = this._multiPartHeader(field, value, options);
	  var footer = this._multiPartFooter();

	  append(header);
	  append(value);
	  append(footer);

	  // pass along options.knownLength
	  this._trackLength(header, value, options);
	};

	FormData.prototype._trackLength = function(header, value, options) {
	  var valueLength = 0;

	  // used w/ getLengthSync(), when length is known.
	  // e.g. for streaming directly from a remote server,
	  // w/ a known file a size, and not wanting to wait for
	  // incoming file to finish to get its size.
	  if (options.knownLength != null) {
	    valueLength += +options.knownLength;
	  } else if (Buffer.isBuffer(value)) {
	    valueLength = value.length;
	  } else if (typeof value === 'string') {
	    valueLength = Buffer.byteLength(value);
	  }

	  this._valueLength += valueLength;

	  // @check why add CRLF? does this account for custom/multiple CRLFs?
	  this._overheadLength +=
	    Buffer.byteLength(header) +
	    FormData.LINE_BREAK.length;

	  // empty or either doesn't have path or not an http response or not a stream
	  if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) && !(value instanceof Stream))) {
	    return;
	  }

	  // no need to bother with the length
	  if (!options.knownLength) {
	    this._valuesToMeasure.push(value);
	  }
	};

	FormData.prototype._lengthRetriever = function(value, callback) {

	  if (value.hasOwnProperty('fd')) {

	    // take read range into a account
	    // `end` = Infinity –> read file till the end
	    //
	    // TODO: Looks like there is bug in Node fs.createReadStream
	    // it doesn't respect `end` options without `start` options
	    // Fix it when node fixes it.
	    // https://github.com/joyent/node/issues/7819
	    if (value.end != undefined && value.end != Infinity && value.start != undefined) {

	      // when end specified
	      // no need to calculate range
	      // inclusive, starts with 0
	      callback(null, value.end + 1 - (value.start ? value.start : 0));

	    // not that fast snoopy
	    } else {
	      // still need to fetch file size from fs
	      fs.stat(value.path, function(err, stat) {

	        var fileSize;

	        if (err) {
	          callback(err);
	          return;
	        }

	        // update final size based on the range options
	        fileSize = stat.size - (value.start ? value.start : 0);
	        callback(null, fileSize);
	      });
	    }

	  // or http response
	  } else if (value.hasOwnProperty('httpVersion')) {
	    callback(null, +value.headers['content-length']);

	  // or request stream http://github.com/mikeal/request
	  } else if (value.hasOwnProperty('httpModule')) {
	    // wait till response come back
	    value.on('response', function(response) {
	      value.pause();
	      callback(null, +response.headers['content-length']);
	    });
	    value.resume();

	  // something else
	  } else {
	    callback('Unknown stream');
	  }
	};

	FormData.prototype._multiPartHeader = function(field, value, options) {
	  // custom header specified (as string)?
	  // it becomes responsible for boundary
	  // (e.g. to handle extra CRLFs on .NET servers)
	  if (typeof options.header == 'string') {
	    return options.header;
	  }

	  var contentDisposition = this._getContentDisposition(value, options);
	  var contentType = this._getContentType(value, options);

	  var contents = '';
	  var headers  = {
	    // add custom disposition as third element or keep it two elements if not
	    'Content-Disposition': ['form-data', 'name="' + field + '"'].concat(contentDisposition || []),
	    // if no content type. allow it to be empty array
	    'Content-Type': [].concat(contentType || [])
	  };

	  // allow custom headers.
	  if (typeof options.header == 'object') {
	    populate(headers, options.header);
	  }

	  var header;
	  for (var prop in headers) {
	    if (!headers.hasOwnProperty(prop)) continue;
	    header = headers[prop];

	    // skip nullish headers.
	    if (header == null) {
	      continue;
	    }

	    // convert all headers to arrays.
	    if (!Array.isArray(header)) {
	      header = [header];
	    }

	    // add non-empty headers.
	    if (header.length) {
	      contents += prop + ': ' + header.join('; ') + FormData.LINE_BREAK;
	    }
	  }

	  return '--' + this.getBoundary() + FormData.LINE_BREAK + contents + FormData.LINE_BREAK;
	};

	FormData.prototype._getContentDisposition = function(value, options) {

	  var filename
	    , contentDisposition
	    ;

	  if (typeof options.filepath === 'string') {
	    // custom filepath for relative paths
	    filename = path.normalize(options.filepath).replace(/\\/g, '/');
	  } else if (options.filename || value.name || value.path) {
	    // custom filename take precedence
	    // formidable and the browser add a name property
	    // fs- and request- streams have path property
	    filename = path.basename(options.filename || value.name || value.path);
	  } else if (value.readable && value.hasOwnProperty('httpVersion')) {
	    // or try http response
	    filename = path.basename(value.client._httpMessage.path || '');
	  }

	  if (filename) {
	    contentDisposition = 'filename="' + filename + '"';
	  }

	  return contentDisposition;
	};

	FormData.prototype._getContentType = function(value, options) {

	  // use custom content-type above all
	  var contentType = options.contentType;

	  // or try `name` from formidable, browser
	  if (!contentType && value.name) {
	    contentType = mime.lookup(value.name);
	  }

	  // or try `path` from fs-, request- streams
	  if (!contentType && value.path) {
	    contentType = mime.lookup(value.path);
	  }

	  // or if it's http-reponse
	  if (!contentType && value.readable && value.hasOwnProperty('httpVersion')) {
	    contentType = value.headers['content-type'];
	  }

	  // or guess it from the filepath or filename
	  if (!contentType && (options.filepath || options.filename)) {
	    contentType = mime.lookup(options.filepath || options.filename);
	  }

	  // fallback to the default content type if `value` is not simple value
	  if (!contentType && typeof value == 'object') {
	    contentType = FormData.DEFAULT_CONTENT_TYPE;
	  }

	  return contentType;
	};

	FormData.prototype._multiPartFooter = function() {
	  return function(next) {
	    var footer = FormData.LINE_BREAK;

	    var lastPart = (this._streams.length === 0);
	    if (lastPart) {
	      footer += this._lastBoundary();
	    }

	    next(footer);
	  }.bind(this);
	};

	FormData.prototype._lastBoundary = function() {
	  return '--' + this.getBoundary() + '--' + FormData.LINE_BREAK;
	};

	FormData.prototype.getHeaders = function(userHeaders) {
	  var header;
	  var formHeaders = {
	    'content-type': 'multipart/form-data; boundary=' + this.getBoundary()
	  };

	  for (header in userHeaders) {
	    if (userHeaders.hasOwnProperty(header)) {
	      formHeaders[header.toLowerCase()] = userHeaders[header];
	    }
	  }

	  return formHeaders;
	};

	FormData.prototype.setBoundary = function(boundary) {
	  this._boundary = boundary;
	};

	FormData.prototype.getBoundary = function() {
	  if (!this._boundary) {
	    this._generateBoundary();
	  }

	  return this._boundary;
	};

	FormData.prototype.getBuffer = function() {
	  var dataBuffer = new Buffer.alloc( 0 );
	  var boundary = this.getBoundary();

	  // Create the form content. Add Line breaks to the end of data.
	  for (var i = 0, len = this._streams.length; i < len; i++) {
	    if (typeof this._streams[i] !== 'function') {

	      // Add content to the buffer.
	      if(Buffer.isBuffer(this._streams[i])) {
	        dataBuffer = Buffer.concat( [dataBuffer, this._streams[i]]);
	      }else {
	        dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(this._streams[i])]);
	      }

	      // Add break after content.
	      if (typeof this._streams[i] !== 'string' || this._streams[i].substring( 2, boundary.length + 2 ) !== boundary) {
	        dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(FormData.LINE_BREAK)] );
	      }
	    }
	  }

	  // Add the footer and return the Buffer object.
	  return Buffer.concat( [dataBuffer, Buffer.from(this._lastBoundary())] );
	};

	FormData.prototype._generateBoundary = function() {
	  // This generates a 50 character boundary similar to those used by Firefox.
	  // They are optimized for boyer-moore parsing.
	  var boundary = '--------------------------';
	  for (var i = 0; i < 24; i++) {
	    boundary += Math.floor(Math.random() * 10).toString(16);
	  }

	  this._boundary = boundary;
	};

	// Note: getLengthSync DOESN'T calculate streams length
	// As workaround one can calculate file size manually
	// and add it as knownLength option
	FormData.prototype.getLengthSync = function() {
	  var knownLength = this._overheadLength + this._valueLength;

	  // Don't get confused, there are 3 "internal" streams for each keyval pair
	  // so it basically checks if there is any value added to the form
	  if (this._streams.length) {
	    knownLength += this._lastBoundary().length;
	  }

	  // https://github.com/form-data/form-data/issues/40
	  if (!this.hasKnownLength()) {
	    // Some async length retrievers are present
	    // therefore synchronous length calculation is false.
	    // Please use getLength(callback) to get proper length
	    this._error(new Error('Cannot calculate proper length in synchronous way.'));
	  }

	  return knownLength;
	};

	// Public API to check if length of added values is known
	// https://github.com/form-data/form-data/issues/196
	// https://github.com/form-data/form-data/issues/262
	FormData.prototype.hasKnownLength = function() {
	  var hasKnownLength = true;

	  if (this._valuesToMeasure.length) {
	    hasKnownLength = false;
	  }

	  return hasKnownLength;
	};

	FormData.prototype.getLength = function(cb) {
	  var knownLength = this._overheadLength + this._valueLength;

	  if (this._streams.length) {
	    knownLength += this._lastBoundary().length;
	  }

	  if (!this._valuesToMeasure.length) {
	    process.nextTick(cb.bind(this, null, knownLength));
	    return;
	  }

	  asynckit.parallel(this._valuesToMeasure, this._lengthRetriever, function(err, values) {
	    if (err) {
	      cb(err);
	      return;
	    }

	    values.forEach(function(length) {
	      knownLength += length;
	    });

	    cb(null, knownLength);
	  });
	};

	FormData.prototype.submit = function(params, cb) {
	  var request
	    , options
	    , defaults = {method: 'post'}
	    ;

	  // parse provided url if it's string
	  // or treat it as options object
	  if (typeof params == 'string') {

	    params = parseUrl(params);
	    options = populate({
	      port: params.port,
	      path: params.pathname,
	      host: params.hostname,
	      protocol: params.protocol
	    }, defaults);

	  // use custom params
	  } else {

	    options = populate(params, defaults);
	    // if no port provided use default one
	    if (!options.port) {
	      options.port = options.protocol == 'https:' ? 443 : 80;
	    }
	  }

	  // put that good code in getHeaders to some use
	  options.headers = this.getHeaders(params.headers);

	  // https if specified, fallback to http in any other case
	  if (options.protocol == 'https:') {
	    request = https.request(options);
	  } else {
	    request = http.request(options);
	  }

	  // get content length and fire away
	  this.getLength(function(err, length) {
	    if (err && err !== 'Unknown stream') {
	      this._error(err);
	      return;
	    }

	    // add content length
	    if (length) {
	      request.setHeader('Content-Length', length);
	    }

	    this.pipe(request);
	    if (cb) {
	      var onResponse;

	      var callback = function (error, responce) {
	        request.removeListener('error', callback);
	        request.removeListener('response', onResponse);

	        return cb.call(this, error, responce);
	      };

	      onResponse = callback.bind(this, null);

	      request.on('error', callback);
	      request.on('response', onResponse);
	    }
	  }.bind(this));

	  return request;
	};

	FormData.prototype._error = function(err) {
	  if (!this.error) {
	    this.error = err;
	    this.pause();
	    this.emit('error', err);
	  }
	};

	FormData.prototype.toString = function () {
	  return '[object FormData]';
	};
	return form_data;
}

var hasRequiredFormData;

function requireFormData () {
	if (hasRequiredFormData) return FormData$1.exports;
	hasRequiredFormData = 1;
	(function (module) {
		// eslint-disable-next-line strict
		module.exports = requireForm_data();
} (FormData$1));
	return FormData$1.exports;
}

var utils$5 = utils$9;
var normalizeHeaderName = normalizeHeaderName$1;
var AxiosError$1 = requireAxiosError();
var transitionalDefaults = transitional;
var toFormData = requireToFormData();

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

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

function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = requireXhr();
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = requireHttp();
  }
  return adapter;
}

function stringifySafely(rawValue, parser, encoder) {
  if (utils$5.isString(rawValue)) {
    try {
      (parser || JSON.parse)(rawValue);
      return utils$5.trim(rawValue);
    } catch (e) {
      if (e.name !== 'SyntaxError') {
        throw e;
      }
    }
  }

  return (encoder || JSON.stringify)(rawValue);
}

var defaults$3 = {

  transitional: transitionalDefaults,

  adapter: getDefaultAdapter(),

  transformRequest: [function transformRequest(data, headers) {
    normalizeHeaderName(headers, 'Accept');
    normalizeHeaderName(headers, 'Content-Type');

    if (utils$5.isFormData(data) ||
      utils$5.isArrayBuffer(data) ||
      utils$5.isBuffer(data) ||
      utils$5.isStream(data) ||
      utils$5.isFile(data) ||
      utils$5.isBlob(data)
    ) {
      return data;
    }
    if (utils$5.isArrayBufferView(data)) {
      return data.buffer;
    }
    if (utils$5.isURLSearchParams(data)) {
      setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
      return data.toString();
    }

    var isObjectPayload = utils$5.isObject(data);
    var contentType = headers && headers['Content-Type'];

    var isFileList;

    if ((isFileList = utils$5.isFileList(data)) || (isObjectPayload && contentType === 'multipart/form-data')) {
      var _FormData = this.env && this.env.FormData;
      return toFormData(isFileList ? {'files[]': data} : data, _FormData && new _FormData());
    } else if (isObjectPayload || contentType === 'application/json') {
      setContentTypeIfUnset(headers, 'application/json');
      return stringifySafely(data);
    }

    return data;
  }],

  transformResponse: [function transformResponse(data) {
    var transitional = this.transitional || defaults$3.transitional;
    var silentJSONParsing = transitional && transitional.silentJSONParsing;
    var forcedJSONParsing = transitional && transitional.forcedJSONParsing;
    var strictJSONParsing = !silentJSONParsing && this.responseType === 'json';

    if (strictJSONParsing || (forcedJSONParsing && utils$5.isString(data) && data.length)) {
      try {
        return JSON.parse(data);
      } catch (e) {
        if (strictJSONParsing) {
          if (e.name === 'SyntaxError') {
            throw AxiosError$1.from(e, AxiosError$1.ERR_BAD_RESPONSE, this, null, this.response);
          }
          throw e;
        }
      }
    }

    return data;
  }],

  /**
   * A timeout in milliseconds to abort a request. If set to 0 (default) a
   * timeout is not created.
   */
  timeout: 0,

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

  maxContentLength: -1,
  maxBodyLength: -1,

  env: {
    FormData: requireFormData()
  },

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

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

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

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

var defaults_1 = defaults$3;

var utils$4 = utils$9;
var defaults$2 = defaults_1;

/**
 * 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
 */
var transformData$1 = function transformData(data, headers, fns) {
  var context = this || defaults$2;
  /*eslint no-param-reassign:0*/
  utils$4.forEach(fns, function transform(fn) {
    data = fn.call(context, data, headers);
  });

  return data;
};

var isCancel$1;
var hasRequiredIsCancel;

function requireIsCancel () {
	if (hasRequiredIsCancel) return isCancel$1;
	hasRequiredIsCancel = 1;

	isCancel$1 = function isCancel(value) {
	  return !!(value && value.__CANCEL__);
	};
	return isCancel$1;
}

var utils$3 = utils$9;
var transformData = transformData$1;
var isCancel = requireIsCancel();
var defaults$1 = defaults_1;
var CanceledError = requireCanceledError();

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

  if (config.signal && config.signal.aborted) {
    throw new CanceledError();
  }
}

/**
 * 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
 */
var dispatchRequest$1 = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

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

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

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

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

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

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

    // Transform response data
    response.data = transformData.call(
      config,
      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.call(
          config,
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

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

var utils$2 = utils$9;

/**
 * Config-specific merge-function which creates a new config-object
 * by merging two configuration objects together.
 *
 * @param {Object} config1
 * @param {Object} config2
 * @returns {Object} New object resulting from merging config2 to config1
 */
var mergeConfig$2 = function mergeConfig(config1, config2) {
  // eslint-disable-next-line no-param-reassign
  config2 = config2 || {};
  var config = {};

  function getMergedValue(target, source) {
    if (utils$2.isPlainObject(target) && utils$2.isPlainObject(source)) {
      return utils$2.merge(target, source);
    } else if (utils$2.isPlainObject(source)) {
      return utils$2.merge({}, source);
    } else if (utils$2.isArray(source)) {
      return source.slice();
    }
    return source;
  }

  // eslint-disable-next-line consistent-return
  function mergeDeepProperties(prop) {
    if (!utils$2.isUndefined(config2[prop])) {
      return getMergedValue(config1[prop], config2[prop]);
    } else if (!utils$2.isUndefined(config1[prop])) {
      return getMergedValue(undefined, config1[prop]);
    }
  }

  // eslint-disable-next-line consistent-return
  function valueFromConfig2(prop) {
    if (!utils$2.isUndefined(config2[prop])) {
      return getMergedValue(undefined, config2[prop]);
    }
  }

  // eslint-disable-next-line consistent-return
  function defaultToConfig2(prop) {
    if (!utils$2.isUndefined(config2[prop])) {
      return getMergedValue(undefined, config2[prop]);
    } else if (!utils$2.isUndefined(config1[prop])) {
      return getMergedValue(undefined, config1[prop]);
    }
  }

  // eslint-disable-next-line consistent-return
  function mergeDirectKeys(prop) {
    if (prop in config2) {
      return getMergedValue(config1[prop], config2[prop]);
    } else if (prop in config1) {
      return getMergedValue(undefined, config1[prop]);
    }
  }

  var mergeMap = {
    'url': valueFromConfig2,
    'method': valueFromConfig2,
    'data': valueFromConfig2,
    'baseURL': defaultToConfig2,
    'transformRequest': defaultToConfig2,
    'transformResponse': defaultToConfig2,
    'paramsSerializer': defaultToConfig2,
    'timeout': defaultToConfig2,
    'timeoutMessage': defaultToConfig2,
    'withCredentials': defaultToConfig2,
    'adapter': defaultToConfig2,
    'responseType': defaultToConfig2,
    'xsrfCookieName': defaultToConfig2,
    'xsrfHeaderName': defaultToConfig2,
    'onUploadProgress': defaultToConfig2,
    'onDownloadProgress': defaultToConfig2,
    'decompress': defaultToConfig2,
    'maxContentLength': defaultToConfig2,
    'maxBodyLength': defaultToConfig2,
    'beforeRedirect': defaultToConfig2,
    'transport': defaultToConfig2,
    'httpAgent': defaultToConfig2,
    'httpsAgent': defaultToConfig2,
    'cancelToken': defaultToConfig2,
    'socketPath': defaultToConfig2,
    'responseEncoding': defaultToConfig2,
    'validateStatus': mergeDirectKeys
  };

  utils$2.forEach(Object.keys(config1).concat(Object.keys(config2)), function computeConfigValue(prop) {
    var merge = mergeMap[prop] || mergeDeepProperties;
    var configValue = merge(prop);
    (utils$2.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue);
  });

  return config;
};

var VERSION = requireData().version;
var AxiosError = requireAxiosError();

var validators$1 = {};

// eslint-disable-next-line func-names
['object', 'boolean', 'number', 'function', 'string', 'symbol'].forEach(function(type, i) {
  validators$1[type] = function validator(thing) {
    return typeof thing === type || 'a' + (i < 1 ? 'n ' : ' ') + type;
  };
});

var deprecatedWarnings = {};

/**
 * Transitional option validator
 * @param {function|boolean?} validator - set to false if the transitional option has been removed
 * @param {string?} version - deprecated version / removed since version
 * @param {string?} message - some message with additional info
 * @returns {function}
 */
validators$1.transitional = function transitional(validator, version, message) {
  function formatMessage(opt, desc) {
    return '[Axios v' + VERSION + '] Transitional option \'' + opt + '\'' + desc + (message ? '. ' + message : '');
  }

  // eslint-disable-next-line func-names
  return function(value, opt, opts) {
    if (validator === false) {
      throw new AxiosError(
        formatMessage(opt, ' has been removed' + (version ? ' in ' + version : '')),
        AxiosError.ERR_DEPRECATED
      );
    }

    if (version && !deprecatedWarnings[opt]) {
      deprecatedWarnings[opt] = true;
      // eslint-disable-next-line no-console
      console.warn(
        formatMessage(
          opt,
          ' has been deprecated since v' + version + ' and will be removed in the near future'
        )
      );
    }

    return validator ? validator(value, opt, opts) : true;
  };
};

/**
 * Assert object's properties type
 * @param {object} options
 * @param {object} schema
 * @param {boolean?} allowUnknown
 */

function assertOptions(options, schema, allowUnknown) {
  if (typeof options !== 'object') {
    throw new AxiosError('options must be an object', AxiosError.ERR_BAD_OPTION_VALUE);
  }
  var keys = Object.keys(options);
  var i = keys.length;
  while (i-- > 0) {
    var opt = keys[i];
    var validator = schema[opt];
    if (validator) {
      var value = options[opt];
      var result = value === undefined || validator(value, opt, options);
      if (result !== true) {
        throw new AxiosError('option ' + opt + ' must be ' + result, AxiosError.ERR_BAD_OPTION_VALUE);
      }
      continue;
    }
    if (allowUnknown !== true) {
      throw new AxiosError('Unknown option ' + opt, AxiosError.ERR_BAD_OPTION);
    }
  }
}

var validator$1 = {
  assertOptions: assertOptions,
  validators: validators$1
};

var utils$1 = utils$9;
var buildURL = buildURL$1;
var InterceptorManager = InterceptorManager_1;
var dispatchRequest = dispatchRequest$1;
var mergeConfig$1 = mergeConfig$2;
var buildFullPath = buildFullPath$1;
var validator = validator$1;

var validators = validator.validators;
/**
 * Create a new instance of Axios
 *
 * @param {Object} instanceConfig The default config for the instance
 */
function Axios$2(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$2.prototype.request = function request(configOrUrl, config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof configOrUrl === 'string') {
    config = config || {};
    config.url = configOrUrl;
  } else {
    config = configOrUrl || {};
  }

  config = mergeConfig$1(this.defaults, config);

  // Set config.method
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }

  var transitional = config.transitional;

  if (transitional !== undefined) {
    validator.assertOptions(transitional, {
      silentJSONParsing: validators.transitional(validators.boolean),
      forcedJSONParsing: validators.transitional(validators.boolean),
      clarifyTimeoutError: validators.transitional(validators.boolean)
    }, false);
  }

  // filter out skipped interceptors
  var requestInterceptorChain = [];
  var synchronousRequestInterceptors = true;
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
      return;
    }

    synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;

    requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  var responseInterceptorChain = [];
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
  });

  var promise;

  if (!synchronousRequestInterceptors) {
    var chain = [dispatchRequest, undefined];

    Array.prototype.unshift.apply(chain, requestInterceptorChain);
    chain = chain.concat(responseInterceptorChain);

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

    return promise;
  }


  var newConfig = config;
  while (requestInterceptorChain.length) {
    var onFulfilled = requestInterceptorChain.shift();
    var onRejected = requestInterceptorChain.shift();
    try {
      newConfig = onFulfilled(newConfig);
    } catch (error) {
      onRejected(error);
      break;
    }
  }

  try {
    promise = dispatchRequest(newConfig);
  } catch (error) {
    return Promise.reject(error);
  }

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

  return promise;
};

Axios$2.prototype.getUri = function getUri(config) {
  config = mergeConfig$1(this.defaults, config);
  var fullPath = buildFullPath(config.baseURL, config.url);
  return buildURL(fullPath, config.params, config.paramsSerializer);
};

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

utils$1.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/

  function generateHTTPMethod(isForm) {
    return function httpMethod(url, data, config) {
      return this.request(mergeConfig$1(config || {}, {
        method: method,
        headers: isForm ? {
          'Content-Type': 'multipart/form-data'
        } : {},
        url: url,
        data: data
      }));
    };
  }

  Axios$2.prototype[method] = generateHTTPMethod();

  Axios$2.prototype[method + 'Form'] = generateHTTPMethod(true);
});

var Axios_1 = Axios$2;

var CancelToken_1;
var hasRequiredCancelToken;

function requireCancelToken () {
	if (hasRequiredCancelToken) return CancelToken_1;
	hasRequiredCancelToken = 1;

	var CanceledError = requireCanceledError();

	/**
	 * 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;

	  // eslint-disable-next-line func-names
	  this.promise.then(function(cancel) {
	    if (!token._listeners) return;

	    var i;
	    var l = token._listeners.length;

	    for (i = 0; i < l; i++) {
	      token._listeners[i](cancel);
	    }
	    token._listeners = null;
	  });

	  // eslint-disable-next-line func-names
	  this.promise.then = function(onfulfilled) {
	    var _resolve;
	    // eslint-disable-next-line func-names
	    var promise = new Promise(function(resolve) {
	      token.subscribe(resolve);
	      _resolve = resolve;
	    }).then(onfulfilled);

	    promise.cancel = function reject() {
	      token.unsubscribe(_resolve);
	    };

	    return promise;
	  };

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

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

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

	/**
	 * Subscribe to the cancel signal
	 */

	CancelToken.prototype.subscribe = function subscribe(listener) {
	  if (this.reason) {
	    listener(this.reason);
	    return;
	  }

	  if (this._listeners) {
	    this._listeners.push(listener);
	  } else {
	    this._listeners = [listener];
	  }
	};

	/**
	 * Unsubscribe from the cancel signal
	 */

	CancelToken.prototype.unsubscribe = function unsubscribe(listener) {
	  if (!this._listeners) {
	    return;
	  }
	  var index = this._listeners.indexOf(listener);
	  if (index !== -1) {
	    this._listeners.splice(index, 1);
	  }
	};

	/**
	 * 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
	  };
	};

	CancelToken_1 = CancelToken;
	return CancelToken_1;
}

var spread;
var hasRequiredSpread;

function requireSpread () {
	if (hasRequiredSpread) return spread;
	hasRequiredSpread = 1;

	/**
	 * 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}
	 */
	spread = function spread(callback) {
	  return function wrap(arr) {
	    return callback.apply(null, arr);
	  };
	};
	return spread;
}

var isAxiosError;
var hasRequiredIsAxiosError;

function requireIsAxiosError () {
	if (hasRequiredIsAxiosError) return isAxiosError;
	hasRequiredIsAxiosError = 1;

	var utils = utils$9;

	/**
	 * Determines whether the payload is an error thrown by Axios
	 *
	 * @param {*} payload The value to test
	 * @returns {boolean} True if the payload is an error thrown by Axios, otherwise false
	 */
	isAxiosError = function isAxiosError(payload) {
	  return utils.isObject(payload) && (payload.isAxiosError === true);
	};
	return isAxiosError;
}

var utils = utils$9;
var bind = bind$2;
var Axios$1 = Axios_1;
var mergeConfig = mergeConfig$2;
var defaults = defaults_1;

/**
 * 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$1(defaultConfig);
  var instance = bind(Axios$1.prototype.request, context);

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

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

  // Factory for creating new instances
  instance.create = function create(instanceConfig) {
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

  return instance;
}

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

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

// Expose Cancel & CancelToken
axios.CanceledError = requireCanceledError();
axios.CancelToken = requireCancelToken();
axios.isCancel = requireIsCancel();
axios.VERSION = requireData().version;
axios.toFormData = requireToFormData();

// Expose AxiosError class
axios.AxiosError = requireAxiosError();

// alias for CanceledError for backward compatibility
axios.Cancel = axios.CanceledError;

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

// Expose isAxiosError
axios.isAxiosError = requireIsAxiosError();

axios$1.exports = axios;

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

(function (module) {
	module.exports = axios$1.exports;
} (axios$2));

var Axios = /*@__PURE__*/getDefaultExportFromCjs(axios$2.exports);

/*
 This file is part of GNU Taler
 (C) 2019 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>

 SPDX-License-Identifier: AGPL3.0-or-later
*/
const logger$4 = new Logger("NodeHttpLib.ts");
/**
 * Implementation of the HTTP request library interface for node.
 */
class NodeHttpLib {
    constructor() {
        this.throttle = new RequestThrottler();
        this.throttlingEnabled = true;
    }
    /**
     * Set whether requests should be throttled.
     */
    setThrottling(enabled) {
        this.throttlingEnabled = enabled;
    }
    async fetch(url, opt) {
        var _a, _b;
        const method = (_a = opt === null || opt === void 0 ? void 0 : opt.method) !== null && _a !== void 0 ? _a : "GET";
        let body = opt === null || opt === void 0 ? void 0 : opt.body;
        logger$4.trace(`Requesting ${method} ${url}`);
        const parsedUrl = new URL$1(url);
        if (this.throttlingEnabled && this.throttle.applyThrottle(url)) {
            throw TalerError.fromDetail(TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, {
                requestMethod: method,
                requestUrl: url,
                throttleStats: this.throttle.getThrottleStats(url),
            }, `request to origin ${parsedUrl.origin} was throttled`);
        }
        let timeoutMs;
        if (typeof ((_b = opt === null || opt === void 0 ? void 0 : opt.timeout) === null || _b === void 0 ? void 0 : _b.d_ms) === "number") {
            timeoutMs = opt.timeout.d_ms;
        }
        else {
            timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS;
        }
        // FIXME: Use AbortController / etc. to handle cancellation
        let resp;
        try {
            let respPromise = Axios({
                method,
                url: url,
                responseType: "arraybuffer",
                headers: opt === null || opt === void 0 ? void 0 : opt.headers,
                validateStatus: () => true,
                transformResponse: (x) => x,
                data: body,
                timeout: timeoutMs,
                maxRedirects: 0,
            });
            if (opt === null || opt === void 0 ? void 0 : opt.cancellationToken) {
                respPromise = opt.cancellationToken.racePromise(respPromise);
            }
            resp = await respPromise;
        }
        catch (e) {
            throw TalerError.fromDetail(TalerErrorCode.WALLET_NETWORK_ERROR, {
                requestUrl: url,
                requestMethod: method,
            }, `${e.message}`);
        }
        const makeText = async () => {
            var _a;
            (_a = opt === null || opt === void 0 ? void 0 : opt.cancellationToken) === null || _a === void 0 ? void 0 : _a.throwIfCancelled();
            const respText = new Uint8Array(resp.data);
            return bytesToString(respText);
        };
        const makeJson = async () => {
            var _a;
            (_a = opt === null || opt === void 0 ? void 0 : opt.cancellationToken) === null || _a === void 0 ? void 0 : _a.throwIfCancelled();
            let responseJson;
            const respText = await makeText();
            try {
                responseJson = JSON.parse(respText);
            }
            catch (e) {
                logger$4.trace(`invalid json: '${resp.data}'`);
                throw TalerError.fromDetail(TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, {
                    httpStatusCode: resp.status,
                    requestUrl: url,
                    requestMethod: method,
                }, "Could not parse response body as JSON");
            }
            if (responseJson === null || typeof responseJson !== "object") {
                logger$4.trace(`invalid json (not an object): '${respText}'`);
                throw TalerError.fromDetail(TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, {
                    httpStatusCode: resp.status,
                    requestUrl: url,
                    requestMethod: method,
                }, `invalid JSON`);
            }
            return responseJson;
        };
        const makeBytes = async () => {
            var _a;
            (_a = opt === null || opt === void 0 ? void 0 : opt.cancellationToken) === null || _a === void 0 ? void 0 : _a.throwIfCancelled();
            if (typeof resp.data.byteLength !== "number") {
                throw Error("expected array buffer");
            }
            const buf = resp.data;
            return buf;
        };
        const headers = new Headers();
        for (const hn of Object.keys(resp.headers)) {
            headers.set(hn, resp.headers[hn]);
        }
        return {
            requestUrl: url,
            requestMethod: method,
            headers,
            status: resp.status,
            text: makeText,
            json: makeJson,
            bytes: makeBytes,
        };
    }
    async get(url, opt) {
        return this.fetch(url, Object.assign({ method: "GET" }, opt));
    }
    async postJson(url, body, opt) {
        return this.fetch(url, Object.assign({ method: "POST", body }, opt));
    }
}

/*
 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/>
 */
const logger$3 = new Logger("nodeThreadWorker.ts");
const f = __filename;
const workerCode = `
  // Try loading the glue library for embedded
  try {
    require("akono");
  } catch (e) {
    try {
      require("iono");
    } catch (e2) {
      // Probably we're not on embedded ...
    }
  }
  const worker_threads = require('worker_threads');
  const parentPort = worker_threads.parentPort;
  let tw;
  try {
    tw = require("${f}");
  } catch (e) {
    console.warn("could not load from ${f}");
  }
  if (!tw) {
    try {
      tw = require("@gnu-taler/taler-wallet-embedded");
    } catch (e) {
      console.warn("could not load taler-wallet-embedded either");
      throw e;
    }
  }
  if (typeof tw.handleWorkerMessage !== "function") {
    throw Error("module loaded for crypto worker lacks handleWorkerMessage");
  }
  if (typeof tw.handleWorkerError !== "function") {
    throw Error("module loaded for crypto worker lacks handleWorkerError");
  }
  parentPort.on("message", tw.handleWorkerMessage);
  parentPort.on("error", tw.handleWorkerError);
`;
/**
 * This function is executed in the worker thread to handle
 * a message.
 */
function handleWorkerMessage(msg) {
    const req = msg.req;
    if (typeof req !== "object") {
        console.error("request must be an object");
        return;
    }
    const id = msg.id;
    if (typeof id !== "number") {
        console.error("RPC id must be number");
        return;
    }
    const operation = msg.operation;
    if (typeof operation !== "string") {
        console.error("RPC operation must be string");
        return;
    }
    const handleRequest = async () => {
        var _a;
        const impl = nativeCryptoR;
        if (!(operation in impl)) {
            console.error(`crypto operation '${operation}' not found`);
            return;
        }
        try {
            const result = await impl[operation](impl, req);
            // eslint-disable-next-line @typescript-eslint/no-var-requires
            const _r = "require";
            const worker_threads = module[_r]("worker_threads");
            // const worker_threads = require("worker_threads");
            const p = worker_threads.parentPort;
            (_a = worker_threads.parentPort) === null || _a === void 0 ? void 0 : _a.postMessage;
            if (p) {
                p.postMessage({ data: { result, id } });
            }
            else {
                console.error("parent port not available (not running in thread?");
            }
        }
        catch (e) {
            console.error("error during operation", e);
            return;
        }
    };
    handleRequest().catch((e) => {
        console.error("error in node worker", e);
    });
}
function handleWorkerError(e) {
    console.log("got error from worker", e);
}
class NodeThreadCryptoWorkerFactory {
    startWorker() {
        if (typeof require === "undefined") {
            throw Error("cannot make worker, require(...) not defined");
        }
        return new NodeThreadCryptoWorker();
    }
    getConcurrency() {
        return Math.max(1, require$$0__default["default"].cpus().length - 1);
    }
}
/**
 * Worker implementation that uses node subprocesses.
 */
class NodeThreadCryptoWorker {
    constructor() {
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const _r = "require";
        const worker_threads = module[_r]("worker_threads");
        logger$3.trace("starting node crypto worker");
        this.nodeWorker = new worker_threads.Worker(workerCode, { eval: true });
        this.nodeWorker.on("error", (err) => {
            console.error("error in node worker:", err);
            if (this.onerror) {
                this.onerror(err);
            }
        });
        this.nodeWorker.on("exit", (err) => {
            logger$3.trace(`worker exited with code ${err}`);
        });
        this.nodeWorker.on("message", (v) => {
            if (this.onmessage) {
                this.onmessage(v);
            }
        });
        this.nodeWorker.unref();
    }
    /**
     * 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.nodeWorker.postMessage(msg);
    }
    /**
     * Forcibly terminate the worker thread.
     */
    terminate() {
        this.nodeWorker.terminate();
    }
}

/*
 This file is part of GNU Taler
 (C) 2022 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$2 = new Logger("synchronousWorkerFactory.ts");
class CryptoRpcClient {
    constructor() {
        this.requests = [];
        const stdoutChunks = [];
        this.proc = child_process__default["default"].spawn("taler-crypto-worker", {
            //stdio: ["pipe", "pipe", "inherit"],
            stdio: ["pipe", "pipe", "inherit"],
            detached: true,
        });
        this.proc.on("close", () => {
            logger$2.error("child process exited");
        });
        this.proc.stdout.unref();
        this.proc.stdin.unref();
        this.proc.unref();
        this.proc.stdout.on("data", (x) => {
            // console.log("got chunk", x.toString("utf-8"));
            if (x instanceof Buffer) {
                const nlIndex = x.indexOf("\n");
                if (nlIndex >= 0) {
                    const before = x.slice(0, nlIndex);
                    const after = x.slice(nlIndex + 1);
                    stdoutChunks.push(after);
                    const str = Buffer.concat([...stdoutChunks, before]).toString("utf-8");
                    const req = this.requests.shift();
                    if (!req) {
                        throw Error("request was undefined");
                    }
                    if (this.requests.length === 0) {
                        this.proc.unref();
                    }
                    //logger.info(`got response: ${str}`);
                    req.p.resolve(JSON.parse(str));
                }
                else {
                    stdoutChunks.push(x);
                }
            }
            else {
                throw Error(`unexpected data chunk type (${typeof x})`);
            }
        });
    }
    async queueRequest(req) {
        const p = openPromise$1();
        if (this.requests.length === 0) {
            this.proc.ref();
        }
        this.requests.push({ req, p });
        this.proc.stdin.write(`${JSON.stringify(req)}\n`);
        return p.promise;
    }
}

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger$1 = new Logger("synchronousWorker.ts");
/**
 * Worker implementation that uses node subprocesses.
 */
class SynchronousCryptoWorker {
    constructor() {
        this.onerror = undefined;
        this.onmessage = undefined;
        this.cryptoImplR = Object.assign({}, nativeCryptoR);
        if (process.env["TALER_WALLET_PRIMITIVE_WORKER"]) {
            logger$1.info("using RPC for some crypto operations");
            const rpc = (this.rpcClient = new CryptoRpcClient());
            this.cryptoImplR.eddsaSign = async (_, req) => {
                return await rpc.queueRequest({
                    op: "eddsa_sign",
                    args: {
                        msg: req.msg,
                        priv: req.priv,
                    },
                });
            };
            this.cryptoImplR.setupRefreshPlanchet = async (_, req) => {
                const res = await rpc.queueRequest({
                    op: "setup_refresh_planchet",
                    args: {
                        coin_index: req.coinNumber,
                        transfer_secret: req.transferSecret,
                    },
                });
                return {
                    bks: res.blinding_key,
                    coinPriv: res.coin_priv,
                    coinPub: res.coin_pub,
                };
            };
            this.cryptoImplR.rsaBlind = async (_, req) => {
                const res = await rpc.queueRequest({
                    op: "rsa_blind",
                    args: {
                        bks: req.bks,
                        hm: req.hm,
                        pub: req.pub,
                    },
                });
                return {
                    blinded: res.blinded,
                };
            };
            this.cryptoImplR.keyExchangeEcdheEddsa = async (_, req) => {
                const res = await rpc.queueRequest({
                    op: "kx_ecdhe_eddsa",
                    args: {
                        ecdhe_priv: req.ecdhePriv,
                        eddsa_pub: req.eddsaPub,
                    },
                });
                return {
                    h: res.h,
                };
            };
            this.cryptoImplR.eddsaGetPublic = async (_, req) => {
                const res = await rpc.queueRequest({
                    op: "eddsa_get_public",
                    args: {
                        eddsa_priv: req.priv,
                    },
                });
                return {
                    pub: res.eddsa_pub,
                };
            };
            this.cryptoImplR.ecdheGetPublic = async (_, req) => {
                const res = await rpc.queueRequest({
                    op: "ecdhe_get_public",
                    args: {
                        ecdhe_priv: req.priv,
                    },
                });
                return {
                    pub: res.ecdhe_pub,
                };
            };
        }
    }
    /**
     * 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;
        }
    }
    dispatchMessage(msg) {
        if (this.onmessage) {
            this.onmessage({ data: msg });
        }
    }
    async handleRequest(operation, id, req) {
        const impl = this.cryptoImplR;
        if (!(operation in impl)) {
            console.error(`crypto operation '${operation}' not found`);
            return;
        }
        let result;
        try {
            result = await impl[operation](impl, req);
        }
        catch (e) {
            logger$1.error(`error during operation '${operation}': ${e}`);
            return;
        }
        try {
            setTimeout(() => this.dispatchMessage({ result, id }), 0);
        }
        catch (e) {
            logger$1.error("got error during dispatch", e);
        }
    }
    /**
     * Send a message to the worker thread.
     */
    postMessage(msg) {
        const req = msg.req;
        if (typeof req !== "object") {
            console.error("request must be an object");
            return;
        }
        const id = msg.id;
        if (typeof id !== "number") {
            console.error("RPC id must be number");
            return;
        }
        const operation = msg.operation;
        if (typeof operation !== "string") {
            console.error("RPC operation must be string");
            return;
        }
        this.handleRequest(operation, id, req).catch((e) => {
            console.error("Error while handling crypto request:", e);
        });
    }
    /**
     * Forcibly terminate the worker thread.
     */
    terminate() {
        // This is a no-op.
    }
}

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * The synchronous crypto worker produced by this factory doesn't run in the
 * background, but actually blocks the caller until the operation is done.
 */
class SynchronousCryptoWorkerFactory {
    startWorker() {
        if (typeof require === "undefined") {
            throw Error("cannot make worker, require(...) not defined");
        }
        return new SynchronousCryptoWorker();
    }
    getConcurrency() {
        return 1;
    }
}

/*
 This file is part of GNU Taler
 (C) 2019 Taler Systems S.A.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
const logger = new Logger("headless/helpers.ts");
/**
 * Generate a random alphanumeric ID.  Does *not* use cryptographically
 * secure randomness.
 */
function makeId(length) {
    let result = "";
    const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
    for (let i = 0; i < length; i++) {
        result += characters.charAt(Math.floor(Math.random() * characters.length));
    }
    return result;
}
/**
 * Get a wallet instance with default settings for node.
 */
async function getDefaultNodeWallet(args = {}) {
    const res = await getDefaultNodeWallet2(args);
    return res.wallet;
}
/**
 * Get a wallet instance with default settings for node.
 *
 * Extended version that allows getting DB stats.
 */
async function getDefaultNodeWallet2(args = {}) {
    BridgeIDBFactory.enableTracing = false;
    const myBackend = new MemoryBackend();
    myBackend.enableTracing = false;
    const storagePath = args.persistentStoragePath;
    if (storagePath) {
        try {
            const dbContentStr = fs__namespace.readFileSync(storagePath, {
                encoding: "utf-8",
            });
            const dbContent = JSON.parse(dbContentStr);
            myBackend.importDump(dbContent);
        }
        catch (e) {
            const code = e.code;
            if (code === "ENOENT") {
                logger.trace("wallet file doesn't exist yet");
            }
            else {
                logger.error("could not open wallet database file");
                throw e;
            }
        }
        myBackend.afterCommitCallback = async () => {
            logger.trace("committing database");
            // Allow caller to stop persisting the wallet.
            if (args.persistentStoragePath === undefined) {
                return;
            }
            const tmpPath = `${args.persistentStoragePath}-${makeId(5)}.tmp`;
            const dbContent = myBackend.exportDump();
            fs__namespace.writeFileSync(tmpPath, JSON.stringify(dbContent, undefined, 2), {
                encoding: "utf-8",
            });
            // Atomically move the temporary file onto the DB path.
            fs__namespace.renameSync(tmpPath, args.persistentStoragePath);
            logger.trace("committing database done");
        };
    }
    BridgeIDBFactory.enableTracing = false;
    const myBridgeIdbFactory = new BridgeIDBFactory(myBackend);
    const myIdbFactory = myBridgeIdbFactory;
    let myHttpLib;
    if (args.httpLib) {
        myHttpLib = args.httpLib;
    }
    else {
        myHttpLib = new NodeHttpLib();
    }
    const myVersionChange = () => {
        logger.error("version change requested, should not happen");
        throw Error("BUG: wallet DB version change event can't happen with memory IDB");
    };
    shimIndexedDB(myBridgeIdbFactory);
    const myDb = await openTalerDatabase(myIdbFactory, myVersionChange);
    let workerFactory;
    if (process.env["TALER_WALLET_SYNC_CRYPTO"]) {
        logger.info("using synchronous crypto worker");
        workerFactory = new SynchronousCryptoWorkerFactory();
    }
    else {
        try {
            // Try if we have worker threads available, fails in older node versions.
            const _r = "require";
            // eslint-disable-next-line no-unused-vars
            const worker_threads = module[_r]("worker_threads");
            // require("worker_threads");
            workerFactory = new NodeThreadCryptoWorkerFactory();
        }
        catch (e) {
            logger.warn("worker threads not available, falling back to synchronous workers");
            workerFactory = new SynchronousCryptoWorkerFactory();
        }
    }
    const timer = new SetTimeoutTimerAPI();
    const w = await Wallet.create(myDb, myHttpLib, timer, workerFactory);
    if (args.notifyHandler) {
        w.addNotificationListener(args.notifyHandler);
    }
    return {
        wallet: w,
        getDbStats: () => myBackend.accessStats,
    };
}

/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

 GNU 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.

 GNU 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
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
class NativeHttpLib {
    constructor(sendMessage) {
        this.sendMessage = sendMessage;
        this.useNfcTunnel = false;
        this.nodeHttpLib = new NodeHttpLib();
        this.requestId = 1;
        this.requestMap = {};
    }
    fetch(url, opt) {
        return this.nodeHttpLib.fetch(url, opt);
    }
    get(url, opt) {
        if (this.useNfcTunnel) {
            const myId = this.requestId++;
            const p = openPromise$1();
            this.requestMap[myId] = p;
            const request = {
                method: "get",
                url,
            };
            this.sendMessage(JSON.stringify({
                type: "tunnelHttp",
                request,
                id: myId,
            }));
            return p.promise;
        }
        else {
            return this.nodeHttpLib.get(url, opt);
        }
    }
    postJson(url, body, opt) {
        if (this.useNfcTunnel) {
            const myId = this.requestId++;
            const p = openPromise$1();
            this.requestMap[myId] = p;
            const request = {
                method: "postJson",
                url,
                body,
            };
            this.sendMessage(JSON.stringify({ type: "tunnelHttp", request, id: myId }));
            return p.promise;
        }
        else {
            return this.nodeHttpLib.postJson(url, body, opt);
        }
    }
    handleTunnelResponse(msg) {
        const myId = msg.id;
        const p = this.requestMap[myId];
        if (!p) {
            console.error(`no matching request for tunneled HTTP response, id=${myId}`);
        }
        const headers = new Headers();
        if (msg.status != 0) {
            const resp = {
                // FIXME: pass through this URL
                requestUrl: "",
                headers,
                status: msg.status,
                requestMethod: "FIXME",
                json: () => __awaiter(this, void 0, void 0, function* () { return JSON.parse(msg.responseText); }),
                text: () => __awaiter(this, void 0, void 0, function* () { return msg.responseText; }),
                bytes: () => __awaiter(this, void 0, void 0, function* () {
                    throw Error("bytes() not supported for tunnel response");
                }),
            };
            p.resolve(resp);
        }
        else {
            p.reject(new Error(`unexpected HTTP status code ${msg.status}`));
        }
        delete this.requestMap[myId];
    }
}
function sendNativeMessage(ev) {
    // @ts-ignore
    const sendMessage = globalThis.__native_sendMessage;
    if (typeof sendMessage !== "function") {
        const errMsg = "FATAL: cannot install native wallet listener: native functions missing";
        console.error(errMsg);
        throw new Error(errMsg);
    }
    const m = JSON.stringify(ev);
    // @ts-ignore
    sendMessage(m);
}
class NativeWalletMessageHandler {
    constructor() {
        this.wp = openPromise$1();
        this.httpLib = new NodeHttpLib();
    }
    /**
     * Handle a request from the native wallet.
     */
    handleMessage(operation, id, args) {
        return __awaiter(this, void 0, void 0, function* () {
            const wrapResponse = (result) => {
                return {
                    type: "response",
                    id,
                    operation,
                    result,
                };
            };
            const reinit = () => __awaiter(this, void 0, void 0, function* () {
                const w = yield getDefaultNodeWallet(this.walletArgs);
                this.maybeWallet = w;
                yield w.handleCoreApiRequest("initWallet", "native-init", {});
                w.runTaskLoop().catch((e) => {
                    console.error("Error during wallet retry loop", e);
                });
                this.wp.resolve(w);
            });
            switch (operation) {
                case "init": {
                    this.walletArgs = {
                        notifyHandler: (notification) => __awaiter(this, void 0, void 0, function* () {
                            sendNativeMessage({ type: "notification", payload: notification });
                        }),
                        persistentStoragePath: args.persistentStoragePath,
                        httpLib: this.httpLib,
                    };
                    yield reinit();
                    return wrapResponse({
                        supported_protocol_versions: {
                            exchange: WALLET_EXCHANGE_PROTOCOL_VERSION,
                            merchant: WALLET_MERCHANT_PROTOCOL_VERSION,
                        },
                    });
                }
                case "startTunnel": {
                    // this.httpLib.useNfcTunnel = true;
                    throw Error("not implemented");
                }
                case "stopTunnel": {
                    // this.httpLib.useNfcTunnel = false;
                    throw Error("not implemented");
                }
                case "tunnelResponse": {
                    // httpLib.handleTunnelResponse(msg.args);
                    throw Error("not implemented");
                }
                case "reset": {
                    const oldArgs = this.walletArgs;
                    this.walletArgs = Object.assign({}, oldArgs);
                    if (oldArgs && oldArgs.persistentStoragePath) {
                        try {
                            fs__default["default"].unlinkSync(oldArgs.persistentStoragePath);
                        }
                        catch (e) {
                            console.error("Error while deleting the wallet db:", e);
                        }
                        // Prevent further storage!
                        this.walletArgs.persistentStoragePath = undefined;
                    }
                    const wallet = yield this.wp.promise;
                    wallet.stop();
                    this.wp = openPromise$1();
                    this.maybeWallet = undefined;
                    yield reinit();
                    return wrapResponse({});
                }
                default: {
                    const wallet = yield this.wp.promise;
                    return yield wallet.handleCoreApiRequest(operation, id, args);
                }
            }
        });
    }
}
function installNativeWalletListener() {
    const handler = new NativeWalletMessageHandler();
    const onMessage = (msgStr) => __awaiter(this, void 0, void 0, function* () {
        if (typeof msgStr !== "string") {
            console.error("expected string as message");
            return;
        }
        const msg = JSON.parse(msgStr);
        const operation = msg.operation;
        if (typeof operation !== "string") {
            console.error("message to native wallet helper must contain operation of type string");
            return;
        }
        const id = msg.id;
        console.log(`native listener: got request for ${operation} (${id})`);
        try {
            const respMsg = yield handler.handleMessage(operation, id, msg.args);
            console.log(`native listener: sending success response for ${operation} (${id})`);
            sendNativeMessage(respMsg);
        }
        catch (e) {
            const respMsg = {
                type: "error",
                id,
                operation,
                error: getErrorDetailFromException(e),
            };
            sendNativeMessage(respMsg);
            return;
        }
    });
    // @ts-ignore
    globalThis.__native_onMessage = onMessage;
    console.log("native wallet listener installed");
}

exports.NativeHttpLib = NativeHttpLib;
exports.handleWorkerError = handleWorkerError;
exports.handleWorkerMessage = handleWorkerMessage;
exports.installNativeWalletListener = installNativeWalletListener;
