From b836ca1c21307174a3f706234981d98c5dbe0e52 Mon Sep 17 00:00:00 2001 From: Niels Klomp Date: Wed, 24 Jul 2024 02:20:42 +0200 Subject: [PATCH] fix: Doesn't make sense to always download issuer images, even if we already have it stored. Other stability improvements for image handling --- .../src/agent/IssuanceBranding.ts | 35 +++++++++---------- .../oid4vci-holder/src/agent/OID4VCIHolder.ts | 35 ++++++++++++------- .../src/agent/OID4VCIHolderService.ts | 4 +-- packages/oid4vci-issuer-rest-api/package.json | 1 + packages/ssi-sdk-core/src/utils/image.ts | 29 ++++++++++----- 5 files changed, 62 insertions(+), 42 deletions(-) diff --git a/packages/issuance-branding/src/agent/IssuanceBranding.ts b/packages/issuance-branding/src/agent/IssuanceBranding.ts index 4d666ad1c..6a79796a4 100644 --- a/packages/issuance-branding/src/agent/IssuanceBranding.ts +++ b/packages/issuance-branding/src/agent/IssuanceBranding.ts @@ -62,6 +62,12 @@ export const issuanceBrandingMethods: Array = [ 'ibIssuerLocaleBrandingFrom', ] +const EMPTY_IMAGE_ATTRIBUTES = { + mediaType: undefined, + dataUri: undefined, + dimensions: undefined, +} + /** * {@inheritDoc IIssuanceBranding} */ @@ -291,11 +297,7 @@ export class IssuanceBranding implements IAgentPlugin { ? { ...(await this.getAdditionalImageAttributes(localeBranding.logo)), } - : { - mediaType: undefined, - dataUri: undefined, - dimensions: undefined, - }), + : EMPTY_IMAGE_ATTRIBUTES), }, }), ...(localeBranding.background && { @@ -308,11 +310,7 @@ export class IssuanceBranding implements IAgentPlugin { ? { ...(await this.getAdditionalImageAttributes(localeBranding.background.image)), } - : { - mediaType: undefined, - dataUri: undefined, - dimensions: undefined, - }), + : EMPTY_IMAGE_ATTRIBUTES), }, }), }, @@ -320,17 +318,18 @@ export class IssuanceBranding implements IAgentPlugin { } } - private async getAdditionalImageAttributes(image: IBasicImageAttributes): Promise { + private async getAdditionalImageAttributes(image: IBasicImageAttributes): Promise { if (!image.uri) { - return Promise.reject(Error('Image has no 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)) + const dimensions: IImageDimensions = image.dimensions ?? (await getImageDimensions(base64Content)) + const mediaType: string = image.mediaType ?? (await this.getDataTypeFromDataURI(image.uri)) return { mediaType, @@ -341,15 +340,15 @@ export class IssuanceBranding implements IAgentPlugin { 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!)))) + image.dimensions ?? (await getImageDimensions(resource?.base64Content ?? (await this.extractBase64FromDataURI(image.dataUri!)))) const mediaType: string | undefined = - image.mediaType || - resource?.contentType || + 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}`, + dataUri: image.dataUri ?? `data:${mediaType};base64,${resource!.base64Content}`, dimensions, } } diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts index 4a880e4bc..52f0ee6d9 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts @@ -77,7 +77,7 @@ import { getCredentialConfigsSupportedMerged, getIdentifierOpts, getIssuanceOpts, - getIssuerBranding, + getBasicIssuerLocaleBranding, mapCredentialToAccept, selectCredentialLocaleBranding, signatureAlgorithmFromKey, @@ -410,9 +410,9 @@ export class OID4VCIHolder implements IAgentPlugin { // const client = await OpenID4VCIClient.fromState({ state: openID4VCIClientState! }) // TODO see if we need the check openID4VCIClientState defined /*const credentialsSupported = await getCredentialConfigsSupportedBySingleTypeOrId({ - client, - vcFormatPreferences: this.vcFormatPreferences, - })*/ + client, + vcFormatPreferences: this.vcFormatPreferences, + })*/ logger.info(`Credentials supported ${Object.keys(credentialsSupported).join(', ')}`) const credentialSelection: Array = await Promise.all( @@ -667,19 +667,28 @@ export class OID4VCIHolder implements IAgentPlugin { private async oid4vciHolderAddIssuerBranding(args: AddIssuerBrandingArgs, context: RequiredContext): Promise { const { serverMetadata, contact } = args - if (serverMetadata?.credentialIssuerMetadata?.display && contact) { - const issuerBrandings: IBasicIssuerLocaleBranding[] = await getIssuerBranding({ - display: serverMetadata.credentialIssuerMetadata.display, - context, - }) + if (!contact) { + return logger.warning('Missing contact in context, so cannot get issuer branding') + } + + if (serverMetadata?.credentialIssuerMetadata?.display) { const issuerCorrelationId: string = contact.identities .filter((identity) => identity.roles.includes(CredentialRole.ISSUER)) .map((identity) => identity.identifier.correlationId)[0] ?? undefined - if (issuerBrandings && issuerBrandings.length) { - const brandings: IIssuerBranding[] = await context.agent.ibGetIssuerBranding({ filter: [{ issuerCorrelationId }] }) - if (!brandings || !brandings.length) { - await context.agent.ibAddIssuerBranding({ localeBranding: issuerBrandings, issuerCorrelationId }) + + const brandings: IIssuerBranding[] = await context.agent.ibGetIssuerBranding({ filter: [{ issuerCorrelationId }] }) + // todo: Probably wise to look at last updated at and update in case it has been a while + if (!brandings || brandings.length === 0) { + const basicIssuerLocaleBrandings: IBasicIssuerLocaleBranding[] = await getBasicIssuerLocaleBranding({ + display: serverMetadata.credentialIssuerMetadata.display, + context, + }) + if (basicIssuerLocaleBrandings && basicIssuerLocaleBrandings.length > 0) { + await context.agent.ibAddIssuerBranding({ + localeBranding: basicIssuerLocaleBrandings, + issuerCorrelationId, + }) } } } diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts index eaf8a1f54..9ec428476 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts @@ -88,10 +88,10 @@ export const getCredentialBranding = async (args: GetCredentialBrandingArgs): Pr return credentialBranding } -export const getIssuerBranding = async (args: GetIssuerBrandingArgs): Promise> => { +export const getBasicIssuerLocaleBranding = async (args: GetIssuerBrandingArgs): Promise> => { const { display, context } = args return await Promise.all( - (display ?? []).map(async (displayItem: MetadataDisplay): Promise => { + display.map(async (displayItem: MetadataDisplay): Promise => { const branding = await issuerLocaleBrandingFrom(displayItem) return context.agent.ibIssuerLocaleBrandingFrom({ localeBranding: branding }) }), diff --git a/packages/oid4vci-issuer-rest-api/package.json b/packages/oid4vci-issuer-rest-api/package.json index da3ce73c4..f10285847 100644 --- a/packages/oid4vci-issuer-rest-api/package.json +++ b/packages/oid4vci-issuer-rest-api/package.json @@ -21,6 +21,7 @@ "@sphereon/ssi-types": "workspace:*", "@veramo/core": "4.2.0", "@veramo/credential-w3c": "4.2.0", + "awesome-qr": "^2.1.5-rc.0", "body-parser": "^1.20.2", "cookie-parser": "^1.4.6", "cors": "^2.8.5", diff --git a/packages/ssi-sdk-core/src/utils/image.ts b/packages/ssi-sdk-core/src/utils/image.ts index ee9b5a0b4..0065224dc 100644 --- a/packages/ssi-sdk-core/src/utils/image.ts +++ b/packages/ssi-sdk-core/src/utils/image.ts @@ -1,8 +1,10 @@ +import { Loggers } from '@sphereon/ssi-types' import fetch from 'cross-fetch' import { imageSize } from 'image-size' import { IImageDimensions, IImageResource } from '../types' import * as u8a from 'uint8arrays' +const logger = Loggers.DEFAULT.get('sphereon:core') type SizeCalculationResult = { width?: number height?: number @@ -72,17 +74,26 @@ export const getImageDimensions = async (value: string | Uint8Array): Promise => { - const response: Response = await fetch(url) - if (!response.ok) { - return Promise.reject(Error(`Failed to download resource. Status: ${response.status} ${response.statusText}`)) +export const downloadImage = async (url: string): Promise => { + if (!url) { + logger.warning(`Could not download image when nu url is provided`) + return } + try { + const response: Response = await fetch(url) + if (!response.ok) { + logger.error(`Could not download image ${url}. Status: ${response.status} ${response.statusText}`) + } - const contentType: string | null = response.headers.get('Content-Type') - const base64Content: string = Buffer.from(await response.arrayBuffer()).toString('base64') + const contentType: string | null = response.headers.get('Content-Type') + const base64Content: string = Buffer.from(await response.arrayBuffer()).toString('base64') - return { - base64Content, - contentType: contentType || undefined, + return { + base64Content, + contentType: contentType || undefined, + } + } catch (e) { + logger.error(`Could not download image ${url}`, e) + return undefined } }