Skip to main content

Migrating from AFJ 0.1.0 to 0.2.x

This document describes everything you need to know for updating AFJ 0.1.0 to 0.2.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.2.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.2.0 @aries-framework/core@^0.2.0 indy-sdk-react-native@^0.2.0

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

Breaking Code Changes

This section will list all breaking changes made to the public API of AFJ between version 0.1.0 and 0.2.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 Messages to the New Message Type Objects), but it is possible some breaking changes are not documented here (feel free to open PRs).

Credentials Module

Module API Updates

With the addition of the issue credential v2 protocol and the preparation for multiple attachment formats (to be added in a later release), we've made some big changes to the credentials module API. Most changes are related to structure, so updating your code to the new API should be straightforward.

Basically for all methods in the credential 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 a protocol (starting from offer, proposal), you should pass protocolVersion: 'v1' to indicate we should use the v1 protocol. (v2 is also supported, but this focuses on the breaking changes, not the new features).
  3. All indy specific attributes (e.g. credentialDefinitionId) should be passed in the credentialFormats.indy object.
  4. The preview should now be passed as only the preview attributes (the the full preview) and provided in the credentialFormats.indy object.
await agent.credentials.offerCredential('connectionId', {
autoAcceptCredential: AutoAcceptCredential.Always,
comment: 'hello',

credentialDefinitionId: 'credentialDefinitionId',
preview: new CredentialPreview({
attributes: [new CredentialPreviewAttribute({ name: 'key', value: 'value' })],
}),
})

Data from Received Messages only Stored in Record after Accepting

Previously when we received a message from another connection we would store the relevant data from the exchange in the credential record. The values we would store were the credentialDefinitionId and schemaId in the credential metadata, and the credentialAttributes field.

Starting with AFJ 0.2.0 the values are not stored in the credential record until after the message content has been accepted (in the case of an offer this means after sending the request message). This is to avoid ambiguity of the values in the credential record. If I have sent a proposal and then receive an offer, should the credential record contain the values from the proposal or the values from the offer? The first one reflects our view on what the data should be, the second one reflects the latest data.

We decided to make the record properties always hold our view of what the data should be, and only update it after accepting the contents of a received message (either using auto accept, or by calling the acceptXXX methods on the credential module).

This is an important change and requires some updates to how you extract the relevant data from the offer (or other messages such the proposal). We've added a new getFormatData method on the credentials module that allows you to retrieve the attributes and format data for all messages in an exchange. One of the advantages of this approach is that we don't have to store all relevant data in the credential record anymore, which helps when adding new formats that don't match with the attributes used for indy credentials. In addition, the return value for this method is the same whether v1 or v2 of the protocol is used. This means your code should only care about the credential format (indy in this case) and doesn't have to worry about the protocol version.

agent.events.on<CredentialStateChangedEvent>(
CredentialEventTypes.CredentialStateChanged,
({ payload: { credentialRecord } }) => {
const indyCredentialMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyCredential)

// Get credential definition id, schema id and attributes from offer
const credentialDefinitionId = indyCredentialMetadata?.credentialDefinitionId
const schemaId = indyCredentialMetadata?.schemaId
const attributes = credentialRecord.credentialAttributes
}
)

Messages Extracted from Credential Record

The DIDComm messages that were previously stored on the credential 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 credential exchange record agnostic of the protocol version. Instead of accessing the messages through the proposalMessage, offerMessage, requestMessage and credentialMessage parameters, we now expose dedicated methods on the credentials 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 (V1ProposeCredentialMessage and V2ProposeCredentialMessage). If you were using these messages classes throughout your codebase, update them to use the V1 prefix.

const credentialRecord = await agent.credentials.getById('credentialRecordId')

const proposalMessage = credentialRecord.proposalMessage
const offerMessage = credentialRecord.offerMessage
const requestMessage = credentialRecord.requestMessage
const credentialMessage = credentialRecord.credentialMessage

Connections Module

