Skip to content

Commit

Permalink
feat: initial plugin api
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <[email protected]>
  • Loading branch information
TimoGlastra committed Jun 28, 2022
1 parent 3c84265 commit 0f56e8c
Show file tree
Hide file tree
Showing 103 changed files with 714 additions and 290 deletions.
125 changes: 86 additions & 39 deletions packages/core/src/agent/Agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,37 @@ import type { DependencyContainer } from 'tsyringe'
import { concatMap, takeUntil } from 'rxjs/operators'
import { container as baseContainer } from 'tsyringe'

import { CachePlugin } from '../cache/plugin'
import { InjectionSymbols } from '../constants'
import { CryptoPlugin } from '../crypto/plugin'
import { AriesFrameworkError } from '../error'
import { BasicMessagesModule } from '../modules/basic-messages/BasicMessagesModule'
import { ConnectionsModule } from '../modules/connections/ConnectionsModule'
import { CredentialsModule } from '../modules/credentials/CredentialsModule'
import { DidsModule } from '../modules/dids/DidsModule'
import { DiscoverFeaturesModule } from '../modules/discover-features'
import { GenericRecordsModule } from '../modules/generic-records/GenericRecordsModule'
import { IndyPlugin } from '../modules/indy/plugin'
import { LedgerModule } from '../modules/ledger/LedgerModule'
import { OutOfBandModule } from '../modules/oob/OutOfBandModule'
import { ProofsModule } from '../modules/proofs/ProofsModule'
import { QuestionAnswerModule } from '../modules/question-answer/QuestionAnswerModule'
import { MediatorModule } from '../modules/routing/MediatorModule'
import { RecipientModule } from '../modules/routing/RecipientModule'
import { DependencyManager } from '../plugins'
import { StorageUpdateService } from '../storage'
import { InMemoryMessageRepository } from '../storage/InMemoryMessageRepository'
import { IndyStorageService } from '../storage/IndyStorageService'
import { UpdateAssistant } from '../storage/migration/UpdateAssistant'
import { DEFAULT_UPDATE_CONFIG } from '../storage/migration/updates'
import { DidCommMessagePlugin, StorageUpdatePlugin } from '../storage/plugin'
import { IndyWallet } from '../wallet/IndyWallet'
import { WalletModule } from '../wallet/WalletModule'
import { WalletError } from '../wallet/error'

