UNPKG

virgil-sdk

Version:
3,523 lines 122 kB
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

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

function createCommonjsModule(fn, module) {
	return module = { exports: {} }, fn(module, module.exports), module.exports;
}

var base64 = createCommonjsModule(function (module, exports) {
(function(root) {

	// Detect free variables `exports`.
	var freeExports = exports;

	// Detect free variable `module`.
	var freeModule = module &&
		module.exports == freeExports && module;

	// Detect free variable `global`, from Node.js or Browserified code, and use
	// it as `root`.
	var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal;
	if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
		root = freeGlobal;
	}

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

	var InvalidCharacterError = function(message) {
		this.message = message;
	};
	InvalidCharacterError.prototype = new Error;
	InvalidCharacterError.prototype.name = 'InvalidCharacterError';

	var error = function(message) {
		// Note: the error messages used throughout this file match those used by
		// the native `atob`/`btoa` implementation in Chromium.
		throw new InvalidCharacterError(message);
	};

	var TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
	// http://whatwg.org/html/common-microsyntaxes.html#space-character
	var REGEX_SPACE_CHARACTERS = /[\t\n\f\r ]/g;

	// `decode` is designed to be fully compatible with `atob` as described in the
	// HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob
	// The optimized base64-decoding algorithm used is based on @atk’s excellent
	// implementation. https://gist.github.com/atk/1020396
	var decode = function(input) {
		input = String(input)
			.replace(REGEX_SPACE_CHARACTERS, '');
		var length = input.length;
		if (length % 4 == 0) {
			input = input.replace(/==?$/, '');
			length = input.length;
		}
		if (
			length % 4 == 1 ||
			// http://whatwg.org/C#alphanumeric-ascii-characters
			/[^+a-zA-Z0-9/]/.test(input)
		) {
			error(
				'Invalid character: the string to be decoded is not correctly encoded.'
			);
		}
		var bitCounter = 0;
		var bitStorage;
		var buffer;
		var output = '';
		var position = -1;
		while (++position < length) {
			buffer = TABLE.indexOf(input.charAt(position));
			bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer;
			// Unless this is the first of a group of 4 characters…
			if (bitCounter++ % 4) {
				// …convert the first 8 bits to a single ASCII character.
				output += String.fromCharCode(
					0xFF & bitStorage >> (-2 * bitCounter & 6)
				);
			}
		}
		return output;
	};

	// `encode` is designed to be fully compatible with `btoa` as described in the
	// HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa
	var encode = function(input) {
		input = String(input);
		if (/[^\0-\xFF]/.test(input)) {
			// Note: no need to special-case astral symbols here, as surrogates are
			// matched, and the input is supposed to only contain ASCII anyway.
			error(
				'The string to be encoded contains characters outside of the ' +
				'Latin1 range.'
			);
		}
		var padding = input.length % 3;
		var output = '';
		var position = -1;
		var a;
		var b;
		var c;
		var buffer;
		// Make sure any padding is handled outside of the loop.
		var length = input.length - padding;

		while (++position < length) {
			// Read three bytes, i.e. 24 bits.
			a = input.charCodeAt(position) << 16;
			b = input.charCodeAt(++position) << 8;
			c = input.charCodeAt(++position);
			buffer = a + b + c;
			// Turn the 24 bits into four chunks of 6 bits each, and append the
			// matching character for each of them to the output.
			output += (
				TABLE.charAt(buffer >> 18 & 0x3F) +
				TABLE.charAt(buffer >> 12 & 0x3F) +
				TABLE.charAt(buffer >> 6 & 0x3F) +
				TABLE.charAt(buffer & 0x3F)
			);
		}

		if (padding == 2) {
			a = input.charCodeAt(position) << 8;
			b = input.charCodeAt(++position);
			buffer = a + b;
			output += (
				TABLE.charAt(buffer >> 10) +
				TABLE.charAt((buffer >> 4) & 0x3F) +
				TABLE.charAt((buffer << 2) & 0x3F) +
				'='
			);
		} else if (padding == 1) {
			buffer = input.charCodeAt(position);
			output += (
				TABLE.charAt(buffer >> 2) +
				TABLE.charAt((buffer << 4) & 0x3F) +
				'=='
			);
		}

		return output;
	};

	var base64 = {
		'encode': encode,
		'decode': decode,
		'version': '1.0.0'
	};

	// Some AMD build optimizers, like r.js, check for specific condition patterns
	// like the following:
	if (freeExports && !freeExports.nodeType) {
		if (freeModule) { // in Node.js or RingoJS v0.8.0+
			freeModule.exports = base64;
		} else { // in Narwhal or RingoJS v0.7.0-
			for (var key in base64) {
				base64.hasOwnProperty(key) && (freeExports[key] = base64[key]);
			}
		}
	} else { // in Rhino or a web browser
		root.base64 = base64;
	}

}(commonjsGlobal));
});

/**
 * Decodes the base64 encoded string into a `string`.
 * @hidden
 * @param {string} input
 * @returns {string}
 */
function base64Decode(input) {
    return base64.decode(input);
}
/**
 * Encodes the `input` string into a base64 `string`.
 * @hidden
 * @param {string} input
 * @returns {string}
 */
function base64Encode(input) {
    return base64.encode(input);
}
/**
 * Converts regular base64 encoded string to URL-safe base64 encoded string.
 * @hidden
 * @param {string} input - Regular base64 encoded string.
 * @returns {string} - URL-safe base64 encoded string.
 */
