Skip to content

Commit edd81bc

Browse files
genarisIskander508conanocgithub-actions[bot]niall-shaw
committed
chore: merge branch 'main' into 0.3.0-pre (openwallet-foundation#1030)
* feat: OOB public did (openwallet-foundation#930) Signed-off-by: Pavel Zarecky <zarecky@procivis.ch> * feat(routing): manual mediator pickup lifecycle management (openwallet-foundation#989) Signed-off-by: Ariel Gentile <gentilester@gmail.com> * docs(demo): faber creates invitation (openwallet-foundation#995) Signed-off-by: conanoc <conanoc@gmail.com> * 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 <gentilester@gmail.com> * feat: Action Menu protocol (Aries RFC 0509) implementation (openwallet-foundation#974) Signed-off-by: Ariel Gentile <gentilester@gmail.com> * fix(ledger): remove poolConnected on pool close (openwallet-foundation#1011) Signed-off-by: Niall Shaw <niall.shaw@absa.africa> * fix(ledger): check taa version instad of aml version (openwallet-foundation#1013) Signed-off-by: Jakub Koci <jakub.koci@gmail.com> * chore: add @janrtvld to maintainers (openwallet-foundation#1016) Signed-off-by: Timo Glastra <timo@animo.id> * feat(routing): add settings to control back off strategy on mediator reconnection (openwallet-foundation#1017) Signed-off-by: Sergi Garreta <sergi.garreta@entrust.com> * fix: avoid crash when an unexpected message arrives (openwallet-foundation#1019) Signed-off-by: Pavel Zarecky <zarecky@procivis.ch> * 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 <gentilester@gmail.com> * feat: use did:key flag (openwallet-foundation#1029) Signed-off-by: Ariel Gentile <gentilester@gmail.com> * ci: set default rust version (openwallet-foundation#1036) Signed-off-by: Sai Ranjit Tummalapalli <sairanjit5@gmail.com> * fix(oob): allow encoding in content type header (openwallet-foundation#1037) Signed-off-by: Timo Glastra <timo@animo.id> * feat: connection type (openwallet-foundation#994) Signed-off-by: KolbyRKunz <KolbyKunz@yahoo.com> * chore(module-tenants): match package versions Signed-off-by: Ariel Gentile <gentilester@gmail.com> * feat: improve sending error handling (openwallet-foundation#1045) Signed-off-by: Ariel Gentile <gentilester@gmail.com> * feat: expose findAllByQuery method in modules and services (openwallet-foundation#1044) Signed-off-by: Jim Ezesinachi <jim@animo.id> * feat: possibility to set masterSecretId inside of WalletConfig (openwallet-foundation#1043) Signed-off-by: Andrii Uhryn <an.ugryn@gmail.com> * fix(oob): set connection alias when creating invitation (openwallet-foundation#1047) Signed-off-by: Jakub Koci <jakub.koci@gmail.com> * build: fix missing parameter Signed-off-by: Ariel Gentile <gentilester@gmail.com> Signed-off-by: Pavel Zarecky <zarecky@procivis.ch> Signed-off-by: Ariel Gentile <gentilester@gmail.com> Signed-off-by: conanoc <conanoc@gmail.com> Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Signed-off-by: Niall Shaw <niall.shaw@absa.africa> Signed-off-by: Jakub Koci <jakub.koci@gmail.com> Signed-off-by: Timo Glastra <timo@animo.id> Signed-off-by: Sergi Garreta <sergi.garreta@entrust.com> Signed-off-by: Sai Ranjit Tummalapalli <sairanjit5@gmail.com> Signed-off-by: KolbyRKunz <KolbyKunz@yahoo.com> Signed-off-by: Jim Ezesinachi <jim@animo.id> Signed-off-by: Andrii Uhryn <an.ugryn@gmail.com> Co-authored-by: Iskander508 <pavel.zarecky@seznam.cz> Co-authored-by: conanoc <conanoc@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Niall Shaw <100220424+niallshaw-absa@users.noreply.github.com> Co-authored-by: jakubkoci <jakub.koci@gmail.com> Co-authored-by: Timo Glastra <timo@animo.id> Co-authored-by: Sergi Garreta Serra <garretaserra@gmail.com> Co-authored-by: Sai Ranjit Tummalapalli <34263716+sairanjit@users.noreply.github.com> Co-authored-by: KolbyRKunz <KolbyKunz@yahoo.com> Co-authored-by: Jim Ezesinachi <ezesinachijim@gmail.com> Co-authored-by: an-uhryn <55444541+an-uhryn@users.noreply.github.com>
1 parent 9ca4221 commit edd81bc

File tree

56 files changed

+925
-149
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+925
-149
lines changed

packages/core/src/agent/AgentConfig.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@ export class AgentConfig {
113113
return this.initConfig.maximumMediatorReconnectionIntervalMs ?? Number.POSITIVE_INFINITY
114114
}
115115

116+
/**
117+
* Encode keys in did:key format instead of 'naked' keys, as stated in Aries RFC 0360.
118+
*
119+
* This setting will not be taken into account if the other party has previously used naked keys
120+
* in a given protocol (i.e. it does not support Aries RFC 0360).
121+
*/
122+
public get useDidKeyInProtocols() {
123+
return this.initConfig.useDidKeyInProtocols ?? false
124+
}
125+
116126
public get endpoints(): [string, ...string[]] {
117127
// if endpoints is not set, return queue endpoint
118128
// https://github.com/hyperledger/aries-rfcs/issues/405#issuecomment-582612875

packages/core/src/agent/MessageSender.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { AgentContext } from './context'
1212

1313
import { DID_COMM_TRANSPORT_QUEUE, InjectionSymbols } from '../constants'
1414
import { ReturnRouteTypes } from '../decorators/transport/TransportDecorator'
15-
import { AriesFrameworkError } from '../error'
15+
import { AriesFrameworkError, MessageSendingError } from '../error'
1616
import { Logger } from '../logger'
1717
import { DidCommDocumentService } from '../modules/didcomm'
1818
import { getKeyDidMappingByVerificationMethod } from '../modules/dids/domain/key-type'
@@ -223,8 +223,9 @@ export class MessageSender {
223223

224224
if (!connection.did) {
225225
this.logger.error(`Unable to send message using connection '${connection.id}' that doesn't have a did`)
226-
throw new AriesFrameworkError(
227-
`Unable to send message using connection '${connection.id}' that doesn't have a did`
226+
throw new MessageSendingError(
227+
`Unable to send message using connection '${connection.id}' that doesn't have a did`,
228+
{ outboundMessage }
228229
)
229230
}
230231

@@ -291,7 +292,10 @@ export class MessageSender {
291292
errors,
292293
connection,
293294
})
294-
throw new AriesFrameworkError(`Message is undeliverable to connection ${connection.id} (${connection.theirLabel})`)
295+
throw new MessageSendingError(
296+
`Message is undeliverable to connection ${connection.id} (${connection.theirLabel})`,
297+
{ outboundMessage }
298+
)
295299
}
296300

297301
public async sendMessageToService(
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { OutboundMessage } from '../types'
2+
3+
import { AriesFrameworkError } from './AriesFrameworkError'
4+
5+
export class MessageSendingError extends AriesFrameworkError {
6+
public outboundMessage: OutboundMessage
7+
public constructor(message: string, { outboundMessage, cause }: { outboundMessage: OutboundMessage; cause?: Error }) {
8+
super(message, { cause })
9+
this.outboundMessage = outboundMessage
10+
}
11+
}

packages/core/src/error/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from './RecordNotFoundError'
33
export * from './RecordDuplicateError'
44
export * from './IndySdkError'
55
export * from './ClassValidationError'
6+
export * from './MessageSendingError'

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export * from './storage/BaseRecord'
3030
export { InMemoryMessageRepository } from './storage/InMemoryMessageRepository'
3131
export { Repository } from './storage/Repository'
3232
export * from './storage/RepositoryEvents'
33-
export { StorageService } from './storage/StorageService'
33+
export { StorageService, Query } from './storage/StorageService'
3434
export { getDirFromFilePath } from './utils/path'
3535
export { InjectionSymbols } from './constants'
3636
export * from './wallet'

packages/core/src/modules/action-menu/services/ActionMenuService.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { AgentContext } from '../../../agent'
22
import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext'
33
import type { Logger } from '../../../logger'
4+
import type { Query } from '../../../storage/StorageService'
45
import type { ActionMenuStateChangedEvent } from '../ActionMenuEvents'
56
import type { ActionMenuProblemReportMessage } from '../messages'
67
import type {
@@ -347,6 +348,10 @@ export class ActionMenuService {
347348
})
348349
}
349350

351+
public async findAllByQuery(agentContext: AgentContext, options: Query<ActionMenuRecord>) {
352+
return await this.actionMenuRepository.findByQuery(agentContext, options)
353+
}
354+
350355
private emitStateChangedEvent(
351356
agentContext: AgentContext,
352357
actionMenuRecord: ActionMenuRecord,

packages/core/src/modules/basic-messages/BasicMessagesApi.ts

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { BasicMessageTags } from './repository/BasicMessageRecord'
1+
import type { Query } from '../../storage/StorageService'
2+
import type { BasicMessageRecord } from './repository/BasicMessageRecord'
23

34
import { AgentContext } from '../../agent'
45
import { Dispatcher } from '../../agent/Dispatcher'
@@ -31,18 +32,62 @@ export class BasicMessagesApi {
3132
this.registerHandlers(dispatcher)
3233
}
3334

35+
/**
36+
* Send a message to an active connection
37+
*
38+
* @param connectionId Connection Id
39+
* @param message Message contents
40+
* @throws {RecordNotFoundError} If connection is not found
41+
* @throws {MessageSendingError} If message is undeliverable
42+
* @returns the created record
43+
*/
3444
public async sendMessage(connectionId: string, message: string) {
3545
const connection = await this.connectionService.getById(this.agentContext, connectionId)
3646

37-
const basicMessage = await this.basicMessageService.createMessage(this.agentContext, message, connection)
47+
const { message: basicMessage, record: basicMessageRecord } = await this.basicMessageService.createMessage(
48+
this.agentContext,
49+
message,
50+
connection
51+
)
3852
const outboundMessage = createOutboundMessage(connection, basicMessage)
53+
outboundMessage.associatedRecord = basicMessageRecord
54+
3955
await this.messageSender.sendMessage(this.agentContext, outboundMessage)
56+
return basicMessageRecord
4057
}
4158

42-
public async findAllByQuery(query: Partial<BasicMessageTags>) {
59+
/**
60+
* Retrieve all basic messages matching a given query
61+
*
62+
* @param query The query
63+
* @returns array containing all matching records
64+
*/
65+
public async findAllByQuery(query: Query<BasicMessageRecord>) {
4366
return this.basicMessageService.findAllByQuery(this.agentContext, query)
4467
}
4568

69+
/**
70+
* Retrieve a basic message record by id
71+
*
72+
* @param basicMessageRecordId The basic message record id
73+
* @throws {RecordNotFoundError} If no record is found
74+
* @return The basic message record
75+
*
76+
*/
77+
public async getById(basicMessageRecordId: string) {
78+
return this.basicMessageService.getById(this.agentContext, basicMessageRecordId)
79+
}
80+
81+
/**
82+
* Delete a basic message record by id
83+
*
84+
* @param connectionId the basic message record id
85+
* @throws {RecordNotFoundError} If no record is found
86+
*/
87+
public async deleteById(basicMessageRecordId: string) {
88+
await this.basicMessageService.deleteById(this.agentContext, basicMessageRecordId)
89+
}
90+
4691
private registerHandlers(dispatcher: Dispatcher) {
4792
dispatcher.registerHandler(new BasicMessageHandler(this.basicMessageService))
4893
}

packages/core/src/modules/basic-messages/__tests__/BasicMessageService.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ describe('BasicMessageService', () => {
3030

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

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

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
2+
import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport'
3+
import type { ConnectionRecord } from '../../../modules/connections'
4+
5+
import { Subject } from 'rxjs'
6+
7+
import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport'
8+
import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport'
9+
import { getAgentOptions, makeConnection, waitForBasicMessage } from '../../../../tests/helpers'
10+
import testLogger from '../../../../tests/logger'
11+
import { Agent } from '../../../agent/Agent'
12+
import { MessageSendingError, RecordNotFoundError } from '../../../error'
13+
import { BasicMessage } from '../messages'
14+
import { BasicMessageRecord } from '../repository'
15+
16+
const faberConfig = getAgentOptions('Faber Basic Messages', {
17+
endpoints: ['rxjs:faber'],
18+
})
19+
20+
const aliceConfig = getAgentOptions('Alice Basic Messages', {
21+
endpoints: ['rxjs:alice'],
22+
})
23+
24+
describe('Basic Messages E2E', () => {
25+
let faberAgent: Agent
26+
let aliceAgent: Agent
27+
let faberConnection: ConnectionRecord
28+
let aliceConnection: ConnectionRecord
29+
30+
beforeEach(async () => {
31+
const faberMessages = new Subject<SubjectMessage>()
32+
const aliceMessages = new Subject<SubjectMessage>()
33+
const subjectMap = {
34+
'rxjs:faber': faberMessages,
35+
'rxjs:alice': aliceMessages,
36+
}
37+
38+
faberAgent = new Agent(faberConfig)
39+
faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages))
40+
faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap))
41+
await faberAgent.initialize()
42+
43+
aliceAgent = new Agent(aliceConfig)
44+
aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages))
45+
aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap))
46+
await aliceAgent.initialize()
47+
;[aliceConnection, faberConnection] = await makeConnection(aliceAgent, faberAgent)
48+
})
49+
50+
afterEach(async () => {
51+
await faberAgent.shutdown()
52+
await faberAgent.wallet.delete()
53+
await aliceAgent.shutdown()
54+
await aliceAgent.wallet.delete()
55+
})
56+
57+
test('Alice and Faber exchange messages', async () => {
58+
testLogger.test('Alice sends message to Faber')
59+
const helloRecord = await aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello')
60+
61+
expect(helloRecord.content).toBe('Hello')
62+
63+
testLogger.test('Faber waits for message from Alice')
64+
await waitForBasicMessage(faberAgent, {
65+
content: 'Hello',
66+
})
67+
68+
testLogger.test('Faber sends message to Alice')
69+
const replyRecord = await faberAgent.basicMessages.sendMessage(faberConnection.id, 'How are you?')
70+
expect(replyRecord.content).toBe('How are you?')
71+
72+
testLogger.test('Alice waits until she receives message from faber')
73+
await waitForBasicMessage(aliceAgent, {
74+
content: 'How are you?',
75+
})
76+
})
77+
78+
test('Alice is unable to send a message', async () => {
79+
testLogger.test('Alice sends message to Faber that is undeliverable')
80+
81+
const spy = jest.spyOn(aliceAgent.outboundTransports[0], 'sendMessage').mockRejectedValue(new Error('any error'))
82+
83+
await expect(aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello')).rejects.toThrowError(
84+
MessageSendingError
85+
)
86+
try {
87+
await aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello undeliverable')
88+
} catch (error) {
89+
const thrownError = error as MessageSendingError
90+
expect(thrownError.message).toEqual(
91+
`Message is undeliverable to connection ${aliceConnection.id} (${aliceConnection.theirLabel})`
92+
)
93+
testLogger.test('Error thrown includes the outbound message and recently created record id')
94+
expect(thrownError.outboundMessage.associatedRecord).toBeInstanceOf(BasicMessageRecord)
95+
expect(thrownError.outboundMessage.payload).toBeInstanceOf(BasicMessage)
96+
expect((thrownError.outboundMessage.payload as BasicMessage).content).toBe('Hello undeliverable')
97+
98+
testLogger.test('Created record can be found and deleted by id')
99+
const storedRecord = await aliceAgent.basicMessages.getById(thrownError.outboundMessage.associatedRecord!.id)
100+
expect(storedRecord).toBeInstanceOf(BasicMessageRecord)
101+
expect(storedRecord.content).toBe('Hello undeliverable')
102+
103+
await aliceAgent.basicMessages.deleteById(storedRecord.id)
104+
await expect(
105+
aliceAgent.basicMessages.getById(thrownError.outboundMessage.associatedRecord!.id)
106+
).rejects.toThrowError(RecordNotFoundError)
107+
}
108+
spy.mockClear()
109+
})
110+
})

