Skip to content

Commit

Permalink
chore: merge branch 'main' into 0.3.0-pre (openwallet-foundation#1030)
Browse files Browse the repository at this point in the history
* feat: OOB public did (openwallet-foundation#930)

Signed-off-by: Pavel Zarecky <[email protected]>

* feat(routing): manual mediator pickup lifecycle management (openwallet-foundation#989)

Signed-off-by: Ariel Gentile <[email protected]>

* docs(demo): faber creates invitation (openwallet-foundation#995)

Signed-off-by: conanoc <[email protected]>

* chore(release): v0.2.3 (openwallet-foundation#999)

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(question-answer): question answer protocol state/role check (openwallet-foundation#1001)

Signed-off-by: Ariel Gentile <[email protected]>

* feat: Action Menu protocol (Aries RFC 0509) implementation (openwallet-foundation#974)

Signed-off-by: Ariel Gentile <[email protected]>

* fix(ledger): remove poolConnected on pool close (openwallet-foundation#1011)

Signed-off-by: Niall Shaw <[email protected]>

* fix(ledger): check taa version instad of aml version (openwallet-foundation#1013)

Signed-off-by: Jakub Koci <[email protected]>

* chore: add @janrtvld to maintainers (openwallet-foundation#1016)

Signed-off-by: Timo Glastra <[email protected]>

* feat(routing): add settings to control back off strategy on mediator reconnection (openwallet-foundation#1017)

Signed-off-by: Sergi Garreta <[email protected]>

* fix: avoid crash when an unexpected message arrives (openwallet-foundation#1019)

Signed-off-by: Pavel Zarecky <[email protected]>

* chore(release): v0.2.4 (openwallet-foundation#1024)

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* style: fix some lint errors

Signed-off-by: Ariel Gentile <[email protected]>

* feat: use did:key flag (openwallet-foundation#1029)

Signed-off-by: Ariel Gentile <[email protected]>

* ci: set default rust version (openwallet-foundation#1036)

Signed-off-by: Sai Ranjit Tummalapalli <[email protected]>

* fix(oob): allow encoding in content type header (openwallet-foundation#1037)

Signed-off-by: Timo Glastra <[email protected]>

* feat: connection type (openwallet-foundation#994)

Signed-off-by: KolbyRKunz <[email protected]>

* chore(module-tenants): match package versions

Signed-off-by: Ariel Gentile <[email protected]>

* feat: improve sending error handling (openwallet-foundation#1045)

Signed-off-by: Ariel Gentile <[email protected]>

* feat: expose findAllByQuery method in modules and services (openwallet-foundation#1044)

Signed-off-by: Jim Ezesinachi <[email protected]>

* feat: possibility to set masterSecretId inside of WalletConfig (openwallet-foundation#1043)

Signed-off-by: Andrii Uhryn <[email protected]>

* fix(oob): set connection alias when creating invitation (openwallet-foundation#1047)

Signed-off-by: Jakub Koci <[email protected]>

* build: fix missing parameter

Signed-off-by: Ariel Gentile <[email protected]>

Signed-off-by: Pavel Zarecky <[email protected]>
Signed-off-by: Ariel Gentile <[email protected]>
Signed-off-by: conanoc <[email protected]>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: Niall Shaw <[email protected]>
Signed-off-by: Jakub Koci <[email protected]>
Signed-off-by: Timo Glastra <[email protected]>
Signed-off-by: Sergi Garreta <[email protected]>
Signed-off-by: Sai Ranjit Tummalapalli <[email protected]>
Signed-off-by: KolbyRKunz <[email protected]>
Signed-off-by: Jim Ezesinachi <[email protected]>
Signed-off-by: Andrii Uhryn <[email protected]>
Co-authored-by: Iskander508 <[email protected]>
Co-authored-by: conanoc <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Niall Shaw <[email protected]>
Co-authored-by: jakubkoci <[email protected]>
Co-authored-by: Timo Glastra <[email protected]>
Co-authored-by: Sergi Garreta Serra <[email protected]>
Co-authored-by: Sai Ranjit Tummalapalli <[email protected]>
Co-authored-by: KolbyRKunz <[email protected]>
Co-authored-by: Jim Ezesinachi <[email protected]>
Co-authored-by: an-uhryn <[email protected]>
  • Loading branch information
12 people committed Oct 13, 2022
1 parent 9ca4221 commit edd81bc
Show file tree
Hide file tree
Showing 56 changed files with 925 additions and 149 deletions.
10 changes: 10 additions & 0 deletions packages/core/src/agent/AgentConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ export class AgentConfig {
return this.initConfig.maximumMediatorReconnectionIntervalMs ?? Number.POSITIVE_INFINITY
}

/**
* Encode keys in did:key format instead of 'naked' keys, as stated in Aries RFC 0360.
*
* This setting will not be taken into account if the other party has previously used naked keys
* in a given protocol (i.e. it does not support Aries RFC 0360).
*/
public get useDidKeyInProtocols() {
return this.initConfig.useDidKeyInProtocols ?? false
}

public get endpoints(): [string, ...string[]] {
// if endpoints is not set, return queue endpoint
// https://github.com/hyperledger/aries-rfcs/issues/405#issuecomment-582612875
Expand Down
12 changes: 8 additions & 4 deletions packages/core/src/agent/MessageSender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { AgentContext } from './context'

import { DID_COMM_TRANSPORT_QUEUE, InjectionSymbols } from '../constants'
import { ReturnRouteTypes } from '../decorators/transport/TransportDecorator'
import { AriesFrameworkError } from '../error'
import { AriesFrameworkError, MessageSendingError } from '../error'
import { Logger } from '../logger'
import { DidCommDocumentService } from '../modules/didcomm'
import { getKeyDidMappingByVerificationMethod } from '../modules/dids/domain/key-type'
Expand Down Expand Up @@ -223,8 +223,9 @@ export class MessageSender {

if (!connection.did) {
this.logger.error(`Unable to send message using connection '${connection.id}' that doesn't have a did`)
throw new AriesFrameworkError(
`Unable to send message using connection '${connection.id}' that doesn't have a did`
throw new MessageSendingError(
`Unable to send message using connection '${connection.id}' that doesn't have a did`,
{ outboundMessage }
)
}

Expand Down Expand Up @@ -291,7 +292,10 @@ export class MessageSender {
errors,
connection,
})
throw new AriesFrameworkError(`Message is undeliverable to connection ${connection.id} (${connection.theirLabel})`)
throw new MessageSendingError(
`Message is undeliverable to connection ${connection.id} (${connection.theirLabel})`,
{ outboundMessage }
)
}

public async sendMessageToService(
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/error/MessageSendingError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { OutboundMessage } from '../types'

import { AriesFrameworkError } from './AriesFrameworkError'

export class MessageSendingError extends AriesFrameworkError {
public outboundMessage: OutboundMessage
public constructor(message: string, { outboundMessage, cause }: { outboundMessage: OutboundMessage; cause?: Error }) {
super(message, { cause })
this.outboundMessage = outboundMessage
}
}
1 change: 1 addition & 0 deletions packages/core/src/error/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './RecordNotFoundError'
export * from './RecordDuplicateError'
export * from './IndySdkError'
export * from './ClassValidationError'
export * from './MessageSendingError'
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export * from './storage/BaseRecord'
export { InMemoryMessageRepository } from './storage/InMemoryMessageRepository'
export { Repository } from './storage/Repository'
export * from './storage/RepositoryEvents'
export { StorageService } from './storage/StorageService'
export { StorageService, Query } from './storage/StorageService'
export { getDirFromFilePath } from './utils/path'
export { InjectionSymbols } from './constants'
export * from './wallet'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { AgentContext } from '../../../agent'
import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext'
import type { Logger } from '../../../logger'
import type { Query } from '../../../storage/StorageService'
import type { ActionMenuStateChangedEvent } from '../ActionMenuEvents'
import type { ActionMenuProblemReportMessage } from '../messages'
import type {
Expand Down Expand Up @@ -347,6 +348,10 @@ export class ActionMenuService {
})
}

public async findAllByQuery(agentContext: AgentContext, options: Query<ActionMenuRecord>) {
return await this.actionMenuRepository.findByQuery(agentContext, options)
}

private emitStateChangedEvent(
agentContext: AgentContext,
actionMenuRecord: ActionMenuRecord,
Expand Down
51 changes: 48 additions & 3 deletions packages/core/src/modules/basic-messages/BasicMessagesApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { BasicMessageTags } from './repository/BasicMessageRecord'
import type { Query } from '../../storage/StorageService'
import type { BasicMessageRecord } from './repository/BasicMessageRecord'

import { AgentContext } from '../../agent'
import { Dispatcher } from '../../agent/Dispatcher'
Expand Down Expand Up @@ -31,18 +32,62 @@ export class BasicMessagesApi {
this.registerHandlers(dispatcher)
}

/**
* Send a message to an active connection
*
* @param connectionId Connection Id
* @param message Message contents
* @throws {RecordNotFoundError} If connection is not found
* @throws {MessageSendingError} If message is undeliverable
* @returns the created record
*/
public async sendMessage(connectionId: string, message: string) {
const connection = await this.connectionService.getById(this.agentContext, connectionId)

const basicMessage = await this.basicMessageService.createMessage(this.agentContext, message, connection)
const { message: basicMessage, record: basicMessageRecord } = await this.basicMessageService.createMessage(
this.agentContext,
message,
connection
)
const outboundMessage = createOutboundMessage(connection, basicMessage)
outboundMessage.associatedRecord = basicMessageRecord

await this.messageSender.sendMessage(this.agentContext, outboundMessage)
return basicMessageRecord
}

public async findAllByQuery(query: Partial<BasicMessageTags>) {
/**
* Retrieve all basic messages matching a given query
*
* @param query The query
* @returns array containing all matching records
*/
public async findAllByQuery(query: Query<BasicMessageRecord>) {
return this.basicMessageService.findAllByQuery(this.agentContext, query)
}

/**
* Retrieve a basic message record by id
*
* @param basicMessageRecordId The basic message record id
* @throws {RecordNotFoundError} If no record is found
* @return The basic message record
*
*/
public async getById(basicMessageRecordId: string) {
return this.basicMessageService.getById(this.agentContext, basicMessageRecordId)
}

/**
* Delete a basic message record by id
*
* @param connectionId the basic message record id
* @throws {RecordNotFoundError} If no record is found
*/
public async deleteById(basicMessageRecordId: string) {
await this.basicMessageService.deleteById(this.agentContext, basicMessageRecordId)
}

private registerHandlers(dispatcher: Dispatcher) {
dispatcher.registerHandler(new BasicMessageHandler(this.basicMessageService))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('BasicMessageService', () => {

describe('createMessage', () => {
it(`creates message and record, and emits message and basic message record`, async () => {
const message = await basicMessageService.createMessage(agentContext, 'hello', mockConnectionRecord)
const { message } = await basicMessageService.createMessage(agentContext, 'hello', mockConnectionRecord)

expect(message.content).toBe('hello')

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport'
import type { ConnectionRecord } from '../../../modules/connections'

import { Subject } from 'rxjs'

import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport'
import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport'
import { getAgentOptions, makeConnection, waitForBasicMessage } from '../../../../tests/helpers'
import testLogger from '../../../../tests/logger'
import { Agent } from '../../../agent/Agent'
import { MessageSendingError, RecordNotFoundError } from '../../../error'
import { BasicMessage } from '../messages'
import { BasicMessageRecord } from '../repository'

const faberConfig = getAgentOptions('Faber Basic Messages', {
endpoints: ['rxjs:faber'],
})

const aliceConfig = getAgentOptions('Alice Basic Messages', {
endpoints: ['rxjs:alice'],
})

describe('Basic Messages E2E', () => {
let faberAgent: Agent
let aliceAgent: Agent
let faberConnection: ConnectionRecord
let aliceConnection: ConnectionRecord

beforeEach(async () => {
const faberMessages = new Subject<SubjectMessage>()
const aliceMessages = new Subject<SubjectMessage>()
const subjectMap = {
'rxjs:faber': faberMessages,
'rxjs:alice': aliceMessages,
}

faberAgent = new Agent(faberConfig)
faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages))
faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap))
await faberAgent.initialize()

aliceAgent = new Agent(aliceConfig)
aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages))
aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap))
await aliceAgent.initialize()
;[aliceConnection, faberConnection] = await makeConnection(aliceAgent, faberAgent)
})

afterEach(async () => {
await faberAgent.shutdown()
await faberAgent.wallet.delete()
await aliceAgent.shutdown()
await aliceAgent.wallet.delete()
})

test('Alice and Faber exchange messages', async () => {
testLogger.test('Alice sends message to Faber')
const helloRecord = await aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello')

expect(helloRecord.content).toBe('Hello')

testLogger.test('Faber waits for message from Alice')
await waitForBasicMessage(faberAgent, {
content: 'Hello',
})

testLogger.test('Faber sends message to Alice')
const replyRecord = await faberAgent.basicMessages.sendMessage(faberConnection.id, 'How are you?')
expect(replyRecord.content).toBe('How are you?')

testLogger.test('Alice waits until she receives message from faber')
await waitForBasicMessage(aliceAgent, {
content: 'How are you?',
})
})

test('Alice is unable to send a message', async () => {
testLogger.test('Alice sends message to Faber that is undeliverable')

const spy = jest.spyOn(aliceAgent.outboundTransports[0], 'sendMessage').mockRejectedValue(new Error('any error'))

await expect(aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello')).rejects.toThrowError(
MessageSendingError
)
try {
await aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello undeliverable')
} catch (error) {
const thrownError = error as MessageSendingError
expect(thrownError.message).toEqual(
`Message is undeliverable to connection ${aliceConnection.id} (${aliceConnection.theirLabel})`
)
testLogger.test('Error thrown includes the outbound message and recently created record id')
expect(thrownError.outboundMessage.associatedRecord).toBeInstanceOf(BasicMessageRecord)
expect(thrownError.outboundMessage.payload).toBeInstanceOf(BasicMessage)
expect((thrownError.outboundMessage.payload as BasicMessage).content).toBe('Hello undeliverable')

testLogger.test('Created record can be found and deleted by id')
const storedRecord = await aliceAgent.basicMessages.getById(thrownError.outboundMessage.associatedRecord!.id)
expect(storedRecord).toBeInstanceOf(BasicMessageRecord)
expect(storedRecord.content).toBe('Hello undeliverable')

await aliceAgent.basicMessages.deleteById(storedRecord.id)
await expect(
aliceAgent.basicMessages.getById(thrownError.outboundMessage.associatedRecord!.id)
).rejects.toThrowError(RecordNotFoundError)
}
spy.mockClear()
})
})
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { AgentContext } from '../../../agent'
import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext'
import type { Query } from '../../../storage/StorageService'
import type { ConnectionRecord } from '../../connections/repository/ConnectionRecord'
import type { BasicMessageStateChangedEvent } from '../BasicMessageEvents'
import type { BasicMessageTags } from '../repository'

import { EventEmitter } from '../../../agent/EventEmitter'
import { injectable } from '../../../plugins'
Expand Down Expand Up @@ -35,7 +35,7 @@ export class BasicMessageService {
await this.basicMessageRepository.save(agentContext, basicMessageRecord)
this.emitStateChangedEvent(agentContext, basicMessageRecord, basicMessage)

return basicMessage
return { message: basicMessage, record: basicMessageRecord }
}

/**
Expand Down Expand Up @@ -65,7 +65,16 @@ export class BasicMessageService {
})
}

public async findAllByQuery(agentContext: AgentContext, query: Partial<BasicMessageTags>) {
public async findAllByQuery(agentContext: AgentContext, query: Query<BasicMessageRecord>) {
return this.basicMessageRepository.findByQuery(agentContext, query)
}

public async getById(agentContext: AgentContext, basicMessageRecordId: string) {
return this.basicMessageRepository.getById(agentContext, basicMessageRecordId)
}

public async deleteById(agentContext: AgentContext, basicMessageRecordId: string) {
const basicMessageRecord = await this.getById(agentContext, basicMessageRecordId)
return this.basicMessageRepository.delete(agentContext, basicMessageRecord)
}
}
64 changes: 64 additions & 0 deletions packages/core/src/modules/connections/ConnectionsApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { Query } from '../../storage/StorageService'
import type { OutOfBandRecord } from '../oob/repository'
import type { ConnectionType } from './models'
import type { ConnectionRecord } from './repository/ConnectionRecord'
import type { Routing } from './services'

Expand Down Expand Up @@ -216,6 +218,68 @@ export class ConnectionsApi {
return this.connectionService.getAll(this.agentContext)
}

/**
* Retrieve all connections records by specified query params
*
* @returns List containing all connection records matching specified query paramaters
*/
public findAllByQuery(query: Query<ConnectionRecord>) {
return this.connectionService.findAllByQuery(this.agentContext, query)
}

/**
* Allows for the addition of connectionType to the record.
* Either updates or creates an array of string conection types
* @param connectionId
* @param type
* @throws {RecordNotFoundError} If no record is found
*/
public async addConnectionType(connectionId: string, type: ConnectionType | string) {
const record = await this.getById(connectionId)

const tags = (record.getTag('connectionType') as string[]) || ([] as string[])
record.setTag('connectionType', [type, ...tags])
await this.connectionService.update(this.agentContext, record)
}
/**
* Removes the given tag from the given record found by connectionId, if the tag exists otherwise does nothing
* @param connectionId
* @param type
* @throws {RecordNotFoundError} If no record is found
*/
public async removeConnectionType(connectionId: string, type: ConnectionType | string) {
const record = await this.getById(connectionId)

const tags = (record.getTag('connectionType') as string[]) || ([] as string[])

const newTags = tags.filter((value: string) => {
if (value != type) return value
})
record.setTag('connectionType', [...newTags])

await this.connectionService.update(this.agentContext, record)
}
/**
* Gets the known connection types for the record matching the given connectionId
* @param connectionId
* @returns An array of known connection types or null if none exist
* @throws {RecordNotFoundError} If no record is found
*/
public async getConnectionTypes(connectionId: string) {
const record = await this.getById(connectionId)
const tags = record.getTag('connectionType') as string[]
return tags || null
}

/**
*
* @param connectionTypes An array of connection types to query for a match for
* @returns a promise of ab array of connection records
*/
public async findAllByConnectionType(connectionTypes: [ConnectionType | string]) {
return this.connectionService.findAllByConnectionType(this.agentContext, connectionTypes)
}

/**
* Retrieve a connection record by id
*
Expand Down
Loading

0 comments on commit edd81bc

Please sign in to comment.