Version 0.2.0 added support for the Out of Band protocol with support for the DID Exchange protocol. Internally AFJ now uses out of band invitations for all connections, even if you're connecting using the old invitations from the Connection protocol.

Creating a Legacy Invitation

The createInvitation method on the connections module has been moved to the out of band module and renamed to createLegacyInvitation. The method is not planned to be removed in the near future, the legacy merely indicates this will create an RFC 0160 connection invitation. Internally AFJ creates an out of band invitation and transforms it into a legacy invitation. If you want to create an out of band invitation instead, you should use oob.createInvitation.

const { connectionRecord, invitation } = await agent.connections.createInvitation({
/* config */
})

const invitationUrl = invitation.toUrl({ domain: 'https://example.com' })

Receiving a Legacy Invitation

The receiveInvitation and receiveInvitationFromUrl methods on the connections module have also been moved to the out of band module. Both methods support the new out of band invitations and the legacy RFC 0160 connection invitations. Internally AFJ converts the old invitations to out of band invitations.

const invitationUrl = 'https://example.com?c_i=eyXXX'

// Receive invitation directly from url
const connectionRecord = await agent.connections.receiveInvitationFromUrl(invitationUrl, {
/* config */
})

// Parse invitation and receive invitation
const parsedInvitation = await ConnectionInvitationMessage.fromUrl(invitationUrl)
const connectionRecord = await agent.connections.receiveInvitation(parsedInvitation, {
/* config */
})

Updating to use DidExchangeState

TODO

Updating Custom Messages to the New Message Type Objects

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 when creating custom modules. Starting from AFJ 0.2.0 we now support handling messages with different minor versions (e.g. receive a message with @type version 1.1 while we only support 1.0). With this change messages must now declare the message type as an ParsedMessageType object. We've added an parseMessageType util method that can help with this.

import { AgentMessage } from '@aries-framework/core'
import { Equals } from 'class-validator'

class MyMessage extends AgentMessage {
@Equals(MyMessage.type)
public readonly type = MyMessage.type
public static readonly type = 'https://didcomm.org/my-protocol/1.0/my-type'
}

Breaking Storage Changes

The 0.2.0 release is heavy on breaking changes to the storage format. This is not what we intend to do with every release. But as there's not that many people yet using the framework in production, and there were a lot of changes needed to keep the API straightforward, we decided to bundle a lot of breaking changes in this one release.

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.

The following config can be provided to the update assistant to migrate from 0.1.0 to 0.2.0:

{
"v0_1ToV0_2": {
"mediationRoleUpdateStrategy": "<mediationRoleUpdateStrategy>"
}
}

Credential Metadata

The credential record had a custom metadata property in pre-0.1.0 storage that contained the requestMetadata, schemaId and credentialDefinition properties. Later a generic metadata API was added that only allows objects to be stored. Therefore the properties were moved into a different structure.

{
"requestMetadata": <value of requestMetadata>,
"schemaId": "<value of schemaId>",
"credentialDefinitionId": "<value of credential definition id>"
}

Migrate Credential Record Properties

In 0.2.0 the v1 DIDComm messages have been moved out of the credential record into separate records using the DidCommMessageRepository. The migration scripts extracts all messages (proposalMessage, offerMessage, requestMessage, credentialMessage) and moves them into the DidCommMessageRepository.

With the addition of support for different protocol versions the credential record now stores the protocol version. With the addition of issue credential v2 support, other credential formats than indy can be used, and multiple credentials can be issued at once. To account for this the credentialId has been replaced by the credentials array. This is an array of objects containing the credentialRecordId and the credentialRecordType. For all current credentials the credentialRecordType will always be indy.

{
"credentialId": "09e46da9-a575-4909-b016-040e96c3c539",
"proposalMessage": { ... },
"offerMessage": { ... },
"requestMessage": { ... },
"credentialMessage": { ... },
}

Mediation Record Role

The role in the mediation record was always being set to MediationRole.Mediator for both mediators and recipients. This didn't cause any issues, but would return the wrong role for recipients.

