Skip to main content

Migrating from AFJ 0.2.x to 0.3.x

This document describes everything you need to know for updating AFJ 0.2.x to 0.3.x. If you're not aware of how updating in AFJ works make sure to first read the guide on Updating AFJ.

First of all, update you dependencies to the 0.3.x versions. This will also update the needed peer depedencnies. Extension packages are not updated with this command. You need to update these manually, and make sure they're up to date with the latest version of AFJ.

yarn add @aries-framework/react-native@^0.3.0 @aries-framework/core@^0.3.0 indy-sdk-react-native@^0.3.0

# or NPM
npn install @aries-framework/react-native@^0.3.0 @aries-framework/core@^0.3.0 indy-sdk-react-native@^0.3.0

Breaking Code Changes

This section will list all breaking changes made to the public API of AFJ between version 0.2.x and 0.3.0.

❗️info

If you have custom modules take into account there could be a lot more breaking changes that aren't documented here. We try to make sure that the biggest breaking changes to the internal API are also documented here (e.g. see Updating Custom Modules to the Plugin API), but it is possible some breaking changes are not documented here (feel free to open PRs).

Agent creation

The agent constructor has been updated to a single AgentOptions object that contains the config and dependencies properties.

const agent = new Agent(agentConfig, agentDependencies)

This object contains:

  • config: Agent's initial configuration
  • dependencies: platform-specific Agent dependencies
  • modules: optional field for internal module configuration and custom module registration

For easy migration, you can simply construct AgentOptions by putting current InitConfig into config key and agentDependencies into dependencies key.

Note that, if you are defining indyLedgers configuration, you should set the indyNamespace for every ledger, as explained in Agent Config tutorial.

did:key usage in protocols

In accordance with Aries RFC 0360, since 0.2.5 there is a configuration parameter called useDidKeyInProtocols which, when enabled, will encode keys in did:key instead of previous base58 format, unless the other party has started a protocol and is using base58.

This parameter was previously disabled by default and now it is enabled. If your agent only interacts with modern agents (e.g. AFJ 0.2.5 and newer) this will not represent any issue. Otherwise it is safer to explicitly set it to false. However, keep in mind that we expect this setting to be deprecated in the future, so we encourage you to update all your agents to use did:key.

Modules extracted from the core

In this release two modules were extracted from the core and published as separate, optional packages:

  • actionMenu has been moved to @aries-framework/action-menu
  • questionAnswer has been moved to @aries-framework/question-answer

If you want to use them, you can integrate in an Agent instance by injecting them in constructor, as follows:

import { ActionMenuModule } from '@aries-framework/action-menu'
import { QuestionAnswerModule } from '@aries-framework/question-answer'

const agent = new Agent({
config: {
/* config */
},
dependencies: agentDependencies,
modules: {
actionMenu: new ActionMenuModule(),
questionAnswer: new QuestionAnswerModule(),
/* other custom modules */
},
})

As they are now considered custom modules, their API can be accessed in modules namespace, so you should add it to every call to them.

await agent.questionAnswer.sendQuestion(connectionId, {
question: 'Do you want to play?',
validResponses: [{ text: 'Yes' }, { text: 'No' }],
})

await agent.questionAnswer.sendAnswer(questionAnswerRecordId, 'Yes')

Discover Features Module

This module now supports both Discover Features V1 and V2, and as it happened to other modules, queryFeatures method parameters have been unified to a single object and requires to specify the version of Discover Features protocol to be used. Note that query property has been replaced by the more general queries which accepts multiple features to be search for. To convert a query to this new format you simply need to create a single-object array whose unique object whose featureType field is 'protocol' and match field is the query itself.

await agent.discovery.queryFeatures(connectionId, {
query: 'https://didcomm.org/messagepickup/2.0',
comment: 'Detect if protocol is supported',
})

The convenience method isProtocolSupported has been replaced by the more general synchronous mode of queryFeatures, which works when awaitDisclosures in options is set. Instead of returning a boolean, it returns an object with matching features:

const isPickUpV2Supported = await agent.discovery.isProtocolSupported(connectionId, StatusRequestMessage)
❗️info

Discover Features module does not rely anymore on Agent Dispatcher to determine protocol support. Instead, it uses the new Feature Registry, where any custom modules implementing protocols must register them.

This procedure can be done in module's register(dependencyManager, featureRegistry).

Ledger Module

Apart from the aforementioned indyLedgers configuration, you should also note a slight change in behaviour when attempting to register credential definitions that already exists on the ledger but not in the wallet.

Proofs Module

Module API Updates

Much in the same way as in 0.2.0 release when Issue Credential V2 protocol has been added, now that Present Proof V2 is supported, we introduced changes to proofs module.

