Skip to content

Commit

Permalink
feat: connection termination (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
genaris authored Apr 3, 2024
2 parents fb8af7c + 377ecbf commit e6a9213
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 6 deletions.
14 changes: 13 additions & 1 deletion doc/service-agent-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ In addition, it supports a notification mechanism to subscribe to any event the
- [Menu Selection](#menu-selection)
- [Invitation](#invitation)
- [Profile](#profile)
- [Terminate Connection](#terminate-connection)
- [Identity Proof Item types](#identity-proof-item-types)
- [Verifiable Credential](#verifiable-credential)
- [Request value](#request-value)
Expand Down Expand Up @@ -99,10 +100,10 @@ Currently, the following messages can be submitted and received:
- Receipts (`receipts`)
- Invitation (`invitation`)
- Profile (`profile`)
- Terminate Connection (`terminate-connection`)

> **TODO**: Messages for:
>
> - Open browsing session
> - System messages in topics
> - Message signaling (typing)
Expand Down Expand Up @@ -405,6 +406,17 @@ Sends User Profile to a particular connection. An Agent may have its default pro
}
```

#### Terminate Connection

Terminates a particular connection, notifying the other party through a 'Hangup' message. No further messages will be allowed after this action.

```json
{
...
"type": "terminate-connection",
}
```

### Identity Proof Item types

When a Credential Issuance is requested, the issuer might require the recipient to present certain identity proofing elements.
Expand Down
20 changes: 17 additions & 3 deletions src/controllers/message/MessageController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
OutboundMessageContext,
OutOfBandRepository,
OutOfBandInvitation,
DidExchangeState,
} from '@credo-ts/core'
import { QuestionAnswerRepository, ValidResponse } from '@credo-ts/question-answer'
import { Body, Controller, HttpException, HttpStatus, Logger, Post } from '@nestjs/common'
Expand All @@ -27,6 +28,7 @@ import {
IBaseMessage,
didcommReceiptFromServiceAgentReceipt,
IdentityProofResultMessage,
TerminateConnectionMessage,
} from '../../model'
import { VerifiableCredentialRequestedProofItem } from '../../model/messages/proofs/vc/VerifiableCredentialRequestedProofItem'
import { AgentService } from '../../services/AgentService'
Expand Down Expand Up @@ -78,6 +80,14 @@ export class MessageController {
const messageType = message.type
this.logger.debug!(`Message submitted. ${JSON.stringify(message)}`)

const connection = await agent.connections.findById(message.connectionId)

if (!connection) throw new Error(`Connection with id ${message.connectionId} not found`)

if (connection.state === DidExchangeState.Completed && (!connection.did || !connection.theirDid)) {
throw new Error(`This connection has been terminated. No further messages are possible`)
}

if (messageType === TextMessage.type) {
const textMsg = JsonTransformer.fromJSON(message, TextMessage)
const record = await agent.basicMessages.sendMessage(textMsg.connectionId, textMsg.content)
Expand Down Expand Up @@ -258,7 +268,6 @@ export class MessageController {
const msg = JsonTransformer.fromJSON(message, InvitationMessage)
const { label, imageUrl, did } = msg

const connection = await agent.connections.getById(msg.connectionId)
const messageSender = agent.context.dependencyManager.resolve(MessageSender)

if (did) {
Expand Down Expand Up @@ -310,8 +319,6 @@ export class MessageController {
const msg = JsonTransformer.fromJSON(message, ProfileMessage)
const { displayImageUrl, displayName, displayIconUrl } = msg

const connection = await agent.connections.getById(msg.connectionId)

await agent.modules.userProfile.sendUserProfile({
connectionId: connection.id,
profileData: {
Expand All @@ -321,8 +328,15 @@ export class MessageController {
},
})

// FIXME: No message id is returned here
} else if (messageType === TerminateConnectionMessage.type) {
JsonTransformer.fromJSON(message, TerminateConnectionMessage)

await agent.connections.hangup({ connectionId: connection.id })

// FIXME: No message id is returned here
}

this.logger.debug!(`messageId: ${messageId}`)
return { id: messageId ?? utils.uuid() } // TODO: persistant mapping between AFJ records and Service Agent flows. Support external message id setting
} catch (error) {
Expand Down
21 changes: 19 additions & 2 deletions src/events/ConnectionEvents.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ServerConfig } from '../utils/ServerConfig'
import type { ConnectionStateChangedEvent } from '@credo-ts/core'
import type { AgentMessageProcessedEvent, ConnectionStateChangedEvent } from '@credo-ts/core'

import { ConnectionEventTypes, ConnectionRepository } from '@credo-ts/core'
import { AgentEventTypes, ConnectionEventTypes, ConnectionRepository, HangupMessage } from '@credo-ts/core'

import { ServiceAgent } from '../utils/ServiceAgent'

Expand Down Expand Up @@ -34,4 +34,21 @@ export const connectionEvents = async (agent: ServiceAgent, config: ServerConfig
await sendWebhookEvent(config.webhookUrl + '/connection-state-updated', body, config.logger)
},
)

// When a hangup message is received for a given connection, it will be effectively terminated. Service Agent controller
// will be notified about this 'termination' status
agent.events.on(AgentEventTypes.AgentMessageProcessed, async ({ payload }: AgentMessageProcessedEvent) => {
const { message, connection } = payload

if (message.type === HangupMessage.type.messageTypeUri && connection) {
const body = {
type: 'connection-state-updated',
connectionId: connection.id,
invitationId: connection.outOfBandId,
state: 'terminated',
}

await sendWebhookEvent(config.webhookUrl + '/connection-state-updated', body, config.logger)
}
})
}
19 changes: 19 additions & 0 deletions src/model/messages/TerminateConnectionMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { BaseMessage, BaseMessageOptions } from './BaseMessage'

export interface TerminateConnectionMessageOptions extends BaseMessageOptions {}

export class TerminateConnectionMessage extends BaseMessage {
public constructor(options: TerminateConnectionMessageOptions) {
super()

if (options) {
this.id = options.id ?? this.generateId()
this.threadId = options.threadId
this.timestamp = options.timestamp ?? new Date()
this.connectionId = options.connectionId
}
}

public readonly type = TerminateConnectionMessage.type
public static readonly type = 'terminate-connection'
}
1 change: 1 addition & 0 deletions src/model/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export * from './MenuDisplayMessage'
export * from './MenuSelectMessage'
export * from './ProfileMessage'
export * from './ReceiptsMessage'
export * from './TerminateConnectionMessage'
export * from './TextMessage'

0 comments on commit e6a9213

Please sign in to comment.