function base64UrlFromBase64(input) {
    input = input.split('=')[0];
    input = input.replace(/\+/g, '-').replace(/\//g, '_');
    return input;
}
/**
 * Converts URL-safe base64 encoded string to regular base64 encoded string.
 * @hidden
 * @param {string} input - URL-safe base64 encoded string.
 * @returns {string} - Regular base64 encoded string.
 */
function base64UrlToBase64(input) {
    input = input.replace(/-/g, '+').replace(/_/g, '/');
    switch (input.length % 4) {
        case 0: break; // no padding needed
        case 2:
            input = input + '==';
            break;
        case 3:
            input = input + '=';
            break;
        default:
            throw new Error('Invalid base64 string');
    }
    return input;
}
/**
 * * Encodes the `input` string into a string using URL-safe base64 encoding.
 *
 * @hidden
 *
 * @param {string} input - The input.
 * @returns {string}
 */
function base64UrlEncode(input) {
    let output = base64Encode(input);
    return base64UrlFromBase64(output);
}
/**
 * Decodes the URL-safe base64-encoded `input` string into a `string`.
 *
 * @hidden
 *
 * @param {string} input
 * @returns {string}
 */
function base64UrlDecode(input) {
    const str = base64UrlToBase64(input);
    return base64Decode(str);
}

/**
 * Converts javascript date object or timestamp in milliseconds
 * to Unix timestamp.
 *
 * @hidden
 *
 * @param {Date | number} date - The date or timestamp to convert.
 * @returns {number}
 */
function getUnixTimestamp(date) {
    let time;
    if (typeof date === 'number') {
        time = date;
    }
    else {
        time = date.getTime();
    }
    return Math.floor(time / 1000);
}
/**
 * Adds the given number of seconds to the given date.
 *
 * @hidden
 *
 * @param {Date | number} date - The date to add seconds to.
 * If `date` is a `number` it is treated as a timestamp in milliseconds.
 * @param {number} seconds - The number of seconds to add.
 * @returns {Date} - The new date.
 */
function addSeconds(date, seconds) {
    if (typeof date === 'number') {
        return new Date(date + seconds * 1000);
    }
    return new Date(date.getTime() + seconds * 1000);
}

/**
 * JWT Subject.
 * @hidden
 */
const SubjectPrefix = "identity-";
/**
 * JWT Issuer.
 * @hidden
 */
const IssuerPrefix = "virgil-";
/**
 * Content type of the token. Used to convey structural information
 * about the JWT.
 * @hidden
 */
const VirgilContentType = "virgil-jwt;v=1";
/**
 * Media type of the JWT.
 * @hidden
 */
const JwtContentType = "JWT";

/**
 * Class representing the JWT providing access to the
 * Virgil Security APIs.
 * Implements {@link IAccessToken} interface.
 */
class Jwt {
    constructor(header, body, signature) {
        if (typeof header === 'string') {
            const stringRepresentation = header;
            const parts = stringRepresentation.split('.');
            if (parts.length !== 3)
                throw new Error('Wrong JWT format');
            try {
                this.header = JSON.parse(base64UrlDecode(parts[0]));
                this.body = JSON.parse(base64UrlDecode(parts[1]));
                this.signature = base64UrlToBase64(parts[2]);
            }
            catch (e) {
                throw new Error('Wrong JWT format');
            }
            this.unsignedData = parts[0] + '.' + parts[1];
            this.stringRepresentation = stringRepresentation;
        }
        else if (typeof header === 'object' && typeof body === 'object') {
            this.header = header;
            this.body = body;
            this.signature = signature;
            this.unsignedData = this.headerBase64() + '.' + this.bodyBase64();
            this.stringRepresentation = this.signature == null
                ? this.unsignedData
                : this.unsignedData + '.' + this.signatureBase64();
        }
        else {
            throw new TypeError('Invalid arguments for function Jwt. ' +
                'Expected a string representation of a token, or header and body as objects');
        }
    }
    /**
     * Parses the string representation of the JWT into
     * an object representation.
     *
     * @param {string} jwtStr - The JWT string. Must have the following format:
     *
     * `base64UrlEncode(Header) + "." + base64UrlEncode(Body) + "." + base64UrlEncode(Signature)`
     *
     * See the {@link https://jwt.io/introduction/ | Introduction to JWT} for more details.
     *
     * @returns {Jwt}
     */
    static fromString(jwtStr) {
        return new Jwt(jwtStr);
    }
    /**
     * Returns the string representation of this JWT.
     * @returns {string}
     */
    toString() {
        return this.stringRepresentation;
    }
    /**
     * Retrieves the identity that is the subject of this JWT.
     * @returns {string}
     */
    identity() {
        if (this.body.sub.indexOf(SubjectPrefix) !== 0) {
            throw new Error('wrong sub format');
        }
        return this.body.sub.substr(SubjectPrefix.length);
    }
    /**
     * Retrieves the application ID that is the issuer of this JWT.
     * @returns {string}
     */
    appId() {
        if (this.body.iss.indexOf(IssuerPrefix) !== 0) {
            throw new Error('wrong iss format');
        }
        return this.body.iss.substr(IssuerPrefix.length);
    }
    /**
     * Returns a boolean indicating whether this JWT is (or will be)
     * expired at the given date or not.
     *
     * @param {Date} at - The date to check. Defaults to `new Date()`.
     * @returns {boolean} - `true` if token is expired, otherwise `false`.
     */
    isExpired(at = new Date) {
        const now = getUnixTimestamp(at);
        return this.body.exp < now;
    }
    headerBase64() {
        return base64UrlEncode(JSON.stringify(this.header));
    }
    bodyBase64() {
        return base64UrlEncode(JSON.stringify(this.body));
    }
    signatureBase64() {
        return base64UrlFromBase64(this.signature);
    }
}

/**
 * Test if `condition` is truthy. If it is not, an `Error` is thrown with a
 * `message` property equal to `message` parameter.
 * @hidden
 * @param {boolean} condition
 * @param {string} message
 */
function assert(condition, message) {
    if (!condition) {
        throw new Error(message);
    }
}

const DEFAULT_TOKEN_TTL = 20 * 60 * 1000; // 20 minutes
/**
 * Class responsible for JWT generation.
 */
class JwtGenerator {
    constructor(options) {
        validateOptions$1(options);
        this.appId = options.appId;
        this.apiKey = options.apiKey;
        this.apiKeyId = options.apiKeyId;
        this.accessTokenSigner = options.accessTokenSigner;
        this.millisecondsToLive = options.millisecondsToLive !== undefined
            ? Number(options.millisecondsToLive)
            : DEFAULT_TOKEN_TTL;
    }
    /**
     * Generates a token with the given identity as the subject and optional
     * additional data.
     * @param {string} identity - Identity to be associated with JWT (i.e.
     * the Subject).
     * @param {IExtraData} ada - Additional data to be encoded in the JWT.
     * @returns {Jwt}
     */
    generateToken(identity, ada) {
        if (!identity) {
            throw new TypeError('Illegal arguments for function `generateToken`. Argument `identity` is required.');
        }
        const iat = getUnixTimestamp(new Date());
        const exp = getUnixTimestamp(new Date().getTime() + this.millisecondsToLive);
        const body = {
            iss: IssuerPrefix + this.appId,
            sub: SubjectPrefix + identity,
            iat,
            exp,
            ada
        };
        const header = {
            alg: this.accessTokenSigner.getAlgorithm(),
            kid: this.apiKeyId,
            typ: JwtContentType,
            cty: VirgilContentType
        };
        const unsignedJwt = new Jwt(header, body);
        const signature = this.accessTokenSigner.generateTokenSignature({ value: unsignedJwt.unsignedData, encoding: 'utf8' }, this.apiKey);
        return new Jwt(header, body, signature.toString('base64'));
    }
}
function validateOptions$1(opts) {
    const invalidOptionMessage = (name) => `Invalid JwtGenerator options. \`${name}\` is required`;
    assert(opts != null, 'JwtGenerator options must be provided');
    assert(opts.apiKey != null, invalidOptionMessage('apiKey'));
    assert(opts.apiKeyId != null, invalidOptionMessage('apiKeyId'));
    assert(opts.appId != null, invalidOptionMessage('appId'));
    assert(opts.accessTokenSigner != null, invalidOptionMessage('accessTokenSigner'));
}

/**
 * Class responsible for verification of JWTs.
 */
class JwtVerifier {
    constructor(options) {
        validateOptions(options);
        this.accessTokenSigner = options.accessTokenSigner;
        this.apiPublicKey = options.apiPublicKey;
        this.apiKeyId = options.apiKeyId;
    }
    /**
     * Verifies the validity of the given JWT.
     * @param {Jwt} token - The JWT to verify.
     * @returns {boolean}
     */
    verifyToken(token) {
        if (token == null) {
            throw new Error('Token is empty');
        }
        if (!this.allFieldsAreCorrect(token)) {
            return false;
        }
        return this.accessTokenSigner.verifyTokenSignature({ value: token.unsignedData, encoding: 'utf8' }, { value: token.signature, encoding: 'base64' }, this.apiPublicKey);
    }
    allFieldsAreCorrect(token) {
        return token.header.kid == this.apiKeyId
            && token.header.alg == this.accessTokenSigner.getAlgorithm()
            && token.header.cty == VirgilContentType
            && token.header.typ == JwtContentType;
    }
}
function validateOptions(opts) {
    const invalidOptionMessage = (name) => `Invalid JwtVerifier options. \`${name}\` is required`;
    assert(opts != null, 'JwtVerifier options must be provided');
    assert(opts.apiPublicKey != null, invalidOptionMessage('apiPublicKey'));
    assert(opts.apiKeyId != null, invalidOptionMessage('apiKeyId'));
    assert(opts.accessTokenSigner != null, invalidOptionMessage('accessTokenSigner'));
}

const TOKEN_EXPIRATION_MARGIN = 5;
/**
 * Implementation of {@link IAccessTokenProvider} that caches the JWT
 * in memory while it's fresh (i.e. not expired) and uses the user-provided
 * callback function to get the JWT when requested by the clients.
 */
class CachingJwtProvider {
    /**
     * Creates a new instance of `CachingJwtProvider`.
     * @param {GetJwtCallback} renewJwtFn - The function that will be called
     * whenever the fresh JWT is needed. If the `renewJwtFn` returns the JWT
     * as a string, it will be converted to {@link Jwt} instance automatically.
     * @param {Jwt|string} [initialToken] - Optional initial JWT.
     */
    constructor(renewJwtFn, initialToken) {
        if (typeof renewJwtFn !== 'function') {
            throw new TypeError('`renewJwtFn` must be a function');
        }
        if (initialToken) {
            let jwt;
            if (typeof initialToken === 'string') {
                jwt = Jwt.fromString(initialToken);
            }
            else if (initialToken instanceof Jwt) {
                jwt = initialToken;
            }
            else {
                throw new Error(`Expected "initialToken" to be a string or an instance of Jwt, got ${typeof initialToken}`);
            }
            this.cachedJwt = jwt;
        }
        this.getJwt = (context) => {
            if (this.cachedJwt && !this.cachedJwt.isExpired(addSeconds(new Date, TOKEN_EXPIRATION_MARGIN))) {
                return Promise.resolve(this.cachedJwt);
            }
            if (this.jwtPromise) {
                return this.jwtPromise;
            }
            this.jwtPromise = Promise.resolve(renewJwtFn(context))
                .then(token => {
                const jwt = typeof token === 'string' ? Jwt.fromString(token) : token;
                this.cachedJwt = jwt;
                this.jwtPromise = undefined;
                return jwt;
            }).catch(err => {
                this.jwtPromise = undefined;
                throw err;
            });
            return this.jwtPromise;
        };
    }
    /**
     * Returns a `Promise` resolved with the cached token if it's fresh, or the
     * token obtained by the call to the `renewJwtCallback` otherwise. The token
     * obtained from the `renewJwtCallback` is then cached. If the `renewJwtCallback`
     * returns the JWT as a string, it is converted to {@link Jwt} instance before returning.
     * @param {ITokenContext} context
     * @returns {Promise<IAccessToken>}
     */
    getToken(context) {
        return this.getJwt(context);
    }
}

/**
 * Implementation of {@link IAccessToken} that calls the user-provided
 * callback function to get the JWT when requested by the clients.
 */
class CallbackJwtProvider {
    /**
     * Creates a new instance of `CallbackJwtProvider`.
     *
     * @param {GetJwtCallback} getJwtFn - The function that will be called
     * whenever the JWT is needed. If the `getJwtFn` returns the JWT as a
     * string, it will be converted to {@link Jwt} instance automatically.
     */
    constructor(getJwtFn) {
        if (typeof getJwtFn !== 'function') {
            throw new TypeError('`getJwtFn` must be a function');
        }
        this.getJwt = (context) => Promise.resolve(getJwtFn(context))
            .then(token => typeof token === 'string' ? Jwt.fromString(token) : token);
    }
    /**
     * Returns a `Promise` resolved with the {@link Jwt} instance obtained
     * by the call to the {@link CallbackJwtProvider.getJwt}. If the
     * `getJwtFn` returns the JWT as a string, it is converted to
     * {@link Jwt} instance before returning.
     *
     * @param {ITokenContext} context
     * @returns {Promise<IAccessToken>}
     */
    getToken(context) {
        return this.getJwt(context);
    }
}

/**
 * Implementation of {@link IAccessTokenProvider} that returns a
 * user-provided constant access token whenever it is requested by the clients.
 */
class ConstAccessTokenProvider {
    /**
     * Creates a new instance of `ConstAccessTokenProvider`
     * @param {IAccessToken} accessToken - The access token to be returned
     * whenever it is requested.
     */
    constructor(accessToken) {
        this.accessToken = accessToken;
        if (accessToken == null) {
            throw new TypeError('`accessToken` is required');
        }
    }
    ;
    /**
     * Returns a `Promise` fulfilled with the
     * {@link ConstAccessTokenProvider.accessToken} provided to the constructor
     * of this instance.
     *
     * @param {ITokenContext} context
     * @returns {Promise<IAccessToken>}
     */
    getToken(context) {
        return Promise.resolve(this.accessToken);
    }
}

/**
 * Implementation of {@link IAccessTokenProvider} that generates a
 * new JWT whenever it is requested by the clients.
 *
 * This class is meant to be used on the server side only.
 */
class GeneratorJwtProvider {
    /**
     * Creates a new instance of `GeneratorJwtProvider` with the given
     * {@link JwtGenerator}, additional data and default identity.
     *
     * @param {JwtGenerator} jwtGenerator - Object to delegate the JWT generation to.
     * @param {IExtraData} additionalData - Additional data to include with the JWT.
     * @param {string} defaultIdentity - Identity of the user to include in the token
     * when none is provided explicitly by the client.
     */
    constructor(jwtGenerator, additionalData, defaultIdentity) {
        this.jwtGenerator = jwtGenerator;
        this.additionalData = additionalData;
        this.defaultIdentity = defaultIdentity;
        if (jwtGenerator == null) {
            throw new TypeError('`jwtGenerator` is required');
        }
    }
    /**
     * Returns a `Promise` fulfilled with the JWT obtained from the call
     * to {@link GeneratorJwtProvider.jwtGenerator} {@link JwtGenerator.generateToken}
     * method, passing it the {@link GeneratorJwtProvider.additionalData} and
     * {@link GeneratorJwtProvider.defaultIdentity}
     *
     * @param {ITokenContext} context
     * @returns {Promise<IAccessToken>}
     */
    getToken(context) {
        return Promise.resolve().then(() => {
            const jwt = this.jwtGenerator.generateToken(context.identity || this.defaultIdentity || '', this.additionalData);
            return jwt;
        });
    }
}

/******************************************************************************
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 __rest(s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
}

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

/**
 * Intermediate representation of the Virgil Card with `contentSnapshot`
 * and `snapshot`s of the signatures in UTF-8.
 */
class RawSignedModel {
    /**
     * Initializes a new instance of `RawSignedModel`.
     * @param {string} contentSnapshot - The content snapshot in UTF-8.
     * @param {IRawSignature[]} signatures - The signatures. If signatures
     * themselves have snapshots, those must also be in UTF-8.
     */
    constructor(contentSnapshot, signatures) {
        this.contentSnapshot = contentSnapshot;
        this.signatures = signatures;
    }
    /**
     * Converts the `str` in base64 encoding into a `RawSignedModel` object.
     *
     * @param {string} str - Base64 string representation of the card as
     * returned by {@RawSignedModel.toString} method.
     *
     * @returns {RawSignedModel}
     */
    static fromString(str) {
        const jsonStr = base64Decode(str);
        let obj;
        try {
            obj = JSON.parse(jsonStr);
        }
        catch (error) {
            throw new Error('The string to be parsed is in invalid format');
        }
        return RawSignedModel.fromJson(obj);
    }
    /**
     * Converts the `json` serializable object into a `RawSignedModel` object.
     * @param {IRawSignedModelJson} json - JSON-serializable object returned by
     * {@link RawSignedModel.toJson} method.
     * @returns {RawSignedModel}
     */
    static fromJson(json) {
        const contentSnapshotUtf8 = base64Decode(json.content_snapshot);
        const signaturesWithUtf8Snapshots = (json.signatures || []).map(({ signer, signature, snapshot }) => {
            if (snapshot) {
                return {
                    signer,
                    signature,
                    snapshot: base64Decode(snapshot)
                };
            }
            return { signer, signature };
        });
        return new RawSignedModel(contentSnapshotUtf8, signaturesWithUtf8Snapshots);
    }
    /**
     * This is to make it work with `JSON.stringify`, calls
     * {@link RawSignedModel.toJson} under the hood.
     * @returns {IRawSignedModelJson}
     */
    toJSON() {
        return this.toJson();
    }
    /**
     * Returns a JSON-serializable representation of this model in the
     * format it is stored in the Virgil Cards Service. (i.e. with
     * `contentSnapshot` and `snapshot`s of the signatures as base64 encoded
     * strings.
     * @returns {IRawSignedModelJson}
     */
    toJson() {
        return {
            content_snapshot: base64Encode(this.contentSnapshot),
            signatures: this.signatures.map(({ signer, signature, snapshot }) => {
                if (snapshot) {
                    return {
                        signer,
                        signature,
                        snapshot: base64Encode(snapshot)
                    };
                }
                return { signer, signature };
            })
        };
    }
    /**
     * Serializes this model to string in base64 encoding.
     * @returns {string}
     */
    toString() {
        return base64Encode(JSON.stringify(this));
    }
    /**
     * Same as {@link RawSignedModel.toJson}. Please use that instead.
     * @returns {IRawSignedModelJson}
     */
    exportAsJson() {
        return this.toJson();
    }
    /**
     * Same as {@link RawSignedModel.toString}. Please use that instead.
     * @returns {string}
     */
    exportAsString() {
        return this.toString();
    }
}

var fetchBrowser = createCommonjsModule(function (module, exports) {
(function (global) {

  function fetchPonyfill(options) {
    var Promise = options && options.Promise || global.Promise;
    var XMLHttpRequest = options && options.XMLHttpRequest || global.XMLHttpRequest;

    return (function () {
      var globalThis = Object.create(global, {
        fetch: {
          value: undefined,
          writable: true
        }
      });

      (function (global, factory) {
        factory(exports) ;
      }(this, (function (exports) {
        var global =
          (typeof globalThis !== 'undefined' && globalThis) ||
          (typeof self !== 'undefined' && self) ||
          (typeof global !== 'undefined' && global);

        var support = {
          searchParams: 'URLSearchParams' in global,
          iterable: 'Symbol' in global && 'iterator' in Symbol,
          blob:
            'FileReader' in global &&
            'Blob' in global &&
            (function() {
              try {
                new Blob();
                return true
              } catch (e) {
                return false
              }
            })(),
          formData: 'FormData' in global,
          arrayBuffer: 'ArrayBuffer' in global
        };

        function isDataView(obj) {
          return obj && DataView.prototype.isPrototypeOf(obj)
        }

        if (support.arrayBuffer) {
          var viewClasses = [
            '[object Int8Array]',
            '[object Uint8Array]',
            '[object Uint8ClampedArray]',
            '[object Int16Array]',
            '[object Uint16Array]',
            '[object Int32Array]',
            '[object Uint32Array]',
            '[object Float32Array]',
            '[object Float64Array]'
          ];

          var isArrayBufferView =
            ArrayBuffer.isView ||
            function(obj) {
              return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1
            };
        }

        function normalizeName(name) {
          if (typeof name !== 'string') {
            name = String(name);
          }
          if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === '') {
            throw new TypeError('Invalid character in header field name')
          }
          return name.toLowerCase()
        }

        function normalizeValue(value) {
          if (typeof value !== 'string') {
            value = String(value);
          }
          return value
        }

        // Build a destructive iterator for the value list
        function iteratorFor(items) {
          var iterator = {
            next: function() {
              var value = items.shift();
              return {done: value === undefined, value: value}
            }
          };

          if (support.iterable) {
            iterator[Symbol.iterator] = function() {
              return iterator
            };
          }

          return iterator
        }

        function Headers(headers) {
          this.map = {};

          if (headers instanceof Headers) {
            headers.forEach(function(value, name) {
              this.append(name, value);
            }, this);
          } else if (Array.isArray(headers)) {
            headers.forEach(function(header) {
              this.append(header[0], header[1]);
            }, this);
          } else if (headers) {
            Object.getOwnPropertyNames(headers).forEach(function(name) {
              this.append(name, headers[name]);
            }, this);
          }
        }

        Headers.prototype.append = function(name, value) {
          name = normalizeName(name);
          value = normalizeValue(value);
          var oldValue = this.map[name];
          this.map[name] = oldValue ? oldValue + ', ' + value : value;
        };

        Headers.prototype['delete'] = function(name) {
          delete this.map[normalizeName(name)];
        };

        Headers.prototype.get = function(name) {
          name = normalizeName(name);
          return this.has(name) ? this.map[name] : null
        };

        Headers.prototype.has = function(name) {
          return this.map.hasOwnProperty(normalizeName(name))
        };

        Headers.prototype.set = function(name, value) {
          this.map[normalizeName(name)] = normalizeValue(value);
        };

        Headers.prototype.forEach = function(callback, thisArg) {
          for (var name in this.map) {
            if (this.map.hasOwnProperty(name)) {
              callback.call(thisArg, this.map[name], name, this);
            }
          }
        };

        Headers.prototype.keys = function() {
          var items = [];
          this.forEach(function(value, name) {
            items.push(name);
          });
          return iteratorFor(items)
        };

        Headers.prototype.values = function() {
          var items = [];
          this.forEach(function(value) {
            items.push(value);
          });
          return iteratorFor(items)
        };

        Headers.prototype.entries = function() {
          var items = [];
          this.forEach(function(value, name) {
            items.push([name, value]);
          });
          return iteratorFor(items)
        };

        if (support.iterable) {
          Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
        }

        function consumed(body) {
          if (body.bodyUsed) {
            return Promise.reject(new TypeError('Already read'))
          }
          body.bodyUsed = true;
        }

        function fileReaderReady(reader) {
          return new Promise(function(resolve, reject) {
            reader.onload = function() {
              resolve(reader.result);
            };
            reader.onerror = function() {
              reject(reader.error);
            };
          })
        }

        function readBlobAsArrayBuffer(blob) {
          var reader = new FileReader();
          var promise = fileReaderReady(reader);
          reader.readAsArrayBuffer(blob);
          return promise
        }

        function readBlobAsText(blob) {
          var reader = new FileReader();
          var promise = fileReaderReady(reader);
          reader.readAsText(blob);
          return promise
        }

        function readArrayBufferAsText(buf) {
          var view = new Uint8Array(buf);
          var chars = new Array(view.length);

          for (var i = 0; i < view.length; i++) {
            chars[i] = String.fromCharCode(view[i]);
          }
          return chars.join('')
        }

        function bufferClone(buf) {
          if (buf.slice) {
            return buf.slice(0)
          } else {
            var view = new Uint8Array(buf.byteLength);
            view.set(new Uint8Array(buf));
            return view.buffer
          }
        }

        function Body() {
          this.bodyUsed = false;

          this._initBody = function(body) {
            /*
              fetch-mock wraps the Response object in an ES6 Proxy to
              provide useful test harness features such as flush. However, on
              ES5 browsers without fetch or Proxy support pollyfills must be used;
              the proxy-pollyfill is unable to proxy an attribute unless it exists
              on the object before the Proxy is created. This change ensures
              Response.bodyUsed exists on the instance, while maintaining the
              semantic of setting Request.bodyUsed in the constructor before
              _initBody is called.
            */
            this.bodyUsed = this.bodyUsed;
            this._bodyInit = body;
            if (!body) {
              this._bodyText = '';
            } else if (typeof body === 'string') {
              this._bodyText = body;
            } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
              this._bodyBlob = body;
            } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
              this._bodyFormData = body;
            } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
              this._bodyText = body.toString();
            } else if (support.arrayBuffer && support.blob && isDataView(body)) {
              this._bodyArrayBuffer = bufferClone(body.buffer);
              // IE 10-11 can't handle a DataView body.
              this._bodyInit = new Blob([this._bodyArrayBuffer]);
            } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
              this._bodyArrayBuffer = bufferClone(body);
            } else {
              this._bodyText = body = Object.prototype.toString.call(body);
            }

            if (!this.headers.get('content-type')) {
              if (typeof body === 'string') {
                this.headers.set('content-type', 'text/plain;charset=UTF-8');
              } else if (this._bodyBlob && this._bodyBlob.type) {
                this.headers.set('content-type', this._bodyBlob.type);
              } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
                this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
              }
            }
          };

          if (support.blob) {
            this.blob = function() {
              var rejected = consumed(this);
              if (rejected) {
                return rejected
              }

              if (this._bodyBlob) {
                return Promise.resolve(this._bodyBlob)
              } else if (this._bodyArrayBuffer) {
                return Promise.resolve(new Blob([this._bodyArrayBuffer]))
              } else if (this._bodyFormData) {
                throw new Error('could not read FormData body as blob')
              } else {
                return Promise.resolve(new Blob([this._bodyText]))
              }
            };

            this.arrayBuffer = function() {
              if (this._bodyArrayBuffer) {
                var isConsumed = consumed(this);
                if (isConsumed) {
                  return isConsumed
                }
                if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
                  return Promise.resolve(
                    this._bodyArrayBuffer.buffer.slice(
                      this._bodyArrayBuffer.byteOffset,
                      this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
                    )
                  )
                } else {
                  return Promise.resolve(this._bodyArrayBuffer)
                }
              } else {
                return this.blob().then(readBlobAsArrayBuffer)
              }
            };
          }

          this.text = function() {
            var rejected = consumed(this);
            if (rejected) {
              return rejected
            }

            if (this._bodyBlob) {
              return readBlobAsText(this._bodyBlob)
            } else if (this._bodyArrayBuffer) {
              return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))
            } else if (this._bodyFormData) {
              throw new Error('could not read FormData body as text')
            } else {
              return Promise.resolve(this._bodyText)
            }
          };

          if (support.formData) {
            this.formData = function() {
              return this.text().then(decode)
            };
          }

          this.json = function() {
            return this.text().then(JSON.parse)
          };

          return this
        }

        // HTTP methods whose capitalization should be normalized
        var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];

        function normalizeMethod(method) {
          var upcased = method.toUpperCase();
          return methods.indexOf(upcased) > -1 ? upcased : method
        }

        function Request(input, options) {
          if (!(this instanceof Request)) {
            throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.')
          }

          options = options || {};
          var body = options.body;

          if (input instanceof Request) {
            if (input.bodyUsed) {
              throw new TypeError('Already read')
            }
            this.url = input.url;
            this.credentials = input.credentials;
            if (!options.headers) {
              this.headers = new Headers(input.headers);
            }
            this.method = input.method;
            this.mode = input.mode;
            this.signal = input.signal;
            if (!body && input._bodyInit != null) {
              body = input._bodyInit;
              input.bodyUsed = true;
            }
          } else {
            this.url = String(input);
          }

          this.credentials = options.credentials || this.credentials || 'same-origin';
          if (options.headers || !this.headers) {
            this.headers = new Headers(options.headers);
          }
          this.method = normalizeMethod(options.method || this.method || 'GET');
          this.mode = options.mode || this.mode || null;
          this.signal = options.signal || this.signal;
          this.referrer = null;

          if ((this.method === 'GET' || this.method === 'HEAD') && body) {
            throw new TypeError('Body not allowed for GET or HEAD requests')
          }
          this._initBody(body);

          if (this.method === 'GET' || this.method === 'HEAD') {
            if (options.cache === 'no-store' || options.cache === 'no-cache') {
              // Search for a '_' parameter in the query string
              var reParamSearch = /([?&])_=[^&]*/;
              if (reParamSearch.test(this.url)) {
                // If it already exists then set the value with the current time
                this.url = this.url.replace(reParamSearch, '$1_=' + new Date().getTime());
              } else {
                // Otherwise add a new '_' parameter to the end with the current time
                var reQueryString = /\?/;
                this.url += (reQueryString.test(this.url) ? '&' : '?') + '_=' + new Date().getTime();
              }
            }
          }
        }

        Request.prototype.clone = function() {
          return new Request(this, {body: this._bodyInit})
        };

        function decode(body) {
          var form = new FormData();
          body
            .trim()
            .split('&')
            .forEach(function(bytes) {
              if (bytes) {
                var split = bytes.split('=');
                var name = split.shift().replace(/\+/g, ' ');
                var value = split.join('=').replace(/\+/g, ' ');
                form.append(decodeURIComponent(name), decodeURIComponent(value));
              }
            });
          return form
        }

        function parseHeaders(rawHeaders) {
          var headers = new Headers();
          // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
          // https://tools.ietf.org/html/rfc7230#section-3.2
          var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ');
          // Avoiding split via regex to work around a common IE11 bug with the core-js 3.6.0 regex polyfill
          // https://github.com/github/fetch/issues/748
          // https://github.com/zloirock/core-js/issues/751
          preProcessedHeaders
            .split('\r')
            .map(function(header) {
              return header.indexOf('\n') === 0 ? header.substr(1, header.length) : header
            })
            .forEach(function(line) {
              var parts = line.split(':');
              var key = parts.shift().trim();
              if (key) {
                var value = parts.join(':').trim();
                headers.append(key, value);
              }
            });
          return headers
        }

        Body.call(Request.prototype);

        function Response(bodyInit, options) {
          if (!(this instanceof Response)) {
            throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.')
          }
          if (!options) {
            options = {};
          }

          this.type = 'default';
          this.status = options.status === undefined ? 200 : options.status;
          this.ok = this.status >= 200 && this.status < 300;
          this.statusText = 'statusText' in options ? options.statusText : '';
          this.headers = new Headers(options.headers);
          this.url = options.url || '';
          this._initBody(bodyInit);
        }

        Body.call(Response.prototype);

        Response.prototype.clone = function() {
          return new Response(this._bodyInit, {
            status: this.status,
            statusText: this.statusText,
            headers: new Headers(this.headers),
            url: this.url
          })
        };

        Response.error = function() {
          var response = new Response(null, {status: 0, statusText: ''});
          response.type = 'error';
          return response
        };

        var redirectStatuses = [301, 302, 303, 307, 308];

        Response.redirect = function(url, status) {
          if (redirectStatuses.indexOf(status) === -1) {
            throw new RangeError('Invalid status code')
          }

          return new Response(null, {status: status, headers: {location: url}})
        };

        exports.DOMException = global.DOMException;
        try {
          new exports.DOMException();
        } catch (err) {
          exports.DOMException = function(message, name) {
            this.message = message;
            this.name = name;
            var error = Error(message);
            this.stack = error.stack;
          };
          exports.DOMException.prototype = Object.create(Error.prototype);
          exports.DOMException.prototype.constructor = exports.DOMException;
        }

        function fetch(input, init) {
          return new Promise(function(resolve, reject) {
            var request = new Request(input, init);

            if (request.signal && request.signal.aborted) {
              return reject(new exports.DOMException('Aborted', 'AbortError'))
            }

            var xhr = new XMLHttpRequest();

            function abortXhr() {
              xhr.abort();
            }

            xhr.onload = function() {
              var options = {
                status: xhr.status,
                statusText: xhr.statusText,
                headers: parseHeaders(xhr.getAllResponseHeaders() || '')
              };
              options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');
              var body = 'response' in xhr ? xhr.response : xhr.responseText;
              setTimeout(function() {
                resolve(new Response(body, options));
              }, 0);
            };

            xhr.onerror = function() {
              setTimeout(function() {
                reject(new TypeError('Network request failed'));
              }, 0);
            };

            xhr.ontimeout = function() {
              setTimeout(function() {
                reject(new TypeError('Network request failed'));
              }, 0);
            };

            xhr.onabort = function() {
              setTimeout(function() {
                reject(new exports.DOMException('Aborted', 'AbortError'));
              }, 0);
            };

            function fixUrl(url) {
              try {
                return url === '' && global.location.href ? global.location.href : url
              } catch (e) {
                return url
              }
            }

            xhr.open(request.method, fixUrl(request.url), true);

            if (request.credentials === 'include') {
              xhr.withCredentials = true;
            } else if (request.credentials === 'omit') {
              xhr.withCredentials = false;
            }

            if ('responseType' in xhr) {
              if (support.blob) {
                xhr.responseType = 'blob';
              } else if (
                support.arrayBuffer &&
                request.headers.get('Content-Type') &&
                request.headers.get('Content-Type').indexOf('application/octet-stream') !== -1
              ) {
                xhr.responseType = 'arraybuffer';
              }
            }

            if (init && typeof init.headers === 'object' && !(init.headers instanceof Headers)) {
              Object.getOwnPropertyNames(init.headers).forEach(function(name) {
                xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
              });
            } else {
              request.headers.forEach(function(value, name) {
                xhr.setRequestHeader(name, value);
              });
            }

            if (request.signal) {
              request.signal.addEventListener('abort', abortXhr);

              xhr.onreadystatechange = function() {
                // DONE (success or failure)
                if (xhr.readyState === 4) {
                  request.signal.removeEventListener('abort', abortXhr);
                }
              };
            }

            xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);
          })
        }

        fetch.polyfill = true;

        if (!global.fetch) {
          global.fetch = fetch;
          global.Headers = Headers;
          global.Request = Request;
          global.Response = Response;
        }

        exports.Headers = Headers;
        exports.Request = Request;
        exports.Response = Response;
        exports.fetch = fetch;

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

      })));


      return {
        fetch: globalThis.fetch,
        Headers: globalThis.Headers,
        Request: globalThis.Request,
        Response: globalThis.Response,
        DOMException: globalThis.DOMException
      };
    }());
  }

  {
    module.exports = fetchPonyfill;
  }
}(typeof globalThis !== 'undefined' ? globalThis : typeof self !== 'undefined' ? self : typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : commonjsGlobal));
});

