From 31e7f13277a2a5ded6d3d5d88e596773969f050f Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 26 Jun 2024 11:33:44 +0200 Subject: [PATCH 1/4] fix: useEffect dependencies for invitation handler (#114) Signed-off-by: Timo Glastra --- .../notifications/DidCommNotificationScreen.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/app/src/features/notifications/DidCommNotificationScreen.tsx b/packages/app/src/features/notifications/DidCommNotificationScreen.tsx index 98967c58..ced8abfe 100644 --- a/packages/app/src/features/notifications/DidCommNotificationScreen.tsx +++ b/packages/app/src/features/notifications/DidCommNotificationScreen.tsx @@ -29,7 +29,7 @@ export function DidCommNotificationScreen() { const toast = useToastController() const router = useRouter() - const [isLoadingInvitation, setIsLoadingInvitation] = useState(false) + const [hasHandledNotificationLoading, setHasHandledNotificationLoading] = useState(false) const [notification, setNotification] = useState( params.credentialExchangeId ? ({ @@ -48,7 +48,8 @@ export function DidCommNotificationScreen() { useEffect(() => { async function handleInvitation() { - if (isLoadingInvitation) return + if (hasHandledNotificationLoading) return + setHasHandledNotificationLoading(true) try { const invitation = params.invitation @@ -57,8 +58,8 @@ export function DidCommNotificationScreen() { ? decodeURIComponent(params.invitationUrl) : undefined + // Might be no invitation if a presentationExchangeId or credentialExchangeId is passed directly if (!invitation) return - setIsLoadingInvitation(true) const parseResult = await parseDidCommInvitation(agent, invitation) if (!parseResult.success) { @@ -86,12 +87,10 @@ export function DidCommNotificationScreen() { toast.show('Error parsing invitation') pushToWallet() } - - setIsLoadingInvitation(false) } void handleInvitation() - }, [params.invitation, params.invitationUrl, isLoadingInvitation, agent, toast, pushToWallet]) + }, [params.invitation, params.invitationUrl, hasHandledNotificationLoading, agent, toast, pushToWallet]) // We were routed here without any notification if (!params.credentialExchangeId && !params.proofExchangeId && !params.invitation && !params.invitationUrl) { From a70d207e4fd5672c5ce6fde5e0285544d8be47cc Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht <61358536+berendsliedrecht@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:06:13 +0200 Subject: [PATCH 2/4] fix: test full flows (#115) Signed-off-by: Berend Sliedrecht --- packages/app/src/hooks/useTransparentNavigationBar.tsx | 2 +- packages/app/src/utils/DeeplinkHandler.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/hooks/useTransparentNavigationBar.tsx b/packages/app/src/hooks/useTransparentNavigationBar.tsx index 7742ceba..86fe04ba 100644 --- a/packages/app/src/hooks/useTransparentNavigationBar.tsx +++ b/packages/app/src/hooks/useTransparentNavigationBar.tsx @@ -1,6 +1,6 @@ import * as NavigationBar from 'expo-navigation-bar' -import { isAndroid } from '../utils' +import { isAndroid } from '../utils/platform' export const useTransparentNavigationBar = () => { if (isAndroid()) { diff --git a/packages/app/src/utils/DeeplinkHandler.tsx b/packages/app/src/utils/DeeplinkHandler.tsx index 8c74fb2d..d027da3c 100644 --- a/packages/app/src/utils/DeeplinkHandler.tsx +++ b/packages/app/src/utils/DeeplinkHandler.tsx @@ -2,12 +2,12 @@ import { useCallback } from 'react' import type { ReactNode } from 'react' import { InvitationQrTypes } from '@package/agent' -import { useCredentialDataHandler } from '@package/app' import { useToastController } from '@package/ui' import { CommonActions } from '@react-navigation/native' import * as Linking from 'expo-linking' import { useNavigation } from 'expo-router' import { useEffect, useState } from 'react' +import { useCredentialDataHandler } from '../hooks' interface DeeplinkHandlerProps { children: ReactNode From 28232061d33bd4c5402f09d8eb41c478c3f626fa Mon Sep 17 00:00:00 2001 From: Ana Goessens Date: Wed, 3 Jul 2024 16:11:43 +0200 Subject: [PATCH 3/4] Update README.md (#117) --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index e3bf508e..bb0336a4 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,9 @@ With Paradym Wallet, you can seamlessly manage and present your digital credenti You can download Paradym Wallet from the [Google Play Store](https://play.google.com/store/apps/details?id=id.paradym.wallet) or [Apple App Store](https://apps.apple.com/nl/app/paradym-wallet/id6449846111?l=en). -The wallet can be used in three environments: +The wallet can be used with existing demo's in these environments: - Dutch Blockchain Coalition (DBC): Use your wallet to access the DBC zone where you can find extra resources related to DBC events. You can use [this link](https://ssi.dutchblockchaincoalition.org/demo/issuer) to receive your credential, and log in on the [DBC website](https://www.dutchblockchaincoalition.org/userlogin). -- Triall: Log in to the Triall environment. Obtain your credential [here](https://ssi.triall.io/demo/issuer) and enter the environment on the [Trial website](https://ssi.triall.io/demo/issuer). - Future Mobility Alliance: Access the Future Mobility Data Marketplace by obtaining your credential [here](https://ssi.future-mobility-alliance.org/demo/issuer) and logging in via the [FMA website](https://marketplace.future-mobility-alliance.org/). ## Project Structure From e62a97a6d370c0803a971d13ab2efa75ad426de2 Mon Sep 17 00:00:00 2001 From: Tom Lanser Date: Thu, 11 Jul 2024 16:46:48 +0200 Subject: [PATCH 4/4] feat: Merge anoncreds proof request credentials (#118) Signed-off-by: Tom Lanser --- .../hooks/useDidCommPresentationActions.ts | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/packages/agent/src/hooks/useDidCommPresentationActions.ts b/packages/agent/src/hooks/useDidCommPresentationActions.ts index 1331ffaf..a8898e29 100644 --- a/packages/agent/src/hooks/useDidCommPresentationActions.ts +++ b/packages/agent/src/hooks/useDidCommPresentationActions.ts @@ -16,6 +16,8 @@ import { filter, first, timeout } from 'rxjs/operators' import { useAgent } from '../agent' import { getDidCommCredentialExchangeDisplayMetadata } from '../didcomm/metadata' +type ProofedCredentialEntry = FormattedSubmission['entries'][number] + export function useDidCommPresentationActions(proofExchangeId: string) { const { agent } = useAgent() @@ -40,10 +42,22 @@ export function useDidCommPresentationActions(proofExchangeId: string) { throw new CredoError('Invalid proof request.') } - const submission: FormattedSubmission = { - areAllSatisfied: false, - entries: [], - name: proofRequest?.name ?? 'Unknown', + const entries = new Map() + const mergeOrSetEntry = (key: string, newEntry: ProofedCredentialEntry) => { + const entry = entries.get(key) + if (entry) { + entries.set(key, { + name: entry.name || newEntry.name, + backgroundColor: entry.backgroundColor || newEntry.backgroundColor, + description: entry.description || newEntry.description, + credentialName: entry.credentialName || newEntry.credentialName, + issuerName: entry.issuerName || newEntry.issuerName, + isSatisfied: entry.isSatisfied && newEntry.isSatisfied, // Check if both are true otherwise it's not satisfied + requestedAttributes: [...(entry.requestedAttributes ?? []), ...(newEntry.requestedAttributes ?? [])], + }) + } else { + entries.set(key, newEntry) + } } await Promise.all( @@ -54,8 +68,13 @@ export function useDidCommPresentationActions(proofExchangeId: string) { const firstMatch = attributeArray[0] + // When the credentialId isn't available and there is no __CREDENTIAL__ in the groupName, we use the groupName as the key but it will result in multiple entries in the view. But I think it's not an easy task to merge them + const credentialKey = + firstMatch?.credentialId ?? + (groupName.includes('__CREDENTIAL__') ? groupName.split('__CREDENTIAL__')[0] : groupName) + if (!firstMatch) { - submission.entries.push({ + mergeOrSetEntry(credentialKey, { credentialName: 'Credential', // TODO: we can extract this from the schema name, but we would have to fetch it isSatisfied: false, name: groupName, // TODO @@ -70,7 +89,7 @@ export function useDidCommPresentationActions(proofExchangeId: string) { ? getDidCommCredentialExchangeDisplayMetadata(credentialExchange) : undefined - submission.entries.push({ + mergeOrSetEntry(credentialKey, { name: groupName, // TODO: humanize string? Or should we let this out? credentialName: credentialDisplayMetadata?.credentialName ?? 'Credential', isSatisfied: true, @@ -94,8 +113,13 @@ export function useDidCommPresentationActions(proofExchangeId: string) { // This should probably be fixed in AFJ. const firstMatch = predicateArray[0] + // When the credentialId isn't available and there is no __CREDENTIAL__ in the groupName, we use the groupName as the key but it will result in multiple entries in the view. But I think it's not an easy task to merge them + const credentialKey = + firstMatch?.credentialId ?? + (groupName.includes('__CREDENTIAL__') ? groupName.split('__CREDENTIAL__')[0] : groupName) + if (!firstMatch) { - submission.entries.push({ + mergeOrSetEntry(credentialKey, { credentialName: 'Credential', // TODO: we can extract this from the schema name, but we would have to fetch it isSatisfied: false, name: groupName, // TODO @@ -110,7 +134,7 @@ export function useDidCommPresentationActions(proofExchangeId: string) { ? getDidCommCredentialExchangeDisplayMetadata(credentialExchange) : undefined - submission.entries.push({ + mergeOrSetEntry(credentialKey, { name: groupName, // TODO: humanize string? Or should we let this out? credentialName: credentialDisplayMetadata?.credentialName ?? 'Credential', isSatisfied: true, @@ -121,6 +145,14 @@ export function useDidCommPresentationActions(proofExchangeId: string) { }) ) + const entriesArray = Array.from(entries.values()) + + const submission: FormattedSubmission = { + areAllSatisfied: entriesArray.every((entry) => entry.isSatisfied), + entries: entriesArray, + name: proofRequest?.name ?? 'Unknown', + } + submission.areAllSatisfied = submission.entries.every((entry) => entry.isSatisfied) return submission