Basically, for all methods in the proofs module you should take the following steps to update your code:

  1. Move all function parameters into a single object. All module methods now take a single object that contain all properties.
  2. For methods that initiate proposals, requests or presentations (proposeProof, acceptProposal, requestProof, acceptPresentation, etc.), you should pass protocolVersion: 'v1' to indicate we should use the v1 protocol
  3. All indy specific attributes (e.g. Presentation Preview) should be passed in the proofFormats.indy object.
  4. Some indy objects, as the preview should now be passed only as their attributes (i.e. no need of creating the object instance) and provided in the proofFormats.indy object.
await agent.proofs.proposeProof(
'connectionId',
new PresentationPreview({
attributes: [new PresentationPreviewAttribute({ name: 'key', value: 'value' })],
predicates: [
new PresentationPreviewPredicate({
name: 'age',
credentialDefinitionId,
predicate: PredicateType.GreaterThanOrEqualTo,
threshold: 50,
}),
],
})
)

Messages Extracted from Proof Exchange Record

The DIDComm messages that were previously stored on the proof record, have been extracted to separate DIDComm message records. This makes it easier to work with multiple versions of the protocol internally, and keeps the proof exchange record agnostic of the protocol version. Instead of accessing the messages through the proposalMessage, requestMessage and presentationMessage parameters, we now expose dedicated methods on the proofs module to retrieve the message.

With the addition of the v2 messages, all v1 messages have been prefixed with V1 while v2 messages have been prefixed with V2 (V1RequestPresentationMessage and V2RequestPresentationMessage). If you were using these messages classes throughout your codebase, update them to use the V1 prefix.

const proofRecord = await agent.proofs.getById('proofRecordId')

const proposalMessage = proofRecord.proposalMessage
const requestMessage = proofRecord.requestMessage
const presentationMessage = proofRecord.presentationMessage

Out Of Band Proofs and Credentials

With the addition of the out of band module, the creation of connection-less messages has been split into two steps, allowing for better control and flexibility. The previous agent.proofs.createOutOfBandRequest has been replaced by the agent.proofs.createRequest method. This new method creates a proof request that is not tied to any connection.

What you can now do is call agent.oob.createLegacyConnectionlessInvitation to attach the service decorator to the message and get a legacy connectionless message.

const { requestMessage, proofRecord } = await agent.proofs.createOutOfBandRequest({
requestedAttributes: {
group1: {
name: 'dateOfBirth',
restrictions: [{ schemaId: 'F72i3Y3Q4i466efjYJYCHM:2:aha_cert:4.1.1' }],
},
},
})

Out of band invitations are the new way to send messages out of band. You can use it for connection-less exchanges, but also for exchanges that you want to establish a connection for first. Here's an example on how to use the out of band module to create a connection-less invitation for a proof request:

const outOfBandRecord = await agent.oob.createInvitation({
handshake: false, // set to true if you want to create a connection
messages: [message],
})

const invitationUrl = outOfBandRecord.outOfBandInvitation.toUrl({
domain: 'https://afj.com',
})

As you can see, there's now a lot more ways to use a message not tied to a connection. By splitting the creation of the message from the creation of the invitation, we can now create a message not bound to a connection (at time of creation) for multiple use cases.

Updating Custom Modules to the new Plugin API

Although this isn't a breaking change to the public API of the framework, it is something that you will need to take into account if you have custom modules and want to upgrade them to make compatible with AFJ 0.3.0.

Renaming handler classes

Handler has been have been renamed to MessageHandler to be be more descriptive, along with related types and methods. This means:

  • Handler is now MessageHandler
  • HandlerInboundMessage is now MessageHandlerInboundMessage
  • Dispatcher.registerHandler is now Dispatcher.registerMessageHandler and is marked as deprecated. The recommended way of registering handlers is by using the new MessageHandlerRegistry object by calling MessageHandlerRegistry.registerMessageHandler.

If your custom module include message handlers, you must update them accordingly.

export class MyHandler implements Handler {
public supportedMessages = [MyMessage]

public async handle(inboundMessage: HandlerInboundMessage<MyHandler>) {
...
}
}

Using AgentContext

First of all, it's worth noting that now all services and repositories have been made stateless. A new AgentContext is introduced that holds the current context, which is passed to each method call. Therefore, you'll need to update every call to services, repositories and also eventEmitter methods to pass AgentContext object as first argument.

AgentContext can be obtained from either:

  • MessageContext used by message handlers (accesed as messageContext.agentContext)
  • Injected in your API constructor: you can store the instance and pass it to all your service and repository calls
  public async createRequest(options: CreateRequestOptions) {
const message = new RequestMessage({
parentThreadId: options.parentThreadId,
})

const record = new MyRecord({
connectionId: options.connectionRecord.id,
threadId: message.id,
parentThreadId: options.parentThreadId,
})

await this.myRecordRepository.save(record)

this.eventEmitter.emit<MyRecordStateChangedEvent>({
type: MyRecordEventTypes.StateChanged,
payload: {
myRecord: record,
previousState: null,
},
})

return { record, message }
}