var fetchPonyfill = unwrapExports(fetchBrowser);

const { fetch, Request, Response, Headers } = fetchPonyfill();

const OS_LIST = [
    /* Windows Phone */
    {
        name: 'Windows Phone',
        test: [/windows phone/i],
    },
    /* Windows */
    {
        test: [/windows/i],
        name: 'Windows'
    },
    /* macOS */
    {
        test: [/macintosh/i],
        name: 'macOS'
    },
    /* iOS */
    {
        test: [/(ipod|iphone|ipad)/i],
        name: 'iOS'
    },
    /* Android */
    {
        test: [/android/i],
        name: 'Android',
    },
    /* Linux */
    {
        test: [/linux/i],
        name: 'Linux',
    },
    /* Chrome OS */
    {
        test: [/CrOS/],
        name: 'Chrome OS'
    },
    /* Playstation 4 */
    {
        test: [/PlayStation 4/],
        name: 'PlayStation 4',
    },
];

// Order of browsers matters! Edge, Opera and Chromium have Chrome in User Agent.
const BROWSER_LIST = [
    {
        test: [/googlebot/i],
        name: 'Googlebot'
    },
    {
        test: [/opera/i, /opr\/|opios/i],
        name: 'Opera',
    },
    {
        test: [/msie|trident/i],
        name: 'Internet Explorer',
    },
    {
        test: [/\sedg/i],
        name: 'Microsoft Edge'
    },
    {
        test: [/firefox|iceweasel|fxios/i],
        name: 'Firefox',
    },
    {
        test: [/chromium/i],
        name: 'Chromium'
    },
    {
        test: [/chrome|crios|crmo/i],
        name: 'Chrome',
    },
    {
        test: [/android/i],
        name: 'Android Browser'
    },
    {
        test: [/playstation 4/i],
        name: 'PlayStation 4',
    },
    {
        test: [/safari|applewebkit/i],
        name: 'Safari',
    }
];

