diff --git a/src/__tests__/helpers.ts b/src/__tests__/helpers.ts index 7037b8b7cd..0f060cc7f1 100644 --- a/src/__tests__/helpers.ts +++ b/src/__tests__/helpers.ts @@ -13,16 +13,16 @@ import { DidCommService, DidDoc, } from '../modules/connections' -import { ProofRecord, ProofState, ProofEventType, ProofStateChangedEvent } from '../modules/proofs' +import { ProofEventTypes, ProofRecord, ProofState, ProofStateChangedEvent } from '../modules/proofs' import { SchemaTemplate, CredentialDefinitionTemplate } from '../modules/ledger' import { CredentialRecord, CredentialOfferTemplate, - CredentialEventType, CredentialStateChangedEvent, CredentialState, + CredentialEventTypes, } from '../modules/credentials' -import { BasicMessage, BasicMessageEventType, BasicMessageReceivedEvent } from '../modules/basic-messages' +import { BasicMessage, BasicMessageEventTypes, BasicMessageReceivedEvent } from '../modules/basic-messages' import testLogger from './logger' import { NodeFileSystem } from '../storage/fs/NodeFileSystem' @@ -84,18 +84,18 @@ export async function waitForProofRecord( ): Promise { return new Promise((resolve) => { const listener = (event: ProofStateChangedEvent) => { - const previousStateMatches = previousState === undefined || event.previousState === previousState - const threadIdMatches = threadId === undefined || event.proofRecord.tags.threadId === threadId - const stateMatches = state === undefined || event.proofRecord.state === state + const previousStateMatches = previousState === undefined || event.payload.previousState === previousState + const threadIdMatches = threadId === undefined || event.payload.proofRecord.tags.threadId === threadId + const stateMatches = state === undefined || event.payload.proofRecord.state === state if (previousStateMatches && threadIdMatches && stateMatches) { - agent.proofs.events.removeListener(ProofEventType.StateChanged, listener) + agent.events.off(ProofEventTypes.ProofStateChanged, listener) - resolve(event.proofRecord) + resolve(event.payload.proofRecord) } } - agent.proofs.events.addListener(ProofEventType.StateChanged, listener) + agent.events.on(ProofEventTypes.ProofStateChanged, listener) }) } @@ -113,18 +113,18 @@ export async function waitForCredentialRecord( ): Promise { return new Promise((resolve) => { const listener = (event: CredentialStateChangedEvent) => { - const previousStateMatches = previousState === undefined || event.previousState === previousState - const threadIdMatches = threadId === undefined || event.credentialRecord.tags.threadId === threadId - const stateMatches = state === undefined || event.credentialRecord.state === state + const previousStateMatches = previousState === undefined || event.payload.previousState === previousState + const threadIdMatches = threadId === undefined || event.payload.credentialRecord.tags.threadId === threadId + const stateMatches = state === undefined || event.payload.credentialRecord.state === state if (previousStateMatches && threadIdMatches && stateMatches) { - agent.credentials.events.removeListener(CredentialEventType.StateChanged, listener) + agent.events.off(CredentialEventTypes.CredentialStateChanged, listener) - resolve(event.credentialRecord) + resolve(event.payload.credentialRecord) } } - agent.credentials.events.addListener(CredentialEventType.StateChanged, listener) + agent.events.on(CredentialEventTypes.CredentialStateChanged, listener) }) } @@ -134,17 +134,17 @@ export async function waitForBasicMessage( ): Promise { return new Promise((resolve) => { const listener = (event: BasicMessageReceivedEvent) => { - const verkeyMatches = verkey === undefined || event.verkey === verkey - const contentMatches = content === undefined || event.message.content === content + const verkeyMatches = verkey === undefined || event.payload.verkey === verkey + const contentMatches = content === undefined || event.payload.message.content === content if (verkeyMatches && contentMatches) { - agent.basicMessages.events.removeListener(BasicMessageEventType.MessageReceived, listener) + agent.events.off(BasicMessageEventTypes.BasicMessageReceived, listener) - resolve(event.message) + resolve(event.payload.message) } } - agent.basicMessages.events.addListener(BasicMessageEventType.MessageReceived, listener) + agent.events.on(BasicMessageEventTypes.BasicMessageReceived, listener) }) } diff --git a/src/agent/Agent.ts b/src/agent/Agent.ts index f4edc86d08..124371faf1 100644 --- a/src/agent/Agent.ts +++ b/src/agent/Agent.ts @@ -1,4 +1,3 @@ -import { EventEmitter } from 'events' import { container as baseContainer, DependencyContainer } from 'tsyringe' import { Logger } from '../logger' @@ -21,6 +20,8 @@ import { LedgerModule } from '../modules/ledger/LedgerModule' import { InMemoryMessageRepository } from '../storage/InMemoryMessageRepository' import { Symbols } from '../symbols' import { Transport } from './TransportService' +import { EventEmitter } from './EventEmitter' +import { AgentEventTypes, AgentMessageReceivedEvent } from './Events' export class Agent { protected agentConfig: AgentConfig @@ -45,11 +46,9 @@ export class Agent { this.agentConfig = new AgentConfig(initialConfig) this.logger = this.agentConfig.logger - this.eventEmitter = new EventEmitter() // Bind class based instances this.container.registerInstance(AgentConfig, this.agentConfig) - this.container.registerInstance(EventEmitter, this.eventEmitter) // Based on interfaces. Need to register which class to use this.container.registerInstance(Symbols.Logger, this.logger) @@ -76,6 +75,7 @@ export class Agent { }) // Resolve instances after everything is registered + this.eventEmitter = this.container.resolve(EventEmitter) this.messageSender = this.container.resolve(MessageSender) this.messageReceiver = this.container.resolve(MessageReceiver) this.wallet = this.container.resolve(Symbols.Wallet) @@ -93,8 +93,8 @@ export class Agent { } private listenForMessages() { - this.eventEmitter.addListener('agentMessage', async (payload) => { - await this.receiveMessage(payload) + this.eventEmitter.on(AgentEventTypes.AgentMessageReceived, async (event) => { + await this.receiveMessage(event.payload.message) }) } @@ -110,6 +110,10 @@ export class Agent { return this.messageSender.outboundTransporter } + public get events() { + return this.eventEmitter + } + public async init() { await this.wallet.init() diff --git a/src/agent/EventEmitter.ts b/src/agent/EventEmitter.ts new file mode 100644 index 0000000000..61b70e359f --- /dev/null +++ b/src/agent/EventEmitter.ts @@ -0,0 +1,20 @@ +import { Lifecycle, scoped } from 'tsyringe' +import { EventEmitter as NativeEventEmitter } from 'events' +import { BaseEvent } from './Events' + +@scoped(Lifecycle.ContainerScoped) +export class EventEmitter { + private eventEmitter = new NativeEventEmitter() + + public emit(data: T) { + this.eventEmitter.emit(data.type, data) + } + + public on(event: T['type'], listener: (data: T) => void | Promise) { + this.eventEmitter.on(event, listener) + } + + public off(event: T['type'], listener: (data: T) => void | Promise) { + this.eventEmitter.off(event, listener) + } +} diff --git a/src/agent/Events.ts b/src/agent/Events.ts new file mode 100644 index 0000000000..2f5e37812a --- /dev/null +++ b/src/agent/Events.ts @@ -0,0 +1,15 @@ +export enum AgentEventTypes { + AgentMessageReceived = 'AgentMessageReceived', +} + +export interface BaseEvent { + type: string + payload: Record +} + +export interface AgentMessageReceivedEvent extends BaseEvent { + type: typeof AgentEventTypes.AgentMessageReceived + payload: { + message: unknown + } +} diff --git a/src/decorators/signature/SignatureDecoratorUtils.test.ts b/src/decorators/signature/SignatureDecoratorUtils.test.ts index 29888dd8c3..30acd345ec 100644 --- a/src/decorators/signature/SignatureDecoratorUtils.test.ts +++ b/src/decorators/signature/SignatureDecoratorUtils.test.ts @@ -1,4 +1,3 @@ -import indy from 'indy-sdk' import { signData, unpackAndVerifySignatureDecorator } from './SignatureDecoratorUtils' import { IndyWallet } from '../../wallet/IndyWallet' import { SignatureDecorator } from './SignatureDecorator' @@ -13,9 +12,6 @@ jest.mock('../../utils/timestamp', () => { }) describe('Decorators | Signature | SignatureDecoratorUtils', () => { - const walletConfig = { id: 'wallet-1' + 'test1' } - const walletCredentials = { key: 'key' } - const data = { did: 'did', did_doc: { diff --git a/src/modules/basic-messages/BasicMessageEvents.ts b/src/modules/basic-messages/BasicMessageEvents.ts new file mode 100644 index 0000000000..79a450a2b1 --- /dev/null +++ b/src/modules/basic-messages/BasicMessageEvents.ts @@ -0,0 +1,15 @@ +import type { Verkey } from 'indy-sdk' +import { BaseEvent } from '../../agent/Events' +import { BasicMessage } from './messages' + +export enum BasicMessageEventTypes { + BasicMessageReceived = 'BasicMessageReceived', +} + +export interface BasicMessageReceivedEvent extends BaseEvent { + type: typeof BasicMessageEventTypes.BasicMessageReceived + payload: { + message: BasicMessage + verkey: Verkey + } +} diff --git a/src/modules/basic-messages/BasicMessagesModule.ts b/src/modules/basic-messages/BasicMessagesModule.ts index a16c159ea0..2d8a47e86b 100644 --- a/src/modules/basic-messages/BasicMessagesModule.ts +++ b/src/modules/basic-messages/BasicMessagesModule.ts @@ -1,5 +1,4 @@ import type { WalletQuery } from 'indy-sdk' -import { EventEmitter } from 'events' import { Lifecycle, scoped } from 'tsyringe' import { BasicMessageService } from './services' @@ -19,16 +18,6 @@ export class BasicMessagesModule { this.registerHandlers(dispatcher) } - /** - * Get the event emitter for the basic message service. Will emit message received events - * when basic messages are received. - * - * @returns event emitter for basic message related events - */ - public get events(): EventEmitter { - return this.basicMessageService - } - public async sendMessage(connection: ConnectionRecord, message: string) { const outboundMessage = await this.basicMessageService.send(message, connection) await this.messageSender.sendMessage(outboundMessage) diff --git a/src/modules/basic-messages/__tests__/BasicMessageService.test.ts b/src/modules/basic-messages/__tests__/BasicMessageService.test.ts index c8435369c0..478a0d580b 100644 --- a/src/modules/basic-messages/__tests__/BasicMessageService.test.ts +++ b/src/modules/basic-messages/__tests__/BasicMessageService.test.ts @@ -1,20 +1,19 @@ -import indy from 'indy-sdk' import { IndyWallet } from '../../../wallet/IndyWallet' import { Wallet } from '../../../wallet/Wallet' import { Repository } from '../../../storage/Repository' import { StorageService } from '../../../storage/StorageService' import { IndyStorageService } from '../../../storage/IndyStorageService' -import { BasicMessageService, BasicMessageEventType } from '../services' +import { BasicMessageService } from '../services' import { BasicMessageRecord } from '../repository/BasicMessageRecord' import { BasicMessage } from '../messages' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import { ConnectionRecord } from '../../connections' import { AgentConfig } from '../../../agent/AgentConfig' import { getBaseConfig } from '../../../__tests__/helpers' +import { EventEmitter } from '../../../agent/EventEmitter' +import { BasicMessageEventTypes, BasicMessageReceivedEvent } from '../BasicMessageEvents' describe('BasicMessageService', () => { - const walletConfig = { id: 'test-wallet' + '-BasicMessageServiceTest' } - const walletCredentials = { key: 'key' } const mockConnectionRecord = { id: 'd3849ac3-c981-455b-a1aa-a10bea6cead8', verkey: '71X9Y1aSPK11ariWUYQCYMjSewf2Kw2JFGeygEf9uZd9', @@ -40,15 +39,17 @@ describe('BasicMessageService', () => { describe('save', () => { let basicMessageRepository: Repository let basicMessageService: BasicMessageService + let eventEmitter: EventEmitter beforeEach(() => { basicMessageRepository = new Repository(BasicMessageRecord, storageService) - basicMessageService = new BasicMessageService(basicMessageRepository) + eventEmitter = new EventEmitter() + basicMessageService = new BasicMessageService(basicMessageRepository, eventEmitter) }) it(`emits newMessage with connection verkey and message itself`, async () => { const eventListenerMock = jest.fn() - basicMessageService.on(BasicMessageEventType.MessageReceived, eventListenerMock) + eventEmitter.on(BasicMessageEventTypes.BasicMessageReceived, eventListenerMock) const basicMessage = new BasicMessage({ id: '123', @@ -66,8 +67,11 @@ describe('BasicMessageService', () => { await basicMessageService.save(messageContext, mockConnectionRecord as ConnectionRecord) expect(eventListenerMock).toHaveBeenCalledWith({ - verkey: mockConnectionRecord.verkey, - message: messageContext.message, + type: 'BasicMessageReceived', + payload: { + verkey: mockConnectionRecord.verkey, + message: messageContext.message, + }, }) }) }) diff --git a/src/modules/basic-messages/index.ts b/src/modules/basic-messages/index.ts index a23fbcdfab..ec7994d368 100644 --- a/src/modules/basic-messages/index.ts +++ b/src/modules/basic-messages/index.ts @@ -1,3 +1,4 @@ export * from './messages' export * from './services' export * from './repository' +export * from './BasicMessageEvents' diff --git a/src/modules/basic-messages/services/BasicMessageService.ts b/src/modules/basic-messages/services/BasicMessageService.ts index 85d28ce4e9..883d204187 100644 --- a/src/modules/basic-messages/services/BasicMessageService.ts +++ b/src/modules/basic-messages/services/BasicMessageService.ts @@ -1,5 +1,4 @@ -import type { Verkey, WalletQuery } from 'indy-sdk' -import { EventEmitter } from 'events' +import type { WalletQuery } from 'indy-sdk' import { Lifecycle, scoped } from 'tsyringe' import { OutboundMessage } from '../../../types' @@ -9,23 +8,17 @@ import { ConnectionRecord } from '../../connections/repository/ConnectionRecord' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import { BasicMessage } from '../messages' import { BasicMessageRepository } from '../repository' - -export enum BasicMessageEventType { - MessageReceived = 'messageReceived', -} - -export interface BasicMessageReceivedEvent { - message: BasicMessage - verkey: Verkey -} +import { EventEmitter } from '../../../agent/EventEmitter' +import { BasicMessageEventTypes, BasicMessageReceivedEvent } from '../BasicMessageEvents' @scoped(Lifecycle.ContainerScoped) -export class BasicMessageService extends EventEmitter { +export class BasicMessageService { private basicMessageRepository: BasicMessageRepository + private eventEmitter: EventEmitter - public constructor(basicMessageRepository: BasicMessageRepository) { - super() + public constructor(basicMessageRepository: BasicMessageRepository, eventEmitter: EventEmitter) { this.basicMessageRepository = basicMessageRepository + this.eventEmitter = eventEmitter } public async send(message: string, connection: ConnectionRecord): Promise> { @@ -56,11 +49,10 @@ export class BasicMessageService extends EventEmitter { }) await this.basicMessageRepository.save(basicMessageRecord) - const event: BasicMessageReceivedEvent = { - message, - verkey: connection.verkey, - } - this.emit(BasicMessageEventType.MessageReceived, event) + this.eventEmitter.emit({ + type: BasicMessageEventTypes.BasicMessageReceived, + payload: { message, verkey: connection.verkey }, + }) } public async findAllByQuery(query: WalletQuery) { diff --git a/src/modules/connections/ConnectionEvents.ts b/src/modules/connections/ConnectionEvents.ts new file mode 100644 index 0000000000..e4cef97cd6 --- /dev/null +++ b/src/modules/connections/ConnectionEvents.ts @@ -0,0 +1,15 @@ +import { BaseEvent } from '../../agent/Events' +import { ConnectionRecord } from './repository/ConnectionRecord' +import { ConnectionState } from './models/ConnectionState' + +export enum ConnectionEventTypes { + ConnectionStateChanged = 'ConnectionStateChanged', +} + +export interface ConnectionStateChangedEvent extends BaseEvent { + type: typeof ConnectionEventTypes.ConnectionStateChanged + payload: { + connectionRecord: ConnectionRecord + previousState: ConnectionState | null + } +} diff --git a/src/modules/connections/ConnectionsModule.ts b/src/modules/connections/ConnectionsModule.ts index aae9e31ba8..43859bab34 100644 --- a/src/modules/connections/ConnectionsModule.ts +++ b/src/modules/connections/ConnectionsModule.ts @@ -1,5 +1,4 @@ import type { Verkey } from 'indy-sdk' -import { EventEmitter } from 'events' import { Lifecycle, scoped } from 'tsyringe' import { AgentConfig } from '../../agent/AgentConfig' @@ -42,16 +41,6 @@ export class ConnectionsModule { this.registerHandlers(dispatcher) } - /** - * Get the event emitter for the connection service. Will emit state changed events - * when the state of connections records changes. - * - * @returns event emitter for connection related state changes - */ - public get events(): EventEmitter { - return this.connectionService - } - public async createConnection(config?: { autoAcceptConnection?: boolean alias?: string diff --git a/src/modules/connections/__tests__/ConnectionService.test.ts b/src/modules/connections/__tests__/ConnectionService.test.ts index a0d2f44e18..cf28ef8062 100644 --- a/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/src/modules/connections/__tests__/ConnectionService.test.ts @@ -18,6 +18,7 @@ import { InboundMessageContext } from '../../../agent/models/InboundMessageConte import { SignatureDecorator } from '../../../decorators/signature/SignatureDecorator' import { JsonTransformer } from '../../../utils/JsonTransformer' import { getBaseConfig, getMockConnection } from '../../../__tests__/helpers' +import { EventEmitter } from '../../../agent/EventEmitter' jest.mock('./../../../storage/Repository') const ConnectionRepository = >>(Repository) @@ -32,6 +33,7 @@ describe('ConnectionService', () => { let agentConfig: AgentConfig let connectionRepository: Repository let connectionService: ConnectionService + let eventEmitter: EventEmitter beforeAll(async () => { agentConfig = new AgentConfig(initConfig) @@ -49,7 +51,8 @@ describe('ConnectionService', () => { ConnectionRepository.mockClear() connectionRepository = new ConnectionRepository() - connectionService = new ConnectionService(wallet, agentConfig, connectionRepository) + eventEmitter = new EventEmitter() + connectionService = new ConnectionService(wallet, agentConfig, connectionRepository, eventEmitter) }) describe('createConnectionWithInvitation', () => { diff --git a/src/modules/connections/index.ts b/src/modules/connections/index.ts index f35a723422..df0a38e559 100644 --- a/src/modules/connections/index.ts +++ b/src/modules/connections/index.ts @@ -2,3 +2,4 @@ export * from './messages' export * from './services' export * from './models' export * from './repository' +export * from './ConnectionEvents' diff --git a/src/modules/connections/services/ConnectionService.ts b/src/modules/connections/services/ConnectionService.ts index 5dec02f6ab..3cd2d492e1 100644 --- a/src/modules/connections/services/ConnectionService.ts +++ b/src/modules/connections/services/ConnectionService.ts @@ -1,5 +1,4 @@ import type { Verkey } from 'indy-sdk' -import { EventEmitter } from 'events' import { validateOrReject } from 'class-validator' import { inject, scoped, Lifecycle } from 'tsyringe' @@ -30,15 +29,8 @@ import { InboundMessageContext } from '../../../agent/models/InboundMessageConte import { JsonTransformer } from '../../../utils/JsonTransformer' import { AgentMessage } from '../../../agent/AgentMessage' import { Symbols } from '../../../symbols' - -export enum ConnectionEventType { - StateChanged = 'stateChanged', -} - -export interface ConnectionStateChangedEvent { - connectionRecord: ConnectionRecord - previousState: ConnectionState | null -} +import { EventEmitter } from '../../../agent/EventEmitter' +import { ConnectionEventTypes, ConnectionStateChangedEvent } from '../ConnectionEvents' export interface ConnectionProtocolMsgReturnType { message: MessageType @@ -46,20 +38,22 @@ export interface ConnectionProtocolMsgReturnType({ + type: ConnectionEventTypes.ConnectionStateChanged, + payload: { + connectionRecord: connectionRecord, + previousState: null, + }, + }) return { connectionRecord: connectionRecord, message: invitation } } @@ -130,12 +126,13 @@ export class ConnectionService extends EventEmitter { }) await this.connectionRepository.update(connectionRecord) - - const event: ConnectionStateChangedEvent = { - connectionRecord: connectionRecord, - previousState: null, - } - this.emit(ConnectionEventType.StateChanged, event) + this.eventEmitter.emit({ + type: ConnectionEventTypes.ConnectionStateChanged, + payload: { + connectionRecord: connectionRecord, + previousState: null, + }, + }) return connectionRecord } @@ -348,12 +345,13 @@ export class ConnectionService extends EventEmitter { connectionRecord.state = newState await this.connectionRepository.update(connectionRecord) - const event: ConnectionStateChangedEvent = { - connectionRecord: connectionRecord, - previousState, - } - - this.emit(ConnectionEventType.StateChanged, event) + this.eventEmitter.emit({ + type: ConnectionEventTypes.ConnectionStateChanged, + payload: { + connectionRecord: connectionRecord, + previousState, + }, + }) } private async createConnection(options: { @@ -488,14 +486,14 @@ export class ConnectionService extends EventEmitter { if (connection && isConnected(connection)) return connection return new Promise((resolve) => { - const listener = ({ connectionRecord: connectionRecord }: ConnectionStateChangedEvent) => { + const listener = ({ payload: { connectionRecord } }: ConnectionStateChangedEvent) => { if (isConnected(connectionRecord)) { - this.off(ConnectionEventType.StateChanged, listener) + this.eventEmitter.off(ConnectionEventTypes.ConnectionStateChanged, listener) resolve(connectionRecord) } } - this.on(ConnectionEventType.StateChanged, listener) + this.eventEmitter.on(ConnectionEventTypes.ConnectionStateChanged, listener) }) } } diff --git a/src/modules/credentials/CredentialEvents.ts b/src/modules/credentials/CredentialEvents.ts new file mode 100644 index 0000000000..e4ba0b1b56 --- /dev/null +++ b/src/modules/credentials/CredentialEvents.ts @@ -0,0 +1,14 @@ +import { BaseEvent } from '../../agent/Events' +import { CredentialState } from './CredentialState' +import { CredentialRecord } from './repository/CredentialRecord' + +export enum CredentialEventTypes { + CredentialStateChanged = 'CredentialStateChanged', +} +export interface CredentialStateChangedEvent extends BaseEvent { + type: typeof CredentialEventTypes.CredentialStateChanged + payload: { + credentialRecord: CredentialRecord + previousState: CredentialState | null + } +} diff --git a/src/modules/credentials/CredentialsModule.ts b/src/modules/credentials/CredentialsModule.ts index f5464aa249..8e7a7fa953 100644 --- a/src/modules/credentials/CredentialsModule.ts +++ b/src/modules/credentials/CredentialsModule.ts @@ -4,10 +4,8 @@ import { CredentialRecord } from './repository/CredentialRecord' import { createOutboundMessage } from '../../agent/helpers' import { MessageSender } from '../../agent/MessageSender' import { ConnectionService } from '../connections' -import { EventEmitter } from 'events' import { CredentialOfferTemplate, CredentialService } from './services' import { ProposeCredentialMessageOptions } from './messages' -import { IndyCredentialInfo } from './models' import { Dispatcher } from '../../agent/Dispatcher' import { ProposeCredentialHandler, @@ -35,16 +33,6 @@ export class CredentialsModule { this.registerHandlers(dispatcher) } - /** - * Get the event emitter for the credential service. Will emit state changed events - * when the state of credential records changes. - * - * @returns event emitter for credential related state changes - */ - public get events(): EventEmitter { - return this.credentialService - } - /** * Initiate a new credential exchange as holder by sending a credential proposal message * to the connection with the specified connection id. diff --git a/src/modules/credentials/__tests__/CredentialService.test.ts b/src/modules/credentials/__tests__/CredentialService.test.ts index d7399d62a9..699926514b 100644 --- a/src/modules/credentials/__tests__/CredentialService.test.ts +++ b/src/modules/credentials/__tests__/CredentialService.test.ts @@ -1,6 +1,6 @@ import type { WalletQuery, CredDef } from 'indy-sdk' import { Wallet } from '../../../wallet/Wallet' -import { CredentialOfferTemplate, CredentialService, CredentialEventType } from '../services' +import { CredentialOfferTemplate, CredentialService } from '../services' import { CredentialRecord, CredentialRecordMetadata, CredentialRecordTags } from '../repository/CredentialRecord' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import { CredentialState } from '../CredentialState' @@ -31,6 +31,8 @@ import { LedgerService } from '../../ledger/services' import { IndyIssuerService } from '../../indy/services/IndyIssuerService' import { IndyHolderService } from '../../indy/services/IndyHolderService' import { getBaseConfig, getMockConnection } from '../../../__tests__/helpers' +import { EventEmitter } from '../../../agent/EventEmitter' +import { CredentialEventTypes, CredentialStateChangedEvent } from '../CredentialEvents' // Mock classes jest.mock('./../repository/CredentialRepository') @@ -133,6 +135,7 @@ describe('CredentialService', () => { let repositoryFindMock: jest.Mock, [string]> let repositoryFindByQueryMock: jest.Mock, [WalletQuery]> let ledgerServiceGetCredDef: jest.Mock, [string]> + let eventEmitter: EventEmitter beforeAll(async () => { wallet = new StubWallet() @@ -149,15 +152,16 @@ describe('CredentialService', () => { indyIssuerService = new IndyIssuerServiceMock() indyHolderService = new IndyHolderServiceMock() ledgerService = new LedgerServiceMock() + eventEmitter = new EventEmitter() credentialService = new CredentialService( - wallet, credentialRepository, { getById: () => Promise.resolve(connection) } as any, ledgerService, new AgentConfig(getBaseConfig('CredentialServiceTest')), indyIssuerService, - indyHolderService + indyHolderService, + eventEmitter ) // make separate repositoryFindMock variable to get the correct jest mock typing @@ -206,16 +210,17 @@ describe('CredentialService', () => { test(`emits stateChange event with a new credential in ${CredentialState.OfferSent} state`, async () => { const eventListenerMock = jest.fn() - credentialService.on(CredentialEventType.StateChanged, eventListenerMock) + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) await credentialService.createOffer(connection, credentialTemplate) - expect(eventListenerMock).toHaveBeenCalledTimes(1) - const [[event]] = eventListenerMock.mock.calls - expect(event).toMatchObject({ - previousState: null, - credentialRecord: { - state: CredentialState.OfferSent, + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: null, + credentialRecord: expect.objectContaining({ + state: CredentialState.OfferSent, + }), }, }) }) @@ -294,18 +299,19 @@ describe('CredentialService', () => { test(`emits stateChange event with ${CredentialState.OfferReceived}`, async () => { const eventListenerMock = jest.fn() - credentialService.on(CredentialEventType.StateChanged, eventListenerMock) + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // when await credentialService.processOffer(messageContext) // then - expect(eventListenerMock).toHaveBeenCalledTimes(1) - const [[event]] = eventListenerMock.mock.calls - expect(event).toMatchObject({ - previousState: null, - credentialRecord: { - state: CredentialState.OfferReceived, + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: null, + credentialRecord: expect.objectContaining({ + state: CredentialState.OfferReceived, + }), }, }) }) @@ -338,18 +344,19 @@ describe('CredentialService', () => { test(`emits stateChange event with ${CredentialState.RequestSent}`, async () => { const eventListenerMock = jest.fn() - credentialService.on(CredentialEventType.StateChanged, eventListenerMock) + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // when await credentialService.createRequest(credentialRecord) // then - expect(eventListenerMock).toHaveBeenCalledTimes(1) - const [[event]] = eventListenerMock.mock.calls - expect(event).toMatchObject({ - previousState: CredentialState.OfferReceived, - credentialRecord: { - state: CredentialState.RequestSent, + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: CredentialState.OfferReceived, + credentialRecord: expect.objectContaining({ + state: CredentialState.RequestSent, + }), }, }) }) @@ -439,17 +446,18 @@ describe('CredentialService', () => { test(`emits stateChange event from ${CredentialState.OfferSent} to ${CredentialState.RequestReceived}`, async () => { const eventListenerMock = jest.fn() - credentialService.on(CredentialEventType.StateChanged, eventListenerMock) + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) repositoryFindByQueryMock.mockReturnValue(Promise.resolve([credential])) await credentialService.processRequest(messageContext) - expect(eventListenerMock).toHaveBeenCalledTimes(1) - const [[event]] = eventListenerMock.mock.calls - expect(event).toMatchObject({ - previousState: CredentialState.OfferSent, - credentialRecord: { - state: CredentialState.RequestReceived, + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: CredentialState.OfferSent, + credentialRecord: expect.objectContaining({ + state: CredentialState.RequestReceived, + }), }, }) }) @@ -499,7 +507,7 @@ describe('CredentialService', () => { test(`emits stateChange event from ${CredentialState.RequestReceived} to ${CredentialState.CredentialIssued}`, async () => { const eventListenerMock = jest.fn() - credentialService.on(CredentialEventType.StateChanged, eventListenerMock) + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // given repositoryFindMock.mockReturnValue(Promise.resolve(credential)) @@ -508,12 +516,13 @@ describe('CredentialService', () => { await credentialService.createCredential(credential) // then - expect(eventListenerMock).toHaveBeenCalledTimes(1) - const [[event]] = eventListenerMock.mock.calls - expect(event).toMatchObject({ - previousState: CredentialState.RequestReceived, - credentialRecord: { - state: CredentialState.CredentialIssued, + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: CredentialState.RequestReceived, + credentialRecord: expect.objectContaining({ + state: CredentialState.CredentialIssued, + }), }, }) }) @@ -662,7 +671,7 @@ describe('CredentialService', () => { test(`emits stateChange event from ${CredentialState.RequestSent} to ${CredentialState.CredentialReceived}`, async () => { const eventListenerMock = jest.fn() - credentialService.on(CredentialEventType.StateChanged, eventListenerMock) + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // given repositoryFindByQueryMock.mockReturnValue(Promise.resolve([credential])) @@ -671,12 +680,13 @@ describe('CredentialService', () => { await credentialService.processCredential(messageContext) // then - expect(eventListenerMock).toHaveBeenCalledTimes(1) - const [[event]] = eventListenerMock.mock.calls - expect(event).toMatchObject({ - previousState: CredentialState.RequestSent, - credentialRecord: { - state: CredentialState.CredentialReceived, + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: CredentialState.RequestSent, + credentialRecord: expect.objectContaining({ + state: CredentialState.CredentialReceived, + }), }, }) }) @@ -762,7 +772,7 @@ describe('CredentialService', () => { test(`emits stateChange event from ${CredentialState.CredentialReceived} to ${CredentialState.Done}`, async () => { const eventListenerMock = jest.fn() - credentialService.on(CredentialEventType.StateChanged, eventListenerMock) + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // given repositoryFindMock.mockReturnValue(Promise.resolve(credential)) @@ -771,12 +781,13 @@ describe('CredentialService', () => { await credentialService.createAck(credential) // then - expect(eventListenerMock).toHaveBeenCalledTimes(1) - const [[event]] = eventListenerMock.mock.calls - expect(event).toMatchObject({ - previousState: CredentialState.CredentialReceived, - credentialRecord: { - state: CredentialState.Done, + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: CredentialState.CredentialReceived, + credentialRecord: expect.objectContaining({ + state: CredentialState.Done, + }), }, }) }) @@ -851,7 +862,7 @@ describe('CredentialService', () => { test(`emits stateChange event from ${CredentialState.CredentialIssued} to ${CredentialState.Done}`, async () => { const eventListenerMock = jest.fn() - credentialService.on(CredentialEventType.StateChanged, eventListenerMock) + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // given repositoryFindByQueryMock.mockReturnValue(Promise.resolve([credential])) @@ -860,12 +871,13 @@ describe('CredentialService', () => { await credentialService.processAck(messageContext) // then - expect(eventListenerMock).toHaveBeenCalledTimes(1) - const [[event]] = eventListenerMock.mock.calls - expect(event).toMatchObject({ - previousState: CredentialState.CredentialIssued, - credentialRecord: { - state: CredentialState.Done, + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: CredentialState.CredentialIssued, + credentialRecord: expect.objectContaining({ + state: CredentialState.Done, + }), }, }) }) diff --git a/src/modules/credentials/index.ts b/src/modules/credentials/index.ts index 52fff14c90..d656650eca 100644 --- a/src/modules/credentials/index.ts +++ b/src/modules/credentials/index.ts @@ -4,3 +4,4 @@ export * from './CredentialUtils' export * from './models' export * from './repository' export * from './CredentialState' +export * from './CredentialEvents' diff --git a/src/modules/credentials/services/CredentialService.ts b/src/modules/credentials/services/CredentialService.ts index c922757a0c..222172a075 100644 --- a/src/modules/credentials/services/CredentialService.ts +++ b/src/modules/credentials/services/CredentialService.ts @@ -1,6 +1,5 @@ -import { inject, scoped, Lifecycle } from 'tsyringe' +import { scoped, Lifecycle } from 'tsyringe' import type { CredDefId } from 'indy-sdk' -import { EventEmitter } from 'events' import { uuid } from '../../../utils/uuid' import { AgentMessage } from '../../../agent/AgentMessage' @@ -10,7 +9,6 @@ import { Attachment, AttachmentData } from '../../../decorators/attachment/Attac import { ConnectionService, ConnectionRecord } from '../../connections' import { CredentialRecord } from '../repository/CredentialRecord' import { JsonEncoder } from '../../../utils/JsonEncoder' -import { Wallet } from '../../../wallet/Wallet' import { CredentialState } from '../CredentialState' import { CredentialUtils } from '../CredentialUtils' @@ -30,17 +28,9 @@ import { AckStatus } from '../../common' import { Logger } from '../../../logger' import { AgentConfig } from '../../../agent/AgentConfig' import { CredentialRepository } from '../repository' -import { Symbols } from '../../../symbols' import { IndyIssuerService, IndyHolderService } from '../../indy' - -export enum CredentialEventType { - StateChanged = 'stateChanged', -} - -export interface CredentialStateChangedEvent { - credentialRecord: CredentialRecord - previousState: CredentialState -} +import { CredentialEventTypes, CredentialStateChangedEvent } from '../CredentialEvents' +import { EventEmitter } from '../../../agent/EventEmitter' export interface CredentialProtocolMsgReturnType { message: MessageType @@ -48,32 +38,31 @@ export interface CredentialProtocolMsgReturnType({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: null, + }, }) return { message: proposalMessage, credentialRecord } @@ -188,9 +180,12 @@ export class CredentialService extends EventEmitter { // Save record await this.credentialRepository.save(credentialRecord) - this.emit(CredentialEventType.StateChanged, { - credentialRecord, - previousState: null, + this.eventEmitter.emit({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: null, + }, }) } @@ -287,9 +282,12 @@ export class CredentialService extends EventEmitter { }) await this.credentialRepository.save(credentialRecord) - this.emit(CredentialEventType.StateChanged, { - credentialRecord, - previousState: null, + this.eventEmitter.emit({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: null, + }, }) return { message: credentialOfferMessage, credentialRecord } @@ -354,9 +352,12 @@ export class CredentialService extends EventEmitter { // Save in repository await this.credentialRepository.save(credentialRecord) - this.emit(CredentialEventType.StateChanged, { - credentialRecord, - previousState: null, + this.eventEmitter.emit({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: null, + }, }) } @@ -716,12 +717,13 @@ export class CredentialService extends EventEmitter { credentialRecord.state = newState await this.credentialRepository.update(credentialRecord) - const event: CredentialStateChangedEvent = { - credentialRecord, - previousState: previousState, - } - - this.emit(CredentialEventType.StateChanged, event) + this.eventEmitter.emit({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: previousState, + }, + }) } } diff --git a/src/modules/proofs/ProofEvents.ts b/src/modules/proofs/ProofEvents.ts new file mode 100644 index 0000000000..f8715652a7 --- /dev/null +++ b/src/modules/proofs/ProofEvents.ts @@ -0,0 +1,15 @@ +import { BaseEvent } from '../../agent/Events' +import { ProofState } from './ProofState' +import { ProofRecord } from './repository' + +export enum ProofEventTypes { + ProofStateChanged = 'ProofStateChanged', +} + +export interface ProofStateChangedEvent extends BaseEvent { + type: typeof ProofEventTypes.ProofStateChanged + payload: { + proofRecord: ProofRecord + previousState: ProofState | null + } +} diff --git a/src/modules/proofs/ProofsModule.ts b/src/modules/proofs/ProofsModule.ts index 95478f844f..bebbcfd91f 100644 --- a/src/modules/proofs/ProofsModule.ts +++ b/src/modules/proofs/ProofsModule.ts @@ -1,5 +1,4 @@ import { Lifecycle, scoped } from 'tsyringe' -import { EventEmitter } from 'events' import { createOutboundMessage } from '../../agent/helpers' import { MessageSender } from '../../agent/MessageSender' @@ -35,16 +34,6 @@ export class ProofsModule { this.registerHandlers(dispatcher) } - /** - * Get the event emitter for the proof service. Will emit state changed events - * when the state of proof records changes. - * - * @returns event emitter for proof related actions - */ - public get events(): EventEmitter { - return this.proofService - } - /** * Initiate a new presentation exchange as prover by sending a presentation proposal message * to the connection with the specified connection id. diff --git a/src/modules/proofs/index.ts b/src/modules/proofs/index.ts index 5845a7de5b..294461e655 100644 --- a/src/modules/proofs/index.ts +++ b/src/modules/proofs/index.ts @@ -3,3 +3,4 @@ export * from './models' export * from './services' export * from './ProofState' export * from './repository' +export * from './ProofEvents' diff --git a/src/modules/proofs/services/ProofService.ts b/src/modules/proofs/services/ProofService.ts index a5f201598e..4becfb4087 100644 --- a/src/modules/proofs/services/ProofService.ts +++ b/src/modules/proofs/services/ProofService.ts @@ -1,6 +1,5 @@ import { inject, scoped, Lifecycle } from 'tsyringe' import type { IndyProof, Schema, CredDef } from 'indy-sdk' -import { EventEmitter } from 'events' import { validateOrReject } from 'class-validator' import { AgentMessage } from '../../../agent/AgentMessage' @@ -42,16 +41,8 @@ import { Logger } from '../../../logger' import { ProofRepository } from '../repository' import { Symbols } from '../../../symbols' import { IndyHolderService, IndyVerifierService } from '../../indy' - -export enum ProofEventType { - StateChanged = 'stateChanged', -} - -export interface ProofStateChangedEvent { - proofRecord: ProofRecord - previousState: ProofState -} - +import { EventEmitter } from '../../../agent/EventEmitter' +import { ProofEventTypes, ProofStateChangedEvent } from '../ProofEvents' export interface ProofProtocolMsgReturnType { message: MessageType proofRecord: ProofRecord @@ -63,13 +54,14 @@ export interface ProofProtocolMsgReturnType { * @todo validate attachments / messages */ @scoped(Lifecycle.ContainerScoped) -export class ProofService extends EventEmitter { +export class ProofService { private proofRepository: ProofRepository private ledgerService: LedgerService private wallet: Wallet private logger: Logger private indyHolderService: IndyHolderService private indyVerifierService: IndyVerifierService + private eventEmitter: EventEmitter public constructor( proofRepository: ProofRepository, @@ -77,16 +69,16 @@ export class ProofService extends EventEmitter { @inject(Symbols.Wallet) wallet: Wallet, agentConfig: AgentConfig, indyHolderService: IndyHolderService, - indyVerifierService: IndyVerifierService + indyVerifierService: IndyVerifierService, + eventEmitter: EventEmitter ) { - super() - this.proofRepository = proofRepository this.ledgerService = ledgerService this.wallet = wallet this.logger = agentConfig.logger this.indyHolderService = indyHolderService this.indyVerifierService = indyVerifierService + this.eventEmitter = eventEmitter } /** @@ -123,7 +115,10 @@ export class ProofService extends EventEmitter { tags: { threadId: proposalMessage.threadId }, }) await this.proofRepository.save(proofRecord) - this.emit(ProofEventType.StateChanged, { proofRecord, previousState: null }) + this.eventEmitter.emit({ + type: ProofEventTypes.ProofStateChanged, + payload: { proofRecord, previousState: null }, + }) return { message: proposalMessage, proofRecord } } @@ -208,9 +203,12 @@ export class ProofService extends EventEmitter { // Save record await this.proofRepository.save(proofRecord) - this.emit(ProofEventType.StateChanged, { - proofRecord, - previousState: null, + this.eventEmitter.emit({ + type: ProofEventTypes.ProofStateChanged, + payload: { + proofRecord, + previousState: null, + }, }) } @@ -302,7 +300,10 @@ export class ProofService extends EventEmitter { }) await this.proofRepository.save(proofRecord) - this.emit(ProofEventType.StateChanged, { proofRecord, previousState: null }) + this.eventEmitter.emit({ + type: ProofEventTypes.ProofStateChanged, + payload: { proofRecord, previousState: null }, + }) return { message: requestPresentationMessage, proofRecord } } @@ -363,9 +364,9 @@ export class ProofService extends EventEmitter { // Save in repository await this.proofRepository.save(proofRecord) - this.emit(ProofEventType.StateChanged, { - proofRecord, - previousState: null, + this.eventEmitter.emit({ + type: ProofEventTypes.ProofStateChanged, + payload: { proofRecord, previousState: null }, }) } @@ -867,12 +868,10 @@ export class ProofService extends EventEmitter { proofRecord.state = newState await this.proofRepository.update(proofRecord) - const event: ProofStateChangedEvent = { - proofRecord, - previousState: previousState, - } - - this.emit(ProofEventType.StateChanged, event) + this.eventEmitter.emit({ + type: ProofEventTypes.ProofStateChanged, + payload: { proofRecord, previousState: previousState }, + }) } /** diff --git a/src/modules/routing/RoutingModule.ts b/src/modules/routing/RoutingModule.ts index 3b84026c95..50db2a86d8 100644 --- a/src/modules/routing/RoutingModule.ts +++ b/src/modules/routing/RoutingModule.ts @@ -1,4 +1,3 @@ -import { EventEmitter } from 'events' import { Lifecycle, scoped } from 'tsyringe' import type { Verkey } from 'indy-sdk' @@ -17,6 +16,7 @@ import { } from './handlers' import { Logger } from '../../logger' import { ReturnRouteTypes } from '../../decorators/transport/TransportDecorator' +import { EventEmitter } from '../../agent/EventEmitter' @scoped(Lifecycle.ContainerScoped) export class RoutingModule { diff --git a/src/modules/routing/handlers/BatchHandler.ts b/src/modules/routing/handlers/BatchHandler.ts index ad08d4b77d..6899845c24 100644 --- a/src/modules/routing/handlers/BatchHandler.ts +++ b/src/modules/routing/handlers/BatchHandler.ts @@ -1,4 +1,5 @@ -import { EventEmitter } from 'events' +import { EventEmitter } from '../../../agent/EventEmitter' +import { AgentEventTypes, AgentMessageReceivedEvent } from '../../../agent/Events' import { Handler, HandlerInboundMessage } from '../../../agent/Handler' import { BatchMessage } from '../messages' @@ -7,8 +8,8 @@ export class BatchHandler implements Handler { private eventEmitter: EventEmitter public supportedMessages = [BatchMessage] - public constructor(eventEmmiter: EventEmitter) { - this.eventEmitter = eventEmmiter + public constructor(eventEmitter: EventEmitter) { + this.eventEmitter = eventEmitter } public async handle(messageContext: HandlerInboundMessage) { @@ -20,7 +21,12 @@ export class BatchHandler implements Handler { const forwardedMessages = message.messages forwardedMessages.forEach((message) => { - this.eventEmitter.emit('agentMessage', message.message) + this.eventEmitter.emit({ + type: AgentEventTypes.AgentMessageReceived, + payload: { + message: message.message, + }, + }) }) } } diff --git a/src/storage/Repository.ts b/src/storage/Repository.ts index e3da6c8ad4..606371439f 100644 --- a/src/storage/Repository.ts +++ b/src/storage/Repository.ts @@ -14,7 +14,7 @@ export class Repository { } public async save(record: T): Promise { - this.storageService.save(record) + return this.storageService.save(record) } public async update(record: T): Promise {