UNPKG

@sphereon/ssi-sdk.issuance-branding

Version:

380 lines (336 loc) • 15.8 kB
import { IAgentPlugin } from '@veramo/core' import { downloadImage, getImageDimensions, getImageMediaType, IImageDimensions, IImageResource } from '@sphereon/ssi-sdk.core' import { AbstractIssuanceBrandingStore, IAddIssuerBrandingArgs, IBasicCredentialBranding, IBasicCredentialLocaleBranding, IBasicImageAttributes, IBasicIssuerBranding, IBasicIssuerLocaleBranding, ICredentialBranding, ICredentialLocaleBranding, IGetCredentialBrandingArgs, IGetCredentialLocaleBrandingArgs, IGetIssuerBrandingArgs, IIssuerBranding, IIssuerLocaleBranding, ILocaleBranding, } from '@sphereon/ssi-sdk.data-store' import { schema } from '../index' import { IAddCredentialBrandingArgs, IAdditionalImageAttributes, IDeletionResult, IIssuanceBranding, IRequiredContext, IRemoveCredentialBrandingArgs, IRemoveIssuerBrandingArgs, IUpdateCredentialBrandingArgs, IUpdateIssuerBrandingArgs, IAddCredentialLocaleBrandingArgs, IAddIssuerLocaleBrandingArgs, IGetIssuerLocaleBrandingArgs, IRemoveIssuerLocaleBrandingArgs, IRemoveCredentialLocaleBrandingArgs, IUpdateCredentialLocaleBrandingArgs, IUpdateIssuerLocaleBrandingArgs, ICredentialBrandingFromArgs, IIssuerBrandingFromArgs, } from '../types/IIssuanceBranding' import Debug from 'debug' const debug: Debug.Debugger = Debug('sphereon:ssi-sdk:issuance-branding') export const issuanceBrandingMethods: Array<string> = [ 'ibAddCredentialBranding', 'ibGetCredentialBranding', 'ibUpdateCredentialBranding', 'ibRemoveCredentialBranding', 'ibAddCredentialLocaleBranding', 'ibGetCredentialLocaleBranding', 'ibRemoveCredentialLocaleBranding', 'ibUpdateCredentialLocaleBranding', 'ibCredentialLocaleBrandingFrom', 'ibAddIssuerBranding', 'ibGetIssuerBranding', 'ibUpdateIssuerBranding', 'ibRemoveIssuerBranding', 'ibAddIssuerLocaleBranding', 'ibGetIssuerLocaleBranding', 'ibRemoveIssuerLocaleBranding', 'ibUpdateIssuerLocaleBranding', 'ibIssuerLocaleBrandingFrom', ] const EMPTY_IMAGE_ATTRIBUTES = { mediaType: undefined, dataUri: undefined, dimensions: undefined, } /** * {@inheritDoc IIssuanceBranding} */ export class IssuanceBranding implements IAgentPlugin { readonly schema = schema.IIssuanceBranding readonly methods: IIssuanceBranding = { ibAddCredentialBranding: this.ibAddCredentialBranding.bind(this), ibGetCredentialBranding: this.ibGetCredentialBranding.bind(this), ibUpdateCredentialBranding: this.ibUpdateCredentialBranding.bind(this), ibRemoveCredentialBranding: this.ibRemoveCredentialBranding.bind(this), ibAddCredentialLocaleBranding: this.ibAddCredentialLocaleBranding.bind(this), ibGetCredentialLocaleBranding: this.ibGetCredentialLocaleBranding.bind(this), ibRemoveCredentialLocaleBranding: this.ibRemoveCredentialLocaleBranding.bind(this), ibUpdateCredentialLocaleBranding: this.ibUpdateCredentialLocaleBranding.bind(this), ibCredentialLocaleBrandingFrom: this.ibCredentialLocaleBrandingFrom.bind(this), ibAddIssuerBranding: this.ibAddIssuerBranding.bind(this), ibGetIssuerBranding: this.ibGetIssuerBranding.bind(this), ibUpdateIssuerBranding: this.ibUpdateIssuerBranding.bind(this), ibRemoveIssuerBranding: this.inRemoveIssuerBranding.bind(this), ibAddIssuerLocaleBranding: this.ibAddIssuerLocaleBranding.bind(this), ibGetIssuerLocaleBranding: this.ibAGetIssuerLocaleBranding.bind(this), ibRemoveIssuerLocaleBranding: this.ibRemoveIssuerLocaleBranding.bind(this), ibUpdateIssuerLocaleBranding: this.ibUpdateIssuerLocaleBranding.bind(this), ibIssuerLocaleBrandingFrom: this.ibIssuerLocaleBrandingFrom.bind(this), } private readonly store: AbstractIssuanceBrandingStore constructor(options: { store: AbstractIssuanceBrandingStore }) { this.store = options.store } /** {@inheritDoc IIssuanceBranding.ibAddCredentialBranding} */ private async ibAddCredentialBranding(args: IAddCredentialBrandingArgs, context: IRequiredContext): Promise<ICredentialBranding> { const localeBranding: Array<IBasicIssuerLocaleBranding> = await Promise.all( args.localeBranding.map( (localeBranding: IBasicCredentialLocaleBranding): IBasicCredentialLocaleBranding | Promise<IBasicCredentialLocaleBranding> => this.setAdditionalImageAttributes(localeBranding), ), ) const credentialBranding: IBasicCredentialBranding = { ...args, localeBranding, } debug('Adding credential branding', credentialBranding) return this.store.addCredentialBranding(credentialBranding) } /** {@inheritDoc IIssuanceBranding.ibGetCredentialBranding} */ private async ibGetCredentialBranding(args?: IGetCredentialBrandingArgs): Promise<Array<ICredentialBranding>> { debug('Getting credential branding', args) return this.store.getCredentialBranding(args) } /** {@inheritDoc IIssuanceBranding.ibUpdateCredentialBranding} */ private async ibUpdateCredentialBranding(args: IUpdateCredentialBrandingArgs, context: IRequiredContext): Promise<ICredentialBranding> { debug('Updating credential branding', args) return this.store.updateCredentialBranding(args) } /** {@inheritDoc IIssuanceBranding.ibRemoveCredentialBranding} */ private async ibRemoveCredentialBranding(args: IRemoveCredentialBrandingArgs, context: IRequiredContext): Promise<IDeletionResult> { debug('Removing credential branding', args) return this.store .removeCredentialBranding(args) .then(() => ({ result: true })) .catch((error: Error) => ({ result: false, error: error.message })) } /** {@inheritDoc IIssuanceBranding.ibAddCredentialLocaleBranding} */ private async ibAddCredentialLocaleBranding(args: IAddCredentialLocaleBrandingArgs, context: IRequiredContext): Promise<ICredentialBranding> { const localeBranding: Array<IBasicCredentialLocaleBranding> = await Promise.all( args.localeBranding.map( (localeBranding: IBasicCredentialLocaleBranding): Promise<IBasicCredentialLocaleBranding> => this.setAdditionalImageAttributes(localeBranding), ), ) const addCredentialLocaleBrandingArgs: IAddCredentialLocaleBrandingArgs = { ...args, localeBranding, } debug('Adding credential locale branding', addCredentialLocaleBrandingArgs) return this.store.addCredentialLocaleBranding(addCredentialLocaleBrandingArgs) } /** {@inheritDoc IIssuanceBranding.ibGetCredentialLocaleBranding} */ private async ibGetCredentialLocaleBranding(args?: IGetCredentialLocaleBrandingArgs): Promise<Array<ICredentialLocaleBranding>> { debug('Getting credential locale branding', args) return this.store.getCredentialLocaleBranding(args) } /** {@inheritDoc IIssuanceBranding.ibRemoveCredentialLocaleBranding} */ private async ibRemoveCredentialLocaleBranding(args: IRemoveCredentialLocaleBrandingArgs, context: IRequiredContext): Promise<IDeletionResult> { debug('Removing credential locale branding', args) return this.store .removeCredentialLocaleBranding(args) .then(() => ({ result: true })) .catch((error: Error) => ({ result: false, error: error.message })) } /** {@inheritDoc IIssuanceBranding.ibUpdateCredentialLocaleBranding} */ private async ibUpdateCredentialLocaleBranding( args: IUpdateCredentialLocaleBrandingArgs, context: IRequiredContext, ): Promise<ICredentialLocaleBranding> { const localeBranding: Omit<ILocaleBranding, 'createdAt' | 'lastUpdatedAt'> = (await this.setAdditionalImageAttributes( args.localeBranding, )) as ICredentialLocaleBranding const updateCredentialLocaleBrandingArgs: IUpdateCredentialLocaleBrandingArgs = { ...args, localeBranding, } debug('Updating credential locale branding', updateCredentialLocaleBrandingArgs) return this.store.updateCredentialLocaleBranding(updateCredentialLocaleBrandingArgs) } /** {@inheritDoc IIssuanceBranding.ibCredentialLocaleBrandingFrom} */ private async ibCredentialLocaleBrandingFrom( args: ICredentialBrandingFromArgs, context: IRequiredContext, ): Promise<IBasicCredentialLocaleBranding> { debug('get credential locale branding from', args) return this.setAdditionalImageAttributes(args.localeBranding) } /** {@inheritDoc IIssuanceBranding.ibAddIssuerBranding} */ private async ibAddIssuerBranding(args: IAddIssuerBrandingArgs, context: IRequiredContext): Promise<IIssuerBranding> { const localeBranding: Array<IBasicIssuerLocaleBranding> = await Promise.all( args.localeBranding.map( (localeBranding: IBasicIssuerLocaleBranding): Promise<IBasicIssuerLocaleBranding> => this.setAdditionalImageAttributes(localeBranding), ), ) const issuerBranding: IBasicIssuerBranding = { ...args, localeBranding, } debug('Adding issuer branding', issuerBranding) return this.store.addIssuerBranding(issuerBranding) } /** {@inheritDoc IIssuanceBranding.ibGetIssuerBranding} */ private async ibGetIssuerBranding(args?: IGetIssuerBrandingArgs): Promise<Array<IIssuerBranding>> { debug('Getting issuer branding', args) return this.store.getIssuerBranding(args) } /** {@inheritDoc IIssuanceBranding.ibUpdateIssuerBranding} */ private async ibUpdateIssuerBranding(args: IUpdateIssuerBrandingArgs, context: IRequiredContext): Promise<IIssuerBranding> { debug('Updating issuer branding', args) return this.store.updateIssuerBranding(args) } /** {@inheritDoc IIssuanceBranding.inRemoveIssuerBranding} */ private async inRemoveIssuerBranding(args: IRemoveIssuerBrandingArgs, context: IRequiredContext): Promise<IDeletionResult> { debug('Removing issuer branding', args) return this.store .removeIssuerBranding(args) .then(() => ({ result: true })) .catch((error: Error) => ({ result: false, error: error.message })) } /** {@inheritDoc IIssuanceBranding.ibAddIssuerLocaleBranding} */ private async ibAddIssuerLocaleBranding(args: IAddIssuerLocaleBrandingArgs, context: IRequiredContext): Promise<IIssuerBranding> { const localeBranding: Array<IBasicIssuerLocaleBranding> = await Promise.all( args.localeBranding.map( (localeBranding: IBasicIssuerLocaleBranding): Promise<IBasicIssuerLocaleBranding> => this.setAdditionalImageAttributes(localeBranding), ), ) const addIssuerLocaleBrandingArgs: IAddIssuerLocaleBrandingArgs = { ...args, localeBranding, } debug('Adding issuer locale branding', addIssuerLocaleBrandingArgs) return this.store.addIssuerLocaleBranding(addIssuerLocaleBrandingArgs) } /** {@inheritDoc IIssuanceBranding.ibAGetIssuerLocaleBranding} */ private async ibAGetIssuerLocaleBranding(args?: IGetIssuerLocaleBrandingArgs): Promise<Array<IIssuerLocaleBranding>> { debug('Getting issuer locale branding', args) return this.store.getIssuerLocaleBranding(args) } /** {@inheritDoc IIssuanceBranding.ibRemoveIssuerLocaleBranding} */ private async ibRemoveIssuerLocaleBranding(args: IRemoveIssuerLocaleBrandingArgs, context: IRequiredContext): Promise<IDeletionResult> { debug('Removing issuer locale branding', args) return this.store .removeIssuerLocaleBranding(args) .then(() => ({ result: true })) .catch((error: Error) => ({ result: false, error: error.message })) } /** {@inheritDoc IIssuanceBranding.ibUpdateIssuerLocaleBranding} */ private async ibUpdateIssuerLocaleBranding(args: IUpdateIssuerLocaleBrandingArgs, context: IRequiredContext): Promise<IIssuerLocaleBranding> { const localeBranding: Omit<ILocaleBranding, 'createdAt' | 'lastUpdatedAt'> = (await this.setAdditionalImageAttributes( args.localeBranding, )) as IIssuerLocaleBranding const updateIssuerLocaleBrandingArgs: IUpdateIssuerLocaleBrandingArgs = { ...args, localeBranding, } debug('Updating issuer locale branding', updateIssuerLocaleBrandingArgs) return this.store.updateIssuerLocaleBranding(updateIssuerLocaleBrandingArgs) } /** {@inheritDoc IIssuanceBranding.ibIssuerLocaleBrandingFrom} */ private async ibIssuerLocaleBrandingFrom(args: IIssuerBrandingFromArgs, context: IRequiredContext): Promise<IBasicIssuerLocaleBranding> { debug('get issuer locale branding from', args) return this.setAdditionalImageAttributes(args.localeBranding) } // todo: We really should add a cache for urls. We now fetch the same images multiple times in case the logo/background image is the same for multiple locales. private async setAdditionalImageAttributes( localeBranding: IBasicIssuerLocaleBranding | IBasicCredentialLocaleBranding, ): Promise<IBasicIssuerLocaleBranding | IBasicCredentialLocaleBranding> { return { ...localeBranding, ...(localeBranding.logo && { logo: { ...localeBranding.logo, ...(localeBranding.logo.uri ? { ...(await this.getAdditionalImageAttributes(localeBranding.logo)), } : EMPTY_IMAGE_ATTRIBUTES), }, }), ...(localeBranding.background && { background: { ...localeBranding.background, ...(localeBranding.background.image && { image: { ...localeBranding.background.image, ...(localeBranding.background.image.uri ? { ...(await this.getAdditionalImageAttributes(localeBranding.background.image)), } : EMPTY_IMAGE_ATTRIBUTES), }, }), }, }), } } private async getAdditionalImageAttributes(image: IBasicImageAttributes): Promise<IAdditionalImageAttributes | IBasicImageAttributes> { if (!image.uri) { debug(`No image URI present, returning empty attributes`) return EMPTY_IMAGE_ATTRIBUTES } const data_uri_regex: RegExp = /^data:image\/[^;]+;base64,/ if (data_uri_regex.test(image.uri)) { debug('Setting additional image properties for uri', image.uri) const base64Content: string = await this.extractBase64FromDataURI(image.uri) const dimensions: IImageDimensions = image.dimensions ?? (await getImageDimensions(base64Content)) const mediaType: string = image.mediaType ?? (await this.getDataTypeFromDataURI(image.uri)) return { mediaType, dimensions, } } debug('Setting additional image properties for url', image.uri) const resource: IImageResource | undefined = !image.dataUri ? await downloadImage(image.uri) : undefined const dimensions: IImageDimensions = image.dimensions ?? (await getImageDimensions(resource?.base64Content ?? (await this.extractBase64FromDataURI(image.dataUri!)))) const mediaType: string | undefined = image.mediaType ?? resource?.contentType ?? (resource?.base64Content ? await getImageMediaType(resource?.base64Content!) : await this.getDataTypeFromDataURI(image.uri)) return { mediaType, dataUri: image.dataUri ?? `data:${mediaType};base64,${resource!.base64Content}`, dimensions, } } private async extractBase64FromDataURI(uri: string): Promise<string> { const data_uri_base64_regex: RegExp = /^data:[^;]+;base64,([\w+/=-]+)$/i const matches: RegExpMatchArray | null = uri.match(data_uri_base64_regex) if (!matches || matches.length <= 1) { return Promise.reject(Error('invalid base64 uri')) } return matches[1] } private async getDataTypeFromDataURI(uri: string): Promise<string> { const data_uri_data_type_regex: RegExp = /^data:([^;]+);base64,([\w+/=-]+)$/i const matches: RegExpMatchArray | null = uri.match(data_uri_data_type_regex) if (!matches || matches.length <= 1) { return Promise.reject(Error('invalid base64 uri')) } return matches[1] } }