UNPKG

virgil-sdk

Version:
2,090 lines 73 kB
import base64 from 'base-64';
import fetchPonyfill from 'fetch-ponyfill';
import fs from 'fs';
import path from 'path';
import crypto from 'crypto';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';

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

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 (!undefined && typeof global !== 'undefined') {
                const majorVersion = process.version.replace(/\.\d+\.\d+$/, '').replace('v', '');
                return `Node${majorVersion}/${process.platform}`;
            }
            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);
    }
}

/**
 * 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);
    }
}

const NO_SUCH_FILE = 'ENOENT';
const FILE_EXISTS = 'EEXIST';
/**
 * Implementation of {@link IStorageAdapter} that uses file system for
 * persistence. For use in Node.js.
 */
class FileSystemStorageAdapter {
    /**
     * Initializes a new instance of `FileSystemStorageAdapter`.
     * @param {IStorageAdapterConfig} config - Configuration options.
     * Currently only `dir` is supported and must be a file system path
     * to the folder where the data will be stored.
     */
    constructor(config) {
        this.config = config;
        mkdirp.sync(path.resolve(this.config.dir));
    }
    /**
     * @inheritDoc
     */
    store(key, data) {
        return new Promise((resolve, reject) => {
            const file = this.resolveFilePath(key);
            fs.writeFile(file, data, { flag: 'wx' }, err => {
                if (err && err.code === FILE_EXISTS) {
                    return reject(new StorageEntryAlreadyExistsError());
                }
                resolve();
            });
        });
    }
    /**
     * @inheritDoc
     */
    load(key) {
        return Promise.resolve().then(() => {
            const filename = this.resolveFilePath(key);
            return readFileAsync(filename);
        });
    }
    /**
     * @inheritDoc
     */
    exists(key) {
        return new Promise((resolve, reject) => {
            const file = this.resolveFilePath(key);
            fs.access(file, err => {
                if (err) {
                    if (err.code === NO_SUCH_FILE) {
                        return resolve(false);
                    }
                    return reject(err);
                }
                resolve(true);
            });
        });
    }
    /**
     * @inheritDoc
     */
    remove(key) {
        return new Promise((resolve, reject) => {
            const file = this.resolveFilePath(key);
            fs.unlink(file, err => {
                if (err) {
                    if (err.code === NO_SUCH_FILE) {
                        return resolve(false);
                    }
                    return reject(err);
                }
                resolve(true);
            });
        });
    }
    /**
     * @inheritDoc
     */
    update(key, data) {
        return new Promise((resolve, reject) => {
            const file = this.resolveFilePath(key);
            fs.writeFile(file, data, { flag: 'w' }, err => {
                if (err) {
                    return reject(err);
                }
                resolve();
            });
        });
    }
    /**
     * @inheritDoc
     */
    clear() {
        return new Promise((resolve, reject) => {
            rimraf(this.config.dir, err => {
                if (err) {
                    return reject(err);
                }
                resolve();
            });
        });
    }
    /**
     * @inheritDoc
     */
    list() {
        return new Promise((resolve, reject) => {
            fs.readdir(this.config.dir, (err, files) => {
                if (err) {
                    return reject(err);
                }
                Promise.all(files.map(filename => readFileAsync(path.resolve(this.config.dir, filename)))).then(contents => {
                    const entries = contents.filter(content => content !== null);
                    resolve(entries);
                }).catch(reject);
            });
        });
    }
    resolveFilePath(key) {
        return path.resolve(this.config.dir, this.hash(key));
    }
    hash(data) {
        return crypto
            .createHash('sha256')
            .update(data)
            .digest('hex');
    }
}
function readFileAsync(filename) {
    return new Promise((resolve, reject) => {
        fs.readFile(filename, (err, data) => {
            if (err) {
                if (err.code === NO_SUCH_FILE) {
                    return resolve(null);
                }
                return reject(err);
            }
            resolve(data.toString('utf8'));
        });
    });
}

/**
 * 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 FileSystemStorageAdapter({ dir: config, name: config });
    }
    const { adapter } = config, rest = __rest(config, ["adapter"]);
    if (adapter != null) {
        return adapter;
    }
    return new FileSystemStorageAdapter(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 FileSystemStorageAdapter({ dir: config, name: config });
    }
    const { adapter } = config, rest = __rest(config, ["adapter"]);
    if (adapter != null) {
        return adapter;
    }
    return new FileSystemStorageAdapter(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, FileSystemStorageAdapter as DefaultStorageAdapter, GeneratorJwtProvider, InvalidKeyEntryError, Jwt, JwtGenerator, JwtVerifier, KeyEntryAlreadyExistsError, KeyEntryDoesNotExistError, KeyEntryStorage, KeyStorage, ModelSigner, PrivateKeyStorage, RawSignedModel, StorageEntryAlreadyExistsError, VirgilAgent, VirgilCardVerifier };