Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding trust ping events and trust ping command #1182

Merged
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions packages/core/src/modules/connections/ConnectionsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ import { HandshakeProtocol } from './models'
import { ConnectionService } from './services/ConnectionService'
import { TrustPingService } from './services/TrustPingService'

export interface SendPingOptions {
responseRequested?: boolean
withReturnRouting?: boolean
}

@injectable()
export class ConnectionsApi {
/**
Expand Down Expand Up @@ -227,6 +232,41 @@ export class ConnectionsApi {
return connectionRecord
}

/**
* Send a trust ping to an established connection
*
* @param connectionId the id of the connection for which to accept the response
* @param responseRequested do we want a response to our ping
* @param withReturnRouting do we want a response at the time of posting
* @returns TurstPingMessage
*/
public async sendPing(
connectionId: string,
{ responseRequested = true, withReturnRouting = undefined }: SendPingOptions
) {
const connection = await this.getById(connectionId)

const { message } = await this.connectionService.createTrustPing(this.agentContext, connection, {
responseRequested: responseRequested,
})

if (withReturnRouting === true) {
message.setReturnRouting(ReturnRouteTypes.all)
}

// Disable return routing as we don't want to receive a response for this message over the same channel
// This has led to long timeouts as not all clients actually close an http socket if there is no response message
if (withReturnRouting === false) {
message.setReturnRouting(ReturnRouteTypes.none)
}

await this.messageSender.sendMessage(
new OutboundMessageContext(message, { agentContext: this.agentContext, connection })
)

return message
}

public async returnWhenIsConnected(connectionId: string, options?: { timeoutMs: number }): Promise<ConnectionRecord> {
return this.connectionService.returnWhenIsConnected(this.agentContext, connectionId, options?.timeoutMs)
}
Expand Down
24 changes: 24 additions & 0 deletions packages/core/src/modules/connections/TrustPingEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { BaseEvent } from '../../agent/Events'
import type { TrustPingMessage, TrustPingResponseMessage } from './messages'
import type { ConnectionRecord } from './repository/ConnectionRecord'

export enum TrustPingEventTypes {
TrustPingReceivedEvent = 'TrustPingReceivedEvent',
TrustPingResponseReceivedEvent = 'TrustPingResponseReceivedEvent',
}

export interface TrustPingReceivedEvent extends BaseEvent {
type: typeof TrustPingEventTypes.TrustPingReceivedEvent
payload: {
connectionRecord: ConnectionRecord
message: TrustPingMessage
}
}

export interface TrustPingResponseReceivedEvent extends BaseEvent {
type: typeof TrustPingEventTypes.TrustPingResponseReceivedEvent
payload: {
connectionRecord: ConnectionRecord
message: TrustPingResponseMessage
}
}
1 change: 1 addition & 0 deletions packages/core/src/modules/connections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './models'
export * from './repository'
export * from './services'
export * from './ConnectionEvents'
export * from './TrustPingEvents'
export * from './ConnectionsApi'
export * from './DidExchangeProtocol'
export * from './ConnectionsModuleConfig'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext'
import type { TrustPingReceivedEvent, TrustPingResponseReceivedEvent } from '../TrustPingEvents'
import type { TrustPingMessage } from '../messages'
import type { ConnectionRecord } from '../repository/ConnectionRecord'

import { EventEmitter } from '../../../agent/EventEmitter'
import { OutboundMessageContext } from '../../../agent/models'
import { injectable } from '../../../plugins'
import { TrustPingEventTypes } from '../TrustPingEvents'
import { TrustPingResponseMessage } from '../messages'

@injectable()
export class TrustPingService {
private eventEmitter: EventEmitter

public constructor(eventEmitter: EventEmitter) {
this.eventEmitter = eventEmitter
}

public processPing({ message, agentContext }: InboundMessageContext<TrustPingMessage>, connection: ConnectionRecord) {
this.eventEmitter.emit<TrustPingReceivedEvent>(agentContext, {
type: TrustPingEventTypes.TrustPingReceivedEvent,
payload: {
connectionRecord: connection,
message: message,
},
})

if (message.responseRequested) {
const response = new TrustPingResponseMessage({
threadId: message.id,
Expand All @@ -18,8 +35,17 @@ export class TrustPingService {
}
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
public processPingResponse(inboundMessage: InboundMessageContext<TrustPingResponseMessage>) {
// TODO: handle ping response message
const { agentContext, message } = inboundMessage

const connection = inboundMessage.assertReadyConnection()

this.eventEmitter.emit<TrustPingResponseReceivedEvent>(agentContext, {
type: TrustPingEventTypes.TrustPingResponseReceivedEvent,
payload: {
connectionRecord: connection,
message: message,
},
})
}
}
21 changes: 20 additions & 1 deletion packages/core/tests/connections.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Agent } from '../src/agent/Agent'
import { didKeyToVerkey } from '../src/modules/dids/helpers'
import { OutOfBandState } from '../src/modules/oob/domain/OutOfBandState'

import { getAgentOptions } from './helpers'
import { getAgentOptions, waitForTrustPingResponseReceivedEvent } from './helpers'

describe('connections', () => {
let faberAgent: Agent
Expand Down Expand Up @@ -85,6 +85,25 @@ describe('connections', () => {
await mediatorAgent.wallet.delete()
})

it('one agent should be able to send and receive a ping', async () => {
const faberOutOfBandRecord = await faberAgent.oob.createInvitation({
handshakeProtocols: [HandshakeProtocol.Connections],
multiUseInvitation: true,
})

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

// Receive invitation with alice agent
let { connectionRecord: aliceFaberConnection } = await aliceAgent.oob.receiveInvitationFromUrl(invitationUrl)
aliceFaberConnection = await aliceAgent.connections.returnWhenIsConnected(aliceFaberConnection!.id)
expect(aliceFaberConnection.state).toBe(DidExchangeState.Completed)

const ping = await aliceAgent.connections.sendPing(aliceFaberConnection.id, {})

await waitForTrustPingResponseReceivedEvent(aliceAgent, { threadId: ping.threadId })
})

it('one should be able to make multiple connections using a multi use invite', async () => {
const faberOutOfBandRecord = await faberAgent.oob.createInvitation({
handshakeProtocols: [HandshakeProtocol.Connections],
Expand Down
46 changes: 46 additions & 0 deletions packages/core/tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
ConnectionRecordProps,
CredentialDefinitionTemplate,
CredentialStateChangedEvent,
TrustPingResponseReceivedEvent,
InitConfig,
InjectionToken,
ProofStateChangedEvent,
Expand Down Expand Up @@ -45,6 +46,7 @@ import {
ConnectionRecord,
CredentialEventTypes,
CredentialState,
TrustPingEventTypes,
DependencyManager,
DidExchangeRole,
DidExchangeState,
Expand Down Expand Up @@ -240,6 +242,50 @@ export function waitForProofExchangeRecordSubject(
)
}

export async function waitForTrustPingResponseReceivedEvent(
agent: Agent,
options: {
threadId?: string
parentThreadId?: string
state?: ProofState
previousState?: ProofState | null
timeoutMs?: number
}
) {
const observable = agent.events.observable<TrustPingResponseReceivedEvent>(
TrustPingEventTypes.TrustPingResponseReceivedEvent
)

return waitForTrustPingResponseReceivedEventSubject(observable, options)
}

export function waitForTrustPingResponseReceivedEventSubject(
subject: ReplaySubject<TrustPingResponseReceivedEvent> | Observable<TrustPingResponseReceivedEvent>,
{
threadId,
timeoutMs = 10000,
}: {
threadId?: string
timeoutMs?: number
}
) {
const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject
return firstValueFrom(
observable.pipe(
filter((e) => threadId === undefined || e.payload.message.threadId === threadId),
timeout(timeoutMs),
catchError(() => {
throw new Error(
`TrustPingResponseReceivedEvent event not emitted within specified timeout: {
threadId: ${threadId},
}`
)
}),
map((e) => e.payload.message)
)
)
}

export function waitForCredentialRecordSubject(
subject: ReplaySubject<CredentialStateChangedEvent> | Observable<CredentialStateChangedEvent>,
{
Expand Down