public async processRequest(messageContext: HandlerInboundMessage<RequestHandler>) {
const { message } = messageContext

const record = new MyRecord({
connectionId: connection.id,
threadId: messageContext.message.id,
parentThreadId: messageContext.message.thread?.parentThreadId,
})

await this.myRepository.save(record)

return record
}

Using OutboundMessageContext

If your module implements a protocol that sends messages to other agents, you will notice that Agent's MessageSender now receives the more generic OutboundMessageContext class, which replaces previous helper method createOutboundMessage.

You can take advantage of this new mechanism to associate a record to the context, in order to do specific actions to it when outbound message state changes (e.g. a MessageSendingError is thrown or AgentMessageSentEvent is emitted).

import { createOutboundMessage } from '@aries-framework/core'

const outboundMessage = createOutboundMessage(connection, message)
await this.messageSender.sendMessage(outboundMessage)

Updating module structure to register in new Plugin API

Existing modules can benefit from the new Plugin API mechanism by doing the following modifications:

  1. Rename Module class (e.g. MyModule) to API class (MyApi) and add @injectable decorator. Inject AgentContext in order to pass it to any services or repositories it might call. For instance:
import { injectable } from '@aries-framework/core'

@injectable() // <-- Add this
export class MyApi {
private messageSender: MessageSender
private myService: MyService
private connectionService: ConnectionService
private agentContext: AgentContext // <-- Add this

public constructor(
messageHandlerRegistry: MessageHandlerRegistry, // <-- use this instead of Dispatcher
messageSender: MessageSender,
myService: MyService,
connectionService: ConnectionService,
agentContext: AgentContext // <-- Add this
) {
this.messageSender = messageSender
this.myService = myService
this.connectionService = connectionService
this.agentContext = agentContext // <-- Add this
this.registerHandlers(messageHandlerRegistry) // <-- use messageHandlerRegistry instead of dispatcher
}
  1. Create a new Module class that implements Module interface and registers the dependencies and features. For instance:
import type { DependencyManager, FeatureRegistry, Module } from '@aries-framework/core'

import { Protocol } from '@aries-framework/core'

export class MyModule implements Module {
public readonly api = MyApi // the one we've just renamed from MyModule

public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) {
// Api
dependencyManager.registerContextScoped(MyApi)

// Services
dependencyManager.registerSingleton(MyService)

// Repositories
dependencyManager.registerSingleton(MyRepository)

// Feature Registry: don't forget to register your protocols and other features your module may add
featureRegistry.register(
new Protocol({
id: 'https://didcomm.org/my-protocol/1.0',
roles: [MyRole.Sender, MyRole.Receiver],
})
)
}

After doing this, you can add your module to agent constructor like this:

const agent = new Agent({
config: {
/* config */
},
dependencies: agentDependencies,
modules: {
myModule: new MyModule(),
/* other custom modules */
},
})

// MyModule API can be accessed in agent.modules namespace
await agent.modules.myModule.doSomething()

await agent.modules.myModule.doAnotherThing()

Breaking Storage Changes

The 0.3.0 release introduces some breaking changes to the storage format, mainly related to Proof Exchanges.

Below all breaking storage changes are explained in as much detail as possible. The update assistant provides all tools to migrate without a hassle, but it is important to know what has changed. All examples only show the keys that have changed, unrelated keys in records have been omitted.

See the Update Assistant documentation for a guide on how to use the update assistant.

There are no config parameters to be provided to the update assistant to migrate from 0.2.x to 0.3.x.

Migrate Proof Record Properties

In 0.3.0 the v1 DIDComm messages have been moved out of the proof record into separate records using the DidCommMessageRepository. The migration scripts extracts all messages (proposalMessage, requestMessage, presentationMessage) and moves them into the DidCommMessageRepository. With the addition of support for different protocol versions the proof record now stores the protocol version.

{
"proposalMessage": { ... },
"requestMessage": { ... },
"presentationMessage": { ... },
}

Migrate Connection Record properties

The recently introduced connectionType tag has been pluralized to reflect the fact that more than a single connection type can be defined for a given connection. Also, it is now available as a direct record property (e.g. can be queried and set by using connectionRecord.connectionTypes) apart from the tag for efficient search.

The migration script renames connectionType to connectionTypes in all connections, and also searches for any mediation connection and adds ConnectionType.Mediator as one of its types.

Migrate Did Record properties

The didRecord.id was previously the did itself. However to allow for connecting with self, where multiple did records are created for the same did, the id property is now an uuid and a separate did property is added.

The migration script generates a new ID for each did record and stores its did into didRecord.did property.

{
"id": "did"
}