From c447c32013347757756b5695f07d3473c5905b3a Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Tue, 22 Aug 2023 18:40:55 -0300 Subject: [PATCH 1/8] feat: generalize getOutboundMessageContext Signed-off-by: Ariel Gentile --- .../src/agent/getOutboundMessageContext.ts | 28 ++++++++++++++++--- .../versions/v2/DidCommV2BaseMessage.ts | 24 ++++++++++++++++ .../didcomm/versions/v2/DidCommV2Message.ts | 12 ++++++++ .../storage/didcomm/DidCommMessageRecord.ts | 8 +++--- 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/packages/core/src/agent/getOutboundMessageContext.ts b/packages/core/src/agent/getOutboundMessageContext.ts index ea57257da8..7b2b0c0648 100644 --- a/packages/core/src/agent/getOutboundMessageContext.ts +++ b/packages/core/src/agent/getOutboundMessageContext.ts @@ -1,5 +1,5 @@ +import type { AgentBaseMessage } from './AgentBaseMessage' import type { AgentContext } from './context' -import type { DidCommV1Message } from '../didcomm/versions/v1' import type { ConnectionRecord, Routing } from '../modules/connections' import type { ResolvedDidCommService } from '../modules/didcomm' import type { OutOfBandRecord } from '../modules/oob' @@ -7,6 +7,7 @@ import type { BaseRecordAny } from '../storage/BaseRecord' import { Key } from '../crypto' import { ServiceDecorator } from '../decorators/service/ServiceDecorator' +import { DidCommV1Message, DidCommV2Message } from '../didcomm' import { AriesFrameworkError } from '../error' import { OutOfBandService, OutOfBandRole, OutOfBandRepository } from '../modules/oob' import { OutOfBandRecordMetadataKeys } from '../modules/oob/repository/outOfBandRecordMetadataTypes' @@ -37,9 +38,9 @@ export async function getOutboundMessageContext( }: { connectionRecord?: ConnectionRecord associatedRecord?: BaseRecordAny - message: DidCommV1Message - lastReceivedMessage?: DidCommV1Message - lastSentMessage?: DidCommV1Message + message: AgentBaseMessage + lastReceivedMessage?: AgentBaseMessage + lastSentMessage?: AgentBaseMessage } ) { // TODO: even if using a connection record, we should check if there's an oob record associated and this @@ -48,6 +49,17 @@ export async function getOutboundMessageContext( agentContext.config.logger.debug( `Creating outbound message context for message ${message.id} with connection ${connectionRecord.id}` ) + + // Attach 'from' and 'to' fields according to connection record (unless they are previously defined) + if (message instanceof DidCommV2Message) { + message.from = message.from ?? connectionRecord.did + const recipients = message.to ?? (connectionRecord.theirDid ? [connectionRecord.theirDid] : undefined) + if (!recipients) { + throw new AriesFrameworkError('Cannot find recipient did for message') + } + message.to = recipients + } + return new OutboundMessageContext(message, { agentContext, associatedRecord, @@ -67,6 +79,14 @@ export async function getOutboundMessageContext( ) } + if ( + !(message instanceof DidCommV1Message) || + !(lastReceivedMessage instanceof DidCommV1Message) || + !(lastSentMessage instanceof DidCommV1Message) + ) { + throw new AriesFrameworkError('No connection record associated with DIDComm V2 messages exchange') + } + // Connectionless return getConnectionlessOutboundMessageContext(agentContext, { message, diff --git a/packages/core/src/didcomm/versions/v2/DidCommV2BaseMessage.ts b/packages/core/src/didcomm/versions/v2/DidCommV2BaseMessage.ts index 4e17749e91..a49a97f9e2 100644 --- a/packages/core/src/didcomm/versions/v2/DidCommV2BaseMessage.ts +++ b/packages/core/src/didcomm/versions/v2/DidCommV2BaseMessage.ts @@ -17,6 +17,8 @@ export type DidCommV2MessageParams = { to?: string | string[] thid?: string parentThreadId?: string + senderOrder?: number + receivedOrders?: { [key: string]: number } // TODO: Update to DIDComm V2 format createdTime?: number expiresTime?: number fromPrior?: string @@ -24,6 +26,12 @@ export type DidCommV2MessageParams = { body?: unknown } +type DidCommV2ReceiverOrder = { + id: string + last: number + gaps: number[] +} + export class DidCommV2BaseMessage { @Matches(MessageIdRegExp) public id!: string @@ -63,6 +71,16 @@ export class DidCommV2BaseMessage { @IsOptional() public parentThreadId?: string + @Expose({ name: 'sender_order' }) + @IsNumber() + @IsOptional() + public senderOrder?: number + + @Expose({ name: 'received_orders' }) + @IsOptional() + @IsArray() + public receivedOrders?: DidCommV2ReceiverOrder[] + @Expose({ name: 'from_prior' }) @IsString() @IsOptional() @@ -86,6 +104,12 @@ export class DidCommV2BaseMessage { this.to = typeof options.to === 'string' ? [options.to] : options.to this.thid = options.thid this.parentThreadId = options.parentThreadId + this.senderOrder = options.senderOrder + this.receivedOrders = Object.entries(options.receivedOrders ?? {}).map(([id, last]) => ({ + id, + last, + gaps: [], + })) this.createdTime = options.createdTime this.expiresTime = options.expiresTime this.fromPrior = options.fromPrior diff --git a/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts b/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts index a45f45da6f..cc283bbb5e 100644 --- a/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts +++ b/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts @@ -1,6 +1,7 @@ import type { PlaintextDidCommV2Message } from './types' import type { AgentBaseMessage } from '../../../agent/AgentBaseMessage' import type { ServiceDecorator } from '../../../decorators/service/ServiceDecorator' +import type { ThreadDecorator } from '../../../decorators/thread/ThreadDecorator' import type { PlaintextMessage } from '../../types' import { AriesFrameworkError } from '../../../error' @@ -26,6 +27,17 @@ export class DidCommV2Message extends DidCommV2BaseMessage implements AgentBaseM return this.thid } + public setThread(options: Partial) { + this.thid = options.threadId + this.parentThreadId = options.parentThreadId + this.senderOrder = options.senderOrder + this.receivedOrders = Object.entries(options.receivedOrders ?? {}).map(([id, last]) => ({ + id, + last, + gaps: [], + })) + } + public hasAnyReturnRoute() { return false } diff --git a/packages/core/src/storage/didcomm/DidCommMessageRecord.ts b/packages/core/src/storage/didcomm/DidCommMessageRecord.ts index 6e5a60b3b0..7d0a88c555 100644 --- a/packages/core/src/storage/didcomm/DidCommMessageRecord.ts +++ b/packages/core/src/storage/didcomm/DidCommMessageRecord.ts @@ -58,13 +58,13 @@ export class DidCommMessageRecord extends BaseRecord } public getTags() { - const messageId = this.message['@id'] as string - const messageType = this.message['@type'] as string + const messageId = (this.message['@id'] ?? this.message['id']) as string + const messageType = (this.message['@type'] ?? this.message['type']) as string const { protocolName, protocolMajorVersion, protocolMinorVersion, messageName } = parseMessageType(messageType) const thread = this.message['~thread'] - let threadId = messageId + let threadId = (this.message['thid'] ?? messageId) as string if (isJsonObject(thread) && typeof thread.thid === 'string') { threadId = thread.thid @@ -89,7 +89,7 @@ export class DidCommMessageRecord extends BaseRecord public getMessageInstance( messageClass: MessageClass ): InstanceType { - const messageType = parseMessageType(this.message['@type'] as string) + const messageType = parseMessageType((this.message['@type'] ?? this.message['type']) as string) if (!canHandleMessageType(messageClass, messageType)) { throw new AriesFrameworkError('Provided message class type does not match type of stored message') From 76e8a5cbeaf5d5ba2a0c5cfe56ac60de1aee0793 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Mon, 28 Aug 2023 22:21:03 -0300 Subject: [PATCH 2/8] fix: OOB in outbound message context Signed-off-by: Ariel Gentile --- packages/core/src/agent/getOutboundMessageContext.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/agent/getOutboundMessageContext.ts b/packages/core/src/agent/getOutboundMessageContext.ts index 7b2b0c0648..d6f4101b44 100644 --- a/packages/core/src/agent/getOutboundMessageContext.ts +++ b/packages/core/src/agent/getOutboundMessageContext.ts @@ -81,8 +81,8 @@ export async function getOutboundMessageContext( if ( !(message instanceof DidCommV1Message) || - !(lastReceivedMessage instanceof DidCommV1Message) || - !(lastSentMessage instanceof DidCommV1Message) + (lastReceivedMessage !== undefined && !(lastReceivedMessage instanceof DidCommV1Message)) || + (lastSentMessage !== undefined && !(lastSentMessage instanceof DidCommV1Message)) ) { throw new AriesFrameworkError('No connection record associated with DIDComm V2 messages exchange') } From 0a4d4f2358f98fc89640b3fea61103ae2a5f7b37 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Fri, 15 Sep 2023 17:40:43 -0300 Subject: [PATCH 3/8] feat: filter messages by didcomm version Signed-off-by: Ariel Gentile --- packages/core/src/agent/AgentBaseMessage.ts | 2 +- packages/core/src/agent/Dispatcher.ts | 2 +- .../core/src/agent/MessageHandlerRegistry.ts | 20 ++++++++++++++----- packages/core/src/agent/MessageReceiver.ts | 4 +++- .../__tests__/MessageHandlerRegistry.test.ts | 14 ++++++++----- packages/core/src/didcomm/index.ts | 6 +++++- .../didcomm/versions/v1/DidCommV1Message.ts | 6 +++--- .../didcomm/versions/v2/DidCommV2Message.ts | 8 +++++--- 8 files changed, 42 insertions(+), 20 deletions(-) diff --git a/packages/core/src/agent/AgentBaseMessage.ts b/packages/core/src/agent/AgentBaseMessage.ts index ba92cc782d..e809f449d2 100644 --- a/packages/core/src/agent/AgentBaseMessage.ts +++ b/packages/core/src/agent/AgentBaseMessage.ts @@ -4,8 +4,8 @@ import type { DidCommMessageVersion, PlaintextMessage } from '../didcomm/types' export interface AgentBaseMessage { readonly type: string + readonly didCommVersion: DidCommMessageVersion - get didCommVersion(): DidCommMessageVersion get id(): string get threadId(): string | undefined diff --git a/packages/core/src/agent/Dispatcher.ts b/packages/core/src/agent/Dispatcher.ts index eb0530bf18..537da1578b 100644 --- a/packages/core/src/agent/Dispatcher.ts +++ b/packages/core/src/agent/Dispatcher.ts @@ -36,7 +36,7 @@ class Dispatcher { public async dispatch(messageContext: InboundMessageContext): Promise { const { agentContext, connection, senderKey, recipientKey, message } = messageContext - const messageHandler = this.messageHandlerRegistry.getHandlerForMessageType(message.type) + const messageHandler = this.messageHandlerRegistry.getHandlerForMessageType(message.type, message.didCommVersion) if (!messageHandler) { throw new AriesFrameworkError(`No handler for message type "${message.type}" found`) diff --git a/packages/core/src/agent/MessageHandlerRegistry.ts b/packages/core/src/agent/MessageHandlerRegistry.ts index f050d4ea9e..89a8b2bad5 100644 --- a/packages/core/src/agent/MessageHandlerRegistry.ts +++ b/packages/core/src/agent/MessageHandlerRegistry.ts @@ -1,5 +1,5 @@ import type { MessageHandler } from './MessageHandler' -import type { ConstructableDidCommMessage } from '../didcomm' +import type { ConstructableDidCommMessage, DidCommMessageVersion } from '../didcomm' import { injectable } from 'tsyringe' @@ -13,22 +13,32 @@ export class MessageHandlerRegistry { this.messageHandlers.push(messageHandler) } - public getHandlerForMessageType(messageType: string): MessageHandler | undefined { + public getHandlerForMessageType( + messageType: string, + didcommVersion: DidCommMessageVersion + ): MessageHandler | undefined { const incomingMessageType = parseMessageType(messageType) for (const handler of this.messageHandlers) { for (const MessageClass of handler.supportedMessages) { - if (canHandleMessageType(MessageClass, incomingMessageType)) return handler + if (didcommVersion === MessageClass.didCommVersion && canHandleMessageType(MessageClass, incomingMessageType)) { + return handler + } } } } - public getMessageClassForMessageType(messageType: string): ConstructableDidCommMessage | undefined { + public getMessageClassForMessageType( + messageType: string, + didcommVersion: DidCommMessageVersion + ): ConstructableDidCommMessage | undefined { const incomingMessageType = parseMessageType(messageType) for (const handler of this.messageHandlers) { for (const MessageClass of handler.supportedMessages) { - if (canHandleMessageType(MessageClass, incomingMessageType)) return MessageClass + if (didcommVersion === MessageClass.didCommVersion && canHandleMessageType(MessageClass, incomingMessageType)) { + return MessageClass + } } } } diff --git a/packages/core/src/agent/MessageReceiver.ts b/packages/core/src/agent/MessageReceiver.ts index d177ddee16..5314d72ded 100644 --- a/packages/core/src/agent/MessageReceiver.ts +++ b/packages/core/src/agent/MessageReceiver.ts @@ -300,7 +300,9 @@ export class MessageReceiver { throw new AriesFrameworkError(`No type found in the message: ${message}`) } - const MessageClass = this.messageHandlerRegistry.getMessageClassForMessageType(messageType) + const didcommVersion = isPlaintextMessageV1(message) ? DidCommMessageVersion.V1 : DidCommMessageVersion.V2 + + const MessageClass = this.messageHandlerRegistry.getMessageClassForMessageType(messageType, didcommVersion) if (!MessageClass) { throw new ProblemReportError(`No message class found for message type "${messageType}"`, { diff --git a/packages/core/src/agent/__tests__/MessageHandlerRegistry.test.ts b/packages/core/src/agent/__tests__/MessageHandlerRegistry.test.ts index 6af8ff4292..4db1a5171a 100644 --- a/packages/core/src/agent/__tests__/MessageHandlerRegistry.test.ts +++ b/packages/core/src/agent/__tests__/MessageHandlerRegistry.test.ts @@ -1,6 +1,6 @@ import type { MessageHandler } from '../MessageHandler' -import { DidCommV1Message } from '../../didcomm' +import { DidCommMessageVersion, DidCommV1Message } from '../../didcomm' import { parseMessageType } from '../../utils/messageType' import { MessageHandlerRegistry } from '../MessageHandlerRegistry' @@ -112,28 +112,32 @@ describe('MessageHandlerRegistry', () => { describe('getMessageClassForMessageType()', () => { it('should return the correct message class for a registered message type', () => { const messageClass = messageHandlerRegistry.getMessageClassForMessageType( - 'https://didcomm.org/connections/1.0/invitation' + 'https://didcomm.org/connections/1.0/invitation', + DidCommMessageVersion.V1 ) expect(messageClass).toBe(ConnectionInvitationTestMessage) }) it('should return undefined if no message class is registered for the message type', () => { const messageClass = messageHandlerRegistry.getMessageClassForMessageType( - 'https://didcomm.org/non-existing/1.0/invitation' + 'https://didcomm.org/non-existing/1.0/invitation', + DidCommMessageVersion.V1 ) expect(messageClass).toBeUndefined() }) it('should return the message class with a higher minor version for the message type', () => { const messageClass = messageHandlerRegistry.getMessageClassForMessageType( - 'https://didcomm.org/fake-protocol/1.0/message' + 'https://didcomm.org/fake-protocol/1.0/message', + DidCommMessageVersion.V1 ) expect(messageClass).toBe(CustomProtocolMessage) }) it('should not return the message class with a different major version', () => { const messageClass = messageHandlerRegistry.getMessageClassForMessageType( - 'https://didcomm.org/fake-protocol/2.0/message' + 'https://didcomm.org/fake-protocol/2.0/message', + DidCommMessageVersion.V1 ) expect(messageClass).toBeUndefined() }) diff --git a/packages/core/src/didcomm/index.ts b/packages/core/src/didcomm/index.ts index e74379142a..1a0eb9c7b7 100644 --- a/packages/core/src/didcomm/index.ts +++ b/packages/core/src/didcomm/index.ts @@ -1,3 +1,4 @@ +import type { DidCommMessageVersion } from './types' import type { DidCommV1Message } from './versions/v1' import type { DidCommV2Message } from './versions/v2' import type { ParsedMessageType } from '../utils/messageType' @@ -9,4 +10,7 @@ export * from './types' export * from './helpers' export * from './JweEnvelope' -export type ConstructableDidCommMessage = Constructor & { type: ParsedMessageType } +export type ConstructableDidCommMessage = Constructor & { + type: ParsedMessageType + didCommVersion: DidCommMessageVersion +} diff --git a/packages/core/src/didcomm/versions/v1/DidCommV1Message.ts b/packages/core/src/didcomm/versions/v1/DidCommV1Message.ts index e5e9ae3651..4361748a9c 100644 --- a/packages/core/src/didcomm/versions/v1/DidCommV1Message.ts +++ b/packages/core/src/didcomm/versions/v1/DidCommV1Message.ts @@ -33,9 +33,9 @@ export class DidCommV1Message extends Decorated implements AgentBaseMessage { @Exclude() public readonly allowDidSovPrefix: boolean = false - public get didCommVersion(): DidCommMessageVersion { - return DidCommMessageVersion.V1 - } + @Exclude() + public readonly didCommVersion = DidCommMessageVersion.V1 + public static readonly didCommVersion = DidCommMessageVersion.V1 public serviceDecorator(): ServiceDecorator | undefined { return this.service diff --git a/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts b/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts index cc283bbb5e..c50ca596e2 100644 --- a/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts +++ b/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts @@ -4,6 +4,8 @@ import type { ServiceDecorator } from '../../../decorators/service/ServiceDecora import type { ThreadDecorator } from '../../../decorators/thread/ThreadDecorator' import type { PlaintextMessage } from '../../types' +import { Exclude } from 'class-transformer' + import { AriesFrameworkError } from '../../../error' import { JsonTransformer } from '../../../utils/JsonTransformer' import { DidCommMessageVersion } from '../../types' @@ -11,9 +13,9 @@ import { DidCommMessageVersion } from '../../types' import { DidCommV2BaseMessage } from './DidCommV2BaseMessage' export class DidCommV2Message extends DidCommV2BaseMessage implements AgentBaseMessage { - public get didCommVersion(): DidCommMessageVersion { - return DidCommMessageVersion.V2 - } + @Exclude() + public readonly didCommVersion = DidCommMessageVersion.V2 + public static readonly didCommVersion = DidCommMessageVersion.V2 public toJSON(): PlaintextMessage { return JsonTransformer.toJSON(this) as PlaintextDidCommV2Message From 7acfdacc468c769dba4fd7ebb1093eb6a39d417d Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Fri, 15 Sep 2023 17:41:39 -0300 Subject: [PATCH 4/8] refactor: V2ProblemReport Signed-off-by: Ariel Gentile --- .../core/src/modules/problem-reports/versions/v2/helpers.ts | 6 +++--- .../core/src/modules/problem-reports/versions/v2/index.ts | 2 +- .../{ProblemReportMessage.ts => V2ProblemReportMessage.ts} | 6 +++--- .../modules/problem-reports/versions/v2/messages/index.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) rename packages/core/src/modules/problem-reports/versions/v2/messages/{ProblemReportMessage.ts => V2ProblemReportMessage.ts} (86%) diff --git a/packages/core/src/modules/problem-reports/versions/v2/helpers.ts b/packages/core/src/modules/problem-reports/versions/v2/helpers.ts index 1440efd8fb..72d0a3ee6a 100644 --- a/packages/core/src/modules/problem-reports/versions/v2/helpers.ts +++ b/packages/core/src/modules/problem-reports/versions/v2/helpers.ts @@ -2,7 +2,7 @@ import type { PlaintextDidCommV2Message } from '../../../../didcomm' import { ProblemReportReason } from '../../models/ProblemReportReason' -import { ProblemReportMessage } from './messages' +import { V2ProblemReportMessage } from './messages' /** * Build the v2 problem report message to the recipient. @@ -12,11 +12,11 @@ import { ProblemReportMessage } from './messages' export const buildProblemReportV2Message = ( plaintextMessage: PlaintextDidCommV2Message, errorMessage: string -): ProblemReportMessage | undefined => { +): V2ProblemReportMessage | undefined => { // Cannot send problem report for message with unknown sender or recipient if (!plaintextMessage.from || !plaintextMessage.to?.length) return - return new ProblemReportMessage({ + return new V2ProblemReportMessage({ parentThreadId: plaintextMessage.id, from: plaintextMessage.to.length ? plaintextMessage.to[0] : undefined, to: plaintextMessage.from, diff --git a/packages/core/src/modules/problem-reports/versions/v2/index.ts b/packages/core/src/modules/problem-reports/versions/v2/index.ts index f9d434a2f5..efbb9da5dd 100644 --- a/packages/core/src/modules/problem-reports/versions/v2/index.ts +++ b/packages/core/src/modules/problem-reports/versions/v2/index.ts @@ -1,2 +1,2 @@ -export { ProblemReportMessage as V2ProblemReportMessage } from './messages' +export { V2ProblemReportMessage } from './messages' export { buildProblemReportV2Message } from './helpers' diff --git a/packages/core/src/modules/problem-reports/versions/v2/messages/ProblemReportMessage.ts b/packages/core/src/modules/problem-reports/versions/v2/messages/V2ProblemReportMessage.ts similarity index 86% rename from packages/core/src/modules/problem-reports/versions/v2/messages/ProblemReportMessage.ts rename to packages/core/src/modules/problem-reports/versions/v2/messages/V2ProblemReportMessage.ts index 299066f047..3469ff4b3f 100644 --- a/packages/core/src/modules/problem-reports/versions/v2/messages/ProblemReportMessage.ts +++ b/packages/core/src/modules/problem-reports/versions/v2/messages/V2ProblemReportMessage.ts @@ -27,7 +27,7 @@ export class ProblemReportBody { public args?: string[] } -export class ProblemReportMessage extends DidCommV2Message { +export class V2ProblemReportMessage extends DidCommV2Message { public constructor(options?: V2ProblemReportMessageOptions) { super(options) if (options) { @@ -36,8 +36,8 @@ export class ProblemReportMessage extends DidCommV2Message { } } - @IsValidMessageType(ProblemReportMessage.type) - public readonly type = ProblemReportMessage.type.messageTypeUri + @IsValidMessageType(V2ProblemReportMessage.type) + public readonly type = V2ProblemReportMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/report-problem/2.0/problem-report') @Expose({ name: 'body' }) diff --git a/packages/core/src/modules/problem-reports/versions/v2/messages/index.ts b/packages/core/src/modules/problem-reports/versions/v2/messages/index.ts index 57670e5421..0b4cfdcb5d 100644 --- a/packages/core/src/modules/problem-reports/versions/v2/messages/index.ts +++ b/packages/core/src/modules/problem-reports/versions/v2/messages/index.ts @@ -1 +1 @@ -export * from './ProblemReportMessage' +export * from './V2ProblemReportMessage' From af61ae7fcff0d1d331fec72f9797eeb9a4adc113 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Fri, 15 Sep 2023 20:46:22 -0300 Subject: [PATCH 5/8] feat: add transformers and test helpers Signed-off-by: Ariel Gentile --- packages/core/src/agent/MessageReceiver.ts | 2 +- packages/core/src/agent/MessageSender.ts | 7 ++- .../decorators/attachment/v2/V2Attachment.ts | 40 ++++++++++++++-- .../thread/ThreadDecoratorExtension.ts | 4 ++ packages/core/src/didcomm/index.ts | 1 + packages/core/src/didcomm/transformers.ts | 39 +++++++++++++++ packages/core/tests/helpers.ts | 48 ++++++++++++++----- 7 files changed, 119 insertions(+), 22 deletions(-) create mode 100644 packages/core/src/didcomm/transformers.ts diff --git a/packages/core/src/agent/MessageReceiver.ts b/packages/core/src/agent/MessageReceiver.ts index 5314d72ded..111b751dbc 100644 --- a/packages/core/src/agent/MessageReceiver.ts +++ b/packages/core/src/agent/MessageReceiver.ts @@ -7,7 +7,7 @@ import type { ConnectionRecord } from '../modules/connections' import type { InboundTransport } from '../transport' import { InjectionSymbols } from '../constants' -import { isPlaintextMessageV1, isPlaintextMessageV2 } from '../didcomm' +import { DidCommMessageVersion, isPlaintextMessageV1, isPlaintextMessageV2 } from '../didcomm' import { getPlaintextMessageType, isEncryptedMessage, isPlaintextMessage } from '../didcomm/helpers' import { AriesFrameworkError } from '../error' import { Logger } from '../logger' diff --git a/packages/core/src/agent/MessageSender.ts b/packages/core/src/agent/MessageSender.ts index 13433e1c0a..f81011ceda 100644 --- a/packages/core/src/agent/MessageSender.ts +++ b/packages/core/src/agent/MessageSender.ts @@ -3,7 +3,7 @@ import type { PackMessageParams } from './EnvelopeService' import type { AgentMessageSentEvent } from './Events' import type { TransportSession } from './TransportService' import type { AgentContext } from './context' -import type { DidCommV1Message, EncryptedMessage, OutboundPackage } from '../didcomm' +import type { EncryptedMessage, OutboundPackage } from '../didcomm' import type { ConnectionRecord } from '../modules/connections' import type { ResolvedDidCommService } from '../modules/didcomm' import type { DidDocument } from '../modules/dids' @@ -217,7 +217,7 @@ export class MessageSender { } ) { const { agentContext, connection, outOfBand } = outboundMessageContext - const message = outboundMessageContext.message as DidCommV1Message + const message = outboundMessageContext.message const errors: Error[] = [] @@ -320,8 +320,7 @@ export class MessageSender { // an incoming message. const [firstOurAuthenticationKey] = ourAuthenticationKeys // If the returnRoute is already set we won't override it. This allows to set the returnRoute manually if this is desired. - const shouldAddReturnRoute = - message.transport?.returnRoute === undefined && !this.transportService.hasInboundEndpoint(ourDidDocument) + const shouldAddReturnRoute = !message.hasAnyReturnRoute && !this.transportService.hasInboundEndpoint(ourDidDocument) // Loop through all available services and try to send the message for await (const service of services) { diff --git a/packages/core/src/decorators/attachment/v2/V2Attachment.ts b/packages/core/src/decorators/attachment/v2/V2Attachment.ts index e46aad845c..6b4e4aa89f 100644 --- a/packages/core/src/decorators/attachment/v2/V2Attachment.ts +++ b/packages/core/src/decorators/attachment/v2/V2Attachment.ts @@ -1,7 +1,8 @@ +import type { JwsDetachedFormat, JwsFlattenedDetachedFormat } from '../../../crypto/JwsTypes' + import { Expose, Type } from 'class-transformer' -import { IsBase64, IsInstance, IsMimeType, IsOptional, IsString, ValidateNested } from 'class-validator' +import { IsBase64, IsDate, IsInstance, IsInt, IsMimeType, IsOptional, IsString, ValidateNested } from 'class-validator' -import { Jws } from '../../../crypto/JwsTypes' import { AriesFrameworkError } from '../../../error' import { JsonEncoder } from '../../../utils/JsonEncoder' import { uuid } from '../../../utils/uuid' @@ -12,6 +13,8 @@ export interface V2AttachmentOptions { id?: string description?: string filename?: string + format?: string + lastmodTime?: Date mediaType?: string byteCount?: number data: V2AttachmentData @@ -21,7 +24,8 @@ export interface V2AttachmentDataOptions { base64?: string json?: Record links?: string[] - jws?: Jws + jws?: JwsDetachedFormat | JwsFlattenedDetachedFormat + hash?: string } /** @@ -52,7 +56,14 @@ export class V2AttachmentData { * A JSON Web Signature over the content of the attachment. Optional. */ @IsOptional() - public jws?: Jws + public jws?: JwsDetachedFormat | JwsFlattenedDetachedFormat + + /** + * The hash of the content encoded in multi-hash format. Used as an integrity check for the attachment, and MUST be used if the data is referenced via the links data attribute. + */ + @IsOptional() + @IsString() + public hash?: string public constructor(options: V2AttachmentDataOptions) { if (options) { @@ -60,6 +71,7 @@ export class V2AttachmentData { this.json = options.json this.links = options.links this.jws = options.jws + this.hash = options.hash } } } @@ -73,7 +85,10 @@ export class V2Attachment { if (options) { this.id = options.id ?? uuid() this.description = options.description + this.byteCount = options.byteCount this.filename = options.filename + this.format = options.format + this.lastmodTime = options.lastmodTime this.mediaType = options.mediaType this.data = options.data } @@ -110,6 +125,23 @@ export class V2Attachment { @IsMimeType() public mediaType?: string + /** + * A hint about when the content in this attachment was last modified. + */ + @Expose({ name: 'lastmod_time' }) + @Type(() => Date) + @IsOptional() + @IsDate() + public lastmodTime?: Date + + /** + * Optional, and mostly relevant when content is included by reference instead of by value. Lets the receiver guess how expensive it will be, in time, bandwidth, and storage, to fully fetch the attachment. + */ + @Expose({ name: 'byte_count' }) + @IsOptional() + @IsInt() + public byteCount?: number + @Type(() => V2AttachmentData) @ValidateNested() @IsInstance(V2AttachmentData) diff --git a/packages/core/src/decorators/thread/ThreadDecoratorExtension.ts b/packages/core/src/decorators/thread/ThreadDecoratorExtension.ts index 5141e5a051..a7cad5f7a0 100644 --- a/packages/core/src/decorators/thread/ThreadDecoratorExtension.ts +++ b/packages/core/src/decorators/thread/ThreadDecoratorExtension.ts @@ -21,6 +21,10 @@ export function ThreadDecorated(Base: return this.thread?.threadId ?? this.id } + public get parentThreadId(): string | undefined { + return this.thread?.parentThreadId + } + public setThread(options: Partial) { this.thread = new ThreadDecorator(options) } diff --git a/packages/core/src/didcomm/index.ts b/packages/core/src/didcomm/index.ts index 1a0eb9c7b7..87485fe332 100644 --- a/packages/core/src/didcomm/index.ts +++ b/packages/core/src/didcomm/index.ts @@ -6,6 +6,7 @@ import type { Constructor } from '../utils/mixins' export * from './versions/v1' export * from './versions/v2' +export * from './transformers' export * from './types' export * from './helpers' export * from './JweEnvelope' diff --git a/packages/core/src/didcomm/transformers.ts b/packages/core/src/didcomm/transformers.ts new file mode 100644 index 0000000000..7e29a728af --- /dev/null +++ b/packages/core/src/didcomm/transformers.ts @@ -0,0 +1,39 @@ +import { Attachment, AttachmentData, V2Attachment, V2AttachmentData } from '../decorators/attachment' + +export function toV2Attachment(v1Attachment: Attachment): V2Attachment { + const { id, description, byteCount, filename, lastmodTime, mimeType, data } = v1Attachment + return new V2Attachment({ + id, + description, + byteCount, + filename, + lastmodTime, + mediaType: mimeType, + data: new V2AttachmentData({ + base64: data.base64, + json: data.json, + jws: data.jws, + links: data.links, + hash: data.sha256, + }), + }) +} + +export function toV1Attachment(v2Attachment: V2Attachment): Attachment { + const { id, description, byteCount, filename, lastmodTime, mediaType, data } = v2Attachment + return new Attachment({ + id, + description, + byteCount, + filename, + lastmodTime, + mimeType: mediaType, + data: new AttachmentData({ + base64: data.base64, + json: data.json, + jws: data.jws, + links: data.links, + sha256: data.hash, + }), + }) +} diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 4fe9732007..2ccd901218 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -31,6 +31,7 @@ import { catchError, filter, map, take, timeout } from 'rxjs/operators' import { agentDependencies, IndySdkPostgresWalletScheme } from '../../node/src' import { + OutOfBandVersion, OutOfBandDidCommService, ConnectionsModule, ConnectionEventTypes, @@ -217,7 +218,7 @@ const isCredentialStateChangedEvent = (e: BaseEvent): e is CredentialStateChange const isConnectionStateChangedEvent = (e: BaseEvent): e is ConnectionStateChangedEvent => e.type === ConnectionEventTypes.ConnectionStateChanged const isTrustPingReceivedEvent = (e: BaseEvent): e is TrustPingReceivedEvent => - e.type === TrustPingEventTypes.TrustPingReceivedEvent + e.type === TrustPingEventTypes.TrustPingReceivedEvent || e.type === TrustPingEventTypes.V2TrustPingReceivedEvent const isTrustPingResponseReceivedEvent = (e: BaseEvent): e is TrustPingResponseReceivedEvent => e.type === TrustPingEventTypes.TrustPingResponseReceivedEvent @@ -267,11 +268,16 @@ export function waitForProofExchangeRecordSubject( export async function waitForTrustPingReceivedEvent( agent: Agent, options: { + protocolVersion?: 'v1' | 'v2' threadId?: string timeoutMs?: number } ) { - const observable = agent.events.observable(TrustPingEventTypes.TrustPingReceivedEvent) + const observable = agent.events.observable( + options.protocolVersion === 'v2' + ? TrustPingEventTypes.V2TrustPingReceivedEvent + : TrustPingEventTypes.TrustPingReceivedEvent + ) return waitForTrustPingReceivedEventSubject(observable, options) } @@ -532,20 +538,36 @@ export function getMockOutOfBand({ return outOfBandRecord } -export async function makeConnection(agentA: Agent, agentB: Agent) { - const agentAOutOfBand = await agentA.oob.createInvitation({ - handshakeProtocols: [HandshakeProtocol.Connections], - }) +export async function makeConnection(agentA: Agent, agentB: Agent, version?: OutOfBandVersion) { + if (version === OutOfBandVersion.V2) { + const agentAOutOfBand = await agentA.oob.createInvitation({ + version, + }) - let { connectionRecord: agentBConnection } = await agentB.oob.receiveInvitation( - agentAOutOfBand.getOutOfBandInvitation() - ) + const { connectionRecord: agentBConnection } = await agentB.oob.receiveInvitation( + agentAOutOfBand.v2OutOfBandInvitation! + ) + if (!agentBConnection) throw new Error('No connection for receiver') + await agentB.connections.sendPing(agentBConnection.id, {}) + await waitForTrustPingReceivedEvent(agentA, { protocolVersion: 'v2', timeoutMs: 4000 }) + const [agentAConnection] = await agentA.connections.findAllByOutOfBandId(agentAOutOfBand.id) + if (!agentAConnection) throw new Error('No connection for inviter') + return [agentAConnection, agentBConnection] + } else { + const agentAOutOfBand = await agentA.oob.createInvitation({ + handshakeProtocols: [HandshakeProtocol.Connections], + }) + + let { connectionRecord: agentBConnection } = await agentB.oob.receiveInvitation( + agentAOutOfBand.getOutOfBandInvitation() + ) - agentBConnection = await agentB.connections.returnWhenIsConnected(agentBConnection!.id) - let [agentAConnection] = await agentA.connections.findAllByOutOfBandId(agentAOutOfBand.id) - agentAConnection = await agentA.connections.returnWhenIsConnected(agentAConnection!.id) + agentBConnection = await agentB.connections.returnWhenIsConnected(agentBConnection!.id) + let [agentAConnection] = await agentA.connections.findAllByOutOfBandId(agentAOutOfBand.id) + agentAConnection = await agentA.connections.returnWhenIsConnected(agentAConnection!.id) - return [agentAConnection, agentBConnection] + return [agentAConnection, agentBConnection] + } } /** From 49ad374d3c32e0f467179da2973e6a33e90ee2eb Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Sat, 16 Sep 2023 14:15:29 -0300 Subject: [PATCH 6/8] feat: do didcomm version checking according to connection Signed-off-by: Ariel Gentile --- packages/core/src/agent/MessageReceiver.ts | 4 +++- .../src/agent/getOutboundMessageContext.ts | 20 +++++++++++++++---- .../didcomm/versions/v2/DidCommV2Message.ts | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/core/src/agent/MessageReceiver.ts b/packages/core/src/agent/MessageReceiver.ts index 111b751dbc..68732713d5 100644 --- a/packages/core/src/agent/MessageReceiver.ts +++ b/packages/core/src/agent/MessageReceiver.ts @@ -153,7 +153,9 @@ export class MessageReceiver { const { plaintextMessage, senderKey, recipientKey } = unpackedMessage this.logger.info( - `Received message with type '${plaintextMessage['@type']}', recipient key ${recipientKey?.fingerprint} and sender key ${senderKey?.fingerprint}`, + `Received message with type '${plaintextMessage['@type'] ?? plaintextMessage['type']}', recipient key ${ + recipientKey?.fingerprint + } and sender key ${senderKey?.fingerprint}`, plaintextMessage ) diff --git a/packages/core/src/agent/getOutboundMessageContext.ts b/packages/core/src/agent/getOutboundMessageContext.ts index d6f4101b44..40e4118451 100644 --- a/packages/core/src/agent/getOutboundMessageContext.ts +++ b/packages/core/src/agent/getOutboundMessageContext.ts @@ -7,12 +7,14 @@ import type { BaseRecordAny } from '../storage/BaseRecord' import { Key } from '../crypto' import { ServiceDecorator } from '../decorators/service/ServiceDecorator' -import { DidCommV1Message, DidCommV2Message } from '../didcomm' +import { DidCommMessageVersion, DidCommV1Message, DidCommV2Message } from '../didcomm' import { AriesFrameworkError } from '../error' -import { OutOfBandService, OutOfBandRole, OutOfBandRepository } from '../modules/oob' +import { OutOfBandRole } from '../modules/oob/domain' +import { OutOfBandService } from '../modules/oob/protocols' +import { OutOfBandRepository } from '../modules/oob/repository' import { OutOfBandRecordMetadataKeys } from '../modules/oob/repository/outOfBandRecordMetadataTypes' -import { RoutingService } from '../modules/routing' -import { DidCommMessageRepository, DidCommMessageRole } from '../storage' +import { RoutingService } from '../modules/routing/services' +import { DidCommMessageRepository, DidCommMessageRole } from '../storage/didcomm' import { uuid } from '../utils/uuid' import { OutboundMessageContext } from './models' @@ -50,6 +52,16 @@ export async function getOutboundMessageContext( `Creating outbound message context for message ${message.id} with connection ${connectionRecord.id}` ) + // Check that message DIDComm version matches connection + if ( + (connectionRecord.isDidCommV1Connection && message.didCommVersion !== DidCommMessageVersion.V1) || + (connectionRecord.isDidCommV2Connection && message.didCommVersion !== DidCommMessageVersion.V2) + ) { + throw new AriesFrameworkError( + `Message DIDComm version ${message.didCommVersion} does not match connection ${connectionRecord.id}` + ) + } + // Attach 'from' and 'to' fields according to connection record (unless they are previously defined) if (message instanceof DidCommV2Message) { message.from = message.from ?? connectionRecord.did diff --git a/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts b/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts index c50ca596e2..1b0959a0f7 100644 --- a/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts +++ b/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts @@ -25,8 +25,8 @@ export class DidCommV2Message extends DidCommV2BaseMessage implements AgentBaseM return undefined } - public get threadId(): string | undefined { - return this.thid + public get threadId(): string { + return this.thid ?? this.id } public setThread(options: Partial) { From ef0b93459e52854f8cc7a6cbdaf5ce310f16f60f Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Sat, 16 Sep 2023 15:05:35 -0300 Subject: [PATCH 7/8] feat: support Discover Features V2 over DIDComm V2 Signed-off-by: Ariel Gentile --- .../discover-features/DiscoverFeaturesApi.ts | 24 +- .../DiscoverFeaturesEvents.ts | 6 +- .../DiscoverFeaturesServiceOptions.ts | 7 +- .../v2-discover-features.e2e.test.ts | 385 +++++++++--------- .../protocol/v2/V2DiscoverFeaturesService.ts | 34 +- .../handlers/V2DisclosuresMessageHandler.ts | 3 +- .../v2/handlers/V2QueriesMessageHandler.ts | 13 +- .../messages/V2DisclosuresDidCommV2Message.ts | 51 +++ .../v2/messages/V2DisclosuresMessage.ts | 8 +- .../messages/V2DisclosuresMessageOptions.ts | 7 + .../v2/messages/V2QueriesDidCommV2Message.ts | 46 +++ .../protocol/v2/messages/V2QueriesMessage.ts | 10 +- .../v2/messages/V2QueriesMessageOptions.ts | 6 + .../services/DiscoverFeaturesService.ts | 14 +- 14 files changed, 369 insertions(+), 245 deletions(-) create mode 100644 packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresDidCommV2Message.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresMessageOptions.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesDidCommV2Message.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesMessageOptions.ts diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts index 3d074f9d18..9ba149b7a4 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts @@ -13,7 +13,7 @@ import { catchError, filter, map, takeUntil, timeout } from 'rxjs/operators' import { AgentContext } from '../../agent' import { EventEmitter } from '../../agent/EventEmitter' import { MessageSender } from '../../agent/MessageSender' -import { OutboundMessageContext } from '../../agent/models' +import { getOutboundMessageContext } from '../../agent/getOutboundMessageContext' import { InjectionSymbols } from '../../constants' import { AriesFrameworkError } from '../../error' import { inject, injectable } from '../../plugins' @@ -96,17 +96,15 @@ export class DiscoverFeaturesApi< public async queryFeatures(options: QueryFeaturesOptions) { const service = this.getService(options.protocolVersion) - const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, options.connectionId) - const { message: queryMessage } = await service.createQuery({ + const { message } = await service.createQuery({ + connectionRecord, queries: options.queries, comment: options.comment, }) - const outboundMessageContext = new OutboundMessageContext(queryMessage, { - agentContext: this.agentContext, - connection, - }) + const outboundMessageContext = await getOutboundMessageContext(this.agentContext, { message, connectionRecord }) const replaySubject = new ReplaySubject(1) if (options.awaitDisclosures) { @@ -117,7 +115,7 @@ export class DiscoverFeaturesApi< // Stop when the agent shuts down takeUntil(this.stop$), // filter by connection id - filter((e) => e.payload.connection?.id === connection.id), + filter((e) => e.payload.connection?.id === connectionRecord.id), // Return disclosures map((e) => e.payload.disclosures), // If we don't have an answer in timeoutMs miliseconds (no response, not supported, etc...) error @@ -148,16 +146,14 @@ export class DiscoverFeaturesApi< public async discloseFeatures(options: DiscloseFeaturesOptions) { const service = this.getService(options.protocolVersion) - const connection = await this.connectionService.getById(this.agentContext, options.connectionId) - const { message: disclosuresMessage } = await service.createDisclosure({ + const connectionRecord = await this.connectionService.getById(this.agentContext, options.connectionId) + const { message } = await service.createDisclosure({ + connectionRecord, disclosureQueries: options.disclosureQueries, threadId: options.threadId, }) - const outboundMessageContext = new OutboundMessageContext(disclosuresMessage, { - agentContext: this.agentContext, - connection, - }) + const outboundMessageContext = await getOutboundMessageContext(this.agentContext, { message, connectionRecord }) await this.messageSender.sendMessage(outboundMessageContext) } } diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesEvents.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesEvents.ts index 36cea8a6a2..a80f6f0a04 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesEvents.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesEvents.ts @@ -1,6 +1,6 @@ import type { BaseEvent } from '../../agent/Events' import type { Feature, FeatureQueryOptions } from '../../agent/models' -import type { DidCommV1Message } from '../../didcomm' +import type { DidCommV1Message, DidCommV2Message } from '../../didcomm' import type { ConnectionRecord } from '../connections' export enum DiscoverFeaturesEventTypes { @@ -11,7 +11,7 @@ export enum DiscoverFeaturesEventTypes { export interface DiscoverFeaturesQueryReceivedEvent extends BaseEvent { type: typeof DiscoverFeaturesEventTypes.QueryReceived payload: { - message: DidCommV1Message + message: DidCommV1Message | DidCommV2Message queries: FeatureQueryOptions[] protocolVersion: string connection: ConnectionRecord @@ -22,7 +22,7 @@ export interface DiscoverFeaturesQueryReceivedEvent extends BaseEvent { export interface DiscoverFeaturesDisclosureReceivedEvent extends BaseEvent { type: typeof DiscoverFeaturesEventTypes.DisclosureReceived payload: { - message: DidCommV1Message + message: DidCommV1Message | DidCommV2Message disclosures: Feature[] protocolVersion: string connection: ConnectionRecord diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesServiceOptions.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesServiceOptions.ts index 04c9e651d8..8d0f477982 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesServiceOptions.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesServiceOptions.ts @@ -1,16 +1,19 @@ +import type { AgentBaseMessage } from '../../agent/AgentBaseMessage' import type { FeatureQueryOptions } from '../../agent/models' -import type { DidCommV1Message } from '../../didcomm' +import type { ConnectionRecord } from '../connections' export interface CreateQueryOptions { + connectionRecord?: ConnectionRecord queries: FeatureQueryOptions[] comment?: string } export interface CreateDisclosureOptions { + connectionRecord?: ConnectionRecord disclosureQueries: FeatureQueryOptions[] threadId?: string } -export interface DiscoverFeaturesProtocolMsgReturnType { +export interface DiscoverFeaturesProtocolMsgReturnType { message: MessageType } diff --git a/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts b/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts index f5a4b9f782..231af67e90 100644 --- a/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts +++ b/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts @@ -6,11 +6,13 @@ import type { import { ReplaySubject } from 'rxjs' -import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' +import { getAskarAnonCredsIndyModules } from '../../../../../anoncreds/tests/legacyAnonCredsSetup' import { setupSubjectTransports } from '../../../../tests' import { getAgentOptions, makeConnection } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { GoalCode, Feature } from '../../../agent/models' +import { DidCommMessageVersion } from '../../../didcomm' +import { OutOfBandVersion } from '../../oob' import { DiscoverFeaturesEventTypes } from '../DiscoverFeaturesEvents' import { waitForDisclosureSubject, waitForQuerySubject } from './helpers' @@ -20,7 +22,7 @@ const faberAgentOptions = getAgentOptions( { endpoints: ['rxjs:faber'], }, - getIndySdkModules() + getAskarAnonCredsIndyModules() ) const aliceAgentOptions = getAgentOptions( @@ -28,206 +30,213 @@ const aliceAgentOptions = getAgentOptions( { endpoints: ['rxjs:alice'], }, - getIndySdkModules() + getAskarAnonCredsIndyModules() ) -describe('v2 discover features', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let faberConnection: ConnectionRecord - - beforeAll(async () => { - faberAgent = new Agent(faberAgentOptions) - aliceAgent = new Agent(aliceAgentOptions) - setupSubjectTransports([faberAgent, aliceAgent]) - - await faberAgent.initialize() - await aliceAgent.initialize() - ;[faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) - }) - - afterAll(async () => { - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test('Faber asks Alice for issue credential protocol support', async () => { - const faberReplay = new ReplaySubject() - const aliceReplay = new ReplaySubject() - - faberAgent.discovery.config.autoAcceptQueries - faberAgent.events - .observable(DiscoverFeaturesEventTypes.DisclosureReceived) - .subscribe(faberReplay) - aliceAgent.events - .observable(DiscoverFeaturesEventTypes.QueryReceived) - .subscribe(aliceReplay) - - await faberAgent.discovery.queryFeatures({ - connectionId: faberConnection.id, - protocolVersion: 'v2', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], +describe.each([[DidCommMessageVersion.V1], [DidCommMessageVersion.V2]])( + `v2 discover features - %s`, + (didcommVersion) => { + let faberAgent: Agent + let aliceAgent: Agent + let aliceConnection: ConnectionRecord + let faberConnection: ConnectionRecord + + beforeAll(async () => { + faberAgent = new Agent(faberAgentOptions) + aliceAgent = new Agent(aliceAgentOptions) + setupSubjectTransports([faberAgent, aliceAgent]) + + await faberAgent.initialize() + await aliceAgent.initialize() + ;[faberConnection, aliceConnection] = await makeConnection( + faberAgent, + aliceAgent, + didcommVersion === DidCommMessageVersion.V2 ? OutOfBandVersion.V2 : undefined + ) }) - const query = await waitForQuerySubject(aliceReplay, { timeoutMs: 10000 }) - - expect(query).toMatchObject({ - protocolVersion: 'v2', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() }) - const disclosure = await waitForDisclosureSubject(faberReplay, { timeoutMs: 10000 }) - - expect(disclosure).toMatchObject({ - protocolVersion: 'v2', - disclosures: [ - { type: 'protocol', id: 'https://didcomm.org/revocation_notification/1.0', roles: ['holder'] }, - { type: 'protocol', id: 'https://didcomm.org/revocation_notification/2.0', roles: ['holder'] }, - ], + test('Faber asks Alice for issue credential protocol support', async () => { + const faberReplay = new ReplaySubject() + const aliceReplay = new ReplaySubject() + + faberAgent.discovery.config.autoAcceptQueries + faberAgent.events + .observable(DiscoverFeaturesEventTypes.DisclosureReceived) + .subscribe(faberReplay) + aliceAgent.events + .observable(DiscoverFeaturesEventTypes.QueryReceived) + .subscribe(aliceReplay) + + await faberAgent.discovery.queryFeatures({ + connectionId: faberConnection.id, + protocolVersion: 'v2', + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], + }) + + const query = await waitForQuerySubject(aliceReplay, { timeoutMs: 10000 }) + + expect(query).toMatchObject({ + protocolVersion: 'v2', + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], + }) + + const disclosure = await waitForDisclosureSubject(faberReplay, { timeoutMs: 10000 }) + + expect(disclosure).toMatchObject({ + protocolVersion: 'v2', + disclosures: [ + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/1.0', roles: ['holder'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/2.0', roles: ['holder'] }, + ], + }) }) - }) - - test('Faber defines a supported goal code and Alice queries', async () => { - const faberReplay = new ReplaySubject() - const aliceReplay = new ReplaySubject() - - aliceAgent.events - .observable(DiscoverFeaturesEventTypes.DisclosureReceived) - .subscribe(aliceReplay) - faberAgent.events - .observable(DiscoverFeaturesEventTypes.QueryReceived) - .subscribe(faberReplay) - - // Register some goal codes - faberAgent.features.register(new GoalCode({ id: 'faber.vc.issuance' }), new GoalCode({ id: 'faber.vc.query' })) - - await aliceAgent.discovery.queryFeatures({ - connectionId: aliceConnection.id, - protocolVersion: 'v2', - queries: [{ featureType: 'goal-code', match: '*' }], - }) - - const query = await waitForQuerySubject(faberReplay, { timeoutMs: 10000 }) - expect(query).toMatchObject({ - protocolVersion: 'v2', - queries: [{ featureType: 'goal-code', match: '*' }], + test('Faber defines a supported goal code and Alice queries', async () => { + const faberReplay = new ReplaySubject() + const aliceReplay = new ReplaySubject() + + aliceAgent.events + .observable(DiscoverFeaturesEventTypes.DisclosureReceived) + .subscribe(aliceReplay) + faberAgent.events + .observable(DiscoverFeaturesEventTypes.QueryReceived) + .subscribe(faberReplay) + + // Register some goal codes + faberAgent.features.register(new GoalCode({ id: 'faber.vc.issuance' }), new GoalCode({ id: 'faber.vc.query' })) + + await aliceAgent.discovery.queryFeatures({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + queries: [{ featureType: 'goal-code', match: '*' }], + }) + + const query = await waitForQuerySubject(faberReplay, { timeoutMs: 10000 }) + + expect(query).toMatchObject({ + protocolVersion: 'v2', + queries: [{ featureType: 'goal-code', match: '*' }], + }) + + const disclosure = await waitForDisclosureSubject(aliceReplay, { timeoutMs: 10000 }) + + expect(disclosure).toMatchObject({ + protocolVersion: 'v2', + disclosures: [ + { type: 'goal-code', id: 'faber.vc.issuance' }, + { type: 'goal-code', id: 'faber.vc.query' }, + ], + }) }) - const disclosure = await waitForDisclosureSubject(aliceReplay, { timeoutMs: 10000 }) - - expect(disclosure).toMatchObject({ - protocolVersion: 'v2', - disclosures: [ - { type: 'goal-code', id: 'faber.vc.issuance' }, - { type: 'goal-code', id: 'faber.vc.query' }, - ], - }) - }) - - test('Faber defines a custom feature and Alice queries', async () => { - const faberReplay = new ReplaySubject() - const aliceReplay = new ReplaySubject() - - aliceAgent.events - .observable(DiscoverFeaturesEventTypes.DisclosureReceived) - .subscribe(aliceReplay) - faberAgent.events - .observable(DiscoverFeaturesEventTypes.QueryReceived) - .subscribe(faberReplay) - - // Define a custom feature type - class GenericFeature extends Feature { - public 'generic-field'!: string - - public constructor(options: { id: string; genericField: string }) { - super({ id: options.id, type: 'generic' }) - this['generic-field'] = options.genericField + test('Faber defines a custom feature and Alice queries', async () => { + const faberReplay = new ReplaySubject() + const aliceReplay = new ReplaySubject() + + aliceAgent.events + .observable(DiscoverFeaturesEventTypes.DisclosureReceived) + .subscribe(aliceReplay) + faberAgent.events + .observable(DiscoverFeaturesEventTypes.QueryReceived) + .subscribe(faberReplay) + + // Define a custom feature type + class GenericFeature extends Feature { + public 'generic-field'!: string + + public constructor(options: { id: string; genericField: string }) { + super({ id: options.id, type: 'generic' }) + this['generic-field'] = options.genericField + } } - } - - // Register a custom feature - faberAgent.features.register(new GenericFeature({ id: 'custom-feature', genericField: 'custom-field' })) - - await aliceAgent.discovery.queryFeatures({ - connectionId: aliceConnection.id, - protocolVersion: 'v2', - queries: [{ featureType: 'generic', match: 'custom-feature' }], - }) - - const query = await waitForQuerySubject(faberReplay, { timeoutMs: 10000 }) - - expect(query).toMatchObject({ - protocolVersion: 'v2', - queries: [{ featureType: 'generic', match: 'custom-feature' }], - }) - const disclosure = await waitForDisclosureSubject(aliceReplay, { timeoutMs: 10000 }) - - expect(disclosure).toMatchObject({ - protocolVersion: 'v2', - disclosures: [ - { - type: 'generic', - id: 'custom-feature', - 'generic-field': 'custom-field', - }, - ], + // Register a custom feature + faberAgent.features.register(new GenericFeature({ id: 'custom-feature', genericField: 'custom-field' })) + + await aliceAgent.discovery.queryFeatures({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + queries: [{ featureType: 'generic', match: 'custom-feature' }], + }) + + const query = await waitForQuerySubject(faberReplay, { timeoutMs: 10000 }) + + expect(query).toMatchObject({ + protocolVersion: 'v2', + queries: [{ featureType: 'generic', match: 'custom-feature' }], + }) + + const disclosure = await waitForDisclosureSubject(aliceReplay, { timeoutMs: 10000 }) + + expect(disclosure).toMatchObject({ + protocolVersion: 'v2', + disclosures: [ + { + type: 'generic', + id: 'custom-feature', + 'generic-field': 'custom-field', + }, + ], + }) }) - }) - - test('Faber proactively sends a set of features to Alice', async () => { - const faberReplay = new ReplaySubject() - const aliceReplay = new ReplaySubject() - - aliceAgent.events - .observable(DiscoverFeaturesEventTypes.DisclosureReceived) - .subscribe(aliceReplay) - faberAgent.events - .observable(DiscoverFeaturesEventTypes.QueryReceived) - .subscribe(faberReplay) - - // Register a custom feature - faberAgent.features.register( - new Feature({ id: 'AIP2.0', type: 'aip' }), - new Feature({ id: 'AIP2.0/INDYCRED', type: 'aip' }), - new Feature({ id: 'AIP2.0/MEDIATE', type: 'aip' }) - ) - - await faberAgent.discovery.discloseFeatures({ - connectionId: faberConnection.id, - protocolVersion: 'v2', - disclosureQueries: [{ featureType: 'aip', match: '*' }], - }) - - const disclosure = await waitForDisclosureSubject(aliceReplay, { timeoutMs: 10000 }) - expect(disclosure).toMatchObject({ - protocolVersion: 'v2', - disclosures: [ - { type: 'aip', id: 'AIP2.0' }, - { type: 'aip', id: 'AIP2.0/INDYCRED' }, - { type: 'aip', id: 'AIP2.0/MEDIATE' }, - ], - }) - }) - - test('Faber asks Alice for issue credential protocol support synchronously', async () => { - const matchingFeatures = await faberAgent.discovery.queryFeatures({ - connectionId: faberConnection.id, - protocolVersion: 'v2', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], - awaitDisclosures: true, + test('Faber proactively sends a set of features to Alice', async () => { + const faberReplay = new ReplaySubject() + const aliceReplay = new ReplaySubject() + + aliceAgent.events + .observable(DiscoverFeaturesEventTypes.DisclosureReceived) + .subscribe(aliceReplay) + faberAgent.events + .observable(DiscoverFeaturesEventTypes.QueryReceived) + .subscribe(faberReplay) + + // Register a custom feature + faberAgent.features.register( + new Feature({ id: 'AIP2.0', type: 'aip' }), + new Feature({ id: 'AIP2.0/INDYCRED', type: 'aip' }), + new Feature({ id: 'AIP2.0/MEDIATE', type: 'aip' }) + ) + + await faberAgent.discovery.discloseFeatures({ + connectionId: faberConnection.id, + protocolVersion: 'v2', + disclosureQueries: [{ featureType: 'aip', match: '*' }], + }) + + const disclosure = await waitForDisclosureSubject(aliceReplay, { timeoutMs: 10000 }) + + expect(disclosure).toMatchObject({ + protocolVersion: 'v2', + disclosures: [ + { type: 'aip', id: 'AIP2.0' }, + { type: 'aip', id: 'AIP2.0/INDYCRED' }, + { type: 'aip', id: 'AIP2.0/MEDIATE' }, + ], + }) }) - expect(matchingFeatures).toMatchObject({ - features: [ - { type: 'protocol', id: 'https://didcomm.org/revocation_notification/1.0', roles: ['holder'] }, - { type: 'protocol', id: 'https://didcomm.org/revocation_notification/2.0', roles: ['holder'] }, - ], + test('Faber asks Alice for issue credential protocol support synchronously', async () => { + const matchingFeatures = await faberAgent.discovery.queryFeatures({ + connectionId: faberConnection.id, + protocolVersion: 'v2', + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], + awaitDisclosures: true, + }) + + expect(matchingFeatures).toMatchObject({ + features: [ + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/1.0', roles: ['holder'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/2.0', roles: ['holder'] }, + ], + }) }) - }) -}) + } +) diff --git a/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts b/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts index 0196a351c4..4755252443 100644 --- a/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts +++ b/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts @@ -21,6 +21,8 @@ import { DiscoverFeaturesService } from '../../services' import { V2DisclosuresMessageHandler, V2QueriesMessageHandler } from './handlers' import { V2QueriesMessage, V2DisclosuresMessage } from './messages' +import { V2DisclosuresDidCommV2Message } from './messages/V2DisclosuresDidCommV2Message' +import { V2QueriesDidCommV2Message } from './messages/V2QueriesDidCommV2Message' @injectable() export class V2DiscoverFeaturesService extends DiscoverFeaturesService { @@ -47,15 +49,17 @@ export class V2DiscoverFeaturesService extends DiscoverFeaturesService { public async createQuery( options: CreateQueryOptions - ): Promise> { - const queryMessage = new V2QueriesMessage({ queries: options.queries }) + ): Promise> { + const queryMessage = options.connectionRecord?.isDidCommV2Connection + ? new V2QueriesDidCommV2Message({ queries: options.queries }) + : new V2QueriesMessage({ queries: options.queries }) return { message: queryMessage } } public async processQuery( - messageContext: InboundMessageContext - ): Promise | void> { + messageContext: InboundMessageContext + ): Promise | void> { const { queries, threadId } = messageContext.message const connection = messageContext.assertReadyConnection() @@ -71,10 +75,11 @@ export class V2DiscoverFeaturesService extends DiscoverFeaturesService { }, }) - // Process query and send responde automatically if configured to do so, otherwise + // Process query and send respose automatically if configured to do so, otherwise // just send the event and let controller decide if (this.discoverFeaturesModuleConfig.autoAcceptQueries) { return await this.createDisclosure({ + connectionRecord: connection, threadId, disclosureQueries: queries, }) @@ -83,18 +88,25 @@ export class V2DiscoverFeaturesService extends DiscoverFeaturesService { public async createDisclosure( options: CreateDisclosureOptions - ): Promise> { + ): Promise> { const matches = this.featureRegistry.query(...options.disclosureQueries) - const discloseMessage = new V2DisclosuresMessage({ - threadId: options.threadId, - features: matches, - }) + const discloseMessage = options.connectionRecord?.isDidCommV2Connection + ? new V2DisclosuresDidCommV2Message({ + threadId: options.threadId, + features: matches, + }) + : new V2DisclosuresMessage({ + threadId: options.threadId, + features: matches, + }) return { message: discloseMessage } } - public async processDisclosure(messageContext: InboundMessageContext): Promise { + public async processDisclosure( + messageContext: InboundMessageContext + ): Promise { const { disclosures, threadId } = messageContext.message const connection = messageContext.assertReadyConnection() diff --git a/packages/core/src/modules/discover-features/protocol/v2/handlers/V2DisclosuresMessageHandler.ts b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2DisclosuresMessageHandler.ts index 1691e7a5a8..4524ffaa1d 100644 --- a/packages/core/src/modules/discover-features/protocol/v2/handlers/V2DisclosuresMessageHandler.ts +++ b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2DisclosuresMessageHandler.ts @@ -2,10 +2,11 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../. import type { V2DiscoverFeaturesService } from '../V2DiscoverFeaturesService' import { V2DisclosuresMessage } from '../messages' +import { V2DisclosuresDidCommV2Message } from '../messages/V2DisclosuresDidCommV2Message' export class V2DisclosuresMessageHandler implements MessageHandler { private discoverFeaturesService: V2DiscoverFeaturesService - public supportedMessages = [V2DisclosuresMessage] + public supportedMessages = [V2DisclosuresMessage, V2DisclosuresDidCommV2Message] public constructor(discoverFeaturesService: V2DiscoverFeaturesService) { this.discoverFeaturesService = discoverFeaturesService diff --git a/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts index 45798397be..7269cd9f48 100644 --- a/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts +++ b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts @@ -1,26 +1,27 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V2DiscoverFeaturesService } from '../V2DiscoverFeaturesService' -import { OutboundMessageContext } from '../../../../../agent/models' +import { getOutboundMessageContext } from '../../../../../agent/getOutboundMessageContext' import { V2QueriesMessage } from '../messages' +import { V2QueriesDidCommV2Message } from '../messages/V2QueriesDidCommV2Message' export class V2QueriesMessageHandler implements MessageHandler { private discoverFeaturesService: V2DiscoverFeaturesService - public supportedMessages = [V2QueriesMessage] + public supportedMessages = [V2QueriesMessage, V2QueriesDidCommV2Message] public constructor(discoverFeaturesService: V2DiscoverFeaturesService) { this.discoverFeaturesService = discoverFeaturesService } public async handle(inboundMessage: MessageHandlerInboundMessage) { - const connection = inboundMessage.assertReadyConnection() + const connectionRecord = inboundMessage.assertReadyConnection() const discloseMessage = await this.discoverFeaturesService.processQuery(inboundMessage) if (discloseMessage) { - return new OutboundMessageContext(discloseMessage.message, { - agentContext: inboundMessage.agentContext, - connection, + return getOutboundMessageContext(inboundMessage.agentContext, { + message: discloseMessage.message, + connectionRecord, }) } } diff --git a/packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresDidCommV2Message.ts b/packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresDidCommV2Message.ts new file mode 100644 index 0000000000..5c2069d342 --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresDidCommV2Message.ts @@ -0,0 +1,51 @@ +import type { V2DisclosuresMessageOptions } from './V2DisclosuresMessageOptions' + +import { Type } from 'class-transformer' +import { IsInstance, IsObject, ValidateNested } from 'class-validator' + +import { Feature } from '../../../../../agent/models' +import { DidCommV2Message } from '../../../../../didcomm' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' + +class V2DisclosuresMessageBody { + public constructor(options: { disclosures: Feature[] }) { + if (options) { + this.disclosures = options.disclosures + } + } + @IsInstance(Feature, { each: true }) + @Type(() => Feature) + public disclosures!: Feature[] +} + +export class V2DisclosuresDidCommV2Message extends DidCommV2Message { + public readonly allowDidSovPrefix = false + + public constructor(options: V2DisclosuresMessageOptions) { + super() + + if (options) { + this.id = options.id ?? this.generateId() + this.body = new V2DisclosuresMessageBody({ disclosures: options.features ?? [] }) + + if (options.threadId) { + this.setThread({ + threadId: options.threadId, + }) + } + } + } + + @IsValidMessageType(V2DisclosuresDidCommV2Message.type) + public readonly type = V2DisclosuresDidCommV2Message.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/discover-features/2.0/disclosures') + + @IsObject() + @ValidateNested() + @Type(() => V2DisclosuresMessageBody) + public body!: V2DisclosuresMessageBody + + public get disclosures() { + return this.body.disclosures + } +} diff --git a/packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresMessage.ts b/packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresMessage.ts index 4d3c1fa87f..438e930817 100644 --- a/packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresMessage.ts +++ b/packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresMessage.ts @@ -1,3 +1,5 @@ +import type { V2DisclosuresMessageOptions } from './V2DisclosuresMessageOptions' + import { Type } from 'class-transformer' import { IsInstance } from 'class-validator' @@ -5,12 +7,6 @@ import { Feature } from '../../../../../agent/models' import { DidCommV1Message } from '../../../../../didcomm' import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -export interface V2DisclosuresMessageOptions { - id?: string - threadId?: string - features?: Feature[] -} - export class V2DisclosuresMessage extends DidCommV1Message { public constructor(options: V2DisclosuresMessageOptions) { super() diff --git a/packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresMessageOptions.ts b/packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresMessageOptions.ts new file mode 100644 index 0000000000..7098f1024b --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresMessageOptions.ts @@ -0,0 +1,7 @@ +import type { Feature } from '../../../../../agent/models' + +export interface V2DisclosuresMessageOptions { + id?: string + threadId?: string + features?: Feature[] +} diff --git a/packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesDidCommV2Message.ts b/packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesDidCommV2Message.ts new file mode 100644 index 0000000000..4ea9c05575 --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesDidCommV2Message.ts @@ -0,0 +1,46 @@ +import type { V2QueriesMessageOptions } from './V2QueriesMessageOptions' + +import { Type } from 'class-transformer' +import { ArrayNotEmpty, IsInstance, IsObject, ValidateNested } from 'class-validator' + +import { FeatureQuery } from '../../../../../agent/models' +import { DidCommV2Message } from '../../../../../didcomm' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' + +class V2QueriesMessageBody { + public constructor(options: { queries: FeatureQuery[] }) { + if (options) { + this.queries = options.queries + } + } + @IsInstance(FeatureQuery, { each: true }) + @Type(() => FeatureQuery) + @ArrayNotEmpty() + public queries!: FeatureQuery[] +} + +export class V2QueriesDidCommV2Message extends DidCommV2Message { + public readonly allowDidSovPrefix = false + + public constructor(options: V2QueriesMessageOptions) { + super() + + if (options) { + this.id = options.id ?? this.generateId() + this.body = new V2QueriesMessageBody({ queries: options.queries.map((q) => new FeatureQuery(q)) }) + } + } + + @IsValidMessageType(V2QueriesDidCommV2Message.type) + public readonly type = V2QueriesDidCommV2Message.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/discover-features/2.0/queries') + + @IsObject() + @ValidateNested() + @Type(() => V2QueriesMessageBody) + public body!: V2QueriesMessageBody + + public get queries() { + return this.body.queries + } +} diff --git a/packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesMessage.ts b/packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesMessage.ts index 10faaa51f2..d9bcc84022 100644 --- a/packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesMessage.ts +++ b/packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesMessage.ts @@ -1,4 +1,4 @@ -import type { FeatureQueryOptions } from '../../../../../agent/models' +import type { V2QueriesMessageOptions } from './V2QueriesMessageOptions' import { Type } from 'class-transformer' import { ArrayNotEmpty, IsInstance } from 'class-validator' @@ -7,14 +7,8 @@ import { FeatureQuery } from '../../../../../agent/models' import { DidCommV1Message } from '../../../../../didcomm' import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -export interface V2DiscoverFeaturesQueriesMessageOptions { - id?: string - queries: FeatureQueryOptions[] - comment?: string -} - export class V2QueriesMessage extends DidCommV1Message { - public constructor(options: V2DiscoverFeaturesQueriesMessageOptions) { + public constructor(options: V2QueriesMessageOptions) { super() if (options) { diff --git a/packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesMessageOptions.ts b/packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesMessageOptions.ts new file mode 100644 index 0000000000..a534ce4870 --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesMessageOptions.ts @@ -0,0 +1,6 @@ +import type { FeatureQueryOptions } from '../../../../../agent/models' + +export interface V2QueriesMessageOptions { + id?: string + queries: FeatureQueryOptions[] +} diff --git a/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts b/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts index dfe4bd0f32..d00cf1816d 100644 --- a/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts +++ b/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts @@ -1,7 +1,7 @@ import type { EventEmitter } from '../../../agent/EventEmitter' import type { FeatureRegistry } from '../../../agent/FeatureRegistry' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import type { DidCommV1Message } from '../../../didcomm' +import type { DidCommV1Message, DidCommV2Message } from '../../../didcomm' import type { Logger } from '../../../logger' import type { DiscoverFeaturesModuleConfig } from '../DiscoverFeaturesModuleConfig' import type { @@ -32,13 +32,15 @@ export abstract class DiscoverFeaturesService { public abstract createQuery( options: CreateQueryOptions - ): Promise> + ): Promise> public abstract processQuery( - messageContext: InboundMessageContext - ): Promise | void> + messageContext: InboundMessageContext + ): Promise | void> public abstract createDisclosure( options: CreateDisclosureOptions - ): Promise> - public abstract processDisclosure(messageContext: InboundMessageContext): Promise + ): Promise> + public abstract processDisclosure( + messageContext: InboundMessageContext + ): Promise } From 7d7525415eab122682f743f8610b4ffb43492c64 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Sat, 16 Sep 2023 15:13:03 -0300 Subject: [PATCH 8/8] fix: check retourn routes Signed-off-by: Ariel Gentile --- packages/core/src/agent/MessageSender.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/agent/MessageSender.ts b/packages/core/src/agent/MessageSender.ts index f81011ceda..2f2365fc66 100644 --- a/packages/core/src/agent/MessageSender.ts +++ b/packages/core/src/agent/MessageSender.ts @@ -320,7 +320,8 @@ export class MessageSender { // an incoming message. const [firstOurAuthenticationKey] = ourAuthenticationKeys // If the returnRoute is already set we won't override it. This allows to set the returnRoute manually if this is desired. - const shouldAddReturnRoute = !message.hasAnyReturnRoute && !this.transportService.hasInboundEndpoint(ourDidDocument) + const shouldAddReturnRoute = + !message.hasAnyReturnRoute() && !this.transportService.hasInboundEndpoint(ourDidDocument) // Loop through all available services and try to send the message for await (const service of services) {