Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Pack and send message based on DidDoc services #304

Merged
merged 4 commits into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/agent/Dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,14 @@ class Dispatcher {
outboundMessage.payload.setReturnRouting(ReturnRouteTypes.all)
}

// check for return routing, with thread id
// Check for return routing, with thread id
if (message.hasReturnRouting(threadId)) {
return await this.messageSender.packMessage(outboundMessage)
const keys = {
recipientKeys: messageContext.senderVerkey ? [messageContext.senderVerkey] : [],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW -- what happens when there are no recipient keys

routingKeys: [],
senderKey: messageContext.connection?.verkey || null,
}
return await this.messageSender.packMessage(outboundMessage, keys)
}

await this.messageSender.sendMessage(outboundMessage)
Expand Down
16 changes: 13 additions & 3 deletions src/agent/EnvelopeService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Logger } from '../logger'
import type { OutboundMessage, UnpackedMessageContext } from '../types'
import type { UnpackedMessageContext } from '../types'
import type { AgentMessage } from './AgentMessage'
import type { Verkey } from 'indy-sdk'

import { inject, scoped, Lifecycle } from 'tsyringe'

Expand All @@ -9,6 +11,12 @@ import { Wallet } from '../wallet/Wallet'

import { AgentConfig } from './AgentConfig'

export interface EnvelopeKeys {
recipientKeys: Verkey[]
routingKeys: Verkey[]
senderKey: Verkey | null
}

