Establishing New Connections

Accept connection invitations, establish new peer connections, and manage pending connections.

In Decentralized Identity, establishing an end-to-end encrypted communication channels with a peer is an essential step before receiving or proving credentials. As such, the agent's ConnectionExchangeModule provides all functionality needed to establish those connections with peers, and manage pending connections.

The functionality of the ConnectionExchangeModule is accessed via the agent's fields: agent.connections.exchange. The functionality provided is described below.

Receive a Connection Invitation

A ConnectionExchange, representing a pending connection, is created by the agent once a connection invitation is received. To have the agent 'receive' an invitation message, this can be achieved in multiple ways. An invitation JSON payload message can be pre-processed and passed to the agent via the receiveMessage API. Or, if the invitation is in its URL form (as it typically is when scanned from a QR Code), our URL Message Source can be used to process and receive that payload.

After an invitation is received by the agent, a ConnectionExchange is created in the agent's wallet and can be accessed and used by the other APIs mentioned below. If the agent is subscribed to agent events, a new ConnectionExchange will invoke a connection update event.

Accepting a Connection

With a ConnectionExchange in the INVITATION state, the agent can "accept" that connection invitation, which will begin the next steps of the Aries protocol to establish an end-to-end encrypted channel with that peer.

Note that after calling the acceptConnection API, the connection is not fully established immediately, further messages between the peer and agent are sent in the background. The status updates of the establishment process can be tracked via agent events.

The acceptConnection requires the following;

  • A connectionExchangeId - ID of the ConnectionExchange to accept,

  • A Routing object, which is an item that the agent will use to inform the peer about how this agent should be contacted. This object notably includes a serviceEndpoint (a HTTP endpoint where the peer should send DIDComm messages to), and an optional list of routingVerkeys, which (if using a mediator) describes what layers of encryption the peer should wrap their messages in. If the agent is being used with the Sudo DI Relay, then a relay postbox can be used to create a Routing for that Postbox (see below). If a custom message source is being used, Routing will need to be manually constructed.

  • A ConnectionConfiguration object, containing configuration details about specific configurations for this connection. Including how the agent should present itself to the peer. Any provided configuration here will override the global setting.

let connectionExchangeId: String // The identifier of the [ConnectionExchange]
let routingVerkeys: [String] // any additional routing verkeys that 
// the peer should encrypt their messages with before sending.
// In the case of the sudo relay, this is an emptyList(), whereas mediator 
// services may specify routing keys.

let routing = Routing(serviceEndpoint: "https://sample.endpoint.com/abc", routingVerkeys: routingVerkeys)

do {
    let configuration = ConnectionConfiguration()
    try await agent.connections.exchange.acceptConnection(
        connectionExchangeId: connectionExchangeId,
        routing: routing,
        configuration: configuration
    )
} catch {
    // handle error
}

Creating a Connection Route from the Sudo Relay SDK

When integrating with the Sudo Relay SDK (i.e. via the relay message source), the Routing for the acceptConnection API can be easily created with the routingFromPostbox helper API on the relay message source.

let connectionExchangeId: String // The identifier of the [ConnectionExchange]
let postbox: Postbox // A new postbox created by a sudo relay client.

let relayRouting = SudoDIRelayMessageSource.routingFromPostbox(postbox: postbox)

do {
    let configuration = ConnectionConfiguration()

    try await agent.connections.exchange.acceptConnection(
        connectionExchangeId: connectionExchangeId,
        routing: relayRouting,
        configuration: configuration
    )
} catch {
    // handle error
}

Please refer to the Sudo Relay SDK documentation for more information about creating Postboxes and more.

To avoid any chance of peers correlating different connections to a specific relay endpoint, it is recommended that a new Postbox (and therefore new Postbox.serviceEndpoint and corresponding Routing) is created per connection.

Get a Pending Connection by ID

As mentioned above, a pending connection is represented by ConnectionExchange objects. To retrieve the current state of a specific ConnectionExchange in the agent's wallet, the getById API can be used. If a connection exchange cannot be found by the given ID, then null is returned:

let id: String // id of the ConnectionExchange

do {
    let connectionExchange = try await agent.connections.exchange.getById(connectionExchangeId: id)
} catch {
    // handle error
}

Delete a Pending Connection by ID

Similarly, a ConnectionExchange in the wallet can be easily deleted via the deleteById API:

let id: String // id of the ConnectionExchange

do {
    try await agent.connections.exchange.deleteById(connectionExchangeId: id)
} catch {
    // handle error
}

Updating the Metadata of a Pending Connection

ConnectionExchange objects contain some metadata that can be controlled by SDK consumers, allowing custom information to be attached to each ConnectionExchange, and allowing custom listing functionality to be leveraged.

Each ConnectionExchange contains a list of RecordTag (ConnectionExchange.tags) attached to it, where a RecordTag is simply a name-value pair stored with the record. By default, some tags are attached to a new ConnectionExchange, this includes:

  • tag-name: ~started_timestamp

  • tag-value: The UNIX epoch seconds which this connection began establishment

The tags on a ConnectionExchange can be replaced or updated by using the updateConnectionExchange API, and providing a new set to update. This will replace whatever the current set of tags is:

let id: String // ID of the connection exchange to update

// add a 'category' of 'work' to this connection, and a 'priority' of '1'
let update = ConnectionExchangeUpdate(tags: [
    RecordTag(name: "category", value: "work"),
    RecordTag(name: "~priority", value: "1")
])

do {
    try await agent.connections.exchange.updateConnectionExchange(connectionExchangeId: id, connectionExchangeUpdate: update)
} catch {
    // handle error
}

Like most data in the wallet, RecordTag will be stored encrypted. Unless, the tag name is prefixed with ~, then the tag value will be stored unencrypted. Storing a tag value as unencrypted will allow some additional listing queries to be performed (see below).

Listing Pending Connections

To list all pending connections in the agent's wallet, the listAll API can be used:

do {
    let conns = try await agent.connections.exchange.listAll(options: nil)
} catch {
    // handle error
}

Filtered Listing

More complicated ConnectionExchange list queries can also be achieved by utilizing the ListConnectionExchangeFilters.

These filters allow for the list of ConnectionExchange to be filtered by their state, tags, or both together.

Filtering by state can be achieved as follows:

// get all the pending connections which are waiting for their invitation to be accepted
let filters = ListConnectionExchangeFilters(state: ConnectionExchangeState.invitation)
let options = ListConnectionExchangeOptions(filters: filters)
do {
    let conns = try await agent.connections.exchange.listAll(options: options)
} catch {
    // handle error
}

To filter by tags applied to the ConnectionExchange (i.e. applied via the update API), the tagFilter field of ListConnectionExchangeFilters should be used. This field takes a String in compliance with a Wallet Query Language (WQL) Query.

Continuing from the example in the Update section:

// WQL Query, filter for 'category' == 'work'
let wqlQuery = "{ \"category\": \"work\" }"
let filters = ListConnectionExchangeFilters(tagFilter: wqlQuery)
let options = ListConnectionExchangeOptions(filters: filters)
do {
    let pendingWorkConnections = try await agent.connections.exchange.listAll(options: options)
} catch {
    // handle error
}
// WQL Query, filter for priority < 2 (e.g. 'high' priority items)
let wqlQuery = "{ \"~priority\": { \"$lt\": \"2\" } }"
let filters = ListConnectionExchangeFilters(tagFilter: wqlQuery)
let options = ListConnectionExchangeOptions(filters: filters)

do {
    let highPriorityPendingConnections = try await agent.connections.exchange.listAll(options: options)
} catch {
    // Handle error
}

Last updated