In 0.2 a check is added to make sure the role of a mediation record matches with actions (e.g. a recipient can't grant mediation), which means it will throw an error if the role is not set correctly.

Because it's not always possible detect whether the role should actually be mediator or recipient, a number of configuration options are provided on how the role should be updated using the v0_1ToV0_2.mediationRoleUpdateStrategy option:

  • allMediator: The role is set to MediationRole.Mediator for both mediators and recipients
  • allRecipient: The role is set to MediationRole.Recipient for both mediators and recipients
  • recipientIfEndpoint (default): The role is set to MediationRole.Recipient if their is an endpoint configured on the record. The endpoint is not set when running as a mediator. There is one case where this could be problematic when the role should be recipient, if the mediation grant hasn't actually occurred (meaning the endpoint is not set). This is probably the best approach otherwise it is set to MediationRole.Mediator
  • doNotChange: The role is not changed

Most agents only act as either the role of mediator or recipient, in which case the allMediator or allRecipient configuration is the most appropriate. If your agent acts as both a recipient and mediator, the recipientIfEndpoint configuration is the most appropriate. The doNotChange options is not recommended and can lead to errors if the role is not set correctly.

Extracting Did Documents to Did Repository

The connection record previously stored both did documents from a connection in the connection record itself. Version 0.2.0 added a generic did storage that can be used for numerous usages, one of which is the storage of did documents for connection records.

The migration script extracts the did documents from the didDoc and theirDidDoc properties from the connection record, updates them to did documents compliant with the did core spec, and stores them in the did repository. By doing so it also updates the unqualified dids in the did and theirDid fields generated by the indy-sdk to fully qualified did:peer dids compliant with the Peer DID Method Specification.

To account for the fact that the mechanism to migrate legacy did document to peer did documents is not defined yet, the legacy did and did document are stored in the did record metadata. This will be deleted later if we can be certain the did doc conversion to a did:peer did document is correct.

{
"did": "BBPoJqRKatdcfLEAFL7exC",
"theirDid": "UppcJ5APts7ot5WX25943F",
"verkey": "GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf",
"didDoc": <legacyDidDoc>,
"theirDidDoc": <legacyTheirDidDoc>,
}

Migrating to the Out of Band Record

With the addition of the out of band protocol, invitations are now stored in the OutOfBandRecord. In addition a new field invitationDid is added to the connection record that is generated based on the invitation service or did. This allows to reuse existing connections.

The migration script extracts the invitation and other relevant data into a separate OutOfBandRecord. By doing so it converts the old connection protocol invitation into the new Out of band invitation message. Based on the service or did of the invitation, the invitationDid is populated.

Previously when creating a multi use invitation, a connection record would be created with the multiUseInvitation set to true. The connection record would always be in state invited. If a request for the multi use invitation came in, a new connection record would be created. With the addition of the out of band module, no connection records are created until a request is received. So for multi use invitation this means that the connection record with multiUseInvitation=true will be deleted, and instead all connections created using that out of band invitation will contain the outOfBandId of the multi use invitation.

{
"invitation": {
"@type": "https://didcomm.org/connections/1.0/invitation",
"@id": "04a2c382-999e-4de9-a1d2-9dec0b2fa5e4",
"recipientKeys": ["E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu"],
"serviceEndpoint": "https://example.com",
"label": "test"
},
"multiUseInvitation": "false"
}

Unifying Connection States and Roles

With the addition of the did exchange protocol there are now two states and roles related to the connection record; for the did exchange protocol and for the connection protocol. To keep it easy to work with the connection record, all state and role values are updated to those of the DidExchangeRole and DidExchangeState enums.

The migration script transforms all connection record state and role values to their respective values of the DidExchangeRole and DidExchangeState enums. For convenience a getter property rfc0160ConnectionState is added to the connection record which returns the ConnectionState value.

{
"state": "invited",
"role": "inviter"
}