/**
 * Class responsible for tracking which Virgil SDK is being used to make requests, its version,
 * browser and platform.
 */
class VirgilAgent {
    /**
     * Initializes a new instance of `VirgilAgent`.
     * @param {string} product - name of product eg (sdk, brainkey, bpp, keyknox, ratchet, e3kit, purekit)
     * argument of request methods.
     * @param {string} version - version of the product.
     * @param {string} [userAgent] - string with device user agent. Optional
     */
    constructor(product, version, userAgent) {
        /**
         * Detect Cordova / PhoneGap / Ionic frameworks on a mobile device.
         * @return {boolean} true if detects Ionic.
         */
        this.isIonic = () => typeof window !== "undefined" &&
            !!("cordova" in window || "phonegap" in window || "PhoneGap" in window) &&
            /android|ios|iphone|ipod|ipad|iemobile/i.test(this.userAgent);
        this.userAgent = userAgent || this.getUserAgent();
        this.value = `${product};js;${this.getHeaderValue()};${version}`;
    }
    /**
     * Returns navigator.userAgent string or '' if it's not defined.
     * @return {string} user agent string
     */
    getUserAgent() {
        if (typeof navigator !== "undefined" && typeof navigator.userAgent === "string") {
            return navigator.userAgent;
        }
        else {
            return "";
        }
    }
    ;
    /**
     * Detects device OS
     * @returns {string} returns OS if detected or 'other'.
     */
    getOsName() {
        const os = OS_LIST.find((os) => os.test.some(condition => condition.test(this.userAgent)));
        return os ? os.name : 'other';
    }
    /**
     * Detects device browser
     * @returns {string} returns browser if detected of 'other'.
     */
    getBrowser() {
        const browser = BROWSER_LIST.find((browser) => browser.test.some(condition => condition.test(this.userAgent)));
        return browser ? browser.name : 'other';
    }
    /**
     * Detect React Native.
     * @return {boolean} true if detects ReactNative .
     */
    isReactNative() {
        return typeof navigator === "object" && navigator.product === "ReactNative";
    }
    /**
     * Return information for `virgil-agent` header.
     * @return {string} string in format: PRODUCT;FAMILY;PLATFORM;VERSION
     */
    getHeaderValue() {
        try {
            if (this.isReactNative())
                return "ReactNative";
            if (this.isIonic())
                return `Ionic/${this.getOsName()}`;
            if (!true && typeof global !== 'undefined') ;
            return `${this.getBrowser()}/${this.getOsName()}`;
        }
        catch (e) {
            return `Unknown`;
        }
    }
}

/**
 * Class responsible for making HTTP requests.
 * @hidden
 */
class Connection {
    /**
     * Initializes a new instance of `Connection`.
     * @param {string} prefix - `prefix` will be prepended to the `endpoint`
     * argument of request methods.
     * @param {VirgilAgentValue} [virgilAgentValue] - optional instance of VirgilAgent for products that wraps
     * Virgil SDK
     */
    constructor(prefix, info) {
        this.prefix = prefix;
        if (!info)
            info = { product: 'sdk', version: "6.2.0" };
        this.virgilAgentValue = new VirgilAgent(info.product, info.version).value;
    }
    /**
     * Issues a GET request against the `endpoint`.
     * @param {string} endpoint - Endpoint URL relative to the `prefix`.
     * @param {string} accessToken - Token to authenticate the request.
     * @returns {Promise<Response>}
     */
    get(endpoint, accessToken) {
        const headers = this.createHeaders(accessToken);
        return this.send(endpoint, 'GET', { headers });
    }
    /**
     * Issues a POST request against the `endpoint` sending the `data` as JSON.
     * @param {string} endpoint - Endpoint URL relative to the `prefix`.
     * @param {string} accessToken - Token to authenticate the request.
     * @param {object} data - Response body.
     * @returns {Promise<Response>}
     */
    post(endpoint, accessToken, data = {}) {
        const headers = this.createHeaders(accessToken);
        headers.set('Content-Type', 'application/json');
        return this.send(endpoint, 'POST', {
            headers: headers,
            body: JSON.stringify(data)
        });
    }
    send(endpoint, method, params) {
        return fetch(this.prefix + endpoint, Object.assign({ method }, params));
    }
    createHeaders(accessToken) {
        const headers = new Headers();
        headers.set('Authorization', `Virgil ${accessToken}`);
        headers.set('Virgil-Agent', this.virgilAgentValue);
        return headers;
    }
}

/**
 * Custom error class for errors specific to Virgil SDK.
 */
class VirgilError extends Error {
    constructor(m, name = 'VirgilError', DerivedClass = VirgilError) {
        super(m);
        Object.setPrototypeOf(this, DerivedClass.prototype);
        this.name = name;
    }
}

var ErrorCode;
(function (ErrorCode) {
    ErrorCode[ErrorCode["AccessTokenExpired"] = 20304] = "AccessTokenExpired";
    ErrorCode[ErrorCode["Unknown"] = 0] = "Unknown";
})(ErrorCode || (ErrorCode = {}));
/**
 * Error thrown by {@link CardManager} when request to the Virgil Cards Service
 * fails.
 */
class VirgilHttpError extends VirgilError {
    constructor(message, status, errorCode) {
        super(message, 'VirgilHttpError', VirgilHttpError);
        this.httpStatus = status;
        this.errorCode = errorCode;
    }
}
/**
 * Generates error object from response object with HTTP status >= 400
 *
 * @hidden
 *
 * @param {Response} response
 * @returns {Promise<VirgilHttpError>}
 */
function generateErrorFromResponse(response) {
    return __awaiter(this, void 0, void 0, function* () {
        if (response.status >= 400 && response.status < 500) {
            const reason = yield response.json();
            return new VirgilHttpError(reason.message, response.status, reason.code);
        }
        else {
            return new VirgilHttpError(response.statusText, response.status, 0);
        }
    });
}

const PublishEndpoint = '/card/v5';
const SearchEndpoint = '/card/v5/actions/search';
const GetCardEndpoint = (cardId) => `/card/v5/${cardId}`;
const RevokeCardEndpoint = (cardId) => `/card/v5/actions/revoke/${cardId}`;
/**
 * Class responsible for sending requests to the Virgil Cards Service.
 *
 * @hidden
 */
class CardClient {
    /**
     * Initializes new instance of `CardClient`.
     * @param {IConnection | string} connection - Object implementing the
     * {@link IConnection} interface.
     */
    constructor(connection, productInfo) {
        if (typeof connection === 'string') {
            this.connection = new Connection(connection, productInfo);
        }
        else if (connection) {
            this.connection = connection;
        }
        else {
            this.connection = new Connection('https://api.virgilsecurity.com', productInfo);
        }
    }
    /**
     * Issues a request to search cards by the `identity`.
     * @param {string[]} identities - Array of identities to search for.
     * @param {string} accessToken - A token to authenticate the request.
     * @returns {Promise<RawSignedModel[]>}
     */
    searchCards(identities, accessToken) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield this.connection.post(SearchEndpoint, accessToken, { identities });
            if (!response.ok)
                throw yield generateErrorFromResponse(response);
            const cardsJson = yield response.json();
            if (cardsJson === null)
                return [];
            return cardsJson.map(RawSignedModel.fromJson);
        });
    }
    /**
     * Issues a request to get the card by id.
     * @param {string} cardId - Id of the card to fetch.
     * @param {string} accessToken - A token to authenticate the request.
     * @returns {Promise<ICardResult>}
     */
    getCard(cardId, accessToken) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!cardId)
                throw new TypeError('`cardId` should not be empty');
            if (!accessToken)
                throw new TypeError('`accessToken` should not be empty');
            const response = yield this.connection.get(GetCardEndpoint(cardId), accessToken);
            if (!response.ok) {
                throw yield generateErrorFromResponse(response);
            }
            const isOutdated = response.headers.get('X-Virgil-Is-Superseeded') === 'true';
            const cardJson = yield response.json();
            const cardRaw = RawSignedModel.fromJson(cardJson);
            return { cardRaw, isOutdated };
        });
    }
    /**
     * Issues a request to publish the card.
     * @param {RawSignedModel} model - Card to publish.
     * @param {string} accessToken - A token to authenticate the request.
     * @returns {Promise<RawSignedModel>}
     */
    publishCard(model, accessToken) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!model)
                throw new TypeError('`model` should not be empty');
            if (!accessToken)
                throw new TypeError('`accessToken` should not be empty');
            const response = yield this.connection.post(PublishEndpoint, accessToken, model);
            if (!response.ok) {
                throw yield generateErrorFromResponse(response);
            }
            const cardJson = yield response.json();
            return RawSignedModel.fromJson(cardJson);
        });
    }
    revokeCard(cardId, accessToken) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!cardId)
                throw new TypeError('`cardId` should not be empty');
            if (!accessToken)
                throw new TypeError('`accessToken` should not be empty');
            const response = yield this.connection.post(RevokeCardEndpoint(cardId), accessToken);
            if (!response.ok) {
                throw yield generateErrorFromResponse(response);
            }
        });
    }
}