import { AgentConfig } from './AgentConfig'
import { Dispatcher } from './Dispatcher'
import { EnvelopeService } from './EnvelopeService'
import { EventEmitter } from './EventEmitter'
import { AgentEventTypes } from './Events'
import { MessageReceiver } from './MessageReceiver'
Expand All @@ -45,7 +52,7 @@ import { TransportService } from './TransportService'
export class Agent {
protected agentConfig: AgentConfig
protected logger: Logger
protected container: DependencyContainer
public readonly dependencyManager: DependencyManager
protected eventEmitter: EventEmitter
protected messageReceiver: MessageReceiver
protected transportService: TransportService
Expand Down Expand Up @@ -74,28 +81,13 @@ export class Agent {
injectionContainer?: DependencyContainer
) {
// Take input container or child container so we don't interfere with anything outside of this agent
this.container = injectionContainer ?? baseContainer.createChildContainer()
const container = injectionContainer ?? baseContainer.createChildContainer()

this.dependencyManager = new DependencyManager(container)

this.agentConfig = new AgentConfig(initialConfig, dependencies)
this.logger = this.agentConfig.logger

// Bind class based instances
this.container.registerInstance(AgentConfig, this.agentConfig)

// Based on interfaces. Need to register which class to use
if (!this.container.isRegistered(InjectionSymbols.Wallet)) {
this.container.register(InjectionSymbols.Wallet, { useToken: IndyWallet })
}
if (!this.container.isRegistered(InjectionSymbols.Logger)) {
this.container.registerInstance(InjectionSymbols.Logger, this.logger)
}
if (!this.container.isRegistered(InjectionSymbols.StorageService)) {
this.container.registerSingleton(InjectionSymbols.StorageService, IndyStorageService)
}
if (!this.container.isRegistered(InjectionSymbols.MessageRepository)) {
this.container.registerSingleton(InjectionSymbols.MessageRepository, InMemoryMessageRepository)
}

this.logger.info('Creating agent with config', {
...initialConfig,
// Prevent large object being logged.
Expand All @@ -111,27 +103,29 @@ export class Agent {
)
}

this.registerDependencies(this.dependencyManager)

// 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.transportService = this.container.resolve(TransportService)
this.walletService = this.container.resolve(InjectionSymbols.Wallet)
this.eventEmitter = this.dependencyManager.resolve(EventEmitter)
this.messageSender = this.dependencyManager.resolve(MessageSender)
this.messageReceiver = this.dependencyManager.resolve(MessageReceiver)
this.transportService = this.dependencyManager.resolve(TransportService)
this.walletService = this.dependencyManager.resolve(InjectionSymbols.Wallet)

// We set the modules in the constructor because that allows to set them as read-only
this.connections = this.container.resolve(ConnectionsModule)
this.credentials = this.container.resolve(CredentialsModule) as CredentialsModule
this.proofs = this.container.resolve(ProofsModule)
this.mediator = this.container.resolve(MediatorModule)
this.mediationRecipient = this.container.resolve(RecipientModule)
this.basicMessages = this.container.resolve(BasicMessagesModule)
this.questionAnswer = this.container.resolve(QuestionAnswerModule)
this.genericRecords = this.container.resolve(GenericRecordsModule)
this.ledger = this.container.resolve(LedgerModule)
this.discovery = this.container.resolve(DiscoverFeaturesModule)
this.dids = this.container.resolve(DidsModule)
this.wallet = this.container.resolve(WalletModule)
this.oob = this.container.resolve(OutOfBandModule)
this.connections = this.dependencyManager.resolve(ConnectionsModule)
this.credentials = this.dependencyManager.resolve(CredentialsModule) as CredentialsModule
this.proofs = this.dependencyManager.resolve(ProofsModule)
this.mediator = this.dependencyManager.resolve(MediatorModule)
this.mediationRecipient = this.dependencyManager.resolve(RecipientModule)
this.basicMessages = this.dependencyManager.resolve(BasicMessagesModule)
this.questionAnswer = this.dependencyManager.resolve(QuestionAnswerModule)
this.genericRecords = this.dependencyManager.resolve(GenericRecordsModule)
this.ledger = this.dependencyManager.resolve(LedgerModule)
this.discovery = this.dependencyManager.resolve(DiscoverFeaturesModule)
this.dids = this.dependencyManager.resolve(DidsModule)
this.wallet = this.dependencyManager.resolve(WalletModule)
this.oob = this.dependencyManager.resolve(OutOfBandModule)

// Listen for new messages (either from transports or somewhere else in the framework / extensions)
this.messageSubscription = this.eventEmitter
Expand Down Expand Up @@ -187,7 +181,7 @@ export class Agent {
}

// Make sure the storage is up to date
const storageUpdateService = this.container.resolve(StorageUpdateService)
const storageUpdateService = this.dependencyManager.resolve(StorageUpdateService)
const isStorageUpToDate = await storageUpdateService.isUpToDate()
this.logger.info(`Agent storage is ${isStorageUpToDate ? '' : 'not '}up to date.`)

Expand Down Expand Up @@ -269,7 +263,7 @@ export class Agent {
}

public get injectionContainer() {
return this.container
return this.dependencyManager.container
}

public get config() {
Expand Down Expand Up @@ -304,4 +298,57 @@ export class Agent {
}
return connection
}

private registerDependencies(dependencyManager: DependencyManager) {
dependencyManager.registerInstance(AgentConfig, this.agentConfig)

dependencyManager.registerSingleton(EventEmitter)
dependencyManager.registerSingleton(MessageSender)
dependencyManager.registerSingleton(MessageReceiver)
dependencyManager.registerSingleton(TransportService)
dependencyManager.registerSingleton(Dispatcher)
dependencyManager.registerSingleton(EnvelopeService)

// Register possibly already defined services
if (!dependencyManager.isRegistered(InjectionSymbols.Wallet)) {
this.dependencyManager.registerSingleton(IndyWallet)
const wallet = this.dependencyManager.resolve(IndyWallet)
dependencyManager.registerInstance(InjectionSymbols.Wallet, wallet)
}
if (!dependencyManager.isRegistered(InjectionSymbols.Logger)) {
dependencyManager.registerInstance(InjectionSymbols.Logger, this.logger)
}
if (!dependencyManager.isRegistered(InjectionSymbols.StorageService)) {
dependencyManager.registerSingleton(InjectionSymbols.StorageService, IndyStorageService)
}
if (!dependencyManager.isRegistered(InjectionSymbols.MessageRepository)) {
dependencyManager.registerSingleton(InjectionSymbols.MessageRepository, InMemoryMessageRepository)
}

// Register all modules with public API
dependencyManager.registerModulePlugins([
ConnectionsModule,
CredentialsModule,
ProofsModule,
MediatorModule,
RecipientModule,
BasicMessagesModule,
QuestionAnswerModule,
GenericRecordsModule,
LedgerModule,
DiscoverFeaturesModule,
DidsModule,
WalletModule,
OutOfBandModule,
])

// Register non-module plugins without public API
dependencyManager.registerPlugins([
IndyPlugin,
CachePlugin,
CryptoPlugin,
DidCommMessagePlugin,
StorageUpdatePlugin,
])
}
}
5 changes: 2 additions & 3 deletions packages/core/src/agent/Dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import type { AgentMessageProcessedEvent } from './Events'
import type { Handler } from './Handler'
import type { InboundMessageContext } from './models/InboundMessageContext'

import { Lifecycle, scoped } from 'tsyringe'

import { AgentConfig } from '../agent/AgentConfig'
import { AriesFrameworkError } from '../error/AriesFrameworkError'
import { injectable } from '../plugins'
import { canHandleMessageType, parseMessageType } from '../utils/messageType'

import { ProblemReportMessage } from './../modules/problem-reports/messages/ProblemReportMessage'
Expand All @@ -17,7 +16,7 @@ import { AgentEventTypes } from './Events'
import { MessageSender } from './MessageSender'
import { isOutboundServiceMessage } from './helpers'

@scoped(Lifecycle.ContainerScoped)
@injectable()
class Dispatcher {
private handlers: Handler[] = []
private messageSender: MessageSender
Expand Down
5 changes: 2 additions & 3 deletions packages/core/src/agent/EnvelopeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import type { Logger } from '../logger'
import type { EncryptedMessage, PlaintextMessage } from '../types'
import type { AgentMessage } from './AgentMessage'

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

import { InjectionSymbols } from '../constants'
import { KeyType } from '../crypto'
import { Key } from '../modules/dids'
import { ForwardMessage } from '../modules/routing/messages'
import { inject, injectable } from '../plugins'
import { Wallet } from '../wallet/Wallet'

import { AgentConfig } from './AgentConfig'
Expand All @@ -18,7 +17,7 @@ export interface EnvelopeKeys {
senderKey: Key | null
}

@scoped(Lifecycle.ContainerScoped)
@injectable()
export class EnvelopeService {
private wallet: Wallet
private logger: Logger
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/agent/EventEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import type { EventEmitter as NativeEventEmitter } from 'events'

import { fromEventPattern } from 'rxjs'
import { takeUntil } from 'rxjs/operators'
import { Lifecycle, scoped } from 'tsyringe'

import { injectable } from '../plugins'

import { AgentConfig } from './AgentConfig'

@scoped(Lifecycle.ContainerScoped)
@injectable()
export class EventEmitter {
private agentConfig: AgentConfig
private eventEmitter: NativeEventEmitter
Expand Down
5 changes: 2 additions & 3 deletions packages/core/src/agent/MessageReceiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import type { AgentMessage } from './AgentMessage'
import type { DecryptedMessageContext } from './EnvelopeService'
import type { TransportSession } from './TransportService'

import { Lifecycle, scoped } from 'tsyringe'

import { AriesFrameworkError } from '../error'
import { ConnectionsModule } from '../modules/connections'
import { ProblemReportError, ProblemReportMessage, ProblemReportReason } from '../modules/problem-reports'
import { injectable } from '../plugins'
import { isValidJweStructure } from '../utils/JWE'
import { JsonTransformer } from '../utils/JsonTransformer'
import { canHandleMessageType, parseMessageType, replaceLegacyDidSovPrefixOnMessage } from '../utils/messageType'
Expand All @@ -23,7 +22,7 @@ import { TransportService } from './TransportService'
import { createOutboundMessage } from './helpers'
import { InboundMessageContext } from './models/InboundMessageContext'

@scoped(Lifecycle.ContainerScoped)
@injectable()
export class MessageReceiver {
private config: AgentConfig
private envelopeService: EnvelopeService
Expand Down
5 changes: 2 additions & 3 deletions packages/core/src/agent/MessageSender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import type { AgentMessage } from './AgentMessage'
import type { EnvelopeKeys } from './EnvelopeService'
import type { TransportSession } from './TransportService'

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

import { DID_COMM_TRANSPORT_QUEUE, InjectionSymbols } from '../constants'
import { ReturnRouteTypes } from '../decorators/transport/TransportDecorator'
import { AriesFrameworkError } from '../error'
Expand All @@ -18,6 +16,7 @@ import { getKeyDidMappingByVerificationMethod } from '../modules/dids/domain/key
import { DidCommV1Service, IndyAgentService } from '../modules/dids/domain/service'
import { didKeyToInstanceOfKey, verkeyToInstanceOfKey } from '../modules/dids/helpers'
import { DidResolverService } from '../modules/dids/services/DidResolverService'
import { inject, injectable } from '../plugins'
import { MessageRepository } from '../storage/MessageRepository'
import { MessageValidator } from '../utils/MessageValidator'
import { getProtocolScheme } from '../utils/uri'
Expand All @@ -37,7 +36,7 @@ export interface TransportPriorityOptions {
restrictive?: boolean
}

@scoped(Lifecycle.ContainerScoped)
@injectable()
export class MessageSender {
private envelopeService: EnvelopeService
private transportService: TransportService
Expand Down
5 changes: 2 additions & 3 deletions packages/core/src/agent/TransportService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import type { EncryptedMessage } from '../types'
import type { AgentMessage } from './AgentMessage'
import type { EnvelopeKeys } from './EnvelopeService'

import { Lifecycle, scoped } from 'tsyringe'

import { DID_COMM_TRANSPORT_QUEUE } from '../constants'
import { injectable } from '../plugins'

@scoped(Lifecycle.ContainerScoped)
@injectable()
export class TransportService {
public transportSessionTable: TransportSessionTable = {}

Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/agent/__tests__/Agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('Agent', () => {
let agent: Agent

afterEach(async () => {
const wallet = agent.injectionContainer.resolve<Wallet>(InjectionSymbols.Wallet)
const wallet = agent.dependencyManager.resolve<Wallet>(InjectionSymbols.Wallet)

if (wallet.isInitialized) {
await wallet.delete()
Expand All @@ -59,7 +59,7 @@ describe('Agent', () => {
expect.assertions(4)

agent = new Agent(config, dependencies)
const wallet = agent.injectionContainer.resolve<Wallet>(InjectionSymbols.Wallet)
const wallet = agent.dependencyManager.resolve<Wallet>(InjectionSymbols.Wallet)

expect(agent.isInitialized).toBe(false)
expect(wallet.isInitialized).toBe(false)
Expand Down Expand Up @@ -110,7 +110,7 @@ describe('Agent', () => {
describe('Dependency Injection', () => {
it('should be able to resolve registered instances', () => {
const agent = new Agent(config, dependencies)
const container = agent.injectionContainer
const container = agent.dependencyManager

// Modules
expect(container.resolve(ConnectionsModule)).toBeInstanceOf(ConnectionsModule)
Expand Down Expand Up @@ -153,7 +153,7 @@ describe('Agent', () => {

it('should return the same instance for consequent resolves', () => {
const agent = new Agent(config, dependencies)
const container = agent.injectionContainer
const container = agent.dependencyManager

// Modules
expect(container.resolve(ConnectionsModule)).toBe(container.resolve(ConnectionsModule))
Expand Down
5 changes: 2 additions & 3 deletions packages/core/src/cache/CacheRepository.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { inject, scoped, Lifecycle } from 'tsyringe'

import { EventEmitter } from '../agent/EventEmitter'
import { InjectionSymbols } from '../constants'
import { inject, injectable } from '../plugins'
import { Repository } from '../storage/Repository'
import { StorageService } from '../storage/StorageService'

import { CacheRecord } from './CacheRecord'

@scoped(Lifecycle.ContainerScoped)
@injectable()
export class CacheRepository extends Repository<CacheRecord> {
public constructor(
@inject(InjectionSymbols.StorageService) storageService: StorageService<CacheRecord>,
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/cache/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { DependencyManager } from '../plugins'

import { plugin } from '../plugins'

import { CacheRepository } from './CacheRepository'

@plugin()
export class CachePlugin {
public static register(dependencyManager: DependencyManager) {
dependencyManager.registerSingleton(CacheRepository)
}
}
Loading

0 comments on commit 0f56e8c

Please sign in to comment.