Skip to content

Commit

Permalink
feat(credentials)!: custom registration of credential protocols (#1158)
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <timo@animo.id>
  • Loading branch information
TimoGlastra authored Dec 19, 2022
1 parent c9acef3 commit ff6293c
Show file tree
Hide file tree
Showing 59 changed files with 1,921 additions and 1,383 deletions.
8 changes: 6 additions & 2 deletions packages/core/src/agent/Agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { InboundTransport } from '../transport/InboundTransport'
import type { OutboundTransport } from '../transport/OutboundTransport'
import type { InitConfig } from '../types'
import type { AgentDependencies } from './AgentDependencies'
import type { AgentModulesInput, ModulesMap } from './AgentModules'
import type { AgentModulesInput } from './AgentModules'
import type { AgentMessageReceivedEvent } from './Events'
import type { Subscription } from 'rxjs'

Expand All @@ -28,6 +28,7 @@ import { EnvelopeService } from './EnvelopeService'
import { EventEmitter } from './EventEmitter'
import { AgentEventTypes } from './Events'
import { FeatureRegistry } from './FeatureRegistry'
import { MessageHandlerRegistry } from './MessageHandlerRegistry'
import { MessageReceiver } from './MessageReceiver'
import { MessageSender } from './MessageSender'
import { TransportService } from './TransportService'
Expand All @@ -39,14 +40,17 @@ interface AgentOptions<AgentModules extends AgentModulesInput> {
dependencies: AgentDependencies
}

export class Agent<AgentModules extends AgentModulesInput = ModulesMap> extends BaseAgent<AgentModules> {
// Any makes sure you can use Agent as a type without always needing to specify the exact generics for the agent
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export class Agent<AgentModules extends AgentModulesInput = any> extends BaseAgent<AgentModules> {
public messageSubscription: Subscription

public constructor(options: AgentOptions<AgentModules>, dependencyManager = new DependencyManager()) {
const agentConfig = new AgentConfig(options.config, options.dependencies)
const modulesWithDefaultModules = extendModulesWithDefaultModules(agentConfig, options.modules)

// Register internal dependencies
dependencyManager.registerSingleton(MessageHandlerRegistry)
dependencyManager.registerSingleton(EventEmitter)
dependencyManager.registerSingleton(MessageSender)
dependencyManager.registerSingleton(MessageReceiver)
Expand Down
28 changes: 26 additions & 2 deletions packages/core/src/agent/AgentModules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Module, DependencyManager } from '../plugins'
import type { Module, DependencyManager, ApiModule } from '../plugins'
import type { Constructor } from '../utils/mixins'
import type { AgentConfig } from './AgentConfig'

Expand Down Expand Up @@ -28,7 +28,16 @@ export type EmptyModuleMap = {}
* Default modules can be optionally defined to provide custom configuration. This type makes it so that it is not
* possible to use a different key for the default modules
*/
export type AgentModulesInput = Partial<DefaultAgentModules> & ModulesMap
export type AgentModulesInput = Partial<DefaultAgentModulesInput> & ModulesMap

/**
* Defines the input type for the default agent modules. This is overwritten as we
* want the input type to allow for generics to be passed in for the credentials module.
*/
export type DefaultAgentModulesInput = Omit<DefaultAgentModules, 'credentials'> & {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
credentials: CredentialsModule<any>
}

/**
* Type that represents the default agent modules. This is the {@link ModulesMap} variant for the default modules in the framework.
Expand Down Expand Up @@ -83,6 +92,21 @@ export type AgentApi<Modules extends ModulesMap> = {
: never]: Modules[moduleKey]['api'] extends Constructor<unknown> ? InstanceType<Modules[moduleKey]['api']> : never
}

/**
* Returns the `api` type from the CustomModuleType if the module is an ApiModule. If the module is not defined
* which is the case if you don't configure a default agent module (e.g. credentials module), it will use the default
* module type and use that for the typing. This will contain the default typing, and thus provide the correct agent api
* interface
*/
export type CustomOrDefaultApi<
CustomModuleType,
DefaultModuleType extends ApiModule
> = CustomModuleType extends ApiModule
? InstanceType<CustomModuleType['api']>
: CustomModuleType extends Module
? never
: InstanceType<DefaultModuleType['api']>

/**
* Method to get the default agent modules to be registered on any agent instance.
*
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/agent/BaseAgent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Logger } from '../logger'
import type { CredentialsModule } from '../modules/credentials'
import type { DependencyManager } from '../plugins'
import type { AgentConfig } from './AgentConfig'
import type { AgentApi, EmptyModuleMap, ModulesMap, WithoutDefaultModules } from './AgentModules'
import type { AgentApi, EmptyModuleMap, ModulesMap, WithoutDefaultModules, CustomOrDefaultApi } from './AgentModules'
import type { TransportSession } from './TransportService'

import { AriesFrameworkError } from '../error'
Expand Down Expand Up @@ -42,7 +43,7 @@ export abstract class BaseAgent<AgentModules extends ModulesMap = EmptyModuleMap
protected agentContext: AgentContext

public readonly connections: ConnectionsApi
public readonly credentials: CredentialsApi
public readonly credentials: CustomOrDefaultApi<AgentModules['credentials'], CredentialsModule>
public readonly proofs: ProofsApi
public readonly mediator: MediatorApi
public readonly mediationRecipient: RecipientApi
Expand Down Expand Up @@ -83,7 +84,10 @@ export abstract class BaseAgent<AgentModules extends ModulesMap = EmptyModuleMap
this.agentContext = this.dependencyManager.resolve(AgentContext)

this.connections = this.dependencyManager.resolve(ConnectionsApi)
this.credentials = this.dependencyManager.resolve(CredentialsApi) as CredentialsApi
this.credentials = this.dependencyManager.resolve(CredentialsApi) as CustomOrDefaultApi<
AgentModules['credentials'],
CredentialsModule
>
this.proofs = this.dependencyManager.resolve(ProofsApi)
this.mediator = this.dependencyManager.resolve(MediatorApi)
this.mediationRecipient = this.dependencyManager.resolve(RecipientApi)
Expand Down
59 changes: 10 additions & 49 deletions packages/core/src/agent/Dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,43 @@ import { InjectionSymbols } from '../constants'
import { AriesFrameworkError } from '../error/AriesFrameworkError'
import { Logger } from '../logger'
import { injectable, inject } from '../plugins'
import { canHandleMessageType, parseMessageType } from '../utils/messageType'

import { ProblemReportMessage } from './../modules/problem-reports/messages/ProblemReportMessage'
import { EventEmitter } from './EventEmitter'
import { AgentEventTypes } from './Events'
import { MessageHandlerRegistry } from './MessageHandlerRegistry'
import { MessageSender } from './MessageSender'
import { OutboundMessageContext } from './models'

@injectable()
class Dispatcher {
private messageHandlers: MessageHandler[] = []
private messageHandlerRegistry: MessageHandlerRegistry
private messageSender: MessageSender
private eventEmitter: EventEmitter
private logger: Logger

public constructor(
messageSender: MessageSender,
eventEmitter: EventEmitter,
messageHandlerRegistry: MessageHandlerRegistry,
@inject(InjectionSymbols.Logger) logger: Logger
) {
this.messageSender = messageSender
this.eventEmitter = eventEmitter
this.messageHandlerRegistry = messageHandlerRegistry
this.logger = logger
}

public registerMessageHandler(handler: MessageHandler) {
this.messageHandlers.push(handler)
/**
* @deprecated Use {@link MessageHandlerRegistry.registerMessageHandler} directly
*/
public registerMessageHandler(messageHandler: MessageHandler) {
this.messageHandlerRegistry.registerMessageHandler(messageHandler)
}

public async dispatch(messageContext: InboundMessageContext): Promise<void> {
const { agentContext, connection, senderKey, recipientKey, message } = messageContext
const messageHandler = this.getMessageHandlerForType(message.type)
const messageHandler = this.messageHandlerRegistry.getHandlerForMessageType(message.type)

if (!messageHandler) {
throw new AriesFrameworkError(`No handler for message type "${message.type}" found`)
Expand Down Expand Up @@ -89,50 +94,6 @@ class Dispatcher {
},
})
}

private getMessageHandlerForType(messageType: string): MessageHandler | undefined {
const incomingMessageType = parseMessageType(messageType)

for (const messageHandler of this.messageHandlers) {
for (const MessageClass of messageHandler.supportedMessages) {
if (canHandleMessageType(MessageClass, incomingMessageType)) return messageHandler
}
}
}

public getMessageClassForType(messageType: string): typeof AgentMessage | undefined {
const incomingMessageType = parseMessageType(messageType)

for (const messageHandler of this.messageHandlers) {
for (const MessageClass of messageHandler.supportedMessages) {
if (canHandleMessageType(MessageClass, incomingMessageType)) return MessageClass
}
}
}

/**
* Returns array of message types that dispatcher is able to handle.
* Message type format is MTURI specified at https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0003-protocols/README.md#mturi.
*/
public get supportedMessageTypes() {
return this.messageHandlers
.reduce<typeof AgentMessage[]>((all, cur) => [...all, ...cur.supportedMessages], [])
.map((m) => m.type)
}

/**
* Returns array of protocol IDs that dispatcher is able to handle.
* Protocol ID format is PIURI specified at https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0003-protocols/README.md#piuri.
*/
public get supportedProtocols() {
return Array.from(new Set(this.supportedMessageTypes.map((m) => m.protocolUri)))
}

public filterSupportedProtocolsByMessageFamilies(messageFamilies: string[]) {
return this.supportedProtocols.filter((protocolId) =>
messageFamilies.find((messageFamily) => protocolId.startsWith(messageFamily))
)
}
}

export { Dispatcher }
59 changes: 59 additions & 0 deletions packages/core/src/agent/MessageHandlerRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { AgentMessage } from './AgentMessage'
import type { MessageHandler } from './MessageHandler'

import { injectable } from 'tsyringe'

import { canHandleMessageType, parseMessageType } from '../utils/messageType'

@injectable()
export class MessageHandlerRegistry {
private messageHandlers: MessageHandler[] = []

public registerMessageHandler(messageHandler: MessageHandler) {
this.messageHandlers.push(messageHandler)
}

public getHandlerForMessageType(messageType: string): MessageHandler | undefined {
const incomingMessageType = parseMessageType(messageType)

for (const handler of this.messageHandlers) {
for (const MessageClass of handler.supportedMessages) {
if (canHandleMessageType(MessageClass, incomingMessageType)) return handler
}
}
}

public getMessageClassForMessageType(messageType: string): typeof AgentMessage | undefined {
const incomingMessageType = parseMessageType(messageType)

for (const handler of this.messageHandlers) {
for (const MessageClass of handler.supportedMessages) {
if (canHandleMessageType(MessageClass, incomingMessageType)) return MessageClass
}
}
}

/**
* Returns array of message types that dispatcher is able to handle.
* Message type format is MTURI specified at https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0003-protocols/README.md#mturi.
*/
public get supportedMessageTypes() {
return this.messageHandlers
.reduce<typeof AgentMessage[]>((all, cur) => [...all, ...cur.supportedMessages], [])
.map((m) => m.type)
}

/**
* Returns array of protocol IDs that dispatcher is able to handle.
* Protocol ID format is PIURI specified at https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0003-protocols/README.md#piuri.
*/
public get supportedProtocols() {
return Array.from(new Set(this.supportedMessageTypes.map((m) => m.protocolUri)))
}

public filterSupportedProtocolsByMessageFamilies(messageFamilies: string[]) {
return this.supportedProtocols.filter((protocolId) =>
messageFamilies.find((messageFamily) => protocolId.startsWith(messageFamily))
)
}
}
6 changes: 5 additions & 1 deletion packages/core/src/agent/MessageReceiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { canHandleMessageType, parseMessageType, replaceLegacyDidSovPrefixOnMess

import { Dispatcher } from './Dispatcher'
import { EnvelopeService } from './EnvelopeService'
import { MessageHandlerRegistry } from './MessageHandlerRegistry'
import { MessageSender } from './MessageSender'
import { TransportService } from './TransportService'
import { AgentContextProvider } from './context'
Expand All @@ -31,6 +32,7 @@ export class MessageReceiver {
private dispatcher: Dispatcher
private logger: Logger
private connectionService: ConnectionService
private messageHandlerRegistry: MessageHandlerRegistry
private agentContextProvider: AgentContextProvider
public readonly inboundTransports: InboundTransport[] = []

Expand All @@ -40,6 +42,7 @@ export class MessageReceiver {
messageSender: MessageSender,
connectionService: ConnectionService,
dispatcher: Dispatcher,
messageHandlerRegistry: MessageHandlerRegistry,
@inject(InjectionSymbols.AgentContextProvider) agentContextProvider: AgentContextProvider,
@inject(InjectionSymbols.Logger) logger: Logger
) {
Expand All @@ -48,6 +51,7 @@ export class MessageReceiver {
this.messageSender = messageSender
this.connectionService = connectionService
this.dispatcher = dispatcher
this.messageHandlerRegistry = messageHandlerRegistry
this.agentContextProvider = agentContextProvider
this.logger = logger
}
Expand Down Expand Up @@ -227,7 +231,7 @@ export class MessageReceiver {
replaceLegacyDidSovPrefixOnMessage(message)

const messageType = message['@type']
const MessageClass = this.dispatcher.getMessageClassForType(messageType)
const MessageClass = this.messageHandlerRegistry.getMessageClassForMessageType(messageType)

if (!MessageClass) {
throw new ProblemReportError(`No message class found for message type "${messageType}"`, {
Expand Down
Loading

0 comments on commit ff6293c

Please sign in to comment.