/**
 * @hidden
 */
const SelfSigner = "self";
/**
 * @hidden
 */
const VirgilSigner = "virgil";
/**
 * @hidden
 */
const CardVersion = '5.0';
/**
 * @hidden
 */
const CardIdByteLength = 32;

/**
 * Class responsible for generating signatures of the cards.
 */
class ModelSigner {
    /**
     * Initializes a new instance of `ModelSigner`.
     * @param {ICardCrypto} crypto - Object implementing the
     * {@link ICardCrypto} interface.
     */
    constructor(crypto) {
        this.crypto = crypto;
    }
    /**
     * Generates a new signature based on `rawParams`.
     * @param {IRawSignParams} rawParams
     */
    sign(rawParams) {
        const { model, signerPrivateKey, signer, extraSnapshot } = this.prepareParams(rawParams);
        const signedSnapshot = extraSnapshot != null
            ? model.contentSnapshot + extraSnapshot
            : model.contentSnapshot;
        const signature = this.crypto.generateSignature({ value: signedSnapshot, encoding: 'utf8' }, signerPrivateKey);
        model.signatures.push({
            signer,
            signature: signature.toString('base64'),
            snapshot: extraSnapshot
        });
    }
    prepareParams({ model, signerPrivateKey, extraFields, signer }) {
        signer = signer || SelfSigner;
        let extraSnapshot;
        if (extraFields != null) {
            extraSnapshot = JSON.stringify(extraFields);
        }
        const final = { model, signerPrivateKey, signer, extraSnapshot };
        this.validate(final);
        return final;
    }
    validate({ model, signerPrivateKey, signer }) {
        if (model == null) {
            throw new Error("Model is empty");
        }
        if (signerPrivateKey == null) {
            throw new Error("`signerPrivateKey` property is mandatory");
        }
        if (model.signatures != null && model.signatures.some(s => s.signer == signer)) {
            throw new Error("The model already has this signature.");
        }
    }
}

/**
 * Converts an {@link ICard} to a {@link RawSignedModel}.
 *
 * @hidden
 *
 * @param {ICard} card - The {@link ICard} to convert.
 * @returns {RawSignedModel}
 */
function cardToRawSignedModel(card) {
    return new RawSignedModel(card.contentSnapshot, card.signatures.slice());
}
/**
 * Generates a {@link RawSignedModel} from the given `params`.
 *
 * @hidden
 *
 * @param {ICardCrypto} crypto - Object implementing the {@link ICardCrypto}
 * interface.
 * @param {INewCardParams} params - New card parameters.
 * @returns {RawSignedModel}
 */
function generateRawSigned(crypto, params) {
    const { identity, publicKey, previousCardId } = params;
    const now = getUnixTimestamp(new Date);
    const details = {
        identity: identity,
        previous_card_id: previousCardId,
        created_at: now,
        version: CardVersion,
        public_key: crypto.exportPublicKey(publicKey).toString('base64'),
    };
    return new RawSignedModel(JSON.stringify(details), []);
}
/**
 * Converts the {@link RawSignedModel} into the {@link ICard}.
 *
 * @hidden
 *
 * @param {ICardCrypto} crypto - Object implementing the {@link ICardCrypto}
 * interface.
 * @param {RawSignedModel} model - The model to convert.
 * @param {boolean} isOutdated - Boolean indicating whether there is a newer
 * Virgil Card replacing the one that `model` represents.
 *
 * @returns {ICard}
 */
function parseRawSignedModel(crypto, model, isOutdated = false) {
    const content = JSON.parse(model.contentSnapshot);
    const signatures = model.signatures.map(rawSignToCardSign);
    return {
        id: generateCardId(crypto, model.contentSnapshot),
        publicKey: crypto.importPublicKey({ value: content.public_key, encoding: 'base64' }),
        contentSnapshot: model.contentSnapshot,
        identity: content.identity,
        version: content.version,
        createdAt: new Date(content.created_at * 1000),
        previousCardId: content.previous_card_id,
        signatures,
        isOutdated
    };
}
/**
 * Given the array of `cards`, returns another array with outdated cards
 * filtered out and the `previousCard` properties of the cards that replace
 * the outdated ones being populated with appropriate outdated cards.
 * i.e. turns this (A is for Actual, O is for Outdated):
 * ```
 * A -> O -> A -> A -> O
 * ```
 * into this
 * ```
 * A -> A -> A
 * |         |
 * O         O
 * ```
 *
 * @hidden
 *
 * @param {ICard[]} cards - The cards array to transform.
 * @returns {ICard[]} - Transformed array.
 */
function linkedCardList(cards) {
    const unsorted = Object.create(null);
    for (const card of cards) {
        unsorted[card.id] = card;
    }
    for (const card of cards) {
        if (card.previousCardId == null)
            continue;
        if (unsorted[card.previousCardId] == null)
            continue;
        unsorted[card.previousCardId].isOutdated = true;
        card.previousCard = unsorted[card.previousCardId];
        delete unsorted[card.previousCardId];
    }
    return Object.keys(unsorted).map(key => unsorted[key]);
}
/**
 * Calculates ID for the VirgilCard from the `snapshot` of its contents.
 *
 * @hidden
 *
 * @param {ICardCrypto} crypto - Object implementing the {@link ICardCrypto}
 * interface.
 * @param {string} snapshot - The VirgilCard's contents snapshot.
 * @returns {string} - VirgilCard's ID encoded in HEX.
 */
function generateCardId(crypto, snapshot) {
    const fingerprint = crypto
        .generateSha512({ value: snapshot, encoding: 'utf8' })
        .slice(0, CardIdByteLength);
    return fingerprint.toString('hex');
}
function rawSignToCardSign({ snapshot, signature, signer }) {
    return {
        signer,
        signature,
        snapshot,
        extraFields: tryParseExtraFields(snapshot)
    };
}
function tryParseExtraFields(snapshot) {
    if (snapshot) {
        try {
            return JSON.parse(snapshot);
        }
        catch (ignored) { }
    }
    return {};
}

/**
 * Error thrown by {@link CardManager} instances when the card received from
 * the network (or imported from string\json) fails verification.
 */
class VirgilCardVerificationError extends VirgilError {
    constructor(m) {
        super(m, 'CardVerificationError', VirgilCardVerificationError);
    }
}

const CARDS_SERVICE__NAME = 'cards';
/**
 * @hidden
 */
const throwingAccessTokenProvider = {
    getToken: () => {
        throw new Error('Please set `CardManager.accessTokenProvider` to be able to make requests.');
    }
};
const getCardServiceTokenContext = (context) => (Object.assign(Object.assign({}, context), { service: CARDS_SERVICE__NAME }));
/**
 * Class responsible for creating, publishing and retrieving Virgil Cards.
 */
class CardManager {
    constructor(params) {
        this.crypto = params.cardCrypto;
        this.client = new CardClient(params.apiUrl, params.productInfo);
        this.modelSigner = new ModelSigner(params.cardCrypto);
        this.signCallback = params.signCallback;
        this.retryOnUnauthorized = params.retryOnUnauthorized;
        this.cardVerifier = params.cardVerifier;
        this.accessTokenProvider = params.accessTokenProvider || throwingAccessTokenProvider;
    }
    /**
     * Generates a {@link RawSignedModel} that represents a card from
     * `cardParams`.
     * Use this method if you don't need to publish the card right away, for
     * example if you need to first send it to your backend server to apply
     * additional signature.
     *
     * @param {INewCardParams} cardParams - New card parameters.
     * @returns {RawSignedModel}
     */
    generateRawCard(cardParams) {
        const model = generateRawSigned(this.crypto, cardParams);
        this.modelSigner.sign({
            model,
            signerPrivateKey: cardParams.privateKey,
            signer: SelfSigner,
            extraFields: cardParams.extraFields
        });
        return model;
    }
    /**
     * Generates a card from `cardParams` and publishes it in the Virgil Cards
     * Service.
     * @param {INewCardParams} cardParams - New card parameters.
     * @returns {Promise<ICard>}
     */
    publishCard(cardParams) {
        return __awaiter(this, void 0, void 0, function* () {
            validateCardParams(cardParams);
            const tokenContext = {
                service: CARDS_SERVICE__NAME,
                identity: cardParams.identity,
                operation: 'publish'
            };
            const token = yield this.accessTokenProvider.getToken(tokenContext);
            const rawSignedModel = this.generateRawCard(Object.assign({}, cardParams, { identity: token.identity() }));
            return yield this.publishRawSignedModel(rawSignedModel, tokenContext, token);
        });
    }
    /**
     * Publishes a previously generated card in the form of
     * {@link RawSignedModel} object.
     *
     * @param {RawSignedModel} rawCard - The card to publish.
     * @returns {Promise<ICard>}
     */
    publishRawCard(rawCard) {
        return __awaiter(this, void 0, void 0, function* () {
            assert(rawCard != null && rawCard.contentSnapshot != null, '`rawCard` should not be empty');
            const cardDetails = JSON.parse(rawCard.contentSnapshot);
            const tokenContext = getCardServiceTokenContext({ identity: cardDetails.identity, operation: 'publish' });
            const token = yield this.accessTokenProvider.getToken(tokenContext);
            return this.publishRawSignedModel(rawCard, tokenContext, token);
        });
    }
    /**
     * Fetches the card by `cardId` from the Virgil Card Service.
     * @param {string} cardId - Id of the card to fetch.
     * @returns {Promise<ICard>}
     */
    getCard(cardId) {
        return __awaiter(this, void 0, void 0, function* () {
            const tokenContext = getCardServiceTokenContext({ operation: 'get' });
            const accessToken = yield this.accessTokenProvider.getToken(tokenContext);
            const cardWithStatus = yield this.tryDo(tokenContext, accessToken, (token) => __awaiter(this, void 0, void 0, function* () { return yield this.client.getCard(cardId, token.toString()); }));
            const card = parseRawSignedModel(this.crypto, cardWithStatus.cardRaw, cardWithStatus.isOutdated);
            if (card.id !== cardId) {
                throw new VirgilCardVerificationError('Received invalid card');
            }
            this.validateCards([card]);
            return card;
        });
    }
    /**
     * Fetches collection of cards with the given `identity` from the Virgil
     * Cards Service.
     * @param {string|string[]} identities - Identity or an array of identities of the cards to fetch.
     * @returns {Promise<ICard[]>}
     */
    searchCards(identities) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!identities)
                throw new TypeError('Argument `identities` is required');
            const identitiesArr = Array.isArray(identities) ? identities : [identities];
            if (identitiesArr.length === 0)
                throw new TypeError('Identities array must not be empty');
            const tokenContext = getCardServiceTokenContext({ operation: 'search' });
            const accessToken = yield this.accessTokenProvider.getToken(tokenContext);
            const rawCards = yield this.tryDo(tokenContext, accessToken, (token) => __awaiter(this, void 0, void 0, function* () { return yield this.client.searchCards(identitiesArr, token.toString()); }));
            const cards = rawCards.map(raw => parseRawSignedModel(this.crypto, raw, false));
            const identitiesSet = new Set(identitiesArr);
            if (cards.some(c => !identitiesSet.has(c.identity))) {
                throw new VirgilCardVerificationError('Received invalid cards');
            }
            this.validateCards(cards);
            return linkedCardList(cards);
        });
    }
    /**
     * Marks the Virgil Card specified by `cardId` as revoked.  Revoked cards will have `isOutdated`
     * property set to `true` when retrieved via {@link CardManager.getCard} method.
     * Also revoked cards will be absent in the {@link CardManager.searchCards} result.
     * @param {string} cardId - Id of the card to revoke.
     * @returns {Promise}
     */
    revokeCard(cardId) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!cardId)
                throw new TypeError('Argument `cardId` is required');
            const tokenContext = getCardServiceTokenContext({ operation: 'revoke' });
            const accessToken = yield this.accessTokenProvider.getToken(tokenContext);
            yield this.tryDo(tokenContext, accessToken, (token) => __awaiter(this, void 0, void 0, function* () { return yield this.client.revokeCard(cardId, token.toString()); }));
        });
    }
    /**
     * Converts the card in the form of {@link RawSignedModel} object to the
     * {@link ICard} object.
     *
     * @see {@link CardManager.exportCard}
     *
     * @param {RawSignedModel} rawCard - The card to convert.
     * @returns {ICard}
     */
    importCard(rawCard) {
        const card = parseRawSignedModel(this.crypto, rawCard);
        this.validateCards([card]);
        return card;
    }
    /**
     * Converts the card in the base64 string form to the {@link ICard} object.
     *
     * @see {@link CardManager.exportCardAsString}
     *
     * @param {string} str - The string in base64.
     * @returns {ICard}
     */
    importCardFromString(str) {
        assert(Boolean(str), '`str` should not be empty');
        return this.importCard(RawSignedModel.fromString(str));
    }
    /**
     * Converts the card in the JSON-serializable object form to the
     * {@link ICard} object.
     *
     * @see {@link CardManager.exportCardAsJson}
     *
     * @param {IRawSignedModelJson} json
     * @returns {ICard}
     */
    importCardFromJson(json) {
        assert(Boolean(json), '`json` should not be empty');
        return this.importCard(RawSignedModel.fromJson(json));
    }
    /**
     * Converts the card in the form of {@link ICard} object to the
     * {@link RawSignedModel} object.
     *
     * @see {@link CardManager.importCard}
     *
     * @param {ICard} card
     * @returns {RawSignedModel}
     */
    exportCard(card) {
        return cardToRawSignedModel(card);
    }
    /**
     * Converts the card in the form of {@link ICard} object to the string
     * in base64 encoding.
     *
     * @see {@link CardManager.importCardFromString}
     *
     * @param {ICard} card
     * @returns {string}
     */
    exportCardAsString(card) {
        return this.exportCard(card).toString();
    }
    /**
     * Converts the card in the form of {@link ICard} object to the
     * JSON-serializable object form.
     *
     * @see {@link CardManager.importCardFromJson}
     *
     * @param {ICard} card
     * @returns {IRawSignedModelJson}
     */
    exportCardAsJson(card) {
        return this.exportCard(card).toJson();
    }
    /**
     * @hidden
     */
    publishRawSignedModel(rawCard, context, accessToken) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.signCallback != null) {
                rawCard = yield this.signCallback(rawCard);
            }
            const publishedModel = yield this.tryDo(context, accessToken, (token) => __awaiter(this, void 0, void 0, function* () { return yield this.client.publishCard(rawCard, token.toString()); }));
            if (rawCard.contentSnapshot !== publishedModel.contentSnapshot) {
                throw new VirgilCardVerificationError('Received invalid card');
            }
            const card = parseRawSignedModel(this.crypto, publishedModel);
            this.validateCards([card]);
            return card;
        });
    }
    /**
     * @hidden
     */
    tryDo(context, token, func) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                return yield func(token);
            }
            catch (e) {
                if (e instanceof VirgilHttpError &&
                    e.httpStatus === 401 &&
                    e.errorCode === ErrorCode.AccessTokenExpired &&
                    this.retryOnUnauthorized) {
                    token = yield this.accessTokenProvider.getToken(getCardServiceTokenContext({
                        identity: context.identity,
                        operation: context.operation,
                        forceReload: true
                    }));
                    return yield func(token);
                }
                throw e;
            }
        });
    }
    /**
     * Delegates to the {@link CardManager.cardVerifier} to verify the validity
     * of the `cards`.
     *
     * @throws {@link VirgilCardVerificationError} if any of the cards is not
     * valid.
     *
     * @param {ICard[]} cards
     */
    validateCards(cards) {
        if (this.cardVerifier == null)
            return;
        for (const card of cards) {
            if (!this.cardVerifier.verifyCard(card)) {
                throw new VirgilCardVerificationError('Validation errors have been detected');
            }
        }
    }
}
/**
 * @hidden
 */