packages/core/src/modules/basic-messages/services/BasicMessageService.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { AgentContext } from '../../../agent'
22
import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext'
3+
import type { Query } from '../../../storage/StorageService'
34
import type { ConnectionRecord } from '../../connections/repository/ConnectionRecord'
45
import type { BasicMessageStateChangedEvent } from '../BasicMessageEvents'
5-
import type { BasicMessageTags } from '../repository'
66

77
import { EventEmitter } from '../../../agent/EventEmitter'
88
import { injectable } from '../../../plugins'
@@ -35,7 +35,7 @@ export class BasicMessageService {
3535
await this.basicMessageRepository.save(agentContext, basicMessageRecord)
3636
this.emitStateChangedEvent(agentContext, basicMessageRecord, basicMessage)
3737

38-
return basicMessage
38+
return { message: basicMessage, record: basicMessageRecord }
3939
}
4040

4141
/**
@@ -65,7 +65,16 @@ export class BasicMessageService {
6565
})
6666
}
6767

68-
public async findAllByQuery(agentContext: AgentContext, query: Partial<BasicMessageTags>) {
68+
public async findAllByQuery(agentContext: AgentContext, query: Query<BasicMessageRecord>) {
6969
return this.basicMessageRepository.findByQuery(agentContext, query)
7070
}
71+
72+
public async getById(agentContext: AgentContext, basicMessageRecordId: string) {
73+
return this.basicMessageRepository.getById(agentContext, basicMessageRecordId)
74+
}
75+
76+
public async deleteById(agentContext: AgentContext, basicMessageRecordId: string) {
77+
const basicMessageRecord = await this.getById(agentContext, basicMessageRecordId)
78+
return this.basicMessageRepository.delete(agentContext, basicMessageRecord)
79+
}
7180
}

0 commit comments

Comments
 (0)