Skip to content
Snippets Groups Projects
connections.service.ts 5.1 KiB
Newer Older
import type { AppAgent } from '../agent.service.js';
import type {
  ConnectionRecord,
  ConnectionStateChangedEvent,
} from '@credo-ts/core';
  EventDidcommConnectionsBlock,
  EventDidcommConnectionsBlockInput,
  EventDidcommConnectionsCreateInvitationInput,
  EventDidcommConnectionsCreateWithSelf,
  EventDidcommConnectionsCreateWithSelfInput,
  EventDidcommConnectionsGetAllInput,
  EventDidcommConnectionsGetById,
  EventDidcommConnectionsGetByIdInput,
  EventDidcommConnectionsReceiveInvitationFromUrlInput,
import {
  ConnectionEventTypes,
  ConnectionRepository,
  DidExchangeState,
} from '@credo-ts/core';
import { isDid } from '@credo-ts/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';
import { WithTenantService } from '../withTenantService.js';

@Injectable()
export class ConnectionsService {
  private agent: AppAgent;
  public constructor(
    agentService: AgentService,
    private withTenantService: WithTenantService,
    private configService: ConfigService,
    this.agent = agentService.agent;
  }

  public async getAll({
    tenantId,
  }: EventDidcommConnectionsGetAllInput): Promise<Array<ConnectionRecord>> {
    return this.withTenantService.invoke(tenantId, (t) =>
      t.connections.getAll(),
    );
  public async getById({
    tenantId,
    id,
  }: EventDidcommConnectionsGetByIdInput): Promise<
    EventDidcommConnectionsGetById['data']
  > {
    return this.withTenantService.invoke(tenantId, (t) =>
      t.connections.findById(id),
    );
  public async blockByIdOrDid({
    tenantId,
    idOrDid,
  }: EventDidcommConnectionsBlockInput): Promise<
    EventDidcommConnectionsBlock['data']
  > {
    return this.withTenantService.invoke(tenantId, async (t) => {
      if (isDid(idOrDid)) {
        const records = await t.connections.findAllByQuery({
          theirDid: idOrDid,
        });

        if (records.length === 0) {
          return null;
        }

        if (records.length > 1) {
          throw new Error(
            'Found multiple records with the same DID. This should not be possible',
          );
        }
        await t.connections.deleteById(records[0].id);
      const record = await t.connections.findById(idOrDid);
      if (!record) return null;
      await t.connections.deleteById(record.id);
  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<
    EventDidcommConnectionsCreateWithSelf['data']
  > {
    return this.withTenantService.invoke(tenantId, async (t) => {
      const outOfBandRecord = await t.oob.createInvitation();
      const invitation = outOfBandRecord.outOfBandInvitation;

      const { connectionRecord } = await t.oob.receiveInvitation(invitation);

      if (connectionRecord) {
        connectionRecord.metadata.set(MetadataTokens.CONNECTION_METADATA_KEY, {
          trusted: true,
          withSelf: true,
        });

        const connRepo = t.dependencyManager.resolve(ConnectionRepository);
        await connRepo.update(t.context, connectionRecord);
      }

      return new Promise((resolve) =>
        this.agent.events.on<ConnectionStateChangedEvent>(
          ConnectionEventTypes.ConnectionStateChanged,
          async ({ payload: { connectionRecord } }) => {
            if (connectionRecord.state !== DidExchangeState.Completed) return;
            connectionRecord.metadata.set(
                withSelf: true,
              },
            );

            const connRepo = t.dependencyManager.resolve(ConnectionRepository);
            await connRepo.update(t.context, connectionRecord);

            resolve(connectionRecord);
          },
        ),
      );
    });