function validateCardParams(params, validateIdentity = false) {
    assert(params != null, 'Card parameters must be provided');
    assert(params.privateKey != null, 'Card\'s private key is required');
    assert(params.publicKey != null, 'Card\'s public key is required');
    if (validateIdentity) {
        assert(typeof params.identity === 'string' && params.identity !== '', 'Card\'s identity is required');
    }
}

const DEFAULTS$2 = {
    verifySelfSignature: true,
    verifyVirgilSignature: true,
    whitelists: []
};
const VIRGIL_CARDS_PUBKEY_BASE64 = 'MCowBQYDK2VwAyEAljOYGANYiVq1WbvVvoYIKtvZi2ji9bAhxyu6iV/LF8M=';
/**
 * Class responsible for validating cards by verifying their digital
 * signatures.
 */
class VirgilCardVerifier {
    /**
     * Initializes a new instance of `VirgilCardVerifier`.
     * @param {ICardCrypto} crypto - Object implementing the
     * {@link ICardCrypto} interface.
     * @param {IVirgilCardVerifierParams} options - Initialization options.
     */
    constructor(crypto, options) {
        this.crypto = crypto;
        const params = Object.assign(Object.assign({}, DEFAULTS$2), (options || {}));
        this.verifySelfSignature = params.verifySelfSignature;
        this.verifyVirgilSignature = params.verifyVirgilSignature;
        this.whitelists = params.whitelists;
        this.virgilCardsPublicKey = crypto.importPublicKey({ value: VIRGIL_CARDS_PUBKEY_BASE64, encoding: 'base64' });
    }
    /**
     * Verifies the signatures of the `card`.
     * @param {ICard} card
     * @returns {boolean} `true` if the signatures to be verified are present
     * and valid, otherwise `false`.
     */
    verifyCard(card) {
        if (this.selfValidationFailed(card)) {
            return false;
        }
        if (this.virgilValidationFailed(card)) {
            return false;
        }
        if (!this.whitelists || this.whitelists.length === 0) {
            return true;
        }
        const signers = card.signatures.map(s => s.signer);
        for (const whitelist of this.whitelists) {
            if (whitelist == null || whitelist.length === 0) {
                return false;
            }
            const intersectedCreds = whitelist.filter(x => signers.indexOf(x.signer) !== -1);
            if (intersectedCreds.length === 0) {
                return false;
            }
            const isValidForSome = intersectedCreds.some(cred => this.validateSignerSignature(card, this.getPublicKey(cred.publicKeyBase64), cred.signer));
            if (!isValidForSome) {
                return false;
            }
        }
        return true;
    }
    selfValidationFailed(card) {
        return this.verifySelfSignature
            && !this.validateSignerSignature(card, card.publicKey, SelfSigner);
    }
    virgilValidationFailed(card) {
        return this.verifyVirgilSignature
            && !this.validateSignerSignature(card, this.virgilCardsPublicKey, VirgilSigner);
    }
    getPublicKey(signerPublicKeyBase64) {
        return this.crypto.importPublicKey({ value: signerPublicKeyBase64, encoding: 'base64' });
    }
    validateSignerSignature(card, signerPublicKey, signer) {
        const signature = card.signatures.find(s => s.signer === signer);
        if (signature == null)
            return false;
        const extendedSnapshot = signature.snapshot == null
            ? card.contentSnapshot
            : card.contentSnapshot + signature.snapshot;
        return this.crypto.verifySignature({ value: extendedSnapshot, encoding: 'utf8' }, { value: signature.signature, encoding: 'base64' }, signerPublicKey);
    }
}

var utf8 = createCommonjsModule(function (module, exports) {
(function(root) {

	var stringFromCharCode = String.fromCharCode;

	// Taken from https://mths.be/punycode
	function ucs2decode(string) {
		var output = [];
		var counter = 0;
		var length = string.length;
		var value;
		var extra;
		while (counter < length) {
			value = string.charCodeAt(counter++);
			if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
				// high surrogate, and there is a next character
				extra = string.charCodeAt(counter++);
				if ((extra & 0xFC00) == 0xDC00) { // low surrogate
					output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
				} else {
					// unmatched surrogate; only append this code unit, in case the next
					// code unit is the high surrogate of a surrogate pair
					output.push(value);
					counter--;
				}
			} else {
				output.push(value);
			}
		}
		return output;
	}

	// Taken from https://mths.be/punycode
	function ucs2encode(array) {
		var length = array.length;
		var index = -1;
		var value;
		var output = '';
		while (++index < length) {
			value = array[index];
			if (value > 0xFFFF) {
				value -= 0x10000;
				output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
				value = 0xDC00 | value & 0x3FF;
			}
			output += stringFromCharCode(value);
		}
		return output;
	}

	function checkScalarValue(codePoint) {
		if (codePoint >= 0xD800 && codePoint <= 0xDFFF) {
			throw Error(
				'Lone surrogate U+' + codePoint.toString(16).toUpperCase() +
				' is not a scalar value'
			);
		}
	}
	/*--------------------------------------------------------------------------*/

	function createByte(codePoint, shift) {
		return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80);
	}

	function encodeCodePoint(codePoint) {
		if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence
			return stringFromCharCode(codePoint);
		}
		var symbol = '';
		if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence
			symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0);
		}
		else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence
			checkScalarValue(codePoint);
			symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0);
			symbol += createByte(codePoint, 6);
		}
		else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence
			symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0);
			symbol += createByte(codePoint, 12);
			symbol += createByte(codePoint, 6);
		}
		symbol += stringFromCharCode((codePoint & 0x3F) | 0x80);
		return symbol;
	}

	function utf8encode(string) {
		var codePoints = ucs2decode(string);
		var length = codePoints.length;
		var index = -1;
		var codePoint;
		var byteString = '';
		while (++index < length) {
			codePoint = codePoints[index];
			byteString += encodeCodePoint(codePoint);
		}
		return byteString;
	}

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

	function readContinuationByte() {
		if (byteIndex >= byteCount) {
			throw Error('Invalid byte index');
		}

		var continuationByte = byteArray[byteIndex] & 0xFF;
		byteIndex++;

		if ((continuationByte & 0xC0) == 0x80) {
			return continuationByte & 0x3F;
		}

		// If we end up here, it’s not a continuation byte
		throw Error('Invalid continuation byte');
	}

	function decodeSymbol() {
		var byte1;
		var byte2;
		var byte3;
		var byte4;
		var codePoint;

		if (byteIndex > byteCount) {
			throw Error('Invalid byte index');
		}

		if (byteIndex == byteCount) {
			return false;
		}

		// Read first byte
		byte1 = byteArray[byteIndex] & 0xFF;
		byteIndex++;

		// 1-byte sequence (no continuation bytes)
		if ((byte1 & 0x80) == 0) {
			return byte1;
		}

		// 2-byte sequence
		if ((byte1 & 0xE0) == 0xC0) {
			byte2 = readContinuationByte();
			codePoint = ((byte1 & 0x1F) << 6) | byte2;
			if (codePoint >= 0x80) {
				return codePoint;
			} else {
				throw Error('Invalid continuation byte');
			}
		}

		// 3-byte sequence (may include unpaired surrogates)
		if ((byte1 & 0xF0) == 0xE0) {
			byte2 = readContinuationByte();
			byte3 = readContinuationByte();
			codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3;
			if (codePoint >= 0x0800) {
				checkScalarValue(codePoint);
				return codePoint;
			} else {
				throw Error('Invalid continuation byte');
			}
		}

		// 4-byte sequence
		if ((byte1 & 0xF8) == 0xF0) {
			byte2 = readContinuationByte();
			byte3 = readContinuationByte();
			byte4 = readContinuationByte();
			codePoint = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0C) |
				(byte3 << 0x06) | byte4;
			if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
				return codePoint;
			}
		}

		throw Error('Invalid UTF-8 detected');
	}

	var byteArray;
	var byteCount;
	var byteIndex;
	function utf8decode(byteString) {
		byteArray = ucs2decode(byteString);
		byteCount = byteArray.length;
		byteIndex = 0;
		var codePoints = [];
		var tmp;
		while ((tmp = decodeSymbol()) !== false) {
			codePoints.push(tmp);
		}
		return ucs2encode(codePoints);
	}

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

	root.version = '3.0.0';
	root.encode = utf8encode;
	root.decode = utf8decode;

}(exports));
});

// Some code originally from localForage
// See: https://github.com/localForage/localForage/blob/master/src/utils/isIndexedDBValid.js
/**
 * @hidden
 * @returns {boolean}
 */
function isIndexedDbValid() {
    // We mimic PouchDB here
    // Following #7085 (see https://github.com/pouchdb/pouchdb/issues/7085)
    // buggy idb versions (typically Safari < 10.1) are considered valid.
    // On Firefox SecurityError is thrown while referencing indexedDB if cookies
    // are not allowed. `typeof indexedDB` also triggers the error.
    try {
        // some outdated implementations of IDB that appear on Samsung
        // and HTC Android devices <4.4 are missing IDBKeyRange
        return typeof indexedDB !== 'undefined' && typeof IDBKeyRange !== 'undefined';
    }
    catch (e) {
        return false;
    }
}

/**
 * Error thrown by {@link IStorageAdapter.store} method when saving a value
 * with a key that already exists in store.
 */
class StorageEntryAlreadyExistsError extends VirgilError {
    constructor(key) {
        super(`Storage entry ${key ? 'with key ' + name : 'with the given key'}already exists`, 'StorageEntryAlreadyExistsError', StorageEntryAlreadyExistsError);
    }
}

