Skip to content

Commit

Permalink
fix: Doesn't make sense to always download issuer images, even if we …
Browse files Browse the repository at this point in the history
…already have it stored. Other stability improvements for image handling
  • Loading branch information
nklomp committed Jul 24, 2024
1 parent 2789fb0 commit b836ca1
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 42 deletions.
35 changes: 17 additions & 18 deletions packages/issuance-branding/src/agent/IssuanceBranding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ export const issuanceBrandingMethods: Array<string> = [
'ibIssuerLocaleBrandingFrom',
]

const EMPTY_IMAGE_ATTRIBUTES = {
mediaType: undefined,
dataUri: undefined,
dimensions: undefined,
}

/**
* {@inheritDoc IIssuanceBranding}
*/
Expand Down Expand Up @@ -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 && {
Expand All @@ -308,29 +310,26 @@ export class IssuanceBranding implements IAgentPlugin {
? {
...(await this.getAdditionalImageAttributes(localeBranding.background.image)),
}
: {
mediaType: undefined,
dataUri: undefined,
dimensions: undefined,
}),
: EMPTY_IMAGE_ATTRIBUTES),
},
}),
},
}),
}
}

private async getAdditionalImageAttributes(image: IBasicImageAttributes): Promise<IAdditionalImageAttributes> {
private async getAdditionalImageAttributes(image: IBasicImageAttributes): Promise<IAdditionalImageAttributes | IBasicImageAttributes> {
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,
Expand All @@ -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,
}
}
Expand Down
35 changes: 22 additions & 13 deletions packages/oid4vci-holder/src/agent/OID4VCIHolder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import {
getCredentialConfigsSupportedMerged,
getIdentifierOpts,
getIssuanceOpts,
getIssuerBranding,
getBasicIssuerLocaleBranding,
mapCredentialToAccept,
selectCredentialLocaleBranding,
signatureAlgorithmFromKey,
Expand Down Expand Up @@ -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<CredentialToSelectFromResult> = await Promise.all(
Expand Down Expand Up @@ -667,19 +667,28 @@ export class OID4VCIHolder implements IAgentPlugin {

private async oid4vciHolderAddIssuerBranding(args: AddIssuerBrandingArgs, context: RequiredContext): Promise<void> {
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,
})
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ export const getCredentialBranding = async (args: GetCredentialBrandingArgs): Pr
return credentialBranding
}

export const getIssuerBranding = async (args: GetIssuerBrandingArgs): Promise<Array<IBasicIssuerLocaleBranding>> => {
export const getBasicIssuerLocaleBranding = async (args: GetIssuerBrandingArgs): Promise<Array<IBasicIssuerLocaleBranding>> => {
const { display, context } = args
return await Promise.all(
(display ?? []).map(async (displayItem: MetadataDisplay): Promise<IBasicIssuerLocaleBranding> => {
display.map(async (displayItem: MetadataDisplay): Promise<IBasicIssuerLocaleBranding> => {
const branding = await issuerLocaleBrandingFrom(displayItem)
return context.agent.ibIssuerLocaleBrandingFrom({ localeBranding: branding })
}),
Expand Down
1 change: 1 addition & 0 deletions packages/oid4vci-issuer-rest-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
29 changes: 20 additions & 9 deletions packages/ssi-sdk-core/src/utils/image.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -72,17 +74,26 @@ export const getImageDimensions = async (value: string | Uint8Array): Promise<II
return { width: dimensions.width, height: dimensions.height }
}

export const downloadImage = async (url: string): Promise<IImageResource> => {
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<IImageResource | undefined> => {
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
}
}

0 comments on commit b836ca1

Please sign in to comment.