From a5392f7d90fe3b0ffca2124f8cc892c1b0389324 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 26 Apr 2024 11:59:22 +0200 Subject: [PATCH 1/3] fix: credentials shown multiple times Signed-off-by: Timo Glastra --- apps/expo/package.json | 2 +- packages/agent/src/display.ts | 24 +---------- .../src/hooks/useCredentialsForDisplay.ts | 23 +++------- .../src/hooks/useDidCommCredentialActions.ts | 42 ++++++++++++++++++- packages/agent/src/invitation/handler.ts | 10 +++-- packages/agent/src/openid4vc/metadata.ts | 12 ++---- 6 files changed, 59 insertions(+), 54 deletions(-) diff --git a/apps/expo/package.json b/apps/expo/package.json index e62b72e8..a2b75faa 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -1,6 +1,6 @@ { "name": "expo-app", - "version": "1.3.6", + "version": "1.3.7", "main": "expo-router/entry", "private": true, "scripts": { diff --git a/packages/agent/src/display.ts b/packages/agent/src/display.ts index 96be1574..b8d28080 100644 --- a/packages/agent/src/display.ts +++ b/packages/agent/src/display.ts @@ -1,13 +1,12 @@ import type { CredentialForDisplayId } from './hooks' import type { OpenId4VcCredentialMetadata } from './openid4vc/metadata' import type { W3cCredentialJson, W3cIssuerJson } from './types' -import type { CredentialExchangeRecord, W3cCredentialRecord } from '@credo-ts/core' +import type { W3cCredentialRecord } from '@credo-ts/core' import { Hasher, SdJwtVcRecord, ClaimFormat, JsonTransformer } from '@credo-ts/core' import { sanitizeString, getHostNameFromUrl } from '@internal/utils' import { decodeSdJwtSync, getClaimsSync } from '@sd-jwt/decode' -import { getDidCommCredentialExchangeDisplayMetadata } from './didcomm/metadata' import { getOpenId4VcCredentialMetadata } from './openid4vc/metadata' type JffW3cCredentialJson = W3cCredentialJson & { @@ -255,27 +254,6 @@ function getSdJwtCredentialDisplay( } } -export function getCredentialExchangeForDisplay( - credentialExchangeRecord: CredentialExchangeRecord -) { - const didCommDisplayMetadata = - getDidCommCredentialExchangeDisplayMetadata(credentialExchangeRecord) - - return { - id: `credential-exchange-${credentialExchangeRecord.id}` satisfies CredentialForDisplayId, - createdAt: credentialExchangeRecord.createdAt, - display: { - issuer: { - name: didCommDisplayMetadata?.issuerName ?? 'Unknown', - }, - name: didCommDisplayMetadata?.credentialName ?? 'Credential', - } as CredentialDisplay, - attributes: Object.fromEntries( - credentialExchangeRecord.credentialAttributes?.map(({ name, value }) => [name, value]) ?? [] - ) satisfies Record, - } -} - interface CredentialMetadata { type: string issuer: string diff --git a/packages/agent/src/hooks/useCredentialsForDisplay.ts b/packages/agent/src/hooks/useCredentialsForDisplay.ts index cfcaf8f4..860b529b 100644 --- a/packages/agent/src/hooks/useCredentialsForDisplay.ts +++ b/packages/agent/src/hooks/useCredentialsForDisplay.ts @@ -1,8 +1,7 @@ -import { CredentialState } from '@credo-ts/core' -import { useCredentials as _useCredentials, useCredentialByState } from '@credo-ts/react-hooks' +import { useCredentials as _useCredentials } from '@credo-ts/react-hooks' import { useMemo } from 'react' -import { getCredentialForDisplay, getCredentialExchangeForDisplay } from '../display' +import { getCredentialForDisplay } from '../display' import { useSdJwtVcRecords, useW3cCredentialRecords } from '../providers' export const useCredentialsForDisplay = () => { @@ -10,28 +9,18 @@ export const useCredentialsForDisplay = () => { const { sdJwtVcRecords, isLoading: isLoadingSdJwt } = useSdJwtVcRecords() const { loading } = _useCredentials() - const credentialExchangeRecords = useCredentialByState([ - CredentialState.Done, - CredentialState.CredentialReceived, - ]) - const credentials = useMemo(() => { // Map into common structure that can be rendered const uniformW3cCredentialRecords = w3cCredentialRecords.map(getCredentialForDisplay) const uniformSdJwtVcRecords = sdJwtVcRecords.map(getCredentialForDisplay) - const uniformCredentialExchangeRecords = credentialExchangeRecords.map( - getCredentialExchangeForDisplay - ) // Sort by creation date - const sortedRecords = [ - ...uniformCredentialExchangeRecords, - ...uniformW3cCredentialRecords, - ...uniformSdJwtVcRecords, - ].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) + const sortedRecords = [...uniformW3cCredentialRecords, ...uniformSdJwtVcRecords].sort( + (a, b) => b.createdAt.getTime() - a.createdAt.getTime() + ) return sortedRecords - }, [w3cCredentialRecords, credentialExchangeRecords, sdJwtVcRecords]) + }, [w3cCredentialRecords, sdJwtVcRecords]) return { credentials, diff --git a/packages/agent/src/hooks/useDidCommCredentialActions.ts b/packages/agent/src/hooks/useDidCommCredentialActions.ts index b5380d52..1441b646 100644 --- a/packages/agent/src/hooks/useDidCommCredentialActions.ts +++ b/packages/agent/src/hooks/useDidCommCredentialActions.ts @@ -1,6 +1,6 @@ import type { CredentialStateChangedEvent } from '@credo-ts/core' -import { CredentialState, CredentialEventTypes } from '@credo-ts/core' +import { W3cCredentialRepository, CredentialState, CredentialEventTypes } from '@credo-ts/core' import { useCredentialById } from '@credo-ts/react-hooks' import { useMutation, useQuery } from '@tanstack/react-query' import { firstValueFrom } from 'rxjs' @@ -8,6 +8,7 @@ import { filter, first, timeout } from 'rxjs/operators' import { useAgent } from '../agent' import { getDidCommCredentialExchangeDisplayMetadata } from '../didcomm/metadata' +import { setOpenId4VcCredentialMetadata } from '../openid4vc/metadata' function useOfferAttributes(credentialExchangeId: string) { const { agent } = useAgent() @@ -64,7 +65,44 @@ export function useDidCommCredentialActions(credentialExchangeId: string) { const credentialDonePromise = firstValueFrom(credentialDone$) await agent.credentials.acceptOffer({ credentialRecordId: credentialExchangeId }) - await credentialDonePromise + const doneEvent = await credentialDonePromise + const w3cCredentialRecordId = doneEvent.payload.credentialRecord.credentials.find( + (c) => c.credentialRecordType === 'w3c' + )?.credentialRecordId + + // Update the w3c credential record metadata, based on the didcomm credential exchange display + // metadata + if (w3cCredentialRecordId) { + const didCommDisplayMetadata = getDidCommCredentialExchangeDisplayMetadata( + doneEvent.payload.credentialRecord + ) + // NOTE: we store the metadata also in openid4vc format, just because it's simple. In the future + // we may want to have our own display format we use for all credential types + const w3cRecord = await agent.w3cCredentials.getCredentialRecordById(w3cCredentialRecordId) + + // TODO: we must somehow link the w3c credential record to a DIDComm connection + // first in Paradym Wallet, but would alos be nice to do this within Credo + setOpenId4VcCredentialMetadata(w3cRecord, { + credential: { + display: [ + { + name: didCommDisplayMetadata?.credentialName ?? 'Credential', + }, + ], + }, + issuer: { + id: didCommDisplayMetadata?.issuerName ?? 'Unkown', + display: [ + { + name: didCommDisplayMetadata?.issuerName, + }, + ], + }, + }) + + const w3cCredentialRepository = agent.dependencyManager.resolve(W3cCredentialRepository) + await w3cCredentialRepository.update(agent.context, w3cRecord) + } }, }) diff --git a/packages/agent/src/invitation/handler.ts b/packages/agent/src/invitation/handler.ts index 74e3557c..dbb4b411 100644 --- a/packages/agent/src/invitation/handler.ts +++ b/packages/agent/src/invitation/handler.ts @@ -40,7 +40,10 @@ import { OpenId4VciCredentialFormatProfile } from '@credo-ts/openid4vc' import { getHostNameFromUrl } from '@internal/utils' import { filter, firstValueFrom, merge, first, timeout } from 'rxjs' -import { setOpenId4VcCredentialMetadata } from '../openid4vc/metadata' +import { + extractOpenId4VcCredentialMetadata, + setOpenId4VcCredentialMetadata, +} from '../openid4vc/metadata' export const receiveCredentialFromOpenId4VciOffer = async ({ agent, @@ -176,12 +179,13 @@ export const receiveCredentialFromOpenId4VciOffer = async ({ }) } - setOpenId4VcCredentialMetadata( - record, + const openId4VcMetadata = extractOpenId4VcCredentialMetadata( resolvedCredentialOffer.offeredCredentials[0] as OpenId4VciCredentialSupportedWithId, resolvedCredentialOffer.metadata ) + setOpenId4VcCredentialMetadata(record, openId4VcMetadata) + return record } diff --git a/packages/agent/src/openid4vc/metadata.ts b/packages/agent/src/openid4vc/metadata.ts index 8fc6d0ae..4f27d345 100644 --- a/packages/agent/src/openid4vc/metadata.ts +++ b/packages/agent/src/openid4vc/metadata.ts @@ -18,10 +18,10 @@ export interface OpenId4VcCredentialMetadata { const openId4VcCredentialMetadataKey = '_paradym/openId4VcCredentialMetadata' -function extractOpenId4VcCredentialMetadata( +export function extractOpenId4VcCredentialMetadata( credentialMetadata: OpenId4VciCredentialSupported, serverMetadata: EndpointMetadataResult -) { +): OpenId4VcCredentialMetadata { return { credential: { display: credentialMetadata.display, @@ -50,11 +50,7 @@ export function getOpenId4VcCredentialMetadata( */ export function setOpenId4VcCredentialMetadata( credentialRecord: W3cCredentialRecord | SdJwtVcRecord, - credentialMetadata: OpenId4VciCredentialSupported, - serverMetadata: EndpointMetadataResult + metadata: OpenId4VcCredentialMetadata ) { - credentialRecord.metadata.set( - openId4VcCredentialMetadataKey, - extractOpenId4VcCredentialMetadata(credentialMetadata, serverMetadata) - ) + credentialRecord.metadata.set(openId4VcCredentialMetadataKey, metadata) } From 61118feb6520c59a7c7c035a50b494789fed9ef4 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 26 Apr 2024 13:47:39 +0200 Subject: [PATCH 2/3] cleanup Signed-off-by: Timo Glastra --- packages/agent/src/didcomm/metadata.ts | 28 ++++++++++++++ .../src/hooks/useCredentialsForDisplay.ts | 4 +- .../src/hooks/useDidCommCredentialActions.ts | 38 +++++++------------ 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/packages/agent/src/didcomm/metadata.ts b/packages/agent/src/didcomm/metadata.ts index 0deb3182..bb760346 100644 --- a/packages/agent/src/didcomm/metadata.ts +++ b/packages/agent/src/didcomm/metadata.ts @@ -1,3 +1,4 @@ +import type { OpenId4VcCredentialMetadata } from '../openid4vc/metadata' import type { CredentialExchangeRecord, ProofExchangeRecord } from '@credo-ts/core' export interface DidCommCredentialExchangeDisplayMetadata { @@ -54,3 +55,30 @@ export function setDidCommProofExchangeMetadata( ) { proofExchangeRecord.metadata.set(didCommProofExchangeDisplayMetadataKey, metadata) } + +export function openIdCredentialMetadataFromDidCommCredentialExchangeMetadata( + didcommMetadata: DidCommCredentialExchangeDisplayMetadata +): OpenId4VcCredentialMetadata { + return { + credential: { + display: didcommMetadata.credentialName + ? [ + { + name: didcommMetadata?.credentialName, + }, + ] + : undefined, + }, + issuer: { + // FIXME: what is issuer url? + id: didcommMetadata?.issuerName ?? 'Unkown', + display: didcommMetadata.issuerName + ? [ + { + name: didcommMetadata?.issuerName, + }, + ] + : undefined, + }, + } +} diff --git a/packages/agent/src/hooks/useCredentialsForDisplay.ts b/packages/agent/src/hooks/useCredentialsForDisplay.ts index 860b529b..65a255e8 100644 --- a/packages/agent/src/hooks/useCredentialsForDisplay.ts +++ b/packages/agent/src/hooks/useCredentialsForDisplay.ts @@ -1,4 +1,3 @@ -import { useCredentials as _useCredentials } from '@credo-ts/react-hooks' import { useMemo } from 'react' import { getCredentialForDisplay } from '../display' @@ -7,7 +6,6 @@ import { useSdJwtVcRecords, useW3cCredentialRecords } from '../providers' export const useCredentialsForDisplay = () => { const { w3cCredentialRecords, isLoading: isLoadingW3c } = useW3cCredentialRecords() const { sdJwtVcRecords, isLoading: isLoadingSdJwt } = useSdJwtVcRecords() - const { loading } = _useCredentials() const credentials = useMemo(() => { // Map into common structure that can be rendered @@ -24,6 +22,6 @@ export const useCredentialsForDisplay = () => { return { credentials, - isLoading: isLoadingSdJwt || isLoadingW3c || loading, + isLoading: isLoadingSdJwt || isLoadingW3c, } } diff --git a/packages/agent/src/hooks/useDidCommCredentialActions.ts b/packages/agent/src/hooks/useDidCommCredentialActions.ts index 1441b646..b100f0eb 100644 --- a/packages/agent/src/hooks/useDidCommCredentialActions.ts +++ b/packages/agent/src/hooks/useDidCommCredentialActions.ts @@ -7,7 +7,10 @@ import { firstValueFrom } from 'rxjs' import { filter, first, timeout } from 'rxjs/operators' import { useAgent } from '../agent' -import { getDidCommCredentialExchangeDisplayMetadata } from '../didcomm/metadata' +import { + openIdCredentialMetadataFromDidCommCredentialExchangeMetadata, + getDidCommCredentialExchangeDisplayMetadata, +} from '../didcomm/metadata' import { setOpenId4VcCredentialMetadata } from '../openid4vc/metadata' function useOfferAttributes(credentialExchangeId: string) { @@ -66,39 +69,26 @@ export function useDidCommCredentialActions(credentialExchangeId: string) { await agent.credentials.acceptOffer({ credentialRecordId: credentialExchangeId }) const doneEvent = await credentialDonePromise + const w3cCredentialRecordId = doneEvent.payload.credentialRecord.credentials.find( (c) => c.credentialRecordType === 'w3c' )?.credentialRecordId + const didCommDisplayMetadata = getDidCommCredentialExchangeDisplayMetadata( + doneEvent.payload.credentialRecord + ) - // Update the w3c credential record metadata, based on the didcomm credential exchange display - // metadata - if (w3cCredentialRecordId) { - const didCommDisplayMetadata = getDidCommCredentialExchangeDisplayMetadata( - doneEvent.payload.credentialRecord - ) + // Update the w3c credential record metadata, based on the didcomm credential exchange display metadata + if (w3cCredentialRecordId && didCommDisplayMetadata) { // NOTE: we store the metadata also in openid4vc format, just because it's simple. In the future // we may want to have our own display format we use for all credential types const w3cRecord = await agent.w3cCredentials.getCredentialRecordById(w3cCredentialRecordId) // TODO: we must somehow link the w3c credential record to a DIDComm connection // first in Paradym Wallet, but would alos be nice to do this within Credo - setOpenId4VcCredentialMetadata(w3cRecord, { - credential: { - display: [ - { - name: didCommDisplayMetadata?.credentialName ?? 'Credential', - }, - ], - }, - issuer: { - id: didCommDisplayMetadata?.issuerName ?? 'Unkown', - display: [ - { - name: didCommDisplayMetadata?.issuerName, - }, - ], - }, - }) + setOpenId4VcCredentialMetadata( + w3cRecord, + openIdCredentialMetadataFromDidCommCredentialExchangeMetadata(didCommDisplayMetadata) + ) const w3cCredentialRepository = agent.dependencyManager.resolve(W3cCredentialRepository) await w3cCredentialRepository.update(agent.context, w3cRecord) From 3266afffabb097a816cfcefd6c38e04106849d3b Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 26 Apr 2024 14:50:26 +0200 Subject: [PATCH 3/3] fixes Signed-off-by: Timo Glastra --- .../src/hooks/useCredentialForDisplayById.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/packages/agent/src/hooks/useCredentialForDisplayById.ts b/packages/agent/src/hooks/useCredentialForDisplayById.ts index 2b2ed1e7..3b44ce1b 100644 --- a/packages/agent/src/hooks/useCredentialForDisplayById.ts +++ b/packages/agent/src/hooks/useCredentialForDisplayById.ts @@ -1,20 +1,10 @@ -import { useCredentialById as _useCredentialById } from '@credo-ts/react-hooks' - -import { getCredentialExchangeForDisplay, getCredentialForDisplay } from '../display' +import { getCredentialForDisplay } from '../display' import { useSdJwtVcRecordById, useW3cCredentialRecordById } from '../providers' -export type CredentialForDisplayId = - | `credential-exchange-${string}` - | `w3c-credential-${string}` - | `sd-jwt-vc-${string}` +export type CredentialForDisplayId = `w3c-credential-${string}` | `sd-jwt-vc-${string}` export const useCredentialForDisplayById = (credentialId: CredentialForDisplayId) => { - if (credentialId.startsWith('credential-exchange-')) { - const c = _useCredentialById(credentialId.replace('credential-exchange-', '')) - if (!c) return null - - return getCredentialExchangeForDisplay(c) - } else if (credentialId.startsWith('w3c-credential-')) { + if (credentialId.startsWith('w3c-credential-')) { const c = useW3cCredentialRecordById(credentialId.replace('w3c-credential-', '')) if (!c) return null