@scoped(Lifecycle.ContainerScoped)
class EnvelopeService {
private wallet: Wallet
Expand All @@ -19,10 +27,12 @@ class EnvelopeService {
this.logger = agentConfig.logger
}

public async packMessage(outboundMessage: OutboundMessage): Promise<JsonWebKey> {
const { routingKeys, recipientKeys, senderVk, payload } = outboundMessage
public async packMessage(payload: AgentMessage, keys: EnvelopeKeys): Promise<JsonWebKey> {
const { routingKeys, recipientKeys, senderKey: senderVk } = keys
const message = payload.toJSON()

this.logger.debug('Pack outbound message', { message })

let wireMessage = await this.wallet.pack(message, recipientKeys, senderVk)

if (routingKeys && routingKeys.length > 0) {
Expand Down
54 changes: 43 additions & 11 deletions src/agent/MessageSender.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { OutboundTransporter } from '../transport/OutboundTransporter'
import type { OutboundMessage, OutboundPackage } from '../types'
import type { EnvelopeKeys } from './EnvelopeService'

import { inject, Lifecycle, scoped } from 'tsyringe'

Expand Down Expand Up @@ -35,23 +36,54 @@ export class MessageSender {
return this._outboundTransporter
}

public async packMessage(outboundMessage: OutboundMessage): Promise<OutboundPackage> {
public async packMessage(outboundMessage: OutboundMessage, keys: EnvelopeKeys): Promise<OutboundPackage> {
const { connection, payload } = outboundMessage
const { verkey, theirKey } = connection
const endpoint = this.transportService.findEndpoint(connection)
const message = payload.toJSON()
this.logger.debug('outboundMessage', { verkey, theirKey, message })
const responseRequested = outboundMessage.payload.hasReturnRouting()
const wireMessage = await this.envelopeService.packMessage(outboundMessage)
return { connection, payload: wireMessage, endpoint, responseRequested }
const wireMessage = await this.envelopeService.packMessage(payload, keys)
return { connection, payload: wireMessage }
}

public async sendMessage(outboundMessage: OutboundMessage): Promise<void> {
if (!this.outboundTransporter) {
throw new AriesFrameworkError('Agent has no outbound transporter!')
}
const outboundPackage = await this.packMessage(outboundMessage)
outboundPackage.session = this.transportService.findSession(outboundMessage.connection.id)
await this.outboundTransporter.sendMessage(outboundPackage)

const { connection, payload } = outboundMessage
const { id, verkey, theirKey } = connection
const message = payload.toJSON()
this.logger.debug('Send outbound message', {
messageId: message.id,
connection: { id, verkey, theirKey },
})

const services = this.transportService.findDidCommServices(connection)
if (services.length === 0) {
throw new AriesFrameworkError(`Connection with id ${connection.id} has no service!`)
}

for await (const service of services) {
this.logger.debug(`Sending outbound message to service:`, { messageId: message.id, service })
try {
const keys = {
recipientKeys: service.recipientKeys,
routingKeys: service.routingKeys || [],
senderKey: connection.verkey,
}
const outboundPackage = await this.packMessage(outboundMessage, keys)
outboundPackage.session = this.transportService.findSession(connection.id)
outboundPackage.endpoint = service.serviceEndpoint
outboundPackage.responseRequested = outboundMessage.payload.hasReturnRouting()

await this.outboundTransporter.sendMessage(outboundPackage)
break
} catch (error) {
this.logger.debug(
`Sending outbound message to service with id ${service.id} failed with the following error:`,
{
message: error.message,
error: error,
}
)
}
}
}
}
26 changes: 13 additions & 13 deletions src/agent/TransportService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import type { ConnectionRecord } from '../modules/connections/repository'
import { Lifecycle, scoped, inject } from 'tsyringe'

import { DID_COMM_TRANSPORT_QUEUE, InjectionSymbols } from '../constants'
import { AriesFrameworkError } from '../error'
import { Logger } from '../logger'
import { ConnectionRole } from '../modules/connections/models'
import { ConnectionRole, DidCommService } from '../modules/connections/models'

@scoped(Lifecycle.ContainerScoped)
export class TransportService {
Expand All @@ -28,23 +27,24 @@ export class TransportService {
return this.transportSessionTable[connectionId]
}

public findEndpoint(connection: ConnectionRecord) {
public findDidCommServices(connection: ConnectionRecord): DidCommService[] {
if (connection.theirDidDoc) {
const endpoint = connection.theirDidDoc.didCommServices[0].serviceEndpoint
if (endpoint) {
this.logger.debug(`Taking service endpoint ${endpoint} from their DidDoc`)
return endpoint
}
return connection.theirDidDoc.didCommServices
}

if (connection.role === ConnectionRole.Invitee && connection.invitation) {
const endpoint = connection.invitation.serviceEndpoint
if (endpoint) {
this.logger.debug(`Taking service endpoint ${endpoint} from invitation`)
return endpoint
const { invitation } = connection
if (invitation.serviceEndpoint) {
const service = new DidCommService({
id: `${connection.id}-invitation`,
serviceEndpoint: invitation.serviceEndpoint,
recipientKeys: invitation.recipientKeys || [],
routingKeys: invitation.routingKeys || [],
})
return [service]
}
}
throw new AriesFrameworkError(`No endpoint found for connection with id ${connection.id}`)
return []
}
}

Expand Down
116 changes: 89 additions & 27 deletions src/agent/__tests__/MessageSender.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { ConnectionRecord } from '../../modules/connections'
import type { OutboundTransporter } from '../../transport'
import type { OutboundMessage } from '../../types'
import type { TransportSession } from '../TransportService'

import { getMockConnection, mockFunction } from '../../__tests__/helpers'
import testLogger from '../../__tests__/logger'
import { ReturnRouteTypes } from '../../decorators/transport/TransportDecorator'
import { DidCommService } from '../../modules/connections'
import { AgentMessage } from '../AgentMessage'
import { EnvelopeService as EnvelopeServiceImpl } from '../EnvelopeService'
import { MessageSender } from '../MessageSender'
Expand Down Expand Up @@ -51,50 +53,111 @@ describe('MessageSender', () => {

const enveloperService = new EnvelopeService()
const envelopeServicePackMessageMock = mockFunction(enveloperService.packMessage)
envelopeServicePackMessageMock.mockReturnValue(Promise.resolve(wireMessage))

const transportService = new TransportService()
const session = new DummyTransportSession()
const transportServiceFindSessionMock = mockFunction(transportService.findSession)
transportServiceFindSessionMock.mockReturnValue(session)

const endpoint = 'https://www.exampleEndpoint.com'
const transportServiceFindEndpointMock = mockFunction(transportService.findEndpoint)
transportServiceFindEndpointMock.mockReturnValue(endpoint)
const firstDidCommService = new DidCommService({
id: `<did>;indy`,
serviceEndpoint: 'https://www.first-endpoint.com',
recipientKeys: ['verkey'],
})
const secondDidCommService = new DidCommService({
id: `<did>;indy`,
serviceEndpoint: 'https://www.second-endpoint.com',
recipientKeys: ['verkey'],
})
const transportServiceFindServicesMock = mockFunction(transportService.findDidCommServices)

let messageSender: MessageSender
let outboundTransporter: OutboundTransporter
let connection: ConnectionRecord
let outboundMessage: OutboundMessage

describe('sendMessage', () => {
beforeEach(() => {
outboundTransporter = new DummyOutboundTransporter()
messageSender = new MessageSender(enveloperService, transportService, logger)
connection = getMockConnection({ id: 'test-123' })

outboundMessage = createOutboundMessage(connection, new AgentMessage())

envelopeServicePackMessageMock.mockReturnValue(Promise.resolve(wireMessage))
transportServiceFindServicesMock.mockReturnValue([firstDidCommService, secondDidCommService])
transportServiceFindSessionMock.mockReturnValue(session)
})

afterEach(() => {
jest.resetAllMocks()
})

test('throws error when there is no outbound transport', async () => {
const message = new AgentMessage()
const outboundMessage = createOutboundMessage(connection, message)
await expect(messageSender.sendMessage(outboundMessage)).rejects.toThrow(`Agent has no outbound transporter!`)
})

test('calls transporter with connection, payload and endpoint', async () => {
const message = new AgentMessage()
const spy = jest.spyOn(outboundTransporter, 'sendMessage')
const outboundMessage = createOutboundMessage(connection, message)
test('throws error when there is no service', async () => {
messageSender.setOutboundTransporter(outboundTransporter)
transportServiceFindServicesMock.mockReturnValue([])

await expect(messageSender.sendMessage(outboundMessage)).rejects.toThrow(
`Connection with id test-123 has no service!`
)
})

test('calls send message with connection, payload and endpoint from first DidComm service', async () => {
messageSender.setOutboundTransporter(outboundTransporter)
const sendMessageSpy = jest.spyOn(outboundTransporter, 'sendMessage')

await messageSender.sendMessage(outboundMessage)

expect(sendMessageSpy).toHaveBeenCalledWith({
connection,
payload: wireMessage,
endpoint: firstDidCommService.serviceEndpoint,
responseRequested: false,
session,
})
expect(sendMessageSpy).toHaveBeenCalledTimes(1)
})

test('calls send message with connection, payload and endpoint from second DidComm service when the first fails', async () => {
messageSender.setOutboundTransporter(outboundTransporter)
const sendMessageSpy = jest.spyOn(outboundTransporter, 'sendMessage')

// Simulate the case when the first call fails
sendMessageSpy.mockRejectedValueOnce(new Error())

await messageSender.sendMessage(outboundMessage)

const [[sendMessageCall]] = spy.mock.calls
expect(sendMessageCall).toEqual({
expect(sendMessageSpy).toHaveBeenNthCalledWith(2, {
connection,
payload: wireMessage,
endpoint,
endpoint: secondDidCommService.serviceEndpoint,
responseRequested: false,
session,
})
expect(sendMessageSpy).toHaveBeenCalledTimes(2)
})

test('calls send message with responseRequested when message has return route', async () => {
messageSender.setOutboundTransporter(outboundTransporter)
const sendMessageSpy = jest.spyOn(outboundTransporter, 'sendMessage')

const message = new AgentMessage()
message.setReturnRouting(ReturnRouteTypes.all)
const outboundMessage = createOutboundMessage(connection, message)

await messageSender.sendMessage(outboundMessage)

expect(sendMessageSpy).toHaveBeenCalledWith({
connection,
payload: wireMessage,
endpoint: firstDidCommService.serviceEndpoint,
responseRequested: true,
session,
})
expect(sendMessageSpy).toHaveBeenCalledTimes(1)
})
})

Expand All @@ -103,30 +166,29 @@ describe('MessageSender', () => {
outboundTransporter = new DummyOutboundTransporter()
messageSender = new MessageSender(enveloperService, transportService, logger)
connection = getMockConnection({ id: 'test-123' })

envelopeServicePackMessageMock.mockReturnValue(Promise.resolve(wireMessage))
})

afterEach(() => {
jest.resetAllMocks()
})

test('returns outbound message context with connection, payload and endpoint', async () => {
const message = new AgentMessage()
const outboundMessage = createOutboundMessage(connection, message)

const result = await messageSender.packMessage(outboundMessage)
const keys = {
recipientKeys: ['service.recipientKeys'],
routingKeys: [],
senderKey: connection.verkey,
}
const result = await messageSender.packMessage(outboundMessage, keys)

expect(result).toEqual({
connection,
payload: wireMessage,
endpoint,
responseRequested: false,
})
})

test('when message has return route returns outbound message context with responseRequested', async () => {
const message = new AgentMessage()
message.setReturnRouting(ReturnRouteTypes.all)
const outboundMessage = createOutboundMessage(connection, message)

const result = await messageSender.packMessage(outboundMessage)

expect(result.responseRequested).toEqual(true)
})
})
})
Loading