diff --git a/apps/shared/src/events/__tests__/didEvents.spec.ts b/apps/shared/src/events/__tests__/didEvents.spec.ts index 8776ea77515d531272414dcfebb3c742255890fc..abd678744068de7b62e03bd03bd3fcc0691923cc 100644 --- a/apps/shared/src/events/__tests__/didEvents.spec.ts +++ b/apps/shared/src/events/__tests__/didEvents.spec.ts @@ -1,22 +1,12 @@ import { DidDocument } from '@aries-framework/core'; -import { EventDidsPublicDid, EventDidsResolve } from '../didEvents.js'; +import { EventDidsResolve } from '../didEvents.js'; describe('Did Events', () => { it('should return module', () => { jest.requireActual('../didEvents'); }); - it.skip('should create get public did event', () => { - const doc = new DidDocument({ id: 'did:web:123.com' }); - const event = new EventDidsPublicDid(doc, 'tenantId'); - - expect(typeof event.id).toStrictEqual('string'); - expect(event.type).toStrictEqual('EventDidsPublicDid'); - expect(event.timestamp).toBeInstanceOf(Date); - expect(event.instance).toMatchObject(doc); - }); - it('should create did resolve event', () => { const doc = new DidDocument({ id: 'did:my:id' }); const event = new EventDidsResolve(doc, 'tenantId'); diff --git a/apps/shared/src/events/connectionEvents.ts b/apps/shared/src/events/connectionEvents.ts index a9ae32a9e47c061241b343cb004db41703b03f83..166f6cdd8cd9f7a53aebd365c37ec6d302aa4dac 100644 --- a/apps/shared/src/events/connectionEvents.ts +++ b/apps/shared/src/events/connectionEvents.ts @@ -48,6 +48,51 @@ export class EventDidcommConnectionsGetById extends BaseEvent<ConnectionRecord | } } +export type EventDidcommConnectionsCreateInvitationInput = BaseEventInput; +export class EventDidcommConnectionsCreateInvitation extends BaseEvent<{ + invitationUrl: string; +}> { + public static token = 'didcomm.connections.createInvitation'; + + public get instance() { + return this.data; + } + + public static fromEvent(e: EventDidcommConnectionsCreateInvitation) { + return new EventDidcommConnectionsCreateInvitation( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} + +export type EventDidcommConnectionsReceiveInvitationFromUrlInput = + BaseEventInput<{ + invitationUrl: string; + }>; +export class EventDidcommConnectionsReceiveInvitationFromUrl extends BaseEvent<ConnectionRecord | null> { + public static token = 'didcomm.connections.receiveInvitationFromUrl'; + + public get instance() { + return this.data + ? JsonTransformer.fromJSON(this.data, ConnectionRecord) + : null; + } + + public static fromEvent(e: EventDidcommConnectionsReceiveInvitationFromUrl) { + return new EventDidcommConnectionsReceiveInvitationFromUrl( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} + export type EventDidcommConnectionsCreateWithSelfInput = BaseEventInput; export class EventDidcommConnectionsCreateWithSelf extends BaseEvent<ConnectionRecord> { public static token = 'didcomm.connections.createWithSelf'; diff --git a/apps/ssi-abstraction/src/agent/connections/connections.controller.ts b/apps/ssi-abstraction/src/agent/connections/connections.controller.ts index 4f04f50d6352bdf72ce7359ad7d29d8b7d96769a..8b59f12e9c8cf97917058de9f7dc29b9d524f90b 100644 --- a/apps/ssi-abstraction/src/agent/connections/connections.controller.ts +++ b/apps/ssi-abstraction/src/agent/connections/connections.controller.ts @@ -9,6 +9,10 @@ import { EventDidcommConnectionsGetByIdInput, EventDidcommConnectionsCreateWithSelfInput, EventDidcommConnectionsBlockInput, + EventDidcommConnectionsCreateInvitation, + EventDidcommConnectionsCreateInvitationInput, + EventDidcommConnectionsReceiveInvitationFromUrl, + EventDidcommConnectionsReceiveInvitationFromUrlInput, } from '@ocm/shared'; import { ConnectionsService } from './connections.service.js'; @@ -37,6 +41,26 @@ export class ConnectionsController { ); } + @MessagePattern(EventDidcommConnectionsCreateInvitation.token) + public async createInvitation( + options: EventDidcommConnectionsCreateInvitationInput, + ): Promise<EventDidcommConnectionsCreateInvitation> { + return new EventDidcommConnectionsCreateInvitation( + await this.connectionsService.createInvitation(options), + options.tenantId, + ); + } + + @MessagePattern(EventDidcommConnectionsReceiveInvitationFromUrl.token) + public async receiveInvitationFromUrl( + options: EventDidcommConnectionsReceiveInvitationFromUrlInput, + ): Promise<EventDidcommConnectionsReceiveInvitationFromUrl> { + return new EventDidcommConnectionsReceiveInvitationFromUrl( + await this.connectionsService.receiveInvitationFromUrl(options), + options.tenantId, + ); + } + @MessagePattern(EventDidcommConnectionsCreateWithSelf.token) public async createConnectionWithSelf( options: EventDidcommConnectionsCreateWithSelfInput, diff --git a/apps/ssi-abstraction/src/agent/connections/connections.module.ts b/apps/ssi-abstraction/src/agent/connections/connections.module.ts index edee4f45a63a1731c0bf23487a1028f8f5dae183..0689f1a4ea6545be547e375109a3d8d49bcd5129 100644 --- a/apps/ssi-abstraction/src/agent/connections/connections.module.ts +++ b/apps/ssi-abstraction/src/agent/connections/connections.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; import { AgentModule } from '../agent.module.js'; @@ -6,7 +7,7 @@ import { ConnectionsController } from './connections.controller.js'; import { ConnectionsService } from './connections.service.js'; @Module({ - imports: [AgentModule], + imports: [AgentModule, ConfigModule], providers: [ConnectionsService], controllers: [ConnectionsController], }) diff --git a/apps/ssi-abstraction/src/agent/connections/connections.service.ts b/apps/ssi-abstraction/src/agent/connections/connections.service.ts index 2e2b3c975f3feca342a34176c29625110cff7c93..620b916110476727711ebdb77ec18c2bb116815a 100644 --- a/apps/ssi-abstraction/src/agent/connections/connections.service.ts +++ b/apps/ssi-abstraction/src/agent/connections/connections.service.ts @@ -5,9 +5,11 @@ import type { } from '@aries-framework/core'; import type { EventDidcommConnectionsBlockInput, + EventDidcommConnectionsCreateInvitationInput, EventDidcommConnectionsCreateWithSelfInput, EventDidcommConnectionsGetAllInput, EventDidcommConnectionsGetByIdInput, + EventDidcommConnectionsReceiveInvitationFromUrlInput, } from '@ocm/shared'; import { @@ -17,6 +19,7 @@ import { } from '@aries-framework/core'; import { isDid } from '@aries-framework/core/build/utils/did.js'; import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { MetadataTokens } from '../../common/constants.js'; import { AgentService } from '../agent.service.js'; @@ -29,6 +32,7 @@ export class ConnectionsService { public constructor( agentService: AgentService, private withTenantService: WithTenantService, + private configService: ConfigService, ) { this.agent = agentService.agent; } @@ -84,6 +88,47 @@ export class ConnectionsService { }); } + public async createInvitation({ + tenantId, + }: EventDidcommConnectionsCreateInvitationInput): Promise<{ + invitationUrl: string; + }> { + const host = this.configService.get<string>('agent.host'); + if (!host) { + throw new Error( + 'Could not get the `agentHost` from the config. This is required to create an invitation', + ); + } + + return this.withTenantService.invoke(tenantId, async (t) => { + const { outOfBandInvitation } = await t.oob.createInvitation(); + + return { + invitationUrl: outOfBandInvitation.toUrl({ + domain: host, + }), + }; + }); + } + + public async receiveInvitationFromUrl({ + tenantId, + invitationUrl, + }: EventDidcommConnectionsReceiveInvitationFromUrlInput): Promise<ConnectionRecord> { + return this.withTenantService.invoke(tenantId, async (t) => { + const { connectionRecord } = + await t.oob.receiveInvitationFromUrl(invitationUrl); + + if (!connectionRecord) { + throw new Error( + 'Invitation did not establish a connection. Is it a connection invitation?', + ); + } + + return connectionRecord; + }); + } + public async createConnectionWithSelf({ tenantId, }: EventDidcommConnectionsCreateWithSelfInput): Promise<ConnectionRecord> { diff --git a/apps/ssi-abstraction/test/connections.e2e-spec.ts b/apps/ssi-abstraction/test/connections.e2e-spec.ts index ff9a2e49c28d3baa6f6dc04ff345229b77370b6a..cbda3cafbfbbab328f140e5fd14673dfec87d4f5 100644 --- a/apps/ssi-abstraction/test/connections.e2e-spec.ts +++ b/apps/ssi-abstraction/test/connections.e2e-spec.ts @@ -5,8 +5,11 @@ import type { EventDidcommConnectionsCreateWithSelfInput, EventDidcommConnectionsGetByIdInput, EventDidcommConnectionsBlockInput, + EventDidcommConnectionsReceiveInvitationFromUrlInput, + EventDidcommConnectionsCreateInvitationInput, } from '@ocm/shared'; +import { ConnectionRecord } from '@aries-framework/core'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { Test } from '@nestjs/testing'; import { @@ -14,6 +17,8 @@ import { EventDidcommConnectionsGetAll, EventDidcommConnectionsCreateWithSelf, EventDidcommConnectionsBlock, + EventDidcommConnectionsReceiveInvitationFromUrl, + EventDidcommConnectionsCreateInvitation, } from '@ocm/shared'; import { firstValueFrom } from 'rxjs'; @@ -86,6 +91,53 @@ describe('Connections', () => { expect(eventInstance.instance).toBeNull(); }); + it(EventDidcommConnectionsCreateInvitation.token, async () => { + const response$ = client.send< + EventDidcommConnectionsCreateInvitation, + EventDidcommConnectionsCreateInvitationInput + >(EventDidcommConnectionsCreateInvitation.token, { + tenantId, + }); + const response = await firstValueFrom(response$); + const eventInstance = + EventDidcommConnectionsCreateInvitation.fromEvent(response); + + expect(eventInstance.instance).toMatchObject({ + invitationUrl: expect.any(String), + }); + }); + + it(EventDidcommConnectionsReceiveInvitationFromUrl.token, async () => { + const createInvitationResponse$ = client.send< + EventDidcommConnectionsCreateInvitation, + EventDidcommConnectionsCreateInvitationInput + >(EventDidcommConnectionsCreateInvitation.token, { + tenantId, + }); + const createInvitationResponse = await firstValueFrom( + createInvitationResponse$, + ); + const createInvitationEventInstance = + EventDidcommConnectionsCreateInvitation.fromEvent( + createInvitationResponse, + ); + + const { invitationUrl } = createInvitationEventInstance.instance; + + const response$ = client.send< + EventDidcommConnectionsReceiveInvitationFromUrl, + EventDidcommConnectionsReceiveInvitationFromUrlInput + >(EventDidcommConnectionsReceiveInvitationFromUrl.token, { + invitationUrl, + tenantId, + }); + const response = await firstValueFrom(response$); + const eventInstance = + EventDidcommConnectionsReceiveInvitationFromUrl.fromEvent(response); + + expect(eventInstance.instance).toBeInstanceOf(ConnectionRecord); + }); + it(EventDidcommConnectionsCreateWithSelf.token, async () => { const response$ = client.send< EventDidcommConnectionsCreateWithSelf,