import { ICardCrypto } from '../types' ; import { IRawSignedModelJson , RawSignedModel } from './RawSignedModel' ; import { CardClient } from '../Client/CardClient' ; import { ModelSigner } from './ModelSigner' ; import { ICard , INewCardParams , IRawCardContent } from './ICard' ; import { ICardVerifier } from './CardVerifier' ; import { IAccessToken , IAccessTokenProvider , ITokenContext } from '../Auth/AccessTokenProviders' ; import { cardToRawSignedModel , generateRawSigned , linkedCardList , parseRawSignedModel } from './CardUtils' ; import { assert } from '../Lib/assert' ; import { VirgilCardVerificationError } from './errors' ; import { VirgilHttpError , ErrorCode } from '../Client/errors' ; import { SelfSigner } from './constants' ; import { IProductInfo } from '../Client/Connection' ; const CARDS_SERVICE__NAME = 'cards' ; hidden const throwingAccessTokenProvider = { getToken : ( ) => { throw new Error ( 'Please set `CardManager.accessTokenProvider` to be able to make requests.' ) ; } } ; type Omit < T , K extends keyof T > = Pick < T , Exclude < keyof T , K > > ; const getCardServiceTokenContext = ( context ) : ITokenContext => ( { ... context , service : CARDS_SERVICE__NAME , } ) ; /**
 * User-specified callback function to be called just before publishing
 * a Virgil Card to append additional signatures to it.
 *
 * Receives a single parameter of type  RawSignedModel and must
 * return a  RawSignedModel with additional signatures.
 * Use  ModelSigner to add signatures.
  export type ISignCallback = ( model : RawSignedModel ) => Promise < RawSignedModel > | RawSignedModel ; export { ICard } ; /**
 *  CardManager initialization options.
  export interface ICardManagerParams { /**
     * Implementation of  IAccessTokenProvider to use to
     * get the token for requests authentication. Optional.
      readonly accessTokenProvider ? : IAccessTokenProvider ; /**
     * Implementation of  ICardCrypto interface.
      readonly cardCrypto : ICardCrypto ; /**
     * Implementation of  ICardVerifier interface to
     * be used to verify the validity of Virgil Cards received
     * from network and via  CardManager . importCard method.
      readonly cardVerifier : ICardVerifier ; /**
     * Alters the behavior of the `CardManager` when it receives an
     * HTTP Unauthorized (401) error from the server. If this is set to
     * `true` it will silently request a new token form the
     *  CardManager . accessTokenProvider and re-send the request,
     * otherwise the error will be returned to the client.
      readonly retryOnUnauthorized : boolean ; /**
     * Optional  ISignCallback .
      readonly signCallback ? : ISignCallback ; hidden readonly apiUrl ? : string ; hidden readonly productInfo ? : IProductInfo ; } /**
 * Class responsible for creating, publishing and retrieving Virgil Cards.
 */ export class CardManager { public readonly crypto : ICardCrypto ; public readonly client : CardClient ; public readonly modelSigner : ModelSigner ; public readonly signCallback ? : ISignCallback ; public readonly cardVerifier : ICardVerifier ; public retryOnUnauthorized : boolean ; public accessTokenProvider : IAccessTokenProvider ; public 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  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 returns { RawSignedModel } generateRawCard ( cardParams ) { const model = generateRawSigned ( this . crypto , cardParams ) ; this . modelSigner . sign ( { model , signerPrivateKey : cardParams . privateKey , signer : SelfSigner , extraFields : cardParams . extraFields } ) ; return model ; } param { INewCardParams } cardParams returns { Promise < ICard > } async publishCard ( cardParams ) { validateCardParams ( cardParams ) ; const tokenContext = { service : CARDS_SERVICE__NAME , identity : cardParams . identity , operation : 'publish' } ; const token = await this . accessTokenProvider . getToken ( tokenContext ) ; const rawSignedModel = this . generateRawCard ( Object . assign ( { } , cardParams , { identity : token . identity ( ) } ) ) ; return await this . publishRawSignedModel ( rawSignedModel , tokenContext , token ) ; } /**
     * Publishes a previously generated card in the form of
     *  RawSignedModel object.
     *
     *  param { RawSignedModel } rawCard returns { Promise < ICard > } async publishRawCard ( rawCard ) { assert ( rawCard != null && rawCard . contentSnapshot != null , '`rawCard` should not be empty' ) ; const cardDetails = JSON . parse ( rawCard . contentSnapshot ) as IRawCardContent ; const tokenContext = getCardServiceTokenContext ( { identity : cardDetails . identity , operation : 'publish' } ) ; const token = await this . accessTokenProvider . getToken ( tokenContext ) ; return this . publishRawSignedModel ( rawCard , tokenContext , token ) ; } param { string } cardId returns { Promise < ICard > } async getCard ( cardId ) { const tokenContext = getCardServiceTokenContext ( { operation : 'get' } ) ; const accessToken = await this . accessTokenProvider . getToken ( tokenContext ) ; const cardWithStatus = await this . tryDo ( tokenContext , accessToken , async ( token ) => await 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 ; } param { string | string [ ] } identities returns { Promise < ICard [ ] > } async searchCards ( identities ) { 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 = await this . accessTokenProvider . getToken ( tokenContext ) ; const rawCards = await this . tryDo ( tokenContext , accessToken , async ( token ) => await 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  CardManager . getCard method.
     * Also revoked cards will be absent in the  CardManager . searchCards result.
     *  param { string } cardId returns { Promise } async revokeCard ( cardId ) { if ( ! cardId ) throw new TypeError ( 'Argument `cardId` is required' ) ; const tokenContext = getCardServiceTokenContext ( { operation : 'revoke' } ) ; const accessToken = await this . accessTokenProvider . getToken ( tokenContext ) ; await this . tryDo ( tokenContext , accessToken , async ( token ) => await this . client . revokeCard ( cardId , token . toString ( ) ) ) ; } /**
     * Converts the card in the form of  RawSignedModel object to the
     *  ICard object.
     *
     *  see CardManager . exportCard param { RawSignedModel } rawCard 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  ICard object.
     *
     *  see CardManager . exportCardAsString param { string } str 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
     *  ICard object.
     *
     *  see 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  ICard object to the
     *  RawSignedModel object.
     *
     *  see CardManager . importCard param { ICard } card returns { RawSignedModel } exportCard ( card ) { return cardToRawSignedModel ( card ) ; } /**
     * Converts the card in the form of  ICard object to the string
     * in base64 encoding.
     *
     *  see CardManager . importCardFromString param { ICard } card returns { string } exportCardAsString ( card ) { return this . exportCard ( card ) . toString ( ) ; } /**
     * Converts the card in the form of  ICard object to the
     * JSON-serializable object form.
     *
     *  see CardManager . importCardFromJson param { ICard } card returns { IRawSignedModelJson } exportCardAsJson ( card ) { return this . exportCard ( card ) . toJson ( ) ; } hidden private async publishRawSignedModel ( rawCard , context , accessToken ) { if ( this . signCallback != null ) { rawCard = await this . signCallback ( rawCard ) ; } const publishedModel = await this . tryDo ( context , accessToken , async ( token ) => await 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 private async tryDo < T > ( context , token , func ) { try { return await func ( token ) ; } catch ( e ) { if ( e instanceof VirgilHttpError && e . httpStatus === 401 && e . errorCode === ErrorCode . AccessTokenExpired && this . retryOnUnauthorized ) { token = await this . accessTokenProvider . getToken ( getCardServiceTokenContext ( { identity : context . identity , operation : context . operation , forceReload : true } ) ) ; return await func ( token ) ; } throw e ; } } /**
     * Delegates to the  CardManager . cardVerifier to verify the validity
     * of the `cards`.
     *
     *  throws VirgilCardVerificationError if any of the cards is not
     * valid.
     *
     *  param { ICard [ ] } cards private 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' ) ; } }