// Some code originally from localForage in
// [localForage](https://github.com/localForage/localForage).
// Transaction Modes
const READ_ONLY = 'readonly';
const READ_WRITE = 'readwrite';
const dbContexts = {};
/**
 * Implementation of {@link IStorageAdapter} that uses IndexedDB for
 * persistence. For use in browsers.
 */
class IndexedDbStorageAdapter {
    /**
     * Initializes an instance of `IndexedDbStorageAdapter`.
     * @param {IStorageAdapterConfig} config - Configuration options.
     * Currently only `name` is supported and must be the name of the
     * IndexedDB database where the data will be stored.
     */
    constructor(config) {
        // Open the IndexedDB database (automatically creates one if one didn't
        // previously exist), using any options set in the config.
        this._initStorage = () => {
            const dbInfo = {
                db: null,
                name: this._defaultConfig.name,
                storeName: this._defaultConfig.storeName,
                version: this._defaultConfig.version
            };
            // Get the current context of the database;
            let dbContext = dbContexts[dbInfo.name];
            // ...or create a new context.
            if (!dbContext) {
                dbContext = createDbContext();
                // Register the new context in the global container.
                dbContexts[dbInfo.name] = dbContext;
            }
            // Initialize the connection process only when
            // all the related storages aren't pending.
            return Promise.resolve()
                .then(() => {
                dbInfo.db = dbContext.db;
                // Get the connection or open a new one without upgrade.
                return _getOriginalConnection(dbInfo);
            })
                .then(db => {
                dbInfo.db = db;
                if (_isUpgradeNeeded(dbInfo, this._defaultConfig.version)) {
                    // Reopen the database for upgrading.
                    return _getUpgradedConnection(dbInfo);
                }
                return db;
            })
                .then(db => {
                dbInfo.db = dbContext.db = db;
                this._dbInfo = dbInfo;
            });
        };
        // Specialize the default `ready()` function by making it dependent
        // on the current database operations. Thus, the driver will be actually
        // ready when it's been initialized (default) *and* there are no pending
        // operations on the database (initiated by some other instances).
        this.ready = () => {
            const promise = this._ready.then(() => {
                const dbContext = dbContexts[this._dbInfo.name];
                if (dbContext && dbContext.dbReady) {
                    return dbContext.dbReady;
                }
            });
            return promise;
        };
        if (!isIndexedDbValid()) {
            throw new Error('Cannot use IndexedDbStorageAdapter. indexedDb is not supported');
        }
        this._defaultConfig = {
            name: config.name,
            version: 1,
            storeName: 'keyvaluepairs'
        };
        this._ready = this._initStorage();
    }
    /**
     * @inheritDoc
     */
    store(key, data) {
        key = normalizeKey(key);
        return new Promise((resolve, reject) => {
            this.ready().then(() => {
                createTransaction(this._dbInfo, READ_WRITE, (err, transaction) => {
                    if (err) {
                        return reject(err);
                    }
                    try {
                        const store = transaction.objectStore(this._dbInfo.storeName);
                        const req = store.add(data, key);
                        transaction.oncomplete = () => {
                            resolve();
                        };
                        transaction.onabort = transaction.onerror = () => {
                            let error = req.error
                                ? req.error
                                : req.transaction.error;
                            if (error && error.name === 'ConstraintError') {
                                reject(new StorageEntryAlreadyExistsError(key));
                            }
                            reject(error);
                        };
                    }
                    catch (error) {
                        reject(error);
                    }
                });
            }).catch(reject);
        });
    }
    /**
     * @inheritDoc
     */
    load(key) {
        key = normalizeKey(key);
        return new Promise((resolve, reject) => {
            this.ready().then(() => {
                createTransaction(this._dbInfo, READ_ONLY, (err, transaction) => {
                    if (err) {
                        return reject(err);
                    }
                    try {
                        const store = transaction.objectStore(this._dbInfo.storeName);
                        const req = store.get(key);
                        req.onsuccess = function () {
                            if (req.result == null) {
                                return resolve(null);
                            }
                            resolve(convertDbValue(req.result));
                        };
                        req.onerror = function () {
                            reject(req.error);
                        };
                    }
                    catch (e) {
                        reject(e);
                    }
                });
            }).catch(reject);
        });
    }
    /**
     * @inheritDoc
     */
    exists(key) {
        key = normalizeKey(key);
        return new Promise((resolve, reject) => {
            this.ready().then(() => {
                createTransaction(this._dbInfo, READ_ONLY, (err, transaction) => {
                    if (err) {
                        return reject(err);
                    }
                    try {
                        const store = transaction.objectStore(this._dbInfo.storeName);
                        const req = store.openCursor(key);
                        req.onsuccess = () => {
                            const cursor = req.result;
                            resolve(cursor !== null);
                        };
                        req.onerror = () => {
                            reject(req.error);
                        };
                    }
                    catch (e) {
                        reject(e);
                    }
                });
            }).catch(reject);
        });
    }
    /**
     * @inheritDoc
     */
    remove(key) {
        key = normalizeKey(key);
        return new Promise((resolve, reject) => {
            this.ready().then(() => {
                createTransaction(this._dbInfo, READ_WRITE, (err, transaction) => {
                    if (err) {
                        return reject(err);
                    }
                    try {
                        const store = transaction.objectStore(this._dbInfo.storeName);
                        const countReq = store.count(key);
                        let delReq;
                        countReq.onsuccess = () => {
                            const count = countReq.result;
                            if (count === 0) {
                                return resolve(false);
                            }
                            // safe for IE and some versions of Android
                            // (including those used by Cordova).
                            // Normally IE won't like `.delete()` and will insist on
                            // using `['delete']()`
                            delReq = store['delete'](key);
                            delReq.onsuccess = () => resolve(true);
                        };
                        // The request will be also be aborted if we've exceeded our storage
                        // space.
                        transaction.onabort = transaction.onerror = () => {
                            const req = delReq || countReq;
                            const err = req.error
                                ? req.error
                                : req.transaction.error;
                            reject(err);
                        };
                    }
                    catch (e) {
                        reject(e);
                    }
                });
            }).catch(reject);
        });
    }
    /**
     * @inheritDoc
     */
    update(key, data) {
        key = normalizeKey(key);
        return new Promise((resolve, reject) => {
            this.ready().then(() => {
                createTransaction(this._dbInfo, READ_WRITE, (err, transaction) => {
                    if (err) {
                        return reject(err);
                    }
                    try {
                        const store = transaction.objectStore(this._dbInfo.storeName);
                        const req = store.put(data, key);
                        req.onsuccess = () => {
                            resolve();
                        };
                        req.onerror = () => {
                            reject(req.error);
                        };
                    }
                    catch (error) {
                        reject(error);
                    }
                });
            }).catch(reject);
        });
    }
    /**
     * @inheritDoc
     */
    clear() {
        return new Promise((resolve, reject) => {
            this.ready().then(() => {
                createTransaction(this._dbInfo, READ_WRITE, (err, transaction) => {
                    if (err) {
                        return reject(err);
                    }
                    try {
                        const store = transaction.objectStore(this._dbInfo.storeName);
                        const req = store.clear();
                        transaction.oncomplete = () => resolve();
                        transaction.onabort = transaction.onerror = () => {
                            const err = req.error
                                ? req.error
                                : req.transaction.error;
                            reject(err);
                        };
                    }
                    catch (e) {
                        reject(e);
                    }
                });
            }).catch(reject);
        });
    }
    /**
     * @inheritDoc
     */
    list() {
        return new Promise((resolve, reject) => {
            this.ready().then(() => {
                createTransaction(this._dbInfo, READ_ONLY, (err, transaction) => {
                    if (err) {
                        return reject(err);
                    }
                    try {
                        const store = transaction.objectStore(this._dbInfo.storeName);
                        const req = store.openCursor();
                        const entries = [];
                        req.onsuccess = () => {
                            const cursor = req.result;
                            if (!cursor) {
                                resolve(entries);
                            }
                            else {
                                entries.push(convertDbValue(cursor.value));
                                cursor.continue();
                            }
                        };
                        req.onerror = () => {
                            reject(req.error);
                        };
                    }
                    catch (e) {
                        reject(e);
                    }
                });
            }).catch(reject);
        });
    }
}
function createDbContext() {
    return {
        // Database.
        db: null,
        // Database readiness (promise).
        dbReady: null,
        // Deferred operations on the database.
        deferredOperations: []
    };
}
function _deferReadiness(dbInfo) {
    const dbContext = dbContexts[dbInfo.name];
    // Create a deferred object representing the current database operation.
    const deferredOperation = {};
    deferredOperation.promise = new Promise((resolve, reject) => {
        deferredOperation.resolve = resolve;
        deferredOperation.reject = reject;
    });
    // Enqueue the deferred operation.
    dbContext.deferredOperations.push(deferredOperation);
    // Chain its promise to the database readiness.
    if (!dbContext.dbReady) {
        dbContext.dbReady = deferredOperation.promise;
    }
    else {
        dbContext.dbReady = dbContext.dbReady.then(() => deferredOperation.promise);
    }
}
function _advanceReadiness(dbInfo) {
    const dbContext = dbContexts[dbInfo.name];
    // Dequeue a deferred operation.
    const deferredOperation = dbContext.deferredOperations.pop();
    // Resolve its promise (which is part of the database readiness
    // chain of promises).
    if (deferredOperation) {
        deferredOperation.resolve();
        return deferredOperation.promise;
    }
}
function _rejectReadiness(dbInfo, err) {
    const dbContext = dbContexts[dbInfo.name];
    // Dequeue a deferred operation.
    const deferredOperation = dbContext.deferredOperations.pop();
    // Reject its promise (which is part of the database readiness
    // chain of promises).
    if (deferredOperation) {
        deferredOperation.reject(err);
        return deferredOperation.promise;
    }
}
function _getConnection(dbInfo, upgradeNeeded) {
    return new Promise(function (resolve, reject) {
        dbContexts[dbInfo.name] = dbContexts[dbInfo.name] || createDbContext();
        if (dbInfo.db) {
            if (upgradeNeeded) {
                _deferReadiness(dbInfo);
                dbInfo.db.close();
            }
            else {
                return resolve(dbInfo.db);
            }
        }
        const dbArgs = [dbInfo.name];
        if (upgradeNeeded) {
            dbArgs.push(String(dbInfo.version));
        }
        const openReq = indexedDB.open.apply(indexedDB, dbArgs);
        if (upgradeNeeded) {
            openReq.onupgradeneeded = (e) => {
                const db = openReq.result;
                try {
                    db.createObjectStore(dbInfo.storeName);
                }
                catch (ex) {
                    if (ex.name === 'ConstraintError') {
                        console.warn('The database "' +
                            dbInfo.name +
                            '"' +
                            ' has been upgraded from version ' +
                            e.oldVersion +
                            ' to version ' +
                            e.newVersion +
                            ', but the storage "' +
                            dbInfo.storeName +
                            '" already exists.');
                    }
                    else {
                        throw ex;
                    }
                }
            };
        }
        openReq.onerror = (e) => {
            e.preventDefault();
            reject(openReq.error);
        };
        openReq.onsuccess = () => {
            resolve(openReq.result);
            _advanceReadiness(dbInfo);
        };
    });
}
function _getOriginalConnection(dbInfo) {
    return _getConnection(dbInfo, false);
}
function _getUpgradedConnection(dbInfo) {
    return _getConnection(dbInfo, true);
}
function _isUpgradeNeeded(dbInfo, defaultVersion) {
    if (!dbInfo.db) {
        return true;
    }
    const isNewStore = !dbInfo.db.objectStoreNames.contains(dbInfo.storeName);
    const isDowngrade = dbInfo.version < dbInfo.db.version;
    const isUpgrade = dbInfo.version > dbInfo.db.version;
    if (isDowngrade) {
        // If the version is not the default one
        // then warn for impossible downgrade.
        if (dbInfo.version !== defaultVersion) {
            console.warn('The database "' +
                dbInfo.name +
                '"' +
                " can't be downgraded from version " +
                dbInfo.db.version +
                ' to version ' +
                dbInfo.version +
                '.');
        }
        // Align the versions to prevent errors.
        dbInfo.version = dbInfo.db.version;
    }
    if (isUpgrade || isNewStore) {
        // If the store is new then increment the version (if needed).
        // This will trigger an "upgradeneeded" event which is required
        // for creating a store.
        if (isNewStore) {
            const incVersion = dbInfo.db.version + 1;
            if (incVersion > dbInfo.version) {
                dbInfo.version = incVersion;
            }
        }
        return true;
    }
    return false;
}
// Try to establish a new db connection to replace the
// current one which is broken (i.e. experiencing
// InvalidStateError while creating a transaction).
function _tryReconnect(dbInfo) {
    _deferReadiness(dbInfo);
    const dbContext = dbContexts[dbInfo.name];
    dbInfo.db = null;
    return _getOriginalConnection(dbInfo)
        .then(db => {
        dbInfo.db = db;
        if (_isUpgradeNeeded(dbInfo)) {
            // Reopen the database for upgrading.
            return _getUpgradedConnection(dbInfo);
        }
        return db;
    })
        .then(db => {
        // store the latest db reference
        // in case the db was upgraded
        dbInfo.db = dbContext.db = db;
    })
        .catch(err => {
        _rejectReadiness(dbInfo, err);
        throw err;
    });
}
// FF doesn't like Promises (micro-tasks) and IDDB store operations,
// so we have to do it with callbacks
function createTransaction(dbInfo, mode, callback, retries) {
    if (retries === undefined) {
        retries = 1;
    }
    try {
        const tx = dbInfo.db.transaction(dbInfo.storeName, mode);
        callback(null, tx);
    }
    catch (err) {
        if (retries > 0 &&
            (!dbInfo.db ||
                err.name === 'InvalidStateError' ||
                err.name === 'NotFoundError')) {
            Promise.resolve()
                .then(() => {
                if (!dbInfo.db ||
                    (err.name === 'NotFoundError' &&
                        !dbInfo.db.objectStoreNames.contains(dbInfo.storeName) &&
                        dbInfo.version <= dbInfo.db.version)) {
                    // increase the db version, to create the new ObjectStore
                    if (dbInfo.db) {
                        dbInfo.version = dbInfo.db.version + 1;
                    }
                    // Reopen the database for upgrading.
                    return _getUpgradedConnection(dbInfo);
                }
            })
                .then(() => {
                return _tryReconnect(dbInfo).then(function () {
                    createTransaction(dbInfo, mode, callback, retries - 1);
                });
            })
                .catch(callback);
        }
        callback(err);
    }
}
function normalizeKey(key) {
    // Cast the key to a string, as that's all we can set as a key.
    if (typeof key !== 'string') {
        console.warn(`${key} used as a key, but it is not a string.`);
        key = String(key);
    }
    return key;
}
function convertDbValue(value) {
    if (value instanceof ArrayBuffer) {
        const uint8Array = new Uint8Array(value);
        // @ts-ignore
        return utf8.decode(String.fromCharCode.apply(null, uint8Array));
    }
    if (typeof value === 'string') {
        return value;
    }
    throw new TypeError('Unknown value format');
}

