From 1ba4b24bdd288f710fd8c547c3aa3cde917274e6 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Thu, 3 Oct 2024 12:01:31 +0200 Subject: [PATCH] feat: whole flow working with better UX Signed-off-by: Berend Sliedrecht --- example/src/credo/agent.ts | 2 +- example/src/credo/bleRequestProof.ts | 184 ---------------- example/src/credo/bleShareProof.ts | 197 ------------------ .../utils/autoRespondToBleProofRequest.ts | 37 ---- example/src/credo/utils/closeBleTransports.ts | 16 -- .../src/credo/utils/createBleProofMessage.ts | 53 ----- example/src/credo/utils/date.ts | 6 - example/src/credo/utils/extractRequested.ts | 53 ----- .../credo/utils/returnWhenProofIsShared.tsx | 20 -- library/src/bleRequestProof.ts | 38 ++-- library/src/bleShareProof.ts | 74 +++---- 11 files changed, 43 insertions(+), 637 deletions(-) delete mode 100644 example/src/credo/bleRequestProof.ts delete mode 100644 example/src/credo/bleShareProof.ts delete mode 100644 example/src/credo/utils/autoRespondToBleProofRequest.ts delete mode 100644 example/src/credo/utils/closeBleTransports.ts delete mode 100644 example/src/credo/utils/createBleProofMessage.ts delete mode 100644 example/src/credo/utils/date.ts delete mode 100644 example/src/credo/utils/extractRequested.ts delete mode 100644 example/src/credo/utils/returnWhenProofIsShared.tsx diff --git a/example/src/credo/agent.ts b/example/src/credo/agent.ts index cfd206f..6db49bc 100644 --- a/example/src/credo/agent.ts +++ b/example/src/credo/agent.ts @@ -36,7 +36,7 @@ export const setupAgent = async () => { id: 'react-native-ble-didcomm-agent', key: 'react-native-ble-didcomm-key', }, - logger: new ConsoleLogger(LogLevel.info), + logger: new ConsoleLogger(LogLevel.off), }, modules: { askar: new AskarModule({ ariesAskar }), diff --git a/example/src/credo/bleRequestProof.ts b/example/src/credo/bleRequestProof.ts deleted file mode 100644 index cecfa21..0000000 --- a/example/src/credo/bleRequestProof.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { - DEFAULT_DIDCOMM_INDICATE_CHARACTERISTIC_UUID, - DEFAULT_DIDCOMM_MESSAGE_CHARACTERISTIC_UUID, - type Peripheral, -} from '@animo-id/react-native-ble-didcomm' -import type { AnonCredsProofFormat } from '@credo-ts/anoncreds' -import { - AutoAcceptProof, - MessageReceiver, - ProofEventTypes, - type ProofExchangeRecord, - type ProofFormatPayload, - ProofState, - type ProofStateChangedEvent, -} from '@credo-ts/core' -import { BleOutboundTransport } from '@credo-ts/transport-ble' -import type { AppAgent } from './agent' - -export type PresentationTemplate = { - id: string - name: string - requestMessage: ProofFormatPayload<[AnonCredsProofFormat], 'createRequest'> -} - -export type BleRequestProofOptions = { - agent: AppAgent - peripheral: Peripheral - serviceUuid: string - presentationTemplate: PresentationTemplate - onFailure: () => Promise | void - onConnected?: () => Promise | void - onDisconnected?: () => Promise | void -} - -export const bleRequestProof = async ({ - peripheral, - agent, - serviceUuid, - presentationTemplate, - onFailure, - onConnected, - onDisconnected, -}: BleRequestProofOptions) => { - try { - await startBleTransport(agent, peripheral) - - await startPeripheral(peripheral, serviceUuid) - - disconnctedNotifier(agent, peripheral, onDisconnected) - - const proofRecordId = await sendMessageWhenConnected( - agent, - peripheral, - presentationTemplate, - serviceUuid, - onConnected - ) - - const messageListener = startMessageReceiver(agent, peripheral) - await returnWhenProofReceived(proofRecordId, agent) - messageListener.remove() - - return proofRecordId - } catch (e) { - if (e instanceof Error) { - agent.config.logger.error(e.message, { cause: e }) - } else { - agent.config.logger.error(e) - } - - onFailure() - } finally { - await shutdownProcess(agent, peripheral) - } -} - -const startBleTransport = async (agent: AppAgent, peripheral: Peripheral) => { - const bleOutboundTransport = new BleOutboundTransport(peripheral) - agent.registerOutboundTransport(bleOutboundTransport) - await bleOutboundTransport.start(agent) -} - -const startPeripheral = async (peripheral: Peripheral, serviceUuid: string) => { - await peripheral.start() - - await peripheral.setService({ - serviceUUID: serviceUuid, - messagingUUID: DEFAULT_DIDCOMM_MESSAGE_CHARACTERISTIC_UUID, - indicationUUID: DEFAULT_DIDCOMM_INDICATE_CHARACTERISTIC_UUID, - }) - - await peripheral.advertise() -} - -const shutdownProcess = async (agent: AppAgent, peripheral: Peripheral) => { - for (const ot of agent.outboundTransports) { - if (ot instanceof BleOutboundTransport) { - void agent.unregisterOutboundTransport(ot) - } - } - - await peripheral.shutdown() -} - -const sendMessageWhenConnected = async ( - agent: AppAgent, - peripheral: Peripheral, - presentationTemplate: PresentationTemplate, - serviceUuid: string, - onConnectedListener: () => Promise | void -) => - new Promise((resolve) => { - const connectedListener = peripheral.registerOnConnectedListener(async () => { - await onConnectedListener() - const { message, proofRecordId } = await createBleProofRequestMessage( - agent, - presentationTemplate.requestMessage, - serviceUuid - ) - await peripheral.sendMessage(message) - connectedListener.remove() - resolve(proofRecordId) - }) - }) - -const disconnctedNotifier = (agent: AppAgent, peripheral: Peripheral, onDisconnected: () => Promise | void) => { - const disconnectedListener = peripheral.registerOnDisconnectedListener(async ({ identifier }) => { - agent.config.logger.info(`[PERIPHERAL]: Disconnected from device ${identifier}`) - await onDisconnected() - disconnectedListener.remove() - }) -} - -// TODO: is this required? -const startMessageReceiver = (agent: AppAgent, peripheral: Peripheral) => { - const messageReceiver = agent.dependencyManager.resolve(MessageReceiver) - return peripheral.registerMessageListener(async ({ message }) => { - agent.config.logger.info(`[PERIPHERAL]: received message ${message.slice(0, 16)}...`) - await messageReceiver.receiveMessage(JSON.parse(message)) - }) -} - -const returnWhenProofReceived = (id: string, agent: AppAgent): Promise => { - return new Promise((resolve, reject) => { - const listener = ({ payload }: ProofStateChangedEvent) => { - const off = () => agent.events.off(ProofEventTypes.ProofStateChanged, listener) - if (payload.proofRecord.id === id) { - if (payload.proofRecord.state === ProofState.PresentationReceived) { - off() - resolve(payload.proofRecord) - } else if ([ProofState.Abandoned, ProofState.Declined].includes(payload.proofRecord.state)) { - off() - reject(new Error(`Proof could not be shared because it has been ${payload.proofRecord.state}`)) - } - } - } - agent.events.on(ProofEventTypes.ProofStateChanged, listener) - }) -} - -const createBleProofRequestMessage = async ( - agent: AppAgent, - requestMessage: PresentationTemplate['requestMessage'], - serviceUuid: string -) => { - const { proofRecord, message } = await agent.proofs.createRequest({ - proofFormats: requestMessage, - protocolVersion: 'v2', - autoAcceptProof: AutoAcceptProof.Always, - }) - - const routing = await agent.mediationRecipient.getRouting({ useDefaultMediator: false }) - routing.endpoints = [`ble://${serviceUuid}`] - const { outOfBandInvitation } = await agent.oob.createInvitation({ - routing, - handshake: false, - messages: [message], - }) - - return { - message: JSON.stringify(outOfBandInvitation.toJSON()), - proofRecordId: proofRecord.id, - } -} diff --git a/example/src/credo/bleShareProof.ts b/example/src/credo/bleShareProof.ts deleted file mode 100644 index 329a3b4..0000000 --- a/example/src/credo/bleShareProof.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { - type Central, - DEFAULT_DIDCOMM_INDICATE_CHARACTERISTIC_UUID, - DEFAULT_DIDCOMM_MESSAGE_CHARACTERISTIC_UUID, -} from '@animo-id/react-native-ble-didcomm' -import { - type Agent, - JsonTransformer, - OutOfBandInvitation, - ProofEventTypes, - type ProofExchangeRecord, - ProofRepository, - ProofState, - type ProofStateChangedEvent, -} from '@credo-ts/core' -import { BleInboundTransport, BleOutboundTransport } from '@credo-ts/transport-ble' -import type { AppAgent } from './agent' - -const METADATA_KEY_FORMAT_DATA = 'FORMAT_DATA' - -export type BleShareProofOptions = { - agent: AppAgent - central: Central - serviceUuid: string - onFailure: () => Promise | void - onConnected?: () => Promise | void - onDisconnected?: () => Promise | void -} - -// TODO: would be nice to have a rejection timeout here -export const bleShareProof = async ({ - agent, - central, - serviceUuid, - onFailure, - onConnected, - onDisconnected, -}: BleShareProofOptions) => { - try { - await startBleTransport(agent, central) - - await startCentral(central, serviceUuid) - - disconnctedNotifier(agent, central, onDisconnected) - - await discoverAndConnect(agent, central) - - await connectedNotifier(agent, central, onConnected) - - await shareProof(agent, central, serviceUuid) - } catch (e) { - if (e instanceof Error) { - agent.config.logger.error(e.message, { cause: e }) - } else { - agent.config.logger.error(e) - } - - onFailure() - } finally { - await shutdownProcess(agent, central) - } -} - -const startBleTransport = async (agent: AppAgent, central: Central) => { - const bleInboundTransport = new BleInboundTransport(central) - agent.registerInboundTransport(bleInboundTransport) - await bleInboundTransport.start(agent) - const bleOutboundTransport = new BleOutboundTransport(central) - agent.registerOutboundTransport(bleOutboundTransport) - await bleOutboundTransport.start(agent) -} - -const startCentral = async (central: Central, serviceUuid: string) => { - await central.start() - await central.setService({ - serviceUUID: serviceUuid, - messagingUUID: DEFAULT_DIDCOMM_MESSAGE_CHARACTERISTIC_UUID, - indicationUUID: DEFAULT_DIDCOMM_INDICATE_CHARACTERISTIC_UUID, - }) - await central.scan() -} - -const discoverAndConnect = async (agent: AppAgent, central: Central) => - await new Promise((resolve) => { - const listener = central.registerOnDiscoveredListener(({ identifier, name }) => { - agent.config.logger.info(`[CENTRAL]: Discovered device ${name ? `(${name})` : ''}: ${identifier}`) - - central.connect(identifier).then(() => { - listener.remove() - resolve() - }) - }) - }) - -const connectedNotifier = async (agent: AppAgent, central: Central, onConnected?: () => Promise | void) => - new Promise((resolve) => { - const connectedListener = central.registerOnConnectedListener(async ({ identifier, name }) => { - agent.config.logger.info(`[CENTRAL]: Connected to device ${name ? `(${name})` : ''}: ${identifier}`) - await onConnected() - connectedListener.remove() - resolve() - }) - }) - -const disconnctedNotifier = (agent: Agent, central: Central, onDisconnected: () => Promise | void) => { - const disconnectedListener = central.registerOnDisconnectedListener(async ({ identifier }) => { - agent.config.logger.info(`[CENTRAL]: Disconnected from device ${identifier}`) - await onDisconnected() - disconnectedListener.remove() - }) -} - -const shutdownProcess = async (agent: Agent, central: Central) => { - for (const it of agent.inboundTransports) { - if (it instanceof BleInboundTransport) { - void agent.unregisterInboundTransport(it) - } - } - - for (const ot of agent.outboundTransports) { - if (ot instanceof BleOutboundTransport) { - void agent.unregisterOutboundTransport(ot) - } - } - - await central.shutdown() -} - -const shareProof = async (agent: AppAgent, central: Central, serviceUuid: string) => - new Promise((resolve) => { - const receivedMessageListener = central.registerMessageListener(async ({ message }) => { - agent.config.logger.info(`[CENTRAL]: received message ${message.slice(0, 16)}...`) - - const parsedMessage = JsonTransformer.deserialize(message, OutOfBandInvitation) - - const responder = autoRespondToBleProofRequest(agent) - - const routing = await agent.mediationRecipient.getRouting({ - useDefaultMediator: false, - }) - - await agent.oob.receiveInvitation(parsedMessage, { - routing: { ...routing, endpoints: [`ble://${serviceUuid}`] }, - }) - - const { id } = await responder - - await waitForSharedProof(id, agent) - - receivedMessageListener.remove() - resolve() - }) - }) - -const autoRespondToBleProofRequest = (agent: AppAgent): Promise => { - return new Promise((resolve, reject) => { - const listener = async ({ payload: { proofRecord } }: ProofStateChangedEvent) => { - if (proofRecord.state === ProofState.RequestReceived) { - const formatData = await agent.proofs.getFormatData(proofRecord.id) - - if (!formatData.request?.anoncreds) { - reject('Proof request does not contain anoncreds request') - return - } - - await agent.proofs.acceptRequest({ proofRecordId: proofRecord.id }) - - resolve(proofRecord) - } else if (proofRecord.state === ProofState.Done || proofRecord.state === ProofState.PresentationSent) { - const formatData = await agent.proofs.getFormatData(proofRecord.id) - const proofRepository = agent.dependencyManager.resolve(ProofRepository) - proofRecord.metadata.set(METADATA_KEY_FORMAT_DATA, formatData) - await proofRepository.update(agent.context, proofRecord) - agent.events.off(ProofEventTypes.ProofStateChanged, listener) - resolve(proofRecord) - } - } - agent.events.on(ProofEventTypes.ProofStateChanged, listener) - }) -} - -const waitForSharedProof = (id: string, agent: AppAgent): Promise => - new Promise((resolve, reject) => { - const listener = ({ payload }: ProofStateChangedEvent) => { - const off = () => agent.events.off(ProofEventTypes.ProofStateChanged, listener) - if (payload.proofRecord.id === id) { - if (payload.proofRecord.state === ProofState.PresentationReceived) { - off() - resolve(payload.proofRecord) - } else if ([ProofState.Abandoned, ProofState.Declined].includes(payload.proofRecord.state)) { - off() - reject(new Error(`Proof could not be shared because it has been ${payload.proofRecord.state}`)) - } - } - } - agent.events.on(ProofEventTypes.ProofStateChanged, listener) - }) diff --git a/example/src/credo/utils/autoRespondToBleProofRequest.ts b/example/src/credo/utils/autoRespondToBleProofRequest.ts deleted file mode 100644 index 198c14a..0000000 --- a/example/src/credo/utils/autoRespondToBleProofRequest.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - ProofEventTypes, - type ProofExchangeRecord, - ProofRepository, - ProofState, - type ProofStateChangedEvent, -} from '@credo-ts/core' -import type { AppAgent } from '../agent' - -export const METADATA_KEY_FORMAT_DATA = 'FORMAT_DATA' - -export const autoRespondToBleProofRequest = (agent: AppAgent): Promise => { - return new Promise((resolve, reject) => { - const listener = async ({ payload: { proofRecord } }: ProofStateChangedEvent) => { - if (proofRecord.state === ProofState.RequestReceived) { - const formatData = await agent.proofs.getFormatData(proofRecord.id) - - if (!formatData.request?.anoncreds) { - reject('Proof request does not contain anoncreds request') - return - } - - await agent.proofs.acceptRequest({ proofRecordId: proofRecord.id }) - - resolve(proofRecord) - } else if (proofRecord.state === ProofState.Done || proofRecord.state === ProofState.PresentationSent) { - const formatData = await agent.proofs.getFormatData(proofRecord.id) - const proofRepository = agent.dependencyManager.resolve(ProofRepository) - proofRecord.metadata.set(METADATA_KEY_FORMAT_DATA, formatData) - await proofRepository.update(agent.context, proofRecord) - agent.events.off(ProofEventTypes.ProofStateChanged, listener) - resolve(proofRecord) - } - } - agent.events.on(ProofEventTypes.ProofStateChanged, listener) - }) -} diff --git a/example/src/credo/utils/closeBleTransports.ts b/example/src/credo/utils/closeBleTransports.ts deleted file mode 100644 index ed65ca4..0000000 --- a/example/src/credo/utils/closeBleTransports.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { BleInboundTransport, BleOutboundTransport } from '@credo-ts/transport-ble' -import type { AppAgent } from '../agent' - -export const closeBleTransports = (agent: AppAgent) => { - for (const it of agent.inboundTransports) { - if (it instanceof BleInboundTransport) { - void agent.unregisterInboundTransport(it) - } - } - - for (const ot of agent.outboundTransports) { - if (ot instanceof BleOutboundTransport) { - void agent.unregisterOutboundTransport(ot) - } - } -} diff --git a/example/src/credo/utils/createBleProofMessage.ts b/example/src/credo/utils/createBleProofMessage.ts deleted file mode 100644 index 49d4e97..0000000 --- a/example/src/credo/utils/createBleProofMessage.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { - AnonCredsProof, - AnonCredsProofFormat, - AnonCredsProposeProofFormat, - AnonCredsRequestProofFormat, -} from '@credo-ts/anoncreds' -import { AutoAcceptProof, type ProofFormatPayload } from '@credo-ts/core' -import type { AppAgent } from '../agent' - -export type RequestMessage = ProofFormatPayload<[AnonCredsProofFormat], 'createRequest'> - -export type ProofTemplate = { - id: string - name: string - requestMessage: RequestMessage -} - -export type FormattedProof = { - proposal: { - anoncreds?: AnonCredsProposeProofFormat - } - request: { - anoncreds?: AnonCredsRequestProofFormat - } - presentation: { - anoncreds?: AnonCredsProof - } -} - -export const createBleProofRequestMessage = async ( - agent: AppAgent, - requestMessage: RequestMessage, - serviceUuid: string -) => { - const { proofRecord, message } = await agent.proofs.createRequest({ - proofFormats: requestMessage, - protocolVersion: 'v2', - autoAcceptProof: AutoAcceptProof.Always, - }) - - const routing = await agent.mediationRecipient.getRouting({ useDefaultMediator: false }) - routing.endpoints = [`ble://${serviceUuid}`] - const { outOfBandInvitation } = await agent.oob.createInvitation({ - routing, - handshake: false, - messages: [message], - }) - - return { - message: JSON.stringify(outOfBandInvitation.toJSON()), - proofRecordId: proofRecord.id, - } -} diff --git a/example/src/credo/utils/date.ts b/example/src/credo/utils/date.ts deleted file mode 100644 index ee922b1..0000000 --- a/example/src/credo/utils/date.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const convertDate = (date: Date, months: Record, short = true) => { - const dayNum = `0${date.getDate()}`.slice(-2) - const month = short ? months[date.getMonth()].substring(0, 3) : months[date.getMonth()] - const year = date.getFullYear() - return `${dayNum} ${month}, ${year}` -} diff --git a/example/src/credo/utils/extractRequested.ts b/example/src/credo/utils/extractRequested.ts deleted file mode 100644 index 4dfff53..0000000 --- a/example/src/credo/utils/extractRequested.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { AnonCredsRequestedPredicate } from '@credo-ts/anoncreds' -import { JsonTransformer } from '@credo-ts/core' -import type { FormattedProof } from './createBleProofMessage' - -export const extractRequestAttributes = (data: FormattedProof): Record => { - const requestedAttributes = data.request.anoncreds?.requested_attributes ?? {} - const revealedAttributes = data.presentation.anoncreds?.requested_proof.revealed_attrs ?? {} - const revealedAttributeGroups = data.presentation.anoncreds?.requested_proof.revealed_attr_groups ?? {} - - const result: Record = {} - - for (const attribute in revealedAttributes) { - const attrName = requestedAttributes[attribute]?.name - const revealedValue = revealedAttributes[attribute]?.raw - - if (attrName && revealedValue) result[attrName] = revealedValue - } - - // map the requested attributes to the revealed values - for (const attribute in revealedAttributeGroups) { - const attrNames = requestedAttributes[attribute]?.names - const revealedValues = revealedAttributeGroups[attribute]?.values - - if (attrNames && revealedValues) { - for (const attributeName of attrNames) { - result[attributeName] = revealedValues[attributeName].raw - } - } - } - - return result -} - -export const extractRequestedPredicates = (data: FormattedProof): Record => { - type PredicateInfo = { - name: string - p_value: number - p_type: string - } - - const requestedPredicatesJson = JsonTransformer.toJSON(data.request?.anoncreds ?? {}).requested_predicates as Map< - string, - AnonCredsRequestedPredicate - > - - const result: Record = {} - - Object.values(requestedPredicatesJson ?? {}).map((predicateInfo: PredicateInfo) => { - // Add the p_value because we otherwise lose this info about comparison - result[predicateInfo.name] = `${predicateInfo.p_type} ${predicateInfo.p_value}` - }) - return result -} diff --git a/example/src/credo/utils/returnWhenProofIsShared.tsx b/example/src/credo/utils/returnWhenProofIsShared.tsx deleted file mode 100644 index 4127c7f..0000000 --- a/example/src/credo/utils/returnWhenProofIsShared.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { ProofEventTypes, type ProofExchangeRecord, ProofState, type ProofStateChangedEvent } from '@credo-ts/core' -import type { AppAgent } from '../agent' - -export const returnWhenProofShared = (id: string, agent: AppAgent): Promise => { - return new Promise((resolve, reject) => { - const listener = ({ payload }: ProofStateChangedEvent) => { - const off = () => agent.events.off(ProofEventTypes.ProofStateChanged, listener) - if (payload.proofRecord.id === id) { - if (payload.proofRecord.state === ProofState.PresentationReceived) { - off() - resolve(payload.proofRecord) - } else if ([ProofState.Abandoned, ProofState.Declined].includes(payload.proofRecord.state)) { - off() - reject(new Error(`Proof could not be shared because it has been ${payload.proofRecord.state}`)) - } - } - } - agent.events.on(ProofEventTypes.ProofStateChanged, listener) - }) -} diff --git a/library/src/bleRequestProof.ts b/library/src/bleRequestProof.ts index afe5f53..20a0456 100644 --- a/library/src/bleRequestProof.ts +++ b/library/src/bleRequestProof.ts @@ -2,6 +2,7 @@ import type { AnonCredsProofFormat } from '@credo-ts/anoncreds' import { type Agent, AutoAcceptProof, + JsonTransformer, MessageReceiver, ProofEventTypes, type ProofExchangeRecord, @@ -54,10 +55,9 @@ export const bleRequestProof = async ({ ) const messageListener = startMessageReceiver(agent, peripheral) - await returnWhenProofReceived(proofRecordId, agent) + await returnWhenProofReceived(proofRecordId, agent, peripheral) messageListener.remove() - await shutdownProcess(agent, peripheral) return proofRecordId } catch (e) { if (e instanceof Error) { @@ -66,7 +66,6 @@ export const bleRequestProof = async ({ agent.config.logger.error(e as string) } onFailure() - await shutdownProcess(agent, peripheral) throw e } } @@ -90,16 +89,6 @@ const startPeripheral = async (peripheral: Peripheral, agent: Agent, serviceUuid agent.config.logger.info(`[PERIPHERAL]: Advertising on service UUID '${serviceUuid}'`) } -const shutdownProcess = async (agent: Agent, peripheral: Peripheral) => { - for (const ot of agent.outboundTransports) { - if (ot instanceof BleOutboundTransport) { - void agent.unregisterOutboundTransport(ot) - } - } - - await peripheral.shutdown() -} - const sendMessageWhenConnected = async ( agent: Agent, peripheral: Peripheral, @@ -138,17 +127,24 @@ const startMessageReceiver = (agent: Agent, peripheral: Peripheral) => { }) } -const returnWhenProofReceived = (id: string, agent: Agent): Promise => { +const returnWhenProofReceived = (id: string, agent: Agent, peripheral: Peripheral): Promise => { return new Promise((resolve, reject) => { - const listener = ({ payload }: ProofStateChangedEvent) => { + const listener = async ({ payload: { proofRecord } }: ProofStateChangedEvent) => { const off = () => agent.events.off(ProofEventTypes.ProofStateChanged, listener) - if (payload.proofRecord.id === id) { - if (payload.proofRecord.state === ProofState.PresentationReceived) { + if (proofRecord.id === id) { + if (proofRecord.state === ProofState.PresentationReceived) { + console.log('') + const proofProtocol = agent.proofs.config.proofProtocols.find((pp) => pp.version === 'v2') + if (!proofProtocol) throw new Error('No V2 proof protocol registered on the agent') + const { message } = await proofProtocol.acceptPresentation(agent.context, { proofRecord }) + const serializedMessage = JsonTransformer.serialize(message) + await peripheral.sendMessage(serializedMessage) + } else if (proofRecord.state === ProofState.Done) { off() - resolve(payload.proofRecord) - } else if ([ProofState.Abandoned, ProofState.Declined].includes(payload.proofRecord.state)) { + resolve(proofRecord) + } else if ([ProofState.Abandoned, ProofState.Declined].includes(proofRecord.state)) { off() - reject(new Error(`Proof could not be shared because it has been ${payload.proofRecord.state}`)) + reject(new Error(`Proof could not be shared because it has been ${proofRecord.state}`)) } } } @@ -164,7 +160,7 @@ const createBleProofRequestMessage = async ( const { proofRecord, message } = await agent.proofs.createRequest({ proofFormats: requestMessage, protocolVersion: 'v2', - autoAcceptProof: AutoAcceptProof.Always, + autoAcceptProof: AutoAcceptProof.Never, }) const routing = await agent.mediationRecipient.getRouting({ diff --git a/library/src/bleShareProof.ts b/library/src/bleShareProof.ts index b1934c7..9e169b7 100644 --- a/library/src/bleShareProof.ts +++ b/library/src/bleShareProof.ts @@ -7,6 +7,7 @@ import { ProofRepository, ProofState, type ProofStateChangedEvent, + V2PresentationAckMessage, } from '@credo-ts/core' import { BleInboundTransport, BleOutboundTransport } from '@credo-ts/transport-ble' import type { Central } from './central' @@ -43,8 +44,8 @@ export const bleShareProof = async ({ await connectedNotifier(agent, central, onConnected) - await shareProof(agent, central, serviceUuid) - await shutdownProcess(agent, central) + const proofRecord = await shareProof(agent, central, serviceUuid) + await handleAck(agent, central, proofRecord) } catch (e) { if (e instanceof Error) { agent.config.logger.error(e.message, { cause: e }) @@ -53,7 +54,6 @@ export const bleShareProof = async ({ } onFailure() - await shutdownProcess(agent, central) throw e } } @@ -108,31 +108,13 @@ const disconnctedNotifier = (agent: Agent, central: Central, onDisconnected?: () }) } -const shutdownProcess = async (agent: Agent, central: Central) => { - for (const it of agent.inboundTransports) { - if (it instanceof BleInboundTransport) { - void agent.unregisterInboundTransport(it) - } - } - - for (const ot of agent.outboundTransports) { - if (ot instanceof BleOutboundTransport) { - void agent.unregisterOutboundTransport(ot) - } - } - - await central.shutdown() -} - const shareProof = async (agent: Agent, central: Central, serviceUuid: string) => - new Promise((resolve) => { + new Promise((resolve) => { const receivedMessageListener = central.registerMessageListener(async ({ message }) => { agent.config.logger.info(`[CENTRAL]: received message ${message.slice(0, 16)}...`) const parsedMessage = JsonTransformer.deserialize(message, OutOfBandInvitation) - const responder = autoRespondToBleProofRequest(agent) - const routing = await agent.mediationRecipient.getRouting({ useDefaultMediator: false, }) @@ -141,18 +123,17 @@ const shareProof = async (agent: Agent, central: Central, serviceUuid: string) = routing: { ...routing, endpoints: [`ble://${serviceUuid}`] }, }) - const { id } = await responder - - await waitForSharedProof(id, agent) + const proofExchangeRecord = await autoRespondToBleProofRequest(agent) receivedMessageListener.remove() - resolve() + resolve(proofExchangeRecord) }) }) const autoRespondToBleProofRequest = (agent: Agent): Promise => { return new Promise((resolve, reject) => { const listener = async ({ payload: { proofRecord } }: ProofStateChangedEvent) => { + const off = () => agent.events.off(ProofEventTypes.ProofStateChanged, listener) if (proofRecord.state === ProofState.RequestReceived) { const formatData = await agent.proofs.getFormatData(proofRecord.id) @@ -162,34 +143,29 @@ const autoRespondToBleProofRequest = (agent: Agent): Promise(ProofEventTypes.ProofStateChanged, listener) }) } -const waitForSharedProof = (id: string, agent: Agent): Promise => - new Promise((resolve, reject) => { - const listener = ({ payload }: ProofStateChangedEvent) => { - const off = () => agent.events.off(ProofEventTypes.ProofStateChanged, listener) - if (payload.proofRecord.id === id) { - if (payload.proofRecord.state === ProofState.PresentationReceived) { - off() - resolve(payload.proofRecord) - } else if ([ProofState.Abandoned, ProofState.Declined].includes(payload.proofRecord.state)) { - off() - reject(new Error(`Proof could not be shared because it has been ${payload.proofRecord.state}`)) - } - } - } - agent.events.on(ProofEventTypes.ProofStateChanged, listener) +const handleAck = async (agent: Agent, central: Central, proofRecord: ProofExchangeRecord) => + new Promise((resolve) => { + const listener = central.registerMessageListener(async ({ message }) => { + if (!message.includes('@type')) throw new Error(`Received invalid message '${message}'`) + + const deserializedMessage = JsonTransformer.deserialize(message, V2PresentationAckMessage) + if (deserializedMessage.threadId !== proofRecord.threadId) throw new Error('Received Ack with invalid thread id') + + const proofRepository = agent.dependencyManager.resolve(ProofRepository) + proofRecord.state = ProofState.Done + const formatData = await agent.proofs.getFormatData(proofRecord.id) + proofRecord.metadata.set(METADATA_KEY_FORMAT_DATA, formatData) + await proofRepository.update(agent.context, proofRecord) + + listener.remove() + resolve() + }) })