From 6e07a0a9a37b71c54bf30905f8b1f9b90e41a0c2 Mon Sep 17 00:00:00 2001 From: "A.G.J. Cate" Date: Tue, 19 Nov 2024 12:35:48 +0100 Subject: [PATCH] feat: added issuer credential subject branding support --- .../src/details/CredentialDetails.ts | 121 ++++++++---------- .../credential-branding/src/types/index.ts | 54 +++++++- .../buttons/SecondaryButton/index.tsx | 2 +- 3 files changed, 110 insertions(+), 67 deletions(-) diff --git a/packages/credential-branding/src/details/CredentialDetails.ts b/packages/credential-branding/src/details/CredentialDetails.ts index 82472fc..a5c01da 100644 --- a/packages/credential-branding/src/details/CredentialDetails.ts +++ b/packages/credential-branding/src/details/CredentialDetails.ts @@ -1,13 +1,22 @@ import {v4 as uuidv4} from 'uuid' import {VerifiableCredential} from '@veramo/core' import {asArray, computeEntryHash} from '@veramo/utils' -import {CredentialRole, IBasicCredentialLocaleBranding, IBasicIssuerLocaleBranding, Identity, Party} from '@sphereon/ssi-sdk.data-store' +import {IBasicCredentialLocaleBranding, IBasicIssuerLocaleBranding, Identity, Party} from '@sphereon/ssi-sdk.data-store' import {EPOCH_MILLISECONDS, IS_IMAGE_URI_REGEX, Localization} from '@sphereon/ui-components.core' import {downloadImage, getImageDimensions, IImageDimensions} from '@sphereon/ssi-sdk.core' -import {CredentialDetailsRow, CredentialSummary, ISelectAppLocaleBrandingArgs} from '../types' import {IImagePreloader} from '../services' import {getCredentialStatus, getIssuerLogo, isImageAddress} from '../utils' -import {ICredential} from '@sphereon/ssi-types' +import { + CredentialDetailsRow, + CredentialSummary, + GetCredentialDisplayNameArgs, + GetCredentialIssuerNameAndAliasArgs, + GetTermsOfUseArgs, + ISelectAppLocaleBrandingArgs, + ToCredentialDetailsRowArgs, + ToCredentialSummaryArgs, + ToNonPersistedCredentialSummaryArgs +} from '../types' function findCorrelationIdName(correlationId: string, parties: Party[], activeUser?: Party): string { let allParties = parties @@ -37,15 +46,9 @@ const getImageSize = async (image: string): Promise - subject?: Party - issuer?: Party -}): Promise => { +export const toCredentialDetailsRow = async (args: ToCredentialDetailsRowArgs): Promise => { + const {object, subject, issuer, branding, parentKey = '' } = args + let rows: CredentialDetailsRow[] = [] if (!object) { return rows @@ -72,13 +75,13 @@ export const toCredentialDetailsRow = async ({ // label: key, // value: undefined, // }); - rows = rows.concat(await toCredentialDetailsRow({object: value, subject, issuer})) + rows = rows.concat(await toCredentialDetailsRow({object: value, subject, issuer, branding, parentKey: parentKey ? `${parentKey}.${key}` : key})) } else { if (key === '0' || value === undefined || value === null) { continue } - let label: string = key + let label: string = branding?.find((claim) => claim.key === (parentKey ? `${parentKey}.${key}` : key))?.name ?? key if (key === 'id' && typeof value === 'string' && value.startsWith('did:')) { label = 'subject' } @@ -111,19 +114,15 @@ export const toCredentialDetailsRow = async ({ * @param issuer Optional contact for issuer name * @param subject Optional contact for subject name */ -export const toNonPersistedCredentialSummary = ({ - verifiableCredential, - credentialRole, - branding, - issuer, - subject, -}: { - verifiableCredential: ICredential | VerifiableCredential - credentialRole: CredentialRole - branding?: Array - issuer?: Party - subject?: Party -}): Promise => { +export const toNonPersistedCredentialSummary = (args: ToNonPersistedCredentialSummaryArgs): Promise => { + const { + verifiableCredential, + credentialRole, + branding, + issuer, + subject, + } = args + return toCredentialSummary({ verifiableCredential: verifiableCredential as VerifiableCredential, hash: computeEntryHash(verifiableCredential as VerifiableCredential), @@ -144,13 +143,12 @@ export const getDate = (...dates: (number | string | undefined)[]): number | und return Math.round(new Date(date + '').valueOf() / EPOCH_MILLISECONDS) } -export const getCredentialDisplayName = ({ - verifiableCredential, - localeBranding, -}: { - verifiableCredential: VerifiableCredential - localeBranding?: IBasicCredentialLocaleBranding -}): string => { +export const getCredentialDisplayName = (args: GetCredentialDisplayNameArgs): string => { + const { + verifiableCredential, + localeBranding, + } = args + let title: string | undefined = localeBranding?.alias ?? verifiableCredential.name ?? (!verifiableCredential.type ? 'unknown' : undefined) if (verifiableCredential.type) { @@ -167,13 +165,12 @@ export const getCredentialDisplayName = ({ return title } -export const getCredentialIssuerNameAndAlias = ({ - verifiableCredential, - issuer, -}: { - verifiableCredential: VerifiableCredential - issuer?: Party -}): {issuerAlias: string; issuerName: string} => { +export const getCredentialIssuerNameAndAlias = (args: GetCredentialIssuerNameAndAliasArgs): {issuerAlias: string; issuerName: string} => { + const { + verifiableCredential, + issuer, + } = args + const issuerName: string = typeof verifiableCredential.issuer === 'string' ? verifiableCredential.issuer : verifiableCredential.issuer?.id let issuerAlias: string | undefined = undefined @@ -191,14 +188,13 @@ export const getCredentialIssuerNameAndAlias = ({ return {issuerName, issuerAlias} } -const getTermsOfUse = ({ - verifiableCredential, -}: { - verifiableCredential: VerifiableCredential -}): undefined | Array & {type?: string}> => { +const getTermsOfUse = (args: GetTermsOfUseArgs): undefined | Array & {type?: string}> => { + const { verifiableCredential } = args + if (!verifiableCredential.termsOfUse) { return } + const termsOfUse = asArray(verifiableCredential.termsOfUse) return termsOfUse.map((tou: any) => { const {type, id, ...rest} = tou @@ -208,6 +204,7 @@ const getTermsOfUse = ({ } }) } + /** * Map persisted (Unique) VCs to the summaries we display * @param hash The hash of the unique verifiable credential @@ -217,30 +214,26 @@ const getTermsOfUse = ({ * @param issuer Optional contact for issuer name * @param subject Optional contact for subject name */ -export const toCredentialSummary = async ({ - verifiableCredential, - hash, - credentialRole, - branding, - issuer, - subject, -}: { - verifiableCredential: VerifiableCredential - hash: string - credentialRole: CredentialRole - branding?: Array - issuer?: Party - subject?: Party -}): Promise => { +export const toCredentialSummary = async (args: ToCredentialSummaryArgs): Promise => { + const { + verifiableCredential, + hash, + credentialRole, + branding, + issuer, + subject + } = args + const expirationDate = getDate(verifiableCredential.expirationDate, verifiableCredential.validTo, verifiableCredential.exp) ?? 0 const issueDate = getDate(verifiableCredential.issuanceDate, verifiableCredential.validFrom, verifiableCredential.nbf, verifiableCredential.iat)! - const localeBranding = await selectAppLocaleBranding({localeBranding: branding}) + const localeBranding: IBasicCredentialLocaleBranding | undefined = await selectAppLocaleBranding({localeBranding: branding}) const credentialStatus = getCredentialStatus(verifiableCredential) const title = getCredentialDisplayName({verifiableCredential, localeBranding}) const properties = await toCredentialDetailsRow({ object: {...verifiableCredential.vc?.credentialSubject, ...verifiableCredential.credentialSubject}, subject, issuer, + branding: localeBranding?.claims }) const logo = getIssuerLogo(verifiableCredential, localeBranding) const url = verifiableCredential.issuer && typeof verifiableCredential.issuer !== 'string' ? verifiableCredential.issuer.url : undefined @@ -266,9 +259,7 @@ export const toCredentialSummary = async ({ } } -export const selectAppLocaleBranding = async ( - args: ISelectAppLocaleBrandingArgs, -): Promise => { +export const selectAppLocaleBranding = async (args: ISelectAppLocaleBrandingArgs): Promise => { // We need to retrieve the locale of the app and select a matching branding or fallback on a branding without a locale // We search for a first match that starts with the app locale const appLocale: string = Localization.getLocale() diff --git a/packages/credential-branding/src/types/index.ts b/packages/credential-branding/src/types/index.ts index 3324e3a..5abe37d 100644 --- a/packages/credential-branding/src/types/index.ts +++ b/packages/credential-branding/src/types/index.ts @@ -1,5 +1,18 @@ import {CredentialStatus, ImageSize, IssuerStatus} from '@sphereon/ui-components.core' -import {CredentialRole, IBasicCredentialLocaleBranding, IBasicIssuerLocaleBranding} from '@sphereon/ssi-sdk.data-store' +import { + CredentialRole, IBasicCredentialClaim, + IBasicCredentialLocaleBranding, + IBasicIssuerLocaleBranding, + Party +} from '@sphereon/ssi-sdk.data-store' +import { + getCredentialDisplayName, + getCredentialIssuerNameAndAlias, + toCredentialSummary, + toNonPersistedCredentialSummary +} from '../details'; +import {ICredential} from '@sphereon/ssi-types'; +import {VerifiableCredential} from '@veramo/core'; export type CredentialSummary = { hash: string @@ -37,3 +50,42 @@ export type LabelStatus = CredentialStatus | IssuerStatus export interface ISelectAppLocaleBrandingArgs { localeBranding?: Array } + +export type ToCredentialDetailsRowArgs = { + object: Record + parentKey?: string + subject?: Party + issuer?: Party + branding?: Array +} + +export type ToNonPersistedCredentialSummaryArgs = { + verifiableCredential: ICredential | VerifiableCredential + credentialRole: CredentialRole + branding?: Array + issuer?: Party + subject?: Party +} + +export type GetCredentialDisplayNameArgs = { + verifiableCredential: VerifiableCredential + localeBranding?: IBasicCredentialLocaleBranding +} + +export type GetCredentialIssuerNameAndAliasArgs = { + verifiableCredential: VerifiableCredential + issuer?: Party +} + +export type GetTermsOfUseArgs = { + verifiableCredential: VerifiableCredential +} + +export type ToCredentialSummaryArgs = { + verifiableCredential: VerifiableCredential + hash: string + credentialRole: CredentialRole + branding?: Array + issuer?: Party + subject?: Party +} diff --git a/packages/ssi-react-native/src/components/buttons/SecondaryButton/index.tsx b/packages/ssi-react-native/src/components/buttons/SecondaryButton/index.tsx index 4ecd58c..0eff99f 100644 --- a/packages/ssi-react-native/src/components/buttons/SecondaryButton/index.tsx +++ b/packages/ssi-react-native/src/components/buttons/SecondaryButton/index.tsx @@ -1,5 +1,5 @@ import React, {FC, ReactElement} from 'react' -import {ColorValue, PressableProps, TouchableOpacityProps, ViewStyle} from 'react-native' +import {ColorValue, TouchableOpacityProps, ViewStyle} from 'react-native' import {fontColors, gradientsColors, OpacityStyleEnum} from '@sphereon/ui-components.core' import { SSITouchableOpacityButtonFlexRowStyled as Button,