/**
 * Error thrown from {@link PrivateKeyStorage.save} method when saving
 * a private key with a name that already exists in store.
 */
class PrivateKeyExistsError extends VirgilError {
    constructor(message = 'Private key with same name already exists') {
        super(message, 'PrivateKeyExistsError', PrivateKeyExistsError);
    }
}

const DEFAULTS$1 = {
    dir: '.virgil_keys',
    name: 'VirgilKeys'
};
/**
 * Class representing a storage container for private key data.
 * Use this class if you need to load the keys stored with
 * version 4.x of this library. For new code, use the
 * {@link PrivateKeyStorage} instead.
 *
 * @deprecated since version 5.0
 */
class KeyStorage {
    constructor(config = {}) {
        console.log('Warning! `KeyStorage` is deprecated. Use `PrivateKeyStorage` instead.');
        this.adapter = resolveAdapter$1(config);
    }
    /**
     * Checks whether a private key data with the given name exist in persistent storage.
     * @param {string} name - Name to check.
     * @returns {Promise<boolean>} - True if key data exist, otherwise false.
     */
    exists(name) {
        validateName$1(name);
        return this.adapter.exists(name);
    }
    /**
     * Loads the private key data by the given name.
     * @param {string} name - Name of key data to load.
     * @returns {Promise<string | null>} - Private key data as a string,
     * or null if there is no data for the given name.
     */
    load(name) {
        validateName$1(name);
        return this.adapter.load(name);
    }
    /**
     * Removes the private key data stored under the given name from persistent storage.
     * @param {string} name - Name of the key data to remove.
     * @returns {Promise<boolean>} - True if the key has been removed, otherwise false.
     */
    remove(name) {
        validateName$1(name);
        return this.adapter.remove(name);
    }
    /**
     * Persists the private key data under the given name.
     * @param {string} name - Name of the key data.
     * @param {string} data - The key data.
     * @returns {Promise<void>}
     */
    save(name, data) {
        validateName$1(name);
        return this.adapter.store(name, data)
            .catch(error => {
            if (error && error.code === 'EEXIST') {
                return Promise.reject(new PrivateKeyExistsError());
            }
            return Promise.reject(error);
        });
    }
}
function resolveAdapter$1(config) {
    if (typeof config === 'string') {
        return new IndexedDbStorageAdapter({ dir: config, name: config });
    }
    const { adapter } = config, rest = __rest(config, ["adapter"]);
    if (adapter != null) {
        return adapter;
    }
    return new IndexedDbStorageAdapter(Object.assign(Object.assign({}, DEFAULTS$1), rest));
}
function validateName$1(name) {
    if (!name)
        throw new TypeError('Argument `name` is required.');
}

/**
 * Error thrown when the value loaded from persistent storage cannot be
 * parsed as a {@link IKeyEntry} object.
 */
class InvalidKeyEntryError extends VirgilError {
    constructor(message = 'Loaded key entry was in invalid format.') {
        super(message, 'InvalidKeyEntryError', InvalidKeyEntryError);
    }
}
/**
 * Error thrown from {@link KeyEntryStorage.save} method when saving a
 * a key entry with the name that already exists in store.
 */
class KeyEntryAlreadyExistsError extends VirgilError {
    constructor(name) {
        super(`Key entry ${name ? 'named ' + name : 'with same name'}already exists`, 'KeyEntryAlreadyExistsError', KeyEntryAlreadyExistsError);
    }
}
/**
 * Error thrown from {@link KeyEntryStorage.update} method when updating
 * a key entry that doesn't exist in store.
 */
class KeyEntryDoesNotExistError extends VirgilError {
    constructor(name) {
        super(`Key entry ${name ? 'named ' + name : 'with the given name'} does not exist.`, 'KeyEntryDoesNotExistError', KeyEntryDoesNotExistError);
    }
}

const DEFAULTS = {
    dir: '.virgil_key_entries',
    name: 'VirgilKeyEntries'
};
const CREATION_DATE_KEY = 'creationDate';
const MODIFICATION_DATE_KEY = 'modificationDate';
/**
 * Class responsible for persisting private key bytes with optional
 * user-defined metadata.
 */
class KeyEntryStorage {
    /**
     * Initializes a new instance of `KeyEntryStorage`.
     *
     * @param {IKeyEntryStorageConfig} config - Instance configuration.
     */
    constructor(config = {}) {
        this.adapter = resolveAdapter(config);
    }
    /**
     * @inheritDoc
     */
    exists(name) {
        validateName(name);
        return this.adapter.exists(name);
    }
    /**
     * @inheritDoc
     */
    load(name) {
        validateName(name);
        return this.adapter.load(name).then(data => {
            if (data == null) {
                return null;
            }
            return deserializeKeyEntry(data);
        });
    }
    /**
     * @inheritDoc
     */
    remove(name) {
        validateName(name);
        return this.adapter.remove(name);
    }
    /**
     * @inheritDoc
     */
    save({ name, value, meta }) {
        validateNameProperty(name);
        validateValueProperty(value);
        const keyEntry = {
            name: name,
            value: value,
            meta: meta,
            creationDate: new Date(),
            modificationDate: new Date()
        };
        return this.adapter.store(name, serializeKeyEntry(keyEntry))
            .then(() => keyEntry)
            .catch(error => {
            if (error && error.name === 'StorageEntryAlreadyExistsError') {
                throw new KeyEntryAlreadyExistsError(name);
            }
            throw error;
        });
    }
    /**
     * @inheritDoc
     */
    list() {
        return this.adapter.list()
            .then(entries => entries.map(entry => deserializeKeyEntry(entry)));
    }
    /**
     * @inheritDoc
     */
    update({ name, value, meta }) {
        validateNameProperty(name);
        if (!(value || meta)) {
            throw new TypeError('Invalid argument. Either `value` or `meta` property is required.');
        }
        return this.adapter.load(name)
            .then(data => {
            if (data === null) {
                throw new KeyEntryDoesNotExistError(name);
            }
            const entry = deserializeKeyEntry(data);
            const updatedEntry = Object.assign(entry, {
                value: value || entry.value,
                meta: meta || entry.meta,
                modificationDate: new Date()
            });
            return this.adapter.update(name, serializeKeyEntry(updatedEntry))
                .then(() => updatedEntry);
        });
    }
    /**
     * @inheritDoc
     */
    clear() {
        return this.adapter.clear();
    }
}
function serializeKeyEntry(keyEntry) {
    return JSON.stringify(keyEntry);
}
function deserializeKeyEntry(data) {
    try {
        return JSON.parse(data, (key, value) => {
            if (key === CREATION_DATE_KEY || key === MODIFICATION_DATE_KEY) {
                return new Date(value);
            }
            return value;
        });
    }
    catch (error) {
        throw new InvalidKeyEntryError();
    }
}
function resolveAdapter(config) {
    if (typeof config === 'string') {
        return new IndexedDbStorageAdapter({ dir: config, name: config });
    }
    const { adapter } = config, rest = __rest(config, ["adapter"]);
    if (adapter != null) {
        return adapter;
    }
    return new IndexedDbStorageAdapter(Object.assign(Object.assign({}, DEFAULTS), rest));
}
const requiredArg = (name) => (value) => {
    if (!value)
        throw new TypeError(`Argument '${name}' is required.`);
};
const requiredProp = (name) => (value) => {
    if (!value)
        throw new TypeError(`Invalid argument. Property ${name} is required`);
};
const validateName = requiredArg('name');
const validateNameProperty = requiredProp('name');
const validateValueProperty = requiredProp('value');

/**
 * Class responsible for storage of private keys.
 */
class PrivateKeyStorage {
    /**
     * Initializes a new instance of `PrivateKeyStorage`.
     * @param {IPrivateKeyExporter} privateKeyExporter - Object responsible for
     * exporting private key bytes from `IPrivateKey` objects and importing
     * private key bytes into `IPrivateKey` objects.
     * @param {IKeyEntryStorage} keyEntryStorage - Object responsible for
     * persistence of private keys data.
     */
    constructor(privateKeyExporter, keyEntryStorage = new KeyEntryStorage()) {
        this.privateKeyExporter = privateKeyExporter;
        this.keyEntryStorage = keyEntryStorage;
    }
    /**
     * Persists the given `privateKey` and `meta` under the given `name`.
     * If an entry with the same name already exists rejects the returned
     * Promise with {@link PrivateKeyExistsError} error.
     *
     * @param {string} name - Name of the private key.
     * @param {IPrivateKey} privateKey - The private key object.
     * @param {Object<string, string>} [meta] - Optional metadata to store with the key.
     *
     * @returns {Promise<void>}
     */
    store(name, privateKey, meta) {
        return __awaiter(this, void 0, void 0, function* () {
            const privateKeyData = this.privateKeyExporter.exportPrivateKey(privateKey);
            try {
                yield this.keyEntryStorage.save({ name, value: privateKeyData.toString('base64'), meta });
            }
            catch (error) {
                if (error && error.name === 'KeyEntryAlreadyExistsError') {
                    throw new PrivateKeyExistsError(`Private key with the name ${name} already exists.`);
                }
                throw error;
            }
        });
    }
    /**
     * Retrieves the private key with the given `name` from persistent storage.
     * If private with the given name does not exist, resolves the returned
     * Promise with `null`.
     *
     * @param {string} name - Name of the private key to load.
     * @returns {Promise<IPrivateKeyEntry|null>}
     */
    load(name) {
        return __awaiter(this, void 0, void 0, function* () {
            const keyEntry = yield this.keyEntryStorage.load(name);
            if (keyEntry === null) {
                return null;
            }
            const privateKey = this.privateKeyExporter.importPrivateKey(keyEntry.value);
            return {
                privateKey,
                meta: keyEntry.meta
            };
        });
    }
    /**
     * Removes the private key entry with the given `name` from persistent
     * storage.
     *
     * @param {string} name - Name of the private key to remove.
     * @returns {Promise<void>}
     */
    delete(name) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.keyEntryStorage.remove(name);
        });
    }
}

export { CachingJwtProvider, CallbackJwtProvider, CardManager, ConstAccessTokenProvider, IndexedDbStorageAdapter as DefaultStorageAdapter, GeneratorJwtProvider, InvalidKeyEntryError, Jwt, JwtGenerator, JwtVerifier, KeyEntryAlreadyExistsError, KeyEntryDoesNotExistError, KeyEntryStorage, KeyStorage, ModelSigner, PrivateKeyStorage, RawSignedModel, StorageEntryAlreadyExistsError, VirgilAgent, VirgilCardVerifier };