From dad975d9d9b658c6b37749ece2a91381e2a314c9 Mon Sep 17 00:00:00 2001 From: Jan <60812202+janrtvld@users.noreply.github.com> Date: Wed, 24 Aug 2022 22:06:15 +0200 Subject: [PATCH 001/125] fix: expose oob domain (#990) --- packages/core/src/modules/oob/domain/index.ts | 3 +++ packages/core/src/modules/oob/index.ts | 1 + 2 files changed, 4 insertions(+) create mode 100644 packages/core/src/modules/oob/domain/index.ts diff --git a/packages/core/src/modules/oob/domain/index.ts b/packages/core/src/modules/oob/domain/index.ts new file mode 100644 index 0000000000..43c069cafb --- /dev/null +++ b/packages/core/src/modules/oob/domain/index.ts @@ -0,0 +1,3 @@ +export * from './OutOfBandRole' +export * from './OutOfBandState' +export * from './OutOfBandDidCommService' diff --git a/packages/core/src/modules/oob/index.ts b/packages/core/src/modules/oob/index.ts index e9873404a1..2216f42770 100644 --- a/packages/core/src/modules/oob/index.ts +++ b/packages/core/src/modules/oob/index.ts @@ -2,3 +2,4 @@ export * from './messages' export * from './repository' export * from './OutOfBandModule' export * from './OutOfBandService' +export * from './domain' From f90a27b05a56b75e033ac28acc185e91d58ce4f8 Mon Sep 17 00:00:00 2001 From: Lance <2byrds@gmail.com> Date: Fri, 26 Aug 2022 14:59:25 -0400 Subject: [PATCH 002/125] test(credentials): fix flaky tests with events (#966) Signed-off-by: 2byrds <2byrds@gmail.com> --- .../v2-credentials-auto-accept.e2e.test.ts | 299 +++++++++--------- .../v2/__tests__/v2-credentials.e2e.test.ts | 133 +++++--- packages/core/tests/helpers.ts | 12 +- 3 files changed, 249 insertions(+), 195 deletions(-) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts index 09612c9877..5036232d4a 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts @@ -5,9 +5,7 @@ import type { Schema } from 'indy-sdk' import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { sleep } from '../../../../../utils/sleep' import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' @@ -50,9 +48,18 @@ describe('v2 credentials', () => { }) test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { - testLogger.test('Alice sends credential proposal to Faber') + testLogger.test('Alice begins listening for credential') + const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { + state: CredentialState.CredentialReceived, + }) - const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + testLogger.test('Faber begins listening for credential ack') + const faberCredAckPromise = waitForCredentialRecord(faberAgent, { + state: CredentialState.Done, + }) + + testLogger.test('Alice sends credential proposal to Faber') + await aliceAgent.credentials.proposeCredential({ connectionId: aliceConnection.id, protocolVersion: 'v2', credentialFormats: { @@ -65,16 +72,10 @@ describe('v2 credentials', () => { }) testLogger.test('Alice waits for credential from Faber') - let aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: aliceCredentialExchangeRecord.threadId, - state: CredentialState.CredentialReceived, - }) + let aliceCredentialRecord = await aliceCredReceivedPromise testLogger.test('Faber waits for credential ack from Alice') - aliceCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.Done, - }) + aliceCredentialRecord = await faberCredAckPromise expect(aliceCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, @@ -93,9 +94,19 @@ describe('v2 credentials', () => { }) test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `always`', async () => { + testLogger.test('Alice begins listening for credential') + const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber begins listening for credential ack') + const faberCredAckPromise = waitForCredentialRecord(faberAgent, { + state: CredentialState.Done, + }) + testLogger.test('Faber sends credential offer to Alice') const schemaId = schema.id - const faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ + await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', connectionId: faberConnection.id, credentialFormats: { @@ -107,15 +118,7 @@ describe('v2 credentials', () => { protocolVersion: 'v2', }) testLogger.test('Alice waits for credential from Faber') - const aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.CredentialReceived, - }) - testLogger.test('Faber waits for credential ack from Alice') - const faberCredentialRecord: CredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.Done, - }) + const aliceCredentialRecord = await aliceCredReceivedPromise expect(aliceCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), @@ -137,6 +140,9 @@ describe('v2 credentials', () => { ], state: CredentialState.CredentialReceived, }) + + testLogger.test('Faber waits for credential ack from Alice') + const faberCredentialRecord: CredentialExchangeRecord = await faberCredAckPromise expect(faberCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), @@ -168,10 +174,13 @@ describe('v2 credentials', () => { test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { testLogger.test('Alice sends credential proposal to Faber') const schemaId = schema.id - let faberCredentialExchangeRecord: CredentialExchangeRecord - let aliceCredentialExchangeRecord: CredentialExchangeRecord - aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + testLogger.test('Faber starts listening for credential proposal from Alice') + const faberPropReceivedPromise = waitForCredentialRecord(faberAgent, { + state: CredentialState.ProposalReceived, + }) + + await aliceAgent.credentials.proposeCredential({ connectionId: aliceConnection.id, protocolVersion: 'v2', credentialFormats: { @@ -183,13 +192,20 @@ describe('v2 credentials', () => { }) testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialExchangeRecord.threadId, - state: CredentialState.ProposalReceived, + const faberPropReceivedRecord = await faberPropReceivedPromise + + const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { + threadId: faberPropReceivedRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + const faberCredAckPromise = waitForCredentialRecord(faberAgent, { + threadId: faberPropReceivedRecord.threadId, + state: CredentialState.Done, }) const options: AcceptProposalOptions = { - credentialRecordId: faberCredentialExchangeRecord.id, + credentialRecordId: faberPropReceivedRecord.id, comment: 'V2 Indy Offer', credentialFormats: { indy: { @@ -199,22 +215,13 @@ describe('v2 credentials', () => { }, } testLogger.test('Faber sends credential offer to Alice') - options.credentialRecordId = faberCredentialExchangeRecord.id - faberCredentialExchangeRecord = await faberAgent.credentials.acceptProposal(options) + options.credentialRecordId = faberPropReceivedRecord.id + await faberAgent.credentials.acceptProposal(options) testLogger.test('Alice waits for credential from Faber') - aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.Done, - }) + const aliceCredReceivedRecord = await aliceCredReceivedPromise - expect(aliceCredentialExchangeRecord).toMatchObject({ + expect(aliceCredReceivedRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), @@ -236,7 +243,10 @@ describe('v2 credentials', () => { state: CredentialState.CredentialReceived, }) - expect(faberCredentialExchangeRecord).toMatchObject({ + testLogger.test('Faber waits for credential ack from Alice') + const faberCredAckRecord = await faberCredAckPromise + + expect(faberCredAckRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), @@ -253,12 +263,14 @@ describe('v2 credentials', () => { }) test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `contentApproved`', async () => { + testLogger.test('Alice starts listening for credential offer from Faber') + const aliceOfferReceivedPromise = waitForCredentialRecord(aliceAgent, { + state: CredentialState.OfferReceived, + }) + testLogger.test('Faber sends credential offer to Alice') const schemaId = schema.id - let aliceCredentialExchangeRecord: CredentialExchangeRecord - let faberCredentialExchangeRecord: CredentialExchangeRecord - - faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ + await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', connectionId: faberConnection.id, credentialFormats: { @@ -271,79 +283,80 @@ describe('v2 credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.OfferReceived, - }) + const aliceOfferReceivedRecord = await aliceOfferReceivedPromise - expect(JsonTransformer.toJSON(aliceCredentialExchangeRecord)).toMatchObject({ + expect(JsonTransformer.toJSON(aliceOfferReceivedRecord)).toMatchObject({ state: CredentialState.OfferReceived, }) // below values are not in json object - expect(aliceCredentialExchangeRecord.id).not.toBeNull() - expect(aliceCredentialExchangeRecord.getTags()).toEqual({ - threadId: aliceCredentialExchangeRecord.threadId, - state: aliceCredentialExchangeRecord.state, + expect(aliceOfferReceivedRecord.id).not.toBeNull() + expect(aliceOfferReceivedRecord.getTags()).toEqual({ + threadId: aliceOfferReceivedRecord.threadId, + state: aliceOfferReceivedRecord.state, connectionId: aliceConnection.id, credentialIds: [], }) + testLogger.test('Alice received credential offer from Faber') + + testLogger.test('Alice starts listening for credential from Faber') + const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { + state: CredentialState.CredentialReceived, + }) + + const faberCredAckPromise = waitForCredentialRecord(faberAgent, { + state: CredentialState.Done, + }) + + const acceptOfferOptions: AcceptOfferOptions = { + credentialRecordId: aliceOfferReceivedRecord.id, + } + testLogger.test('alice sends credential request to faber') + await aliceAgent.credentials.acceptOffer(acceptOfferOptions) - if (aliceCredentialExchangeRecord.connectionId) { - const acceptOfferOptions: AcceptOfferOptions = { - credentialRecordId: aliceCredentialExchangeRecord.id, - } - testLogger.test('alice sends credential request to faber') - faberCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer(acceptOfferOptions) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.Done, - }) - - expect(aliceCredentialExchangeRecord).toMatchObject({ - type: CredentialExchangeRecord.type, - id: expect.any(String), - createdAt: expect.any(Date), - metadata: { - data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { - schemaId, - credentialDefinitionId: credDefId, - }, + testLogger.test('Alice waits for credential from Faber') + const aliceCredReceivedRecord = await aliceCredReceivedPromise + expect(aliceCredReceivedRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyRequest': expect.any(Object), + '_internal/indyCredential': { + schemaId, + credentialDefinitionId: credDefId, }, }, - credentials: [ - { - credentialRecordType: 'indy', - credentialRecordId: expect.any(String), - }, - ], - state: CredentialState.CredentialReceived, - }) - - expect(faberCredentialExchangeRecord).toMatchObject({ - type: CredentialExchangeRecord.type, - id: expect.any(String), - createdAt: expect.any(Date), - state: CredentialState.Done, - }) - } else { - throw new AriesFrameworkError('missing alice connection id') - } + }, + credentials: [ + { + credentialRecordType: 'indy', + credentialRecordId: expect.any(String), + }, + ], + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber waits for credential ack from Alice') + const faberCredAckRecord = await faberCredAckPromise + + expect(faberCredAckRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + }) }) test('Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + testLogger.test('Faber starts listening for proposal from Alice') + const faberPropReceivedPromise = waitForCredentialRecord(faberAgent, { + state: CredentialState.ProposalReceived, + }) + testLogger.test('Alice sends credential proposal to Faber') - const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + const aliceCredProposal = await aliceAgent.credentials.proposeCredential({ connectionId: aliceConnection.id, protocolVersion: 'v2', credentialFormats: { @@ -354,15 +367,19 @@ describe('v2 credentials', () => { }, comment: 'v2 propose credential test', }) + expect(aliceCredProposal.state).toBe(CredentialState.ProposalSent) testLogger.test('Faber waits for credential proposal from Alice') - let faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialExchangeRecord.threadId, - state: CredentialState.ProposalReceived, + const faberPropReceivedRecord = await faberPropReceivedPromise + + testLogger.test('Alice starts listening for credential offer from Faber') + const aliceOfferReceivedPromise = waitForCredentialRecord(aliceAgent, { + state: CredentialState.OfferReceived, }) - await faberAgent.credentials.negotiateProposal({ - credentialRecordId: faberCredentialExchangeRecord.id, + testLogger.test('Faber negotiated proposal, sending credential offer to Alice') + const faberOfferSentRecord = await faberAgent.credentials.negotiateProposal({ + credentialRecordId: faberPropReceivedRecord.id, credentialFormats: { indy: { credentialDefinitionId: credDefId, @@ -372,32 +389,33 @@ describe('v2 credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - - const record = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.OfferReceived, - }) + const aliceOfferReceivedRecord = await aliceOfferReceivedPromise // below values are not in json object - expect(record.id).not.toBeNull() - expect(record.getTags()).toEqual({ - threadId: record.threadId, - state: record.state, + expect(aliceOfferReceivedRecord.id).not.toBeNull() + expect(aliceOfferReceivedRecord.getTags()).toEqual({ + threadId: aliceOfferReceivedRecord.threadId, + state: aliceOfferReceivedRecord.state, connectionId: aliceConnection.id, credentialIds: [], }) // Check if the state of the credential records did not change - faberCredentialExchangeRecord = await faberAgent.credentials.getById(faberCredentialExchangeRecord.id) - faberCredentialExchangeRecord.assertState(CredentialState.OfferSent) + const faberRecord = await faberAgent.credentials.getById(faberOfferSentRecord.id) + faberRecord.assertState(CredentialState.OfferSent) - const aliceRecord = await aliceAgent.credentials.getById(record.id) + const aliceRecord = await aliceAgent.credentials.getById(aliceOfferReceivedRecord.id) aliceRecord.assertState(CredentialState.OfferReceived) }) test('Faber starts with V2 credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + testLogger.test('Alice starts listening for offer from Faber') + const aliceCredentialExchangeRecordPromise = waitForCredentialRecord(aliceAgent, { + state: CredentialState.OfferReceived, + }) + testLogger.test('Faber sends credential offer to Alice') - let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ + await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', connectionId: faberConnection.id, credentialFormats: { @@ -410,23 +428,25 @@ describe('v2 credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - let aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.OfferReceived, - }) + const aliceOfferReceivedRecord = await aliceCredentialExchangeRecordPromise // below values are not in json object - expect(aliceCredentialExchangeRecord.id).not.toBeNull() - expect(aliceCredentialExchangeRecord.getTags()).toEqual({ - threadId: aliceCredentialExchangeRecord.threadId, - state: aliceCredentialExchangeRecord.state, + expect(aliceOfferReceivedRecord.id).not.toBeNull() + expect(aliceOfferReceivedRecord.getTags()).toEqual({ + threadId: aliceOfferReceivedRecord.threadId, + state: aliceOfferReceivedRecord.state, connectionId: aliceConnection.id, credentialIds: [], }) + testLogger.test('Faber starts listening for proposal received') + const faberProposalReceivedPromise = waitForCredentialRecord(faberAgent, { + state: CredentialState.ProposalReceived, + }) + testLogger.test('Alice sends credential request to Faber') - const aliceExchangeCredentialRecord = await aliceAgent.credentials.negotiateOffer({ - credentialRecordId: aliceCredentialExchangeRecord.id, + const aliceCredRequestRecord = await aliceAgent.credentials.negotiateOffer({ + credentialRecordId: aliceOfferReceivedRecord.id, credentialFormats: { indy: { attributes: newCredentialPreview.attributes, @@ -437,19 +457,14 @@ describe('v2 credentials', () => { }) testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceExchangeCredentialRecord.threadId, - state: CredentialState.ProposalReceived, - }) - - await sleep(5000) + const faberCredProposalRecord = await faberProposalReceivedPromise // Check if the state of fabers credential record did not change - const faberRecord = await faberAgent.credentials.getById(faberCredentialExchangeRecord.id) + const faberRecord = await faberAgent.credentials.getById(faberCredProposalRecord.id) faberRecord.assertState(CredentialState.ProposalReceived) - aliceCredentialExchangeRecord = await aliceAgent.credentials.getById(aliceCredentialExchangeRecord.id) - aliceCredentialExchangeRecord.assertState(CredentialState.ProposalSent) + const aliceRecord = await aliceAgent.credentials.getById(aliceCredRequestRecord.id) + aliceRecord.assertState(CredentialState.ProposalSent) }) }) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts index 0fa0e2c9f2..e8c7905e73 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts @@ -1,8 +1,15 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections' +import type { CredentialStateChangedEvent } from '../../../CredentialEvents' import type { IndyCredPropose } from '../../../formats/indy/models/IndyCredPropose' +import type { ReplaySubject } from 'rxjs' -import { issueCredential, setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' +import { + issueCredential, + setupCredentialTests, + waitForCredentialRecord, + waitForCredentialRecordSubject, +} from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' import { JsonTransformer } from '../../../../../utils' @@ -32,6 +39,9 @@ describe('v2 credentials', () => { let aliceConnection: ConnectionRecord let aliceCredentialRecord: CredentialExchangeRecord let faberCredentialRecord: CredentialExchangeRecord + let faberReplay: ReplaySubject + let aliceReplay: ReplaySubject + let credPropose: IndyCredPropose const newCredentialPreview = V2CredentialPreview.fromRecord({ @@ -42,10 +52,9 @@ describe('v2 credentials', () => { }) beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection } = await setupCredentialTests( - 'Faber Agent Credentials v2', - 'Alice Agent Credentials v2' - )) + ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, faberReplay, aliceReplay } = + await setupCredentialTests('Faber Agent Credentials v2', 'Alice Agent Credentials v2')) + credPropose = { credentialDefinitionId: credDefId, schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', @@ -91,7 +100,7 @@ describe('v2 credentials', () => { }) testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: credentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) @@ -109,7 +118,7 @@ describe('v2 credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.OfferReceived, }) @@ -178,7 +187,7 @@ describe('v2 credentials', () => { }) testLogger.test('Faber waits for credential request from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: aliceCredentialRecord.threadId, state: CredentialState.RequestReceived, }) @@ -190,7 +199,7 @@ describe('v2 credentials', () => { }) testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.CredentialReceived, }) @@ -200,7 +209,7 @@ describe('v2 credentials', () => { }) testLogger.test('Faber waits for state done') - await waitForCredentialRecord(faberAgent, { + await waitForCredentialRecordSubject(faberReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.Done, }) @@ -237,6 +246,10 @@ describe('v2 credentials', () => { test('Alice starts with proposal, faber sends a counter offer, alice sends second proposal, faber sends second offer', async () => { // proposeCredential -> negotiateProposal -> negotiateOffer -> negotiateProposal -> acceptOffer -> acceptRequest -> DONE (credential issued) + let faberCredentialRecordPromise = waitForCredentialRecord(faberAgent, { + state: CredentialState.ProposalReceived, + }) + testLogger.test('Alice sends credential proposal to Faber') let aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ connectionId: aliceConnection.id, @@ -252,9 +265,11 @@ describe('v2 credentials', () => { expect(aliceCredentialExchangeRecord.state).toBe(CredentialState.ProposalSent) testLogger.test('Faber waits for credential proposal from Alice') - let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialExchangeRecord.threadId, - state: CredentialState.ProposalReceived, + let faberCredentialRecord = await faberCredentialRecordPromise + + let aliceCredentialRecordPromise = waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, }) faberCredentialRecord = await faberAgent.credentials.negotiateProposal({ @@ -268,10 +283,7 @@ describe('v2 credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - let aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.OfferReceived, - }) + let aliceCredentialRecord = await aliceCredentialRecordPromise // Check if the state of the credential records did not change faberCredentialRecord = await faberAgent.credentials.getById(faberCredentialRecord.id) @@ -280,6 +292,11 @@ describe('v2 credentials', () => { aliceCredentialRecord = await aliceAgent.credentials.getById(aliceCredentialRecord.id) aliceCredentialRecord.assertState(CredentialState.OfferReceived) + faberCredentialRecordPromise = waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + // second proposal aliceCredentialExchangeRecord = await aliceAgent.credentials.negotiateOffer({ credentialRecordId: aliceCredentialRecord.id, @@ -294,9 +311,11 @@ describe('v2 credentials', () => { expect(aliceCredentialExchangeRecord.state).toBe(CredentialState.ProposalSent) testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialExchangeRecord.threadId, - state: CredentialState.ProposalReceived, + faberCredentialRecord = await faberCredentialRecordPromise + + aliceCredentialRecordPromise = waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, }) faberCredentialRecord = await faberAgent.credentials.negotiateProposal({ @@ -311,10 +330,7 @@ describe('v2 credentials', () => { testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.OfferReceived, - }) + aliceCredentialRecord = await aliceCredentialRecordPromise const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ credentialRecordId: aliceCredentialExchangeRecord.id, @@ -328,7 +344,7 @@ describe('v2 credentials', () => { }) testLogger.test('Faber waits for credential request from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: aliceCredentialExchangeRecord.threadId, state: CredentialState.RequestReceived, }) @@ -340,16 +356,15 @@ describe('v2 credentials', () => { }) testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.CredentialReceived, }) - // testLogger.test('Alice sends credential ack to Faber') await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.Done, }) @@ -364,8 +379,12 @@ describe('v2 credentials', () => { }) test('Faber starts with offer, alice sends counter proposal, faber sends second offer, alice sends second proposal', async () => { + let aliceCredentialRecordPromise = waitForCredentialRecord(aliceAgent, { + state: CredentialState.OfferReceived, + }) + testLogger.test('Faber sends credential offer to Alice') - const faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ + let faberCredentialRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', connectionId: faberConnection.id, credentialFormats: { @@ -378,9 +397,11 @@ describe('v2 credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.OfferReceived, + aliceCredentialRecord = await aliceCredentialRecordPromise + + let faberCredentialRecordPromise = waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.ProposalReceived, }) aliceCredentialRecord = await aliceAgent.credentials.negotiateOffer({ @@ -396,10 +417,13 @@ describe('v2 credentials', () => { expect(aliceCredentialRecord.state).toBe(CredentialState.ProposalSent) testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.ProposalReceived, + faberCredentialRecord = await faberCredentialRecordPromise + + aliceCredentialRecordPromise = waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, }) + faberCredentialRecord = await faberAgent.credentials.negotiateProposal({ credentialRecordId: faberCredentialRecord.id, credentialFormats: { @@ -412,9 +436,11 @@ describe('v2 credentials', () => { testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.OfferReceived, + aliceCredentialRecord = await aliceCredentialRecordPromise + + faberCredentialRecordPromise = waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.ProposalReceived, }) aliceCredentialRecord = await aliceAgent.credentials.negotiateOffer({ @@ -430,9 +456,11 @@ describe('v2 credentials', () => { expect(aliceCredentialRecord.state).toBe(CredentialState.ProposalSent) testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.ProposalReceived, + faberCredentialRecord = await faberCredentialRecordPromise + + aliceCredentialRecordPromise = waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, }) testLogger.test('Faber sends credential offer to Alice') @@ -448,9 +476,11 @@ describe('v2 credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.OfferReceived, + aliceCredentialRecord = await aliceCredentialRecordPromise + + faberCredentialRecordPromise = waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, }) const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ @@ -464,9 +494,11 @@ describe('v2 credentials', () => { }) testLogger.test('Faber waits for credential request from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.RequestReceived, + faberCredentialRecord = await faberCredentialRecordPromise + + aliceCredentialRecordPromise = waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, }) testLogger.test('Faber sends credential to Alice') @@ -476,10 +508,7 @@ describe('v2 credentials', () => { }) testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.CredentialReceived, - }) + aliceCredentialRecord = await aliceCredentialRecordPromise const proposalMessage = await aliceAgent.credentials.findProposalMessage(aliceCredentialRecord.id) const offerMessage = await aliceAgent.credentials.findOfferMessage(aliceCredentialRecord.id) @@ -608,7 +637,7 @@ describe('v2 credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index c6e72564d7..d021344fb8 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -638,7 +638,17 @@ export async function setupCredentialTests( const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) - return { faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } + const faberReplay = new ReplaySubject() + const aliceReplay = new ReplaySubject() + + faberAgent.events + .observable(CredentialEventTypes.CredentialStateChanged) + .subscribe(faberReplay) + aliceAgent.events + .observable(CredentialEventTypes.CredentialStateChanged) + .subscribe(aliceReplay) + + return { faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection, faberReplay, aliceReplay } } export async function setupProofsTest(faberName: string, aliceName: string, autoAcceptProofs?: AutoAcceptProof) { From 5f91738337fac1efbbb4597e7724791e542f0762 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht <61358536+blu3beri@users.noreply.github.com> Date: Mon, 2 May 2022 13:57:55 +0200 Subject: [PATCH 003/125] feat: bbs createKey, sign and verify (#684) Signed-off-by: Berend Sliedrecht --- packages/core/package.json | 1 + packages/core/src/agent/EnvelopeService.ts | 3 +- packages/core/src/agent/MessageSender.ts | 3 +- .../src/agent/__tests__/MessageSender.test.ts | 4 +- packages/core/src/agent/helpers.ts | 2 +- .../src/agent/models/InboundMessageContext.ts | 2 +- packages/core/src/crypto/BbsService.ts | 151 +++++++++++ packages/core/src/crypto/JwsService.ts | 11 +- .../{modules/dids/domain => crypto}/Key.ts | 6 +- .../src/crypto/__tests__/JwsService.test.ts | 3 +- packages/core/src/crypto/index.ts | 1 + .../key-type => crypto}/multiCodecKey.ts | 2 +- .../signature/SignatureDecoratorUtils.ts | 8 +- .../modules/connections/ConnectionsModule.ts | 10 +- .../connections/DidExchangeProtocol.ts | 4 +- .../__tests__/ConnectionService.test.ts | 4 +- .../connections/services/ConnectionService.ts | 3 +- .../modules/connections/services/helpers.ts | 4 +- packages/core/src/modules/dids/DidsModule.ts | 2 +- .../modules/dids/__tests__/peer-did.test.ts | 4 +- .../src/modules/dids/domain/DidDocument.ts | 3 +- .../dids/domain/createPeerDidFromServices.ts | 3 +- .../core/src/modules/dids/domain/index.ts | 1 - .../key-type/__tests__/bls12381g1.test.ts | 4 +- .../key-type/__tests__/bls12381g1g2.test.ts | 4 +- .../key-type/__tests__/bls12381g2.test.ts | 4 +- .../domain/key-type/__tests__/ed25519.test.ts | 4 +- .../domain/key-type/__tests__/x25519.test.ts | 4 +- .../dids/domain/key-type/bls12381g1.ts | 2 +- .../dids/domain/key-type/bls12381g1g2.ts | 2 +- .../dids/domain/key-type/bls12381g2.ts | 2 +- .../modules/dids/domain/key-type/ed25519.ts | 2 +- .../src/modules/dids/domain/key-type/index.ts | 1 - .../dids/domain/key-type/keyDidMapping.ts | 2 +- .../modules/dids/domain/key-type/x25519.ts | 2 +- .../src/modules/dids/domain/keyDidDocument.ts | 3 +- packages/core/src/modules/dids/helpers.ts | 3 +- .../src/modules/dids/methods/key/DidKey.ts | 2 +- .../dids/methods/key/__tests__/DidKey.test.ts | 2 +- .../peer/__tests__/peerDidNumAlgo0.test.ts | 2 +- .../dids/methods/peer/peerDidNumAlgo0.ts | 2 +- .../dids/methods/peer/peerDidNumAlgo2.ts | 3 +- .../modules/dids/repository/DidRepository.ts | 2 +- .../core/src/modules/oob/OutOfBandModule.ts | 6 +- .../core/src/modules/oob/OutOfBandService.ts | 2 +- .../oob/__tests__/OutOfBandService.test.ts | 3 +- .../oob/messages/OutOfBandInvitation.ts | 2 +- .../services/MediationRecipientService.ts | 5 +- .../routing/services/RoutingService.ts | 3 +- .../MediationRecipientService.test.ts | 2 +- .../services/__tests__/RoutingService.test.ts | 2 +- packages/core/src/types.ts | 2 +- packages/core/src/utils/TypedArrayEncoder.ts | 14 + .../utils/__tests__/TypedArrayEncoder.test.ts | 17 ++ packages/core/src/wallet/IndyWallet.test.ts | 142 ++++++++++ packages/core/src/wallet/IndyWallet.ts | 169 ++++++++++-- packages/core/src/wallet/Wallet.test.ts | 24 -- packages/core/src/wallet/Wallet.ts | 25 +- packages/core/tests/helpers.ts | 24 +- packages/core/tests/oob.test.ts | 2 +- packages/react-native/package.json | 1 + yarn.lock | 242 +++++++++++++++++- 62 files changed, 825 insertions(+), 149 deletions(-) create mode 100644 packages/core/src/crypto/BbsService.ts rename packages/core/src/{modules/dids/domain => crypto}/Key.ts (89%) rename packages/core/src/{modules/dids/domain/key-type => crypto}/multiCodecKey.ts (95%) create mode 100644 packages/core/src/wallet/IndyWallet.test.ts delete mode 100644 packages/core/src/wallet/Wallet.test.ts diff --git a/packages/core/package.json b/packages/core/package.json index 70865022be..20eb074d14 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -23,6 +23,7 @@ "prepublishOnly": "yarn run build" }, "dependencies": { + "@mattrglobal/bbs-signatures": "^1.0.0", "@multiformats/base-x": "^4.0.1", "@stablelib/ed25519": "^1.0.2", "@stablelib/sha256": "^1.0.1", diff --git a/packages/core/src/agent/EnvelopeService.ts b/packages/core/src/agent/EnvelopeService.ts index 9b713f927c..de6e7e9e5b 100644 --- a/packages/core/src/agent/EnvelopeService.ts +++ b/packages/core/src/agent/EnvelopeService.ts @@ -3,8 +3,7 @@ import type { EncryptedMessage, PlaintextMessage } from '../types' import type { AgentMessage } from './AgentMessage' import { InjectionSymbols } from '../constants' -import { KeyType } from '../crypto' -import { Key } from '../modules/dids' +import { Key, KeyType } from '../crypto' import { ForwardMessage } from '../modules/routing/messages' import { inject, injectable } from '../plugins' import { Wallet } from '../wallet/Wallet' diff --git a/packages/core/src/agent/MessageSender.ts b/packages/core/src/agent/MessageSender.ts index 83e5a717b7..46fe9a86a2 100644 --- a/packages/core/src/agent/MessageSender.ts +++ b/packages/core/src/agent/MessageSender.ts @@ -1,5 +1,6 @@ +import type { Key } from '../crypto' import type { ConnectionRecord } from '../modules/connections' -import type { DidDocument, Key } from '../modules/dids' +import type { DidDocument } from '../modules/dids' import type { OutOfBandRecord } from '../modules/oob/repository' import type { OutboundTransport } from '../transport/OutboundTransport' import type { OutboundMessage, OutboundPackage, EncryptedMessage } from '../types' diff --git a/packages/core/src/agent/__tests__/MessageSender.test.ts b/packages/core/src/agent/__tests__/MessageSender.test.ts index f2c0d363e1..d7158a9f47 100644 --- a/packages/core/src/agent/__tests__/MessageSender.test.ts +++ b/packages/core/src/agent/__tests__/MessageSender.test.ts @@ -8,9 +8,9 @@ import type { ResolvedDidCommService } from '../MessageSender' import { TestMessage } from '../../../tests/TestMessage' import { getAgentConfig, getMockConnection, mockFunction } from '../../../tests/helpers' import testLogger from '../../../tests/logger' -import { KeyType } from '../../crypto' +import { Key, KeyType } from '../../crypto' import { ReturnRouteTypes } from '../../decorators/transport/TransportDecorator' -import { Key, DidDocument, VerificationMethod } from '../../modules/dids' +import { DidDocument, VerificationMethod } from '../../modules/dids' import { DidCommV1Service } from '../../modules/dids/domain/service/DidCommV1Service' import { DidResolverService } from '../../modules/dids/services/DidResolverService' import { InMemoryMessageRepository } from '../../storage/InMemoryMessageRepository' diff --git a/packages/core/src/agent/helpers.ts b/packages/core/src/agent/helpers.ts index b3516a1f25..8bce437d96 100644 --- a/packages/core/src/agent/helpers.ts +++ b/packages/core/src/agent/helpers.ts @@ -1,5 +1,5 @@ +import type { Key } from '../crypto' import type { ConnectionRecord } from '../modules/connections' -import type { Key } from '../modules/dids/domain/Key' import type { OutOfBandRecord } from '../modules/oob/repository' import type { OutboundMessage, OutboundServiceMessage } from '../types' import type { AgentMessage } from './AgentMessage' diff --git a/packages/core/src/agent/models/InboundMessageContext.ts b/packages/core/src/agent/models/InboundMessageContext.ts index 34ea296f49..be7e1d4eb9 100644 --- a/packages/core/src/agent/models/InboundMessageContext.ts +++ b/packages/core/src/agent/models/InboundMessageContext.ts @@ -1,5 +1,5 @@ +import type { Key } from '../../crypto' import type { ConnectionRecord } from '../../modules/connections' -import type { Key } from '../../modules/dids' import type { AgentMessage } from '../AgentMessage' import { AriesFrameworkError } from '../../error' diff --git a/packages/core/src/crypto/BbsService.ts b/packages/core/src/crypto/BbsService.ts new file mode 100644 index 0000000000..c00e814249 --- /dev/null +++ b/packages/core/src/crypto/BbsService.ts @@ -0,0 +1,151 @@ +import type { CreateKeyOptions } from '../wallet' +import type { BlsKeyPair as _BlsKeyPair } from '@mattrglobal/bbs-signatures' + +import { + bls12381toBbs, + generateBls12381G2KeyPair, + generateBls12381G1KeyPair, + sign, + verify, +} from '@mattrglobal/bbs-signatures' + +import { TypedArrayEncoder } from '../utils/TypedArrayEncoder' +import { Buffer } from '../utils/buffer' +import { WalletError } from '../wallet/error' + +import { KeyType } from './KeyType' + +export interface BlsKeyPair { + publicKeyBase58: string + privateKeyBase58: string + keyType: Extract +} + +interface BbsCreateKeyOptions extends CreateKeyOptions { + keyType: Extract +} + +interface BbsSignOptions { + messages: Buffer | Buffer[] + publicKey: Buffer + privateKey: Buffer +} + +interface BbsVerifyOptions { + publicKey: Buffer + signature: Buffer + messages: Buffer | Buffer[] +} + +export class BbsService { + /** + * Create an instance of a Key class for the following key types: + * - Bls12381g1 + * - Bls12381g2 + * + * @param keyType KeyType The type of key to be created (see above for the accepted types) + * + * @returns A Key class with the public key and key type + * + * @throws {WalletError} When a key could not be created + * @throws {WalletError} When the method is called with an invalid keytype + */ + public static async createKey({ keyType, seed }: BbsCreateKeyOptions): Promise { + // Generate bytes from the seed as required by the bbs-signatures libraries + const seedBytes = seed ? TypedArrayEncoder.fromString(seed) : undefined + + // Temporary keypair holder + let blsKeyPair: Required<_BlsKeyPair> + + switch (keyType) { + case KeyType.Bls12381g1: + // Generate a bls12-381G1 keypair + blsKeyPair = await generateBls12381G1KeyPair(seedBytes) + break + case KeyType.Bls12381g2: + // Generate a bls12-381G2 keypair + blsKeyPair = await generateBls12381G2KeyPair(seedBytes) + break + default: + // additional check. Should never be hit as this function will only be called from a place where + // a key type check already happened. + throw new WalletError(`Cannot create key with the BbsService for key type: ${keyType}`) + } + + return { + keyType, + publicKeyBase58: TypedArrayEncoder.toBase58(blsKeyPair.publicKey), + privateKeyBase58: TypedArrayEncoder.toBase58(blsKeyPair.secretKey), + } + } + + /** + * Sign an arbitrary amount of messages, in byte form, with a keypair + * + * @param messages Buffer[] List of messages in Buffer form + * @param publicKey Buffer Publickey required for the signing process + * @param privateKey Buffer PrivateKey required for the signing process + * + * @returns A Buffer containing the signature of the messages + * + * @throws {WalletError} When there are no supplied messages + */ + public static async sign({ messages, publicKey, privateKey }: BbsSignOptions): Promise { + if (messages.length === 0) throw new WalletError('Unable to create a signature without any messages') + // Check if it is a single message or list and if it is a single message convert it to a list + const normalizedMessages = (TypedArrayEncoder.isTypedArray(messages) ? [messages as Buffer] : messages) as Buffer[] + + // Get the Uint8Array variant of all the messages + const messageBuffers = normalizedMessages.map((m) => Uint8Array.from(m)) + + const bbsKeyPair = await bls12381toBbs({ + keyPair: { publicKey: Uint8Array.from(publicKey), secretKey: Uint8Array.from(privateKey) }, + messageCount: normalizedMessages.length, + }) + + // Sign the messages via the keyPair + const signature = await sign({ + keyPair: bbsKeyPair, + messages: messageBuffers, + }) + + // Convert the Uint8Array signature to a Buffer type + return Buffer.from(signature) + } + + /** + * Verify an arbitrary amount of messages with their signature created with their key pair + * + * @param publicKey Buffer The public key used to sign the messages + * @param messages Buffer[] The messages that have to be verified if they are signed + * @param signature Buffer The signature that has to be verified if it was created with the messages and public key + * + * @returns A boolean whether the signature is create with the public key over the messages + * + * @throws {WalletError} When the message list is empty + * @throws {WalletError} When the verification process failed + */ + public static async verify({ signature, messages, publicKey }: BbsVerifyOptions): Promise { + if (messages.length === 0) throw new WalletError('Unable to create a signature without any messages') + // Check if it is a single message or list and if it is a single message convert it to a list + const normalizedMessages = (TypedArrayEncoder.isTypedArray(messages) ? [messages as Buffer] : messages) as Buffer[] + + // Get the Uint8Array variant of all the messages + const messageBuffers = normalizedMessages.map((m) => Uint8Array.from(m)) + + const bbsKeyPair = await bls12381toBbs({ + keyPair: { publicKey: Uint8Array.from(publicKey) }, + messageCount: normalizedMessages.length, + }) + + // Verify the signature against the messages with their public key + const { verified, error } = await verify({ signature, messages: messageBuffers, publicKey: bbsKeyPair.publicKey }) + + // If the messages could not be verified and an error occured + if (!verified && error) { + throw new WalletError(`Could not verify the signature against the messages: ${error}`) + } + + return verified + } +} diff --git a/packages/core/src/crypto/JwsService.ts b/packages/core/src/crypto/JwsService.ts index 2a06e71cb5..8e631d4185 100644 --- a/packages/core/src/crypto/JwsService.ts +++ b/packages/core/src/crypto/JwsService.ts @@ -8,6 +8,9 @@ import { JsonEncoder, TypedArrayEncoder } from '../utils' import { Wallet } from '../wallet' import { WalletError } from '../wallet/error' +import { Key } from './Key' +import { KeyType } from './KeyType' + // TODO: support more key types, more generic jws format const JWS_KEY_TYPE = 'OKP' const JWS_CURVE = 'Ed25519' @@ -24,9 +27,10 @@ export class JwsService { public async createJws({ payload, verkey, header }: CreateJwsOptions): Promise { const base64Payload = TypedArrayEncoder.toBase64URL(payload) const base64Protected = JsonEncoder.toBase64URL(this.buildProtected(verkey)) + const key = Key.fromPublicKeyBase58(verkey, KeyType.Ed25519) const signature = TypedArrayEncoder.toBase64URL( - await this.wallet.sign(TypedArrayEncoder.fromString(`${base64Protected}.${base64Payload}`), verkey) + await this.wallet.sign({ data: TypedArrayEncoder.fromString(`${base64Protected}.${base64Payload}`), key }) ) return { @@ -37,7 +41,7 @@ export class JwsService { } /** - * Verify a a JWS + * Verify a JWS */ public async verifyJws({ jws, payload }: VerifyJwsOptions): Promise { const base64Payload = TypedArrayEncoder.toBase64URL(payload) @@ -63,10 +67,11 @@ export class JwsService { const signature = TypedArrayEncoder.fromBase64(jws.signature) const verkey = TypedArrayEncoder.toBase58(TypedArrayEncoder.fromBase64(protectedJson?.jwk?.x)) + const key = Key.fromPublicKeyBase58(verkey, KeyType.Ed25519) signerVerkeys.push(verkey) try { - const isValid = await this.wallet.verify(verkey, data, signature) + const isValid = await this.wallet.verify({ key, data, signature }) if (!isValid) { return { diff --git a/packages/core/src/modules/dids/domain/Key.ts b/packages/core/src/crypto/Key.ts similarity index 89% rename from packages/core/src/modules/dids/domain/Key.ts rename to packages/core/src/crypto/Key.ts index f9c9595966..c5d03507f0 100644 --- a/packages/core/src/modules/dids/domain/Key.ts +++ b/packages/core/src/crypto/Key.ts @@ -1,8 +1,8 @@ -import type { KeyType } from '../../../crypto' +import type { KeyType } from './KeyType' -import { Buffer, TypedArrayEncoder, MultiBaseEncoder, VarintEncoder } from '../../../utils' +import { Buffer, MultiBaseEncoder, TypedArrayEncoder, VarintEncoder } from '../utils' -import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeytype } from './key-type/multiCodecKey' +import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeytype } from './multiCodecKey' export class Key { public readonly publicKey: Buffer diff --git a/packages/core/src/crypto/__tests__/JwsService.test.ts b/packages/core/src/crypto/__tests__/JwsService.test.ts index c1f4be7721..87ced7bd95 100644 --- a/packages/core/src/crypto/__tests__/JwsService.test.ts +++ b/packages/core/src/crypto/__tests__/JwsService.test.ts @@ -1,10 +1,11 @@ import type { Wallet } from '@aries-framework/core' import { getAgentConfig } from '../../../tests/helpers' -import { DidKey, Key } from '../../modules/dids' +import { DidKey } from '../../modules/dids' import { Buffer, JsonEncoder } from '../../utils' import { IndyWallet } from '../../wallet/IndyWallet' import { JwsService } from '../JwsService' +import { Key } from '../Key' import { KeyType } from '../KeyType' import * as didJwsz6Mkf from './__fixtures__/didJwsz6Mkf' diff --git a/packages/core/src/crypto/index.ts b/packages/core/src/crypto/index.ts index 208a940d03..4c598a5a2a 100644 --- a/packages/core/src/crypto/index.ts +++ b/packages/core/src/crypto/index.ts @@ -1 +1,2 @@ export { KeyType } from './KeyType' +export { Key } from './Key' diff --git a/packages/core/src/modules/dids/domain/key-type/multiCodecKey.ts b/packages/core/src/crypto/multiCodecKey.ts similarity index 95% rename from packages/core/src/modules/dids/domain/key-type/multiCodecKey.ts rename to packages/core/src/crypto/multiCodecKey.ts index 884145f1da..20d3f4b070 100644 --- a/packages/core/src/modules/dids/domain/key-type/multiCodecKey.ts +++ b/packages/core/src/crypto/multiCodecKey.ts @@ -1,4 +1,4 @@ -import { KeyType } from '../../../../crypto' +import { KeyType } from './KeyType' // based on https://github.com/multiformats/multicodec/blob/master/table.csv const multiCodecPrefixMap: Record = { diff --git a/packages/core/src/decorators/signature/SignatureDecoratorUtils.ts b/packages/core/src/decorators/signature/SignatureDecoratorUtils.ts index 4518f67b33..dedbde2610 100644 --- a/packages/core/src/decorators/signature/SignatureDecoratorUtils.ts +++ b/packages/core/src/decorators/signature/SignatureDecoratorUtils.ts @@ -1,5 +1,6 @@ import type { Wallet } from '../../wallet/Wallet' +import { Key, KeyType } from '../../crypto' import { AriesFrameworkError } from '../../error' import { JsonEncoder } from '../../utils/JsonEncoder' import { TypedArrayEncoder } from '../../utils/TypedArrayEncoder' @@ -21,12 +22,14 @@ export async function unpackAndVerifySignatureDecorator( wallet: Wallet ): Promise> { const signerVerkey = decorator.signer + const key = Key.fromPublicKeyBase58(signerVerkey, KeyType.Ed25519) // first 8 bytes are for 64 bit integer from unix epoch const signedData = TypedArrayEncoder.fromBase64(decorator.signatureData) const signature = TypedArrayEncoder.fromBase64(decorator.signature) - const isValid = await wallet.verify(signerVerkey, signedData, signature) + // const isValid = await wallet.verify(signerVerkey, signedData, signature) + const isValid = await wallet.verify({ signature, data: signedData, key }) if (!isValid) { throw new AriesFrameworkError('Signature is not valid') @@ -47,8 +50,9 @@ export async function unpackAndVerifySignatureDecorator( */ export async function signData(data: unknown, wallet: Wallet, signerKey: string): Promise { const dataBuffer = Buffer.concat([timestamp(), JsonEncoder.toBuffer(data)]) + const key = Key.fromPublicKeyBase58(signerKey, KeyType.Ed25519) - const signatureBuffer = await wallet.sign(dataBuffer, signerKey) + const signatureBuffer = await wallet.sign({ key, data: dataBuffer }) const signatureDecorator = new SignatureDecorator({ signatureType: 'https://didcomm.org/signature/1.0/ed25519Sha512_single', diff --git a/packages/core/src/modules/connections/ConnectionsModule.ts b/packages/core/src/modules/connections/ConnectionsModule.ts index 697b4492de..1685c183c4 100644 --- a/packages/core/src/modules/connections/ConnectionsModule.ts +++ b/packages/core/src/modules/connections/ConnectionsModule.ts @@ -1,5 +1,5 @@ +import type { Key } from '../../crypto' import type { DependencyManager } from '../../plugins' -import type { Key } from '../dids' import type { OutOfBandRecord } from '../oob/repository' import type { ConnectionRecord } from './repository/ConnectionRecord' import type { Routing } from './services' @@ -18,14 +18,14 @@ import { RoutingService } from '../routing/services/RoutingService' import { DidExchangeProtocol } from './DidExchangeProtocol' import { + AckMessageHandler, ConnectionRequestHandler, ConnectionResponseHandler, - AckMessageHandler, - TrustPingMessageHandler, - TrustPingResponseMessageHandler, + DidExchangeCompleteHandler, DidExchangeRequestHandler, DidExchangeResponseHandler, - DidExchangeCompleteHandler, + TrustPingMessageHandler, + TrustPingResponseMessageHandler, } from './handlers' import { HandshakeProtocol } from './models' import { ConnectionRepository } from './repository' diff --git a/packages/core/src/modules/connections/DidExchangeProtocol.ts b/packages/core/src/modules/connections/DidExchangeProtocol.ts index e3f2ad741b..e5e8554a9f 100644 --- a/packages/core/src/modules/connections/DidExchangeProtocol.ts +++ b/packages/core/src/modules/connections/DidExchangeProtocol.ts @@ -8,14 +8,14 @@ import type { ConnectionRecord } from './repository' import type { Routing } from './services/ConnectionService' import { AgentConfig } from '../../agent/AgentConfig' -import { KeyType } from '../../crypto' +import { Key, KeyType } from '../../crypto' import { JwsService } from '../../crypto/JwsService' import { Attachment, AttachmentData } from '../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../error' import { injectable } from '../../plugins' import { JsonEncoder } from '../../utils/JsonEncoder' import { JsonTransformer } from '../../utils/JsonTransformer' -import { DidDocument, Key } from '../dids' +import { DidDocument } from '../dids' import { DidDocumentRole } from '../dids/domain/DidDocumentRole' import { createDidDocumentFromServices } from '../dids/domain/createPeerDidFromServices' import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type' diff --git a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index 97ef3fbd3d..b27ec3fed1 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -5,13 +5,13 @@ import { getAgentConfig, getMockConnection, getMockOutOfBand, mockFunction } fro import { AgentMessage } from '../../../agent/AgentMessage' import { EventEmitter } from '../../../agent/EventEmitter' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import { KeyType } from '../../../crypto' +import { Key, KeyType } from '../../../crypto' import { signData, unpackAndVerifySignatureDecorator } from '../../../decorators/signature/SignatureDecoratorUtils' import { JsonTransformer } from '../../../utils/JsonTransformer' import { uuid } from '../../../utils/uuid' import { IndyWallet } from '../../../wallet/IndyWallet' import { AckMessage, AckStatus } from '../../common' -import { DidKey, IndyAgentService, Key } from '../../dids' +import { DidKey, IndyAgentService } from '../../dids' import { DidCommV1Service } from '../../dids/domain/service/DidCommV1Service' import { didDocumentJsonToNumAlgo1Did } from '../../dids/methods/peer/peerDidNumAlgo1' import { DidRepository } from '../../dids/repository' diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index 570c314de9..07fa99743b 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -14,13 +14,14 @@ import { first, map, timeout } from 'rxjs/operators' import { AgentConfig } from '../../../agent/AgentConfig' import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' +import { Key } from '../../../crypto' import { signData, unpackAndVerifySignatureDecorator } from '../../../decorators/signature/SignatureDecoratorUtils' import { AriesFrameworkError } from '../../../error' import { inject, injectable } from '../../../plugins' import { JsonTransformer } from '../../../utils/JsonTransformer' import { indyDidFromPublicKeyBase58 } from '../../../utils/did' import { Wallet } from '../../../wallet/Wallet' -import { DidKey, Key, IndyAgentService } from '../../dids' +import { DidKey, IndyAgentService } from '../../dids' import { DidDocumentRole } from '../../dids/domain/DidDocumentRole' import { didKeyToVerkey } from '../../dids/helpers' import { didDocumentJsonToNumAlgo1Did } from '../../dids/methods/peer/peerDidNumAlgo1' diff --git a/packages/core/src/modules/connections/services/helpers.ts b/packages/core/src/modules/connections/services/helpers.ts index c54b3030fc..52879d798f 100644 --- a/packages/core/src/modules/connections/services/helpers.ts +++ b/packages/core/src/modules/connections/services/helpers.ts @@ -1,9 +1,9 @@ import type { DidDocument } from '../../dids' import type { DidDoc, PublicKey } from '../models' -import { KeyType } from '../../../crypto' +import { Key, KeyType } from '../../../crypto' import { AriesFrameworkError } from '../../../error' -import { IndyAgentService, DidCommV1Service, Key, DidDocumentBuilder } from '../../dids' +import { IndyAgentService, DidCommV1Service, DidDocumentBuilder } from '../../dids' import { getEd25519VerificationMethod } from '../../dids/domain/key-type/ed25519' import { didDocumentJsonToNumAlgo1Did } from '../../dids/methods/peer/peerDidNumAlgo1' import { EmbeddedAuthentication } from '../models' diff --git a/packages/core/src/modules/dids/DidsModule.ts b/packages/core/src/modules/dids/DidsModule.ts index 1fddb47453..7fe57d25d6 100644 --- a/packages/core/src/modules/dids/DidsModule.ts +++ b/packages/core/src/modules/dids/DidsModule.ts @@ -1,5 +1,5 @@ +import type { Key } from '../../crypto' import type { DependencyManager } from '../../plugins' -import type { Key } from './domain/Key' import type { DidResolutionOptions } from './types' import { injectable, module } from '../../plugins' diff --git a/packages/core/src/modules/dids/__tests__/peer-did.test.ts b/packages/core/src/modules/dids/__tests__/peer-did.test.ts index 5e5b64f872..c5205f1e60 100644 --- a/packages/core/src/modules/dids/__tests__/peer-did.test.ts +++ b/packages/core/src/modules/dids/__tests__/peer-did.test.ts @@ -2,11 +2,11 @@ import type { IndyLedgerService } from '../../ledger' import { getAgentConfig } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' -import { KeyType } from '../../../crypto' +import { Key, KeyType } from '../../../crypto' import { IndyStorageService } from '../../../storage/IndyStorageService' import { JsonTransformer } from '../../../utils' import { IndyWallet } from '../../../wallet/IndyWallet' -import { DidCommV1Service, DidDocument, DidDocumentBuilder, Key } from '../domain' +import { DidCommV1Service, DidDocument, DidDocumentBuilder } from '../domain' import { DidDocumentRole } from '../domain/DidDocumentRole' import { convertPublicKeyToX25519, getEd25519VerificationMethod } from '../domain/key-type/ed25519' import { getX25519VerificationMethod } from '../domain/key-type/x25519' diff --git a/packages/core/src/modules/dids/domain/DidDocument.ts b/packages/core/src/modules/dids/domain/DidDocument.ts index 28bcfb3f8b..4f3a066835 100644 --- a/packages/core/src/modules/dids/domain/DidDocument.ts +++ b/packages/core/src/modules/dids/domain/DidDocument.ts @@ -3,11 +3,10 @@ import type { DidDocumentService } from './service' import { Expose, Type } from 'class-transformer' import { IsArray, IsOptional, IsString, ValidateNested } from 'class-validator' -import { KeyType } from '../../../crypto' +import { KeyType, Key } from '../../../crypto' import { JsonTransformer } from '../../../utils/JsonTransformer' import { IsStringOrStringArray } from '../../../utils/transformers' -import { Key } from './Key' import { getKeyDidMappingByVerificationMethod } from './key-type' import { IndyAgentService, ServiceTransformer, DidCommV1Service } from './service' import { VerificationMethodTransformer, VerificationMethod, IsStringOrVerificationMethod } from './verificationMethod' diff --git a/packages/core/src/modules/dids/domain/createPeerDidFromServices.ts b/packages/core/src/modules/dids/domain/createPeerDidFromServices.ts index 3fe2375a35..6f4dfe6a00 100644 --- a/packages/core/src/modules/dids/domain/createPeerDidFromServices.ts +++ b/packages/core/src/modules/dids/domain/createPeerDidFromServices.ts @@ -2,13 +2,12 @@ import type { ResolvedDidCommService } from '../../../agent/MessageSender' import { convertPublicKeyToX25519 } from '@stablelib/ed25519' -import { KeyType } from '../../../crypto' +import { KeyType, Key } from '../../../crypto' import { AriesFrameworkError } from '../../../error' import { uuid } from '../../../utils/uuid' import { DidKey } from '../methods/key' import { DidDocumentBuilder } from './DidDocumentBuilder' -import { Key } from './Key' import { getEd25519VerificationMethod } from './key-type/ed25519' import { getX25519VerificationMethod } from './key-type/x25519' import { DidCommV1Service } from './service/DidCommV1Service' diff --git a/packages/core/src/modules/dids/domain/index.ts b/packages/core/src/modules/dids/domain/index.ts index 5e2bbcd60f..bf0ff1c854 100644 --- a/packages/core/src/modules/dids/domain/index.ts +++ b/packages/core/src/modules/dids/domain/index.ts @@ -2,4 +2,3 @@ export * from './service' export * from './verificationMethod' export * from './DidDocument' export * from './DidDocumentBuilder' -export * from './Key' diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts index ef18f6b92e..7fdbf067ab 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1.test.ts @@ -1,7 +1,7 @@ import { KeyType } from '../../../../../crypto' -import { JsonTransformer, TypedArrayEncoder, Buffer } from '../../../../../utils' +import { Key } from '../../../../../crypto/Key' +import { Buffer, JsonTransformer, TypedArrayEncoder } from '../../../../../utils' import keyBls12381g1Fixture from '../../../__tests__/__fixtures__/didKeyBls12381g1.json' -import { Key } from '../../Key' import { VerificationMethod } from '../../verificationMethod' import { keyDidBls12381g1 } from '../bls12381g1' diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts index c1a53d2217..442422f2cb 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g1g2.test.ts @@ -1,7 +1,7 @@ import { KeyType } from '../../../../../crypto' -import { JsonTransformer, TypedArrayEncoder, Buffer } from '../../../../../utils' +import { Key } from '../../../../../crypto/Key' +import { Buffer, JsonTransformer, TypedArrayEncoder } from '../../../../../utils' import keyBls12381g1g2Fixture from '../../../__tests__/__fixtures__/didKeyBls12381g1g2.json' -import { Key } from '../../Key' import { VerificationMethod } from '../../verificationMethod' import { keyDidBls12381g1g2 } from '../bls12381g1g2' diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts index a9f82d19a9..5b326f1f3b 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/bls12381g2.test.ts @@ -1,7 +1,7 @@ import { KeyType } from '../../../../../crypto' -import { JsonTransformer, TypedArrayEncoder, Buffer } from '../../../../../utils' +import { Key } from '../../../../../crypto/Key' +import { Buffer, JsonTransformer, TypedArrayEncoder } from '../../../../../utils' import keyBls12381g2Fixture from '../../../__tests__/__fixtures__/didKeyBls12381g2.json' -import { Key } from '../../Key' import { VerificationMethod } from '../../verificationMethod' import { keyDidBls12381g2 } from '../bls12381g2' diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts index c9c9911e11..cd93ada9cd 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts @@ -1,7 +1,7 @@ import { KeyType } from '../../../../../crypto' -import { JsonTransformer, TypedArrayEncoder, Buffer } from '../../../../../utils' +import { Key } from '../../../../../crypto/Key' +import { Buffer, JsonTransformer, TypedArrayEncoder } from '../../../../../utils' import didKeyEd25519Fixture from '../../../__tests__/__fixtures__//didKeyEd25519.json' -import { Key } from '../../../domain/Key' import { VerificationMethod } from '../../../domain/verificationMethod' import { keyDidEd25519 } from '../ed25519' diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts index 2908c0939b..9562434057 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/x25519.test.ts @@ -1,7 +1,7 @@ import { KeyType } from '../../../../../crypto' -import { JsonTransformer, TypedArrayEncoder, Buffer } from '../../../../../utils' +import { Key } from '../../../../../crypto/Key' +import { Buffer, JsonTransformer, TypedArrayEncoder } from '../../../../../utils' import didKeyX25519Fixture from '../../../__tests__/__fixtures__/didKeyX25519.json' -import { Key } from '../../Key' import { VerificationMethod } from '../../verificationMethod' import { keyDidX25519 } from '../x25519' diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts index 50d208d119..6ac241f5d9 100644 --- a/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts @@ -2,7 +2,7 @@ import type { VerificationMethod } from '../verificationMethod' import type { KeyDidMapping } from './keyDidMapping' import { KeyType } from '../../../../crypto' -import { Key } from '../Key' +import { Key } from '../../../../crypto/Key' const VERIFICATION_METHOD_TYPE_BLS12381G1_KEY_2020 = 'Bls12381G1Key2020' diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g1g2.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g1g2.ts index a84456e0a5..55b0d8c949 100644 --- a/packages/core/src/modules/dids/domain/key-type/bls12381g1g2.ts +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g1g2.ts @@ -1,7 +1,7 @@ import type { KeyDidMapping } from './keyDidMapping' import { KeyType } from '../../../../crypto' -import { Key } from '../Key' +import { Key } from '../../../../crypto/Key' import { getBls12381g1VerificationMethod } from './bls12381g1' import { getBls12381g2VerificationMethod } from './bls12381g2' diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts index 0c476e86bb..a17d20130a 100644 --- a/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts @@ -2,7 +2,7 @@ import type { VerificationMethod } from '../verificationMethod' import type { KeyDidMapping } from './keyDidMapping' import { KeyType } from '../../../../crypto' -import { Key } from '../Key' +import { Key } from '../../../../crypto/Key' const VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020 = 'Bls12381G2Key2020' diff --git a/packages/core/src/modules/dids/domain/key-type/ed25519.ts b/packages/core/src/modules/dids/domain/key-type/ed25519.ts index 6fe91cc67e..eb360c72fb 100644 --- a/packages/core/src/modules/dids/domain/key-type/ed25519.ts +++ b/packages/core/src/modules/dids/domain/key-type/ed25519.ts @@ -4,7 +4,7 @@ import type { KeyDidMapping } from './keyDidMapping' import { convertPublicKeyToX25519 } from '@stablelib/ed25519' import { KeyType } from '../../../../crypto' -import { Key } from '../Key' +import { Key } from '../../../../crypto/Key' const VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018 = 'Ed25519VerificationKey2018' diff --git a/packages/core/src/modules/dids/domain/key-type/index.ts b/packages/core/src/modules/dids/domain/key-type/index.ts index ce5cbb0a5d..8e0d752102 100644 --- a/packages/core/src/modules/dids/domain/key-type/index.ts +++ b/packages/core/src/modules/dids/domain/key-type/index.ts @@ -1,2 +1 @@ -export { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeytype } from './multiCodecKey' export { getKeyDidMappingByKeyType, getKeyDidMappingByVerificationMethod } from './keyDidMapping' diff --git a/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts b/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts index deafe72518..713817d1bb 100644 --- a/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts +++ b/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts @@ -1,4 +1,4 @@ -import type { Key } from '../Key' +import type { Key } from '../../../../crypto/Key' import type { VerificationMethod } from '../verificationMethod' import { KeyType } from '../../../../crypto' diff --git a/packages/core/src/modules/dids/domain/key-type/x25519.ts b/packages/core/src/modules/dids/domain/key-type/x25519.ts index 359e48b2a3..5ce7ff0683 100644 --- a/packages/core/src/modules/dids/domain/key-type/x25519.ts +++ b/packages/core/src/modules/dids/domain/key-type/x25519.ts @@ -2,7 +2,7 @@ import type { VerificationMethod } from '../verificationMethod' import type { KeyDidMapping } from './keyDidMapping' import { KeyType } from '../../../../crypto' -import { Key } from '../Key' +import { Key } from '../../../../crypto/Key' const VERIFICATION_METHOD_TYPE_X25519_KEY_AGREEMENT_KEY_2019 = 'X25519KeyAgreementKey2019' diff --git a/packages/core/src/modules/dids/domain/keyDidDocument.ts b/packages/core/src/modules/dids/domain/keyDidDocument.ts index bd628721ab..893436aeb3 100644 --- a/packages/core/src/modules/dids/domain/keyDidDocument.ts +++ b/packages/core/src/modules/dids/domain/keyDidDocument.ts @@ -1,9 +1,8 @@ import type { VerificationMethod } from './verificationMethod/VerificationMethod' -import { KeyType } from '../../../crypto' +import { KeyType, Key } from '../../../crypto' import { DidDocumentBuilder } from './DidDocumentBuilder' -import { Key } from './Key' import { getBls12381g1VerificationMethod } from './key-type/bls12381g1' import { getBls12381g1g2VerificationMethod } from './key-type/bls12381g1g2' import { getBls12381g2VerificationMethod } from './key-type/bls12381g2' diff --git a/packages/core/src/modules/dids/helpers.ts b/packages/core/src/modules/dids/helpers.ts index 2a8316a59f..ef3c68ab07 100644 --- a/packages/core/src/modules/dids/helpers.ts +++ b/packages/core/src/modules/dids/helpers.ts @@ -1,6 +1,5 @@ -import { KeyType } from '../../crypto' +import { KeyType, Key } from '../../crypto' -import { Key } from './domain/Key' import { DidKey } from './methods/key' export function didKeyToVerkey(key: string) { diff --git a/packages/core/src/modules/dids/methods/key/DidKey.ts b/packages/core/src/modules/dids/methods/key/DidKey.ts index e2e190120d..fb377d63c0 100644 --- a/packages/core/src/modules/dids/methods/key/DidKey.ts +++ b/packages/core/src/modules/dids/methods/key/DidKey.ts @@ -1,4 +1,4 @@ -import { Key } from '../../domain/Key' +import { Key } from '../../../../crypto/Key' import { getDidDocumentForKey } from '../../domain/keyDidDocument' import { parseDid } from '../../domain/parse' diff --git a/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts b/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts index 77127532e8..bacfb3f1a9 100644 --- a/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts +++ b/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts @@ -1,10 +1,10 @@ import { KeyType } from '../../../../../crypto' +import { Key } from '../../../../../crypto/Key' import didKeyBls12381g1 from '../../../__tests__/__fixtures__/didKeyBls12381g1.json' import didKeyBls12381g1g2 from '../../../__tests__/__fixtures__/didKeyBls12381g1g2.json' import didKeyBls12381g2 from '../../../__tests__/__fixtures__/didKeyBls12381g2.json' import didKeyEd25519 from '../../../__tests__/__fixtures__/didKeyEd25519.json' import didKeyX25519 from '../../../__tests__/__fixtures__/didKeyX25519.json' -import { Key } from '../../../domain/Key' import { DidKey } from '../DidKey' describe('DidKey', () => { diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo0.test.ts b/packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo0.test.ts index 7433903849..efc938ae2d 100644 --- a/packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo0.test.ts +++ b/packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo0.test.ts @@ -1,9 +1,9 @@ +import { Key } from '../../../../../crypto' import didKeyBls12381g1 from '../../../__tests__/__fixtures__/didKeyBls12381g1.json' import didKeyBls12381g1g2 from '../../../__tests__/__fixtures__/didKeyBls12381g1g2.json' import didKeyBls12381g2 from '../../../__tests__/__fixtures__/didKeyBls12381g2.json' import didKeyEd25519 from '../../../__tests__/__fixtures__/didKeyEd25519.json' import didKeyX25519 from '../../../__tests__/__fixtures__/didKeyX25519.json' -import { Key } from '../../../domain' import { didToNumAlgo0DidDocument, keyToNumAlgo0DidDocument } from '../peerDidNumAlgo0' describe('peerDidNumAlgo0', () => { diff --git a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo0.ts b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo0.ts index 9842b99f44..934156d5d8 100644 --- a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo0.ts +++ b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo0.ts @@ -1,4 +1,4 @@ -import { Key } from '../../domain/Key' +import { Key } from '../../../../crypto' import { getDidDocumentForKey } from '../../domain/keyDidDocument' import { parseDid } from '../../domain/parse' diff --git a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts index 880cfd9ce4..4b26cd5efa 100644 --- a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts +++ b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts @@ -2,8 +2,9 @@ import type { JsonObject } from '../../../../types' import type { OutOfBandDidCommService } from '../../../oob/domain/OutOfBandDidCommService' import type { DidDocument, VerificationMethod } from '../../domain' +import { Key } from '../../../../crypto' import { JsonEncoder, JsonTransformer } from '../../../../utils' -import { DidCommV1Service, DidDocumentService, Key } from '../../domain' +import { DidCommV1Service, DidDocumentService } from '../../domain' import { DidDocumentBuilder } from '../../domain/DidDocumentBuilder' import { getKeyDidMappingByKeyType, getKeyDidMappingByVerificationMethod } from '../../domain/key-type' import { parseDid } from '../../domain/parse' diff --git a/packages/core/src/modules/dids/repository/DidRepository.ts b/packages/core/src/modules/dids/repository/DidRepository.ts index b5edff754a..cb397cd1fe 100644 --- a/packages/core/src/modules/dids/repository/DidRepository.ts +++ b/packages/core/src/modules/dids/repository/DidRepository.ts @@ -1,4 +1,4 @@ -import type { Key } from '../domain/Key' +import type { Key } from '../../../crypto' import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' diff --git a/packages/core/src/modules/oob/OutOfBandModule.ts b/packages/core/src/modules/oob/OutOfBandModule.ts index 333af57332..18796d7876 100644 --- a/packages/core/src/modules/oob/OutOfBandModule.ts +++ b/packages/core/src/modules/oob/OutOfBandModule.ts @@ -1,11 +1,11 @@ import type { AgentMessage } from '../../agent/AgentMessage' import type { AgentMessageReceivedEvent } from '../../agent/Events' +import type { Key } from '../../crypto' import type { Attachment } from '../../decorators/attachment/Attachment' import type { Logger } from '../../logger' -import type { ConnectionRecord, Routing, ConnectionInvitationMessage } from '../../modules/connections' +import type { ConnectionInvitationMessage, ConnectionRecord, Routing } from '../../modules/connections' import type { DependencyManager } from '../../plugins' import type { PlaintextMessage } from '../../types' -import type { Key } from '../dids' import type { HandshakeReusedEvent } from './domain/OutOfBandEvents' import { catchError, EmptyError, first, firstValueFrom, map, of, timeout } from 'rxjs' @@ -18,7 +18,7 @@ import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' -import { DidExchangeState, HandshakeProtocol, ConnectionsModule } from '../../modules/connections' +import { ConnectionsModule, DidExchangeState, HandshakeProtocol } from '../../modules/connections' import { injectable, module } from '../../plugins' import { DidCommMessageRepository, DidCommMessageRole } from '../../storage' import { JsonEncoder, JsonTransformer } from '../../utils' diff --git a/packages/core/src/modules/oob/OutOfBandService.ts b/packages/core/src/modules/oob/OutOfBandService.ts index b84d332e0f..ce64b5513d 100644 --- a/packages/core/src/modules/oob/OutOfBandService.ts +++ b/packages/core/src/modules/oob/OutOfBandService.ts @@ -1,6 +1,6 @@ import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' +import type { Key } from '../../crypto' import type { ConnectionRecord } from '../connections' -import type { Key } from '../dids/domain/Key' import type { HandshakeReusedEvent, OutOfBandStateChangedEvent } from './domain/OutOfBandEvents' import type { OutOfBandRecord } from './repository' diff --git a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts index 9bfd317ddb..dd1c98098b 100644 --- a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts +++ b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts @@ -3,11 +3,10 @@ import type { Wallet } from '../../../wallet/Wallet' import { getAgentConfig, getMockConnection, getMockOutOfBand, mockFunction } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import { KeyType } from '../../../crypto' +import { KeyType, Key } from '../../../crypto' import { AriesFrameworkError } from '../../../error' import { IndyWallet } from '../../../wallet/IndyWallet' import { DidExchangeState } from '../../connections/models' -import { Key } from '../../dids' import { OutOfBandService } from '../OutOfBandService' import { OutOfBandEventTypes } from '../domain/OutOfBandEvents' import { OutOfBandRole } from '../domain/OutOfBandRole' diff --git a/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts b/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts index 5b6b776499..6e3f5d4018 100644 --- a/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts +++ b/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts @@ -1,6 +1,6 @@ +import type { Key } from '../../../crypto' import type { PlaintextMessage } from '../../../types' import type { HandshakeProtocol } from '../../connections' -import type { Key } from '../../dids' import { Expose, Transform, TransformationType, Type } from 'class-transformer' import { ArrayNotEmpty, IsArray, IsInstance, IsOptional, IsUrl, ValidateNested } from 'class-validator' diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index 5d5073689e..dc25dcb514 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -17,13 +17,11 @@ import { EventEmitter } from '../../../agent/EventEmitter' import { AgentEventTypes } from '../../../agent/Events' import { MessageSender } from '../../../agent/MessageSender' import { createOutboundMessage } from '../../../agent/helpers' -import { KeyType } from '../../../crypto' +import { Key, KeyType } from '../../../crypto' import { AriesFrameworkError } from '../../../error' import { injectable } from '../../../plugins' import { JsonTransformer } from '../../../utils' import { ConnectionService } from '../../connections/services/ConnectionService' -import { Key } from '../../dids' -import { didKeyToVerkey } from '../../dids/helpers' import { ProblemReportError } from '../../problem-reports' import { RoutingEventTypes } from '../RoutingEvents' import { RoutingProblemReportReason } from '../error' @@ -33,6 +31,7 @@ import { MediationRole, MediationState } from '../models' import { DeliveryRequestMessage, MessagesReceivedMessage, StatusRequestMessage } from '../protocol/pickup/v2/messages' import { MediationRecord } from '../repository/MediationRecord' import { MediationRepository } from '../repository/MediationRepository' +import { didKeyToVerkey } from '../../dids/helpers' @injectable() export class MediationRecipientService { diff --git a/packages/core/src/modules/routing/services/RoutingService.ts b/packages/core/src/modules/routing/services/RoutingService.ts index bde1779dde..134507b528 100644 --- a/packages/core/src/modules/routing/services/RoutingService.ts +++ b/packages/core/src/modules/routing/services/RoutingService.ts @@ -4,10 +4,9 @@ import type { RoutingCreatedEvent } from '../RoutingEvents' import { AgentConfig } from '../../../agent/AgentConfig' import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' -import { KeyType } from '../../../crypto' +import { Key, KeyType } from '../../../crypto' import { inject, injectable } from '../../../plugins' import { Wallet } from '../../../wallet' -import { Key } from '../../dids' import { RoutingEventTypes } from '../RoutingEvents' import { MediationRecipientService } from './MediationRecipientService' diff --git a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts index 4f1253a5c2..d266aabb20 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts @@ -6,6 +6,7 @@ import { EventEmitter } from '../../../../agent/EventEmitter' import { AgentEventTypes } from '../../../../agent/Events' import { MessageSender } from '../../../../agent/MessageSender' import { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import { Key } from '../../../../crypto' import { Attachment } from '../../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../../error' import { uuid } from '../../../../utils/uuid' @@ -13,7 +14,6 @@ import { IndyWallet } from '../../../../wallet/IndyWallet' import { DidExchangeState } from '../../../connections' import { ConnectionRepository } from '../../../connections/repository/ConnectionRepository' import { ConnectionService } from '../../../connections/services/ConnectionService' -import { Key } from '../../../dids' import { DidRepository } from '../../../dids/repository/DidRepository' import { MediationGrantMessage } from '../../messages' import { MediationRole, MediationState } from '../../models' diff --git a/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts b/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts index 9f343f575e..4a674a7f6d 100644 --- a/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts @@ -1,7 +1,7 @@ import { getAgentConfig, mockFunction } from '../../../../../tests/helpers' import { EventEmitter } from '../../../../agent/EventEmitter' +import { Key } from '../../../../crypto' import { IndyWallet } from '../../../../wallet/IndyWallet' -import { Key } from '../../../dids' import { RoutingEventTypes } from '../../RoutingEvents' import { MediationRecipientService } from '../MediationRecipientService' import { RoutingService } from '../RoutingService' diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 482a446aca..5c2f404260 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,9 +1,9 @@ import type { AgentMessage } from './agent/AgentMessage' import type { ResolvedDidCommService } from './agent/MessageSender' +import type { Key } from './crypto' import type { Logger } from './logger' import type { ConnectionRecord } from './modules/connections' import type { AutoAcceptCredential } from './modules/credentials/models/CredentialAutoAcceptType' -import type { Key } from './modules/dids/domain/Key' import type { IndyPoolConfig } from './modules/ledger/IndyPool' import type { OutOfBandRecord } from './modules/oob/repository' import type { AutoAcceptProof } from './modules/proofs' diff --git a/packages/core/src/utils/TypedArrayEncoder.ts b/packages/core/src/utils/TypedArrayEncoder.ts index 69cee41d27..685eac485c 100644 --- a/packages/core/src/utils/TypedArrayEncoder.ts +++ b/packages/core/src/utils/TypedArrayEncoder.ts @@ -60,4 +60,18 @@ export class TypedArrayEncoder { public static toUtf8String(buffer: Buffer | Uint8Array) { return Buffer.from(buffer).toString() } + + /** + * Check whether an array is byte, or typed, array + * + * @param array unknown The array that has to be checked + * + * @returns A boolean if the array is a byte array + */ + public static isTypedArray(array: unknown): boolean { + // Checks whether the static property 'BYTES_PER_ELEMENT' exists on the provided array. + // This has to be done, since the TypedArrays, e.g. Uint8Array and Float32Array, do not + // extend a single base class + return 'BYTES_PER_ELEMENT' in (array as Record) + } } diff --git a/packages/core/src/utils/__tests__/TypedArrayEncoder.test.ts b/packages/core/src/utils/__tests__/TypedArrayEncoder.test.ts index 6d2d42d7bd..925bf97f82 100644 --- a/packages/core/src/utils/__tests__/TypedArrayEncoder.test.ts +++ b/packages/core/src/utils/__tests__/TypedArrayEncoder.test.ts @@ -26,6 +26,23 @@ describe('TypedArrayEncoder', () => { }) ) + describe('isTypedArray', () => { + test('is array of type typedArray', () => { + const mockArray = [0, 1, 2] + expect(TypedArrayEncoder.isTypedArray(mockArray)).toStrictEqual(false) + }) + + test('is Uint8Array of type typedArray', () => { + const mockArray = new Uint8Array([0, 1, 2]) + expect(TypedArrayEncoder.isTypedArray(mockArray)).toStrictEqual(true) + }) + + test('is Buffer of type typedArray', () => { + const mockArray = new Buffer([0, 1, 2]) + expect(TypedArrayEncoder.isTypedArray(mockArray)).toStrictEqual(true) + }) + }) + describe('toBase64', () => { test('encodes buffer to Base64 string', () => { expect(TypedArrayEncoder.toBase64(mockCredentialRequestBuffer)).toEqual( diff --git a/packages/core/src/wallet/IndyWallet.test.ts b/packages/core/src/wallet/IndyWallet.test.ts new file mode 100644 index 0000000000..a1147a6260 --- /dev/null +++ b/packages/core/src/wallet/IndyWallet.test.ts @@ -0,0 +1,142 @@ +import { BBS_SIGNATURE_LENGTH } from '@mattrglobal/bbs-signatures' +import { SIGNATURE_LENGTH as ED25519_SIGNATURE_LENGTH } from '@stablelib/ed25519' + +import { getBaseConfig } from '../../tests/helpers' +import { Agent } from '../agent/Agent' +import { KeyType } from '../crypto' +import { TypedArrayEncoder } from '../utils' + +import { IndyWallet } from './IndyWallet' +import { WalletError } from './error' + +describe('IndyWallet', () => { + let indyWallet: IndyWallet + let agent: Agent + const seed = 'sample-seed' + const message = TypedArrayEncoder.fromString('sample-message') + + beforeEach(async () => { + const { config, agentDependencies } = getBaseConfig('IndyWallettest') + agent = new Agent(config, agentDependencies) + indyWallet = agent.injectionContainer.resolve(IndyWallet) + await agent.initialize() + }) + + afterEach(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + test('Get the public DID', () => { + expect(indyWallet.publicDid).toMatchObject({ + did: expect.any(String), + verkey: expect.any(String), + }) + }) + + test('Get the Master Secret', () => { + expect(indyWallet.masterSecretId).toEqual('Wallet: IndyWallettest') + }) + + test('Get the wallet handle', () => { + expect(indyWallet.handle).toEqual(expect.any(Number)) + }) + + test('Initializes a public did', async () => { + await indyWallet.initPublicDid({ seed: '00000000000000000000000Forward01' }) + + expect(indyWallet.publicDid).toEqual({ + did: 'DtWRdd6C5dN5vpcN6XRAvu', + verkey: '82RBSn3heLgXzZd74UsMC8Q8YRfEEhQoAM7LUqE6bevJ', + }) + }) + + test('Create DID', async () => { + const didInfo = await indyWallet.createDid({ seed: '00000000000000000000000Forward01' }) + expect(didInfo).toMatchObject({ + did: 'DtWRdd6C5dN5vpcN6XRAvu', + verkey: '82RBSn3heLgXzZd74UsMC8Q8YRfEEhQoAM7LUqE6bevJ', + }) + }) + + test('Generate Nonce', async () => { + await expect(indyWallet.generateNonce()).resolves.toEqual(expect.any(String)) + }) + + test('Create ed25519 keypair', async () => { + await expect( + indyWallet.createKey({ seed: '2103de41b4ae37e8e28586d84a342b67', keyType: KeyType.Ed25519 }) + ).resolves.toMatchObject({ + keyType: KeyType.Ed25519, + }) + }) + + test('Create blsg12381g1 keypair', async () => { + await expect(indyWallet.createKey({ seed, keyType: KeyType.Bls12381g1 })).resolves.toMatchObject({ + publicKeyBase58: '6RhvX1RK5rA9uXdTtV6WvHWNQqcCW86BQxz1aBPr6ebBcppCYMD3LLy7QLg4cGcWaq', + keyType: KeyType.Bls12381g1, + }) + }) + + test('Create bls12381g2 keypair', async () => { + await expect(indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 })).resolves.toMatchObject({ + publicKeyBase58: + 't54oLBmhhRcDLUyWTvfYRWw8VRXRy1p43pVm62hrpShrYPuHe9WNAgS33DPfeTK6xK7iPrtJDwCHZjYgbFYDVTJHxXex9xt2XEGF8D356jBT1HtqNeucv3YsPLfTWcLcpFA', + keyType: KeyType.Bls12381g2, + }) + }) + + test('Fail to create bls12381g1g2 keypair', async () => { + await expect(indyWallet.createKey({ seed, keyType: KeyType.Bls12381g1g2 })).rejects.toThrowError(WalletError) + }) + + test('Fail to create x25519 keypair', async () => { + await expect(indyWallet.createKey({ seed, keyType: KeyType.X25519 })).rejects.toThrowError(WalletError) + }) + + test('Create a signature with a ed25519 keypair', async () => { + const ed25519Key = await indyWallet.createKey({ keyType: KeyType.Ed25519 }) + const signature = await indyWallet.sign({ + data: message, + key: ed25519Key, + }) + expect(signature.length).toStrictEqual(ED25519_SIGNATURE_LENGTH) + }) + + test('Create a signature with a bls12381g2 keypair', async () => { + const bls12381g2Key = await indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) + const signature = await indyWallet.sign({ + data: message, + key: bls12381g2Key, + }) + expect(signature.length).toStrictEqual(BBS_SIGNATURE_LENGTH) + }) + + test('Fail to create a signature with a bls12381g1 keypair', async () => { + const bls12381g1Key = await indyWallet.createKey({ seed, keyType: KeyType.Bls12381g1 }) + await expect( + indyWallet.sign({ + data: message, + key: bls12381g1Key, + }) + ).rejects.toThrowError(WalletError) + }) + + test('Verify a signed message with a ed25519 publicKey', async () => { + const ed25519Key = await indyWallet.createKey({ keyType: KeyType.Ed25519 }) + const signature = await indyWallet.sign({ + data: message, + key: ed25519Key, + }) + await expect(indyWallet.verify({ key: ed25519Key, data: message, signature })).resolves.toStrictEqual(true) + }) + + test('Verify a signed message with a bls12381g2 publicKey', async () => { + const bls12381g2Key = await indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) + const signature = await indyWallet.sign({ + data: message, + key: bls12381g2Key, + }) + await expect(indyWallet.verify({ key: bls12381g2Key, data: message, signature })).resolves.toStrictEqual(true) + }) +}) diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts index 5411041ae2..117b4d3e5d 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/core/src/wallet/IndyWallet.ts @@ -1,22 +1,34 @@ +import type { BlsKeyPair } from '../crypto/BbsService' import type { Logger } from '../logger' import type { EncryptedMessage, + KeyDerivationMethod, WalletConfig, - WalletExportImportConfig, WalletConfigRekey, - KeyDerivationMethod, + WalletExportImportConfig, } from '../types' import type { Buffer } from '../utils/buffer' -import type { Wallet, DidInfo, DidConfig, UnpackedMessageContext } from './Wallet' +import type { + CreateKeyOptions, + DidConfig, + DidInfo, + SignOptions, + UnpackedMessageContext, + VerifyOptions, + Wallet, +} from './Wallet' import type { default as Indy, WalletStorageConfig } from 'indy-sdk' import { AgentConfig } from '../agent/AgentConfig' -import { AriesFrameworkError } from '../error' +import { KeyType } from '../crypto' +import { BbsService } from '../crypto/BbsService' +import { Key } from '../crypto/Key' +import { AriesFrameworkError, IndySdkError, RecordDuplicateError, RecordNotFoundError } from '../error' import { injectable } from '../plugins' -import { JsonEncoder } from '../utils/JsonEncoder' +import { JsonEncoder, TypedArrayEncoder } from '../utils' import { isIndyError } from '../utils/indyError' -import { WalletDuplicateError, WalletNotFoundError, WalletError } from './error' +import { WalletDuplicateError, WalletError, WalletNotFoundError } from './error' import { WalletInvalidKeyError } from './error/WalletInvalidKeyError' @injectable() @@ -399,6 +411,110 @@ export class IndyWallet implements Wallet { } } + /** + * Create a key with an optional seed and keyType. + * The keypair is also automatically stored in the wallet afterwards + * + * Bls12381g1g2 and X25519 are not supported. + * + * @param seed string The seed for creating a key + * @param keyType KeyType the type of key that should be created + * + * @returns a Key instance with a publicKeyBase58 + * + * @throws {WalletError} When an unsupported keytype is requested + * @throws {WalletError} When the key could not be created + */ + public async createKey({ seed, keyType }: CreateKeyOptions): Promise { + try { + if (keyType === KeyType.Ed25519) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + const verkey = await this.indy.createKey(this.handle, { seed, crypto_type: 'ed25519' }) + return Key.fromPublicKeyBase58(verkey, keyType) + } + + if (keyType === KeyType.Bls12381g1 || keyType === KeyType.Bls12381g2) { + const blsKeyPair = await BbsService.createKey({ keyType, seed }) + await this.storeKeyPair(blsKeyPair) + return Key.fromPublicKeyBase58(blsKeyPair.publicKeyBase58, keyType) + } + } catch (error) { + throw new WalletError(`Error creating key with key type '${keyType}': ${error.message}`, { cause: error }) + } + + throw new WalletError(`Unsupported key type: '${keyType}' for wallet IndyWallet`) + } + + /** + * sign a Buffer with an instance of a Key class + * + * Bls12381g1g2, Bls12381g1 and X25519 are not supported. + * + * @param data Buffer The data that needs to be signed + * @param key Key The key that is used to sign the data + * + * @returns A signature for the data + */ + public async sign({ data, key }: SignOptions): Promise { + try { + if (key.keyType === KeyType.Ed25519) { + // Checks to see if it is an not an Array of messages, but just a single one + if (!TypedArrayEncoder.isTypedArray(data)) { + throw new WalletError(`${KeyType.Ed25519} does not support multiple singing of multiple messages`) + } + return await this.indy.cryptoSign(this.handle, key.publicKeyBase58, data as Buffer) + } + + if (key.keyType === KeyType.Bls12381g2) { + const blsKeyPair = await this.retrieveKeyPair(key.publicKeyBase58) + return BbsService.sign({ + messages: data, + publicKey: key.publicKey, + privateKey: TypedArrayEncoder.fromBase58(blsKeyPair.privateKeyBase58), + }) + } + } catch (error) { + throw new WalletError(`Error signing data with verkey ${key.publicKeyBase58}`, { cause: error }) + } + throw new WalletError(`Unsupported keyType: ${key.keyType}`) + } + + /** + * Verify the signature with the data and the used key + * + * Bls12381g1g2, Bls12381g1 and X25519 are not supported. + * + * @param data Buffer The data that has to be confirmed to be signed + * @param key Key The key that was used in the signing process + * @param signature Buffer The signature that was created by the signing process + * + * @returns A boolean whether the signature was created with the supplied data and key + * + * @throws {WalletError} When it could not do the verification + * @throws {WalletError} When an unsupported keytype is used + */ + public async verify({ data, key, signature }: VerifyOptions): Promise { + try { + if (key.keyType === KeyType.Ed25519) { + // Checks to see if it is an not an Array of messages, but just a single one + if (!TypedArrayEncoder.isTypedArray(data)) { + throw new WalletError(`${KeyType.Ed25519} does not support multiple singing of multiple messages`) + } + return await this.indy.cryptoVerify(key.publicKeyBase58, data as Buffer, signature) + } + + if (key.keyType === KeyType.Bls12381g2) { + return await BbsService.verify({ signature, publicKey: key.publicKey, messages: data }) + } + } catch (error) { + throw new WalletError(`Error verifying signature of data signed with verkey ${key.publicKeyBase58}`, { + cause: error, + }) + } + throw new WalletError(`Unsupported keyType: ${key.keyType}`) + } + public async pack( payload: Record, recipientKeys: string[], @@ -427,30 +543,47 @@ export class IndyWallet implements Wallet { } } - public async sign(data: Buffer, verkey: string): Promise { + public async generateNonce(): Promise { try { - return await this.indy.cryptoSign(this.handle, verkey, data) + return await this.indy.generateNonce() } catch (error) { - throw new WalletError(`Error signing data with verkey ${verkey}`, { cause: error }) + throw new WalletError('Error generating nonce', { cause: error }) } } - public async verify(signerVerkey: string, data: Buffer, signature: Buffer): Promise { + private async retrieveKeyPair(publicKeyBase58: string): Promise { try { - // check signature - const isValid = await this.indy.cryptoVerify(signerVerkey, data, signature) - - return isValid + const { value } = await this.indy.getWalletRecord(this.handle, 'KeyPairRecord', `keypair-${publicKeyBase58}`, {}) + if (value) { + return JsonEncoder.fromString(value) as BlsKeyPair + } else { + throw new WalletError(`No content found for record with public key: ${publicKeyBase58}`) + } } catch (error) { - throw new WalletError(`Error verifying signature of data signed with verkey ${signerVerkey}`, { cause: error }) + if (isIndyError(error, 'WalletItemNotFound')) { + throw new RecordNotFoundError(`KeyPairRecord not found for public key: ${publicKeyBase58}.`, { + recordType: 'KeyPairRecord', + cause: error, + }) + } + throw isIndyError(error) ? new IndySdkError(error) : error } } - public async generateNonce() { + private async storeKeyPair(blsKeyPair: BlsKeyPair): Promise { try { - return await this.indy.generateNonce() + await this.indy.addWalletRecord( + this.handle, + 'KeyPairRecord', + `keypair-${blsKeyPair.publicKeyBase58}`, + JSON.stringify(blsKeyPair), + {} + ) } catch (error) { - throw new WalletError('Error generating nonce', { cause: error }) + if (isIndyError(error, 'WalletItemAlreadyExists')) { + throw new RecordDuplicateError(`Record already exists`, { recordType: 'KeyPairRecord' }) + } + throw isIndyError(error) ? new IndySdkError(error) : error } } } diff --git a/packages/core/src/wallet/Wallet.test.ts b/packages/core/src/wallet/Wallet.test.ts deleted file mode 100644 index 6d6a85da7d..0000000000 --- a/packages/core/src/wallet/Wallet.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { getAgentConfig } from '../../tests/helpers' - -import { IndyWallet } from './IndyWallet' - -describe('Wallet', () => { - const config = getAgentConfig('WalletTest') - const wallet = new IndyWallet(config) - - test('initialize public did', async () => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(config.walletConfig!) - - await wallet.initPublicDid({ seed: '00000000000000000000000Forward01' }) - - expect(wallet.publicDid).toEqual({ - did: 'DtWRdd6C5dN5vpcN6XRAvu', - verkey: '82RBSn3heLgXzZd74UsMC8Q8YRfEEhQoAM7LUqE6bevJ', - }) - }) - - afterEach(async () => { - await wallet.delete() - }) -}) diff --git a/packages/core/src/wallet/Wallet.ts b/packages/core/src/wallet/Wallet.ts index 649cc90a25..06a8899c4c 100644 --- a/packages/core/src/wallet/Wallet.ts +++ b/packages/core/src/wallet/Wallet.ts @@ -1,9 +1,10 @@ +import type { Key, KeyType } from '../crypto' import type { EncryptedMessage, WalletConfig, - WalletExportImportConfig, WalletConfigRekey, PlaintextMessage, + WalletExportImportConfig, } from '../types' import type { Buffer } from '../utils/buffer' @@ -21,12 +22,14 @@ export interface Wallet { export(exportConfig: WalletExportImportConfig): Promise import(walletConfig: WalletConfig, importConfig: WalletExportImportConfig): Promise + createKey(options: CreateKeyOptions): Promise + sign(options: SignOptions): Promise + verify(options: VerifyOptions): Promise + initPublicDid(didConfig: DidConfig): Promise createDid(didConfig?: DidConfig): Promise pack(payload: Record, recipientKeys: string[], senderVerkey?: string): Promise unpack(encryptedMessage: EncryptedMessage): Promise - sign(data: Buffer, verkey: string): Promise - verify(signerVerkey: string, data: Buffer, signature: Buffer): Promise generateNonce(): Promise } @@ -35,6 +38,22 @@ export interface DidInfo { verkey: string } +export interface CreateKeyOptions { + keyType: KeyType + seed?: string +} + +export interface SignOptions { + data: Buffer | Buffer[] + key: Key +} + +export interface VerifyOptions { + data: Buffer | Buffer[] + key: Key + signature: Buffer +} + export interface DidConfig { seed?: string } diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index d021344fb8..6dad0458c1 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -15,40 +15,40 @@ import type { } from '../src' import type { AcceptOfferOptions } from '../src/modules/credentials' import type { IndyOfferCredentialFormat } from '../src/modules/credentials/formats/indy/IndyCredentialFormat' -import type { Schema, CredDef } from 'indy-sdk' +import type { CredDef, Schema } from 'indy-sdk' import type { Observable } from 'rxjs' import path from 'path' -import { firstValueFrom, Subject, ReplaySubject } from 'rxjs' +import { firstValueFrom, ReplaySubject, Subject } from 'rxjs' import { catchError, filter, map, timeout } from 'rxjs/operators' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' import { agentDependencies, WalletScheme } from '../../node/src' import { - PresentationPreview, - PresentationPreviewAttribute, - PresentationPreviewPredicate, - HandshakeProtocol, - DidExchangeState, - DidExchangeRole, - LogLevel, + Agent, AgentConfig, AriesFrameworkError, BasicMessageEventTypes, ConnectionRecord, CredentialEventTypes, CredentialState, + DidExchangeRole, + DidExchangeState, + HandshakeProtocol, + LogLevel, PredicateType, + PresentationPreview, + PresentationPreviewAttribute, + PresentationPreviewPredicate, ProofEventTypes, ProofState, - Agent, } from '../src' -import { KeyType } from '../src/crypto' +import { Key, KeyType } from '../src/crypto' import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment' import { AutoAcceptCredential } from '../src/modules/credentials/models/CredentialAutoAcceptType' import { V1CredentialPreview } from '../src/modules/credentials/protocol/v1/messages/V1CredentialPreview' -import { DidCommV1Service, DidKey, Key } from '../src/modules/dids' +import { DidCommV1Service, DidKey } from '../src/modules/dids' import { OutOfBandRole } from '../src/modules/oob/domain/OutOfBandRole' import { OutOfBandState } from '../src/modules/oob/domain/OutOfBandState' import { OutOfBandInvitation } from '../src/modules/oob/messages' diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index b256a4849e..bde3e6d1c3 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -9,8 +9,8 @@ import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' import { Agent } from '../src/agent/Agent' +import { Key } from '../src/crypto' import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' -import { Key } from '../src/modules/dids' import { OutOfBandDidCommService } from '../src/modules/oob/domain/OutOfBandDidCommService' import { OutOfBandEventTypes } from '../src/modules/oob/domain/OutOfBandEvents' import { OutOfBandRole } from '../src/modules/oob/domain/OutOfBandRole' diff --git a/packages/react-native/package.json b/packages/react-native/package.json index f94520af4d..cd523a1f87 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -29,6 +29,7 @@ "events": "^3.3.0" }, "devDependencies": { + "@animo-id/react-native-bbs-signatures": "^0.1.0", "@types/indy-sdk-react-native": "npm:@types/indy-sdk@^1.16.19", "@types/react-native": "^0.64.10", "indy-sdk-react-native": "^0.2.2", diff --git a/yarn.lock b/yarn.lock index cb241ec15a..275981217c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,6 +10,11 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" +"@animo-id/react-native-bbs-signatures@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@animo-id/react-native-bbs-signatures/-/react-native-bbs-signatures-0.1.0.tgz#f62bc16b867c9f690977982d66f0a03566b21ad2" + integrity sha512-7qvsiWhGfUev8ngE8YzF6ON9PtCID5LiYVYM4EC5eyj80gCdhx3R46CI7K1qbqIlGsoTYQ/Xx5Ubo5Ji9eaUEA== + "@azure/core-asynciterator-polyfill@^1.0.0": version "1.0.2" resolved "https://registry.yarnpkg.com/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.2.tgz#0dd3849fb8d97f062a39db0e5cadc9ffaf861fec" @@ -1710,6 +1715,23 @@ npmlog "^4.1.2" write-file-atomic "^3.0.3" +"@mattrglobal/bbs-signatures@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@mattrglobal/bbs-signatures/-/bbs-signatures-1.0.0.tgz#8ff272c6d201aadab7e08bd84dbfd6e0d48ba12d" + integrity sha512-FFzybdKqSCrS/e7pl5s6Tl/m/x8ZD5EMBbcTBQaqSOms/lebm91lFukYOIe2qc0a5o+gLhtRKye8OfKwD1Ex/g== + dependencies: + "@stablelib/random" "1.0.0" + optionalDependencies: + "@mattrglobal/node-bbs-signatures" "0.13.0" + +"@mattrglobal/node-bbs-signatures@0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@mattrglobal/node-bbs-signatures/-/node-bbs-signatures-0.13.0.tgz#3e431b915325d4b139706f8b26fd84b27c192a29" + integrity sha512-S2wOwDCQYxdjSEjVfcbP3bTq4ZMKeRw/wvBhWRff8CEwuH5u3Qiul+azwDGSesvve1DDceaEhXWiGkXeZTojfQ== + dependencies: + neon-cli "0.8.2" + node-pre-gyp "0.17.0" + "@multiformats/base-x@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@multiformats/base-x/-/base-x-4.0.1.tgz#95ff0fa58711789d53aefb2590a8b7a4e715d121" @@ -2082,7 +2104,7 @@ resolved "https://registry.yarnpkg.com/@sovpro/delimited-stream/-/delimited-stream-1.1.0.tgz#4334bba7ee241036e580fdd99c019377630d26b4" integrity sha512-kQpk267uxB19X3X2T1mvNMjyvIEonpNSHrMlK5ZaBU6aZxw7wPbpgKJOjHN3+/GPVpXgAV9soVT2oyHpLkLtyw== -"@stablelib/binary@^1.0.1": +"@stablelib/binary@^1.0.0", "@stablelib/binary@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@stablelib/binary/-/binary-1.0.1.tgz#c5900b94368baf00f811da5bdb1610963dfddf7f" integrity sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q== @@ -2108,6 +2130,14 @@ resolved "https://registry.yarnpkg.com/@stablelib/int/-/int-1.0.1.tgz#75928cc25d59d73d75ae361f02128588c15fd008" integrity sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w== +"@stablelib/random@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@stablelib/random/-/random-1.0.0.tgz#f441495075cdeaa45de16d7ddcc269c0b8edb16b" + integrity sha512-G9vwwKrNCGMI/uHL6XeWe2Nk4BuxkYyWZagGaDU9wrsuV+9hUwNI1lok2WVo8uJDa2zx7ahNwN7Ij983hOUFEw== + dependencies: + "@stablelib/binary" "^1.0.0" + "@stablelib/wipe" "^1.0.0" + "@stablelib/random@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@stablelib/random/-/random-1.0.1.tgz#4357a00cb1249d484a9a71e6054bc7b8324a7009" @@ -2134,7 +2164,7 @@ "@stablelib/hash" "^1.0.1" "@stablelib/wipe" "^1.0.1" -"@stablelib/wipe@^1.0.1": +"@stablelib/wipe@^1.0.0", "@stablelib/wipe@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@stablelib/wipe/-/wipe-1.0.1.tgz#d21401f1d59ade56a62e139462a97f104ed19a36" integrity sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg== @@ -2824,6 +2854,16 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= +array-back@^3.0.1, array-back@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" + integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== + +array-back@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" + integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== + array-differ@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" @@ -3658,6 +3698,33 @@ command-exists@^1.2.8: resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== +command-line-args@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" + integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg== + dependencies: + array-back "^3.1.0" + find-replace "^3.0.0" + lodash.camelcase "^4.3.0" + typical "^4.0.0" + +command-line-commands@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/command-line-commands/-/command-line-commands-3.0.2.tgz#53872a1181db837f21906b1228e260a4eeb42ee4" + integrity sha512-ac6PdCtdR6q7S3HN+JiVLIWGHY30PRYIEl2qPo+FuEuzwAUk0UYyimrngrg7FvF/mCr4Jgoqv5ZnHZgads50rw== + dependencies: + array-back "^4.0.1" + +command-line-usage@^6.1.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.1.tgz#c908e28686108917758a49f45efb4f02f76bc03f" + integrity sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA== + dependencies: + array-back "^4.0.1" + chalk "^2.4.2" + table-layout "^1.0.1" + typical "^5.2.0" + commander@^2.15.0, commander@^2.19.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -4020,7 +4087,7 @@ debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, d dependencies: ms "2.1.2" -debug@^3.1.0, debug@^3.2.7: +debug@^3.1.0, debug@^3.2.6, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -4060,6 +4127,11 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +deep-extend@^0.6.0, deep-extend@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -4157,6 +4229,11 @@ detect-indent@^6.0.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -4908,6 +4985,13 @@ find-cache-dir@^2.0.0: make-dir "^2.0.0" pkg-dir "^3.0.0" +find-replace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" + integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== + dependencies: + array-back "^3.0.1" + find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" @@ -5195,6 +5279,13 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +git-config@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/git-config/-/git-config-0.0.7.tgz#a9c8a3ef07a776c3d72261356d8b727b62202b28" + integrity sha1-qcij7wendsPXImE1bYtye2IgKyg= + dependencies: + iniparser "~1.0.5" + git-raw-commits@^2.0.8: version "2.0.11" resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.11.tgz#bc3576638071d18655e1cc60d7f524920008d723" @@ -5292,7 +5383,7 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -handlebars@^4.7.7: +handlebars@^4.7.6, handlebars@^4.7.7: version "4.7.7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== @@ -5494,7 +5585,7 @@ husky@^7.0.1: resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== -iconv-lite@0.4.24, iconv-lite@^0.4.24: +iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -5513,7 +5604,7 @@ ieee754@^1.1.13, ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore-walk@^3.0.3: +ignore-walk@^3.0.1, ignore-walk@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== @@ -5603,11 +5694,16 @@ inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.2, ini@^1.3.4: +ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +iniparser@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/iniparser/-/iniparser-1.0.5.tgz#836d6befe6dfbfcee0bccf1cf9f2acc7027f783d" + integrity sha1-g21r7+bfv87gvM8c+fKsxwJ/eD0= + init-package-json@^2.0.2: version "2.0.5" resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-2.0.5.tgz#78b85f3c36014db42d8f32117252504f68022646" @@ -6880,6 +6976,11 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -7033,6 +7134,11 @@ make-fetch-happen@^9.0.1, make-fetch-happen@^9.1.0: socks-proxy-agent "^6.0.0" ssri "^8.0.0" +make-promises-safe@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/make-promises-safe/-/make-promises-safe-5.1.0.tgz#dd9d311f555bcaa144f12e225b3d37785f0aa8f2" + integrity sha512-AfdZ49rtyhQR/6cqVKGoH7y4ql7XkS5HJI1lZm0/5N6CQosy1eYbBJ/qbhkKHzo17UH7M918Bysf6XB9f3kS1g== + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -7620,6 +7726,15 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +needle@^2.5.2: + version "2.9.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.9.1.tgz#22d1dffbe3490c2b83e301f7709b6736cd8f2684" + integrity sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + negotiator@0.6.3, negotiator@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" @@ -7630,6 +7745,26 @@ neo-async@^2.5.0, neo-async@^2.6.0: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +neon-cli@0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/neon-cli/-/neon-cli-0.8.2.tgz#5111b0e9d5d90273bdf85a9aa40a1a47a32df2ef" + integrity sha512-vYRBmiLiwPVeBvR9huCFXRAtdLYfsoSG3hgsXrcuyMSXk7yqpnZlgvOGGuxfhrRb/iNfcd0M0cEs0j22mDgZ+A== + dependencies: + chalk "^4.1.0" + command-line-args "^5.1.1" + command-line-commands "^3.0.1" + command-line-usage "^6.1.0" + git-config "0.0.7" + handlebars "^4.7.6" + inquirer "^7.3.3" + make-promises-safe "^5.1.0" + rimraf "^3.0.2" + semver "^7.3.2" + toml "^3.0.0" + ts-typed-json "^0.3.2" + validate-npm-package-license "^3.0.4" + validate-npm-package-name "^3.0.0" + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -7718,6 +7853,22 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= +node-pre-gyp@0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.17.0.tgz#5af3f7b4c3848b5ed00edc3d298ff836daae5f1d" + integrity sha512-abzZt1hmOjkZez29ppg+5gGqdPLUuJeAEwVPtHYEJgx0qzttCbcKFpxrCQn2HYbwCv2c+7JwH4BgEzFkUGpn4A== + dependencies: + detect-libc "^1.0.3" + mkdirp "^0.5.5" + needle "^2.5.2" + nopt "^4.0.3" + npm-packlist "^1.4.8" + npmlog "^4.1.2" + rc "^1.2.8" + rimraf "^2.7.1" + semver "^5.7.1" + tar "^4.4.13" + node-releases@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476" @@ -7728,7 +7879,7 @@ node-stream-zip@^1.9.1: resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea" integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw== -nopt@^4.0.1: +nopt@^4.0.1, nopt@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== @@ -7780,7 +7931,7 @@ normalize-url@^6.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== -npm-bundled@^1.1.1: +npm-bundled@^1.0.1, npm-bundled@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== @@ -7822,6 +7973,15 @@ npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.0, npm-pack semver "^7.3.4" validate-npm-package-name "^3.0.0" +npm-packlist@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" + integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-normalize-package-bin "^1.0.1" + npm-packlist@^2.1.4: version "2.2.2" resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-2.2.2.tgz#076b97293fa620f632833186a7a8f65aaa6148c8" @@ -8602,6 +8762,16 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" +rc@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-devtools-core@^4.6.0: version "4.24.6" resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.24.6.tgz#3262114f483465179c97a49b7ada845048f4f97e" @@ -8847,6 +9017,11 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +reduce-flatten@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" + integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== + "ref-napi@^2.0.1 || ^3.0.2", ref-napi@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/ref-napi/-/ref-napi-3.0.3.tgz#e259bfc2bbafb3e169e8cd9ba49037dd00396b22" @@ -9067,7 +9242,7 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^2.5.4, rimraf@^2.6.3: +rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -9161,7 +9336,7 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" -sax@^1.2.1: +sax@^1.2.1, sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -9719,6 +9894,11 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + strong-log-transformer@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" @@ -9772,6 +9952,16 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== +table-layout@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" + integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A== + dependencies: + array-back "^4.0.1" + deep-extend "~0.6.0" + typical "^5.2.0" + wordwrapjs "^4.0.0" + table@^6.0.9: version "6.8.0" resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca" @@ -9783,7 +9973,7 @@ table@^6.0.9: string-width "^4.2.3" strip-ansi "^6.0.1" -tar@^4.4.12: +tar@^4.4.12, tar@^4.4.13: version "4.4.19" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== @@ -9950,6 +10140,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +toml@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" + integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== + tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" @@ -10017,6 +10212,11 @@ ts-node@^10.0.0, ts-node@^10.4.0: v8-compile-cache-lib "^3.0.0" yn "3.1.1" +ts-typed-json@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/ts-typed-json/-/ts-typed-json-0.3.2.tgz#f4f20f45950bae0a383857f7b0a94187eca1b56a" + integrity sha512-Tdu3BWzaer7R5RvBIJcg9r8HrTZgpJmsX+1meXMJzYypbkj8NK2oJN0yvm4Dp/Iv6tzFa/L5jKRmEVTga6K3nA== + tsconfig-paths@^3.14.1, tsconfig-paths@^3.9.0: version "3.14.1" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" @@ -10149,6 +10349,16 @@ typescript@~4.3.0: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== +typical@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" + integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== + +typical@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" + integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== + uglify-es@^3.1.9: version "3.3.9" resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" @@ -10523,6 +10733,14 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= +wordwrapjs@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f" + integrity sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA== + dependencies: + reduce-flatten "^2.0.0" + typical "^5.2.0" + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" From ea34c4752712efecf3367c5a5fc4b06e66c1e9d7 Mon Sep 17 00:00:00 2001 From: Karim Stekelenburg Date: Thu, 19 May 2022 23:43:15 +0200 Subject: [PATCH 004/125] feat: jsonld-credential support (#718) Signed-off-by: Karim Stekelenburg --- DEVREADME.md | 5 + packages/core/package.json | 5 + packages/core/src/agent/Agent.ts | 4 +- packages/core/src/crypto/LdKeyPair.ts | 51 + packages/core/src/crypto/WalletKeyPair.ts | 123 + .../JwsLinkedDataSignature.ts | 270 ++ .../bbs/BbsBlsSignature2020.ts | 412 +++ .../bbs/BbsBlsSignatureProof2020.ts | 422 +++ .../signature-suites/bbs/deriveProof.ts | 129 + .../src/crypto/signature-suites/bbs/index.ts | 19 + .../bbs/types/CanonizeOptions.ts | 33 + .../bbs/types/CreateProofOptions.ts | 42 + .../bbs/types/CreateVerifyDataOptions.ts | 43 + .../bbs/types/DeriveProofOptions.ts | 51 + .../bbs/types/DidDocumentPublicKey.ts | 52 + .../bbs/types/GetProofsOptions.ts | 41 + .../bbs/types/GetProofsResult.ts | 28 + .../bbs/types/GetTypeOptions.ts | 30 + .../signature-suites/bbs/types/JsonWebKey.ts | 53 + .../bbs/types/KeyPairOptions.ts | 34 + .../bbs/types/KeyPairSigner.ts | 29 + .../bbs/types/KeyPairVerifier.ts | 30 + .../bbs/types/SignatureSuiteOptions.ts | 52 + .../bbs/types/SuiteSignOptions.ts | 41 + .../bbs/types/VerifyProofOptions.ts | 42 + .../bbs/types/VerifyProofResult.ts | 26 + .../bbs/types/VerifySignatureOptions.ts | 45 + .../signature-suites/bbs/types/index.ts | 28 + .../ed25519/Ed25519Signature2018.ts | 223 ++ .../signature-suites/ed25519/constants.ts | 2 + .../signature-suites/ed25519/context.ts | 99 + .../core/src/crypto/signature-suites/index.ts | 2 + .../dids/methods/sov/SovDidResolver.ts | 7 +- .../src/modules/vc/SignatureSuiteRegistry.ts | 55 + .../src/modules/vc/W3cCredentialService.ts | 381 +++ .../vc/__tests__/W3cCredentialService.test.ts | 420 +++ .../vc/__tests__/contexts/X25519_v1.ts | 26 + .../modules/vc/__tests__/contexts/bbs_v1.ts | 92 + .../vc/__tests__/contexts/citizenship_v1.ts | 45 + .../vc/__tests__/contexts/credentials_v1.ts | 250 ++ .../modules/vc/__tests__/contexts/did_v1.ts | 56 + .../vc/__tests__/contexts/ed25519_v1.ts | 91 + .../vc/__tests__/contexts/examples_v1.ts | 46 + .../modules/vc/__tests__/contexts/index.ts | 11 + .../src/modules/vc/__tests__/contexts/odrl.ts | 181 ++ .../vc/__tests__/contexts/schema_org.ts | 2838 +++++++++++++++++ .../vc/__tests__/contexts/security_v1.ts | 47 + .../vc/__tests__/contexts/security_v2.ts | 90 + .../contexts/security_v3_unstable.ts | 680 ++++ .../vc/__tests__/contexts/vaccination_v1.ts | 88 + .../__tests__/dids/did_example_489398593.ts | 13 + .../dids/did_sov_QqEfJxe752NCmWqR5TssZ5.ts | 24 + ...Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL.ts | 45 + ...AApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV.ts | 45 + ...5xLwESYfhcDGdSrc9mgfu51w939BjmKmng5HvYK.ts | 30 + ...Bt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa.ts | 54 + ...jyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD.ts | 30 + ...T9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh.ts | 30 + ...bLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn.ts | 30 + ...boDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN.ts | 27 + ...haLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ.ts | 30 + ...9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox.ts | 30 + ...ZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F.ts | 30 + ...ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4.ts | 30 + .../modules/vc/__tests__/documentLoader.ts | 113 + .../core/src/modules/vc/__tests__/fixtures.ts | 326 ++ packages/core/src/modules/vc/constants.ts | 14 + packages/core/src/modules/vc/index.ts | 2 + .../src/modules/vc/models/LinkedDataProof.ts | 86 + .../vc/models/W3cCredentialServiceOptions.ts | 57 + .../vc/models/credential/CredentialSchema.ts | 23 + .../vc/models/credential/CredentialSubject.ts | 40 + .../modules/vc/models/credential/Issuer.ts | 68 + .../vc/models/credential/W3cCredential.ts | 128 + .../credential/W3cVerifiableCredential.ts | 53 + .../credential/W3cVerifyCredentialResult.ts | 15 + packages/core/src/modules/vc/models/index.ts | 3 + .../presentation/VerifyPresentationResult.ts | 9 + .../vc/models/presentation/W3Presentation.ts | 77 + .../presentation/W3cVerifiablePresentation.ts | 25 + packages/core/src/modules/vc/module.ts | 14 + .../CredentialIssuancePurpose.ts | 89 + .../modules/vc/proof-purposes/ProofPurpose.ts | 1 + .../vc/repository/W3cCredentialRecord.ts | 57 + .../vc/repository/W3cCredentialRepository.ts | 17 + .../__tests__/W3cCredentialRecord.test.ts | 32 + .../core/src/modules/vc/repository/index.ts | 2 + packages/core/src/modules/vc/validators.ts | 32 + packages/core/src/utils/environment.ts | 9 + packages/core/src/utils/error.ts | 1 + packages/core/src/utils/index.ts | 1 + packages/core/src/utils/jsonld.ts | 156 + packages/core/src/utils/type.ts | 2 + packages/core/src/utils/validators.ts | 56 + packages/core/src/wallet/IndyWallet.ts | 46 +- packages/core/tsconfig.json | 1 + packages/core/types/jsonld-signatures.ts | 23 + packages/core/types/jsonld.ts | 29 + packages/core/types/vc.ts | 12 + packages/react-native/package.json | 3 +- yarn.lock | 810 +++-- 101 files changed, 10508 insertions(+), 242 deletions(-) create mode 100644 packages/core/src/crypto/LdKeyPair.ts create mode 100644 packages/core/src/crypto/WalletKeyPair.ts create mode 100644 packages/core/src/crypto/signature-suites/JwsLinkedDataSignature.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/BbsBlsSignature2020.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/BbsBlsSignatureProof2020.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/deriveProof.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/index.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/CanonizeOptions.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/CreateProofOptions.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/CreateVerifyDataOptions.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/DeriveProofOptions.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/DidDocumentPublicKey.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/GetProofsOptions.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/GetProofsResult.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/GetTypeOptions.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/JsonWebKey.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/KeyPairOptions.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/KeyPairSigner.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/KeyPairVerifier.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/SignatureSuiteOptions.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/SuiteSignOptions.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/VerifyProofOptions.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/VerifyProofResult.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/VerifySignatureOptions.ts create mode 100644 packages/core/src/crypto/signature-suites/bbs/types/index.ts create mode 100644 packages/core/src/crypto/signature-suites/ed25519/Ed25519Signature2018.ts create mode 100644 packages/core/src/crypto/signature-suites/ed25519/constants.ts create mode 100644 packages/core/src/crypto/signature-suites/ed25519/context.ts create mode 100644 packages/core/src/crypto/signature-suites/index.ts create mode 100644 packages/core/src/modules/vc/SignatureSuiteRegistry.ts create mode 100644 packages/core/src/modules/vc/W3cCredentialService.ts create mode 100644 packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/X25519_v1.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/bbs_v1.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/citizenship_v1.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/credentials_v1.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/did_v1.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/ed25519_v1.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/examples_v1.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/index.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/odrl.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/schema_org.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/security_v1.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/security_v2.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/security_v3_unstable.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/vaccination_v1.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_example_489398593.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_sov_QqEfJxe752NCmWqR5TssZ5.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_zUC729nNiUKQ4pHHNYovae25gkkuvtsZmtpjnLYUj1r8Yd4ZRn3FaswicUWs2NYNuWXxQ7MgzAX7dqXxAFZXFvn2jhqGKpjm5xLwESYfhcDGdSrc9mgfu51w939BjmKmng5HvYK.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F.ts create mode 100644 packages/core/src/modules/vc/__tests__/dids/did_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4.ts create mode 100644 packages/core/src/modules/vc/__tests__/documentLoader.ts create mode 100644 packages/core/src/modules/vc/__tests__/fixtures.ts create mode 100644 packages/core/src/modules/vc/constants.ts create mode 100644 packages/core/src/modules/vc/index.ts create mode 100644 packages/core/src/modules/vc/models/LinkedDataProof.ts create mode 100644 packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts create mode 100644 packages/core/src/modules/vc/models/credential/CredentialSchema.ts create mode 100644 packages/core/src/modules/vc/models/credential/CredentialSubject.ts create mode 100644 packages/core/src/modules/vc/models/credential/Issuer.ts create mode 100644 packages/core/src/modules/vc/models/credential/W3cCredential.ts create mode 100644 packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts create mode 100644 packages/core/src/modules/vc/models/credential/W3cVerifyCredentialResult.ts create mode 100644 packages/core/src/modules/vc/models/index.ts create mode 100644 packages/core/src/modules/vc/models/presentation/VerifyPresentationResult.ts create mode 100644 packages/core/src/modules/vc/models/presentation/W3Presentation.ts create mode 100644 packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts create mode 100644 packages/core/src/modules/vc/module.ts create mode 100644 packages/core/src/modules/vc/proof-purposes/CredentialIssuancePurpose.ts create mode 100644 packages/core/src/modules/vc/proof-purposes/ProofPurpose.ts create mode 100644 packages/core/src/modules/vc/repository/W3cCredentialRecord.ts create mode 100644 packages/core/src/modules/vc/repository/W3cCredentialRepository.ts create mode 100644 packages/core/src/modules/vc/repository/__tests__/W3cCredentialRecord.test.ts create mode 100644 packages/core/src/modules/vc/repository/index.ts create mode 100644 packages/core/src/modules/vc/validators.ts create mode 100644 packages/core/src/utils/environment.ts create mode 100644 packages/core/src/utils/error.ts create mode 100644 packages/core/src/utils/jsonld.ts create mode 100644 packages/core/types/jsonld-signatures.ts create mode 100644 packages/core/types/jsonld.ts create mode 100644 packages/core/types/vc.ts diff --git a/DEVREADME.md b/DEVREADME.md index 64aab7671d..c201d8192f 100644 --- a/DEVREADME.md +++ b/DEVREADME.md @@ -2,6 +2,11 @@ This file is intended for developers working on the internals of the framework. If you're just looking how to get started with the framework, see the [docs](./docs) +## Installing dependencies + +Right now, as a patch that will later be changed, some platforms will have an "error" when installing the dependencies with yarn. This is because the BBS signatures library that we use is built for Linux x86 and MacOS x86 (and not Windows and MacOS arm). This means that it will show that it could not download the binary. +This is not an error for developers, the library that fails is `node-bbs-signaturs` and is an optional dependency for perfomance improvements. It will fallback to a, slower, wasm build. + ## Running tests Test are executed using jest. Some test require either the **mediator agents** or the **ledger** to be running. When running tests that require a connection to the ledger pool, you need to set the `TEST_AGENT_PUBLIC_DID_SEED` and `GENESIS_TXN_PATH` environment variables. diff --git a/packages/core/package.json b/packages/core/package.json index 20eb074d14..12dc0a99c4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -23,9 +23,14 @@ "prepublishOnly": "yarn run build" }, "dependencies": { + "@digitalcredentials/jsonld": "^5.2.1", + "@digitalcredentials/jsonld-signatures": "^9.3.1", + "@digitalcredentials/vc": "^1.1.2", "@mattrglobal/bbs-signatures": "^1.0.0", + "@mattrglobal/bls12381-key-pair": "^1.0.0", "@multiformats/base-x": "^4.0.1", "@stablelib/ed25519": "^1.0.2", + "@stablelib/random": "^1.0.1", "@stablelib/sha256": "^1.0.1", "@types/indy-sdk": "^1.16.19", "@types/node-fetch": "^2.5.10", diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index dc9da53e93..16031a144b 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -30,6 +30,7 @@ import { QuestionAnswerModule } from '../modules/question-answer/QuestionAnswerM import { MediatorModule } from '../modules/routing/MediatorModule' import { RecipientModule } from '../modules/routing/RecipientModule' import { RoutingService } from '../modules/routing/services/RoutingService' +import { W3cVcModule } from '../modules/vc/module' import { DependencyManager } from '../plugins' import { StorageUpdateService, DidCommMessageRepository, StorageVersionRepository } from '../storage' import { InMemoryMessageRepository } from '../storage/InMemoryMessageRepository' @@ -348,7 +349,8 @@ export class Agent { DidsModule, WalletModule, OutOfBandModule, - IndyModule + IndyModule, + W3cVcModule ) } } diff --git a/packages/core/src/crypto/LdKeyPair.ts b/packages/core/src/crypto/LdKeyPair.ts new file mode 100644 index 0000000000..3a46c47c1a --- /dev/null +++ b/packages/core/src/crypto/LdKeyPair.ts @@ -0,0 +1,51 @@ +import type { VerificationMethod } from '../modules/dids' + +export interface LdKeyPairOptions { + id: string + controller: string +} + +export abstract class LdKeyPair { + public readonly id: string + public readonly controller: string + public abstract type: string + + public constructor(options: LdKeyPairOptions) { + this.id = options.id + this.controller = options.controller + } + + public static async generate(): Promise { + throw new Error('Not implemented') + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public static async from(verificationMethod: VerificationMethod): Promise { + throw new Error('Abstract method from() must be implemented in subclass.') + } + + public export(publicKey = false, privateKey = false) { + if (!publicKey && !privateKey) { + throw new Error('Export requires specifying either "publicKey" or "privateKey".') + } + const key = { + id: this.id, + type: this.type, + controller: this.controller, + } + + return key + } + + public abstract fingerprint(): string + + public abstract verifyFingerprint(fingerprint: string): boolean + + public abstract signer(): { + sign: (data: { data: Uint8Array | Uint8Array[] }) => Promise> + } + + public abstract verifier(): { + verify: (data: { data: Uint8Array | Uint8Array[]; signature: Uint8Array }) => Promise + } +} diff --git a/packages/core/src/crypto/WalletKeyPair.ts b/packages/core/src/crypto/WalletKeyPair.ts new file mode 100644 index 0000000000..a251d5dfdd --- /dev/null +++ b/packages/core/src/crypto/WalletKeyPair.ts @@ -0,0 +1,123 @@ +import type { Wallet } from '..' +import type { Key } from './Key' +import type { LdKeyPairOptions } from './LdKeyPair' + +import { VerificationMethod } from '../modules/dids' +import { getKeyDidMappingByVerificationMethod } from '../modules/dids/domain/key-type/keyDidMapping' +import { JsonTransformer } from '../utils' +import { MessageValidator } from '../utils/MessageValidator' +import { Buffer } from '../utils/buffer' + +import { LdKeyPair } from './LdKeyPair' + +interface WalletKeyPairOptions extends LdKeyPairOptions { + wallet: Wallet + key: Key +} + +export function createWalletKeyPairClass(wallet: Wallet) { + return class WalletKeyPair extends LdKeyPair { + public wallet: Wallet + public key: Key + public type: string + + public constructor(options: WalletKeyPairOptions) { + super(options) + this.wallet = options.wallet + this.key = options.key + this.type = options.key.keyType + } + + public static async generate(): Promise { + throw new Error('Not implemented') + } + + public fingerprint(): string { + throw new Error('Method not implemented.') + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public verifyFingerprint(fingerprint: string): boolean { + throw new Error('Method not implemented.') + } + + public static async from(verificationMethod: VerificationMethod): Promise { + const vMethod = JsonTransformer.fromJSON(verificationMethod, VerificationMethod) + MessageValidator.validateSync(vMethod) + const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(vMethod) + const key = getKeyFromVerificationMethod(vMethod) + + return new WalletKeyPair({ + id: vMethod.id, + controller: vMethod.controller, + wallet: wallet, + key: key, + }) + } + + /** + * This method returns a wrapped wallet.sign method. The method is being wrapped so we can covert between Uint8Array and Buffer. This is to make it compatible with the external signature libraries. + */ + public signer(): { sign: (data: { data: Uint8Array | Uint8Array[] }) => Promise } { + // wrap function for conversion + const wrappedSign = async (data: { data: Uint8Array | Uint8Array[] }): Promise => { + let converted: Buffer | Buffer[] = [] + + // convert uint8array to buffer + if (Array.isArray(data.data)) { + converted = data.data.map((d) => Buffer.from(d)) + } else { + converted = Buffer.from(data.data) + } + + // sign + const result = await wallet.sign({ + data: converted, + key: this.key, + }) + + // convert result buffer to uint8array + return Uint8Array.from(result) + } + + return { + sign: wrappedSign.bind(this), + } + } + + /** + * This method returns a wrapped wallet.verify method. The method is being wrapped so we can covert between Uint8Array and Buffer. This is to make it compatible with the external signature libraries. + */ + public verifier(): { + verify: (data: { data: Uint8Array | Uint8Array[]; signature: Uint8Array }) => Promise + } { + const wrappedVerify = async (data: { + data: Uint8Array | Uint8Array[] + signature: Uint8Array + }): Promise => { + let converted: Buffer | Buffer[] = [] + + // convert uint8array to buffer + if (Array.isArray(data.data)) { + converted = data.data.map((d) => Buffer.from(d)) + } else { + converted = Buffer.from(data.data) + } + + // verify + return wallet.verify({ + data: converted, + signature: Buffer.from(data.signature), + key: this.key, + }) + } + return { + verify: wrappedVerify.bind(this), + } + } + + public get publicKeyBuffer(): Uint8Array { + return new Uint8Array(this.key.publicKey) + } + } +} diff --git a/packages/core/src/crypto/signature-suites/JwsLinkedDataSignature.ts b/packages/core/src/crypto/signature-suites/JwsLinkedDataSignature.ts new file mode 100644 index 0000000000..e062d503da --- /dev/null +++ b/packages/core/src/crypto/signature-suites/JwsLinkedDataSignature.ts @@ -0,0 +1,270 @@ +/*! + * Copyright (c) 2020-2021 Digital Bazaar, Inc. All rights reserved. + */ +import type { DocumentLoader, Proof, VerificationMethod } from '../../utils' +import type { LdKeyPair } from '../LdKeyPair' + +import { suites } from '../../../types/jsonld-signatures' +import { AriesFrameworkError } from '../../error' +import { TypedArrayEncoder, JsonEncoder } from '../../utils' + +const LinkedDataSignature = suites.LinkedDataSignature +export interface JwsLinkedDataSignatureOptions { + type: string + algorithm: string + LDKeyClass: typeof LdKeyPair + key?: LdKeyPair + proof: Proof + date: string + contextUrl: string + useNativeCanonize: boolean +} + +export class JwsLinkedDataSignature extends LinkedDataSignature { + /** + * @param options - Options hashmap. + * @param options.type - Provided by subclass. + * @param options.alg - JWS alg provided by subclass. + * @param [options.LDKeyClass] - Provided by subclass or subclass + * overrides `getVerificationMethod`. + * + * Either a `key` OR at least one of `signer`/`verifier` is required. + * + * @param [options.key] - An optional key object (containing an + * `id` property, and either `signer` or `verifier`, depending on the + * intended operation. Useful for when the application is managing keys + * itself (when using a KMS, you never have access to the private key, + * and so should use the `signer` param instead). + * + * Advanced optional parameters and overrides. + * + * @param [options.proof] - A JSON-LD document with options to use + * for the `proof` node. Any other custom fields can be provided here + * using a context different from `security-v2`. + * @param [options.date] - Signing date to use if not passed. + * @param options.contextUrl - JSON-LD context url that corresponds + * to this signature suite. Used for enforcing suite context during the + * `sign()` operation. + * @param [options.useNativeCanonize] - Whether to use a native + * canonize algorithm. + */ + public constructor(options: JwsLinkedDataSignatureOptions) { + super({ + type: options.type, + LDKeyClass: options.LDKeyClass, + contextUrl: options.contextUrl, + key: options.key, + signer: undefined, + verifier: undefined, + proof: options.proof, + date: options.date, + useNativeCanonize: options.useNativeCanonize, + }) + this.alg = options.algorithm + } + + /** + * @param options - Options hashmap. + * @param options.verifyData - The data to sign. + * @param options.proof - A JSON-LD document with options to use + * for the `proof` node. Any other custom fields can be provided here + * using a context different from `security-v2`. + * + * @returns The proof containing the signature value. + */ + public async sign(options: { verifyData: Uint8Array; proof: Proof }) { + if (!(this.signer && typeof this.signer.sign === 'function')) { + throw new Error('A signer API has not been specified.') + } + // JWS header + const header = { + alg: this.alg, + b64: false, + crit: ['b64'], + } + + /* + +-------+-----------------------------------------------------------+ + | "b64" | JWS Signing Input Formula | + +-------+-----------------------------------------------------------+ + | true | ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || | + | | BASE64URL(JWS Payload)) | + | | | + | false | ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.') || | + | | JWS Payload | + +-------+-----------------------------------------------------------+ + */ + + // create JWS data and sign + const encodedHeader = JsonEncoder.toBase64URL(header) + + const data = _createJws({ encodedHeader, verifyData: options.verifyData }) + + const signature = await this.signer.sign({ data }) + + // create detached content signature + const encodedSignature = TypedArrayEncoder.toBase64URL(signature) + options.proof.jws = encodedHeader + '..' + encodedSignature + return options.proof + } + + /** + * @param options - Options hashmap. + * @param options.verifyData - The data to verify. + * @param options.verificationMethod - A verification method. + * @param options.proof - The proof to be verified. + * + * @returns Resolves with the verification result. + */ + public async verifySignature(options: { + verifyData: Uint8Array + verificationMethod: VerificationMethod + proof: Proof + }) { + if (!(options.proof.jws && typeof options.proof.jws === 'string' && options.proof.jws.includes('.'))) { + throw new TypeError('The proof does not include a valid "jws" property.') + } + // add payload into detached content signature + const [encodedHeader /*payload*/, , encodedSignature] = options.proof.jws.split('.') + + let header + try { + header = JsonEncoder.fromBase64(encodedHeader) + } catch (e) { + throw new Error('Could not parse JWS header; ' + e) + } + if (!(header && typeof header === 'object')) { + throw new Error('Invalid JWS header.') + } + + // confirm header matches all expectations + if ( + !( + header.alg === this.alg && + header.b64 === false && + Array.isArray(header.crit) && + header.crit.length === 1 && + header.crit[0] === 'b64' + ) && + Object.keys(header).length === 3 + ) { + throw new Error(`Invalid JWS header parameters for ${this.type}.`) + } + + // do signature verification + const signature = TypedArrayEncoder.fromBase64(encodedSignature) + + const data = _createJws({ encodedHeader, verifyData: options.verifyData }) + + let { verifier } = this + if (!verifier) { + const key = await this.LDKeyClass.from(options.verificationMethod) + verifier = key.verifier() + } + return verifier.verify({ data, signature }) + } + + public async getVerificationMethod(options: { proof: Proof; documentLoader?: DocumentLoader }) { + if (this.key) { + // This happens most often during sign() operations. For verify(), + // the expectation is that the verification method will be fetched + // by the documentLoader (below), not provided as a `key` parameter. + return this.key.export({ publicKey: true }) + } + + let { verificationMethod } = options.proof + + if (typeof verificationMethod === 'object' && verificationMethod !== null) { + verificationMethod = verificationMethod.id + } + + if (!verificationMethod) { + throw new Error('No "verificationMethod" found in proof.') + } + + if (!options.documentLoader) { + throw new AriesFrameworkError( + 'Missing custom document loader. This is required for resolving verification methods.' + ) + } + + const { document } = await options.documentLoader(verificationMethod) + + verificationMethod = typeof document === 'string' ? JSON.parse(document) : document + + await this.assertVerificationMethod(verificationMethod) + return verificationMethod + } + + /** + * Checks whether a given proof exists in the document. + * + * @param options - Options hashmap. + * @param options.proof - A proof. + * @param options.document - A JSON-LD document. + * @param options.purpose - A jsonld-signatures ProofPurpose + * instance (e.g. AssertionProofPurpose, AuthenticationProofPurpose, etc). + * @param options.documentLoader - A secure document loader (it is + * recommended to use one that provides static known documents, instead of + * fetching from the web) for returning contexts, controller documents, + * keys, and other relevant URLs needed for the proof. + * @param [options.expansionMap] - A custom expansion map that is + * passed to the JSON-LD processor; by default a function that will throw + * an error when unmapped properties are detected in the input, use `false` + * to turn this off and allow unmapped properties to be dropped or use a + * custom function. + * + * @returns Whether a match for the proof was found. + */ + public async matchProof(options: { + proof: Proof + document: VerificationMethod + // eslint-disable-next-line @typescript-eslint/no-explicit-any + purpose: any + documentLoader?: DocumentLoader + expansionMap?: () => void + }) { + const proofMatches = await super.matchProof({ + proof: options.proof, + document: options.document, + purpose: options.purpose, + documentLoader: options.documentLoader, + expansionMap: options.expansionMap, + }) + if (!proofMatches) { + return false + } + // NOTE: When subclassing this suite: Extending suites will need to check + + if (!this.key) { + // no key specified, so assume this suite matches and it can be retrieved + return true + } + + const { verificationMethod } = options.proof + + // only match if the key specified matches the one in the proof + if (typeof verificationMethod === 'object') { + return verificationMethod.id === this.key.id + } + return verificationMethod === this.key.id + } +} + +/** + * Creates the bytes ready for signing. + * + * @param {object} options - Options hashmap. + * @param {string} options.encodedHeader - A base64url encoded JWT header. + * @param {Uint8Array} options.verifyData - Payload to sign/verify. + * @returns {Uint8Array} A combined byte array for signing. + */ +function _createJws(options: { encodedHeader: string; verifyData: Uint8Array }): Uint8Array { + const encodedHeaderBytes = TypedArrayEncoder.fromString(options.encodedHeader + '.') + + // concatenate the two uint8arrays + const data = new Uint8Array(encodedHeaderBytes.length + options.verifyData.length) + data.set(encodedHeaderBytes, 0) + data.set(options.verifyData, encodedHeaderBytes.length) + return data +} diff --git a/packages/core/src/crypto/signature-suites/bbs/BbsBlsSignature2020.ts b/packages/core/src/crypto/signature-suites/bbs/BbsBlsSignature2020.ts new file mode 100644 index 0000000000..094d697e57 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/BbsBlsSignature2020.ts @@ -0,0 +1,412 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { JsonObject } from '../../../types' +import type { DocumentLoader, Proof, VerificationMethod } from '../../../utils' +import type { + SignatureSuiteOptions, + CreateProofOptions, + CanonizeOptions, + CreateVerifyDataOptions, + VerifyProofOptions, + VerifySignatureOptions, + SuiteSignOptions, +} from './types' + +import jsonld from '../../../../types/jsonld' +import { suites } from '../../../../types/jsonld-signatures' +import { AriesFrameworkError } from '../../../error' +import { SECURITY_CONTEXT_BBS_URL, SECURITY_CONTEXT_URL } from '../../../modules/vc/constants' +import { w3cDate, TypedArrayEncoder } from '../../../utils' + +/** + * A BBS+ signature suite for use with BLS12-381 key pairs + */ +export class BbsBlsSignature2020 extends suites.LinkedDataProof { + private proof: Record + /** + * Default constructor + * @param options {SignatureSuiteOptions} options for constructing the signature suite + */ + public constructor(options: SignatureSuiteOptions = {}) { + const { verificationMethod, signer, key, date, useNativeCanonize, LDKeyClass } = options + // validate common options + if (verificationMethod !== undefined && typeof verificationMethod !== 'string') { + throw new TypeError('"verificationMethod" must be a URL string.') + } + super({ + type: 'BbsBlsSignature2020', + }) + + this.proof = { + '@context': [ + { + sec: 'https://w3id.org/security#', + proof: { + '@id': 'sec:proof', + '@type': '@id', + '@container': '@graph', + }, + }, + SECURITY_CONTEXT_BBS_URL, + ], + type: 'BbsBlsSignature2020', + } + + this.LDKeyClass = LDKeyClass + this.signer = signer + this.verificationMethod = verificationMethod + this.proofSignatureKey = 'proofValue' + if (key) { + if (verificationMethod === undefined) { + this.verificationMethod = key.id + } + this.key = key + if (typeof key.signer === 'function') { + this.signer = key.signer() + } + if (typeof key.verifier === 'function') { + this.verifier = key.verifier() + } + } + if (date) { + this.date = new Date(date) + + if (isNaN(this.date)) { + throw TypeError(`"date" "${date}" is not a valid date.`) + } + } + this.useNativeCanonize = useNativeCanonize + } + + public ensureSuiteContext({ document }: { document: Record }) { + if ( + document['@context'] === SECURITY_CONTEXT_BBS_URL || + (Array.isArray(document['@context']) && document['@context'].includes(SECURITY_CONTEXT_BBS_URL)) + ) { + // document already includes the required context + return + } + throw new TypeError( + `The document to be signed must contain this suite's @context, ` + `"${SECURITY_CONTEXT_BBS_URL}".` + ) + } + + /** + * @param options {CreateProofOptions} options for creating the proof + * + * @returns {Promise} Resolves with the created proof object. + */ + public async createProof(options: CreateProofOptions): Promise> { + const { document, purpose, documentLoader, expansionMap, compactProof } = options + + let proof: JsonObject + + // use proof JSON-LD document passed to API + if (this.proof) { + proof = await jsonld.compact(this.proof, SECURITY_CONTEXT_URL, { + documentLoader, + expansionMap, + compactToRelative: true, + }) + } else { + // create proof JSON-LD document + proof = { '@context': SECURITY_CONTEXT_URL } + } + + // ensure proof type is set + proof.type = this.type + + // set default `now` date if not given in `proof` or `options` + let date = this.date + if (proof.created === undefined && date === undefined) { + date = new Date() + } + + // ensure date is in string format + if (date !== undefined && typeof date !== 'string') { + date = w3cDate(date) + } + + // add API overrides + if (date !== undefined) { + proof.created = date + } + + if (this.verificationMethod !== undefined) { + proof.verificationMethod = this.verificationMethod + } + + // allow purpose to update the proof; the `proof` is in the + // SECURITY_CONTEXT_URL `@context` -- therefore the `purpose` must + // ensure any added fields are also represented in that same `@context` + proof = await purpose.update(proof, { + document, + suite: this, + documentLoader, + expansionMap, + }) + + // create data to sign + const verifyData = ( + await this.createVerifyData({ + document, + proof, + documentLoader, + expansionMap, + compactProof, + }) + ).map((item) => new Uint8Array(TypedArrayEncoder.fromString(item))) + + // sign data + proof = await this.sign({ + verifyData, + document, + proof, + documentLoader, + expansionMap, + }) + delete proof['@context'] + + return proof + } + + /** + * @param options {object} options for verifying the proof. + * + * @returns {Promise<{object}>} Resolves with the verification result. + */ + public async verifyProof(options: VerifyProofOptions): Promise> { + const { proof, document, documentLoader, expansionMap, purpose } = options + + try { + // create data to verify + const verifyData = ( + await this.createVerifyData({ + document, + proof, + documentLoader, + expansionMap, + compactProof: false, + }) + ).map((item) => new Uint8Array(TypedArrayEncoder.fromString(item))) + + // fetch verification method + const verificationMethod = await this.getVerificationMethod({ + proof, + documentLoader, + }) + + // verify signature on data + const verified = await this.verifySignature({ + verifyData, + verificationMethod, + document, + proof, + documentLoader, + expansionMap, + }) + if (!verified) { + throw new Error('Invalid signature.') + } + + // ensure proof was performed for a valid purpose + const { valid, error } = await purpose.validate(proof, { + document, + suite: this, + verificationMethod, + documentLoader, + expansionMap, + }) + if (!valid) { + throw error + } + + return { verified: true } + } catch (error) { + return { verified: false, error } + } + } + + public async canonize(input: Record, options: CanonizeOptions): Promise { + const { documentLoader, expansionMap, skipExpansion } = options + return jsonld.canonize(input, { + algorithm: 'URDNA2015', + format: 'application/n-quads', + documentLoader, + expansionMap, + skipExpansion, + useNative: this.useNativeCanonize, + }) + } + + public async canonizeProof(proof: Record, options: CanonizeOptions): Promise { + const { documentLoader, expansionMap } = options + proof = { ...proof } + delete proof[this.proofSignatureKey] + return this.canonize(proof, { + documentLoader, + expansionMap, + skipExpansion: false, + }) + } + + /** + * @param document {CreateVerifyDataOptions} options to create verify data + * + * @returns {Promise<{string[]>}. + */ + public async createVerifyData(options: CreateVerifyDataOptions): Promise { + const { proof, document, documentLoader, expansionMap } = options + + const proof2 = { ...proof, '@context': document['@context'] } + + const proofStatements = await this.createVerifyProofData(proof2, { + documentLoader, + expansionMap, + }) + const documentStatements = await this.createVerifyDocumentData(document, { + documentLoader, + expansionMap, + }) + + // concatenate c14n proof options and c14n document + return proofStatements.concat(documentStatements) + } + + /** + * @param proof to canonicalize + * @param options to create verify data + * + * @returns {Promise<{string[]>}. + */ + public async createVerifyProofData( + proof: Record, + { documentLoader, expansionMap }: { documentLoader?: DocumentLoader; expansionMap?: () => void } + ): Promise { + const c14nProofOptions = await this.canonizeProof(proof, { + documentLoader, + expansionMap, + }) + + return c14nProofOptions.split('\n').filter((_) => _.length > 0) + } + + /** + * @param document to canonicalize + * @param options to create verify data + * + * @returns {Promise<{string[]>}. + */ + public async createVerifyDocumentData( + document: Record, + { documentLoader, expansionMap }: { documentLoader?: DocumentLoader; expansionMap?: () => void } + ): Promise { + const c14nDocument = await this.canonize(document, { + documentLoader, + expansionMap, + }) + + return c14nDocument.split('\n').filter((_) => _.length > 0) + } + + /** + * @param document {object} to be signed. + * @param proof {object} + * @param documentLoader {function} + * @param expansionMap {function} + */ + public async getVerificationMethod({ + proof, + documentLoader, + }: { + proof: Proof + documentLoader?: DocumentLoader + }): Promise { + let { verificationMethod } = proof + + if (typeof verificationMethod === 'object' && verificationMethod !== null) { + verificationMethod = verificationMethod.id + } + + if (!verificationMethod) { + throw new Error('No "verificationMethod" found in proof.') + } + + if (!documentLoader) { + throw new AriesFrameworkError( + 'Missing custom document loader. This is required for resolving verification methods.' + ) + } + + const { document } = await documentLoader(verificationMethod) + + if (!document) { + throw new Error(`Verification method ${verificationMethod} not found.`) + } + + // ensure verification method has not been revoked + if (document.revoked !== undefined) { + throw new Error('The verification method has been revoked.') + } + + return document as VerificationMethod + } + + /** + * @param options {SuiteSignOptions} Options for signing. + * + * @returns {Promise<{object}>} the proof containing the signature value. + */ + public async sign(options: SuiteSignOptions): Promise { + const { verifyData, proof } = options + + if (!(this.signer && typeof this.signer.sign === 'function')) { + throw new Error('A signer API with sign function has not been specified.') + } + + const proofValue: Uint8Array = await this.signer.sign({ + data: verifyData, + }) + + proof[this.proofSignatureKey] = TypedArrayEncoder.toBase64(proofValue) + + return proof as Proof + } + + /** + * @param verifyData {VerifySignatureOptions} Options to verify the signature. + * + * @returns {Promise} + */ + public async verifySignature(options: VerifySignatureOptions): Promise { + const { verificationMethod, verifyData, proof } = options + let { verifier } = this + + if (!verifier) { + const key = await this.LDKeyClass.from(verificationMethod) + verifier = key.verifier(key, this.alg, this.type) + } + + return await verifier.verify({ + data: verifyData, + signature: new Uint8Array(TypedArrayEncoder.fromBase64(proof[this.proofSignatureKey] as string)), + }) + } + + public static proofType = [ + 'BbsBlsSignature2020', + 'sec:BbsBlsSignature2020', + 'https://w3id.org/security#BbsBlsSignature2020', + ] +} diff --git a/packages/core/src/crypto/signature-suites/bbs/BbsBlsSignatureProof2020.ts b/packages/core/src/crypto/signature-suites/bbs/BbsBlsSignatureProof2020.ts new file mode 100644 index 0000000000..c785986435 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/BbsBlsSignatureProof2020.ts @@ -0,0 +1,422 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { JsonObject } from '../../../types' +import type { DocumentLoader, Proof } from '../../../utils' +import type { DeriveProofOptions, VerifyProofOptions, CreateVerifyDataOptions, CanonizeOptions } from './types' +import type { VerifyProofResult } from './types/VerifyProofResult' + +import { blsCreateProof, blsVerifyProof } from '@mattrglobal/bbs-signatures' +import { Bls12381G2KeyPair } from '@mattrglobal/bls12381-key-pair' +import { randomBytes } from '@stablelib/random' + +import jsonld from '../../../../types/jsonld' +import { suites } from '../../../../types/jsonld-signatures' +import { AriesFrameworkError } from '../../../error' +import { SECURITY_CONTEXT_URL } from '../../../modules/vc/constants' +import { TypedArrayEncoder } from '../../../utils' + +import { BbsBlsSignature2020 } from './BbsBlsSignature2020' + +export class BbsBlsSignatureProof2020 extends suites.LinkedDataProof { + public constructor({ useNativeCanonize, key, LDKeyClass }: Record = {}) { + super({ + type: 'BbsBlsSignatureProof2020', + }) + + this.proof = { + '@context': [ + { + sec: 'https://w3id.org/security#', + proof: { + '@id': 'sec:proof', + '@type': '@id', + '@container': '@graph', + }, + }, + 'https://w3id.org/security/bbs/v1', + ], + type: 'BbsBlsSignatureProof2020', + } + this.mappedDerivedProofType = 'BbsBlsSignature2020' + this.supportedDeriveProofType = BbsBlsSignatureProof2020.supportedDerivedProofType + + this.LDKeyClass = LDKeyClass ?? Bls12381G2KeyPair + this.proofSignatureKey = 'proofValue' + this.key = key + this.useNativeCanonize = useNativeCanonize + } + + /** + * Derive a proof from a proof and reveal document + * + * @param options {object} options for deriving a proof. + * + * @returns {Promise} Resolves with the derived proof object. + */ + public async deriveProof(options: DeriveProofOptions): Promise> { + const { document, proof, revealDocument, documentLoader, expansionMap } = options + let { nonce } = options + + const proofType = proof.type + + if (typeof proofType !== 'string') { + throw new TypeError(`Expected proof.type to be of type 'string', got ${typeof proofType} instead.`) + } + + // Validate that the input proof document has a proof compatible with this suite + if (!BbsBlsSignatureProof2020.supportedDerivedProofType.includes(proofType)) { + throw new TypeError( + `proof document proof incompatible, expected proof types of ${JSON.stringify( + BbsBlsSignatureProof2020.supportedDerivedProofType + )} received ${proof.type}` + ) + } + + const signatureBase58 = proof[this.proofSignatureKey] + + if (typeof signatureBase58 !== 'string') { + throw new TypeError(`Expected signature to be a base58 encoded string, got ${typeof signatureBase58} instead.`) + } + + //Extract the BBS signature from the input proof + const signature = TypedArrayEncoder.fromBase64(signatureBase58) + + //Initialize the BBS signature suite + const suite = new BbsBlsSignature2020() + + //Initialize the derived proof + let derivedProof + if (this.proof) { + // use proof JSON-LD document passed to API + derivedProof = await jsonld.compact(this.proof, SECURITY_CONTEXT_URL, { + documentLoader, + expansionMap, + compactToRelative: false, + }) + } else { + // create proof JSON-LD document + derivedProof = { '@context': SECURITY_CONTEXT_URL } + } + + // ensure proof type is set + derivedProof.type = this.type + + // Get the input document statements + const documentStatements = await suite.createVerifyDocumentData(document, { + documentLoader, + expansionMap, + }) + + // Get the proof statements + const proofStatements = await suite.createVerifyProofData(proof, { + documentLoader, + expansionMap, + }) + + // Transform any blank node identifiers for the input + // document statements into actual node identifiers + // e.g _:c14n0 => urn:bnid:_:c14n0 + const transformedInputDocumentStatements = documentStatements.map((element) => + element.replace(/(_:c14n[0-9]+)/g, '') + ) + + //Transform the resulting RDF statements back into JSON-LD + const compactInputProofDocument = await jsonld.fromRDF(transformedInputDocumentStatements.join('\n')) + + // Frame the result to create the reveal document result + const revealDocumentResult = await jsonld.frame(compactInputProofDocument, revealDocument, { documentLoader }) + + // Canonicalize the resulting reveal document + const revealDocumentStatements = await suite.createVerifyDocumentData(revealDocumentResult, { + documentLoader, + expansionMap, + }) + + //Get the indicies of the revealed statements from the transformed input document offset + //by the number of proof statements + const numberOfProofStatements = proofStatements.length + + //Always reveal all the statements associated to the original proof + //these are always the first statements in the normalized form + const proofRevealIndicies = Array.from(Array(numberOfProofStatements).keys()) + + //Reveal the statements indicated from the reveal document + const documentRevealIndicies = revealDocumentStatements.map( + (key) => transformedInputDocumentStatements.indexOf(key) + numberOfProofStatements + ) + + // Check there is not a mismatch + if (documentRevealIndicies.length !== revealDocumentStatements.length) { + throw new Error('Some statements in the reveal document not found in original proof') + } + + // Combine all indicies to get the resulting list of revealed indicies + const revealIndicies = proofRevealIndicies.concat(documentRevealIndicies) + + // Create a nonce if one is not supplied + if (!nonce) { + nonce = randomBytes(50) + } + + // Set the nonce on the derived proof + // derivedProof.nonce = Buffer.from(nonce).toString('base64') + derivedProof.nonce = TypedArrayEncoder.toBase64(nonce) + + //Combine all the input statements that + //were originally signed to generate the proof + const allInputStatements: Uint8Array[] = proofStatements + .concat(documentStatements) + .map((item) => new Uint8Array(TypedArrayEncoder.fromString(item))) + + // Fetch the verification method + const verificationMethod = await this.getVerificationMethod({ + proof, + documentLoader, + }) + + // Construct a key pair class from the returned verification method + const key = verificationMethod.publicKeyJwk + ? await this.LDKeyClass.fromJwk(verificationMethod) + : await this.LDKeyClass.from(verificationMethod) + + // Compute the proof + const outputProof = await blsCreateProof({ + signature, + publicKey: key.publicKeyBuffer, + messages: allInputStatements, + nonce, + revealed: revealIndicies, + }) + + // Set the proof value on the derived proof + derivedProof.proofValue = TypedArrayEncoder.toBase64(outputProof) + + // Set the relevant proof elements on the derived proof from the input proof + derivedProof.verificationMethod = proof.verificationMethod + derivedProof.proofPurpose = proof.proofPurpose + derivedProof.created = proof.created + + return { + document: { ...revealDocumentResult }, + proof: derivedProof, + } + } + + /** + * @param options {object} options for verifying the proof. + * + * @returns {Promise<{object}>} Resolves with the verification result. + */ + public async verifyProof(options: VerifyProofOptions): Promise { + const { document, documentLoader, expansionMap, purpose } = options + const { proof } = options + + try { + proof.type = this.mappedDerivedProofType + + const proofIncludingDocumentContext = { ...proof, '@context': document['@context'] } + + // Get the proof statements + const proofStatements = await this.createVerifyProofData(proofIncludingDocumentContext, { + documentLoader, + expansionMap, + }) + + // Get the document statements + const documentStatements = await this.createVerifyProofData(document, { + documentLoader, + expansionMap, + }) + + // Transform the blank node identifier placeholders for the document statements + // back into actual blank node identifiers + const transformedDocumentStatements = documentStatements.map((element) => + element.replace(//g, '$1') + ) + + // Combine all the statements to be verified + const statementsToVerify: Uint8Array[] = proofStatements + .concat(transformedDocumentStatements) + .map((item) => new Uint8Array(TypedArrayEncoder.fromString(item))) + + // Fetch the verification method + const verificationMethod = await this.getVerificationMethod({ + proof, + documentLoader, + }) + + // Construct a key pair class from the returned verification method + const key = verificationMethod.publicKeyJwk + ? await this.LDKeyClass.fromJwk(verificationMethod) + : await this.LDKeyClass.from(verificationMethod) + + const proofValue = proof.proofValue + + if (typeof proofValue !== 'string') { + throw new AriesFrameworkError(`Expected proof.proofValue to be of type 'string', got ${typeof proof}`) + } + + // Verify the proof + const verified = await blsVerifyProof({ + proof: TypedArrayEncoder.fromBase64(proofValue), + publicKey: key.publicKeyBuffer, + messages: statementsToVerify, + nonce: TypedArrayEncoder.fromBase64(proof.nonce as string), + }) + + // Ensure proof was performed for a valid purpose + const { valid, error } = await purpose.validate(proof, { + document, + suite: this, + verificationMethod, + documentLoader, + expansionMap, + }) + if (!valid) { + throw error + } + + return verified + } catch (error) { + return { verified: false, error } + } + } + + public async canonize(input: JsonObject, options: CanonizeOptions): Promise { + const { documentLoader, expansionMap, skipExpansion } = options + return jsonld.canonize(input, { + algorithm: 'URDNA2015', + format: 'application/n-quads', + documentLoader, + expansionMap, + skipExpansion, + useNative: this.useNativeCanonize, + }) + } + + public async canonizeProof(proof: JsonObject, options: CanonizeOptions): Promise { + const { documentLoader, expansionMap } = options + proof = { ...proof } + + delete proof.nonce + delete proof.proofValue + + return this.canonize(proof, { + documentLoader, + expansionMap, + skipExpansion: false, + }) + } + + /** + * @param document {CreateVerifyDataOptions} options to create verify data + * + * @returns {Promise<{string[]>}. + */ + public async createVerifyData(options: CreateVerifyDataOptions): Promise { + const { proof, document, documentLoader, expansionMap } = options + + const proofStatements = await this.createVerifyProofData(proof, { + documentLoader, + expansionMap, + }) + const documentStatements = await this.createVerifyDocumentData(document, { + documentLoader, + expansionMap, + }) + + // concatenate c14n proof options and c14n document + return proofStatements.concat(documentStatements) + } + + /** + * @param proof to canonicalize + * @param options to create verify data + * + * @returns {Promise<{string[]>}. + */ + public async createVerifyProofData( + proof: JsonObject, + { documentLoader, expansionMap }: { documentLoader?: DocumentLoader; expansionMap?: () => void } + ): Promise { + const c14nProofOptions = await this.canonizeProof(proof, { + documentLoader, + expansionMap, + }) + + return c14nProofOptions.split('\n').filter((_) => _.length > 0) + } + + /** + * @param document to canonicalize + * @param options to create verify data + * + * @returns {Promise<{string[]>}. + */ + public async createVerifyDocumentData( + document: JsonObject, + { documentLoader, expansionMap }: { documentLoader?: DocumentLoader; expansionMap?: () => void } + ): Promise { + const c14nDocument = await this.canonize(document, { + documentLoader, + expansionMap, + }) + + return c14nDocument.split('\n').filter((_) => _.length > 0) + } + + public async getVerificationMethod(options: { proof: Proof; documentLoader?: DocumentLoader }) { + if (this.key) { + // This happens most often during sign() operations. For verify(), + // the expectation is that the verification method will be fetched + // by the documentLoader (below), not provided as a `key` parameter. + return this.key.export({ publicKey: true }) + } + + let { verificationMethod } = options.proof + + if (typeof verificationMethod === 'object' && verificationMethod !== null) { + verificationMethod = verificationMethod.id + } + + if (!verificationMethod) { + throw new Error('No "verificationMethod" found in proof.') + } + + if (!options.documentLoader) { + throw new AriesFrameworkError( + 'Missing custom document loader. This is required for resolving verification methods.' + ) + } + + const { document } = await options.documentLoader(verificationMethod) + + verificationMethod = typeof document === 'string' ? JSON.parse(document) : document + + // await this.assertVerificationMethod(verificationMethod) + return verificationMethod + } + + public static proofType = [ + 'BbsBlsSignatureProof2020', + 'sec:BbsBlsSignatureProof2020', + 'https://w3id.org/security#BbsBlsSignatureProof2020', + ] + + public static supportedDerivedProofType = [ + 'BbsBlsSignature2020', + 'sec:BbsBlsSignature2020', + 'https://w3id.org/security#BbsBlsSignature2020', + ] +} diff --git a/packages/core/src/crypto/signature-suites/bbs/deriveProof.ts b/packages/core/src/crypto/signature-suites/bbs/deriveProof.ts new file mode 100644 index 0000000000..012b15d323 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/deriveProof.ts @@ -0,0 +1,129 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { JsonObject } from '../../../types' + +import jsonld from '../../../../types/jsonld' +import { SECURITY_PROOF_URL } from '../../../modules/vc/constants' +import { W3cVerifiableCredential } from '../../../modules/vc/models' +import { JsonTransformer, getProofs, getTypeInfo } from '../../../utils' + +/** + * Derives a proof from a document featuring a supported linked data proof + * + * NOTE - This is a temporary API extending JSON-LD signatures + * + * @param proofDocument A document featuring a linked data proof capable of proof derivation + * @param revealDocument A document of the form of a JSON-LD frame describing the terms to selectively derive from the proof document + * @param options Options for proof derivation + */ +export const deriveProof = async ( + proofDocument: JsonObject, + revealDocument: JsonObject, + { suite, skipProofCompaction, documentLoader, expansionMap, nonce }: any +): Promise => { + if (!suite) { + throw new TypeError('"options.suite" is required.') + } + + if (Array.isArray(proofDocument)) { + throw new TypeError('proofDocument should be an object not an array.') + } + + const { proofs, document } = await getProofs({ + document: proofDocument, + proofType: suite.supportedDeriveProofType, + documentLoader, + expansionMap, + }) + + if (proofs.length === 0) { + throw new Error(`There were not any proofs provided that can be used to derive a proof with this suite.`) + } + let derivedProof + + derivedProof = await suite.deriveProof({ + document, + proof: proofs[0], + revealDocument, + documentLoader, + expansionMap, + nonce, + }) + + if (proofs.length > 1) { + // convert the proof property value from object ot array of objects + derivedProof = { ...derivedProof, proof: [derivedProof.proof] } + + // drop the first proof because it's already been processed + proofs.splice(0, 1) + + // add all the additional proofs to the derivedProof document + for (const proof of proofs) { + const additionalDerivedProofValue = await suite.deriveProof({ + document, + proof, + revealDocument, + documentLoader, + expansionMap, + }) + derivedProof.proof.push(additionalDerivedProofValue.proof) + } + } + + if (!skipProofCompaction) { + /* eslint-disable prefer-const */ + let expandedProof: Record = { + [SECURITY_PROOF_URL]: { + '@graph': derivedProof.proof, + }, + } + + // account for type-scoped `proof` definition by getting document types + const { types, alias } = await getTypeInfo(derivedProof.document, { + documentLoader, + expansionMap, + }) + + expandedProof['@type'] = types + + const ctx = jsonld.getValues(derivedProof.document, '@context') + + const compactProof = await jsonld.compact(expandedProof, ctx, { + documentLoader, + expansionMap, + compactToRelative: false, + }) + + delete compactProof[alias] + delete compactProof['@context'] + + /** + * removes the @included tag when multiple proofs exist because the + * @included tag messes up the canonicalized bytes leading to a bad + * signature that won't verify. + **/ + if (compactProof.proof?.['@included']) { + compactProof.proof = compactProof.proof['@included'] + } + + // add proof to document + const key = Object.keys(compactProof)[0] + jsonld.addValue(derivedProof.document, key, compactProof[key]) + } else { + delete derivedProof.proof['@context'] + jsonld.addValue(derivedProof.document, 'proof', derivedProof.proof) + } + + return JsonTransformer.fromJSON(derivedProof.document, W3cVerifiableCredential) +} diff --git a/packages/core/src/crypto/signature-suites/bbs/index.ts b/packages/core/src/crypto/signature-suites/bbs/index.ts new file mode 100644 index 0000000000..47275d0d9a --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { Bls12381G2KeyPair } from '@mattrglobal/bls12381-key-pair' +export { BbsBlsSignature2020 } from './BbsBlsSignature2020' +export { BbsBlsSignatureProof2020 } from './BbsBlsSignatureProof2020' +export * from './types' + +export { deriveProof } from './deriveProof' diff --git a/packages/core/src/crypto/signature-suites/bbs/types/CanonizeOptions.ts b/packages/core/src/crypto/signature-suites/bbs/types/CanonizeOptions.ts new file mode 100644 index 0000000000..856baecbde --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/CanonizeOptions.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { DocumentLoader } from '../../../../utils' + +/** + * Options for canonizing a document + */ +export interface CanonizeOptions { + /** + * Optional custom document loader + */ + documentLoader?: DocumentLoader + /** + * Optional expansion map + */ + // eslint-disable-next-line + expansionMap?: () => void + /** + * Indicates whether to skip expansion during canonization + */ + readonly skipExpansion?: boolean +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/CreateProofOptions.ts b/packages/core/src/crypto/signature-suites/bbs/types/CreateProofOptions.ts new file mode 100644 index 0000000000..60e06c0185 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/CreateProofOptions.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ProofPurpose } from '../../../../modules/vc/proof-purposes/ProofPurpose' +import type { JsonObject } from '../../../../types' +import type { DocumentLoader } from '../../../../utils' + +/** + * Options for creating a proof + */ +export interface CreateProofOptions { + /** + * Document to create the proof for + */ + readonly document: JsonObject + /** + * The proof purpose to specify for the generated proof + */ + readonly purpose: ProofPurpose + /** + * Optional custom document loader + */ + documentLoader?: DocumentLoader + /** + * Optional expansion map + */ + expansionMap?: () => void + /** + * Indicates whether to compact the resulting proof + */ + readonly compactProof: boolean +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/CreateVerifyDataOptions.ts b/packages/core/src/crypto/signature-suites/bbs/types/CreateVerifyDataOptions.ts new file mode 100644 index 0000000000..ba493b44a2 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/CreateVerifyDataOptions.ts @@ -0,0 +1,43 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { JsonObject } from '../../../../types' +import type { DocumentLoader } from '../../../../utils' + +/** + * Options for creating a proof + */ +export interface CreateVerifyDataOptions { + /** + * Document to create the proof for + */ + readonly document: JsonObject + /** + * The proof + */ + readonly proof: JsonObject + /** + * Optional custom document loader + */ + + documentLoader?: DocumentLoader + /** + * Optional expansion map + */ + + expansionMap?: () => void + /** + * Indicates whether to compact the proof + */ + readonly compactProof: boolean +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/DeriveProofOptions.ts b/packages/core/src/crypto/signature-suites/bbs/types/DeriveProofOptions.ts new file mode 100644 index 0000000000..68a1322e5f --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/DeriveProofOptions.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { JsonObject } from '../../../../types' +import type { DocumentLoader, Proof } from '../../../../utils' + +/** + * Options for creating a proof + */ +export interface DeriveProofOptions { + /** + * Document outlining what statements to reveal + */ + readonly revealDocument: JsonObject + /** + * The document featuring the proof to derive from + */ + readonly document: JsonObject + /** + * The proof for the document + */ + readonly proof: Proof + /** + * Optional custom document loader + */ + // eslint-disable-next-line + documentLoader?: DocumentLoader + /** + * Optional expansion map + */ + // eslint-disable-next-line + expansionMap?: () => void + /** + * Nonce to include in the derived proof + */ + readonly nonce?: Uint8Array + /** + * Indicates whether to compact the resulting proof + */ + readonly skipProofCompaction?: boolean +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/DidDocumentPublicKey.ts b/packages/core/src/crypto/signature-suites/bbs/types/DidDocumentPublicKey.ts new file mode 100644 index 0000000000..d8a7476e1f --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/DidDocumentPublicKey.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { PublicJsonWebKey } from './JsonWebKey' + +/** + * Interface for the public key definition entry in a DID Document. + * @see https://w3c-ccg.github.io/did-spec/#public-keys + */ +export interface DidDocumentPublicKey { + /** + * Fully qualified identifier of this public key, e.g. did:example:entity.id#keys-1 + */ + readonly id: string + + /** + * The type of this public key, as defined in: https://w3c-ccg.github.io/ld-cryptosuite-registry/ + */ + readonly type: string + + /** + * The DID of the controller of this key. + */ + readonly controller?: string + + /** + * The value of the public key in Base58 format. Only one value field will be present. + */ + readonly publicKeyBase58?: string + + /** + * Public key in JWK format. + * @see https://w3c-ccg.github.io/did-spec/#public-keys + */ + readonly publicKeyJwk?: PublicJsonWebKey + + /** + * Public key in HEX format. + * @see https://w3c-ccg.github.io/did-spec/#public-keys + */ + readonly publicKeyHex?: string +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/GetProofsOptions.ts b/packages/core/src/crypto/signature-suites/bbs/types/GetProofsOptions.ts new file mode 100644 index 0000000000..5dae685de7 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/GetProofsOptions.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { JsonObject } from '../../../../types' +import type { DocumentLoader } from '../../../../utils' + +/** + * Options for getting a proof from a JSON-LD document + */ +export interface GetProofsOptions { + /** + * The JSON-LD document to extract the proofs from. + */ + readonly document: JsonObject + /** + * Optional the proof type(s) to filter the returned proofs by + */ + readonly proofType?: string | readonly string[] + /** + * Optional custom document loader + */ + documentLoader?(): DocumentLoader + /** + * Optional expansion map + */ + expansionMap?(): () => void + /** + * Optional property to indicate whether to skip compacting the resulting proof + */ + readonly skipProofCompaction?: boolean +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/GetProofsResult.ts b/packages/core/src/crypto/signature-suites/bbs/types/GetProofsResult.ts new file mode 100644 index 0000000000..d96eb8b814 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/GetProofsResult.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { JsonArray, JsonObject } from '../../../../types' + +/** + * Result for getting proofs from a JSON-LD document + */ +export interface GetProofsResult { + /** + * The JSON-LD document with the linked data proofs removed. + */ + document: JsonObject + /** + * The list of proofs that matched the requested type. + */ + proofs: JsonArray +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/GetTypeOptions.ts b/packages/core/src/crypto/signature-suites/bbs/types/GetTypeOptions.ts new file mode 100644 index 0000000000..5dd396da4b --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/GetTypeOptions.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { DocumentLoader } from '../../../../utils' + +/** + * Options for getting the type from a JSON-LD document + */ +export interface GetTypeOptions { + /** + * Optional custom document loader + */ + // eslint-disable-next-line + documentLoader?: DocumentLoader + /** + * Optional expansion map + */ + // eslint-disable-next-line + expansionMap?: () => void +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/JsonWebKey.ts b/packages/core/src/crypto/signature-suites/bbs/types/JsonWebKey.ts new file mode 100644 index 0000000000..a027778879 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/JsonWebKey.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum JwkKty { + OctetKeyPair = 'OKP', + EC = 'EC', + RSA = 'RSA', +} + +export interface JwkEc { + readonly kty: JwkKty.EC + readonly crv: string + readonly d?: string + readonly x?: string + readonly y?: string + readonly kid?: string +} + +export interface JwkOctetKeyPair { + readonly kty: JwkKty.OctetKeyPair + readonly crv: string + readonly d?: string + readonly x?: string + readonly y?: string + readonly kid?: string +} + +export interface JwkRsa { + readonly kty: JwkKty.RSA + readonly e: string + readonly n: string +} + +export interface JwkRsaPrivate extends JwkRsa { + readonly d: string + readonly p: string + readonly q: string + readonly dp: string + readonly dq: string + readonly qi: string +} +export type JsonWebKey = JwkOctetKeyPair | JwkEc | JwkRsa | JwkRsaPrivate +export type PublicJsonWebKey = JwkOctetKeyPair | JwkEc | JwkRsa diff --git a/packages/core/src/crypto/signature-suites/bbs/types/KeyPairOptions.ts b/packages/core/src/crypto/signature-suites/bbs/types/KeyPairOptions.ts new file mode 100644 index 0000000000..624029cd9c --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/KeyPairOptions.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Options for constructing a key pair + */ +export interface KeyPairOptions { + /** + * The key id + */ + readonly id?: string + /** + * The key controller + */ + readonly controller?: string + /** + * Base58 encoding of the private key + */ + readonly privateKeyBase58?: string + /** + * Base58 encoding of the public key + */ + readonly publicKeyBase58: string +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/KeyPairSigner.ts b/packages/core/src/crypto/signature-suites/bbs/types/KeyPairSigner.ts new file mode 100644 index 0000000000..2aaa37f7cf --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/KeyPairSigner.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Key pair signer + */ +export interface KeyPairSigner { + /** + * Signer function + */ + readonly sign: (options: KeyPairSignerOptions) => Promise +} + +/** + * Key pair signer options + */ +export interface KeyPairSignerOptions { + readonly data: Uint8Array | Uint8Array[] +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/KeyPairVerifier.ts b/packages/core/src/crypto/signature-suites/bbs/types/KeyPairVerifier.ts new file mode 100644 index 0000000000..ed89f3bffe --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/KeyPairVerifier.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Key pair verifier + */ +export interface KeyPairVerifier { + /** + * Key pair verify function + */ + readonly verify: (options: KeyPairVerifierOptions) => Promise +} + +/** + * Key pair verifier options + */ +export interface KeyPairVerifierOptions { + readonly data: Uint8Array | Uint8Array[] + readonly signature: Uint8Array +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/SignatureSuiteOptions.ts b/packages/core/src/crypto/signature-suites/bbs/types/SignatureSuiteOptions.ts new file mode 100644 index 0000000000..d3f2ba95ba --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/SignatureSuiteOptions.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { JsonArray } from '../../../../types' +import type { LdKeyPair } from '../../../LdKeyPair' +import type { KeyPairSigner } from './KeyPairSigner' +import type { Bls12381G2KeyPair } from '@mattrglobal/bls12381-key-pair' + +/** + * Options for constructing a signature suite + */ +export interface SignatureSuiteOptions { + /** + * An optional signer interface for handling the sign operation + */ + readonly signer?: KeyPairSigner + /** + * The key pair used to generate the proof + */ + readonly key?: Bls12381G2KeyPair + /** + * A key id URL to the paired public key used for verifying the proof + */ + readonly verificationMethod?: string + /** + * The `created` date to report in generated proofs + */ + readonly date?: string | Date + /** + * Indicates whether to use the native implementation + * of RDF Dataset Normalization + */ + readonly useNativeCanonize?: boolean + /** + * Additional proof elements + */ + readonly proof?: JsonArray + /** + * Linked Data Key class implementation + */ + readonly LDKeyClass?: LdKeyPair +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/SuiteSignOptions.ts b/packages/core/src/crypto/signature-suites/bbs/types/SuiteSignOptions.ts new file mode 100644 index 0000000000..9bed5d5644 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/SuiteSignOptions.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { JsonObject } from '../../../../types' +import type { DocumentLoader } from '../../../../utils' + +/** + * Options for signing using a signature suite + */ +export interface SuiteSignOptions { + /** + * Input document to sign + */ + readonly document: JsonObject + /** + * Optional custom document loader + */ + documentLoader?: DocumentLoader + /** + * Optional expansion map + */ + expansionMap?: () => void + /** + * The array of statements to sign + */ + readonly verifyData: readonly Uint8Array[] + /** + * The proof + */ + readonly proof: JsonObject +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/VerifyProofOptions.ts b/packages/core/src/crypto/signature-suites/bbs/types/VerifyProofOptions.ts new file mode 100644 index 0000000000..ba3538e7d4 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/VerifyProofOptions.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ProofPurpose } from '../../../../modules/vc/proof-purposes/ProofPurpose' +import type { JsonObject } from '../../../../types' +import type { DocumentLoader, Proof } from '../../../../utils' + +/** + * Options for verifying a proof + */ +export interface VerifyProofOptions { + /** + * The proof + */ + readonly proof: Proof + /** + * The document + */ + readonly document: JsonObject + /** + * The proof purpose to specify for the generated proof + */ + readonly purpose: ProofPurpose + /** + * Optional custom document loader + */ + documentLoader?: DocumentLoader + /** + * Optional expansion map + */ + expansionMap?: () => void +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/VerifyProofResult.ts b/packages/core/src/crypto/signature-suites/bbs/types/VerifyProofResult.ts new file mode 100644 index 0000000000..96996d006d --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/VerifyProofResult.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Result of calling verify proof + */ +export interface VerifyProofResult { + /** + * A boolean indicating if the verification was successful + */ + readonly verified: boolean + /** + * A string representing the error if the verification failed + */ + readonly error?: unknown +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/VerifySignatureOptions.ts b/packages/core/src/crypto/signature-suites/bbs/types/VerifySignatureOptions.ts new file mode 100644 index 0000000000..02eb2c54b1 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/VerifySignatureOptions.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { JsonObject } from '../../../../types' +import type { DocumentLoader, Proof, VerificationMethod } from '../../../../utils' + +/** + * Options for verifying a signature + */ +export interface VerifySignatureOptions { + /** + * Document to verify + */ + readonly document: JsonObject + /** + * Array of statements to verify + */ + readonly verifyData: Uint8Array[] + /** + * Verification method to verify the signature against + */ + readonly verificationMethod: VerificationMethod + /** + * Proof to verify + */ + readonly proof: Proof + /** + * Optional custom document loader + */ + documentLoader?: DocumentLoader + /** + * Optional expansion map + */ + expansionMap?: () => void +} diff --git a/packages/core/src/crypto/signature-suites/bbs/types/index.ts b/packages/core/src/crypto/signature-suites/bbs/types/index.ts new file mode 100644 index 0000000000..f3436316be --- /dev/null +++ b/packages/core/src/crypto/signature-suites/bbs/types/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2020 - MATTR Limited + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { KeyPairOptions } from './KeyPairOptions' +export { KeyPairSigner } from './KeyPairSigner' +export { KeyPairVerifier } from './KeyPairVerifier' +export { SignatureSuiteOptions } from './SignatureSuiteOptions' +export { CreateProofOptions } from './CreateProofOptions' +export { VerifyProofOptions } from './VerifyProofOptions' +export { CanonizeOptions } from './CanonizeOptions' +export { CreateVerifyDataOptions } from './CreateVerifyDataOptions' +export { VerifySignatureOptions } from './VerifySignatureOptions' +export { SuiteSignOptions } from './SuiteSignOptions' +export { DeriveProofOptions } from './DeriveProofOptions' +export { DidDocumentPublicKey } from './DidDocumentPublicKey' +export { GetProofsOptions } from './GetProofsOptions' +export { GetProofsResult } from './GetProofsResult' +export { GetTypeOptions } from './GetTypeOptions' diff --git a/packages/core/src/crypto/signature-suites/ed25519/Ed25519Signature2018.ts b/packages/core/src/crypto/signature-suites/ed25519/Ed25519Signature2018.ts new file mode 100644 index 0000000000..d32f747056 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/ed25519/Ed25519Signature2018.ts @@ -0,0 +1,223 @@ +import type { DocumentLoader, JsonLdDoc, Proof, VerificationMethod } from '../../../utils' +import type { JwsLinkedDataSignatureOptions } from '../JwsLinkedDataSignature' + +import jsonld from '../../../../types/jsonld' +import { CREDENTIALS_CONTEXT_V1_URL, SECURITY_CONTEXT_URL } from '../../../modules/vc/constants' +import { TypedArrayEncoder, MultiBaseEncoder, _includesContext } from '../../../utils' +import { JwsLinkedDataSignature } from '../JwsLinkedDataSignature' + +import { ED25519_SUITE_CONTEXT_URL_2018, ED25519_SUITE_CONTEXT_URL_2020 } from './constants' +import { ed25519Signature2018Context } from './context' + +type Ed25519Signature2018Options = Pick< + JwsLinkedDataSignatureOptions, + 'key' | 'proof' | 'date' | 'useNativeCanonize' | 'LDKeyClass' +> + +export class Ed25519Signature2018 extends JwsLinkedDataSignature { + public static CONTEXT_URL = ED25519_SUITE_CONTEXT_URL_2018 + public static CONTEXT = ed25519Signature2018Context.get(ED25519_SUITE_CONTEXT_URL_2018) + + /** + * @param {object} options - Options hashmap. + * + * Either a `key` OR at least one of `signer`/`verifier` is required. + * + * @param {object} [options.key] - An optional key object (containing an + * `id` property, and either `signer` or `verifier`, depending on the + * intended operation. Useful for when the application is managing keys + * itself (when using a KMS, you never have access to the private key, + * and so should use the `signer` param instead). + * @param {Function} [options.signer] - Signer function that returns an + * object with an async sign() method. This is useful when interfacing + * with a KMS (since you don't get access to the private key and its + * `signer()`, the KMS client gives you only the signer function to use). + * @param {Function} [options.verifier] - Verifier function that returns + * an object with an async `verify()` method. Useful when working with a + * KMS-provided verifier function. + * + * Advanced optional parameters and overrides. + * + * @param {object} [options.proof] - A JSON-LD document with options to use + * for the `proof` node. Any other custom fields can be provided here + * using a context different from security-v2). + * @param {string|Date} [options.date] - Signing date to use if not passed. + * @param {boolean} [options.useNativeCanonize] - Whether to use a native + * canonize algorithm. + */ + public constructor(options: Ed25519Signature2018Options) { + super({ + type: 'Ed25519Signature2018', + algorithm: 'EdDSA', + LDKeyClass: options.LDKeyClass, + contextUrl: ED25519_SUITE_CONTEXT_URL_2018, + key: options.key, + proof: options.proof, + date: options.date, + useNativeCanonize: options.useNativeCanonize, + }) + this.requiredKeyType = 'Ed25519VerificationKey2018' + } + + public async assertVerificationMethod(document: JsonLdDoc) { + if (!_includesCompatibleContext({ document: document })) { + // For DID Documents, since keys do not have their own contexts, + // the suite context is usually provided by the documentLoader logic + throw new TypeError(`The verification method (key) must contain "${this.contextUrl}".`) + } + + if (!(_isEd2018Key(document) || _isEd2020Key(document))) { + throw new Error(`Invalid key type. Key type must be "${this.requiredKeyType}".`) + } + + // ensure verification method has not been revoked + if (document.revoked !== undefined) { + throw new Error('The verification method has been revoked.') + } + } + + public async getVerificationMethod(options: { proof: Proof; documentLoader?: DocumentLoader }) { + let verificationMethod = await super.getVerificationMethod({ + proof: options.proof, + documentLoader: options.documentLoader, + }) + + // convert Ed25519VerificationKey2020 to Ed25519VerificationKey2018 + if (_isEd2020Key(verificationMethod)) { + // -- convert multibase to base58 -- + const publicKeyBuffer = MultiBaseEncoder.decode(verificationMethod.publicKeyMultibase) + + // -- update context -- + // remove 2020 context + const context2020Index = verificationMethod['@context'].indexOf(ED25519_SUITE_CONTEXT_URL_2020) + verificationMethod['@context'].splice(context2020Index, 1) + + // add 2018 context + verificationMethod['@context'].push(ED25519_SUITE_CONTEXT_URL_2018) + + // -- update type + verificationMethod.type = 'Ed25519VerificationKey2018' + + verificationMethod = { + ...verificationMethod, + publicKeyMultibase: undefined, + publicKeyBase58: TypedArrayEncoder.toBase58(publicKeyBuffer.data), + } + } + + return verificationMethod + } + + /** + * Ensures the document to be signed contains the required signature suite + * specific `@context`, by either adding it (if `addSuiteContext` is true), + * or throwing an error if it's missing. + * + * @override + * + * @param {object} options - Options hashmap. + * @param {object} options.document - JSON-LD document to be signed. + * @param {boolean} options.addSuiteContext - Add suite context? + */ + public ensureSuiteContext(options: { document: JsonLdDoc; addSuiteContext: boolean }) { + if (_includesCompatibleContext({ document: options.document })) { + return + } + + super.ensureSuiteContext({ document: options.document, addSuiteContext: options.addSuiteContext }) + } + + /** + * Checks whether a given proof exists in the document. + * + * @override + * + * @param {object} options - Options hashmap. + * @param {object} options.proof - A proof. + * @param {object} options.document - A JSON-LD document. + * @param {object} options.purpose - A jsonld-signatures ProofPurpose + * instance (e.g. AssertionProofPurpose, AuthenticationProofPurpose, etc). + * @param {Function} options.documentLoader - A secure document loader (it is + * recommended to use one that provides static known documents, instead of + * fetching from the web) for returning contexts, controller documents, + * keys, and other relevant URLs needed for the proof. + * @param {Function} [options.expansionMap] - A custom expansion map that is + * passed to the JSON-LD processor; by default a function that will throw + * an error when unmapped properties are detected in the input, use `false` + * to turn this off and allow unmapped properties to be dropped or use a + * custom function. + * + * @returns {Promise} Whether a match for the proof was found. + */ + public async matchProof(options: { + proof: Proof + document: VerificationMethod + // eslint-disable-next-line @typescript-eslint/no-explicit-any + purpose: any + documentLoader?: DocumentLoader + expansionMap?: () => void + }) { + if (!_includesCompatibleContext({ document: options.document })) { + return false + } + return super.matchProof({ + proof: options.proof, + document: options.document, + purpose: options.purpose, + documentLoader: options.documentLoader, + expansionMap: options.expansionMap, + }) + } +} + +function _includesCompatibleContext(options: { document: JsonLdDoc }) { + // Handle the unfortunate Ed25519Signature2018 / credentials/v1 collision + const hasEd2018 = _includesContext({ + document: options.document, + contextUrl: ED25519_SUITE_CONTEXT_URL_2018, + }) + const hasEd2020 = _includesContext({ + document: options.document, + contextUrl: ED25519_SUITE_CONTEXT_URL_2020, + }) + const hasCred = _includesContext({ document: options.document, contextUrl: CREDENTIALS_CONTEXT_V1_URL }) + const hasSecV2 = _includesContext({ document: options.document, contextUrl: SECURITY_CONTEXT_URL }) + + // TODO: the console.warn statements below should probably be replaced with logging statements. However, this would currently require injection and I'm not sure we want to do that. + if (hasEd2018 && hasCred) { + // Warn if both are present + // console.warn('Warning: The ed25519-2018/v1 and credentials/v1 ' + 'contexts are incompatible.') + // console.warn('For VCs using Ed25519Signature2018 suite,' + ' using the credentials/v1 context is sufficient.') + return false + } + + if (hasEd2018 && hasSecV2) { + // Warn if both are present + // console.warn('Warning: The ed25519-2018/v1 and security/v2 ' + 'contexts are incompatible.') + // console.warn('For VCs using Ed25519Signature2018 suite,' + ' using the security/v2 context is sufficient.') + return false + } + + // Either one by itself is fine, for this suite + return hasEd2018 || hasEd2020 || hasCred || hasSecV2 +} + +function _isEd2018Key(verificationMethod: JsonLdDoc) { + const hasEd2018 = _includesContext({ + document: verificationMethod, + contextUrl: ED25519_SUITE_CONTEXT_URL_2018, + }) + + // @ts-ignore - .hasValue is not part of the public API + return hasEd2018 && jsonld.hasValue(verificationMethod, 'type', 'Ed25519VerificationKey2018') +} + +function _isEd2020Key(verificationMethod: JsonLdDoc) { + const hasEd2020 = _includesContext({ + document: verificationMethod, + contextUrl: ED25519_SUITE_CONTEXT_URL_2020, + }) + + // @ts-ignore - .hasValue is not part of the public API + return hasEd2020 && jsonld.hasValue(verificationMethod, 'type', 'Ed25519VerificationKey2020') +} diff --git a/packages/core/src/crypto/signature-suites/ed25519/constants.ts b/packages/core/src/crypto/signature-suites/ed25519/constants.ts new file mode 100644 index 0000000000..881a7ea3b1 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/ed25519/constants.ts @@ -0,0 +1,2 @@ +export const ED25519_SUITE_CONTEXT_URL_2018 = 'https://w3id.org/security/suites/ed25519-2018/v1' +export const ED25519_SUITE_CONTEXT_URL_2020 = 'https://w3id.org/security/suites/ed25519-2020/v1' diff --git a/packages/core/src/crypto/signature-suites/ed25519/context.ts b/packages/core/src/crypto/signature-suites/ed25519/context.ts new file mode 100644 index 0000000000..1f2c6af92c --- /dev/null +++ b/packages/core/src/crypto/signature-suites/ed25519/context.ts @@ -0,0 +1,99 @@ +import { ED25519_SUITE_CONTEXT_URL_2018 } from './constants' + +export const context = { + '@context': { + id: '@id', + type: '@type', + '@protected': true, + + proof: { + '@id': 'https://w3id.org/security#proof', + '@type': '@id', + '@container': '@graph', + }, + Ed25519VerificationKey2018: { + '@id': 'https://w3id.org/security#Ed25519VerificationKey2018', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + controller: { + '@id': 'https://w3id.org/security#controller', + '@type': '@id', + }, + revoked: { + '@id': 'https://w3id.org/security#revoked', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + publicKeyBase58: { + '@id': 'https://w3id.org/security#publicKeyBase58', + }, + }, + }, + Ed25519Signature2018: { + '@id': 'https://w3id.org/security#Ed25519Signature2018', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + challenge: 'https://w3id.org/security#challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + domain: 'https://w3id.org/security#domain', + expires: { + '@id': 'https://w3id.org/security#expiration', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + nonce: 'https://w3id.org/security#nonce', + proofPurpose: { + '@id': 'https://w3id.org/security#proofPurpose', + '@type': '@vocab', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'https://w3id.org/security#capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityDelegation: { + '@id': 'https://w3id.org/security#capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + keyAgreement: { + '@id': 'https://w3id.org/security#keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + jws: { + '@id': 'https://w3id.org/security#jws', + }, + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + '@type': '@id', + }, + }, + }, + }, +} + +const ed25519Signature2018Context = new Map() +ed25519Signature2018Context.set(ED25519_SUITE_CONTEXT_URL_2018, context) + +export { ed25519Signature2018Context } diff --git a/packages/core/src/crypto/signature-suites/index.ts b/packages/core/src/crypto/signature-suites/index.ts new file mode 100644 index 0000000000..7eecb7ef25 --- /dev/null +++ b/packages/core/src/crypto/signature-suites/index.ts @@ -0,0 +1,2 @@ +export * from './ed25519/Ed25519Signature2018' +export * from './JwsLinkedDataSignature' diff --git a/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts b/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts index e0771577df..694987c059 100644 --- a/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts +++ b/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts @@ -4,8 +4,10 @@ import type { ParsedDid, DidResolutionResult } from '../../types' import { convertPublicKeyToX25519 } from '@stablelib/ed25519' +import { ED25519_SUITE_CONTEXT_URL_2018 } from '../../../../crypto/signature-suites/ed25519/constants' import { TypedArrayEncoder } from '../../../../utils/TypedArrayEncoder' import { getFullVerkey } from '../../../../utils/did' +import { SECURITY_X25519_CONTEXT_URL } from '../../../vc/constants' import { DidDocumentService } from '../../domain' import { DidDocumentBuilder } from '../../domain/DidDocumentBuilder' import { DidCommV1Service } from '../../domain/service/DidCommV1Service' @@ -36,8 +38,9 @@ export class SovDidResolver implements DidResolver { ) const builder = new DidDocumentBuilder(parsed.did) - .addContext('https://w3id.org/security/suites/ed25519-2018/v1') - .addContext('https://w3id.org/security/suites/x25519-2019/v1') + + .addContext(ED25519_SUITE_CONTEXT_URL_2018) + .addContext(SECURITY_X25519_CONTEXT_URL) .addVerificationMethod({ controller: parsed.did, id: verificationMethodId, diff --git a/packages/core/src/modules/vc/SignatureSuiteRegistry.ts b/packages/core/src/modules/vc/SignatureSuiteRegistry.ts new file mode 100644 index 0000000000..469d0a4aaf --- /dev/null +++ b/packages/core/src/modules/vc/SignatureSuiteRegistry.ts @@ -0,0 +1,55 @@ +import { suites } from '../../../types/jsonld-signatures' +import { KeyType } from '../../crypto' +import { Ed25519Signature2018 } from '../../crypto/signature-suites' +import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from '../../crypto/signature-suites/bbs' +import { AriesFrameworkError } from '../../error' + +const LinkedDataSignature = suites.LinkedDataSignature + +export interface SuiteInfo { + suiteClass: typeof LinkedDataSignature + proofType: string + requiredKeyType: string + keyType: string +} + +export class SignatureSuiteRegistry { + private suiteMapping: SuiteInfo[] = [ + { + suiteClass: Ed25519Signature2018, + proofType: 'Ed25519Signature2018', + requiredKeyType: 'Ed25519VerificationKey2018', + keyType: KeyType.Ed25519, + }, + { + suiteClass: BbsBlsSignature2020, + proofType: 'BbsBlsSignature2020', + requiredKeyType: 'BbsBlsSignatureProof2020', + keyType: KeyType.Bls12381g2, + }, + { + suiteClass: BbsBlsSignatureProof2020, + proofType: 'BbsBlsSignatureProof2020', + requiredKeyType: 'BbsBlsSignatureProof2020', + keyType: KeyType.Bls12381g2, + }, + ] + + public get supportedProofTypes(): string[] { + return this.suiteMapping.map((x) => x.proofType) + } + + public getByKeyType(keyType: KeyType) { + return this.suiteMapping.find((x) => x.keyType === keyType) + } + + public getByProofType(proofType: string) { + const suiteInfo = this.suiteMapping.find((x) => x.proofType === proofType) + + if (!suiteInfo) { + throw new AriesFrameworkError(`No signature suite for proof type: ${proofType}`) + } + + return suiteInfo + } +} diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts new file mode 100644 index 0000000000..8935126083 --- /dev/null +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -0,0 +1,381 @@ +import type { Key } from '../../crypto/Key' +import type { DocumentLoaderResult } from '../../utils' +import type { W3cVerifyCredentialResult } from './models' +import type { + CreatePresentationOptions, + DeriveProofOptions, + SignCredentialOptions, + SignPresentationOptions, + StoreCredentialOptions, + VerifyCredentialOptions, + VerifyPresentationOptions, +} from './models/W3cCredentialServiceOptions' +import type { VerifyPresentationResult } from './models/presentation/VerifyPresentationResult' + +import jsonld, { documentLoaderNode, documentLoaderXhr } from '../../../types/jsonld' +import vc from '../../../types/vc' +import { createWalletKeyPairClass } from '../../crypto/WalletKeyPair' +import { deriveProof } from '../../crypto/signature-suites/bbs' +import { AriesFrameworkError } from '../../error' +import { inject, injectable } from '../../plugins' +import { JsonTransformer, orArrayToArray, w3cDate } from '../../utils' +import { isNodeJS, isReactNative } from '../../utils/environment' +import { Wallet } from '../../wallet' +import { DidResolverService, VerificationMethod } from '../dids' +import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type' + +import { SignatureSuiteRegistry } from './SignatureSuiteRegistry' +import { W3cVerifiableCredential } from './models' +import { W3cPresentation } from './models/presentation/W3Presentation' +import { W3cVerifiablePresentation } from './models/presentation/W3cVerifiablePresentation' +import { W3cCredentialRecord, W3cCredentialRepository } from './repository' + +@injectable() +export class W3cCredentialService { + private wallet: Wallet + private w3cCredentialRepository: W3cCredentialRepository + private didResolver: DidResolverService + private suiteRegistry: SignatureSuiteRegistry + + public constructor( + @inject('Wallet') wallet: Wallet, + w3cCredentialRepository: W3cCredentialRepository, + didResolver: DidResolverService + ) { + this.wallet = wallet + this.w3cCredentialRepository = w3cCredentialRepository + this.didResolver = didResolver + this.suiteRegistry = new SignatureSuiteRegistry() + } + + /** + * Signs a credential + * + * @param credential the credential to be signed + * @returns the signed credential + */ + public async signCredential(options: SignCredentialOptions): Promise { + const WalletKeyPair = createWalletKeyPairClass(this.wallet) + + const signingKey = await this.getPublicKeyFromVerificationMethod(options.verificationMethod) + const suiteInfo = this.suiteRegistry.getByProofType(options.proofType) + + if (signingKey.keyType !== suiteInfo.keyType) { + throw new AriesFrameworkError('The key type of the verification method does not match the suite') + } + + const keyPair = new WalletKeyPair({ + controller: options.credential.issuerId, // should we check this against the verificationMethod.controller? + id: options.verificationMethod, + key: signingKey, + wallet: this.wallet, + }) + + const SuiteClass = suiteInfo.suiteClass + + const suite = new SuiteClass({ + key: keyPair, + LDKeyClass: WalletKeyPair, + proof: { + verificationMethod: options.verificationMethod, + }, + useNativeCanonize: false, + date: options.created ?? w3cDate(), + }) + + const result = await vc.issue({ + credential: JsonTransformer.toJSON(options.credential), + suite: suite, + purpose: options.proofPurpose, + documentLoader: this.documentLoader, + }) + + return JsonTransformer.fromJSON(result, W3cVerifiableCredential) + } + + /** + * Verifies the signature(s) of a credential + * + * @param credential the credential to be verified + * @returns the verification result + */ + public async verifyCredential(options: VerifyCredentialOptions): Promise { + const suites = this.getSignatureSuitesForCredential(options.credential) + + const verifyOptions: Record = { + credential: JsonTransformer.toJSON(options.credential), + suite: suites, + documentLoader: this.documentLoader, + } + + // this is a hack because vcjs throws if purpose is passed as undefined or null + if (options.proofPurpose) { + verifyOptions['purpose'] = options.proofPurpose + } + + const result = await vc.verifyCredential(verifyOptions) + + return result as unknown as W3cVerifyCredentialResult + } + + /** + * Utility method that creates a {@link W3cPresentation} from one or more {@link W3cVerifiableCredential}s. + * + * **NOTE: the presentation that is returned is unsigned.** + * + * @param credentials One or more instances of {@link W3cVerifiableCredential} + * @param [id] an optional unique identifier for the presentation + * @param [holderUrl] an optional identifier identifying the entity that is generating the presentation + * @returns An instance of {@link W3cPresentation} + */ + public async createPresentation(options: CreatePresentationOptions): Promise { + if (!Array.isArray(options.credentials)) { + options.credentials = [options.credentials] + } + + const presentationJson = vc.createPresentation({ + verifiableCredential: options.credentials.map((credential) => JsonTransformer.toJSON(credential)), + id: options.id, + holder: options.holderUrl, + }) + + return JsonTransformer.fromJSON(presentationJson, W3cPresentation) + } + + /** + * Signs a presentation including the credentials it includes + * + * @param presentation the presentation to be signed + * @returns the signed presentation + */ + public async signPresentation(options: SignPresentationOptions): Promise { + // create keyPair + const WalletKeyPair = createWalletKeyPairClass(this.wallet) + + const suiteInfo = this.suiteRegistry.getByProofType(options.signatureType) + + if (!suiteInfo) { + throw new AriesFrameworkError(`The requested proofType ${options.signatureType} is not supported`) + } + + const signingKey = await this.getPublicKeyFromVerificationMethod(options.verificationMethod) + + if (signingKey.keyType !== suiteInfo.keyType) { + throw new AriesFrameworkError('The key type of the verification method does not match the suite') + } + + const verificationMethodObject = (await this.documentLoader(options.verificationMethod)).document as Record< + string, + unknown + > + + const keyPair = new WalletKeyPair({ + controller: verificationMethodObject['controller'] as string, + id: options.verificationMethod, + key: signingKey, + wallet: this.wallet, + }) + + const suite = new suiteInfo.suiteClass({ + LDKeyClass: WalletKeyPair, + proof: { + verificationMethod: options.verificationMethod, + }, + date: new Date().toISOString(), + key: keyPair, + useNativeCanonize: false, + }) + + const result = await vc.signPresentation({ + presentation: JsonTransformer.toJSON(options.presentation), + suite: suite, + challenge: options.challenge, + documentLoader: this.documentLoader, + }) + + return JsonTransformer.fromJSON(result, W3cVerifiablePresentation) + } + + /** + * Verifies a presentation including the credentials it includes + * + * @param presentation the presentation to be verified + * @returns the verification result + */ + public async verifyPresentation(options: VerifyPresentationOptions): Promise { + // create keyPair + const WalletKeyPair = createWalletKeyPairClass(this.wallet) + + let proofs = options.presentation.proof + + if (!Array.isArray(proofs)) { + proofs = [proofs] + } + if (options.purpose) { + proofs = proofs.filter((proof) => proof.proofPurpose === options.purpose.term) + } + + const presentationSuites = proofs.map((proof) => { + const SuiteClass = this.suiteRegistry.getByProofType(proof.type).suiteClass + return new SuiteClass({ + LDKeyClass: WalletKeyPair, + proof: { + verificationMethod: proof.verificationMethod, + }, + date: proof.created, + useNativeCanonize: false, + }) + }) + + const credentials = Array.isArray(options.presentation.verifiableCredential) + ? options.presentation.verifiableCredential + : [options.presentation.verifiableCredential] + + const credentialSuites = credentials.map((credential) => this.getSignatureSuitesForCredential(credential)) + const allSuites = presentationSuites.concat(...credentialSuites) + + const verifyOptions: Record = { + presentation: JsonTransformer.toJSON(options.presentation), + suite: allSuites, + challenge: options.challenge, + documentLoader: this.documentLoader, + } + + // this is a hack because vcjs throws if purpose is passed as undefined or null + if (options.purpose) { + verifyOptions['presentationPurpose'] = options.purpose + } + + const result = await vc.verify(verifyOptions) + + return result as unknown as VerifyPresentationResult + } + + public async deriveProof(options: DeriveProofOptions): Promise { + const suiteInfo = this.suiteRegistry.getByProofType('BbsBlsSignatureProof2020') + const SuiteClass = suiteInfo.suiteClass + + const suite = new SuiteClass() + + const proof = await deriveProof(JsonTransformer.toJSON(options.credential), options.revealDocument, { + suite: suite, + documentLoader: this.documentLoader, + }) + + return proof + } + + public documentLoader = async (url: string): Promise => { + if (url.startsWith('did:')) { + const result = await this.didResolver.resolve(url) + + if (result.didResolutionMetadata.error || !result.didDocument) { + throw new AriesFrameworkError(`Unable to resolve DID: ${url}`) + } + + const framed = await jsonld.frame(result.didDocument.toJSON(), { + '@context': result.didDocument.context, + '@embed': '@never', + id: url, + }) + + return { + contextUrl: null, + documentUrl: url, + document: framed, + } + } + + let loader + + if (isNodeJS()) { + loader = documentLoaderNode.apply(jsonld, []) + } else if (isReactNative()) { + loader = documentLoaderXhr.apply(jsonld, []) + } else { + throw new AriesFrameworkError('Unsupported environment') + } + + return await loader(url) + } + + private async getPublicKeyFromVerificationMethod(verificationMethod: string): Promise { + const verificationMethodObject = await this.documentLoader(verificationMethod) + const verificationMethodClass = JsonTransformer.fromJSON(verificationMethodObject.document, VerificationMethod) + + const key = getKeyDidMappingByVerificationMethod(verificationMethodClass) + + return key.getKeyFromVerificationMethod(verificationMethodClass) + } + + /** + * Writes a credential to storage + * + * @param record the credential to be stored + * @returns the credential record that was written to storage + */ + public async storeCredential(options: StoreCredentialOptions): Promise { + // Get the expanded types + const expandedTypes = ( + await jsonld.expand(JsonTransformer.toJSON(options.record), { documentLoader: this.documentLoader }) + )[0]['@type'] + + // Create an instance of the w3cCredentialRecord + const w3cCredentialRecord = new W3cCredentialRecord({ + tags: { expandedTypes: orArrayToArray(expandedTypes) }, + credential: options.record, + }) + + // Store the w3c credential record + await this.w3cCredentialRepository.save(w3cCredentialRecord) + + return w3cCredentialRecord + } + + public async getAllCredentials(): Promise { + const allRecords = await this.w3cCredentialRepository.getAll() + return allRecords.map((record) => record.credential) + } + + public async getCredentialById(id: string): Promise { + return (await this.w3cCredentialRepository.getById(id)).credential + } + + public async findCredentialsByQuery( + query: Parameters[0] + ): Promise { + const result = await this.w3cCredentialRepository.findByQuery(query) + return result.map((record) => record.credential) + } + + public async findSingleCredentialByQuery( + query: Parameters[0] + ): Promise { + const result = await this.w3cCredentialRepository.findSingleByQuery(query) + return result?.credential + } + + private getSignatureSuitesForCredential(credential: W3cVerifiableCredential) { + const WalletKeyPair = createWalletKeyPairClass(this.wallet) + + let proofs = credential.proof + + if (!Array.isArray(proofs)) { + proofs = [proofs] + } + + return proofs.map((proof) => { + const SuiteClass = this.suiteRegistry.getByProofType(proof.type)?.suiteClass + if (SuiteClass) { + return new SuiteClass({ + LDKeyClass: WalletKeyPair, + proof: { + verificationMethod: proof.verificationMethod, + }, + date: proof.created, + useNativeCanonize: false, + }) + } + }) + } +} diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts new file mode 100644 index 0000000000..401d158e51 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -0,0 +1,420 @@ +import type { AgentConfig } from '../../../agent/AgentConfig' + +import { getAgentConfig } from '../../../../tests/helpers' +import { purposes } from '../../../../types/jsonld-signatures' +import { KeyType } from '../../../crypto' +import { Key } from '../../../crypto/Key' +import { JsonTransformer, orArrayToArray } from '../../../utils' +import { IndyWallet } from '../../../wallet/IndyWallet' +import { WalletError } from '../../../wallet/error' +import { DidKey, DidResolverService } from '../../dids' +import { DidRepository } from '../../dids/repository' +import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' +import { W3cCredentialService } from '../W3cCredentialService' +import { W3cCredential, W3cVerifiableCredential } from '../models' +import { LinkedDataProof } from '../models/LinkedDataProof' +import { W3cPresentation } from '../models/presentation/W3Presentation' +import { W3cVerifiablePresentation } from '../models/presentation/W3cVerifiablePresentation' +import { CredentialIssuancePurpose } from '../proof-purposes/CredentialIssuancePurpose' +import { W3cCredentialRepository } from '../repository/W3cCredentialRepository' + +import { customDocumentLoader } from './documentLoader' +import { BbsBlsSignature2020Fixtures, Ed25519Signature2018Fixtures } from './fixtures' + +jest.mock('../../ledger/services/IndyLedgerService') + +const IndyLedgerServiceMock = IndyLedgerService as jest.Mock +const DidRepositoryMock = DidRepository as unknown as jest.Mock + +jest.mock('../repository/W3cCredentialRepository') +const W3cCredentialRepositoryMock = W3cCredentialRepository as jest.Mock + +describe('W3cCredentialService', () => { + let wallet: IndyWallet + let agentConfig: AgentConfig + let didResolverService: DidResolverService + let w3cCredentialService: W3cCredentialService + let w3cCredentialRepository: W3cCredentialRepository + const seed = 'testseed000000000000000000000001' + + beforeAll(async () => { + agentConfig = getAgentConfig('W3cCredentialServiceTest') + wallet = new IndyWallet(agentConfig) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await wallet.createAndOpen(agentConfig.walletConfig!) + didResolverService = new DidResolverService(agentConfig, new IndyLedgerServiceMock(), new DidRepositoryMock()) + w3cCredentialRepository = new W3cCredentialRepositoryMock() + w3cCredentialService = new W3cCredentialService(wallet, w3cCredentialRepository, didResolverService) + w3cCredentialService.documentLoader = customDocumentLoader + }) + + afterAll(async () => { + await wallet.delete() + }) + + describe('Ed25519Signature2018', () => { + let issuerDidKey: DidKey + let verificationMethod: string + beforeAll(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const issuerDidInfo = await wallet.createDid({ seed }) + const issuerKey = Key.fromPublicKeyBase58(issuerDidInfo.verkey, KeyType.Ed25519) + issuerDidKey = new DidKey(issuerKey) + verificationMethod = `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}` + }) + describe('signCredential', () => { + it('should return a successfully signed credential', async () => { + const credentialJson = Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT + + const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) + + const vc = await w3cCredentialService.signCredential({ + credential, + proofType: 'Ed25519Signature2018', + verificationMethod: verificationMethod, + }) + + expect(vc).toBeInstanceOf(W3cVerifiableCredential) + expect(vc.issuer).toEqual(issuerDidKey.did) + expect(Array.isArray(vc.proof)).toBe(false) + expect(vc.proof).toBeInstanceOf(LinkedDataProof) + + // @ts-ignore + expect(vc.proof.verificationMethod).toEqual(verificationMethod) + }) + + it('should throw because of verificationMethod does not belong to this wallet', async () => { + const credentialJson = Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT + credentialJson.issuer = issuerDidKey.did + + const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) + + expect(async () => { + await w3cCredentialService.signCredential({ + credential, + proofType: 'Ed25519Signature2018', + verificationMethod: + 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV#z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV', + }) + }).rejects.toThrowError(WalletError) + }) + }) + describe('verifyCredential', () => { + it('should credential verify successfully', async () => { + const vc = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + W3cVerifiableCredential + ) + const result = await w3cCredentialService.verifyCredential({ credential: vc }) + + expect(result.verified).toBe(true) + expect(result.error).toBeUndefined() + + expect(result.results.length).toBe(1) + + expect(result.results[0].verified).toBe(true) + expect(result.results[0].error).toBeUndefined() + }) + it('should fail because of invalid signature', async () => { + const vc = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_BAD_SIGNED, + W3cVerifiableCredential + ) + const result = await w3cCredentialService.verifyCredential({ credential: vc }) + + expect(result.verified).toBe(false) + expect(result.error).toBeDefined() + + // @ts-ignore + expect(result.error.errors[0]).toBeInstanceOf(Error) + // @ts-ignore + expect(result.error.errors[0].message).toBe('Invalid signature.') + }) + it('should fail because of an unsigned statement', async () => { + const vcJson = { + ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + credentialSubject: { + ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED.credentialSubject, + alumniOf: 'oops', + }, + } + + const vc = JsonTransformer.fromJSON(vcJson, W3cVerifiableCredential) + const result = await w3cCredentialService.verifyCredential({ credential: vc }) + + expect(result.verified).toBe(false) + + // @ts-ignore + expect(result.error.errors[0]).toBeInstanceOf(Error) + // @ts-ignore + expect(result.error.errors[0].message).toBe('Invalid signature.') + }) + it('should fail because of a changed statement', async () => { + const vcJson = { + ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + credentialSubject: { + ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED.credentialSubject, + degree: { + ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED.credentialSubject.degree, + name: 'oops', + }, + }, + } + + const vc = JsonTransformer.fromJSON(vcJson, W3cVerifiableCredential) + const result = await w3cCredentialService.verifyCredential({ credential: vc }) + + expect(result.verified).toBe(false) + + // @ts-ignore + expect(result.error.errors[0]).toBeInstanceOf(Error) + // @ts-ignore + expect(result.error.errors[0].message).toBe('Invalid signature.') + }) + }) + describe('createPresentation', () => { + it('should successfully create a presentation from single verifiable credential', async () => { + const vc = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + W3cVerifiableCredential + ) + const result = await w3cCredentialService.createPresentation({ credentials: vc }) + + expect(result).toBeInstanceOf(W3cPresentation) + + expect(result.type).toEqual(expect.arrayContaining(['VerifiablePresentation'])) + + expect(result.verifiableCredential).toHaveLength(1) + expect(result.verifiableCredential).toEqual(expect.arrayContaining([vc])) + }) + it('should successfully create a presentation from two verifiable credential', async () => { + const vc1 = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + W3cVerifiableCredential + ) + const vc2 = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + W3cVerifiableCredential + ) + + const vcs = [vc1, vc2] + const result = await w3cCredentialService.createPresentation({ credentials: vcs }) + + expect(result).toBeInstanceOf(W3cPresentation) + + expect(result.type).toEqual(expect.arrayContaining(['VerifiablePresentation'])) + + expect(result.verifiableCredential).toHaveLength(2) + expect(result.verifiableCredential).toEqual(expect.arrayContaining([vc1, vc2])) + }) + }) + describe('signPresentation', () => { + it('should successfully create a presentation from single verifiable credential', async () => { + const presentation = JsonTransformer.fromJSON(Ed25519Signature2018Fixtures.TEST_VP_DOCUMENT, W3cPresentation) + + const purpose = new CredentialIssuancePurpose({ + controller: { + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + }, + date: new Date().toISOString(), + }) + + const verifiablePresentation = await w3cCredentialService.signPresentation({ + presentation: presentation, + purpose: purpose, + signatureType: 'Ed25519Signature2018', + challenge: '7bf32d0b-39d4-41f3-96b6-45de52988e4c', + verificationMethod: verificationMethod, + }) + + expect(verifiablePresentation).toBeInstanceOf(W3cVerifiablePresentation) + }) + }) + describe('verifyPresentation', () => { + it('should successfully verify a presentation containing a single verifiable credential', async () => { + const vp = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_VP_DOCUMENT_SIGNED, + W3cVerifiablePresentation + ) + + const result = await w3cCredentialService.verifyPresentation({ + presentation: vp, + proofType: 'Ed25519Signature2018', + challenge: '7bf32d0b-39d4-41f3-96b6-45de52988e4c', + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + }) + + expect(result.verified).toBe(true) + }) + }) + describe('storeCredential', () => { + it('should store a credential', async () => { + const credential = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + W3cVerifiableCredential + ) + + const w3cCredentialRecord = await w3cCredentialService.storeCredential({ record: credential }) + + expect(w3cCredentialRecord).toMatchObject({ + type: 'W3cCredentialRecord', + id: expect.any(String), + createdAt: expect.any(Date), + credential: expect.any(W3cVerifiableCredential), + }) + + expect(w3cCredentialRecord.getTags()).toMatchObject({ + expandedTypes: [ + 'https://www.w3.org/2018/credentials#VerifiableCredential', + 'https://example.org/examples#UniversityDegreeCredential', + ], + }) + }) + }) + }) + + describe('BbsBlsSignature2020', () => { + let issuerDidKey: DidKey + let verificationMethod: string + beforeAll(async () => { + const key = await wallet.createKey({ keyType: KeyType.Bls12381g2, seed }) + issuerDidKey = new DidKey(key) + verificationMethod = `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}` + }) + describe('signCredential', () => { + it('should return a successfully signed credential bbs', async () => { + const credentialJson = BbsBlsSignature2020Fixtures.TEST_LD_DOCUMENT + credentialJson.issuer = issuerDidKey.did + + const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) + + const vc = await w3cCredentialService.signCredential({ + credential, + proofType: 'BbsBlsSignature2020', + verificationMethod: verificationMethod, + }) + + expect(vc).toBeInstanceOf(W3cVerifiableCredential) + expect(vc.issuer).toEqual(issuerDidKey.did) + expect(Array.isArray(vc.proof)).toBe(false) + expect(vc.proof).toBeInstanceOf(LinkedDataProof) + + vc.proof = vc.proof as LinkedDataProof + expect(vc.proof.verificationMethod).toEqual(verificationMethod) + }) + }) + describe('verifyCredential', () => { + it('should verify the credential successfully', async () => { + const result = await w3cCredentialService.verifyCredential({ + credential: JsonTransformer.fromJSON( + BbsBlsSignature2020Fixtures.TEST_LD_DOCUMENT_SIGNED, + W3cVerifiableCredential + ), + proofPurpose: new purposes.AssertionProofPurpose(), + }) + + expect(result.verified).toEqual(true) + }) + }) + describe('deriveProof', () => { + it('should derive proof successfully', async () => { + const credentialJson = BbsBlsSignature2020Fixtures.TEST_LD_DOCUMENT_SIGNED + + const vc = JsonTransformer.fromJSON(credentialJson, W3cVerifiableCredential) + + const revealDocument = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + type: ['VerifiableCredential', 'PermanentResidentCard'], + credentialSubject: { + '@explicit': true, + type: ['PermanentResident', 'Person'], + givenName: {}, + familyName: {}, + gender: {}, + }, + } + + const result = await w3cCredentialService.deriveProof({ + credential: vc, + revealDocument: revealDocument, + verificationMethod: verificationMethod, + }) + + // result.proof = result.proof as LinkedDataProof + expect(orArrayToArray(result.proof)[0].verificationMethod).toBe( + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN' + ) + }) + }) + describe('verifyDerived', () => { + it('should verify the derived proof successfully', async () => { + const result = await w3cCredentialService.verifyCredential({ + credential: JsonTransformer.fromJSON(BbsBlsSignature2020Fixtures.TEST_VALID_DERIVED, W3cVerifiableCredential), + proofPurpose: new purposes.AssertionProofPurpose(), + }) + expect(result.verified).toEqual(true) + }) + }) + describe('createPresentation', () => { + it('should create a presentation successfully', async () => { + const vc = JsonTransformer.fromJSON(BbsBlsSignature2020Fixtures.TEST_VALID_DERIVED, W3cVerifiableCredential) + const result = await w3cCredentialService.createPresentation({ credentials: vc }) + + expect(result).toBeInstanceOf(W3cPresentation) + + expect(result.type).toEqual(expect.arrayContaining(['VerifiablePresentation'])) + + expect(result.verifiableCredential).toHaveLength(1) + expect(result.verifiableCredential).toEqual(expect.arrayContaining([vc])) + }) + }) + describe('signPresentation', () => { + it('should sign the presentation successfully', async () => { + const signingKey = Key.fromPublicKeyBase58((await wallet.createDid({ seed })).verkey, KeyType.Ed25519) + const signingDidKey = new DidKey(signingKey) + const verificationMethod = `${signingDidKey.did}#${signingDidKey.key.fingerprint}` + const presentation = JsonTransformer.fromJSON(BbsBlsSignature2020Fixtures.TEST_VP_DOCUMENT, W3cPresentation) + + const purpose = new CredentialIssuancePurpose({ + controller: { + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + }, + date: new Date().toISOString(), + }) + + const verifiablePresentation = await w3cCredentialService.signPresentation({ + presentation: presentation, + purpose: purpose, + signatureType: 'Ed25519Signature2018', + challenge: 'e950bfe5-d7ec-4303-ad61-6983fb976ac9', + verificationMethod: verificationMethod, + }) + + expect(verifiablePresentation).toBeInstanceOf(W3cVerifiablePresentation) + }) + }) + describe('verifyPresentation', () => { + it('should successfully verify a presentation containing a single verifiable credential bbs', async () => { + const vp = JsonTransformer.fromJSON( + BbsBlsSignature2020Fixtures.TEST_VP_DOCUMENT_SIGNED, + W3cVerifiablePresentation + ) + + const result = await w3cCredentialService.verifyPresentation({ + presentation: vp, + proofType: 'Ed25519Signature2018', + challenge: 'e950bfe5-d7ec-4303-ad61-6983fb976ac9', + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + }) + + expect(result.verified).toBe(true) + }) + }) + }) +}) diff --git a/packages/core/src/modules/vc/__tests__/contexts/X25519_v1.ts b/packages/core/src/modules/vc/__tests__/contexts/X25519_v1.ts new file mode 100644 index 0000000000..3a5b8bf768 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/X25519_v1.ts @@ -0,0 +1,26 @@ +export const X25519_V1 = { + '@context': { + id: '@id', + type: '@type', + '@protected': true, + X25519KeyAgreementKey2019: { + '@id': 'https://w3id.org/security#X25519KeyAgreementKey2019', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + controller: { + '@id': 'https://w3id.org/security#controller', + '@type': '@id', + }, + revoked: { + '@id': 'https://w3id.org/security#revoked', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + publicKeyBase58: { + '@id': 'https://w3id.org/security#publicKeyBase58', + }, + }, + }, + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/bbs_v1.ts b/packages/core/src/modules/vc/__tests__/contexts/bbs_v1.ts new file mode 100644 index 0000000000..d7fa000420 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/bbs_v1.ts @@ -0,0 +1,92 @@ +export const BBS_V1 = { + '@context': { + '@version': 1.1, + id: '@id', + type: '@type', + BbsBlsSignature2020: { + '@id': 'https://w3id.org/security#BbsBlsSignature2020', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + challenge: 'https://w3id.org/security#challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + domain: 'https://w3id.org/security#domain', + proofValue: 'https://w3id.org/security#proofValue', + nonce: 'https://w3id.org/security#nonce', + proofPurpose: { + '@id': 'https://w3id.org/security#proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + '@type': '@id', + }, + }, + }, + BbsBlsSignatureProof2020: { + '@id': 'https://w3id.org/security#BbsBlsSignatureProof2020', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + challenge: 'https://w3id.org/security#challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + domain: 'https://w3id.org/security#domain', + nonce: 'https://w3id.org/security#nonce', + proofPurpose: { + '@id': 'https://w3id.org/security#proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + sec: 'https://w3id.org/security#', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + proofValue: 'https://w3id.org/security#proofValue', + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + '@type': '@id', + }, + }, + }, + Bls12381G1Key2020: 'https://w3id.org/security#Bls12381G1Key2020', + Bls12381G2Key2020: 'https://w3id.org/security#Bls12381G2Key2020', + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/citizenship_v1.ts b/packages/core/src/modules/vc/__tests__/contexts/citizenship_v1.ts new file mode 100644 index 0000000000..13eb72776c --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/citizenship_v1.ts @@ -0,0 +1,45 @@ +export const CITIZENSHIP_V1 = { + '@context': { + '@version': 1.1, + '@protected': true, + name: 'http://schema.org/name', + description: 'http://schema.org/description', + identifier: 'http://schema.org/identifier', + image: { '@id': 'http://schema.org/image', '@type': '@id' }, + PermanentResidentCard: { + '@id': 'https://w3id.org/citizenship#PermanentResidentCard', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + description: 'http://schema.org/description', + name: 'http://schema.org/name', + identifier: 'http://schema.org/identifier', + image: { '@id': 'http://schema.org/image', '@type': '@id' }, + }, + }, + PermanentResident: { + '@id': 'https://w3id.org/citizenship#PermanentResident', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + ctzn: 'https://w3id.org/citizenship#', + schema: 'http://schema.org/', + xsd: 'http://www.w3.org/2001/XMLSchema#', + birthCountry: 'ctzn:birthCountry', + birthDate: { '@id': 'schema:birthDate', '@type': 'xsd:dateTime' }, + commuterClassification: 'ctzn:commuterClassification', + familyName: 'schema:familyName', + gender: 'schema:gender', + givenName: 'schema:givenName', + lprCategory: 'ctzn:lprCategory', + lprNumber: 'ctzn:lprNumber', + residentSince: { '@id': 'ctzn:residentSince', '@type': 'xsd:dateTime' }, + }, + }, + Person: 'http://schema.org/Person', + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/credentials_v1.ts b/packages/core/src/modules/vc/__tests__/contexts/credentials_v1.ts new file mode 100644 index 0000000000..f401fb33f5 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/credentials_v1.ts @@ -0,0 +1,250 @@ +export const CREDENTIALS_V1 = { + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + VerifiableCredential: { + '@id': 'https://www.w3.org/2018/credentials#VerifiableCredential', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + cred: 'https://www.w3.org/2018/credentials#', + sec: 'https://w3id.org/security#', + xsd: 'http://www.w3.org/2001/XMLSchema#', + credentialSchema: { + '@id': 'cred:credentialSchema', + '@type': '@id', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + cred: 'https://www.w3.org/2018/credentials#', + JsonSchemaValidator2018: 'cred:JsonSchemaValidator2018', + }, + }, + credentialStatus: { '@id': 'cred:credentialStatus', '@type': '@id' }, + credentialSubject: { '@id': 'cred:credentialSubject', '@type': '@id' }, + evidence: { '@id': 'cred:evidence', '@type': '@id' }, + expirationDate: { + '@id': 'cred:expirationDate', + '@type': 'xsd:dateTime', + }, + holder: { '@id': 'cred:holder', '@type': '@id' }, + issued: { '@id': 'cred:issued', '@type': 'xsd:dateTime' }, + issuer: { '@id': 'cred:issuer', '@type': '@id' }, + issuanceDate: { '@id': 'cred:issuanceDate', '@type': 'xsd:dateTime' }, + proof: { '@id': 'sec:proof', '@type': '@id', '@container': '@graph' }, + refreshService: { + '@id': 'cred:refreshService', + '@type': '@id', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + cred: 'https://www.w3.org/2018/credentials#', + ManualRefreshService2018: 'cred:ManualRefreshService2018', + }, + }, + termsOfUse: { '@id': 'cred:termsOfUse', '@type': '@id' }, + validFrom: { '@id': 'cred:validFrom', '@type': 'xsd:dateTime' }, + validUntil: { '@id': 'cred:validUntil', '@type': 'xsd:dateTime' }, + }, + }, + VerifiablePresentation: { + '@id': 'https://www.w3.org/2018/credentials#VerifiablePresentation', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + cred: 'https://www.w3.org/2018/credentials#', + sec: 'https://w3id.org/security#', + holder: { '@id': 'cred:holder', '@type': '@id' }, + proof: { '@id': 'sec:proof', '@type': '@id', '@container': '@graph' }, + verifiableCredential: { + '@id': 'cred:verifiableCredential', + '@type': '@id', + '@container': '@graph', + }, + }, + }, + EcdsaSecp256k1Signature2019: { + '@id': 'https://w3id.org/security#EcdsaSecp256k1Signature2019', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + sec: 'https://w3id.org/security#', + xsd: 'http://www.w3.org/2001/XMLSchema#', + challenge: 'sec:challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'xsd:dateTime', + }, + domain: 'sec:domain', + expires: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, + jws: 'sec:jws', + nonce: 'sec:nonce', + proofPurpose: { + '@id': 'sec:proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + sec: 'https://w3id.org/security#', + assertionMethod: { + '@id': 'sec:assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'sec:authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + proofValue: 'sec:proofValue', + verificationMethod: { '@id': 'sec:verificationMethod', '@type': '@id' }, + }, + }, + EcdsaSecp256r1Signature2019: { + '@id': 'https://w3id.org/security#EcdsaSecp256r1Signature2019', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + sec: 'https://w3id.org/security#', + xsd: 'http://www.w3.org/2001/XMLSchema#', + challenge: 'sec:challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'xsd:dateTime', + }, + domain: 'sec:domain', + expires: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, + jws: 'sec:jws', + nonce: 'sec:nonce', + proofPurpose: { + '@id': 'sec:proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + sec: 'https://w3id.org/security#', + assertionMethod: { + '@id': 'sec:assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'sec:authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + proofValue: 'sec:proofValue', + verificationMethod: { '@id': 'sec:verificationMethod', '@type': '@id' }, + }, + }, + Ed25519Signature2018: { + '@id': 'https://w3id.org/security#Ed25519Signature2018', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + sec: 'https://w3id.org/security#', + xsd: 'http://www.w3.org/2001/XMLSchema#', + challenge: 'sec:challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'xsd:dateTime', + }, + domain: 'sec:domain', + expires: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, + jws: 'sec:jws', + nonce: 'sec:nonce', + proofPurpose: { + '@id': 'sec:proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + sec: 'https://w3id.org/security#', + assertionMethod: { + '@id': 'sec:assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'sec:authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + proofValue: 'sec:proofValue', + verificationMethod: { '@id': 'sec:verificationMethod', '@type': '@id' }, + }, + }, + RsaSignature2018: { + '@id': 'https://w3id.org/security#RsaSignature2018', + '@context': { + '@version': 1.1, + '@protected': true, + challenge: 'sec:challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'xsd:dateTime', + }, + domain: 'sec:domain', + expires: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, + jws: 'sec:jws', + nonce: 'sec:nonce', + proofPurpose: { + '@id': 'sec:proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + sec: 'https://w3id.org/security#', + assertionMethod: { + '@id': 'sec:assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'sec:authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + proofValue: 'sec:proofValue', + verificationMethod: { '@id': 'sec:verificationMethod', '@type': '@id' }, + }, + }, + proof: { + '@id': 'https://w3id.org/security#proof', + '@type': '@id', + '@container': '@graph', + }, + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/did_v1.ts b/packages/core/src/modules/vc/__tests__/contexts/did_v1.ts new file mode 100644 index 0000000000..2a431389b3 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/did_v1.ts @@ -0,0 +1,56 @@ +export const DID_V1 = { + '@context': { + '@protected': true, + id: '@id', + type: '@type', + alsoKnownAs: { + '@id': 'https://www.w3.org/ns/activitystreams#alsoKnownAs', + '@type': '@id', + }, + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityDelegation: { + '@id': 'https://w3id.org/security#capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'https://w3id.org/security#capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + controller: { + '@id': 'https://w3id.org/security#controller', + '@type': '@id', + }, + keyAgreement: { + '@id': 'https://w3id.org/security#keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + service: { + '@id': 'https://www.w3.org/ns/did#service', + '@type': '@id', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + serviceEndpoint: { + '@id': 'https://www.w3.org/ns/did#serviceEndpoint', + '@type': '@id', + }, + }, + }, + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + }, + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/ed25519_v1.ts b/packages/core/src/modules/vc/__tests__/contexts/ed25519_v1.ts new file mode 100644 index 0000000000..29e09035d4 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/ed25519_v1.ts @@ -0,0 +1,91 @@ +export const ED25519_V1 = { + '@context': { + id: '@id', + type: '@type', + '@protected': true, + proof: { + '@id': 'https://w3id.org/security#proof', + '@type': '@id', + '@container': '@graph', + }, + Ed25519VerificationKey2018: { + '@id': 'https://w3id.org/security#Ed25519VerificationKey2018', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + controller: { + '@id': 'https://w3id.org/security#controller', + '@type': '@id', + }, + revoked: { + '@id': 'https://w3id.org/security#revoked', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + publicKeyBase58: { + '@id': 'https://w3id.org/security#publicKeyBase58', + }, + }, + }, + Ed25519Signature2018: { + '@id': 'https://w3id.org/security#Ed25519Signature2018', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + challenge: 'https://w3id.org/security#challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + domain: 'https://w3id.org/security#domain', + expires: { + '@id': 'https://w3id.org/security#expiration', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + nonce: 'https://w3id.org/security#nonce', + proofPurpose: { + '@id': 'https://w3id.org/security#proofPurpose', + '@type': '@vocab', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'https://w3id.org/security#capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityDelegation: { + '@id': 'https://w3id.org/security#capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + keyAgreement: { + '@id': 'https://w3id.org/security#keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + jws: { + '@id': 'https://w3id.org/security#jws', + }, + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + '@type': '@id', + }, + }, + }, + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/examples_v1.ts b/packages/core/src/modules/vc/__tests__/contexts/examples_v1.ts new file mode 100644 index 0000000000..c0894b4553 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/examples_v1.ts @@ -0,0 +1,46 @@ +export const EXAMPLES_V1 = { + '@context': [ + { '@version': 1.1 }, + 'https://www.w3.org/ns/odrl.jsonld', + { + ex: 'https://example.org/examples#', + schema: 'http://schema.org/', + rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + '3rdPartyCorrelation': 'ex:3rdPartyCorrelation', + AllVerifiers: 'ex:AllVerifiers', + Archival: 'ex:Archival', + BachelorDegree: 'ex:BachelorDegree', + Child: 'ex:Child', + CLCredentialDefinition2019: 'ex:CLCredentialDefinition2019', + CLSignature2019: 'ex:CLSignature2019', + IssuerPolicy: 'ex:IssuerPolicy', + HolderPolicy: 'ex:HolderPolicy', + Mother: 'ex:Mother', + RelationshipCredential: 'ex:RelationshipCredential', + UniversityDegreeCredential: 'ex:UniversityDegreeCredential', + ZkpExampleSchema2018: 'ex:ZkpExampleSchema2018', + issuerData: 'ex:issuerData', + attributes: 'ex:attributes', + signature: 'ex:signature', + signatureCorrectnessProof: 'ex:signatureCorrectnessProof', + primaryProof: 'ex:primaryProof', + nonRevocationProof: 'ex:nonRevocationProof', + alumniOf: { '@id': 'schema:alumniOf', '@type': 'rdf:HTML' }, + child: { '@id': 'ex:child', '@type': '@id' }, + degree: 'ex:degree', + degreeType: 'ex:degreeType', + degreeSchool: 'ex:degreeSchool', + college: 'ex:college', + name: { '@id': 'schema:name', '@type': 'rdf:HTML' }, + givenName: 'schema:givenName', + familyName: 'schema:familyName', + parent: { '@id': 'ex:parent', '@type': '@id' }, + referenceId: 'ex:referenceId', + documentPresence: 'ex:documentPresence', + evidenceDocument: 'ex:evidenceDocument', + spouse: 'schema:spouse', + subjectPresence: 'ex:subjectPresence', + verifier: { '@id': 'ex:verifier', '@type': '@id' }, + }, + ], +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/index.ts b/packages/core/src/modules/vc/__tests__/contexts/index.ts new file mode 100644 index 0000000000..c66801c24a --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/index.ts @@ -0,0 +1,11 @@ +export * from './bbs_v1' +export * from './citizenship_v1' +export * from './credentials_v1' +export * from './did_v1' +export * from './examples_v1' +export * from './odrl' +export * from './schema_org' +export * from './security_v1' +export * from './security_v2' +export * from './security_v3_unstable' +export * from './vaccination_v1' diff --git a/packages/core/src/modules/vc/__tests__/contexts/odrl.ts b/packages/core/src/modules/vc/__tests__/contexts/odrl.ts new file mode 100644 index 0000000000..6efea2320e --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/odrl.ts @@ -0,0 +1,181 @@ +export const ODRL = { + '@context': { + odrl: 'http://www.w3.org/ns/odrl/2/', + rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + rdfs: 'http://www.w3.org/2000/01/rdf-schema#', + owl: 'http://www.w3.org/2002/07/owl#', + skos: 'http://www.w3.org/2004/02/skos/core#', + dct: 'http://purl.org/dc/terms/', + xsd: 'http://www.w3.org/2001/XMLSchema#', + vcard: 'http://www.w3.org/2006/vcard/ns#', + foaf: 'http://xmlns.com/foaf/0.1/', + schema: 'http://schema.org/', + cc: 'http://creativecommons.org/ns#', + uid: '@id', + type: '@type', + Policy: 'odrl:Policy', + Rule: 'odrl:Rule', + profile: { '@type': '@id', '@id': 'odrl:profile' }, + inheritFrom: { '@type': '@id', '@id': 'odrl:inheritFrom' }, + ConflictTerm: 'odrl:ConflictTerm', + conflict: { '@type': '@vocab', '@id': 'odrl:conflict' }, + perm: 'odrl:perm', + prohibit: 'odrl:prohibit', + invalid: 'odrl:invalid', + Agreement: 'odrl:Agreement', + Assertion: 'odrl:Assertion', + Offer: 'odrl:Offer', + Privacy: 'odrl:Privacy', + Request: 'odrl:Request', + Set: 'odrl:Set', + Ticket: 'odrl:Ticket', + Asset: 'odrl:Asset', + AssetCollection: 'odrl:AssetCollection', + relation: { '@type': '@id', '@id': 'odrl:relation' }, + hasPolicy: { '@type': '@id', '@id': 'odrl:hasPolicy' }, + target: { '@type': '@id', '@id': 'odrl:target' }, + output: { '@type': '@id', '@id': 'odrl:output' }, + partOf: { '@type': '@id', '@id': 'odrl:partOf' }, + source: { '@type': '@id', '@id': 'odrl:source' }, + Party: 'odrl:Party', + PartyCollection: 'odrl:PartyCollection', + function: { '@type': '@vocab', '@id': 'odrl:function' }, + PartyScope: 'odrl:PartyScope', + assignee: { '@type': '@id', '@id': 'odrl:assignee' }, + assigner: { '@type': '@id', '@id': 'odrl:assigner' }, + assigneeOf: { '@type': '@id', '@id': 'odrl:assigneeOf' }, + assignerOf: { '@type': '@id', '@id': 'odrl:assignerOf' }, + attributedParty: { '@type': '@id', '@id': 'odrl:attributedParty' }, + attributingParty: { '@type': '@id', '@id': 'odrl:attributingParty' }, + compensatedParty: { '@type': '@id', '@id': 'odrl:compensatedParty' }, + compensatingParty: { '@type': '@id', '@id': 'odrl:compensatingParty' }, + consentingParty: { '@type': '@id', '@id': 'odrl:consentingParty' }, + consentedParty: { '@type': '@id', '@id': 'odrl:consentedParty' }, + informedParty: { '@type': '@id', '@id': 'odrl:informedParty' }, + informingParty: { '@type': '@id', '@id': 'odrl:informingParty' }, + trackingParty: { '@type': '@id', '@id': 'odrl:trackingParty' }, + trackedParty: { '@type': '@id', '@id': 'odrl:trackedParty' }, + contractingParty: { '@type': '@id', '@id': 'odrl:contractingParty' }, + contractedParty: { '@type': '@id', '@id': 'odrl:contractedParty' }, + Action: 'odrl:Action', + action: { '@type': '@vocab', '@id': 'odrl:action' }, + includedIn: { '@type': '@id', '@id': 'odrl:includedIn' }, + implies: { '@type': '@id', '@id': 'odrl:implies' }, + Permission: 'odrl:Permission', + permission: { '@type': '@id', '@id': 'odrl:permission' }, + Prohibition: 'odrl:Prohibition', + prohibition: { '@type': '@id', '@id': 'odrl:prohibition' }, + obligation: { '@type': '@id', '@id': 'odrl:obligation' }, + use: 'odrl:use', + grantUse: 'odrl:grantUse', + aggregate: 'odrl:aggregate', + annotate: 'odrl:annotate', + anonymize: 'odrl:anonymize', + archive: 'odrl:archive', + concurrentUse: 'odrl:concurrentUse', + derive: 'odrl:derive', + digitize: 'odrl:digitize', + display: 'odrl:display', + distribute: 'odrl:distribute', + execute: 'odrl:execute', + extract: 'odrl:extract', + give: 'odrl:give', + index: 'odrl:index', + install: 'odrl:install', + modify: 'odrl:modify', + move: 'odrl:move', + play: 'odrl:play', + present: 'odrl:present', + print: 'odrl:print', + read: 'odrl:read', + reproduce: 'odrl:reproduce', + sell: 'odrl:sell', + stream: 'odrl:stream', + textToSpeech: 'odrl:textToSpeech', + transfer: 'odrl:transfer', + transform: 'odrl:transform', + translate: 'odrl:translate', + Duty: 'odrl:Duty', + duty: { '@type': '@id', '@id': 'odrl:duty' }, + consequence: { '@type': '@id', '@id': 'odrl:consequence' }, + remedy: { '@type': '@id', '@id': 'odrl:remedy' }, + acceptTracking: 'odrl:acceptTracking', + attribute: 'odrl:attribute', + compensate: 'odrl:compensate', + delete: 'odrl:delete', + ensureExclusivity: 'odrl:ensureExclusivity', + include: 'odrl:include', + inform: 'odrl:inform', + nextPolicy: 'odrl:nextPolicy', + obtainConsent: 'odrl:obtainConsent', + reviewPolicy: 'odrl:reviewPolicy', + uninstall: 'odrl:uninstall', + watermark: 'odrl:watermark', + Constraint: 'odrl:Constraint', + LogicalConstraint: 'odrl:LogicalConstraint', + constraint: { '@type': '@id', '@id': 'odrl:constraint' }, + refinement: { '@type': '@id', '@id': 'odrl:refinement' }, + Operator: 'odrl:Operator', + operator: { '@type': '@vocab', '@id': 'odrl:operator' }, + RightOperand: 'odrl:RightOperand', + rightOperand: 'odrl:rightOperand', + rightOperandReference: { + '@type': 'xsd:anyURI', + '@id': 'odrl:rightOperandReference', + }, + LeftOperand: 'odrl:LeftOperand', + leftOperand: { '@type': '@vocab', '@id': 'odrl:leftOperand' }, + unit: 'odrl:unit', + dataType: { '@type': 'xsd:anyType', '@id': 'odrl:datatype' }, + status: 'odrl:status', + absolutePosition: 'odrl:absolutePosition', + absoluteSpatialPosition: 'odrl:absoluteSpatialPosition', + absoluteTemporalPosition: 'odrl:absoluteTemporalPosition', + absoluteSize: 'odrl:absoluteSize', + count: 'odrl:count', + dateTime: 'odrl:dateTime', + delayPeriod: 'odrl:delayPeriod', + deliveryChannel: 'odrl:deliveryChannel', + elapsedTime: 'odrl:elapsedTime', + event: 'odrl:event', + fileFormat: 'odrl:fileFormat', + industry: 'odrl:industry:', + language: 'odrl:language', + media: 'odrl:media', + meteredTime: 'odrl:meteredTime', + payAmount: 'odrl:payAmount', + percentage: 'odrl:percentage', + product: 'odrl:product', + purpose: 'odrl:purpose', + recipient: 'odrl:recipient', + relativePosition: 'odrl:relativePosition', + relativeSpatialPosition: 'odrl:relativeSpatialPosition', + relativeTemporalPosition: 'odrl:relativeTemporalPosition', + relativeSize: 'odrl:relativeSize', + resolution: 'odrl:resolution', + spatial: 'odrl:spatial', + spatialCoordinates: 'odrl:spatialCoordinates', + systemDevice: 'odrl:systemDevice', + timeInterval: 'odrl:timeInterval', + unitOfCount: 'odrl:unitOfCount', + version: 'odrl:version', + virtualLocation: 'odrl:virtualLocation', + eq: 'odrl:eq', + gt: 'odrl:gt', + gteq: 'odrl:gteq', + lt: 'odrl:lt', + lteq: 'odrl:lteq', + neq: 'odrl:neg', + isA: 'odrl:isA', + hasPart: 'odrl:hasPart', + isPartOf: 'odrl:isPartOf', + isAllOf: 'odrl:isAllOf', + isAnyOf: 'odrl:isAnyOf', + isNoneOf: 'odrl:isNoneOf', + or: 'odrl:or', + xone: 'odrl:xone', + and: 'odrl:and', + andSequence: 'odrl:andSequence', + policyUsage: 'odrl:policyUsage', + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/schema_org.ts b/packages/core/src/modules/vc/__tests__/contexts/schema_org.ts new file mode 100644 index 0000000000..818951da70 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/schema_org.ts @@ -0,0 +1,2838 @@ +export const SCHEMA_ORG = { + '@context': { + type: '@type', + id: '@id', + HTML: { '@id': 'rdf:HTML' }, + '@vocab': 'http://schema.org/', + rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + rdfs: 'http://www.w3.org/2000/01/rdf-schema#', + xsd: 'http://www.w3.org/2001/XMLSchema#', + schema: 'http://schema.org/', + owl: 'http://www.w3.org/2002/07/owl#', + dc: 'http://purl.org/dc/elements/1.1/', + dct: 'http://purl.org/dc/terms/', + dctype: 'http://purl.org/dc/dcmitype/', + void: 'http://rdfs.org/ns/void#', + dcat: 'http://www.w3.org/ns/dcat#', + '3DModel': { '@id': 'schema:3DModel' }, + AMRadioChannel: { '@id': 'schema:AMRadioChannel' }, + APIReference: { '@id': 'schema:APIReference' }, + Abdomen: { '@id': 'schema:Abdomen' }, + AboutPage: { '@id': 'schema:AboutPage' }, + AcceptAction: { '@id': 'schema:AcceptAction' }, + Accommodation: { '@id': 'schema:Accommodation' }, + AccountingService: { '@id': 'schema:AccountingService' }, + AchieveAction: { '@id': 'schema:AchieveAction' }, + Action: { '@id': 'schema:Action' }, + ActionAccessSpecification: { '@id': 'schema:ActionAccessSpecification' }, + ActionStatusType: { '@id': 'schema:ActionStatusType' }, + ActivateAction: { '@id': 'schema:ActivateAction' }, + ActivationFee: { '@id': 'schema:ActivationFee' }, + ActiveActionStatus: { '@id': 'schema:ActiveActionStatus' }, + ActiveNotRecruiting: { '@id': 'schema:ActiveNotRecruiting' }, + AddAction: { '@id': 'schema:AddAction' }, + AdministrativeArea: { '@id': 'schema:AdministrativeArea' }, + AdultEntertainment: { '@id': 'schema:AdultEntertainment' }, + AdvertiserContentArticle: { '@id': 'schema:AdvertiserContentArticle' }, + AerobicActivity: { '@id': 'schema:AerobicActivity' }, + AggregateOffer: { '@id': 'schema:AggregateOffer' }, + AggregateRating: { '@id': 'schema:AggregateRating' }, + AgreeAction: { '@id': 'schema:AgreeAction' }, + Airline: { '@id': 'schema:Airline' }, + Airport: { '@id': 'schema:Airport' }, + AlbumRelease: { '@id': 'schema:AlbumRelease' }, + AlignmentObject: { '@id': 'schema:AlignmentObject' }, + AllWheelDriveConfiguration: { '@id': 'schema:AllWheelDriveConfiguration' }, + AllergiesHealthAspect: { '@id': 'schema:AllergiesHealthAspect' }, + AllocateAction: { '@id': 'schema:AllocateAction' }, + AmpStory: { '@id': 'schema:AmpStory' }, + AmusementPark: { '@id': 'schema:AmusementPark' }, + AnaerobicActivity: { '@id': 'schema:AnaerobicActivity' }, + AnalysisNewsArticle: { '@id': 'schema:AnalysisNewsArticle' }, + AnatomicalStructure: { '@id': 'schema:AnatomicalStructure' }, + AnatomicalSystem: { '@id': 'schema:AnatomicalSystem' }, + Anesthesia: { '@id': 'schema:Anesthesia' }, + AnimalShelter: { '@id': 'schema:AnimalShelter' }, + Answer: { '@id': 'schema:Answer' }, + Apartment: { '@id': 'schema:Apartment' }, + ApartmentComplex: { '@id': 'schema:ApartmentComplex' }, + Appearance: { '@id': 'schema:Appearance' }, + AppendAction: { '@id': 'schema:AppendAction' }, + ApplyAction: { '@id': 'schema:ApplyAction' }, + ApprovedIndication: { '@id': 'schema:ApprovedIndication' }, + Aquarium: { '@id': 'schema:Aquarium' }, + ArchiveComponent: { '@id': 'schema:ArchiveComponent' }, + ArchiveOrganization: { '@id': 'schema:ArchiveOrganization' }, + ArriveAction: { '@id': 'schema:ArriveAction' }, + ArtGallery: { '@id': 'schema:ArtGallery' }, + Artery: { '@id': 'schema:Artery' }, + Article: { '@id': 'schema:Article' }, + AskAction: { '@id': 'schema:AskAction' }, + AskPublicNewsArticle: { '@id': 'schema:AskPublicNewsArticle' }, + AssessAction: { '@id': 'schema:AssessAction' }, + AssignAction: { '@id': 'schema:AssignAction' }, + Atlas: { '@id': 'schema:Atlas' }, + Attorney: { '@id': 'schema:Attorney' }, + Audience: { '@id': 'schema:Audience' }, + AudioObject: { '@id': 'schema:AudioObject' }, + Audiobook: { '@id': 'schema:Audiobook' }, + AudiobookFormat: { '@id': 'schema:AudiobookFormat' }, + AuthoritativeLegalValue: { '@id': 'schema:AuthoritativeLegalValue' }, + AuthorizeAction: { '@id': 'schema:AuthorizeAction' }, + AutoBodyShop: { '@id': 'schema:AutoBodyShop' }, + AutoDealer: { '@id': 'schema:AutoDealer' }, + AutoPartsStore: { '@id': 'schema:AutoPartsStore' }, + AutoRental: { '@id': 'schema:AutoRental' }, + AutoRepair: { '@id': 'schema:AutoRepair' }, + AutoWash: { '@id': 'schema:AutoWash' }, + AutomatedTeller: { '@id': 'schema:AutomatedTeller' }, + AutomotiveBusiness: { '@id': 'schema:AutomotiveBusiness' }, + Ayurvedic: { '@id': 'schema:Ayurvedic' }, + BackOrder: { '@id': 'schema:BackOrder' }, + BackgroundNewsArticle: { '@id': 'schema:BackgroundNewsArticle' }, + Bacteria: { '@id': 'schema:Bacteria' }, + Bakery: { '@id': 'schema:Bakery' }, + Balance: { '@id': 'schema:Balance' }, + BankAccount: { '@id': 'schema:BankAccount' }, + BankOrCreditUnion: { '@id': 'schema:BankOrCreditUnion' }, + BarOrPub: { '@id': 'schema:BarOrPub' }, + Barcode: { '@id': 'schema:Barcode' }, + BasicIncome: { '@id': 'schema:BasicIncome' }, + Beach: { '@id': 'schema:Beach' }, + BeautySalon: { '@id': 'schema:BeautySalon' }, + BedAndBreakfast: { '@id': 'schema:BedAndBreakfast' }, + BedDetails: { '@id': 'schema:BedDetails' }, + BedType: { '@id': 'schema:BedType' }, + BefriendAction: { '@id': 'schema:BefriendAction' }, + BenefitsHealthAspect: { '@id': 'schema:BenefitsHealthAspect' }, + BikeStore: { '@id': 'schema:BikeStore' }, + Blog: { '@id': 'schema:Blog' }, + BlogPosting: { '@id': 'schema:BlogPosting' }, + BloodTest: { '@id': 'schema:BloodTest' }, + BoardingPolicyType: { '@id': 'schema:BoardingPolicyType' }, + BoatReservation: { '@id': 'schema:BoatReservation' }, + BoatTerminal: { '@id': 'schema:BoatTerminal' }, + BoatTrip: { '@id': 'schema:BoatTrip' }, + BodyMeasurementArm: { '@id': 'schema:BodyMeasurementArm' }, + BodyMeasurementBust: { '@id': 'schema:BodyMeasurementBust' }, + BodyMeasurementChest: { '@id': 'schema:BodyMeasurementChest' }, + BodyMeasurementFoot: { '@id': 'schema:BodyMeasurementFoot' }, + BodyMeasurementHand: { '@id': 'schema:BodyMeasurementHand' }, + BodyMeasurementHead: { '@id': 'schema:BodyMeasurementHead' }, + BodyMeasurementHeight: { '@id': 'schema:BodyMeasurementHeight' }, + BodyMeasurementHips: { '@id': 'schema:BodyMeasurementHips' }, + BodyMeasurementInsideLeg: { '@id': 'schema:BodyMeasurementInsideLeg' }, + BodyMeasurementNeck: { '@id': 'schema:BodyMeasurementNeck' }, + BodyMeasurementTypeEnumeration: { + '@id': 'schema:BodyMeasurementTypeEnumeration', + }, + BodyMeasurementUnderbust: { '@id': 'schema:BodyMeasurementUnderbust' }, + BodyMeasurementWaist: { '@id': 'schema:BodyMeasurementWaist' }, + BodyMeasurementWeight: { '@id': 'schema:BodyMeasurementWeight' }, + BodyOfWater: { '@id': 'schema:BodyOfWater' }, + Bone: { '@id': 'schema:Bone' }, + Book: { '@id': 'schema:Book' }, + BookFormatType: { '@id': 'schema:BookFormatType' }, + BookSeries: { '@id': 'schema:BookSeries' }, + BookStore: { '@id': 'schema:BookStore' }, + BookmarkAction: { '@id': 'schema:BookmarkAction' }, + Boolean: { '@id': 'schema:Boolean' }, + BorrowAction: { '@id': 'schema:BorrowAction' }, + BowlingAlley: { '@id': 'schema:BowlingAlley' }, + BrainStructure: { '@id': 'schema:BrainStructure' }, + Brand: { '@id': 'schema:Brand' }, + BreadcrumbList: { '@id': 'schema:BreadcrumbList' }, + Brewery: { '@id': 'schema:Brewery' }, + Bridge: { '@id': 'schema:Bridge' }, + BroadcastChannel: { '@id': 'schema:BroadcastChannel' }, + BroadcastEvent: { '@id': 'schema:BroadcastEvent' }, + BroadcastFrequencySpecification: { + '@id': 'schema:BroadcastFrequencySpecification', + }, + BroadcastRelease: { '@id': 'schema:BroadcastRelease' }, + BroadcastService: { '@id': 'schema:BroadcastService' }, + BrokerageAccount: { '@id': 'schema:BrokerageAccount' }, + BuddhistTemple: { '@id': 'schema:BuddhistTemple' }, + BusOrCoach: { '@id': 'schema:BusOrCoach' }, + BusReservation: { '@id': 'schema:BusReservation' }, + BusStation: { '@id': 'schema:BusStation' }, + BusStop: { '@id': 'schema:BusStop' }, + BusTrip: { '@id': 'schema:BusTrip' }, + BusinessAudience: { '@id': 'schema:BusinessAudience' }, + BusinessEntityType: { '@id': 'schema:BusinessEntityType' }, + BusinessEvent: { '@id': 'schema:BusinessEvent' }, + BusinessFunction: { '@id': 'schema:BusinessFunction' }, + BusinessSupport: { '@id': 'schema:BusinessSupport' }, + BuyAction: { '@id': 'schema:BuyAction' }, + CDCPMDRecord: { '@id': 'schema:CDCPMDRecord' }, + CDFormat: { '@id': 'schema:CDFormat' }, + CT: { '@id': 'schema:CT' }, + CableOrSatelliteService: { '@id': 'schema:CableOrSatelliteService' }, + CafeOrCoffeeShop: { '@id': 'schema:CafeOrCoffeeShop' }, + Campground: { '@id': 'schema:Campground' }, + CampingPitch: { '@id': 'schema:CampingPitch' }, + Canal: { '@id': 'schema:Canal' }, + CancelAction: { '@id': 'schema:CancelAction' }, + Car: { '@id': 'schema:Car' }, + CarUsageType: { '@id': 'schema:CarUsageType' }, + Cardiovascular: { '@id': 'schema:Cardiovascular' }, + CardiovascularExam: { '@id': 'schema:CardiovascularExam' }, + CaseSeries: { '@id': 'schema:CaseSeries' }, + Casino: { '@id': 'schema:Casino' }, + CassetteFormat: { '@id': 'schema:CassetteFormat' }, + CategoryCode: { '@id': 'schema:CategoryCode' }, + CategoryCodeSet: { '@id': 'schema:CategoryCodeSet' }, + CatholicChurch: { '@id': 'schema:CatholicChurch' }, + CausesHealthAspect: { '@id': 'schema:CausesHealthAspect' }, + Cemetery: { '@id': 'schema:Cemetery' }, + Chapter: { '@id': 'schema:Chapter' }, + CharitableIncorporatedOrganization: { + '@id': 'schema:CharitableIncorporatedOrganization', + }, + CheckAction: { '@id': 'schema:CheckAction' }, + CheckInAction: { '@id': 'schema:CheckInAction' }, + CheckOutAction: { '@id': 'schema:CheckOutAction' }, + CheckoutPage: { '@id': 'schema:CheckoutPage' }, + ChildCare: { '@id': 'schema:ChildCare' }, + ChildrensEvent: { '@id': 'schema:ChildrensEvent' }, + Chiropractic: { '@id': 'schema:Chiropractic' }, + ChooseAction: { '@id': 'schema:ChooseAction' }, + Church: { '@id': 'schema:Church' }, + City: { '@id': 'schema:City' }, + CityHall: { '@id': 'schema:CityHall' }, + CivicStructure: { '@id': 'schema:CivicStructure' }, + Claim: { '@id': 'schema:Claim' }, + ClaimReview: { '@id': 'schema:ClaimReview' }, + Class: { '@id': 'schema:Class' }, + CleaningFee: { '@id': 'schema:CleaningFee' }, + Clinician: { '@id': 'schema:Clinician' }, + Clip: { '@id': 'schema:Clip' }, + ClothingStore: { '@id': 'schema:ClothingStore' }, + CoOp: { '@id': 'schema:CoOp' }, + Code: { '@id': 'schema:Code' }, + CohortStudy: { '@id': 'schema:CohortStudy' }, + Collection: { '@id': 'schema:Collection' }, + CollectionPage: { '@id': 'schema:CollectionPage' }, + CollegeOrUniversity: { '@id': 'schema:CollegeOrUniversity' }, + ComedyClub: { '@id': 'schema:ComedyClub' }, + ComedyEvent: { '@id': 'schema:ComedyEvent' }, + ComicCoverArt: { '@id': 'schema:ComicCoverArt' }, + ComicIssue: { '@id': 'schema:ComicIssue' }, + ComicSeries: { '@id': 'schema:ComicSeries' }, + ComicStory: { '@id': 'schema:ComicStory' }, + Comment: { '@id': 'schema:Comment' }, + CommentAction: { '@id': 'schema:CommentAction' }, + CommentPermission: { '@id': 'schema:CommentPermission' }, + CommunicateAction: { '@id': 'schema:CommunicateAction' }, + CommunityHealth: { '@id': 'schema:CommunityHealth' }, + CompilationAlbum: { '@id': 'schema:CompilationAlbum' }, + CompleteDataFeed: { '@id': 'schema:CompleteDataFeed' }, + Completed: { '@id': 'schema:Completed' }, + CompletedActionStatus: { '@id': 'schema:CompletedActionStatus' }, + CompoundPriceSpecification: { '@id': 'schema:CompoundPriceSpecification' }, + ComputerLanguage: { '@id': 'schema:ComputerLanguage' }, + ComputerStore: { '@id': 'schema:ComputerStore' }, + ConfirmAction: { '@id': 'schema:ConfirmAction' }, + Consortium: { '@id': 'schema:Consortium' }, + ConsumeAction: { '@id': 'schema:ConsumeAction' }, + ContactPage: { '@id': 'schema:ContactPage' }, + ContactPoint: { '@id': 'schema:ContactPoint' }, + ContactPointOption: { '@id': 'schema:ContactPointOption' }, + ContagiousnessHealthAspect: { '@id': 'schema:ContagiousnessHealthAspect' }, + Continent: { '@id': 'schema:Continent' }, + ControlAction: { '@id': 'schema:ControlAction' }, + ConvenienceStore: { '@id': 'schema:ConvenienceStore' }, + Conversation: { '@id': 'schema:Conversation' }, + CookAction: { '@id': 'schema:CookAction' }, + Corporation: { '@id': 'schema:Corporation' }, + CorrectionComment: { '@id': 'schema:CorrectionComment' }, + Country: { '@id': 'schema:Country' }, + Course: { '@id': 'schema:Course' }, + CourseInstance: { '@id': 'schema:CourseInstance' }, + Courthouse: { '@id': 'schema:Courthouse' }, + CoverArt: { '@id': 'schema:CoverArt' }, + CovidTestingFacility: { '@id': 'schema:CovidTestingFacility' }, + CreateAction: { '@id': 'schema:CreateAction' }, + CreativeWork: { '@id': 'schema:CreativeWork' }, + CreativeWorkSeason: { '@id': 'schema:CreativeWorkSeason' }, + CreativeWorkSeries: { '@id': 'schema:CreativeWorkSeries' }, + CreditCard: { '@id': 'schema:CreditCard' }, + Crematorium: { '@id': 'schema:Crematorium' }, + CriticReview: { '@id': 'schema:CriticReview' }, + CrossSectional: { '@id': 'schema:CrossSectional' }, + CssSelectorType: { '@id': 'schema:CssSelectorType' }, + CurrencyConversionService: { '@id': 'schema:CurrencyConversionService' }, + DDxElement: { '@id': 'schema:DDxElement' }, + DJMixAlbum: { '@id': 'schema:DJMixAlbum' }, + DVDFormat: { '@id': 'schema:DVDFormat' }, + DamagedCondition: { '@id': 'schema:DamagedCondition' }, + DanceEvent: { '@id': 'schema:DanceEvent' }, + DanceGroup: { '@id': 'schema:DanceGroup' }, + DataCatalog: { '@id': 'schema:DataCatalog' }, + DataDownload: { '@id': 'schema:DataDownload' }, + DataFeed: { '@id': 'schema:DataFeed' }, + DataFeedItem: { '@id': 'schema:DataFeedItem' }, + DataType: { '@id': 'schema:DataType' }, + Dataset: { '@id': 'schema:Dataset' }, + Date: { '@id': 'schema:Date' }, + DateTime: { '@id': 'schema:DateTime' }, + DatedMoneySpecification: { '@id': 'schema:DatedMoneySpecification' }, + DayOfWeek: { '@id': 'schema:DayOfWeek' }, + DaySpa: { '@id': 'schema:DaySpa' }, + DeactivateAction: { '@id': 'schema:DeactivateAction' }, + DecontextualizedContent: { '@id': 'schema:DecontextualizedContent' }, + DefenceEstablishment: { '@id': 'schema:DefenceEstablishment' }, + DefinedRegion: { '@id': 'schema:DefinedRegion' }, + DefinedTerm: { '@id': 'schema:DefinedTerm' }, + DefinedTermSet: { '@id': 'schema:DefinedTermSet' }, + DefinitiveLegalValue: { '@id': 'schema:DefinitiveLegalValue' }, + DeleteAction: { '@id': 'schema:DeleteAction' }, + DeliveryChargeSpecification: { '@id': 'schema:DeliveryChargeSpecification' }, + DeliveryEvent: { '@id': 'schema:DeliveryEvent' }, + DeliveryMethod: { '@id': 'schema:DeliveryMethod' }, + DeliveryTimeSettings: { '@id': 'schema:DeliveryTimeSettings' }, + Demand: { '@id': 'schema:Demand' }, + DemoAlbum: { '@id': 'schema:DemoAlbum' }, + Dentist: { '@id': 'schema:Dentist' }, + Dentistry: { '@id': 'schema:Dentistry' }, + DepartAction: { '@id': 'schema:DepartAction' }, + DepartmentStore: { '@id': 'schema:DepartmentStore' }, + DepositAccount: { '@id': 'schema:DepositAccount' }, + Dermatologic: { '@id': 'schema:Dermatologic' }, + Dermatology: { '@id': 'schema:Dermatology' }, + DiabeticDiet: { '@id': 'schema:DiabeticDiet' }, + Diagnostic: { '@id': 'schema:Diagnostic' }, + DiagnosticLab: { '@id': 'schema:DiagnosticLab' }, + DiagnosticProcedure: { '@id': 'schema:DiagnosticProcedure' }, + Diet: { '@id': 'schema:Diet' }, + DietNutrition: { '@id': 'schema:DietNutrition' }, + DietarySupplement: { '@id': 'schema:DietarySupplement' }, + DigitalAudioTapeFormat: { '@id': 'schema:DigitalAudioTapeFormat' }, + DigitalDocument: { '@id': 'schema:DigitalDocument' }, + DigitalDocumentPermission: { '@id': 'schema:DigitalDocumentPermission' }, + DigitalDocumentPermissionType: { + '@id': 'schema:DigitalDocumentPermissionType', + }, + DigitalFormat: { '@id': 'schema:DigitalFormat' }, + DisabilitySupport: { '@id': 'schema:DisabilitySupport' }, + DisagreeAction: { '@id': 'schema:DisagreeAction' }, + Discontinued: { '@id': 'schema:Discontinued' }, + DiscoverAction: { '@id': 'schema:DiscoverAction' }, + DiscussionForumPosting: { '@id': 'schema:DiscussionForumPosting' }, + DislikeAction: { '@id': 'schema:DislikeAction' }, + Distance: { '@id': 'schema:Distance' }, + DistanceFee: { '@id': 'schema:DistanceFee' }, + Distillery: { '@id': 'schema:Distillery' }, + DonateAction: { '@id': 'schema:DonateAction' }, + DoseSchedule: { '@id': 'schema:DoseSchedule' }, + DoubleBlindedTrial: { '@id': 'schema:DoubleBlindedTrial' }, + DownloadAction: { '@id': 'schema:DownloadAction' }, + Downpayment: { '@id': 'schema:Downpayment' }, + DrawAction: { '@id': 'schema:DrawAction' }, + Drawing: { '@id': 'schema:Drawing' }, + DrinkAction: { '@id': 'schema:DrinkAction' }, + DriveWheelConfigurationValue: { '@id': 'schema:DriveWheelConfigurationValue' }, + DrivingSchoolVehicleUsage: { '@id': 'schema:DrivingSchoolVehicleUsage' }, + Drug: { '@id': 'schema:Drug' }, + DrugClass: { '@id': 'schema:DrugClass' }, + DrugCost: { '@id': 'schema:DrugCost' }, + DrugCostCategory: { '@id': 'schema:DrugCostCategory' }, + DrugLegalStatus: { '@id': 'schema:DrugLegalStatus' }, + DrugPregnancyCategory: { '@id': 'schema:DrugPregnancyCategory' }, + DrugPrescriptionStatus: { '@id': 'schema:DrugPrescriptionStatus' }, + DrugStrength: { '@id': 'schema:DrugStrength' }, + DryCleaningOrLaundry: { '@id': 'schema:DryCleaningOrLaundry' }, + Duration: { '@id': 'schema:Duration' }, + EBook: { '@id': 'schema:EBook' }, + EPRelease: { '@id': 'schema:EPRelease' }, + EUEnergyEfficiencyCategoryA: { '@id': 'schema:EUEnergyEfficiencyCategoryA' }, + EUEnergyEfficiencyCategoryA1Plus: { + '@id': 'schema:EUEnergyEfficiencyCategoryA1Plus', + }, + EUEnergyEfficiencyCategoryA2Plus: { + '@id': 'schema:EUEnergyEfficiencyCategoryA2Plus', + }, + EUEnergyEfficiencyCategoryA3Plus: { + '@id': 'schema:EUEnergyEfficiencyCategoryA3Plus', + }, + EUEnergyEfficiencyCategoryB: { '@id': 'schema:EUEnergyEfficiencyCategoryB' }, + EUEnergyEfficiencyCategoryC: { '@id': 'schema:EUEnergyEfficiencyCategoryC' }, + EUEnergyEfficiencyCategoryD: { '@id': 'schema:EUEnergyEfficiencyCategoryD' }, + EUEnergyEfficiencyCategoryE: { '@id': 'schema:EUEnergyEfficiencyCategoryE' }, + EUEnergyEfficiencyCategoryF: { '@id': 'schema:EUEnergyEfficiencyCategoryF' }, + EUEnergyEfficiencyCategoryG: { '@id': 'schema:EUEnergyEfficiencyCategoryG' }, + EUEnergyEfficiencyEnumeration: { + '@id': 'schema:EUEnergyEfficiencyEnumeration', + }, + Ear: { '@id': 'schema:Ear' }, + EatAction: { '@id': 'schema:EatAction' }, + EditedOrCroppedContent: { '@id': 'schema:EditedOrCroppedContent' }, + EducationEvent: { '@id': 'schema:EducationEvent' }, + EducationalAudience: { '@id': 'schema:EducationalAudience' }, + EducationalOccupationalCredential: { + '@id': 'schema:EducationalOccupationalCredential', + }, + EducationalOccupationalProgram: { + '@id': 'schema:EducationalOccupationalProgram', + }, + EducationalOrganization: { '@id': 'schema:EducationalOrganization' }, + EffectivenessHealthAspect: { '@id': 'schema:EffectivenessHealthAspect' }, + Electrician: { '@id': 'schema:Electrician' }, + ElectronicsStore: { '@id': 'schema:ElectronicsStore' }, + ElementarySchool: { '@id': 'schema:ElementarySchool' }, + EmailMessage: { '@id': 'schema:EmailMessage' }, + Embassy: { '@id': 'schema:Embassy' }, + Emergency: { '@id': 'schema:Emergency' }, + EmergencyService: { '@id': 'schema:EmergencyService' }, + EmployeeRole: { '@id': 'schema:EmployeeRole' }, + EmployerAggregateRating: { '@id': 'schema:EmployerAggregateRating' }, + EmployerReview: { '@id': 'schema:EmployerReview' }, + EmploymentAgency: { '@id': 'schema:EmploymentAgency' }, + Endocrine: { '@id': 'schema:Endocrine' }, + EndorseAction: { '@id': 'schema:EndorseAction' }, + EndorsementRating: { '@id': 'schema:EndorsementRating' }, + Energy: { '@id': 'schema:Energy' }, + EnergyConsumptionDetails: { '@id': 'schema:EnergyConsumptionDetails' }, + EnergyEfficiencyEnumeration: { '@id': 'schema:EnergyEfficiencyEnumeration' }, + EnergyStarCertified: { '@id': 'schema:EnergyStarCertified' }, + EnergyStarEnergyEfficiencyEnumeration: { + '@id': 'schema:EnergyStarEnergyEfficiencyEnumeration', + }, + EngineSpecification: { '@id': 'schema:EngineSpecification' }, + EnrollingByInvitation: { '@id': 'schema:EnrollingByInvitation' }, + EntertainmentBusiness: { '@id': 'schema:EntertainmentBusiness' }, + EntryPoint: { '@id': 'schema:EntryPoint' }, + Enumeration: { '@id': 'schema:Enumeration' }, + Episode: { '@id': 'schema:Episode' }, + Event: { '@id': 'schema:Event' }, + EventAttendanceModeEnumeration: { + '@id': 'schema:EventAttendanceModeEnumeration', + }, + EventCancelled: { '@id': 'schema:EventCancelled' }, + EventMovedOnline: { '@id': 'schema:EventMovedOnline' }, + EventPostponed: { '@id': 'schema:EventPostponed' }, + EventRescheduled: { '@id': 'schema:EventRescheduled' }, + EventReservation: { '@id': 'schema:EventReservation' }, + EventScheduled: { '@id': 'schema:EventScheduled' }, + EventSeries: { '@id': 'schema:EventSeries' }, + EventStatusType: { '@id': 'schema:EventStatusType' }, + EventVenue: { '@id': 'schema:EventVenue' }, + EvidenceLevelA: { '@id': 'schema:EvidenceLevelA' }, + EvidenceLevelB: { '@id': 'schema:EvidenceLevelB' }, + EvidenceLevelC: { '@id': 'schema:EvidenceLevelC' }, + ExchangeRateSpecification: { '@id': 'schema:ExchangeRateSpecification' }, + ExchangeRefund: { '@id': 'schema:ExchangeRefund' }, + ExerciseAction: { '@id': 'schema:ExerciseAction' }, + ExerciseGym: { '@id': 'schema:ExerciseGym' }, + ExercisePlan: { '@id': 'schema:ExercisePlan' }, + ExhibitionEvent: { '@id': 'schema:ExhibitionEvent' }, + Eye: { '@id': 'schema:Eye' }, + FAQPage: { '@id': 'schema:FAQPage' }, + FDAcategoryA: { '@id': 'schema:FDAcategoryA' }, + FDAcategoryB: { '@id': 'schema:FDAcategoryB' }, + FDAcategoryC: { '@id': 'schema:FDAcategoryC' }, + FDAcategoryD: { '@id': 'schema:FDAcategoryD' }, + FDAcategoryX: { '@id': 'schema:FDAcategoryX' }, + FDAnotEvaluated: { '@id': 'schema:FDAnotEvaluated' }, + FMRadioChannel: { '@id': 'schema:FMRadioChannel' }, + FailedActionStatus: { '@id': 'schema:FailedActionStatus' }, + False: { '@id': 'schema:False' }, + FastFoodRestaurant: { '@id': 'schema:FastFoodRestaurant' }, + Female: { '@id': 'schema:Female' }, + Festival: { '@id': 'schema:Festival' }, + FilmAction: { '@id': 'schema:FilmAction' }, + FinancialProduct: { '@id': 'schema:FinancialProduct' }, + FinancialService: { '@id': 'schema:FinancialService' }, + FindAction: { '@id': 'schema:FindAction' }, + FireStation: { '@id': 'schema:FireStation' }, + Flexibility: { '@id': 'schema:Flexibility' }, + Flight: { '@id': 'schema:Flight' }, + FlightReservation: { '@id': 'schema:FlightReservation' }, + Float: { '@id': 'schema:Float' }, + FloorPlan: { '@id': 'schema:FloorPlan' }, + Florist: { '@id': 'schema:Florist' }, + FollowAction: { '@id': 'schema:FollowAction' }, + FoodEstablishment: { '@id': 'schema:FoodEstablishment' }, + FoodEstablishmentReservation: { '@id': 'schema:FoodEstablishmentReservation' }, + FoodEvent: { '@id': 'schema:FoodEvent' }, + FoodService: { '@id': 'schema:FoodService' }, + FourWheelDriveConfiguration: { '@id': 'schema:FourWheelDriveConfiguration' }, + Friday: { '@id': 'schema:Friday' }, + FrontWheelDriveConfiguration: { '@id': 'schema:FrontWheelDriveConfiguration' }, + FullRefund: { '@id': 'schema:FullRefund' }, + FundingAgency: { '@id': 'schema:FundingAgency' }, + FundingScheme: { '@id': 'schema:FundingScheme' }, + Fungus: { '@id': 'schema:Fungus' }, + FurnitureStore: { '@id': 'schema:FurnitureStore' }, + Game: { '@id': 'schema:Game' }, + GamePlayMode: { '@id': 'schema:GamePlayMode' }, + GameServer: { '@id': 'schema:GameServer' }, + GameServerStatus: { '@id': 'schema:GameServerStatus' }, + GardenStore: { '@id': 'schema:GardenStore' }, + GasStation: { '@id': 'schema:GasStation' }, + Gastroenterologic: { '@id': 'schema:Gastroenterologic' }, + GatedResidenceCommunity: { '@id': 'schema:GatedResidenceCommunity' }, + GenderType: { '@id': 'schema:GenderType' }, + GeneralContractor: { '@id': 'schema:GeneralContractor' }, + Genetic: { '@id': 'schema:Genetic' }, + Genitourinary: { '@id': 'schema:Genitourinary' }, + GeoCircle: { '@id': 'schema:GeoCircle' }, + GeoCoordinates: { '@id': 'schema:GeoCoordinates' }, + GeoShape: { '@id': 'schema:GeoShape' }, + GeospatialGeometry: { '@id': 'schema:GeospatialGeometry' }, + Geriatric: { '@id': 'schema:Geriatric' }, + GettingAccessHealthAspect: { '@id': 'schema:GettingAccessHealthAspect' }, + GiveAction: { '@id': 'schema:GiveAction' }, + GlutenFreeDiet: { '@id': 'schema:GlutenFreeDiet' }, + GolfCourse: { '@id': 'schema:GolfCourse' }, + GovernmentBenefitsType: { '@id': 'schema:GovernmentBenefitsType' }, + GovernmentBuilding: { '@id': 'schema:GovernmentBuilding' }, + GovernmentOffice: { '@id': 'schema:GovernmentOffice' }, + GovernmentOrganization: { '@id': 'schema:GovernmentOrganization' }, + GovernmentPermit: { '@id': 'schema:GovernmentPermit' }, + GovernmentService: { '@id': 'schema:GovernmentService' }, + Grant: { '@id': 'schema:Grant' }, + GraphicNovel: { '@id': 'schema:GraphicNovel' }, + GroceryStore: { '@id': 'schema:GroceryStore' }, + GroupBoardingPolicy: { '@id': 'schema:GroupBoardingPolicy' }, + Guide: { '@id': 'schema:Guide' }, + Gynecologic: { '@id': 'schema:Gynecologic' }, + HVACBusiness: { '@id': 'schema:HVACBusiness' }, + Hackathon: { '@id': 'schema:Hackathon' }, + HairSalon: { '@id': 'schema:HairSalon' }, + HalalDiet: { '@id': 'schema:HalalDiet' }, + Hardcover: { '@id': 'schema:Hardcover' }, + HardwareStore: { '@id': 'schema:HardwareStore' }, + Head: { '@id': 'schema:Head' }, + HealthAndBeautyBusiness: { '@id': 'schema:HealthAndBeautyBusiness' }, + HealthAspectEnumeration: { '@id': 'schema:HealthAspectEnumeration' }, + HealthCare: { '@id': 'schema:HealthCare' }, + HealthClub: { '@id': 'schema:HealthClub' }, + HealthInsurancePlan: { '@id': 'schema:HealthInsurancePlan' }, + HealthPlanCostSharingSpecification: { + '@id': 'schema:HealthPlanCostSharingSpecification', + }, + HealthPlanFormulary: { '@id': 'schema:HealthPlanFormulary' }, + HealthPlanNetwork: { '@id': 'schema:HealthPlanNetwork' }, + HealthTopicContent: { '@id': 'schema:HealthTopicContent' }, + HearingImpairedSupported: { '@id': 'schema:HearingImpairedSupported' }, + Hematologic: { '@id': 'schema:Hematologic' }, + HighSchool: { '@id': 'schema:HighSchool' }, + HinduDiet: { '@id': 'schema:HinduDiet' }, + HinduTemple: { '@id': 'schema:HinduTemple' }, + HobbyShop: { '@id': 'schema:HobbyShop' }, + HomeAndConstructionBusiness: { '@id': 'schema:HomeAndConstructionBusiness' }, + HomeGoodsStore: { '@id': 'schema:HomeGoodsStore' }, + Homeopathic: { '@id': 'schema:Homeopathic' }, + Hospital: { '@id': 'schema:Hospital' }, + Hostel: { '@id': 'schema:Hostel' }, + Hotel: { '@id': 'schema:Hotel' }, + HotelRoom: { '@id': 'schema:HotelRoom' }, + House: { '@id': 'schema:House' }, + HousePainter: { '@id': 'schema:HousePainter' }, + HowItWorksHealthAspect: { '@id': 'schema:HowItWorksHealthAspect' }, + HowOrWhereHealthAspect: { '@id': 'schema:HowOrWhereHealthAspect' }, + HowTo: { '@id': 'schema:HowTo' }, + HowToDirection: { '@id': 'schema:HowToDirection' }, + HowToItem: { '@id': 'schema:HowToItem' }, + HowToSection: { '@id': 'schema:HowToSection' }, + HowToStep: { '@id': 'schema:HowToStep' }, + HowToSupply: { '@id': 'schema:HowToSupply' }, + HowToTip: { '@id': 'schema:HowToTip' }, + HowToTool: { '@id': 'schema:HowToTool' }, + HyperToc: { '@id': 'schema:HyperToc' }, + HyperTocEntry: { '@id': 'schema:HyperTocEntry' }, + IceCreamShop: { '@id': 'schema:IceCreamShop' }, + IgnoreAction: { '@id': 'schema:IgnoreAction' }, + ImageGallery: { '@id': 'schema:ImageGallery' }, + ImageObject: { '@id': 'schema:ImageObject' }, + ImagingTest: { '@id': 'schema:ImagingTest' }, + InForce: { '@id': 'schema:InForce' }, + InStock: { '@id': 'schema:InStock' }, + InStoreOnly: { '@id': 'schema:InStoreOnly' }, + IndividualProduct: { '@id': 'schema:IndividualProduct' }, + Infectious: { '@id': 'schema:Infectious' }, + InfectiousAgentClass: { '@id': 'schema:InfectiousAgentClass' }, + InfectiousDisease: { '@id': 'schema:InfectiousDisease' }, + InformAction: { '@id': 'schema:InformAction' }, + IngredientsHealthAspect: { '@id': 'schema:IngredientsHealthAspect' }, + InsertAction: { '@id': 'schema:InsertAction' }, + InstallAction: { '@id': 'schema:InstallAction' }, + Installment: { '@id': 'schema:Installment' }, + InsuranceAgency: { '@id': 'schema:InsuranceAgency' }, + Intangible: { '@id': 'schema:Intangible' }, + Integer: { '@id': 'schema:Integer' }, + InteractAction: { '@id': 'schema:InteractAction' }, + InteractionCounter: { '@id': 'schema:InteractionCounter' }, + InternationalTrial: { '@id': 'schema:InternationalTrial' }, + InternetCafe: { '@id': 'schema:InternetCafe' }, + InvestmentFund: { '@id': 'schema:InvestmentFund' }, + InvestmentOrDeposit: { '@id': 'schema:InvestmentOrDeposit' }, + InviteAction: { '@id': 'schema:InviteAction' }, + Invoice: { '@id': 'schema:Invoice' }, + InvoicePrice: { '@id': 'schema:InvoicePrice' }, + ItemAvailability: { '@id': 'schema:ItemAvailability' }, + ItemList: { '@id': 'schema:ItemList' }, + ItemListOrderAscending: { '@id': 'schema:ItemListOrderAscending' }, + ItemListOrderDescending: { '@id': 'schema:ItemListOrderDescending' }, + ItemListOrderType: { '@id': 'schema:ItemListOrderType' }, + ItemListUnordered: { '@id': 'schema:ItemListUnordered' }, + ItemPage: { '@id': 'schema:ItemPage' }, + JewelryStore: { '@id': 'schema:JewelryStore' }, + JobPosting: { '@id': 'schema:JobPosting' }, + JoinAction: { '@id': 'schema:JoinAction' }, + Joint: { '@id': 'schema:Joint' }, + KosherDiet: { '@id': 'schema:KosherDiet' }, + LaboratoryScience: { '@id': 'schema:LaboratoryScience' }, + LakeBodyOfWater: { '@id': 'schema:LakeBodyOfWater' }, + Landform: { '@id': 'schema:Landform' }, + LandmarksOrHistoricalBuildings: { + '@id': 'schema:LandmarksOrHistoricalBuildings', + }, + Language: { '@id': 'schema:Language' }, + LaserDiscFormat: { '@id': 'schema:LaserDiscFormat' }, + LearningResource: { '@id': 'schema:LearningResource' }, + LeaveAction: { '@id': 'schema:LeaveAction' }, + LeftHandDriving: { '@id': 'schema:LeftHandDriving' }, + LegalForceStatus: { '@id': 'schema:LegalForceStatus' }, + LegalService: { '@id': 'schema:LegalService' }, + LegalValueLevel: { '@id': 'schema:LegalValueLevel' }, + Legislation: { '@id': 'schema:Legislation' }, + LegislationObject: { '@id': 'schema:LegislationObject' }, + LegislativeBuilding: { '@id': 'schema:LegislativeBuilding' }, + LeisureTimeActivity: { '@id': 'schema:LeisureTimeActivity' }, + LendAction: { '@id': 'schema:LendAction' }, + Library: { '@id': 'schema:Library' }, + LibrarySystem: { '@id': 'schema:LibrarySystem' }, + LifestyleModification: { '@id': 'schema:LifestyleModification' }, + Ligament: { '@id': 'schema:Ligament' }, + LikeAction: { '@id': 'schema:LikeAction' }, + LimitedAvailability: { '@id': 'schema:LimitedAvailability' }, + LimitedByGuaranteeCharity: { '@id': 'schema:LimitedByGuaranteeCharity' }, + LinkRole: { '@id': 'schema:LinkRole' }, + LiquorStore: { '@id': 'schema:LiquorStore' }, + ListItem: { '@id': 'schema:ListItem' }, + ListPrice: { '@id': 'schema:ListPrice' }, + ListenAction: { '@id': 'schema:ListenAction' }, + LiteraryEvent: { '@id': 'schema:LiteraryEvent' }, + LiveAlbum: { '@id': 'schema:LiveAlbum' }, + LiveBlogPosting: { '@id': 'schema:LiveBlogPosting' }, + LivingWithHealthAspect: { '@id': 'schema:LivingWithHealthAspect' }, + LoanOrCredit: { '@id': 'schema:LoanOrCredit' }, + LocalBusiness: { '@id': 'schema:LocalBusiness' }, + LocationFeatureSpecification: { '@id': 'schema:LocationFeatureSpecification' }, + LockerDelivery: { '@id': 'schema:LockerDelivery' }, + Locksmith: { '@id': 'schema:Locksmith' }, + LodgingBusiness: { '@id': 'schema:LodgingBusiness' }, + LodgingReservation: { '@id': 'schema:LodgingReservation' }, + Longitudinal: { '@id': 'schema:Longitudinal' }, + LoseAction: { '@id': 'schema:LoseAction' }, + LowCalorieDiet: { '@id': 'schema:LowCalorieDiet' }, + LowFatDiet: { '@id': 'schema:LowFatDiet' }, + LowLactoseDiet: { '@id': 'schema:LowLactoseDiet' }, + LowSaltDiet: { '@id': 'schema:LowSaltDiet' }, + Lung: { '@id': 'schema:Lung' }, + LymphaticVessel: { '@id': 'schema:LymphaticVessel' }, + MRI: { '@id': 'schema:MRI' }, + MSRP: { '@id': 'schema:MSRP' }, + Male: { '@id': 'schema:Male' }, + Manuscript: { '@id': 'schema:Manuscript' }, + Map: { '@id': 'schema:Map' }, + MapCategoryType: { '@id': 'schema:MapCategoryType' }, + MarryAction: { '@id': 'schema:MarryAction' }, + Mass: { '@id': 'schema:Mass' }, + MathSolver: { '@id': 'schema:MathSolver' }, + MaximumDoseSchedule: { '@id': 'schema:MaximumDoseSchedule' }, + MayTreatHealthAspect: { '@id': 'schema:MayTreatHealthAspect' }, + MeasurementTypeEnumeration: { '@id': 'schema:MeasurementTypeEnumeration' }, + MediaGallery: { '@id': 'schema:MediaGallery' }, + MediaManipulationRatingEnumeration: { + '@id': 'schema:MediaManipulationRatingEnumeration', + }, + MediaObject: { '@id': 'schema:MediaObject' }, + MediaReview: { '@id': 'schema:MediaReview' }, + MediaSubscription: { '@id': 'schema:MediaSubscription' }, + MedicalAudience: { '@id': 'schema:MedicalAudience' }, + MedicalAudienceType: { '@id': 'schema:MedicalAudienceType' }, + MedicalBusiness: { '@id': 'schema:MedicalBusiness' }, + MedicalCause: { '@id': 'schema:MedicalCause' }, + MedicalClinic: { '@id': 'schema:MedicalClinic' }, + MedicalCode: { '@id': 'schema:MedicalCode' }, + MedicalCondition: { '@id': 'schema:MedicalCondition' }, + MedicalConditionStage: { '@id': 'schema:MedicalConditionStage' }, + MedicalContraindication: { '@id': 'schema:MedicalContraindication' }, + MedicalDevice: { '@id': 'schema:MedicalDevice' }, + MedicalDevicePurpose: { '@id': 'schema:MedicalDevicePurpose' }, + MedicalEntity: { '@id': 'schema:MedicalEntity' }, + MedicalEnumeration: { '@id': 'schema:MedicalEnumeration' }, + MedicalEvidenceLevel: { '@id': 'schema:MedicalEvidenceLevel' }, + MedicalGuideline: { '@id': 'schema:MedicalGuideline' }, + MedicalGuidelineContraindication: { + '@id': 'schema:MedicalGuidelineContraindication', + }, + MedicalGuidelineRecommendation: { + '@id': 'schema:MedicalGuidelineRecommendation', + }, + MedicalImagingTechnique: { '@id': 'schema:MedicalImagingTechnique' }, + MedicalIndication: { '@id': 'schema:MedicalIndication' }, + MedicalIntangible: { '@id': 'schema:MedicalIntangible' }, + MedicalObservationalStudy: { '@id': 'schema:MedicalObservationalStudy' }, + MedicalObservationalStudyDesign: { + '@id': 'schema:MedicalObservationalStudyDesign', + }, + MedicalOrganization: { '@id': 'schema:MedicalOrganization' }, + MedicalProcedure: { '@id': 'schema:MedicalProcedure' }, + MedicalProcedureType: { '@id': 'schema:MedicalProcedureType' }, + MedicalResearcher: { '@id': 'schema:MedicalResearcher' }, + MedicalRiskCalculator: { '@id': 'schema:MedicalRiskCalculator' }, + MedicalRiskEstimator: { '@id': 'schema:MedicalRiskEstimator' }, + MedicalRiskFactor: { '@id': 'schema:MedicalRiskFactor' }, + MedicalRiskScore: { '@id': 'schema:MedicalRiskScore' }, + MedicalScholarlyArticle: { '@id': 'schema:MedicalScholarlyArticle' }, + MedicalSign: { '@id': 'schema:MedicalSign' }, + MedicalSignOrSymptom: { '@id': 'schema:MedicalSignOrSymptom' }, + MedicalSpecialty: { '@id': 'schema:MedicalSpecialty' }, + MedicalStudy: { '@id': 'schema:MedicalStudy' }, + MedicalStudyStatus: { '@id': 'schema:MedicalStudyStatus' }, + MedicalSymptom: { '@id': 'schema:MedicalSymptom' }, + MedicalTest: { '@id': 'schema:MedicalTest' }, + MedicalTestPanel: { '@id': 'schema:MedicalTestPanel' }, + MedicalTherapy: { '@id': 'schema:MedicalTherapy' }, + MedicalTrial: { '@id': 'schema:MedicalTrial' }, + MedicalTrialDesign: { '@id': 'schema:MedicalTrialDesign' }, + MedicalWebPage: { '@id': 'schema:MedicalWebPage' }, + MedicineSystem: { '@id': 'schema:MedicineSystem' }, + MeetingRoom: { '@id': 'schema:MeetingRoom' }, + MensClothingStore: { '@id': 'schema:MensClothingStore' }, + Menu: { '@id': 'schema:Menu' }, + MenuItem: { '@id': 'schema:MenuItem' }, + MenuSection: { '@id': 'schema:MenuSection' }, + MerchantReturnEnumeration: { '@id': 'schema:MerchantReturnEnumeration' }, + MerchantReturnFiniteReturnWindow: { + '@id': 'schema:MerchantReturnFiniteReturnWindow', + }, + MerchantReturnNotPermitted: { '@id': 'schema:MerchantReturnNotPermitted' }, + MerchantReturnPolicy: { '@id': 'schema:MerchantReturnPolicy' }, + MerchantReturnUnlimitedWindow: { + '@id': 'schema:MerchantReturnUnlimitedWindow', + }, + MerchantReturnUnspecified: { '@id': 'schema:MerchantReturnUnspecified' }, + Message: { '@id': 'schema:Message' }, + MiddleSchool: { '@id': 'schema:MiddleSchool' }, + Midwifery: { '@id': 'schema:Midwifery' }, + MinimumAdvertisedPrice: { '@id': 'schema:MinimumAdvertisedPrice' }, + MisconceptionsHealthAspect: { '@id': 'schema:MisconceptionsHealthAspect' }, + MixedEventAttendanceMode: { '@id': 'schema:MixedEventAttendanceMode' }, + MixtapeAlbum: { '@id': 'schema:MixtapeAlbum' }, + MobileApplication: { '@id': 'schema:MobileApplication' }, + MobilePhoneStore: { '@id': 'schema:MobilePhoneStore' }, + Monday: { '@id': 'schema:Monday' }, + MonetaryAmount: { '@id': 'schema:MonetaryAmount' }, + MonetaryAmountDistribution: { '@id': 'schema:MonetaryAmountDistribution' }, + MonetaryGrant: { '@id': 'schema:MonetaryGrant' }, + MoneyTransfer: { '@id': 'schema:MoneyTransfer' }, + MortgageLoan: { '@id': 'schema:MortgageLoan' }, + Mosque: { '@id': 'schema:Mosque' }, + Motel: { '@id': 'schema:Motel' }, + Motorcycle: { '@id': 'schema:Motorcycle' }, + MotorcycleDealer: { '@id': 'schema:MotorcycleDealer' }, + MotorcycleRepair: { '@id': 'schema:MotorcycleRepair' }, + MotorizedBicycle: { '@id': 'schema:MotorizedBicycle' }, + Mountain: { '@id': 'schema:Mountain' }, + MoveAction: { '@id': 'schema:MoveAction' }, + Movie: { '@id': 'schema:Movie' }, + MovieClip: { '@id': 'schema:MovieClip' }, + MovieRentalStore: { '@id': 'schema:MovieRentalStore' }, + MovieSeries: { '@id': 'schema:MovieSeries' }, + MovieTheater: { '@id': 'schema:MovieTheater' }, + MovingCompany: { '@id': 'schema:MovingCompany' }, + MultiCenterTrial: { '@id': 'schema:MultiCenterTrial' }, + MultiPlayer: { '@id': 'schema:MultiPlayer' }, + MulticellularParasite: { '@id': 'schema:MulticellularParasite' }, + Muscle: { '@id': 'schema:Muscle' }, + Musculoskeletal: { '@id': 'schema:Musculoskeletal' }, + MusculoskeletalExam: { '@id': 'schema:MusculoskeletalExam' }, + Museum: { '@id': 'schema:Museum' }, + MusicAlbum: { '@id': 'schema:MusicAlbum' }, + MusicAlbumProductionType: { '@id': 'schema:MusicAlbumProductionType' }, + MusicAlbumReleaseType: { '@id': 'schema:MusicAlbumReleaseType' }, + MusicComposition: { '@id': 'schema:MusicComposition' }, + MusicEvent: { '@id': 'schema:MusicEvent' }, + MusicGroup: { '@id': 'schema:MusicGroup' }, + MusicPlaylist: { '@id': 'schema:MusicPlaylist' }, + MusicRecording: { '@id': 'schema:MusicRecording' }, + MusicRelease: { '@id': 'schema:MusicRelease' }, + MusicReleaseFormatType: { '@id': 'schema:MusicReleaseFormatType' }, + MusicStore: { '@id': 'schema:MusicStore' }, + MusicVenue: { '@id': 'schema:MusicVenue' }, + MusicVideoObject: { '@id': 'schema:MusicVideoObject' }, + NGO: { '@id': 'schema:NGO' }, + NLNonprofitType: { '@id': 'schema:NLNonprofitType' }, + NailSalon: { '@id': 'schema:NailSalon' }, + Neck: { '@id': 'schema:Neck' }, + Nerve: { '@id': 'schema:Nerve' }, + Neuro: { '@id': 'schema:Neuro' }, + Neurologic: { '@id': 'schema:Neurologic' }, + NewCondition: { '@id': 'schema:NewCondition' }, + NewsArticle: { '@id': 'schema:NewsArticle' }, + NewsMediaOrganization: { '@id': 'schema:NewsMediaOrganization' }, + Newspaper: { '@id': 'schema:Newspaper' }, + NightClub: { '@id': 'schema:NightClub' }, + NoninvasiveProcedure: { '@id': 'schema:NoninvasiveProcedure' }, + Nonprofit501a: { '@id': 'schema:Nonprofit501a' }, + Nonprofit501c1: { '@id': 'schema:Nonprofit501c1' }, + Nonprofit501c10: { '@id': 'schema:Nonprofit501c10' }, + Nonprofit501c11: { '@id': 'schema:Nonprofit501c11' }, + Nonprofit501c12: { '@id': 'schema:Nonprofit501c12' }, + Nonprofit501c13: { '@id': 'schema:Nonprofit501c13' }, + Nonprofit501c14: { '@id': 'schema:Nonprofit501c14' }, + Nonprofit501c15: { '@id': 'schema:Nonprofit501c15' }, + Nonprofit501c16: { '@id': 'schema:Nonprofit501c16' }, + Nonprofit501c17: { '@id': 'schema:Nonprofit501c17' }, + Nonprofit501c18: { '@id': 'schema:Nonprofit501c18' }, + Nonprofit501c19: { '@id': 'schema:Nonprofit501c19' }, + Nonprofit501c2: { '@id': 'schema:Nonprofit501c2' }, + Nonprofit501c20: { '@id': 'schema:Nonprofit501c20' }, + Nonprofit501c21: { '@id': 'schema:Nonprofit501c21' }, + Nonprofit501c22: { '@id': 'schema:Nonprofit501c22' }, + Nonprofit501c23: { '@id': 'schema:Nonprofit501c23' }, + Nonprofit501c24: { '@id': 'schema:Nonprofit501c24' }, + Nonprofit501c25: { '@id': 'schema:Nonprofit501c25' }, + Nonprofit501c26: { '@id': 'schema:Nonprofit501c26' }, + Nonprofit501c27: { '@id': 'schema:Nonprofit501c27' }, + Nonprofit501c28: { '@id': 'schema:Nonprofit501c28' }, + Nonprofit501c3: { '@id': 'schema:Nonprofit501c3' }, + Nonprofit501c4: { '@id': 'schema:Nonprofit501c4' }, + Nonprofit501c5: { '@id': 'schema:Nonprofit501c5' }, + Nonprofit501c6: { '@id': 'schema:Nonprofit501c6' }, + Nonprofit501c7: { '@id': 'schema:Nonprofit501c7' }, + Nonprofit501c8: { '@id': 'schema:Nonprofit501c8' }, + Nonprofit501c9: { '@id': 'schema:Nonprofit501c9' }, + Nonprofit501d: { '@id': 'schema:Nonprofit501d' }, + Nonprofit501e: { '@id': 'schema:Nonprofit501e' }, + Nonprofit501f: { '@id': 'schema:Nonprofit501f' }, + Nonprofit501k: { '@id': 'schema:Nonprofit501k' }, + Nonprofit501n: { '@id': 'schema:Nonprofit501n' }, + Nonprofit501q: { '@id': 'schema:Nonprofit501q' }, + Nonprofit527: { '@id': 'schema:Nonprofit527' }, + NonprofitANBI: { '@id': 'schema:NonprofitANBI' }, + NonprofitSBBI: { '@id': 'schema:NonprofitSBBI' }, + NonprofitType: { '@id': 'schema:NonprofitType' }, + Nose: { '@id': 'schema:Nose' }, + NotInForce: { '@id': 'schema:NotInForce' }, + NotYetRecruiting: { '@id': 'schema:NotYetRecruiting' }, + Notary: { '@id': 'schema:Notary' }, + NoteDigitalDocument: { '@id': 'schema:NoteDigitalDocument' }, + Number: { '@id': 'schema:Number' }, + Nursing: { '@id': 'schema:Nursing' }, + NutritionInformation: { '@id': 'schema:NutritionInformation' }, + OTC: { '@id': 'schema:OTC' }, + Observation: { '@id': 'schema:Observation' }, + Observational: { '@id': 'schema:Observational' }, + Obstetric: { '@id': 'schema:Obstetric' }, + Occupation: { '@id': 'schema:Occupation' }, + OccupationalActivity: { '@id': 'schema:OccupationalActivity' }, + OccupationalExperienceRequirements: { + '@id': 'schema:OccupationalExperienceRequirements', + }, + OccupationalTherapy: { '@id': 'schema:OccupationalTherapy' }, + OceanBodyOfWater: { '@id': 'schema:OceanBodyOfWater' }, + Offer: { '@id': 'schema:Offer' }, + OfferCatalog: { '@id': 'schema:OfferCatalog' }, + OfferForLease: { '@id': 'schema:OfferForLease' }, + OfferForPurchase: { '@id': 'schema:OfferForPurchase' }, + OfferItemCondition: { '@id': 'schema:OfferItemCondition' }, + OfferShippingDetails: { '@id': 'schema:OfferShippingDetails' }, + OfficeEquipmentStore: { '@id': 'schema:OfficeEquipmentStore' }, + OfficialLegalValue: { '@id': 'schema:OfficialLegalValue' }, + OfflineEventAttendanceMode: { '@id': 'schema:OfflineEventAttendanceMode' }, + OfflinePermanently: { '@id': 'schema:OfflinePermanently' }, + OfflineTemporarily: { '@id': 'schema:OfflineTemporarily' }, + OnDemandEvent: { '@id': 'schema:OnDemandEvent' }, + OnSitePickup: { '@id': 'schema:OnSitePickup' }, + Oncologic: { '@id': 'schema:Oncologic' }, + OneTimePayments: { '@id': 'schema:OneTimePayments' }, + Online: { '@id': 'schema:Online' }, + OnlineEventAttendanceMode: { '@id': 'schema:OnlineEventAttendanceMode' }, + OnlineFull: { '@id': 'schema:OnlineFull' }, + OnlineOnly: { '@id': 'schema:OnlineOnly' }, + OpenTrial: { '@id': 'schema:OpenTrial' }, + OpeningHoursSpecification: { '@id': 'schema:OpeningHoursSpecification' }, + OpinionNewsArticle: { '@id': 'schema:OpinionNewsArticle' }, + Optician: { '@id': 'schema:Optician' }, + Optometric: { '@id': 'schema:Optometric' }, + Order: { '@id': 'schema:Order' }, + OrderAction: { '@id': 'schema:OrderAction' }, + OrderCancelled: { '@id': 'schema:OrderCancelled' }, + OrderDelivered: { '@id': 'schema:OrderDelivered' }, + OrderInTransit: { '@id': 'schema:OrderInTransit' }, + OrderItem: { '@id': 'schema:OrderItem' }, + OrderPaymentDue: { '@id': 'schema:OrderPaymentDue' }, + OrderPickupAvailable: { '@id': 'schema:OrderPickupAvailable' }, + OrderProblem: { '@id': 'schema:OrderProblem' }, + OrderProcessing: { '@id': 'schema:OrderProcessing' }, + OrderReturned: { '@id': 'schema:OrderReturned' }, + OrderStatus: { '@id': 'schema:OrderStatus' }, + Organization: { '@id': 'schema:Organization' }, + OrganizationRole: { '@id': 'schema:OrganizationRole' }, + OrganizeAction: { '@id': 'schema:OrganizeAction' }, + OriginalMediaContent: { '@id': 'schema:OriginalMediaContent' }, + OriginalShippingFees: { '@id': 'schema:OriginalShippingFees' }, + Osteopathic: { '@id': 'schema:Osteopathic' }, + Otolaryngologic: { '@id': 'schema:Otolaryngologic' }, + OutOfStock: { '@id': 'schema:OutOfStock' }, + OutletStore: { '@id': 'schema:OutletStore' }, + OverviewHealthAspect: { '@id': 'schema:OverviewHealthAspect' }, + OwnershipInfo: { '@id': 'schema:OwnershipInfo' }, + PET: { '@id': 'schema:PET' }, + PaidLeave: { '@id': 'schema:PaidLeave' }, + PaintAction: { '@id': 'schema:PaintAction' }, + Painting: { '@id': 'schema:Painting' }, + PalliativeProcedure: { '@id': 'schema:PalliativeProcedure' }, + Paperback: { '@id': 'schema:Paperback' }, + ParcelDelivery: { '@id': 'schema:ParcelDelivery' }, + ParcelService: { '@id': 'schema:ParcelService' }, + ParentAudience: { '@id': 'schema:ParentAudience' }, + ParentalSupport: { '@id': 'schema:ParentalSupport' }, + Park: { '@id': 'schema:Park' }, + ParkingFacility: { '@id': 'schema:ParkingFacility' }, + ParkingMap: { '@id': 'schema:ParkingMap' }, + PartiallyInForce: { '@id': 'schema:PartiallyInForce' }, + Pathology: { '@id': 'schema:Pathology' }, + PathologyTest: { '@id': 'schema:PathologyTest' }, + Patient: { '@id': 'schema:Patient' }, + PatientExperienceHealthAspect: { + '@id': 'schema:PatientExperienceHealthAspect', + }, + PawnShop: { '@id': 'schema:PawnShop' }, + PayAction: { '@id': 'schema:PayAction' }, + PaymentAutomaticallyApplied: { '@id': 'schema:PaymentAutomaticallyApplied' }, + PaymentCard: { '@id': 'schema:PaymentCard' }, + PaymentChargeSpecification: { '@id': 'schema:PaymentChargeSpecification' }, + PaymentComplete: { '@id': 'schema:PaymentComplete' }, + PaymentDeclined: { '@id': 'schema:PaymentDeclined' }, + PaymentDue: { '@id': 'schema:PaymentDue' }, + PaymentMethod: { '@id': 'schema:PaymentMethod' }, + PaymentPastDue: { '@id': 'schema:PaymentPastDue' }, + PaymentService: { '@id': 'schema:PaymentService' }, + PaymentStatusType: { '@id': 'schema:PaymentStatusType' }, + Pediatric: { '@id': 'schema:Pediatric' }, + PeopleAudience: { '@id': 'schema:PeopleAudience' }, + PercutaneousProcedure: { '@id': 'schema:PercutaneousProcedure' }, + PerformAction: { '@id': 'schema:PerformAction' }, + PerformanceRole: { '@id': 'schema:PerformanceRole' }, + PerformingArtsTheater: { '@id': 'schema:PerformingArtsTheater' }, + PerformingGroup: { '@id': 'schema:PerformingGroup' }, + Periodical: { '@id': 'schema:Periodical' }, + Permit: { '@id': 'schema:Permit' }, + Person: { '@id': 'schema:Person' }, + PetStore: { '@id': 'schema:PetStore' }, + Pharmacy: { '@id': 'schema:Pharmacy' }, + PharmacySpecialty: { '@id': 'schema:PharmacySpecialty' }, + Photograph: { '@id': 'schema:Photograph' }, + PhotographAction: { '@id': 'schema:PhotographAction' }, + PhysicalActivity: { '@id': 'schema:PhysicalActivity' }, + PhysicalActivityCategory: { '@id': 'schema:PhysicalActivityCategory' }, + PhysicalExam: { '@id': 'schema:PhysicalExam' }, + PhysicalTherapy: { '@id': 'schema:PhysicalTherapy' }, + Physician: { '@id': 'schema:Physician' }, + Physiotherapy: { '@id': 'schema:Physiotherapy' }, + Place: { '@id': 'schema:Place' }, + PlaceOfWorship: { '@id': 'schema:PlaceOfWorship' }, + PlaceboControlledTrial: { '@id': 'schema:PlaceboControlledTrial' }, + PlanAction: { '@id': 'schema:PlanAction' }, + PlasticSurgery: { '@id': 'schema:PlasticSurgery' }, + Play: { '@id': 'schema:Play' }, + PlayAction: { '@id': 'schema:PlayAction' }, + Playground: { '@id': 'schema:Playground' }, + Plumber: { '@id': 'schema:Plumber' }, + PodcastEpisode: { '@id': 'schema:PodcastEpisode' }, + PodcastSeason: { '@id': 'schema:PodcastSeason' }, + PodcastSeries: { '@id': 'schema:PodcastSeries' }, + Podiatric: { '@id': 'schema:Podiatric' }, + PoliceStation: { '@id': 'schema:PoliceStation' }, + Pond: { '@id': 'schema:Pond' }, + PostOffice: { '@id': 'schema:PostOffice' }, + PostalAddress: { '@id': 'schema:PostalAddress' }, + PostalCodeRangeSpecification: { '@id': 'schema:PostalCodeRangeSpecification' }, + Poster: { '@id': 'schema:Poster' }, + PotentialActionStatus: { '@id': 'schema:PotentialActionStatus' }, + PreOrder: { '@id': 'schema:PreOrder' }, + PreOrderAction: { '@id': 'schema:PreOrderAction' }, + PreSale: { '@id': 'schema:PreSale' }, + PregnancyHealthAspect: { '@id': 'schema:PregnancyHealthAspect' }, + PrependAction: { '@id': 'schema:PrependAction' }, + Preschool: { '@id': 'schema:Preschool' }, + PrescriptionOnly: { '@id': 'schema:PrescriptionOnly' }, + PresentationDigitalDocument: { '@id': 'schema:PresentationDigitalDocument' }, + PreventionHealthAspect: { '@id': 'schema:PreventionHealthAspect' }, + PreventionIndication: { '@id': 'schema:PreventionIndication' }, + PriceComponentTypeEnumeration: { + '@id': 'schema:PriceComponentTypeEnumeration', + }, + PriceSpecification: { '@id': 'schema:PriceSpecification' }, + PriceTypeEnumeration: { '@id': 'schema:PriceTypeEnumeration' }, + PrimaryCare: { '@id': 'schema:PrimaryCare' }, + Prion: { '@id': 'schema:Prion' }, + Product: { '@id': 'schema:Product' }, + ProductCollection: { '@id': 'schema:ProductCollection' }, + ProductGroup: { '@id': 'schema:ProductGroup' }, + ProductModel: { '@id': 'schema:ProductModel' }, + ProductReturnEnumeration: { '@id': 'schema:ProductReturnEnumeration' }, + ProductReturnFiniteReturnWindow: { + '@id': 'schema:ProductReturnFiniteReturnWindow', + }, + ProductReturnNotPermitted: { '@id': 'schema:ProductReturnNotPermitted' }, + ProductReturnPolicy: { '@id': 'schema:ProductReturnPolicy' }, + ProductReturnUnlimitedWindow: { '@id': 'schema:ProductReturnUnlimitedWindow' }, + ProductReturnUnspecified: { '@id': 'schema:ProductReturnUnspecified' }, + ProfessionalService: { '@id': 'schema:ProfessionalService' }, + ProfilePage: { '@id': 'schema:ProfilePage' }, + PrognosisHealthAspect: { '@id': 'schema:PrognosisHealthAspect' }, + ProgramMembership: { '@id': 'schema:ProgramMembership' }, + Project: { '@id': 'schema:Project' }, + PronounceableText: { '@id': 'schema:PronounceableText' }, + Property: { '@id': 'schema:Property' }, + PropertyValue: { '@id': 'schema:PropertyValue' }, + PropertyValueSpecification: { '@id': 'schema:PropertyValueSpecification' }, + Protozoa: { '@id': 'schema:Protozoa' }, + Psychiatric: { '@id': 'schema:Psychiatric' }, + PsychologicalTreatment: { '@id': 'schema:PsychologicalTreatment' }, + PublicHealth: { '@id': 'schema:PublicHealth' }, + PublicHolidays: { '@id': 'schema:PublicHolidays' }, + PublicSwimmingPool: { '@id': 'schema:PublicSwimmingPool' }, + PublicToilet: { '@id': 'schema:PublicToilet' }, + PublicationEvent: { '@id': 'schema:PublicationEvent' }, + PublicationIssue: { '@id': 'schema:PublicationIssue' }, + PublicationVolume: { '@id': 'schema:PublicationVolume' }, + Pulmonary: { '@id': 'schema:Pulmonary' }, + QAPage: { '@id': 'schema:QAPage' }, + QualitativeValue: { '@id': 'schema:QualitativeValue' }, + QuantitativeValue: { '@id': 'schema:QuantitativeValue' }, + QuantitativeValueDistribution: { + '@id': 'schema:QuantitativeValueDistribution', + }, + Quantity: { '@id': 'schema:Quantity' }, + Question: { '@id': 'schema:Question' }, + Quiz: { '@id': 'schema:Quiz' }, + Quotation: { '@id': 'schema:Quotation' }, + QuoteAction: { '@id': 'schema:QuoteAction' }, + RVPark: { '@id': 'schema:RVPark' }, + RadiationTherapy: { '@id': 'schema:RadiationTherapy' }, + RadioBroadcastService: { '@id': 'schema:RadioBroadcastService' }, + RadioChannel: { '@id': 'schema:RadioChannel' }, + RadioClip: { '@id': 'schema:RadioClip' }, + RadioEpisode: { '@id': 'schema:RadioEpisode' }, + RadioSeason: { '@id': 'schema:RadioSeason' }, + RadioSeries: { '@id': 'schema:RadioSeries' }, + RadioStation: { '@id': 'schema:RadioStation' }, + Radiography: { '@id': 'schema:Radiography' }, + RandomizedTrial: { '@id': 'schema:RandomizedTrial' }, + Rating: { '@id': 'schema:Rating' }, + ReactAction: { '@id': 'schema:ReactAction' }, + ReadAction: { '@id': 'schema:ReadAction' }, + ReadPermission: { '@id': 'schema:ReadPermission' }, + RealEstateAgent: { '@id': 'schema:RealEstateAgent' }, + RealEstateListing: { '@id': 'schema:RealEstateListing' }, + RearWheelDriveConfiguration: { '@id': 'schema:RearWheelDriveConfiguration' }, + ReceiveAction: { '@id': 'schema:ReceiveAction' }, + Recipe: { '@id': 'schema:Recipe' }, + Recommendation: { '@id': 'schema:Recommendation' }, + RecommendedDoseSchedule: { '@id': 'schema:RecommendedDoseSchedule' }, + Recruiting: { '@id': 'schema:Recruiting' }, + RecyclingCenter: { '@id': 'schema:RecyclingCenter' }, + RefundTypeEnumeration: { '@id': 'schema:RefundTypeEnumeration' }, + RefurbishedCondition: { '@id': 'schema:RefurbishedCondition' }, + RegisterAction: { '@id': 'schema:RegisterAction' }, + Registry: { '@id': 'schema:Registry' }, + ReimbursementCap: { '@id': 'schema:ReimbursementCap' }, + RejectAction: { '@id': 'schema:RejectAction' }, + RelatedTopicsHealthAspect: { '@id': 'schema:RelatedTopicsHealthAspect' }, + RemixAlbum: { '@id': 'schema:RemixAlbum' }, + Renal: { '@id': 'schema:Renal' }, + RentAction: { '@id': 'schema:RentAction' }, + RentalCarReservation: { '@id': 'schema:RentalCarReservation' }, + RentalVehicleUsage: { '@id': 'schema:RentalVehicleUsage' }, + RepaymentSpecification: { '@id': 'schema:RepaymentSpecification' }, + ReplaceAction: { '@id': 'schema:ReplaceAction' }, + ReplyAction: { '@id': 'schema:ReplyAction' }, + Report: { '@id': 'schema:Report' }, + ReportageNewsArticle: { '@id': 'schema:ReportageNewsArticle' }, + ReportedDoseSchedule: { '@id': 'schema:ReportedDoseSchedule' }, + ResearchProject: { '@id': 'schema:ResearchProject' }, + Researcher: { '@id': 'schema:Researcher' }, + Reservation: { '@id': 'schema:Reservation' }, + ReservationCancelled: { '@id': 'schema:ReservationCancelled' }, + ReservationConfirmed: { '@id': 'schema:ReservationConfirmed' }, + ReservationHold: { '@id': 'schema:ReservationHold' }, + ReservationPackage: { '@id': 'schema:ReservationPackage' }, + ReservationPending: { '@id': 'schema:ReservationPending' }, + ReservationStatusType: { '@id': 'schema:ReservationStatusType' }, + ReserveAction: { '@id': 'schema:ReserveAction' }, + Reservoir: { '@id': 'schema:Reservoir' }, + Residence: { '@id': 'schema:Residence' }, + Resort: { '@id': 'schema:Resort' }, + RespiratoryTherapy: { '@id': 'schema:RespiratoryTherapy' }, + Restaurant: { '@id': 'schema:Restaurant' }, + RestockingFees: { '@id': 'schema:RestockingFees' }, + RestrictedDiet: { '@id': 'schema:RestrictedDiet' }, + ResultsAvailable: { '@id': 'schema:ResultsAvailable' }, + ResultsNotAvailable: { '@id': 'schema:ResultsNotAvailable' }, + ResumeAction: { '@id': 'schema:ResumeAction' }, + Retail: { '@id': 'schema:Retail' }, + ReturnAction: { '@id': 'schema:ReturnAction' }, + ReturnFeesEnumeration: { '@id': 'schema:ReturnFeesEnumeration' }, + ReturnShippingFees: { '@id': 'schema:ReturnShippingFees' }, + Review: { '@id': 'schema:Review' }, + ReviewAction: { '@id': 'schema:ReviewAction' }, + ReviewNewsArticle: { '@id': 'schema:ReviewNewsArticle' }, + Rheumatologic: { '@id': 'schema:Rheumatologic' }, + RightHandDriving: { '@id': 'schema:RightHandDriving' }, + RisksOrComplicationsHealthAspect: { + '@id': 'schema:RisksOrComplicationsHealthAspect', + }, + RiverBodyOfWater: { '@id': 'schema:RiverBodyOfWater' }, + Role: { '@id': 'schema:Role' }, + RoofingContractor: { '@id': 'schema:RoofingContractor' }, + Room: { '@id': 'schema:Room' }, + RsvpAction: { '@id': 'schema:RsvpAction' }, + RsvpResponseMaybe: { '@id': 'schema:RsvpResponseMaybe' }, + RsvpResponseNo: { '@id': 'schema:RsvpResponseNo' }, + RsvpResponseType: { '@id': 'schema:RsvpResponseType' }, + RsvpResponseYes: { '@id': 'schema:RsvpResponseYes' }, + SRP: { '@id': 'schema:SRP' }, + SafetyHealthAspect: { '@id': 'schema:SafetyHealthAspect' }, + SaleEvent: { '@id': 'schema:SaleEvent' }, + SalePrice: { '@id': 'schema:SalePrice' }, + SatireOrParodyContent: { '@id': 'schema:SatireOrParodyContent' }, + SatiricalArticle: { '@id': 'schema:SatiricalArticle' }, + Saturday: { '@id': 'schema:Saturday' }, + Schedule: { '@id': 'schema:Schedule' }, + ScheduleAction: { '@id': 'schema:ScheduleAction' }, + ScholarlyArticle: { '@id': 'schema:ScholarlyArticle' }, + School: { '@id': 'schema:School' }, + SchoolDistrict: { '@id': 'schema:SchoolDistrict' }, + ScreeningEvent: { '@id': 'schema:ScreeningEvent' }, + ScreeningHealthAspect: { '@id': 'schema:ScreeningHealthAspect' }, + Sculpture: { '@id': 'schema:Sculpture' }, + SeaBodyOfWater: { '@id': 'schema:SeaBodyOfWater' }, + SearchAction: { '@id': 'schema:SearchAction' }, + SearchResultsPage: { '@id': 'schema:SearchResultsPage' }, + Season: { '@id': 'schema:Season' }, + Seat: { '@id': 'schema:Seat' }, + SeatingMap: { '@id': 'schema:SeatingMap' }, + SeeDoctorHealthAspect: { '@id': 'schema:SeeDoctorHealthAspect' }, + SeekToAction: { '@id': 'schema:SeekToAction' }, + SelfCareHealthAspect: { '@id': 'schema:SelfCareHealthAspect' }, + SelfStorage: { '@id': 'schema:SelfStorage' }, + SellAction: { '@id': 'schema:SellAction' }, + SendAction: { '@id': 'schema:SendAction' }, + Series: { '@id': 'schema:Series' }, + Service: { '@id': 'schema:Service' }, + ServiceChannel: { '@id': 'schema:ServiceChannel' }, + ShareAction: { '@id': 'schema:ShareAction' }, + SheetMusic: { '@id': 'schema:SheetMusic' }, + ShippingDeliveryTime: { '@id': 'schema:ShippingDeliveryTime' }, + ShippingRateSettings: { '@id': 'schema:ShippingRateSettings' }, + ShoeStore: { '@id': 'schema:ShoeStore' }, + ShoppingCenter: { '@id': 'schema:ShoppingCenter' }, + ShortStory: { '@id': 'schema:ShortStory' }, + SideEffectsHealthAspect: { '@id': 'schema:SideEffectsHealthAspect' }, + SingleBlindedTrial: { '@id': 'schema:SingleBlindedTrial' }, + SingleCenterTrial: { '@id': 'schema:SingleCenterTrial' }, + SingleFamilyResidence: { '@id': 'schema:SingleFamilyResidence' }, + SinglePlayer: { '@id': 'schema:SinglePlayer' }, + SingleRelease: { '@id': 'schema:SingleRelease' }, + SiteNavigationElement: { '@id': 'schema:SiteNavigationElement' }, + SizeGroupEnumeration: { '@id': 'schema:SizeGroupEnumeration' }, + SizeSpecification: { '@id': 'schema:SizeSpecification' }, + SizeSystemEnumeration: { '@id': 'schema:SizeSystemEnumeration' }, + SizeSystemImperial: { '@id': 'schema:SizeSystemImperial' }, + SizeSystemMetric: { '@id': 'schema:SizeSystemMetric' }, + SkiResort: { '@id': 'schema:SkiResort' }, + Skin: { '@id': 'schema:Skin' }, + SocialEvent: { '@id': 'schema:SocialEvent' }, + SocialMediaPosting: { '@id': 'schema:SocialMediaPosting' }, + SoftwareApplication: { '@id': 'schema:SoftwareApplication' }, + SoftwareSourceCode: { '@id': 'schema:SoftwareSourceCode' }, + SoldOut: { '@id': 'schema:SoldOut' }, + SolveMathAction: { '@id': 'schema:SolveMathAction' }, + SomeProducts: { '@id': 'schema:SomeProducts' }, + SoundtrackAlbum: { '@id': 'schema:SoundtrackAlbum' }, + SpeakableSpecification: { '@id': 'schema:SpeakableSpecification' }, + SpecialAnnouncement: { '@id': 'schema:SpecialAnnouncement' }, + Specialty: { '@id': 'schema:Specialty' }, + SpeechPathology: { '@id': 'schema:SpeechPathology' }, + SpokenWordAlbum: { '@id': 'schema:SpokenWordAlbum' }, + SportingGoodsStore: { '@id': 'schema:SportingGoodsStore' }, + SportsActivityLocation: { '@id': 'schema:SportsActivityLocation' }, + SportsClub: { '@id': 'schema:SportsClub' }, + SportsEvent: { '@id': 'schema:SportsEvent' }, + SportsOrganization: { '@id': 'schema:SportsOrganization' }, + SportsTeam: { '@id': 'schema:SportsTeam' }, + SpreadsheetDigitalDocument: { '@id': 'schema:SpreadsheetDigitalDocument' }, + StadiumOrArena: { '@id': 'schema:StadiumOrArena' }, + StagedContent: { '@id': 'schema:StagedContent' }, + StagesHealthAspect: { '@id': 'schema:StagesHealthAspect' }, + State: { '@id': 'schema:State' }, + StatisticalPopulation: { '@id': 'schema:StatisticalPopulation' }, + StatusEnumeration: { '@id': 'schema:StatusEnumeration' }, + SteeringPositionValue: { '@id': 'schema:SteeringPositionValue' }, + Store: { '@id': 'schema:Store' }, + StoreCreditRefund: { '@id': 'schema:StoreCreditRefund' }, + StrengthTraining: { '@id': 'schema:StrengthTraining' }, + StructuredValue: { '@id': 'schema:StructuredValue' }, + StudioAlbum: { '@id': 'schema:StudioAlbum' }, + StupidType: { '@id': 'schema:StupidType' }, + SubscribeAction: { '@id': 'schema:SubscribeAction' }, + Subscription: { '@id': 'schema:Subscription' }, + Substance: { '@id': 'schema:Substance' }, + SubwayStation: { '@id': 'schema:SubwayStation' }, + Suite: { '@id': 'schema:Suite' }, + Sunday: { '@id': 'schema:Sunday' }, + SuperficialAnatomy: { '@id': 'schema:SuperficialAnatomy' }, + Surgical: { '@id': 'schema:Surgical' }, + SurgicalProcedure: { '@id': 'schema:SurgicalProcedure' }, + SuspendAction: { '@id': 'schema:SuspendAction' }, + Suspended: { '@id': 'schema:Suspended' }, + SymptomsHealthAspect: { '@id': 'schema:SymptomsHealthAspect' }, + Synagogue: { '@id': 'schema:Synagogue' }, + TVClip: { '@id': 'schema:TVClip' }, + TVEpisode: { '@id': 'schema:TVEpisode' }, + TVSeason: { '@id': 'schema:TVSeason' }, + TVSeries: { '@id': 'schema:TVSeries' }, + Table: { '@id': 'schema:Table' }, + TakeAction: { '@id': 'schema:TakeAction' }, + TattooParlor: { '@id': 'schema:TattooParlor' }, + Taxi: { '@id': 'schema:Taxi' }, + TaxiReservation: { '@id': 'schema:TaxiReservation' }, + TaxiService: { '@id': 'schema:TaxiService' }, + TaxiStand: { '@id': 'schema:TaxiStand' }, + TaxiVehicleUsage: { '@id': 'schema:TaxiVehicleUsage' }, + TechArticle: { '@id': 'schema:TechArticle' }, + TelevisionChannel: { '@id': 'schema:TelevisionChannel' }, + TelevisionStation: { '@id': 'schema:TelevisionStation' }, + TennisComplex: { '@id': 'schema:TennisComplex' }, + Terminated: { '@id': 'schema:Terminated' }, + Text: { '@id': 'schema:Text' }, + TextDigitalDocument: { '@id': 'schema:TextDigitalDocument' }, + TheaterEvent: { '@id': 'schema:TheaterEvent' }, + TheaterGroup: { '@id': 'schema:TheaterGroup' }, + Therapeutic: { '@id': 'schema:Therapeutic' }, + TherapeuticProcedure: { '@id': 'schema:TherapeuticProcedure' }, + Thesis: { '@id': 'schema:Thesis' }, + Thing: { '@id': 'schema:Thing' }, + Throat: { '@id': 'schema:Throat' }, + Thursday: { '@id': 'schema:Thursday' }, + Ticket: { '@id': 'schema:Ticket' }, + TieAction: { '@id': 'schema:TieAction' }, + Time: { '@id': 'schema:Time' }, + TipAction: { '@id': 'schema:TipAction' }, + TireShop: { '@id': 'schema:TireShop' }, + TollFree: { '@id': 'schema:TollFree' }, + TouristAttraction: { '@id': 'schema:TouristAttraction' }, + TouristDestination: { '@id': 'schema:TouristDestination' }, + TouristInformationCenter: { '@id': 'schema:TouristInformationCenter' }, + TouristTrip: { '@id': 'schema:TouristTrip' }, + Toxicologic: { '@id': 'schema:Toxicologic' }, + ToyStore: { '@id': 'schema:ToyStore' }, + TrackAction: { '@id': 'schema:TrackAction' }, + TradeAction: { '@id': 'schema:TradeAction' }, + TraditionalChinese: { '@id': 'schema:TraditionalChinese' }, + TrainReservation: { '@id': 'schema:TrainReservation' }, + TrainStation: { '@id': 'schema:TrainStation' }, + TrainTrip: { '@id': 'schema:TrainTrip' }, + TransferAction: { '@id': 'schema:TransferAction' }, + TransformedContent: { '@id': 'schema:TransformedContent' }, + TransitMap: { '@id': 'schema:TransitMap' }, + TravelAction: { '@id': 'schema:TravelAction' }, + TravelAgency: { '@id': 'schema:TravelAgency' }, + TreatmentIndication: { '@id': 'schema:TreatmentIndication' }, + TreatmentsHealthAspect: { '@id': 'schema:TreatmentsHealthAspect' }, + Trip: { '@id': 'schema:Trip' }, + TripleBlindedTrial: { '@id': 'schema:TripleBlindedTrial' }, + True: { '@id': 'schema:True' }, + Tuesday: { '@id': 'schema:Tuesday' }, + TypeAndQuantityNode: { '@id': 'schema:TypeAndQuantityNode' }, + TypesHealthAspect: { '@id': 'schema:TypesHealthAspect' }, + UKNonprofitType: { '@id': 'schema:UKNonprofitType' }, + UKTrust: { '@id': 'schema:UKTrust' }, + URL: { '@id': 'schema:URL' }, + USNonprofitType: { '@id': 'schema:USNonprofitType' }, + Ultrasound: { '@id': 'schema:Ultrasound' }, + UnRegisterAction: { '@id': 'schema:UnRegisterAction' }, + UnemploymentSupport: { '@id': 'schema:UnemploymentSupport' }, + UnincorporatedAssociationCharity: { + '@id': 'schema:UnincorporatedAssociationCharity', + }, + UnitPriceSpecification: { '@id': 'schema:UnitPriceSpecification' }, + UnofficialLegalValue: { '@id': 'schema:UnofficialLegalValue' }, + UpdateAction: { '@id': 'schema:UpdateAction' }, + Urologic: { '@id': 'schema:Urologic' }, + UsageOrScheduleHealthAspect: { '@id': 'schema:UsageOrScheduleHealthAspect' }, + UseAction: { '@id': 'schema:UseAction' }, + UsedCondition: { '@id': 'schema:UsedCondition' }, + UserBlocks: { '@id': 'schema:UserBlocks' }, + UserCheckins: { '@id': 'schema:UserCheckins' }, + UserComments: { '@id': 'schema:UserComments' }, + UserDownloads: { '@id': 'schema:UserDownloads' }, + UserInteraction: { '@id': 'schema:UserInteraction' }, + UserLikes: { '@id': 'schema:UserLikes' }, + UserPageVisits: { '@id': 'schema:UserPageVisits' }, + UserPlays: { '@id': 'schema:UserPlays' }, + UserPlusOnes: { '@id': 'schema:UserPlusOnes' }, + UserReview: { '@id': 'schema:UserReview' }, + UserTweets: { '@id': 'schema:UserTweets' }, + VeganDiet: { '@id': 'schema:VeganDiet' }, + VegetarianDiet: { '@id': 'schema:VegetarianDiet' }, + Vehicle: { '@id': 'schema:Vehicle' }, + Vein: { '@id': 'schema:Vein' }, + VenueMap: { '@id': 'schema:VenueMap' }, + Vessel: { '@id': 'schema:Vessel' }, + VeterinaryCare: { '@id': 'schema:VeterinaryCare' }, + VideoGallery: { '@id': 'schema:VideoGallery' }, + VideoGame: { '@id': 'schema:VideoGame' }, + VideoGameClip: { '@id': 'schema:VideoGameClip' }, + VideoGameSeries: { '@id': 'schema:VideoGameSeries' }, + VideoObject: { '@id': 'schema:VideoObject' }, + ViewAction: { '@id': 'schema:ViewAction' }, + VinylFormat: { '@id': 'schema:VinylFormat' }, + VirtualLocation: { '@id': 'schema:VirtualLocation' }, + Virus: { '@id': 'schema:Virus' }, + VisualArtsEvent: { '@id': 'schema:VisualArtsEvent' }, + VisualArtwork: { '@id': 'schema:VisualArtwork' }, + VitalSign: { '@id': 'schema:VitalSign' }, + Volcano: { '@id': 'schema:Volcano' }, + VoteAction: { '@id': 'schema:VoteAction' }, + WPAdBlock: { '@id': 'schema:WPAdBlock' }, + WPFooter: { '@id': 'schema:WPFooter' }, + WPHeader: { '@id': 'schema:WPHeader' }, + WPSideBar: { '@id': 'schema:WPSideBar' }, + WantAction: { '@id': 'schema:WantAction' }, + WarrantyPromise: { '@id': 'schema:WarrantyPromise' }, + WarrantyScope: { '@id': 'schema:WarrantyScope' }, + WatchAction: { '@id': 'schema:WatchAction' }, + Waterfall: { '@id': 'schema:Waterfall' }, + WearAction: { '@id': 'schema:WearAction' }, + WearableMeasurementBack: { '@id': 'schema:WearableMeasurementBack' }, + WearableMeasurementChestOrBust: { + '@id': 'schema:WearableMeasurementChestOrBust', + }, + WearableMeasurementCollar: { '@id': 'schema:WearableMeasurementCollar' }, + WearableMeasurementCup: { '@id': 'schema:WearableMeasurementCup' }, + WearableMeasurementHeight: { '@id': 'schema:WearableMeasurementHeight' }, + WearableMeasurementHips: { '@id': 'schema:WearableMeasurementHips' }, + WearableMeasurementInseam: { '@id': 'schema:WearableMeasurementInseam' }, + WearableMeasurementLength: { '@id': 'schema:WearableMeasurementLength' }, + WearableMeasurementOutsideLeg: { + '@id': 'schema:WearableMeasurementOutsideLeg', + }, + WearableMeasurementSleeve: { '@id': 'schema:WearableMeasurementSleeve' }, + WearableMeasurementTypeEnumeration: { + '@id': 'schema:WearableMeasurementTypeEnumeration', + }, + WearableMeasurementWaist: { '@id': 'schema:WearableMeasurementWaist' }, + WearableMeasurementWidth: { '@id': 'schema:WearableMeasurementWidth' }, + WearableSizeGroupBig: { '@id': 'schema:WearableSizeGroupBig' }, + WearableSizeGroupBoys: { '@id': 'schema:WearableSizeGroupBoys' }, + WearableSizeGroupEnumeration: { '@id': 'schema:WearableSizeGroupEnumeration' }, + WearableSizeGroupExtraShort: { '@id': 'schema:WearableSizeGroupExtraShort' }, + WearableSizeGroupExtraTall: { '@id': 'schema:WearableSizeGroupExtraTall' }, + WearableSizeGroupGirls: { '@id': 'schema:WearableSizeGroupGirls' }, + WearableSizeGroupHusky: { '@id': 'schema:WearableSizeGroupHusky' }, + WearableSizeGroupInfants: { '@id': 'schema:WearableSizeGroupInfants' }, + WearableSizeGroupJuniors: { '@id': 'schema:WearableSizeGroupJuniors' }, + WearableSizeGroupMaternity: { '@id': 'schema:WearableSizeGroupMaternity' }, + WearableSizeGroupMens: { '@id': 'schema:WearableSizeGroupMens' }, + WearableSizeGroupMisses: { '@id': 'schema:WearableSizeGroupMisses' }, + WearableSizeGroupPetite: { '@id': 'schema:WearableSizeGroupPetite' }, + WearableSizeGroupPlus: { '@id': 'schema:WearableSizeGroupPlus' }, + WearableSizeGroupRegular: { '@id': 'schema:WearableSizeGroupRegular' }, + WearableSizeGroupShort: { '@id': 'schema:WearableSizeGroupShort' }, + WearableSizeGroupTall: { '@id': 'schema:WearableSizeGroupTall' }, + WearableSizeGroupWomens: { '@id': 'schema:WearableSizeGroupWomens' }, + WearableSizeSystemAU: { '@id': 'schema:WearableSizeSystemAU' }, + WearableSizeSystemBR: { '@id': 'schema:WearableSizeSystemBR' }, + WearableSizeSystemCN: { '@id': 'schema:WearableSizeSystemCN' }, + WearableSizeSystemContinental: { + '@id': 'schema:WearableSizeSystemContinental', + }, + WearableSizeSystemDE: { '@id': 'schema:WearableSizeSystemDE' }, + WearableSizeSystemEN13402: { '@id': 'schema:WearableSizeSystemEN13402' }, + WearableSizeSystemEnumeration: { + '@id': 'schema:WearableSizeSystemEnumeration', + }, + WearableSizeSystemEurope: { '@id': 'schema:WearableSizeSystemEurope' }, + WearableSizeSystemFR: { '@id': 'schema:WearableSizeSystemFR' }, + WearableSizeSystemGS1: { '@id': 'schema:WearableSizeSystemGS1' }, + WearableSizeSystemIT: { '@id': 'schema:WearableSizeSystemIT' }, + WearableSizeSystemJP: { '@id': 'schema:WearableSizeSystemJP' }, + WearableSizeSystemMX: { '@id': 'schema:WearableSizeSystemMX' }, + WearableSizeSystemUK: { '@id': 'schema:WearableSizeSystemUK' }, + WearableSizeSystemUS: { '@id': 'schema:WearableSizeSystemUS' }, + WebAPI: { '@id': 'schema:WebAPI' }, + WebApplication: { '@id': 'schema:WebApplication' }, + WebContent: { '@id': 'schema:WebContent' }, + WebPage: { '@id': 'schema:WebPage' }, + WebPageElement: { '@id': 'schema:WebPageElement' }, + WebSite: { '@id': 'schema:WebSite' }, + Wednesday: { '@id': 'schema:Wednesday' }, + WesternConventional: { '@id': 'schema:WesternConventional' }, + Wholesale: { '@id': 'schema:Wholesale' }, + WholesaleStore: { '@id': 'schema:WholesaleStore' }, + WinAction: { '@id': 'schema:WinAction' }, + Winery: { '@id': 'schema:Winery' }, + Withdrawn: { '@id': 'schema:Withdrawn' }, + WorkBasedProgram: { '@id': 'schema:WorkBasedProgram' }, + WorkersUnion: { '@id': 'schema:WorkersUnion' }, + WriteAction: { '@id': 'schema:WriteAction' }, + WritePermission: { '@id': 'schema:WritePermission' }, + XPathType: { '@id': 'schema:XPathType' }, + XRay: { '@id': 'schema:XRay' }, + ZoneBoardingPolicy: { '@id': 'schema:ZoneBoardingPolicy' }, + Zoo: { '@id': 'schema:Zoo' }, + about: { '@id': 'schema:about' }, + abridged: { '@id': 'schema:abridged' }, + abstract: { '@id': 'schema:abstract' }, + accelerationTime: { '@id': 'schema:accelerationTime' }, + acceptedAnswer: { '@id': 'schema:acceptedAnswer' }, + acceptedOffer: { '@id': 'schema:acceptedOffer' }, + acceptedPaymentMethod: { '@id': 'schema:acceptedPaymentMethod' }, + acceptsReservations: { '@id': 'schema:acceptsReservations' }, + accessCode: { '@id': 'schema:accessCode' }, + accessMode: { '@id': 'schema:accessMode' }, + accessModeSufficient: { '@id': 'schema:accessModeSufficient' }, + accessibilityAPI: { '@id': 'schema:accessibilityAPI' }, + accessibilityControl: { '@id': 'schema:accessibilityControl' }, + accessibilityFeature: { '@id': 'schema:accessibilityFeature' }, + accessibilityHazard: { '@id': 'schema:accessibilityHazard' }, + accessibilitySummary: { '@id': 'schema:accessibilitySummary' }, + accommodationCategory: { '@id': 'schema:accommodationCategory' }, + accommodationFloorPlan: { '@id': 'schema:accommodationFloorPlan' }, + accountId: { '@id': 'schema:accountId' }, + accountMinimumInflow: { '@id': 'schema:accountMinimumInflow' }, + accountOverdraftLimit: { '@id': 'schema:accountOverdraftLimit' }, + accountablePerson: { '@id': 'schema:accountablePerson' }, + acquireLicensePage: { '@id': 'schema:acquireLicensePage', '@type': '@id' }, + acquiredFrom: { '@id': 'schema:acquiredFrom' }, + acrissCode: { '@id': 'schema:acrissCode' }, + actionAccessibilityRequirement: { + '@id': 'schema:actionAccessibilityRequirement', + }, + actionApplication: { '@id': 'schema:actionApplication' }, + actionOption: { '@id': 'schema:actionOption' }, + actionPlatform: { '@id': 'schema:actionPlatform' }, + actionStatus: { '@id': 'schema:actionStatus' }, + actionableFeedbackPolicy: { + '@id': 'schema:actionableFeedbackPolicy', + '@type': '@id', + }, + activeIngredient: { '@id': 'schema:activeIngredient' }, + activityDuration: { '@id': 'schema:activityDuration' }, + activityFrequency: { '@id': 'schema:activityFrequency' }, + actor: { '@id': 'schema:actor' }, + actors: { '@id': 'schema:actors' }, + addOn: { '@id': 'schema:addOn' }, + additionalName: { '@id': 'schema:additionalName' }, + additionalNumberOfGuests: { '@id': 'schema:additionalNumberOfGuests' }, + additionalProperty: { '@id': 'schema:additionalProperty' }, + additionalType: { '@id': 'schema:additionalType', '@type': '@id' }, + additionalVariable: { '@id': 'schema:additionalVariable' }, + address: { '@id': 'schema:address' }, + addressCountry: { '@id': 'schema:addressCountry' }, + addressLocality: { '@id': 'schema:addressLocality' }, + addressRegion: { '@id': 'schema:addressRegion' }, + administrationRoute: { '@id': 'schema:administrationRoute' }, + advanceBookingRequirement: { '@id': 'schema:advanceBookingRequirement' }, + adverseOutcome: { '@id': 'schema:adverseOutcome' }, + affectedBy: { '@id': 'schema:affectedBy' }, + affiliation: { '@id': 'schema:affiliation' }, + afterMedia: { '@id': 'schema:afterMedia', '@type': '@id' }, + agent: { '@id': 'schema:agent' }, + aggregateRating: { '@id': 'schema:aggregateRating' }, + aircraft: { '@id': 'schema:aircraft' }, + album: { '@id': 'schema:album' }, + albumProductionType: { '@id': 'schema:albumProductionType' }, + albumRelease: { '@id': 'schema:albumRelease' }, + albumReleaseType: { '@id': 'schema:albumReleaseType' }, + albums: { '@id': 'schema:albums' }, + alcoholWarning: { '@id': 'schema:alcoholWarning' }, + algorithm: { '@id': 'schema:algorithm' }, + alignmentType: { '@id': 'schema:alignmentType' }, + alternateName: { '@id': 'schema:alternateName' }, + alternativeHeadline: { '@id': 'schema:alternativeHeadline' }, + alumni: { '@id': 'schema:alumni' }, + alumniOf: { '@id': 'schema:alumniOf' }, + amenityFeature: { '@id': 'schema:amenityFeature' }, + amount: { '@id': 'schema:amount' }, + amountOfThisGood: { '@id': 'schema:amountOfThisGood' }, + announcementLocation: { '@id': 'schema:announcementLocation' }, + annualPercentageRate: { '@id': 'schema:annualPercentageRate' }, + answerCount: { '@id': 'schema:answerCount' }, + answerExplanation: { '@id': 'schema:answerExplanation' }, + antagonist: { '@id': 'schema:antagonist' }, + appearance: { '@id': 'schema:appearance' }, + applicableLocation: { '@id': 'schema:applicableLocation' }, + applicantLocationRequirements: { + '@id': 'schema:applicantLocationRequirements', + }, + application: { '@id': 'schema:application' }, + applicationCategory: { '@id': 'schema:applicationCategory' }, + applicationContact: { '@id': 'schema:applicationContact' }, + applicationDeadline: { '@id': 'schema:applicationDeadline', '@type': 'Date' }, + applicationStartDate: { '@id': 'schema:applicationStartDate', '@type': 'Date' }, + applicationSubCategory: { '@id': 'schema:applicationSubCategory' }, + applicationSuite: { '@id': 'schema:applicationSuite' }, + appliesToDeliveryMethod: { '@id': 'schema:appliesToDeliveryMethod' }, + appliesToPaymentMethod: { '@id': 'schema:appliesToPaymentMethod' }, + archiveHeld: { '@id': 'schema:archiveHeld' }, + area: { '@id': 'schema:area' }, + areaServed: { '@id': 'schema:areaServed' }, + arrivalAirport: { '@id': 'schema:arrivalAirport' }, + arrivalBoatTerminal: { '@id': 'schema:arrivalBoatTerminal' }, + arrivalBusStop: { '@id': 'schema:arrivalBusStop' }, + arrivalGate: { '@id': 'schema:arrivalGate' }, + arrivalPlatform: { '@id': 'schema:arrivalPlatform' }, + arrivalStation: { '@id': 'schema:arrivalStation' }, + arrivalTerminal: { '@id': 'schema:arrivalTerminal' }, + arrivalTime: { '@id': 'schema:arrivalTime' }, + artEdition: { '@id': 'schema:artEdition' }, + artMedium: { '@id': 'schema:artMedium' }, + arterialBranch: { '@id': 'schema:arterialBranch' }, + artform: { '@id': 'schema:artform' }, + articleBody: { '@id': 'schema:articleBody' }, + articleSection: { '@id': 'schema:articleSection' }, + artist: { '@id': 'schema:artist' }, + artworkSurface: { '@id': 'schema:artworkSurface' }, + aspect: { '@id': 'schema:aspect' }, + assembly: { '@id': 'schema:assembly' }, + assemblyVersion: { '@id': 'schema:assemblyVersion' }, + assesses: { '@id': 'schema:assesses' }, + associatedAnatomy: { '@id': 'schema:associatedAnatomy' }, + associatedArticle: { '@id': 'schema:associatedArticle' }, + associatedMedia: { '@id': 'schema:associatedMedia' }, + associatedPathophysiology: { '@id': 'schema:associatedPathophysiology' }, + athlete: { '@id': 'schema:athlete' }, + attendee: { '@id': 'schema:attendee' }, + attendees: { '@id': 'schema:attendees' }, + audience: { '@id': 'schema:audience' }, + audienceType: { '@id': 'schema:audienceType' }, + audio: { '@id': 'schema:audio' }, + authenticator: { '@id': 'schema:authenticator' }, + author: { '@id': 'schema:author' }, + availability: { '@id': 'schema:availability' }, + availabilityEnds: { '@id': 'schema:availabilityEnds', '@type': 'Date' }, + availabilityStarts: { '@id': 'schema:availabilityStarts', '@type': 'Date' }, + availableAtOrFrom: { '@id': 'schema:availableAtOrFrom' }, + availableChannel: { '@id': 'schema:availableChannel' }, + availableDeliveryMethod: { '@id': 'schema:availableDeliveryMethod' }, + availableFrom: { '@id': 'schema:availableFrom' }, + availableIn: { '@id': 'schema:availableIn' }, + availableLanguage: { '@id': 'schema:availableLanguage' }, + availableOnDevice: { '@id': 'schema:availableOnDevice' }, + availableService: { '@id': 'schema:availableService' }, + availableStrength: { '@id': 'schema:availableStrength' }, + availableTest: { '@id': 'schema:availableTest' }, + availableThrough: { '@id': 'schema:availableThrough' }, + award: { '@id': 'schema:award' }, + awards: { '@id': 'schema:awards' }, + awayTeam: { '@id': 'schema:awayTeam' }, + backstory: { '@id': 'schema:backstory' }, + bankAccountType: { '@id': 'schema:bankAccountType' }, + baseSalary: { '@id': 'schema:baseSalary' }, + bccRecipient: { '@id': 'schema:bccRecipient' }, + bed: { '@id': 'schema:bed' }, + beforeMedia: { '@id': 'schema:beforeMedia', '@type': '@id' }, + beneficiaryBank: { '@id': 'schema:beneficiaryBank' }, + benefits: { '@id': 'schema:benefits' }, + benefitsSummaryUrl: { '@id': 'schema:benefitsSummaryUrl', '@type': '@id' }, + bestRating: { '@id': 'schema:bestRating' }, + billingAddress: { '@id': 'schema:billingAddress' }, + billingDuration: { '@id': 'schema:billingDuration' }, + billingIncrement: { '@id': 'schema:billingIncrement' }, + billingPeriod: { '@id': 'schema:billingPeriod' }, + billingStart: { '@id': 'schema:billingStart' }, + biomechnicalClass: { '@id': 'schema:biomechnicalClass' }, + birthDate: { '@id': 'schema:birthDate', '@type': 'Date' }, + birthPlace: { '@id': 'schema:birthPlace' }, + bitrate: { '@id': 'schema:bitrate' }, + blogPost: { '@id': 'schema:blogPost' }, + blogPosts: { '@id': 'schema:blogPosts' }, + bloodSupply: { '@id': 'schema:bloodSupply' }, + boardingGroup: { '@id': 'schema:boardingGroup' }, + boardingPolicy: { '@id': 'schema:boardingPolicy' }, + bodyLocation: { '@id': 'schema:bodyLocation' }, + bodyType: { '@id': 'schema:bodyType' }, + bookEdition: { '@id': 'schema:bookEdition' }, + bookFormat: { '@id': 'schema:bookFormat' }, + bookingAgent: { '@id': 'schema:bookingAgent' }, + bookingTime: { '@id': 'schema:bookingTime' }, + borrower: { '@id': 'schema:borrower' }, + box: { '@id': 'schema:box' }, + branch: { '@id': 'schema:branch' }, + branchCode: { '@id': 'schema:branchCode' }, + branchOf: { '@id': 'schema:branchOf' }, + brand: { '@id': 'schema:brand' }, + breadcrumb: { '@id': 'schema:breadcrumb' }, + breastfeedingWarning: { '@id': 'schema:breastfeedingWarning' }, + broadcastAffiliateOf: { '@id': 'schema:broadcastAffiliateOf' }, + broadcastChannelId: { '@id': 'schema:broadcastChannelId' }, + broadcastDisplayName: { '@id': 'schema:broadcastDisplayName' }, + broadcastFrequency: { '@id': 'schema:broadcastFrequency' }, + broadcastFrequencyValue: { '@id': 'schema:broadcastFrequencyValue' }, + broadcastOfEvent: { '@id': 'schema:broadcastOfEvent' }, + broadcastServiceTier: { '@id': 'schema:broadcastServiceTier' }, + broadcastSignalModulation: { '@id': 'schema:broadcastSignalModulation' }, + broadcastSubChannel: { '@id': 'schema:broadcastSubChannel' }, + broadcastTimezone: { '@id': 'schema:broadcastTimezone' }, + broadcaster: { '@id': 'schema:broadcaster' }, + broker: { '@id': 'schema:broker' }, + browserRequirements: { '@id': 'schema:browserRequirements' }, + busName: { '@id': 'schema:busName' }, + busNumber: { '@id': 'schema:busNumber' }, + businessDays: { '@id': 'schema:businessDays' }, + businessFunction: { '@id': 'schema:businessFunction' }, + buyer: { '@id': 'schema:buyer' }, + byArtist: { '@id': 'schema:byArtist' }, + byDay: { '@id': 'schema:byDay' }, + byMonth: { '@id': 'schema:byMonth' }, + byMonthDay: { '@id': 'schema:byMonthDay' }, + byMonthWeek: { '@id': 'schema:byMonthWeek' }, + callSign: { '@id': 'schema:callSign' }, + calories: { '@id': 'schema:calories' }, + candidate: { '@id': 'schema:candidate' }, + caption: { '@id': 'schema:caption' }, + carbohydrateContent: { '@id': 'schema:carbohydrateContent' }, + cargoVolume: { '@id': 'schema:cargoVolume' }, + carrier: { '@id': 'schema:carrier' }, + carrierRequirements: { '@id': 'schema:carrierRequirements' }, + cashBack: { '@id': 'schema:cashBack' }, + catalog: { '@id': 'schema:catalog' }, + catalogNumber: { '@id': 'schema:catalogNumber' }, + category: { '@id': 'schema:category' }, + causeOf: { '@id': 'schema:causeOf' }, + ccRecipient: { '@id': 'schema:ccRecipient' }, + character: { '@id': 'schema:character' }, + characterAttribute: { '@id': 'schema:characterAttribute' }, + characterName: { '@id': 'schema:characterName' }, + cheatCode: { '@id': 'schema:cheatCode' }, + checkinTime: { '@id': 'schema:checkinTime' }, + checkoutTime: { '@id': 'schema:checkoutTime' }, + childMaxAge: { '@id': 'schema:childMaxAge' }, + childMinAge: { '@id': 'schema:childMinAge' }, + children: { '@id': 'schema:children' }, + cholesterolContent: { '@id': 'schema:cholesterolContent' }, + circle: { '@id': 'schema:circle' }, + citation: { '@id': 'schema:citation' }, + claimReviewed: { '@id': 'schema:claimReviewed' }, + clincalPharmacology: { '@id': 'schema:clincalPharmacology' }, + clinicalPharmacology: { '@id': 'schema:clinicalPharmacology' }, + clipNumber: { '@id': 'schema:clipNumber' }, + closes: { '@id': 'schema:closes' }, + coach: { '@id': 'schema:coach' }, + code: { '@id': 'schema:code' }, + codeRepository: { '@id': 'schema:codeRepository', '@type': '@id' }, + codeSampleType: { '@id': 'schema:codeSampleType' }, + codeValue: { '@id': 'schema:codeValue' }, + codingSystem: { '@id': 'schema:codingSystem' }, + colleague: { '@id': 'schema:colleague', '@type': '@id' }, + colleagues: { '@id': 'schema:colleagues' }, + collection: { '@id': 'schema:collection' }, + collectionSize: { '@id': 'schema:collectionSize' }, + color: { '@id': 'schema:color' }, + colorist: { '@id': 'schema:colorist' }, + comment: { '@id': 'schema:comment' }, + commentCount: { '@id': 'schema:commentCount' }, + commentText: { '@id': 'schema:commentText' }, + commentTime: { '@id': 'schema:commentTime', '@type': 'Date' }, + competencyRequired: { '@id': 'schema:competencyRequired' }, + competitor: { '@id': 'schema:competitor' }, + composer: { '@id': 'schema:composer' }, + comprisedOf: { '@id': 'schema:comprisedOf' }, + conditionsOfAccess: { '@id': 'schema:conditionsOfAccess' }, + confirmationNumber: { '@id': 'schema:confirmationNumber' }, + connectedTo: { '@id': 'schema:connectedTo' }, + constrainingProperty: { '@id': 'schema:constrainingProperty' }, + contactOption: { '@id': 'schema:contactOption' }, + contactPoint: { '@id': 'schema:contactPoint' }, + contactPoints: { '@id': 'schema:contactPoints' }, + contactType: { '@id': 'schema:contactType' }, + contactlessPayment: { '@id': 'schema:contactlessPayment' }, + containedIn: { '@id': 'schema:containedIn' }, + containedInPlace: { '@id': 'schema:containedInPlace' }, + containsPlace: { '@id': 'schema:containsPlace' }, + containsSeason: { '@id': 'schema:containsSeason' }, + contentLocation: { '@id': 'schema:contentLocation' }, + contentRating: { '@id': 'schema:contentRating' }, + contentReferenceTime: { '@id': 'schema:contentReferenceTime' }, + contentSize: { '@id': 'schema:contentSize' }, + contentType: { '@id': 'schema:contentType' }, + contentUrl: { '@id': 'schema:contentUrl', '@type': '@id' }, + contraindication: { '@id': 'schema:contraindication' }, + contributor: { '@id': 'schema:contributor' }, + cookTime: { '@id': 'schema:cookTime' }, + cookingMethod: { '@id': 'schema:cookingMethod' }, + copyrightHolder: { '@id': 'schema:copyrightHolder' }, + copyrightNotice: { '@id': 'schema:copyrightNotice' }, + copyrightYear: { '@id': 'schema:copyrightYear' }, + correction: { '@id': 'schema:correction' }, + correctionsPolicy: { '@id': 'schema:correctionsPolicy', '@type': '@id' }, + costCategory: { '@id': 'schema:costCategory' }, + costCurrency: { '@id': 'schema:costCurrency' }, + costOrigin: { '@id': 'schema:costOrigin' }, + costPerUnit: { '@id': 'schema:costPerUnit' }, + countriesNotSupported: { '@id': 'schema:countriesNotSupported' }, + countriesSupported: { '@id': 'schema:countriesSupported' }, + countryOfOrigin: { '@id': 'schema:countryOfOrigin' }, + course: { '@id': 'schema:course' }, + courseCode: { '@id': 'schema:courseCode' }, + courseMode: { '@id': 'schema:courseMode' }, + coursePrerequisites: { '@id': 'schema:coursePrerequisites' }, + courseWorkload: { '@id': 'schema:courseWorkload' }, + coverageEndTime: { '@id': 'schema:coverageEndTime' }, + coverageStartTime: { '@id': 'schema:coverageStartTime' }, + creativeWorkStatus: { '@id': 'schema:creativeWorkStatus' }, + creator: { '@id': 'schema:creator' }, + credentialCategory: { '@id': 'schema:credentialCategory' }, + creditText: { '@id': 'schema:creditText' }, + creditedTo: { '@id': 'schema:creditedTo' }, + cssSelector: { '@id': 'schema:cssSelector' }, + currenciesAccepted: { '@id': 'schema:currenciesAccepted' }, + currency: { '@id': 'schema:currency' }, + currentExchangeRate: { '@id': 'schema:currentExchangeRate' }, + customer: { '@id': 'schema:customer' }, + cutoffTime: { '@id': 'schema:cutoffTime' }, + cvdCollectionDate: { '@id': 'schema:cvdCollectionDate' }, + cvdFacilityCounty: { '@id': 'schema:cvdFacilityCounty' }, + cvdFacilityId: { '@id': 'schema:cvdFacilityId' }, + cvdNumBeds: { '@id': 'schema:cvdNumBeds' }, + cvdNumBedsOcc: { '@id': 'schema:cvdNumBedsOcc' }, + cvdNumC19Died: { '@id': 'schema:cvdNumC19Died' }, + cvdNumC19HOPats: { '@id': 'schema:cvdNumC19HOPats' }, + cvdNumC19HospPats: { '@id': 'schema:cvdNumC19HospPats' }, + cvdNumC19MechVentPats: { '@id': 'schema:cvdNumC19MechVentPats' }, + cvdNumC19OFMechVentPats: { '@id': 'schema:cvdNumC19OFMechVentPats' }, + cvdNumC19OverflowPats: { '@id': 'schema:cvdNumC19OverflowPats' }, + cvdNumICUBeds: { '@id': 'schema:cvdNumICUBeds' }, + cvdNumICUBedsOcc: { '@id': 'schema:cvdNumICUBedsOcc' }, + cvdNumTotBeds: { '@id': 'schema:cvdNumTotBeds' }, + cvdNumVent: { '@id': 'schema:cvdNumVent' }, + cvdNumVentUse: { '@id': 'schema:cvdNumVentUse' }, + dataFeedElement: { '@id': 'schema:dataFeedElement' }, + dataset: { '@id': 'schema:dataset' }, + datasetTimeInterval: { '@id': 'schema:datasetTimeInterval' }, + dateCreated: { '@id': 'schema:dateCreated', '@type': 'Date' }, + dateDeleted: { '@id': 'schema:dateDeleted', '@type': 'Date' }, + dateIssued: { '@id': 'schema:dateIssued', '@type': 'Date' }, + dateModified: { '@id': 'schema:dateModified', '@type': 'Date' }, + datePosted: { '@id': 'schema:datePosted', '@type': 'Date' }, + datePublished: { '@id': 'schema:datePublished', '@type': 'Date' }, + dateRead: { '@id': 'schema:dateRead', '@type': 'Date' }, + dateReceived: { '@id': 'schema:dateReceived' }, + dateSent: { '@id': 'schema:dateSent' }, + dateVehicleFirstRegistered: { + '@id': 'schema:dateVehicleFirstRegistered', + '@type': 'Date', + }, + dateline: { '@id': 'schema:dateline' }, + dayOfWeek: { '@id': 'schema:dayOfWeek' }, + deathDate: { '@id': 'schema:deathDate', '@type': 'Date' }, + deathPlace: { '@id': 'schema:deathPlace' }, + defaultValue: { '@id': 'schema:defaultValue' }, + deliveryAddress: { '@id': 'schema:deliveryAddress' }, + deliveryLeadTime: { '@id': 'schema:deliveryLeadTime' }, + deliveryMethod: { '@id': 'schema:deliveryMethod' }, + deliveryStatus: { '@id': 'schema:deliveryStatus' }, + deliveryTime: { '@id': 'schema:deliveryTime' }, + department: { '@id': 'schema:department' }, + departureAirport: { '@id': 'schema:departureAirport' }, + departureBoatTerminal: { '@id': 'schema:departureBoatTerminal' }, + departureBusStop: { '@id': 'schema:departureBusStop' }, + departureGate: { '@id': 'schema:departureGate' }, + departurePlatform: { '@id': 'schema:departurePlatform' }, + departureStation: { '@id': 'schema:departureStation' }, + departureTerminal: { '@id': 'schema:departureTerminal' }, + departureTime: { '@id': 'schema:departureTime' }, + dependencies: { '@id': 'schema:dependencies' }, + depth: { '@id': 'schema:depth' }, + description: { '@id': 'schema:description' }, + device: { '@id': 'schema:device' }, + diagnosis: { '@id': 'schema:diagnosis' }, + diagram: { '@id': 'schema:diagram' }, + diet: { '@id': 'schema:diet' }, + dietFeatures: { '@id': 'schema:dietFeatures' }, + differentialDiagnosis: { '@id': 'schema:differentialDiagnosis' }, + director: { '@id': 'schema:director' }, + directors: { '@id': 'schema:directors' }, + disambiguatingDescription: { '@id': 'schema:disambiguatingDescription' }, + discount: { '@id': 'schema:discount' }, + discountCode: { '@id': 'schema:discountCode' }, + discountCurrency: { '@id': 'schema:discountCurrency' }, + discusses: { '@id': 'schema:discusses' }, + discussionUrl: { '@id': 'schema:discussionUrl', '@type': '@id' }, + diseasePreventionInfo: { + '@id': 'schema:diseasePreventionInfo', + '@type': '@id', + }, + diseaseSpreadStatistics: { + '@id': 'schema:diseaseSpreadStatistics', + '@type': '@id', + }, + dissolutionDate: { '@id': 'schema:dissolutionDate', '@type': 'Date' }, + distance: { '@id': 'schema:distance' }, + distinguishingSign: { '@id': 'schema:distinguishingSign' }, + distribution: { '@id': 'schema:distribution' }, + diversityPolicy: { '@id': 'schema:diversityPolicy', '@type': '@id' }, + diversityStaffingReport: { + '@id': 'schema:diversityStaffingReport', + '@type': '@id', + }, + documentation: { '@id': 'schema:documentation', '@type': '@id' }, + doesNotShip: { '@id': 'schema:doesNotShip' }, + domainIncludes: { '@id': 'schema:domainIncludes' }, + domiciledMortgage: { '@id': 'schema:domiciledMortgage' }, + doorTime: { '@id': 'schema:doorTime' }, + dosageForm: { '@id': 'schema:dosageForm' }, + doseSchedule: { '@id': 'schema:doseSchedule' }, + doseUnit: { '@id': 'schema:doseUnit' }, + doseValue: { '@id': 'schema:doseValue' }, + downPayment: { '@id': 'schema:downPayment' }, + downloadUrl: { '@id': 'schema:downloadUrl', '@type': '@id' }, + downvoteCount: { '@id': 'schema:downvoteCount' }, + drainsTo: { '@id': 'schema:drainsTo' }, + driveWheelConfiguration: { '@id': 'schema:driveWheelConfiguration' }, + dropoffLocation: { '@id': 'schema:dropoffLocation' }, + dropoffTime: { '@id': 'schema:dropoffTime' }, + drug: { '@id': 'schema:drug' }, + drugClass: { '@id': 'schema:drugClass' }, + drugUnit: { '@id': 'schema:drugUnit' }, + duns: { '@id': 'schema:duns' }, + duplicateTherapy: { '@id': 'schema:duplicateTherapy' }, + duration: { '@id': 'schema:duration' }, + durationOfWarranty: { '@id': 'schema:durationOfWarranty' }, + duringMedia: { '@id': 'schema:duringMedia', '@type': '@id' }, + earlyPrepaymentPenalty: { '@id': 'schema:earlyPrepaymentPenalty' }, + editEIDR: { '@id': 'schema:editEIDR' }, + editor: { '@id': 'schema:editor' }, + eduQuestionType: { '@id': 'schema:eduQuestionType' }, + educationRequirements: { '@id': 'schema:educationRequirements' }, + educationalAlignment: { '@id': 'schema:educationalAlignment' }, + educationalCredentialAwarded: { '@id': 'schema:educationalCredentialAwarded' }, + educationalFramework: { '@id': 'schema:educationalFramework' }, + educationalLevel: { '@id': 'schema:educationalLevel' }, + educationalProgramMode: { '@id': 'schema:educationalProgramMode' }, + educationalRole: { '@id': 'schema:educationalRole' }, + educationalUse: { '@id': 'schema:educationalUse' }, + elevation: { '@id': 'schema:elevation' }, + eligibilityToWorkRequirement: { '@id': 'schema:eligibilityToWorkRequirement' }, + eligibleCustomerType: { '@id': 'schema:eligibleCustomerType' }, + eligibleDuration: { '@id': 'schema:eligibleDuration' }, + eligibleQuantity: { '@id': 'schema:eligibleQuantity' }, + eligibleRegion: { '@id': 'schema:eligibleRegion' }, + eligibleTransactionVolume: { '@id': 'schema:eligibleTransactionVolume' }, + email: { '@id': 'schema:email' }, + embedUrl: { '@id': 'schema:embedUrl', '@type': '@id' }, + emissionsCO2: { '@id': 'schema:emissionsCO2' }, + employee: { '@id': 'schema:employee' }, + employees: { '@id': 'schema:employees' }, + employerOverview: { '@id': 'schema:employerOverview' }, + employmentType: { '@id': 'schema:employmentType' }, + employmentUnit: { '@id': 'schema:employmentUnit' }, + encodesCreativeWork: { '@id': 'schema:encodesCreativeWork' }, + encoding: { '@id': 'schema:encoding' }, + encodingFormat: { '@id': 'schema:encodingFormat' }, + encodingType: { '@id': 'schema:encodingType' }, + encodings: { '@id': 'schema:encodings' }, + endDate: { '@id': 'schema:endDate', '@type': 'Date' }, + endOffset: { '@id': 'schema:endOffset' }, + endTime: { '@id': 'schema:endTime' }, + endorsee: { '@id': 'schema:endorsee' }, + endorsers: { '@id': 'schema:endorsers' }, + energyEfficiencyScaleMax: { '@id': 'schema:energyEfficiencyScaleMax' }, + energyEfficiencyScaleMin: { '@id': 'schema:energyEfficiencyScaleMin' }, + engineDisplacement: { '@id': 'schema:engineDisplacement' }, + enginePower: { '@id': 'schema:enginePower' }, + engineType: { '@id': 'schema:engineType' }, + entertainmentBusiness: { '@id': 'schema:entertainmentBusiness' }, + epidemiology: { '@id': 'schema:epidemiology' }, + episode: { '@id': 'schema:episode' }, + episodeNumber: { '@id': 'schema:episodeNumber' }, + episodes: { '@id': 'schema:episodes' }, + equal: { '@id': 'schema:equal' }, + error: { '@id': 'schema:error' }, + estimatedCost: { '@id': 'schema:estimatedCost' }, + estimatedFlightDuration: { '@id': 'schema:estimatedFlightDuration' }, + estimatedSalary: { '@id': 'schema:estimatedSalary' }, + estimatesRiskOf: { '@id': 'schema:estimatesRiskOf' }, + ethicsPolicy: { '@id': 'schema:ethicsPolicy', '@type': '@id' }, + event: { '@id': 'schema:event' }, + eventAttendanceMode: { '@id': 'schema:eventAttendanceMode' }, + eventSchedule: { '@id': 'schema:eventSchedule' }, + eventStatus: { '@id': 'schema:eventStatus' }, + events: { '@id': 'schema:events' }, + evidenceLevel: { '@id': 'schema:evidenceLevel' }, + evidenceOrigin: { '@id': 'schema:evidenceOrigin' }, + exampleOfWork: { '@id': 'schema:exampleOfWork' }, + exceptDate: { '@id': 'schema:exceptDate', '@type': 'Date' }, + exchangeRateSpread: { '@id': 'schema:exchangeRateSpread' }, + executableLibraryName: { '@id': 'schema:executableLibraryName' }, + exerciseCourse: { '@id': 'schema:exerciseCourse' }, + exercisePlan: { '@id': 'schema:exercisePlan' }, + exerciseRelatedDiet: { '@id': 'schema:exerciseRelatedDiet' }, + exerciseType: { '@id': 'schema:exerciseType' }, + exifData: { '@id': 'schema:exifData' }, + expectedArrivalFrom: { '@id': 'schema:expectedArrivalFrom', '@type': 'Date' }, + expectedArrivalUntil: { '@id': 'schema:expectedArrivalUntil', '@type': 'Date' }, + expectedPrognosis: { '@id': 'schema:expectedPrognosis' }, + expectsAcceptanceOf: { '@id': 'schema:expectsAcceptanceOf' }, + experienceInPlaceOfEducation: { '@id': 'schema:experienceInPlaceOfEducation' }, + experienceRequirements: { '@id': 'schema:experienceRequirements' }, + expertConsiderations: { '@id': 'schema:expertConsiderations' }, + expires: { '@id': 'schema:expires', '@type': 'Date' }, + familyName: { '@id': 'schema:familyName' }, + fatContent: { '@id': 'schema:fatContent' }, + faxNumber: { '@id': 'schema:faxNumber' }, + featureList: { '@id': 'schema:featureList' }, + feesAndCommissionsSpecification: { + '@id': 'schema:feesAndCommissionsSpecification', + }, + fiberContent: { '@id': 'schema:fiberContent' }, + fileFormat: { '@id': 'schema:fileFormat' }, + fileSize: { '@id': 'schema:fileSize' }, + financialAidEligible: { '@id': 'schema:financialAidEligible' }, + firstAppearance: { '@id': 'schema:firstAppearance' }, + firstPerformance: { '@id': 'schema:firstPerformance' }, + flightDistance: { '@id': 'schema:flightDistance' }, + flightNumber: { '@id': 'schema:flightNumber' }, + floorLevel: { '@id': 'schema:floorLevel' }, + floorLimit: { '@id': 'schema:floorLimit' }, + floorSize: { '@id': 'schema:floorSize' }, + followee: { '@id': 'schema:followee' }, + follows: { '@id': 'schema:follows' }, + followup: { '@id': 'schema:followup' }, + foodEstablishment: { '@id': 'schema:foodEstablishment' }, + foodEvent: { '@id': 'schema:foodEvent' }, + foodWarning: { '@id': 'schema:foodWarning' }, + founder: { '@id': 'schema:founder' }, + founders: { '@id': 'schema:founders' }, + foundingDate: { '@id': 'schema:foundingDate', '@type': 'Date' }, + foundingLocation: { '@id': 'schema:foundingLocation' }, + free: { '@id': 'schema:free' }, + freeShippingThreshold: { '@id': 'schema:freeShippingThreshold' }, + frequency: { '@id': 'schema:frequency' }, + fromLocation: { '@id': 'schema:fromLocation' }, + fuelCapacity: { '@id': 'schema:fuelCapacity' }, + fuelConsumption: { '@id': 'schema:fuelConsumption' }, + fuelEfficiency: { '@id': 'schema:fuelEfficiency' }, + fuelType: { '@id': 'schema:fuelType' }, + functionalClass: { '@id': 'schema:functionalClass' }, + fundedItem: { '@id': 'schema:fundedItem' }, + funder: { '@id': 'schema:funder' }, + game: { '@id': 'schema:game' }, + gameItem: { '@id': 'schema:gameItem' }, + gameLocation: { '@id': 'schema:gameLocation', '@type': '@id' }, + gamePlatform: { '@id': 'schema:gamePlatform' }, + gameServer: { '@id': 'schema:gameServer' }, + gameTip: { '@id': 'schema:gameTip' }, + gender: { '@id': 'schema:gender' }, + genre: { '@id': 'schema:genre' }, + geo: { '@id': 'schema:geo' }, + geoContains: { '@id': 'schema:geoContains' }, + geoCoveredBy: { '@id': 'schema:geoCoveredBy' }, + geoCovers: { '@id': 'schema:geoCovers' }, + geoCrosses: { '@id': 'schema:geoCrosses' }, + geoDisjoint: { '@id': 'schema:geoDisjoint' }, + geoEquals: { '@id': 'schema:geoEquals' }, + geoIntersects: { '@id': 'schema:geoIntersects' }, + geoMidpoint: { '@id': 'schema:geoMidpoint' }, + geoOverlaps: { '@id': 'schema:geoOverlaps' }, + geoRadius: { '@id': 'schema:geoRadius' }, + geoTouches: { '@id': 'schema:geoTouches' }, + geoWithin: { '@id': 'schema:geoWithin' }, + geographicArea: { '@id': 'schema:geographicArea' }, + gettingTestedInfo: { '@id': 'schema:gettingTestedInfo', '@type': '@id' }, + givenName: { '@id': 'schema:givenName' }, + globalLocationNumber: { '@id': 'schema:globalLocationNumber' }, + governmentBenefitsInfo: { '@id': 'schema:governmentBenefitsInfo' }, + gracePeriod: { '@id': 'schema:gracePeriod' }, + grantee: { '@id': 'schema:grantee' }, + greater: { '@id': 'schema:greater' }, + greaterOrEqual: { '@id': 'schema:greaterOrEqual' }, + gtin: { '@id': 'schema:gtin' }, + gtin12: { '@id': 'schema:gtin12' }, + gtin13: { '@id': 'schema:gtin13' }, + gtin14: { '@id': 'schema:gtin14' }, + gtin8: { '@id': 'schema:gtin8' }, + guideline: { '@id': 'schema:guideline' }, + guidelineDate: { '@id': 'schema:guidelineDate', '@type': 'Date' }, + guidelineSubject: { '@id': 'schema:guidelineSubject' }, + handlingTime: { '@id': 'schema:handlingTime' }, + hasBroadcastChannel: { '@id': 'schema:hasBroadcastChannel' }, + hasCategoryCode: { '@id': 'schema:hasCategoryCode' }, + hasCourse: { '@id': 'schema:hasCourse' }, + hasCourseInstance: { '@id': 'schema:hasCourseInstance' }, + hasCredential: { '@id': 'schema:hasCredential' }, + hasDefinedTerm: { '@id': 'schema:hasDefinedTerm' }, + hasDeliveryMethod: { '@id': 'schema:hasDeliveryMethod' }, + hasDigitalDocumentPermission: { '@id': 'schema:hasDigitalDocumentPermission' }, + hasDriveThroughService: { '@id': 'schema:hasDriveThroughService' }, + hasEnergyConsumptionDetails: { '@id': 'schema:hasEnergyConsumptionDetails' }, + hasEnergyEfficiencyCategory: { '@id': 'schema:hasEnergyEfficiencyCategory' }, + hasHealthAspect: { '@id': 'schema:hasHealthAspect' }, + hasMap: { '@id': 'schema:hasMap', '@type': '@id' }, + hasMeasurement: { '@id': 'schema:hasMeasurement' }, + hasMenu: { '@id': 'schema:hasMenu' }, + hasMenuItem: { '@id': 'schema:hasMenuItem' }, + hasMenuSection: { '@id': 'schema:hasMenuSection' }, + hasMerchantReturnPolicy: { '@id': 'schema:hasMerchantReturnPolicy' }, + hasOccupation: { '@id': 'schema:hasOccupation' }, + hasOfferCatalog: { '@id': 'schema:hasOfferCatalog' }, + hasPOS: { '@id': 'schema:hasPOS' }, + hasPart: { '@id': 'schema:hasPart' }, + hasProductReturnPolicy: { '@id': 'schema:hasProductReturnPolicy' }, + hasVariant: { '@id': 'schema:hasVariant' }, + headline: { '@id': 'schema:headline' }, + healthCondition: { '@id': 'schema:healthCondition' }, + healthPlanCoinsuranceOption: { '@id': 'schema:healthPlanCoinsuranceOption' }, + healthPlanCoinsuranceRate: { '@id': 'schema:healthPlanCoinsuranceRate' }, + healthPlanCopay: { '@id': 'schema:healthPlanCopay' }, + healthPlanCopayOption: { '@id': 'schema:healthPlanCopayOption' }, + healthPlanCostSharing: { '@id': 'schema:healthPlanCostSharing' }, + healthPlanDrugOption: { '@id': 'schema:healthPlanDrugOption' }, + healthPlanDrugTier: { '@id': 'schema:healthPlanDrugTier' }, + healthPlanId: { '@id': 'schema:healthPlanId' }, + healthPlanMarketingUrl: { + '@id': 'schema:healthPlanMarketingUrl', + '@type': '@id', + }, + healthPlanNetworkId: { '@id': 'schema:healthPlanNetworkId' }, + healthPlanNetworkTier: { '@id': 'schema:healthPlanNetworkTier' }, + healthPlanPharmacyCategory: { '@id': 'schema:healthPlanPharmacyCategory' }, + healthcareReportingData: { '@id': 'schema:healthcareReportingData' }, + height: { '@id': 'schema:height' }, + highPrice: { '@id': 'schema:highPrice' }, + hiringOrganization: { '@id': 'schema:hiringOrganization' }, + holdingArchive: { '@id': 'schema:holdingArchive' }, + homeLocation: { '@id': 'schema:homeLocation' }, + homeTeam: { '@id': 'schema:homeTeam' }, + honorificPrefix: { '@id': 'schema:honorificPrefix' }, + honorificSuffix: { '@id': 'schema:honorificSuffix' }, + hospitalAffiliation: { '@id': 'schema:hospitalAffiliation' }, + hostingOrganization: { '@id': 'schema:hostingOrganization' }, + hoursAvailable: { '@id': 'schema:hoursAvailable' }, + howPerformed: { '@id': 'schema:howPerformed' }, + httpMethod: { '@id': 'schema:httpMethod' }, + iataCode: { '@id': 'schema:iataCode' }, + icaoCode: { '@id': 'schema:icaoCode' }, + identifier: { '@id': 'schema:identifier' }, + identifyingExam: { '@id': 'schema:identifyingExam' }, + identifyingTest: { '@id': 'schema:identifyingTest' }, + illustrator: { '@id': 'schema:illustrator' }, + image: { '@id': 'schema:image', '@type': '@id' }, + imagingTechnique: { '@id': 'schema:imagingTechnique' }, + inAlbum: { '@id': 'schema:inAlbum' }, + inBroadcastLineup: { '@id': 'schema:inBroadcastLineup' }, + inCodeSet: { '@id': 'schema:inCodeSet', '@type': '@id' }, + inDefinedTermSet: { '@id': 'schema:inDefinedTermSet', '@type': '@id' }, + inLanguage: { '@id': 'schema:inLanguage' }, + inPlaylist: { '@id': 'schema:inPlaylist' }, + inProductGroupWithID: { '@id': 'schema:inProductGroupWithID' }, + inStoreReturnsOffered: { '@id': 'schema:inStoreReturnsOffered' }, + inSupportOf: { '@id': 'schema:inSupportOf' }, + incentiveCompensation: { '@id': 'schema:incentiveCompensation' }, + incentives: { '@id': 'schema:incentives' }, + includedComposition: { '@id': 'schema:includedComposition' }, + includedDataCatalog: { '@id': 'schema:includedDataCatalog' }, + includedInDataCatalog: { '@id': 'schema:includedInDataCatalog' }, + includedInHealthInsurancePlan: { + '@id': 'schema:includedInHealthInsurancePlan', + }, + includedRiskFactor: { '@id': 'schema:includedRiskFactor' }, + includesAttraction: { '@id': 'schema:includesAttraction' }, + includesHealthPlanFormulary: { '@id': 'schema:includesHealthPlanFormulary' }, + includesHealthPlanNetwork: { '@id': 'schema:includesHealthPlanNetwork' }, + includesObject: { '@id': 'schema:includesObject' }, + increasesRiskOf: { '@id': 'schema:increasesRiskOf' }, + industry: { '@id': 'schema:industry' }, + ineligibleRegion: { '@id': 'schema:ineligibleRegion' }, + infectiousAgent: { '@id': 'schema:infectiousAgent' }, + infectiousAgentClass: { '@id': 'schema:infectiousAgentClass' }, + ingredients: { '@id': 'schema:ingredients' }, + inker: { '@id': 'schema:inker' }, + insertion: { '@id': 'schema:insertion' }, + installUrl: { '@id': 'schema:installUrl', '@type': '@id' }, + instructor: { '@id': 'schema:instructor' }, + instrument: { '@id': 'schema:instrument' }, + intensity: { '@id': 'schema:intensity' }, + interactingDrug: { '@id': 'schema:interactingDrug' }, + interactionCount: { '@id': 'schema:interactionCount' }, + interactionService: { '@id': 'schema:interactionService' }, + interactionStatistic: { '@id': 'schema:interactionStatistic' }, + interactionType: { '@id': 'schema:interactionType' }, + interactivityType: { '@id': 'schema:interactivityType' }, + interestRate: { '@id': 'schema:interestRate' }, + inventoryLevel: { '@id': 'schema:inventoryLevel' }, + inverseOf: { '@id': 'schema:inverseOf' }, + isAcceptingNewPatients: { '@id': 'schema:isAcceptingNewPatients' }, + isAccessibleForFree: { '@id': 'schema:isAccessibleForFree' }, + isAccessoryOrSparePartFor: { '@id': 'schema:isAccessoryOrSparePartFor' }, + isAvailableGenerically: { '@id': 'schema:isAvailableGenerically' }, + isBasedOn: { '@id': 'schema:isBasedOn', '@type': '@id' }, + isBasedOnUrl: { '@id': 'schema:isBasedOnUrl', '@type': '@id' }, + isConsumableFor: { '@id': 'schema:isConsumableFor' }, + isFamilyFriendly: { '@id': 'schema:isFamilyFriendly' }, + isGift: { '@id': 'schema:isGift' }, + isLiveBroadcast: { '@id': 'schema:isLiveBroadcast' }, + isPartOf: { '@id': 'schema:isPartOf', '@type': '@id' }, + isPlanForApartment: { '@id': 'schema:isPlanForApartment' }, + isProprietary: { '@id': 'schema:isProprietary' }, + isRelatedTo: { '@id': 'schema:isRelatedTo' }, + isResizable: { '@id': 'schema:isResizable' }, + isSimilarTo: { '@id': 'schema:isSimilarTo' }, + isUnlabelledFallback: { '@id': 'schema:isUnlabelledFallback' }, + isVariantOf: { '@id': 'schema:isVariantOf' }, + isbn: { '@id': 'schema:isbn' }, + isicV4: { '@id': 'schema:isicV4' }, + isrcCode: { '@id': 'schema:isrcCode' }, + issn: { '@id': 'schema:issn' }, + issueNumber: { '@id': 'schema:issueNumber' }, + issuedBy: { '@id': 'schema:issuedBy' }, + issuedThrough: { '@id': 'schema:issuedThrough' }, + iswcCode: { '@id': 'schema:iswcCode' }, + item: { '@id': 'schema:item' }, + itemCondition: { '@id': 'schema:itemCondition' }, + itemListElement: { '@id': 'schema:itemListElement' }, + itemListOrder: { '@id': 'schema:itemListOrder' }, + itemLocation: { '@id': 'schema:itemLocation' }, + itemOffered: { '@id': 'schema:itemOffered' }, + itemReviewed: { '@id': 'schema:itemReviewed' }, + itemShipped: { '@id': 'schema:itemShipped' }, + itinerary: { '@id': 'schema:itinerary' }, + jobBenefits: { '@id': 'schema:jobBenefits' }, + jobImmediateStart: { '@id': 'schema:jobImmediateStart' }, + jobLocation: { '@id': 'schema:jobLocation' }, + jobLocationType: { '@id': 'schema:jobLocationType' }, + jobStartDate: { '@id': 'schema:jobStartDate' }, + jobTitle: { '@id': 'schema:jobTitle' }, + jurisdiction: { '@id': 'schema:jurisdiction' }, + keywords: { '@id': 'schema:keywords' }, + knownVehicleDamages: { '@id': 'schema:knownVehicleDamages' }, + knows: { '@id': 'schema:knows' }, + knowsAbout: { '@id': 'schema:knowsAbout' }, + knowsLanguage: { '@id': 'schema:knowsLanguage' }, + labelDetails: { '@id': 'schema:labelDetails', '@type': '@id' }, + landlord: { '@id': 'schema:landlord' }, + language: { '@id': 'schema:language' }, + lastReviewed: { '@id': 'schema:lastReviewed', '@type': 'Date' }, + latitude: { '@id': 'schema:latitude' }, + layoutImage: { '@id': 'schema:layoutImage', '@type': '@id' }, + learningResourceType: { '@id': 'schema:learningResourceType' }, + leaseLength: { '@id': 'schema:leaseLength' }, + legalName: { '@id': 'schema:legalName' }, + legalStatus: { '@id': 'schema:legalStatus' }, + legislationApplies: { '@id': 'schema:legislationApplies' }, + legislationChanges: { '@id': 'schema:legislationChanges' }, + legislationConsolidates: { '@id': 'schema:legislationConsolidates' }, + legislationDate: { '@id': 'schema:legislationDate', '@type': 'Date' }, + legislationDateVersion: { + '@id': 'schema:legislationDateVersion', + '@type': 'Date', + }, + legislationIdentifier: { '@id': 'schema:legislationIdentifier' }, + legislationJurisdiction: { '@id': 'schema:legislationJurisdiction' }, + legislationLegalForce: { '@id': 'schema:legislationLegalForce' }, + legislationLegalValue: { '@id': 'schema:legislationLegalValue' }, + legislationPassedBy: { '@id': 'schema:legislationPassedBy' }, + legislationResponsible: { '@id': 'schema:legislationResponsible' }, + legislationTransposes: { '@id': 'schema:legislationTransposes' }, + legislationType: { '@id': 'schema:legislationType' }, + leiCode: { '@id': 'schema:leiCode' }, + lender: { '@id': 'schema:lender' }, + lesser: { '@id': 'schema:lesser' }, + lesserOrEqual: { '@id': 'schema:lesserOrEqual' }, + letterer: { '@id': 'schema:letterer' }, + license: { '@id': 'schema:license', '@type': '@id' }, + line: { '@id': 'schema:line' }, + linkRelationship: { '@id': 'schema:linkRelationship' }, + liveBlogUpdate: { '@id': 'schema:liveBlogUpdate' }, + loanMortgageMandateAmount: { '@id': 'schema:loanMortgageMandateAmount' }, + loanPaymentAmount: { '@id': 'schema:loanPaymentAmount' }, + loanPaymentFrequency: { '@id': 'schema:loanPaymentFrequency' }, + loanRepaymentForm: { '@id': 'schema:loanRepaymentForm' }, + loanTerm: { '@id': 'schema:loanTerm' }, + loanType: { '@id': 'schema:loanType' }, + location: { '@id': 'schema:location' }, + locationCreated: { '@id': 'schema:locationCreated' }, + lodgingUnitDescription: { '@id': 'schema:lodgingUnitDescription' }, + lodgingUnitType: { '@id': 'schema:lodgingUnitType' }, + logo: { '@id': 'schema:logo', '@type': '@id' }, + longitude: { '@id': 'schema:longitude' }, + loser: { '@id': 'schema:loser' }, + lowPrice: { '@id': 'schema:lowPrice' }, + lyricist: { '@id': 'schema:lyricist' }, + lyrics: { '@id': 'schema:lyrics' }, + mainContentOfPage: { '@id': 'schema:mainContentOfPage' }, + mainEntity: { '@id': 'schema:mainEntity' }, + mainEntityOfPage: { '@id': 'schema:mainEntityOfPage', '@type': '@id' }, + maintainer: { '@id': 'schema:maintainer' }, + makesOffer: { '@id': 'schema:makesOffer' }, + manufacturer: { '@id': 'schema:manufacturer' }, + map: { '@id': 'schema:map', '@type': '@id' }, + mapType: { '@id': 'schema:mapType' }, + maps: { '@id': 'schema:maps', '@type': '@id' }, + marginOfError: { '@id': 'schema:marginOfError' }, + masthead: { '@id': 'schema:masthead', '@type': '@id' }, + material: { '@id': 'schema:material' }, + materialExtent: { '@id': 'schema:materialExtent' }, + mathExpression: { '@id': 'schema:mathExpression' }, + maxPrice: { '@id': 'schema:maxPrice' }, + maxValue: { '@id': 'schema:maxValue' }, + maximumAttendeeCapacity: { '@id': 'schema:maximumAttendeeCapacity' }, + maximumEnrollment: { '@id': 'schema:maximumEnrollment' }, + maximumIntake: { '@id': 'schema:maximumIntake' }, + maximumPhysicalAttendeeCapacity: { + '@id': 'schema:maximumPhysicalAttendeeCapacity', + }, + maximumVirtualAttendeeCapacity: { + '@id': 'schema:maximumVirtualAttendeeCapacity', + }, + mealService: { '@id': 'schema:mealService' }, + measuredProperty: { '@id': 'schema:measuredProperty' }, + measuredValue: { '@id': 'schema:measuredValue' }, + measurementTechnique: { '@id': 'schema:measurementTechnique' }, + mechanismOfAction: { '@id': 'schema:mechanismOfAction' }, + mediaAuthenticityCategory: { '@id': 'schema:mediaAuthenticityCategory' }, + median: { '@id': 'schema:median' }, + medicalAudience: { '@id': 'schema:medicalAudience' }, + medicalSpecialty: { '@id': 'schema:medicalSpecialty' }, + medicineSystem: { '@id': 'schema:medicineSystem' }, + meetsEmissionStandard: { '@id': 'schema:meetsEmissionStandard' }, + member: { '@id': 'schema:member' }, + memberOf: { '@id': 'schema:memberOf' }, + members: { '@id': 'schema:members' }, + membershipNumber: { '@id': 'schema:membershipNumber' }, + membershipPointsEarned: { '@id': 'schema:membershipPointsEarned' }, + memoryRequirements: { '@id': 'schema:memoryRequirements' }, + mentions: { '@id': 'schema:mentions' }, + menu: { '@id': 'schema:menu' }, + menuAddOn: { '@id': 'schema:menuAddOn' }, + merchant: { '@id': 'schema:merchant' }, + merchantReturnDays: { '@id': 'schema:merchantReturnDays' }, + merchantReturnLink: { '@id': 'schema:merchantReturnLink', '@type': '@id' }, + messageAttachment: { '@id': 'schema:messageAttachment' }, + mileageFromOdometer: { '@id': 'schema:mileageFromOdometer' }, + minPrice: { '@id': 'schema:minPrice' }, + minValue: { '@id': 'schema:minValue' }, + minimumPaymentDue: { '@id': 'schema:minimumPaymentDue' }, + missionCoveragePrioritiesPolicy: { + '@id': 'schema:missionCoveragePrioritiesPolicy', + '@type': '@id', + }, + model: { '@id': 'schema:model' }, + modelDate: { '@id': 'schema:modelDate', '@type': 'Date' }, + modifiedTime: { '@id': 'schema:modifiedTime' }, + monthlyMinimumRepaymentAmount: { + '@id': 'schema:monthlyMinimumRepaymentAmount', + }, + monthsOfExperience: { '@id': 'schema:monthsOfExperience' }, + mpn: { '@id': 'schema:mpn' }, + multipleValues: { '@id': 'schema:multipleValues' }, + muscleAction: { '@id': 'schema:muscleAction' }, + musicArrangement: { '@id': 'schema:musicArrangement' }, + musicBy: { '@id': 'schema:musicBy' }, + musicCompositionForm: { '@id': 'schema:musicCompositionForm' }, + musicGroupMember: { '@id': 'schema:musicGroupMember' }, + musicReleaseFormat: { '@id': 'schema:musicReleaseFormat' }, + musicalKey: { '@id': 'schema:musicalKey' }, + naics: { '@id': 'schema:naics' }, + name: { '@id': 'schema:name' }, + namedPosition: { '@id': 'schema:namedPosition' }, + nationality: { '@id': 'schema:nationality' }, + naturalProgression: { '@id': 'schema:naturalProgression' }, + nerve: { '@id': 'schema:nerve' }, + nerveMotor: { '@id': 'schema:nerveMotor' }, + netWorth: { '@id': 'schema:netWorth' }, + newsUpdatesAndGuidelines: { + '@id': 'schema:newsUpdatesAndGuidelines', + '@type': '@id', + }, + nextItem: { '@id': 'schema:nextItem' }, + noBylinesPolicy: { '@id': 'schema:noBylinesPolicy', '@type': '@id' }, + nonEqual: { '@id': 'schema:nonEqual' }, + nonProprietaryName: { '@id': 'schema:nonProprietaryName' }, + nonprofitStatus: { '@id': 'schema:nonprofitStatus' }, + normalRange: { '@id': 'schema:normalRange' }, + nsn: { '@id': 'schema:nsn' }, + numAdults: { '@id': 'schema:numAdults' }, + numChildren: { '@id': 'schema:numChildren' }, + numConstraints: { '@id': 'schema:numConstraints' }, + numTracks: { '@id': 'schema:numTracks' }, + numberOfAccommodationUnits: { '@id': 'schema:numberOfAccommodationUnits' }, + numberOfAirbags: { '@id': 'schema:numberOfAirbags' }, + numberOfAvailableAccommodationUnits: { + '@id': 'schema:numberOfAvailableAccommodationUnits', + }, + numberOfAxles: { '@id': 'schema:numberOfAxles' }, + numberOfBathroomsTotal: { '@id': 'schema:numberOfBathroomsTotal' }, + numberOfBedrooms: { '@id': 'schema:numberOfBedrooms' }, + numberOfBeds: { '@id': 'schema:numberOfBeds' }, + numberOfCredits: { '@id': 'schema:numberOfCredits' }, + numberOfDoors: { '@id': 'schema:numberOfDoors' }, + numberOfEmployees: { '@id': 'schema:numberOfEmployees' }, + numberOfEpisodes: { '@id': 'schema:numberOfEpisodes' }, + numberOfForwardGears: { '@id': 'schema:numberOfForwardGears' }, + numberOfFullBathrooms: { '@id': 'schema:numberOfFullBathrooms' }, + numberOfItems: { '@id': 'schema:numberOfItems' }, + numberOfLoanPayments: { '@id': 'schema:numberOfLoanPayments' }, + numberOfPages: { '@id': 'schema:numberOfPages' }, + numberOfPartialBathrooms: { '@id': 'schema:numberOfPartialBathrooms' }, + numberOfPlayers: { '@id': 'schema:numberOfPlayers' }, + numberOfPreviousOwners: { '@id': 'schema:numberOfPreviousOwners' }, + numberOfRooms: { '@id': 'schema:numberOfRooms' }, + numberOfSeasons: { '@id': 'schema:numberOfSeasons' }, + numberedPosition: { '@id': 'schema:numberedPosition' }, + nutrition: { '@id': 'schema:nutrition' }, + object: { '@id': 'schema:object' }, + observationDate: { '@id': 'schema:observationDate' }, + observedNode: { '@id': 'schema:observedNode' }, + occupancy: { '@id': 'schema:occupancy' }, + occupationLocation: { '@id': 'schema:occupationLocation' }, + occupationalCategory: { '@id': 'schema:occupationalCategory' }, + occupationalCredentialAwarded: { + '@id': 'schema:occupationalCredentialAwarded', + }, + offerCount: { '@id': 'schema:offerCount' }, + offeredBy: { '@id': 'schema:offeredBy' }, + offers: { '@id': 'schema:offers' }, + offersPrescriptionByMail: { '@id': 'schema:offersPrescriptionByMail' }, + openingHours: { '@id': 'schema:openingHours' }, + openingHoursSpecification: { '@id': 'schema:openingHoursSpecification' }, + opens: { '@id': 'schema:opens' }, + operatingSystem: { '@id': 'schema:operatingSystem' }, + opponent: { '@id': 'schema:opponent' }, + option: { '@id': 'schema:option' }, + orderDate: { '@id': 'schema:orderDate', '@type': 'Date' }, + orderDelivery: { '@id': 'schema:orderDelivery' }, + orderItemNumber: { '@id': 'schema:orderItemNumber' }, + orderItemStatus: { '@id': 'schema:orderItemStatus' }, + orderNumber: { '@id': 'schema:orderNumber' }, + orderQuantity: { '@id': 'schema:orderQuantity' }, + orderStatus: { '@id': 'schema:orderStatus' }, + orderedItem: { '@id': 'schema:orderedItem' }, + organizer: { '@id': 'schema:organizer' }, + originAddress: { '@id': 'schema:originAddress' }, + originatesFrom: { '@id': 'schema:originatesFrom' }, + overdosage: { '@id': 'schema:overdosage' }, + ownedFrom: { '@id': 'schema:ownedFrom' }, + ownedThrough: { '@id': 'schema:ownedThrough' }, + ownershipFundingInfo: { '@id': 'schema:ownershipFundingInfo' }, + owns: { '@id': 'schema:owns' }, + pageEnd: { '@id': 'schema:pageEnd' }, + pageStart: { '@id': 'schema:pageStart' }, + pagination: { '@id': 'schema:pagination' }, + parent: { '@id': 'schema:parent' }, + parentItem: { '@id': 'schema:parentItem' }, + parentOrganization: { '@id': 'schema:parentOrganization' }, + parentService: { '@id': 'schema:parentService' }, + parents: { '@id': 'schema:parents' }, + partOfEpisode: { '@id': 'schema:partOfEpisode' }, + partOfInvoice: { '@id': 'schema:partOfInvoice' }, + partOfOrder: { '@id': 'schema:partOfOrder' }, + partOfSeason: { '@id': 'schema:partOfSeason' }, + partOfSeries: { '@id': 'schema:partOfSeries' }, + partOfSystem: { '@id': 'schema:partOfSystem' }, + partOfTVSeries: { '@id': 'schema:partOfTVSeries' }, + partOfTrip: { '@id': 'schema:partOfTrip' }, + participant: { '@id': 'schema:participant' }, + partySize: { '@id': 'schema:partySize' }, + passengerPriorityStatus: { '@id': 'schema:passengerPriorityStatus' }, + passengerSequenceNumber: { '@id': 'schema:passengerSequenceNumber' }, + pathophysiology: { '@id': 'schema:pathophysiology' }, + pattern: { '@id': 'schema:pattern' }, + payload: { '@id': 'schema:payload' }, + paymentAccepted: { '@id': 'schema:paymentAccepted' }, + paymentDue: { '@id': 'schema:paymentDue' }, + paymentDueDate: { '@id': 'schema:paymentDueDate', '@type': 'Date' }, + paymentMethod: { '@id': 'schema:paymentMethod' }, + paymentMethodId: { '@id': 'schema:paymentMethodId' }, + paymentStatus: { '@id': 'schema:paymentStatus' }, + paymentUrl: { '@id': 'schema:paymentUrl', '@type': '@id' }, + penciler: { '@id': 'schema:penciler' }, + percentile10: { '@id': 'schema:percentile10' }, + percentile25: { '@id': 'schema:percentile25' }, + percentile75: { '@id': 'schema:percentile75' }, + percentile90: { '@id': 'schema:percentile90' }, + performTime: { '@id': 'schema:performTime' }, + performer: { '@id': 'schema:performer' }, + performerIn: { '@id': 'schema:performerIn' }, + performers: { '@id': 'schema:performers' }, + permissionType: { '@id': 'schema:permissionType' }, + permissions: { '@id': 'schema:permissions' }, + permitAudience: { '@id': 'schema:permitAudience' }, + permittedUsage: { '@id': 'schema:permittedUsage' }, + petsAllowed: { '@id': 'schema:petsAllowed' }, + phoneticText: { '@id': 'schema:phoneticText' }, + photo: { '@id': 'schema:photo' }, + photos: { '@id': 'schema:photos' }, + physicalRequirement: { '@id': 'schema:physicalRequirement' }, + physiologicalBenefits: { '@id': 'schema:physiologicalBenefits' }, + pickupLocation: { '@id': 'schema:pickupLocation' }, + pickupTime: { '@id': 'schema:pickupTime' }, + playMode: { '@id': 'schema:playMode' }, + playerType: { '@id': 'schema:playerType' }, + playersOnline: { '@id': 'schema:playersOnline' }, + polygon: { '@id': 'schema:polygon' }, + populationType: { '@id': 'schema:populationType' }, + position: { '@id': 'schema:position' }, + possibleComplication: { '@id': 'schema:possibleComplication' }, + possibleTreatment: { '@id': 'schema:possibleTreatment' }, + postOfficeBoxNumber: { '@id': 'schema:postOfficeBoxNumber' }, + postOp: { '@id': 'schema:postOp' }, + postalCode: { '@id': 'schema:postalCode' }, + postalCodeBegin: { '@id': 'schema:postalCodeBegin' }, + postalCodeEnd: { '@id': 'schema:postalCodeEnd' }, + postalCodePrefix: { '@id': 'schema:postalCodePrefix' }, + postalCodeRange: { '@id': 'schema:postalCodeRange' }, + potentialAction: { '@id': 'schema:potentialAction' }, + preOp: { '@id': 'schema:preOp' }, + predecessorOf: { '@id': 'schema:predecessorOf' }, + pregnancyCategory: { '@id': 'schema:pregnancyCategory' }, + pregnancyWarning: { '@id': 'schema:pregnancyWarning' }, + prepTime: { '@id': 'schema:prepTime' }, + preparation: { '@id': 'schema:preparation' }, + prescribingInfo: { '@id': 'schema:prescribingInfo', '@type': '@id' }, + prescriptionStatus: { '@id': 'schema:prescriptionStatus' }, + previousItem: { '@id': 'schema:previousItem' }, + previousStartDate: { '@id': 'schema:previousStartDate', '@type': 'Date' }, + price: { '@id': 'schema:price' }, + priceComponent: { '@id': 'schema:priceComponent' }, + priceComponentType: { '@id': 'schema:priceComponentType' }, + priceCurrency: { '@id': 'schema:priceCurrency' }, + priceRange: { '@id': 'schema:priceRange' }, + priceSpecification: { '@id': 'schema:priceSpecification' }, + priceType: { '@id': 'schema:priceType' }, + priceValidUntil: { '@id': 'schema:priceValidUntil', '@type': 'Date' }, + primaryImageOfPage: { '@id': 'schema:primaryImageOfPage' }, + primaryPrevention: { '@id': 'schema:primaryPrevention' }, + printColumn: { '@id': 'schema:printColumn' }, + printEdition: { '@id': 'schema:printEdition' }, + printPage: { '@id': 'schema:printPage' }, + printSection: { '@id': 'schema:printSection' }, + procedure: { '@id': 'schema:procedure' }, + procedureType: { '@id': 'schema:procedureType' }, + processingTime: { '@id': 'schema:processingTime' }, + processorRequirements: { '@id': 'schema:processorRequirements' }, + producer: { '@id': 'schema:producer' }, + produces: { '@id': 'schema:produces' }, + productGroupID: { '@id': 'schema:productGroupID' }, + productID: { '@id': 'schema:productID' }, + productReturnDays: { '@id': 'schema:productReturnDays' }, + productReturnLink: { '@id': 'schema:productReturnLink', '@type': '@id' }, + productSupported: { '@id': 'schema:productSupported' }, + productionCompany: { '@id': 'schema:productionCompany' }, + productionDate: { '@id': 'schema:productionDate', '@type': 'Date' }, + proficiencyLevel: { '@id': 'schema:proficiencyLevel' }, + programMembershipUsed: { '@id': 'schema:programMembershipUsed' }, + programName: { '@id': 'schema:programName' }, + programPrerequisites: { '@id': 'schema:programPrerequisites' }, + programType: { '@id': 'schema:programType' }, + programmingLanguage: { '@id': 'schema:programmingLanguage' }, + programmingModel: { '@id': 'schema:programmingModel' }, + propertyID: { '@id': 'schema:propertyID' }, + proprietaryName: { '@id': 'schema:proprietaryName' }, + proteinContent: { '@id': 'schema:proteinContent' }, + provider: { '@id': 'schema:provider' }, + providerMobility: { '@id': 'schema:providerMobility' }, + providesBroadcastService: { '@id': 'schema:providesBroadcastService' }, + providesService: { '@id': 'schema:providesService' }, + publicAccess: { '@id': 'schema:publicAccess' }, + publicTransportClosuresInfo: { + '@id': 'schema:publicTransportClosuresInfo', + '@type': '@id', + }, + publication: { '@id': 'schema:publication' }, + publicationType: { '@id': 'schema:publicationType' }, + publishedBy: { '@id': 'schema:publishedBy' }, + publishedOn: { '@id': 'schema:publishedOn' }, + publisher: { '@id': 'schema:publisher' }, + publisherImprint: { '@id': 'schema:publisherImprint' }, + publishingPrinciples: { '@id': 'schema:publishingPrinciples', '@type': '@id' }, + purchaseDate: { '@id': 'schema:purchaseDate', '@type': 'Date' }, + qualifications: { '@id': 'schema:qualifications' }, + quarantineGuidelines: { '@id': 'schema:quarantineGuidelines', '@type': '@id' }, + query: { '@id': 'schema:query' }, + quest: { '@id': 'schema:quest' }, + question: { '@id': 'schema:question' }, + rangeIncludes: { '@id': 'schema:rangeIncludes' }, + ratingCount: { '@id': 'schema:ratingCount' }, + ratingExplanation: { '@id': 'schema:ratingExplanation' }, + ratingValue: { '@id': 'schema:ratingValue' }, + readBy: { '@id': 'schema:readBy' }, + readonlyValue: { '@id': 'schema:readonlyValue' }, + realEstateAgent: { '@id': 'schema:realEstateAgent' }, + recipe: { '@id': 'schema:recipe' }, + recipeCategory: { '@id': 'schema:recipeCategory' }, + recipeCuisine: { '@id': 'schema:recipeCuisine' }, + recipeIngredient: { '@id': 'schema:recipeIngredient' }, + recipeInstructions: { '@id': 'schema:recipeInstructions' }, + recipeYield: { '@id': 'schema:recipeYield' }, + recipient: { '@id': 'schema:recipient' }, + recognizedBy: { '@id': 'schema:recognizedBy' }, + recognizingAuthority: { '@id': 'schema:recognizingAuthority' }, + recommendationStrength: { '@id': 'schema:recommendationStrength' }, + recommendedIntake: { '@id': 'schema:recommendedIntake' }, + recordLabel: { '@id': 'schema:recordLabel' }, + recordedAs: { '@id': 'schema:recordedAs' }, + recordedAt: { '@id': 'schema:recordedAt' }, + recordedIn: { '@id': 'schema:recordedIn' }, + recordingOf: { '@id': 'schema:recordingOf' }, + recourseLoan: { '@id': 'schema:recourseLoan' }, + referenceQuantity: { '@id': 'schema:referenceQuantity' }, + referencesOrder: { '@id': 'schema:referencesOrder' }, + refundType: { '@id': 'schema:refundType' }, + regionDrained: { '@id': 'schema:regionDrained' }, + regionsAllowed: { '@id': 'schema:regionsAllowed' }, + relatedAnatomy: { '@id': 'schema:relatedAnatomy' }, + relatedCondition: { '@id': 'schema:relatedCondition' }, + relatedDrug: { '@id': 'schema:relatedDrug' }, + relatedLink: { '@id': 'schema:relatedLink', '@type': '@id' }, + relatedStructure: { '@id': 'schema:relatedStructure' }, + relatedTherapy: { '@id': 'schema:relatedTherapy' }, + relatedTo: { '@id': 'schema:relatedTo' }, + releaseDate: { '@id': 'schema:releaseDate', '@type': 'Date' }, + releaseNotes: { '@id': 'schema:releaseNotes' }, + releaseOf: { '@id': 'schema:releaseOf' }, + releasedEvent: { '@id': 'schema:releasedEvent' }, + relevantOccupation: { '@id': 'schema:relevantOccupation' }, + relevantSpecialty: { '@id': 'schema:relevantSpecialty' }, + remainingAttendeeCapacity: { '@id': 'schema:remainingAttendeeCapacity' }, + renegotiableLoan: { '@id': 'schema:renegotiableLoan' }, + repeatCount: { '@id': 'schema:repeatCount' }, + repeatFrequency: { '@id': 'schema:repeatFrequency' }, + repetitions: { '@id': 'schema:repetitions' }, + replacee: { '@id': 'schema:replacee' }, + replacer: { '@id': 'schema:replacer' }, + replyToUrl: { '@id': 'schema:replyToUrl', '@type': '@id' }, + reportNumber: { '@id': 'schema:reportNumber' }, + representativeOfPage: { '@id': 'schema:representativeOfPage' }, + requiredCollateral: { '@id': 'schema:requiredCollateral' }, + requiredGender: { '@id': 'schema:requiredGender' }, + requiredMaxAge: { '@id': 'schema:requiredMaxAge' }, + requiredMinAge: { '@id': 'schema:requiredMinAge' }, + requiredQuantity: { '@id': 'schema:requiredQuantity' }, + requirements: { '@id': 'schema:requirements' }, + requiresSubscription: { '@id': 'schema:requiresSubscription' }, + reservationFor: { '@id': 'schema:reservationFor' }, + reservationId: { '@id': 'schema:reservationId' }, + reservationStatus: { '@id': 'schema:reservationStatus' }, + reservedTicket: { '@id': 'schema:reservedTicket' }, + responsibilities: { '@id': 'schema:responsibilities' }, + restPeriods: { '@id': 'schema:restPeriods' }, + result: { '@id': 'schema:result' }, + resultComment: { '@id': 'schema:resultComment' }, + resultReview: { '@id': 'schema:resultReview' }, + returnFees: { '@id': 'schema:returnFees' }, + returnPolicyCategory: { '@id': 'schema:returnPolicyCategory' }, + review: { '@id': 'schema:review' }, + reviewAspect: { '@id': 'schema:reviewAspect' }, + reviewBody: { '@id': 'schema:reviewBody' }, + reviewCount: { '@id': 'schema:reviewCount' }, + reviewRating: { '@id': 'schema:reviewRating' }, + reviewedBy: { '@id': 'schema:reviewedBy' }, + reviews: { '@id': 'schema:reviews' }, + riskFactor: { '@id': 'schema:riskFactor' }, + risks: { '@id': 'schema:risks' }, + roleName: { '@id': 'schema:roleName' }, + roofLoad: { '@id': 'schema:roofLoad' }, + rsvpResponse: { '@id': 'schema:rsvpResponse' }, + runsTo: { '@id': 'schema:runsTo' }, + runtime: { '@id': 'schema:runtime' }, + runtimePlatform: { '@id': 'schema:runtimePlatform' }, + rxcui: { '@id': 'schema:rxcui' }, + safetyConsideration: { '@id': 'schema:safetyConsideration' }, + salaryCurrency: { '@id': 'schema:salaryCurrency' }, + salaryUponCompletion: { '@id': 'schema:salaryUponCompletion' }, + sameAs: { '@id': 'schema:sameAs', '@type': '@id' }, + sampleType: { '@id': 'schema:sampleType' }, + saturatedFatContent: { '@id': 'schema:saturatedFatContent' }, + scheduleTimezone: { '@id': 'schema:scheduleTimezone' }, + scheduledPaymentDate: { '@id': 'schema:scheduledPaymentDate', '@type': 'Date' }, + scheduledTime: { '@id': 'schema:scheduledTime' }, + schemaVersion: { '@id': 'schema:schemaVersion' }, + schoolClosuresInfo: { '@id': 'schema:schoolClosuresInfo', '@type': '@id' }, + screenCount: { '@id': 'schema:screenCount' }, + screenshot: { '@id': 'schema:screenshot', '@type': '@id' }, + sdDatePublished: { '@id': 'schema:sdDatePublished', '@type': 'Date' }, + sdLicense: { '@id': 'schema:sdLicense', '@type': '@id' }, + sdPublisher: { '@id': 'schema:sdPublisher' }, + season: { '@id': 'schema:season', '@type': '@id' }, + seasonNumber: { '@id': 'schema:seasonNumber' }, + seasons: { '@id': 'schema:seasons' }, + seatNumber: { '@id': 'schema:seatNumber' }, + seatRow: { '@id': 'schema:seatRow' }, + seatSection: { '@id': 'schema:seatSection' }, + seatingCapacity: { '@id': 'schema:seatingCapacity' }, + seatingType: { '@id': 'schema:seatingType' }, + secondaryPrevention: { '@id': 'schema:secondaryPrevention' }, + securityClearanceRequirement: { '@id': 'schema:securityClearanceRequirement' }, + securityScreening: { '@id': 'schema:securityScreening' }, + seeks: { '@id': 'schema:seeks' }, + seller: { '@id': 'schema:seller' }, + sender: { '@id': 'schema:sender' }, + sensoryRequirement: { '@id': 'schema:sensoryRequirement' }, + sensoryUnit: { '@id': 'schema:sensoryUnit' }, + serialNumber: { '@id': 'schema:serialNumber' }, + seriousAdverseOutcome: { '@id': 'schema:seriousAdverseOutcome' }, + serverStatus: { '@id': 'schema:serverStatus' }, + servesCuisine: { '@id': 'schema:servesCuisine' }, + serviceArea: { '@id': 'schema:serviceArea' }, + serviceAudience: { '@id': 'schema:serviceAudience' }, + serviceLocation: { '@id': 'schema:serviceLocation' }, + serviceOperator: { '@id': 'schema:serviceOperator' }, + serviceOutput: { '@id': 'schema:serviceOutput' }, + servicePhone: { '@id': 'schema:servicePhone' }, + servicePostalAddress: { '@id': 'schema:servicePostalAddress' }, + serviceSmsNumber: { '@id': 'schema:serviceSmsNumber' }, + serviceType: { '@id': 'schema:serviceType' }, + serviceUrl: { '@id': 'schema:serviceUrl', '@type': '@id' }, + servingSize: { '@id': 'schema:servingSize' }, + sharedContent: { '@id': 'schema:sharedContent' }, + shippingDestination: { '@id': 'schema:shippingDestination' }, + shippingDetails: { '@id': 'schema:shippingDetails' }, + shippingLabel: { '@id': 'schema:shippingLabel' }, + shippingRate: { '@id': 'schema:shippingRate' }, + shippingSettingsLink: { '@id': 'schema:shippingSettingsLink', '@type': '@id' }, + sibling: { '@id': 'schema:sibling' }, + siblings: { '@id': 'schema:siblings' }, + signDetected: { '@id': 'schema:signDetected' }, + signOrSymptom: { '@id': 'schema:signOrSymptom' }, + significance: { '@id': 'schema:significance' }, + significantLink: { '@id': 'schema:significantLink', '@type': '@id' }, + significantLinks: { '@id': 'schema:significantLinks', '@type': '@id' }, + size: { '@id': 'schema:size' }, + sizeGroup: { '@id': 'schema:sizeGroup' }, + sizeSystem: { '@id': 'schema:sizeSystem' }, + skills: { '@id': 'schema:skills' }, + sku: { '@id': 'schema:sku' }, + slogan: { '@id': 'schema:slogan' }, + smokingAllowed: { '@id': 'schema:smokingAllowed' }, + sodiumContent: { '@id': 'schema:sodiumContent' }, + softwareAddOn: { '@id': 'schema:softwareAddOn' }, + softwareHelp: { '@id': 'schema:softwareHelp' }, + softwareRequirements: { '@id': 'schema:softwareRequirements' }, + softwareVersion: { '@id': 'schema:softwareVersion' }, + sourceOrganization: { '@id': 'schema:sourceOrganization' }, + sourcedFrom: { '@id': 'schema:sourcedFrom' }, + spatial: { '@id': 'schema:spatial' }, + spatialCoverage: { '@id': 'schema:spatialCoverage' }, + speakable: { '@id': 'schema:speakable', '@type': '@id' }, + specialCommitments: { '@id': 'schema:specialCommitments' }, + specialOpeningHoursSpecification: { + '@id': 'schema:specialOpeningHoursSpecification', + }, + specialty: { '@id': 'schema:specialty' }, + speechToTextMarkup: { '@id': 'schema:speechToTextMarkup' }, + speed: { '@id': 'schema:speed' }, + spokenByCharacter: { '@id': 'schema:spokenByCharacter' }, + sponsor: { '@id': 'schema:sponsor' }, + sport: { '@id': 'schema:sport' }, + sportsActivityLocation: { '@id': 'schema:sportsActivityLocation' }, + sportsEvent: { '@id': 'schema:sportsEvent' }, + sportsTeam: { '@id': 'schema:sportsTeam' }, + spouse: { '@id': 'schema:spouse' }, + stage: { '@id': 'schema:stage' }, + stageAsNumber: { '@id': 'schema:stageAsNumber' }, + starRating: { '@id': 'schema:starRating' }, + startDate: { '@id': 'schema:startDate', '@type': 'Date' }, + startOffset: { '@id': 'schema:startOffset' }, + startTime: { '@id': 'schema:startTime' }, + status: { '@id': 'schema:status' }, + steeringPosition: { '@id': 'schema:steeringPosition' }, + step: { '@id': 'schema:step' }, + stepValue: { '@id': 'schema:stepValue' }, + steps: { '@id': 'schema:steps' }, + storageRequirements: { '@id': 'schema:storageRequirements' }, + streetAddress: { '@id': 'schema:streetAddress' }, + strengthUnit: { '@id': 'schema:strengthUnit' }, + strengthValue: { '@id': 'schema:strengthValue' }, + structuralClass: { '@id': 'schema:structuralClass' }, + study: { '@id': 'schema:study' }, + studyDesign: { '@id': 'schema:studyDesign' }, + studyLocation: { '@id': 'schema:studyLocation' }, + studySubject: { '@id': 'schema:studySubject' }, + stupidProperty: { '@id': 'schema:stupidProperty' }, + subEvent: { '@id': 'schema:subEvent' }, + subEvents: { '@id': 'schema:subEvents' }, + subOrganization: { '@id': 'schema:subOrganization' }, + subReservation: { '@id': 'schema:subReservation' }, + subStageSuffix: { '@id': 'schema:subStageSuffix' }, + subStructure: { '@id': 'schema:subStructure' }, + subTest: { '@id': 'schema:subTest' }, + subTrip: { '@id': 'schema:subTrip' }, + subjectOf: { '@id': 'schema:subjectOf' }, + subtitleLanguage: { '@id': 'schema:subtitleLanguage' }, + successorOf: { '@id': 'schema:successorOf' }, + sugarContent: { '@id': 'schema:sugarContent' }, + suggestedAge: { '@id': 'schema:suggestedAge' }, + suggestedAnswer: { '@id': 'schema:suggestedAnswer' }, + suggestedGender: { '@id': 'schema:suggestedGender' }, + suggestedMaxAge: { '@id': 'schema:suggestedMaxAge' }, + suggestedMeasurement: { '@id': 'schema:suggestedMeasurement' }, + suggestedMinAge: { '@id': 'schema:suggestedMinAge' }, + suitableForDiet: { '@id': 'schema:suitableForDiet' }, + superEvent: { '@id': 'schema:superEvent' }, + supersededBy: { '@id': 'schema:supersededBy' }, + supply: { '@id': 'schema:supply' }, + supplyTo: { '@id': 'schema:supplyTo' }, + supportingData: { '@id': 'schema:supportingData' }, + surface: { '@id': 'schema:surface' }, + target: { '@id': 'schema:target' }, + targetCollection: { '@id': 'schema:targetCollection' }, + targetDescription: { '@id': 'schema:targetDescription' }, + targetName: { '@id': 'schema:targetName' }, + targetPlatform: { '@id': 'schema:targetPlatform' }, + targetPopulation: { '@id': 'schema:targetPopulation' }, + targetProduct: { '@id': 'schema:targetProduct' }, + targetUrl: { '@id': 'schema:targetUrl', '@type': '@id' }, + taxID: { '@id': 'schema:taxID' }, + teaches: { '@id': 'schema:teaches' }, + telephone: { '@id': 'schema:telephone' }, + temporal: { '@id': 'schema:temporal' }, + temporalCoverage: { '@id': 'schema:temporalCoverage' }, + termCode: { '@id': 'schema:termCode' }, + termDuration: { '@id': 'schema:termDuration' }, + termsOfService: { '@id': 'schema:termsOfService' }, + termsPerYear: { '@id': 'schema:termsPerYear' }, + text: { '@id': 'schema:text' }, + textValue: { '@id': 'schema:textValue' }, + thumbnail: { '@id': 'schema:thumbnail' }, + thumbnailUrl: { '@id': 'schema:thumbnailUrl', '@type': '@id' }, + tickerSymbol: { '@id': 'schema:tickerSymbol' }, + ticketNumber: { '@id': 'schema:ticketNumber' }, + ticketToken: { '@id': 'schema:ticketToken' }, + ticketedSeat: { '@id': 'schema:ticketedSeat' }, + timeOfDay: { '@id': 'schema:timeOfDay' }, + timeRequired: { '@id': 'schema:timeRequired' }, + timeToComplete: { '@id': 'schema:timeToComplete' }, + tissueSample: { '@id': 'schema:tissueSample' }, + title: { '@id': 'schema:title' }, + titleEIDR: { '@id': 'schema:titleEIDR' }, + toLocation: { '@id': 'schema:toLocation' }, + toRecipient: { '@id': 'schema:toRecipient' }, + tocContinuation: { '@id': 'schema:tocContinuation' }, + tocEntry: { '@id': 'schema:tocEntry' }, + tongueWeight: { '@id': 'schema:tongueWeight' }, + tool: { '@id': 'schema:tool' }, + torque: { '@id': 'schema:torque' }, + totalJobOpenings: { '@id': 'schema:totalJobOpenings' }, + totalPaymentDue: { '@id': 'schema:totalPaymentDue' }, + totalPrice: { '@id': 'schema:totalPrice' }, + totalTime: { '@id': 'schema:totalTime' }, + tourBookingPage: { '@id': 'schema:tourBookingPage', '@type': '@id' }, + touristType: { '@id': 'schema:touristType' }, + track: { '@id': 'schema:track' }, + trackingNumber: { '@id': 'schema:trackingNumber' }, + trackingUrl: { '@id': 'schema:trackingUrl', '@type': '@id' }, + tracks: { '@id': 'schema:tracks' }, + trailer: { '@id': 'schema:trailer' }, + trailerWeight: { '@id': 'schema:trailerWeight' }, + trainName: { '@id': 'schema:trainName' }, + trainNumber: { '@id': 'schema:trainNumber' }, + trainingSalary: { '@id': 'schema:trainingSalary' }, + transFatContent: { '@id': 'schema:transFatContent' }, + transcript: { '@id': 'schema:transcript' }, + transitTime: { '@id': 'schema:transitTime' }, + transitTimeLabel: { '@id': 'schema:transitTimeLabel' }, + translationOfWork: { '@id': 'schema:translationOfWork' }, + translator: { '@id': 'schema:translator' }, + transmissionMethod: { '@id': 'schema:transmissionMethod' }, + travelBans: { '@id': 'schema:travelBans', '@type': '@id' }, + trialDesign: { '@id': 'schema:trialDesign' }, + tributary: { '@id': 'schema:tributary' }, + typeOfBed: { '@id': 'schema:typeOfBed' }, + typeOfGood: { '@id': 'schema:typeOfGood' }, + typicalAgeRange: { '@id': 'schema:typicalAgeRange' }, + typicalCreditsPerTerm: { '@id': 'schema:typicalCreditsPerTerm' }, + typicalTest: { '@id': 'schema:typicalTest' }, + underName: { '@id': 'schema:underName' }, + unitCode: { '@id': 'schema:unitCode' }, + unitText: { '@id': 'schema:unitText' }, + unnamedSourcesPolicy: { '@id': 'schema:unnamedSourcesPolicy', '@type': '@id' }, + unsaturatedFatContent: { '@id': 'schema:unsaturatedFatContent' }, + uploadDate: { '@id': 'schema:uploadDate', '@type': 'Date' }, + upvoteCount: { '@id': 'schema:upvoteCount' }, + url: { '@id': 'schema:url', '@type': '@id' }, + urlTemplate: { '@id': 'schema:urlTemplate' }, + usageInfo: { '@id': 'schema:usageInfo', '@type': '@id' }, + usedToDiagnose: { '@id': 'schema:usedToDiagnose' }, + userInteractionCount: { '@id': 'schema:userInteractionCount' }, + usesDevice: { '@id': 'schema:usesDevice' }, + usesHealthPlanIdStandard: { '@id': 'schema:usesHealthPlanIdStandard' }, + utterances: { '@id': 'schema:utterances' }, + validFor: { '@id': 'schema:validFor' }, + validFrom: { '@id': 'schema:validFrom', '@type': 'Date' }, + validIn: { '@id': 'schema:validIn' }, + validThrough: { '@id': 'schema:validThrough', '@type': 'Date' }, + validUntil: { '@id': 'schema:validUntil', '@type': 'Date' }, + value: { '@id': 'schema:value' }, + valueAddedTaxIncluded: { '@id': 'schema:valueAddedTaxIncluded' }, + valueMaxLength: { '@id': 'schema:valueMaxLength' }, + valueMinLength: { '@id': 'schema:valueMinLength' }, + valueName: { '@id': 'schema:valueName' }, + valuePattern: { '@id': 'schema:valuePattern' }, + valueReference: { '@id': 'schema:valueReference' }, + valueRequired: { '@id': 'schema:valueRequired' }, + variableMeasured: { '@id': 'schema:variableMeasured' }, + variablesMeasured: { '@id': 'schema:variablesMeasured' }, + variantCover: { '@id': 'schema:variantCover' }, + variesBy: { '@id': 'schema:variesBy' }, + vatID: { '@id': 'schema:vatID' }, + vehicleConfiguration: { '@id': 'schema:vehicleConfiguration' }, + vehicleEngine: { '@id': 'schema:vehicleEngine' }, + vehicleIdentificationNumber: { '@id': 'schema:vehicleIdentificationNumber' }, + vehicleInteriorColor: { '@id': 'schema:vehicleInteriorColor' }, + vehicleInteriorType: { '@id': 'schema:vehicleInteriorType' }, + vehicleModelDate: { '@id': 'schema:vehicleModelDate', '@type': 'Date' }, + vehicleSeatingCapacity: { '@id': 'schema:vehicleSeatingCapacity' }, + vehicleSpecialUsage: { '@id': 'schema:vehicleSpecialUsage' }, + vehicleTransmission: { '@id': 'schema:vehicleTransmission' }, + vendor: { '@id': 'schema:vendor' }, + verificationFactCheckingPolicy: { + '@id': 'schema:verificationFactCheckingPolicy', + '@type': '@id', + }, + version: { '@id': 'schema:version' }, + video: { '@id': 'schema:video' }, + videoFormat: { '@id': 'schema:videoFormat' }, + videoFrameSize: { '@id': 'schema:videoFrameSize' }, + videoQuality: { '@id': 'schema:videoQuality' }, + volumeNumber: { '@id': 'schema:volumeNumber' }, + warning: { '@id': 'schema:warning' }, + warranty: { '@id': 'schema:warranty' }, + warrantyPromise: { '@id': 'schema:warrantyPromise' }, + warrantyScope: { '@id': 'schema:warrantyScope' }, + webCheckinTime: { '@id': 'schema:webCheckinTime' }, + webFeed: { '@id': 'schema:webFeed', '@type': '@id' }, + weight: { '@id': 'schema:weight' }, + weightTotal: { '@id': 'schema:weightTotal' }, + wheelbase: { '@id': 'schema:wheelbase' }, + width: { '@id': 'schema:width' }, + winner: { '@id': 'schema:winner' }, + wordCount: { '@id': 'schema:wordCount' }, + workExample: { '@id': 'schema:workExample' }, + workFeatured: { '@id': 'schema:workFeatured' }, + workHours: { '@id': 'schema:workHours' }, + workLocation: { '@id': 'schema:workLocation' }, + workPerformed: { '@id': 'schema:workPerformed' }, + workPresented: { '@id': 'schema:workPresented' }, + workTranslation: { '@id': 'schema:workTranslation' }, + workload: { '@id': 'schema:workload' }, + worksFor: { '@id': 'schema:worksFor' }, + worstRating: { '@id': 'schema:worstRating' }, + xpath: { '@id': 'schema:xpath' }, + yearBuilt: { '@id': 'schema:yearBuilt' }, + yearlyRevenue: { '@id': 'schema:yearlyRevenue' }, + yearsInOperation: { '@id': 'schema:yearsInOperation' }, + yield: { '@id': 'schema:yield' }, + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/security_v1.ts b/packages/core/src/modules/vc/__tests__/contexts/security_v1.ts new file mode 100644 index 0000000000..070779f190 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/security_v1.ts @@ -0,0 +1,47 @@ +export const SECURITY_V1 = { + '@context': { + id: '@id', + type: '@type', + dc: 'http://purl.org/dc/terms/', + sec: 'https://w3id.org/security#', + xsd: 'http://www.w3.org/2001/XMLSchema#', + EcdsaKoblitzSignature2016: 'sec:EcdsaKoblitzSignature2016', + Ed25519Signature2018: 'sec:Ed25519Signature2018', + EncryptedMessage: 'sec:EncryptedMessage', + GraphSignature2012: 'sec:GraphSignature2012', + LinkedDataSignature2015: 'sec:LinkedDataSignature2015', + LinkedDataSignature2016: 'sec:LinkedDataSignature2016', + CryptographicKey: 'sec:Key', + authenticationTag: 'sec:authenticationTag', + canonicalizationAlgorithm: 'sec:canonicalizationAlgorithm', + cipherAlgorithm: 'sec:cipherAlgorithm', + cipherData: 'sec:cipherData', + cipherKey: 'sec:cipherKey', + created: { '@id': 'dc:created', '@type': 'xsd:dateTime' }, + creator: { '@id': 'dc:creator', '@type': '@id' }, + digestAlgorithm: 'sec:digestAlgorithm', + digestValue: 'sec:digestValue', + domain: 'sec:domain', + encryptionKey: 'sec:encryptionKey', + expiration: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, + expires: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, + initializationVector: 'sec:initializationVector', + iterationCount: 'sec:iterationCount', + nonce: 'sec:nonce', + normalizationAlgorithm: 'sec:normalizationAlgorithm', + owner: { '@id': 'sec:owner', '@type': '@id' }, + password: 'sec:password', + privateKey: { '@id': 'sec:privateKey', '@type': '@id' }, + privateKeyPem: 'sec:privateKeyPem', + publicKey: { '@id': 'sec:publicKey', '@type': '@id' }, + publicKeyBase58: 'sec:publicKeyBase58', + publicKeyPem: 'sec:publicKeyPem', + publicKeyWif: 'sec:publicKeyWif', + publicKeyService: { '@id': 'sec:publicKeyService', '@type': '@id' }, + revoked: { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, + salt: 'sec:salt', + signature: 'sec:signature', + signatureAlgorithm: 'sec:signingAlgorithm', + signatureValue: 'sec:signatureValue', + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/security_v2.ts b/packages/core/src/modules/vc/__tests__/contexts/security_v2.ts new file mode 100644 index 0000000000..5da116cae1 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/security_v2.ts @@ -0,0 +1,90 @@ +export const SECURITY_V2 = { + '@context': [ + { '@version': 1.1 }, + 'https://w3id.org/security/v1', + { + AesKeyWrappingKey2019: 'sec:AesKeyWrappingKey2019', + DeleteKeyOperation: 'sec:DeleteKeyOperation', + DeriveSecretOperation: 'sec:DeriveSecretOperation', + EcdsaSecp256k1Signature2019: 'sec:EcdsaSecp256k1Signature2019', + EcdsaSecp256r1Signature2019: 'sec:EcdsaSecp256r1Signature2019', + EcdsaSecp256k1VerificationKey2019: 'sec:EcdsaSecp256k1VerificationKey2019', + EcdsaSecp256r1VerificationKey2019: 'sec:EcdsaSecp256r1VerificationKey2019', + Ed25519Signature2018: 'sec:Ed25519Signature2018', + Ed25519VerificationKey2018: 'sec:Ed25519VerificationKey2018', + EquihashProof2018: 'sec:EquihashProof2018', + ExportKeyOperation: 'sec:ExportKeyOperation', + GenerateKeyOperation: 'sec:GenerateKeyOperation', + KmsOperation: 'sec:KmsOperation', + RevokeKeyOperation: 'sec:RevokeKeyOperation', + RsaSignature2018: 'sec:RsaSignature2018', + RsaVerificationKey2018: 'sec:RsaVerificationKey2018', + Sha256HmacKey2019: 'sec:Sha256HmacKey2019', + SignOperation: 'sec:SignOperation', + UnwrapKeyOperation: 'sec:UnwrapKeyOperation', + VerifyOperation: 'sec:VerifyOperation', + WrapKeyOperation: 'sec:WrapKeyOperation', + X25519KeyAgreementKey2019: 'sec:X25519KeyAgreementKey2019', + allowedAction: 'sec:allowedAction', + assertionMethod: { + '@id': 'sec:assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'sec:authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capability: { '@id': 'sec:capability', '@type': '@id' }, + capabilityAction: 'sec:capabilityAction', + capabilityChain: { + '@id': 'sec:capabilityChain', + '@type': '@id', + '@container': '@list', + }, + capabilityDelegation: { + '@id': 'sec:capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'sec:capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + caveat: { '@id': 'sec:caveat', '@type': '@id', '@container': '@set' }, + challenge: 'sec:challenge', + ciphertext: 'sec:ciphertext', + controller: { '@id': 'sec:controller', '@type': '@id' }, + delegator: { '@id': 'sec:delegator', '@type': '@id' }, + equihashParameterK: { + '@id': 'sec:equihashParameterK', + '@type': 'xsd:integer', + }, + equihashParameterN: { + '@id': 'sec:equihashParameterN', + '@type': 'xsd:integer', + }, + invocationTarget: { '@id': 'sec:invocationTarget', '@type': '@id' }, + invoker: { '@id': 'sec:invoker', '@type': '@id' }, + jws: 'sec:jws', + keyAgreement: { + '@id': 'sec:keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + kmsModule: { '@id': 'sec:kmsModule' }, + parentCapability: { '@id': 'sec:parentCapability', '@type': '@id' }, + plaintext: 'sec:plaintext', + proof: { '@id': 'sec:proof', '@type': '@id', '@container': '@graph' }, + proofPurpose: { '@id': 'sec:proofPurpose', '@type': '@vocab' }, + proofValue: 'sec:proofValue', + referenceId: 'sec:referenceId', + unwrappedKey: 'sec:unwrappedKey', + verificationMethod: { '@id': 'sec:verificationMethod', '@type': '@id' }, + verifyData: 'sec:verifyData', + wrappedKey: 'sec:wrappedKey', + }, + ], +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/security_v3_unstable.ts b/packages/core/src/modules/vc/__tests__/contexts/security_v3_unstable.ts new file mode 100644 index 0000000000..e24d51defe --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/security_v3_unstable.ts @@ -0,0 +1,680 @@ +export const SECURITY_V3_UNSTABLE = { + '@context': [ + { + '@version': 1.1, + id: '@id', + type: '@type', + '@protected': true, + JsonWebKey2020: { '@id': 'https://w3id.org/security#JsonWebKey2020' }, + JsonWebSignature2020: { + '@id': 'https://w3id.org/security#JsonWebSignature2020', + '@context': { + '@version': 1.1, + id: '@id', + type: '@type', + '@protected': true, + challenge: 'https://w3id.org/security#challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + domain: 'https://w3id.org/security#domain', + expires: { + '@id': 'https://w3id.org/security#expiration', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + jws: 'https://w3id.org/security#jws', + nonce: 'https://w3id.org/security#nonce', + proofPurpose: { + '@id': 'https://w3id.org/security#proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'https://w3id.org/security#capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityDelegation: { + '@id': 'https://w3id.org/security#capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + keyAgreement: { + '@id': 'https://w3id.org/security#keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + '@type': '@id', + }, + }, + }, + Ed25519VerificationKey2020: { + '@id': 'https://w3id.org/security#Ed25519VerificationKey2020', + }, + Ed25519Signature2020: { + '@id': 'https://w3id.org/security#Ed25519Signature2020', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + challenge: 'https://w3id.org/security#challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + domain: 'https://w3id.org/security#domain', + expires: { + '@id': 'https://w3id.org/security#expiration', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + nonce: 'https://w3id.org/security#nonce', + proofPurpose: { + '@id': 'https://w3id.org/security#proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'https://w3id.org/security#capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityDelegation: { + '@id': 'https://w3id.org/security#capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + keyAgreement: { + '@id': 'https://w3id.org/security#keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + proofValue: { + '@id': 'https://w3id.org/security#proofValue', + '@type': 'https://w3id.org/security#multibase', + }, + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + '@type': '@id', + }, + }, + }, + publicKeyJwk: { + '@id': 'https://w3id.org/security#publicKeyJwk', + '@type': '@json', + }, + ethereumAddress: { '@id': 'https://w3id.org/security#ethereumAddress' }, + publicKeyHex: { '@id': 'https://w3id.org/security#publicKeyHex' }, + blockchainAccountId: { + '@id': 'https://w3id.org/security#blockchainAccountId', + }, + MerkleProof2019: { '@id': 'https://w3id.org/security#MerkleProof2019' }, + Bls12381G1Key2020: { '@id': 'https://w3id.org/security#Bls12381G1Key2020' }, + Bls12381G2Key2020: { '@id': 'https://w3id.org/security#Bls12381G2Key2020' }, + BbsBlsSignature2020: { + '@id': 'https://w3id.org/security#BbsBlsSignature2020', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + challenge: 'https://w3id.org/security#challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + domain: 'https://w3id.org/security#domain', + nonce: 'https://w3id.org/security#nonce', + proofPurpose: { + '@id': 'https://w3id.org/security#proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'https://w3id.org/security#capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityDelegation: { + '@id': 'https://w3id.org/security#capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + keyAgreement: { + '@id': 'https://w3id.org/security#keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + proofValue: 'https://w3id.org/security#proofValue', + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + '@type': '@id', + }, + }, + }, + BbsBlsSignatureProof2020: { + '@id': 'https://w3id.org/security#BbsBlsSignatureProof2020', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + challenge: 'https://w3id.org/security#challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + domain: 'https://w3id.org/security#domain', + nonce: 'https://w3id.org/security#nonce', + proofPurpose: { + '@id': 'https://w3id.org/security#proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'https://w3id.org/security#capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityDelegation: { + '@id': 'https://w3id.org/security#capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + keyAgreement: { + '@id': 'https://w3id.org/security#keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + proofValue: 'https://w3id.org/security#proofValue', + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + '@type': '@id', + }, + }, + }, + EcdsaKoblitzSignature2016: 'https://w3id.org/security#EcdsaKoblitzSignature2016', + Ed25519Signature2018: { + '@id': 'https://w3id.org/security#Ed25519Signature2018', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + challenge: 'https://w3id.org/security#challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + domain: 'https://w3id.org/security#domain', + expires: { + '@id': 'https://w3id.org/security#expiration', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + jws: 'https://w3id.org/security#jws', + nonce: 'https://w3id.org/security#nonce', + proofPurpose: { + '@id': 'https://w3id.org/security#proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'https://w3id.org/security#capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityDelegation: { + '@id': 'https://w3id.org/security#capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + keyAgreement: { + '@id': 'https://w3id.org/security#keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + proofValue: 'https://w3id.org/security#proofValue', + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + '@type': '@id', + }, + }, + }, + EncryptedMessage: 'https://w3id.org/security#EncryptedMessage', + GraphSignature2012: 'https://w3id.org/security#GraphSignature2012', + LinkedDataSignature2015: 'https://w3id.org/security#LinkedDataSignature2015', + LinkedDataSignature2016: 'https://w3id.org/security#LinkedDataSignature2016', + CryptographicKey: 'https://w3id.org/security#Key', + authenticationTag: 'https://w3id.org/security#authenticationTag', + canonicalizationAlgorithm: 'https://w3id.org/security#canonicalizationAlgorithm', + cipherAlgorithm: 'https://w3id.org/security#cipherAlgorithm', + cipherData: 'https://w3id.org/security#cipherData', + cipherKey: 'https://w3id.org/security#cipherKey', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + creator: { '@id': 'http://purl.org/dc/terms/creator', '@type': '@id' }, + digestAlgorithm: 'https://w3id.org/security#digestAlgorithm', + digestValue: 'https://w3id.org/security#digestValue', + domain: 'https://w3id.org/security#domain', + encryptionKey: 'https://w3id.org/security#encryptionKey', + expiration: { + '@id': 'https://w3id.org/security#expiration', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + expires: { + '@id': 'https://w3id.org/security#expiration', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + initializationVector: 'https://w3id.org/security#initializationVector', + iterationCount: 'https://w3id.org/security#iterationCount', + nonce: 'https://w3id.org/security#nonce', + normalizationAlgorithm: 'https://w3id.org/security#normalizationAlgorithm', + owner: 'https://w3id.org/security#owner', + password: 'https://w3id.org/security#password', + privateKey: 'https://w3id.org/security#privateKey', + privateKeyPem: 'https://w3id.org/security#privateKeyPem', + publicKey: 'https://w3id.org/security#publicKey', + publicKeyBase58: 'https://w3id.org/security#publicKeyBase58', + publicKeyPem: 'https://w3id.org/security#publicKeyPem', + publicKeyWif: 'https://w3id.org/security#publicKeyWif', + publicKeyService: 'https://w3id.org/security#publicKeyService', + revoked: { + '@id': 'https://w3id.org/security#revoked', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + salt: 'https://w3id.org/security#salt', + signature: 'https://w3id.org/security#signature', + signatureAlgorithm: 'https://w3id.org/security#signingAlgorithm', + signatureValue: 'https://w3id.org/security#signatureValue', + proofValue: 'https://w3id.org/security#proofValue', + AesKeyWrappingKey2019: 'https://w3id.org/security#AesKeyWrappingKey2019', + DeleteKeyOperation: 'https://w3id.org/security#DeleteKeyOperation', + DeriveSecretOperation: 'https://w3id.org/security#DeriveSecretOperation', + EcdsaSecp256k1Signature2019: { + '@id': 'https://w3id.org/security#EcdsaSecp256k1Signature2019', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + challenge: 'https://w3id.org/security#challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + domain: 'https://w3id.org/security#domain', + expires: { + '@id': 'https://w3id.org/security#expiration', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + jws: 'https://w3id.org/security#jws', + nonce: 'https://w3id.org/security#nonce', + proofPurpose: { + '@id': 'https://w3id.org/security#proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'https://w3id.org/security#capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityDelegation: { + '@id': 'https://w3id.org/security#capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + keyAgreement: { + '@id': 'https://w3id.org/security#keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + proofValue: 'https://w3id.org/security#proofValue', + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + '@type': '@id', + }, + }, + }, + EcdsaSecp256r1Signature2019: { + '@id': 'https://w3id.org/security#EcdsaSecp256r1Signature2019', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + challenge: 'https://w3id.org/security#challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + domain: 'https://w3id.org/security#domain', + expires: { + '@id': 'https://w3id.org/security#expiration', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + jws: 'https://w3id.org/security#jws', + nonce: 'https://w3id.org/security#nonce', + proofPurpose: { + '@id': 'https://w3id.org/security#proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'https://w3id.org/security#capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityDelegation: { + '@id': 'https://w3id.org/security#capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + keyAgreement: { + '@id': 'https://w3id.org/security#keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + proofValue: 'https://w3id.org/security#proofValue', + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + '@type': '@id', + }, + }, + }, + EcdsaSecp256k1VerificationKey2019: 'https://w3id.org/security#EcdsaSecp256k1VerificationKey2019', + EcdsaSecp256r1VerificationKey2019: 'https://w3id.org/security#EcdsaSecp256r1VerificationKey2019', + Ed25519VerificationKey2018: 'https://w3id.org/security#Ed25519VerificationKey2018', + EquihashProof2018: 'https://w3id.org/security#EquihashProof2018', + ExportKeyOperation: 'https://w3id.org/security#ExportKeyOperation', + GenerateKeyOperation: 'https://w3id.org/security#GenerateKeyOperation', + KmsOperation: 'https://w3id.org/security#KmsOperation', + RevokeKeyOperation: 'https://w3id.org/security#RevokeKeyOperation', + RsaSignature2018: { + '@id': 'https://w3id.org/security#RsaSignature2018', + '@context': { + '@protected': true, + challenge: 'https://w3id.org/security#challenge', + created: { + '@id': 'http://purl.org/dc/terms/created', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + domain: 'https://w3id.org/security#domain', + expires: { + '@id': 'https://w3id.org/security#expiration', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + jws: 'https://w3id.org/security#jws', + nonce: 'https://w3id.org/security#nonce', + proofPurpose: { + '@id': 'https://w3id.org/security#proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'https://w3id.org/security#capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityDelegation: { + '@id': 'https://w3id.org/security#capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + keyAgreement: { + '@id': 'https://w3id.org/security#keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + proofValue: 'https://w3id.org/security#proofValue', + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + '@type': '@id', + }, + }, + }, + RsaVerificationKey2018: 'https://w3id.org/security#RsaVerificationKey2018', + Sha256HmacKey2019: 'https://w3id.org/security#Sha256HmacKey2019', + SignOperation: 'https://w3id.org/security#SignOperation', + UnwrapKeyOperation: 'https://w3id.org/security#UnwrapKeyOperation', + VerifyOperation: 'https://w3id.org/security#VerifyOperation', + WrapKeyOperation: 'https://w3id.org/security#WrapKeyOperation', + X25519KeyAgreementKey2019: 'https://w3id.org/security#X25519KeyAgreementKey2019', + allowedAction: 'https://w3id.org/security#allowedAction', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capability: { + '@id': 'https://w3id.org/security#capability', + '@type': '@id', + }, + capabilityAction: 'https://w3id.org/security#capabilityAction', + capabilityChain: { + '@id': 'https://w3id.org/security#capabilityChain', + '@type': '@id', + '@container': '@list', + }, + capabilityDelegation: { + '@id': 'https://w3id.org/security#capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'https://w3id.org/security#capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + caveat: { + '@id': 'https://w3id.org/security#caveat', + '@type': '@id', + '@container': '@set', + }, + challenge: 'https://w3id.org/security#challenge', + ciphertext: 'https://w3id.org/security#ciphertext', + controller: { + '@id': 'https://w3id.org/security#controller', + '@type': '@id', + }, + delegator: { '@id': 'https://w3id.org/security#delegator', '@type': '@id' }, + equihashParameterK: { + '@id': 'https://w3id.org/security#equihashParameterK', + '@type': 'http://www.w3.org/2001/XMLSchema#:integer', + }, + equihashParameterN: { + '@id': 'https://w3id.org/security#equihashParameterN', + '@type': 'http://www.w3.org/2001/XMLSchema#:integer', + }, + invocationTarget: { + '@id': 'https://w3id.org/security#invocationTarget', + '@type': '@id', + }, + invoker: { '@id': 'https://w3id.org/security#invoker', '@type': '@id' }, + jws: 'https://w3id.org/security#jws', + keyAgreement: { + '@id': 'https://w3id.org/security#keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + kmsModule: { '@id': 'https://w3id.org/security#kmsModule' }, + parentCapability: { + '@id': 'https://w3id.org/security#parentCapability', + '@type': '@id', + }, + plaintext: 'https://w3id.org/security#plaintext', + proof: { + '@id': 'https://w3id.org/security#proof', + '@type': '@id', + '@container': '@graph', + }, + proofPurpose: { + '@id': 'https://w3id.org/security#proofPurpose', + '@type': '@vocab', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + assertionMethod: { + '@id': 'https://w3id.org/security#assertionMethod', + '@type': '@id', + '@container': '@set', + }, + authentication: { + '@id': 'https://w3id.org/security#authenticationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityInvocation: { + '@id': 'https://w3id.org/security#capabilityInvocationMethod', + '@type': '@id', + '@container': '@set', + }, + capabilityDelegation: { + '@id': 'https://w3id.org/security#capabilityDelegationMethod', + '@type': '@id', + '@container': '@set', + }, + keyAgreement: { + '@id': 'https://w3id.org/security#keyAgreementMethod', + '@type': '@id', + '@container': '@set', + }, + }, + }, + referenceId: 'https://w3id.org/security#referenceId', + unwrappedKey: 'https://w3id.org/security#unwrappedKey', + verificationMethod: { + '@id': 'https://w3id.org/security#verificationMethod', + '@type': '@id', + }, + verifyData: 'https://w3id.org/security#verifyData', + wrappedKey: 'https://w3id.org/security#wrappedKey', + }, + ], +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/vaccination_v1.ts b/packages/core/src/modules/vc/__tests__/contexts/vaccination_v1.ts new file mode 100644 index 0000000000..ce7d65f499 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/vaccination_v1.ts @@ -0,0 +1,88 @@ +export const VACCINATION_V1 = { + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + description: 'http://schema.org/description', + identifier: 'http://schema.org/identifier', + name: 'http://schema.org/name', + image: 'http://schema.org/image', + VaccinationCertificate: { + '@id': 'https://w3id.org/vaccination#VaccinationCertificate', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + description: 'http://schema.org/description', + identifier: 'http://schema.org/identifier', + name: 'http://schema.org/name', + image: 'http://schema.org/image', + }, + }, + VaccinationEvent: { + '@id': 'https://w3id.org/vaccination#VaccinationEvent', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + administeringCentre: 'https://w3id.org/vaccination#administeringCentre', + batchNumber: 'https://w3id.org/vaccination#batchNumber', + countryOfVaccination: 'https://w3id.org/vaccination#countryOfVaccination', + dateOfVaccination: { + '@id': 'https://w3id.org/vaccination#dateOfVaccination', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + healthProfessional: 'https://w3id.org/vaccination#healthProfessional', + nextVaccinationDate: { + '@id': 'https://w3id.org/vaccination#nextVaccinationDate', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + order: 'https://w3id.org/vaccination#order', + recipient: { + '@id': 'https://w3id.org/vaccination#recipient', + '@type': 'https://w3id.org/vaccination#VaccineRecipient', + }, + vaccine: { + '@id': 'https://w3id.org/vaccination#VaccineEventVaccine', + '@type': 'https://w3id.org/vaccination#Vaccine', + }, + }, + }, + VaccineRecipient: { + '@id': 'https://w3id.org/vaccination#VaccineRecipient', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + birthDate: { + '@id': 'http://schema.org/birthDate', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + familyName: 'http://schema.org/familyName', + gender: 'http://schema.org/gender', + givenName: 'http://schema.org/givenName', + }, + }, + Vaccine: { + '@id': 'https://w3id.org/vaccination#Vaccine', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + atcCode: 'https://w3id.org/vaccination#atc-code', + disease: 'https://w3id.org/vaccination#disease', + event: { + '@id': 'https://w3id.org/vaccination#VaccineRecipientVaccineEvent', + '@type': 'https://w3id.org/vaccination#VaccineEvent', + }, + marketingAuthorizationHolder: 'https://w3id.org/vaccination#marketingAuthorizationHolder', + medicinalProductName: 'https://w3id.org/vaccination#medicinalProductName', + }, + }, + }, +} diff --git a/packages/core/src/modules/vc/__tests__/dids/did_example_489398593.ts b/packages/core/src/modules/vc/__tests__/dids/did_example_489398593.ts new file mode 100644 index 0000000000..9cee4d0e2c --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_example_489398593.ts @@ -0,0 +1,13 @@ +export const DID_EXAMPLE_48939859 = { + '@context': 'https://www.w3.org/ns/did/v1', + id: 'did:example:489398593', + assertionMethod: [ + { + id: 'did:example:489398593#test', + type: 'Bls12381G2Key2020', + controller: 'did:example:489398593', + publicKeyBase58: + 'oqpWYKaZD9M1Kbe94BVXpr8WTdFBNZyKv48cziTiQUeuhm7sBhCABMyYG4kcMrseC68YTFFgyhiNeBKjzdKk9MiRWuLv5H4FFujQsQK2KTAtzU8qTBiZqBHMmnLF4PL7Ytu', + }, + ], +} diff --git a/packages/core/src/modules/vc/__tests__/dids/did_sov_QqEfJxe752NCmWqR5TssZ5.ts b/packages/core/src/modules/vc/__tests__/dids/did_sov_QqEfJxe752NCmWqR5TssZ5.ts new file mode 100644 index 0000000000..0246796da6 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_sov_QqEfJxe752NCmWqR5TssZ5.ts @@ -0,0 +1,24 @@ +export const DID_SOV_QqEfJxe752NCmWqR5TssZ5 = { + '@context': 'https://www.w3.org/ns/did/v1', + id: 'did:sov:QqEfJxe752NCmWqR5TssZ5', + verificationMethod: [ + { + id: 'did:sov:QqEfJxe752NCmWqR5TssZ5#key-1', + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:QqEfJxe752NCmWqR5TssZ5', + publicKeyBase58: 'DzNC1pbarUzgGXmxRsccNJDBjWgCaiy6uSXgPPJZGWCL', + }, + ], + authentication: ['did:sov:QqEfJxe752NCmWqR5TssZ5#key-1'], + assertionMethod: ['did:sov:QqEfJxe752NCmWqR5TssZ5#key-1'], + service: [ + { + id: 'did:sov:QqEfJxe752NCmWqR5TssZ5#did-communication', + type: 'did-communication', + serviceEndpoint: 'http://localhost:3002', + recipientKeys: ['did:sov:QqEfJxe752NCmWqR5TssZ5#key-1'], + routingKeys: [], + priority: 0, + }, + ], +} diff --git a/packages/core/src/modules/vc/__tests__/dids/did_z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL.ts b/packages/core/src/modules/vc/__tests__/dids/did_z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL.ts new file mode 100644 index 0000000000..f3bb1e0b1d --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL.ts @@ -0,0 +1,45 @@ +export const DID_z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL = { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + alsoKnownAs: [], + controller: [], + verificationMethod: [ + { + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + type: 'Ed25519VerificationKey2018', + controller: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + publicKeyBase58: '3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx', + }, + ], + service: [], + authentication: [ + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + ], + assertionMethod: [ + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + ], + keyAgreement: [ + { + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + type: 'Ed25519VerificationKey2018', + controller: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + publicKeyBase58: '3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx', + }, + { + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6LSbkodSr6SU2trs8VUgnrnWtSm7BAPG245ggrBmSrxbv1R', + type: 'X25519KeyAgreementKey2019', + controller: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + publicKeyBase58: '5dTvYHaNaB7mk7iA9LqCJEHG2dGZQsvoi8WGzDRtYEf', + }, + ], + capabilityInvocation: [ + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + ], + capabilityDelegation: [ + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + ], + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', +} diff --git a/packages/core/src/modules/vc/__tests__/dids/did_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV.ts b/packages/core/src/modules/vc/__tests__/dids/did_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV.ts new file mode 100644 index 0000000000..2c8d443940 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV.ts @@ -0,0 +1,45 @@ +export const DID_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV = { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + alsoKnownAs: [], + controller: [], + verificationMethod: [ + { + id: 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV#z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV', + type: 'Ed25519VerificationKey2018', + controller: 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV', + publicKeyBase58: 'HC8vuuvP8x9kVJizh2eujQjo2JwFQJz6w63szzdbu1Q7', + }, + ], + service: [], + authentication: [ + 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV#z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV', + ], + assertionMethod: [ + 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV#z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV', + ], + keyAgreement: [ + { + id: 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV#z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV', + type: 'Ed25519VerificationKey2018', + controller: 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV', + publicKeyBase58: 'HC8vuuvP8x9kVJizh2eujQjo2JwFQJz6w63szzdbu1Q7', + }, + { + id: 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV#z6LSsJwtXqeVHCtCR9QMyX58hfBNY62wQooE4VPYmwyyesov', + type: 'X25519KeyAgreementKey2019', + controller: 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV', + publicKeyBase58: 'Gdmj1XqdBkATKm2bSsZBP4xtgwVpiCd5BWfsHVLSwW3A', + }, + ], + capabilityInvocation: [ + 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV#z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV', + ], + capabilityDelegation: [ + 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV#z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV', + ], + id: 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV', +} diff --git a/packages/core/src/modules/vc/__tests__/dids/did_zUC729nNiUKQ4pHHNYovae25gkkuvtsZmtpjnLYUj1r8Yd4ZRn3FaswicUWs2NYNuWXxQ7MgzAX7dqXxAFZXFvn2jhqGKpjm5xLwESYfhcDGdSrc9mgfu51w939BjmKmng5HvYK.ts b/packages/core/src/modules/vc/__tests__/dids/did_zUC729nNiUKQ4pHHNYovae25gkkuvtsZmtpjnLYUj1r8Yd4ZRn3FaswicUWs2NYNuWXxQ7MgzAX7dqXxAFZXFvn2jhqGKpjm5xLwESYfhcDGdSrc9mgfu51w939BjmKmng5HvYK.ts new file mode 100644 index 0000000000..46ae84b94e --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_zUC729nNiUKQ4pHHNYovae25gkkuvtsZmtpjnLYUj1r8Yd4ZRn3FaswicUWs2NYNuWXxQ7MgzAX7dqXxAFZXFvn2jhqGKpjm5xLwESYfhcDGdSrc9mgfu51w939BjmKmng5HvYK.ts @@ -0,0 +1,30 @@ +export const DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4 = + { + '@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], + id: 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + verificationMethod: [ + { + id: 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4#zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + type: 'JsonWebKey2020', + controller: + 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + publicKeyJwk: { + kty: 'EC', + crv: 'BLS12381_G2', + x: 'rvmIn58iMglCOixwxv7snWjuu8ooQteghivgqrchuIDH8DbG7pzF5io_k2t5HOW1DjcsVioEXLnIdSdUz8jJQq2r-B8zyw4CEiWAM9LUPnmmRDeVFVtA0YVaLo7DdkOn', + }, + }, + ], + assertionMethod: [ + 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4#zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + ], + authentication: [ + 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4#zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + ], + capabilityInvocation: [ + 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4#zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + ], + capabilityDelegation: [ + 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4#zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + ], + } diff --git a/packages/core/src/modules/vc/__tests__/dids/did_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa.ts b/packages/core/src/modules/vc/__tests__/dids/did_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa.ts new file mode 100644 index 0000000000..472bc1e84c --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa.ts @@ -0,0 +1,54 @@ +export const DID_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa = + { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/bbs/v1'], + alsoKnownAs: [], + controller: [], + verificationMethod: [ + { + id: 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa#zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + type: 'Bls12381G2Key2020', + controller: + 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + publicKeyBase58: + 'nZZe9Nizhaz9JGpgjysaNkWGg5TNEhpib5j6WjTUHJ5K46dedUrZ57PUFZBq9Xckv8mFJjx6G6Vvj2rPspq22BagdADEEEy2F8AVLE1DhuwWC5vHFa4fUhUwxMkH7B6joqG', + publicKeyBase64: undefined, + publicKeyJwk: undefined, + publicKeyHex: undefined, + publicKeyMultibase: undefined, + publicKeyPem: undefined, + blockchainAccountId: undefined, + ethereumAddress: undefined, + }, + ], + service: [], + authentication: [ + 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa#zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + ], + assertionMethod: [ + 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa#zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + ], + keyAgreement: [ + { + id: 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa#zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + type: 'Bls12381G2Key2020', + controller: + 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + publicKeyBase58: + 'nZZe9Nizhaz9JGpgjysaNkWGg5TNEhpib5j6WjTUHJ5K46dedUrZ57PUFZBq9Xckv8mFJjx6G6Vvj2rPspq22BagdADEEEy2F8AVLE1DhuwWC5vHFa4fUhUwxMkH7B6joqG', + publicKeyBase64: undefined, + publicKeyJwk: undefined, + publicKeyHex: undefined, + publicKeyMultibase: undefined, + publicKeyPem: undefined, + blockchainAccountId: undefined, + ethereumAddress: undefined, + }, + ], + capabilityInvocation: [ + 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa#zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + ], + capabilityDelegation: [ + 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa#zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + ], + id: 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + } diff --git a/packages/core/src/modules/vc/__tests__/dids/did_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD.ts b/packages/core/src/modules/vc/__tests__/dids/did_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD.ts new file mode 100644 index 0000000000..968aec92bc --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD.ts @@ -0,0 +1,30 @@ +export const DID_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD = + { + '@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], + id: 'did:key:zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD', + verificationMethod: [ + { + id: 'did:key:zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD#zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD', + type: 'JsonWebKey2020', + controller: + 'did:key:zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD', + publicKeyJwk: { + kty: 'EC', + crv: 'BLS12381_G2', + x: 'hbLuuV4otX1HEALBmUGy_ryyTIcY4TsoZYm_UZPCPgITbXvn8YlvlVM_T6_D0ZrUByvZELEX6wXzKhSkCwEqawZOEhUk4iWFID4MR6nRD4icGm97LC4d58WHTfCZ5bXw', + }, + }, + ], + assertionMethod: [ + 'did:key:zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD#zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD', + ], + authentication: [ + 'did:key:zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD#zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD', + ], + capabilityInvocation: [ + 'did:key:zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD#zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD', + ], + capabilityDelegation: [ + 'did:key:zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD#zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD', + ], + } diff --git a/packages/core/src/modules/vc/__tests__/dids/did_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh.ts b/packages/core/src/modules/vc/__tests__/dids/did_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh.ts new file mode 100644 index 0000000000..b3072fa575 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh.ts @@ -0,0 +1,30 @@ +export const DID_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh = + { + '@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], + id: 'did:key:zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh', + verificationMethod: [ + { + id: 'did:key:zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh#zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh', + type: 'JsonWebKey2020', + controller: + 'did:key:zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh', + publicKeyJwk: { + kty: 'EC', + crv: 'BLS12381_G2', + x: 'huBQv7qpuF5FI5bvaku1B8JSPHeHKPI-hhvcJ97I5vNdGtafbPfrPncV4NNXidkzDDASYgt22eMSVKX9Kc9iWFnPmprzDNUt1HhvtBrldXLlRegT93LOogEh7BwoKVGW', + }, + }, + ], + assertionMethod: [ + 'did:key:zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh#zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh', + ], + authentication: [ + 'did:key:zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh#zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh', + ], + capabilityInvocation: [ + 'did:key:zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh#zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh', + ], + capabilityDelegation: [ + 'did:key:zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh#zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh', + ], + } diff --git a/packages/core/src/modules/vc/__tests__/dids/did_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn.ts b/packages/core/src/modules/vc/__tests__/dids/did_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn.ts new file mode 100644 index 0000000000..c2861e2a1a --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn.ts @@ -0,0 +1,30 @@ +export const DID_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn = + { + '@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], + id: 'did:key:zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn', + verificationMethod: [ + { + id: 'did:key:zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn#zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn', + type: 'JsonWebKey2020', + controller: + 'did:key:zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn', + publicKeyJwk: { + kty: 'EC', + crv: 'BLS12381_G2', + x: 'h5pno-Wq71ExNSbjZ91OJavpe0tA871-20TigCvQAs9jHtIV6KjXtX17Cmoz01dQBlPUFPOB5ILw2JeZ2MYtMOzCCYtnuour5XDuyYs6KTAXgYQ2nAlIFfmXXr9Jc48z', + }, + }, + ], + assertionMethod: [ + 'did:key:zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn#zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn', + ], + authentication: [ + 'did:key:zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn#zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn', + ], + capabilityInvocation: [ + 'did:key:zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn#zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn', + ], + capabilityDelegation: [ + 'did:key:zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn#zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn', + ], + } diff --git a/packages/core/src/modules/vc/__tests__/dids/did_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN.ts b/packages/core/src/modules/vc/__tests__/dids/did_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN.ts new file mode 100644 index 0000000000..3991dcd28b --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN.ts @@ -0,0 +1,27 @@ +export const DID_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN = + { + '@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/bls12381-2020/v1'], + id: 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + verificationMethod: [ + { + id: 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + type: 'Bls12381G2Key2020', + controller: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + publicKeyBase58: + 'pegxn1a43zphf3uqGT4cx1bz8Ebb9QmoSWhQyP1qYTSeRuvWLGKJ5KcqaymnSj53YhCFbjr3tJAhqcaxxZ4Lry7KxkpLeA6GVf3Zb1x999dYp3k4jQzYa1PQXC6x1uCd9s4', + }, + ], + assertionMethod: [ + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + ], + authentication: [ + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + ], + capabilityInvocation: [ + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + ], + capabilityDelegation: [ + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + ], + } diff --git a/packages/core/src/modules/vc/__tests__/dids/did_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ.ts b/packages/core/src/modules/vc/__tests__/dids/did_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ.ts new file mode 100644 index 0000000000..d369808fc9 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ.ts @@ -0,0 +1,30 @@ +export const DID_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ = + { + '@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], + id: 'did:key:zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ', + verificationMethod: [ + { + id: 'did:key:zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ#zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ', + type: 'JsonWebKey2020', + controller: + 'did:key:zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ', + publicKeyJwk: { + kty: 'EC', + crv: 'BLS12381_G2', + x: 'kSN7z0XGmPGn81aqNhL4zE-jF799YUzc7nl730o0nBsMZiZzwlqyNvemMYrWAGq5FCoaN0jpCkefgdRrMRPPD_6IK3w0g3ieFxNxdwX7NcGR8aihA9stCdTe0kx-ePJr', + }, + }, + ], + assertionMethod: [ + 'did:key:zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ#zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ', + ], + authentication: [ + 'did:key:zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ#zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ', + ], + capabilityInvocation: [ + 'did:key:zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ#zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ', + ], + capabilityDelegation: [ + 'did:key:zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ#zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ', + ], + } diff --git a/packages/core/src/modules/vc/__tests__/dids/did_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox.ts b/packages/core/src/modules/vc/__tests__/dids/did_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox.ts new file mode 100644 index 0000000000..5288ec249c --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox.ts @@ -0,0 +1,30 @@ +export const DID_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox = + { + '@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], + id: 'did:key:zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox', + verificationMethod: [ + { + id: 'did:key:zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox#zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox', + type: 'JsonWebKey2020', + controller: + 'did:key:zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox', + publicKeyJwk: { + kty: 'EC', + crv: 'BLS12381_G2', + x: 'pA1LXe8EGRU8PTpXfnG3fpJoIW394wpGpx8Q3V5Keh3PUM7j_PRLbk6XN3KJTv7cFesQeo_Q-knymniIm0Ugk9-RGKn65pRIy65aMa1ACfKfGTnnnTuJP4tWRHW2BaHb', + }, + }, + ], + assertionMethod: [ + 'did:key:zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox#zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox', + ], + authentication: [ + 'did:key:zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox#zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox', + ], + capabilityInvocation: [ + 'did:key:zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox#zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox', + ], + capabilityDelegation: [ + 'did:key:zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox#zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox', + ], + } diff --git a/packages/core/src/modules/vc/__tests__/dids/did_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F.ts b/packages/core/src/modules/vc/__tests__/dids/did_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F.ts new file mode 100644 index 0000000000..3e4bac3b13 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F.ts @@ -0,0 +1,30 @@ +export const DID_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F = + { + '@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], + id: 'did:key:zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F', + verificationMethod: [ + { + id: 'did:key:zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F#zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F', + type: 'JsonWebKey2020', + controller: + 'did:key:zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F', + publicKeyJwk: { + kty: 'EC', + crv: 'BLS12381_G2', + x: 'qULVOptm5i4PfW7r6Hu6wzw6BZRywAQcCi3V0q1VDidrf0bZ-rFUaP72vXRa1WkPAoWpjMjM-uYbDQJBQbgVXoFm4L5Qz3YG5ziHRGdVWChY_5TX8yV3fQOsLJDSnfZy', + }, + }, + ], + assertionMethod: [ + 'did:key:zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F#zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F', + ], + authentication: [ + 'did:key:zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F#zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F', + ], + capabilityInvocation: [ + 'did:key:zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F#zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F', + ], + capabilityDelegation: [ + 'did:key:zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F#zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F', + ], + } diff --git a/packages/core/src/modules/vc/__tests__/dids/did_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4.ts b/packages/core/src/modules/vc/__tests__/dids/did_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4.ts new file mode 100644 index 0000000000..46ae84b94e --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4.ts @@ -0,0 +1,30 @@ +export const DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4 = + { + '@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], + id: 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + verificationMethod: [ + { + id: 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4#zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + type: 'JsonWebKey2020', + controller: + 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + publicKeyJwk: { + kty: 'EC', + crv: 'BLS12381_G2', + x: 'rvmIn58iMglCOixwxv7snWjuu8ooQteghivgqrchuIDH8DbG7pzF5io_k2t5HOW1DjcsVioEXLnIdSdUz8jJQq2r-B8zyw4CEiWAM9LUPnmmRDeVFVtA0YVaLo7DdkOn', + }, + }, + ], + assertionMethod: [ + 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4#zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + ], + authentication: [ + 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4#zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + ], + capabilityInvocation: [ + 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4#zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + ], + capabilityDelegation: [ + 'did:key:zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4#zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4', + ], + } diff --git a/packages/core/src/modules/vc/__tests__/documentLoader.ts b/packages/core/src/modules/vc/__tests__/documentLoader.ts new file mode 100644 index 0000000000..91a76bf879 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/documentLoader.ts @@ -0,0 +1,113 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +// eslint-disable-next-line import/no-extraneous-dependencies + +import type { JsonObject } from '../../../types' +import type { DocumentLoaderResult } from '../../../utils' + +import jsonld from '../../../../types/jsonld' + +import { BBS_V1, EXAMPLES_V1, ODRL, SCHEMA_ORG, VACCINATION_V1 } from './contexts' +import { X25519_V1 } from './contexts/X25519_v1' +import { CITIZENSHIP_V1 } from './contexts/citizenship_v1' +import { CREDENTIALS_V1 } from './contexts/credentials_v1' +import { DID_V1 } from './contexts/did_v1' +import { ED25519_V1 } from './contexts/ed25519_v1' +import { SECURITY_V1 } from './contexts/security_v1' +import { SECURITY_V2 } from './contexts/security_v2' +import { SECURITY_V3_UNSTABLE } from './contexts/security_v3_unstable' +import { DID_EXAMPLE_48939859 } from './dids/did_example_489398593' +import { DID_SOV_QqEfJxe752NCmWqR5TssZ5 } from './dids/did_sov_QqEfJxe752NCmWqR5TssZ5' +import { DID_z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL } from './dids/did_z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL' +import { DID_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV } from './dids/did_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV' +import { DID_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa } from './dids/did_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa' +import { DID_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD } from './dids/did_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD' +import { DID_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh } from './dids/did_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh' +import { DID_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn } from './dids/did_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn' +import { DID_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN } from './dids/did_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN' +import { DID_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ } from './dids/did_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ' +import { DID_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox } from './dids/did_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox' +import { DID_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F } from './dids/did_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F' +import { DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4 } from './dids/did_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4' + +export const DOCUMENTS = { + [DID_z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL['id']]: DID_z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL, + [DID_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV['id']]: DID_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV, + [DID_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa[ + 'id' + ]]: DID_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa, + [DID_EXAMPLE_48939859['id']]: DID_EXAMPLE_48939859, + [DID_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh[ + 'id' + ]]: DID_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh, + [DID_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn[ + 'id' + ]]: DID_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn, + [DID_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ[ + 'id' + ]]: DID_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ, + [DID_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox[ + 'id' + ]]: DID_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox, + [DID_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F[ + 'id' + ]]: DID_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F, + [DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4[ + 'id' + ]]: DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4, + [DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4[ + 'id' + ]]: DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4, + [DID_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD[ + 'id' + ]]: DID_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD, + [DID_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN[ + 'id' + ]]: DID_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN, + [DID_SOV_QqEfJxe752NCmWqR5TssZ5['id']]: DID_SOV_QqEfJxe752NCmWqR5TssZ5, + SECURITY_CONTEXT_V1_URL: SECURITY_V1, + SECURITY_CONTEXT_V2_URL: SECURITY_V2, + SECURITY_CONTEXT_V3_URL: SECURITY_V3_UNSTABLE, + DID_V1_CONTEXT_URL: DID_V1, + CREDENTIALS_CONTEXT_V1_URL: CREDENTIALS_V1, + SECURITY_CONTEXT_BBS_URL: BBS_V1, + 'https://w3id.org/security/bbs/v1': BBS_V1, + 'https://w3id.org/security/v1': SECURITY_V1, + 'https://w3id.org/security/v2': SECURITY_V2, + 'https://w3id.org/security/suites/x25519-2019/v1': X25519_V1, + 'https://w3id.org/security/suites/ed25519-2018/v1': ED25519_V1, + 'https://www.w3.org/2018/credentials/examples/v1': EXAMPLES_V1, + 'https://www.w3.org/2018/credentials/v1': CREDENTIALS_V1, + 'https://w3id.org/did/v1': DID_V1, + 'https://w3id.org/citizenship/v1': CITIZENSHIP_V1, + 'https://www.w3.org/ns/odrl.jsonld': ODRL, + 'http://schema.org/': SCHEMA_ORG, + 'https://w3id.org/vaccination/v1': VACCINATION_V1, +} + +export const customDocumentLoader = async (url: string): Promise => { + let result = DOCUMENTS[url] + + if (!result) { + const withoutFragment = url.split('#')[0] + result = DOCUMENTS[withoutFragment] + } + + if (!result) { + throw new Error(`Document not found: ${url}`) + } + + if (url.startsWith('did:')) { + result = await jsonld.frame(result, { + '@context': result['@context'], + '@embed': '@never', + id: url, + }) + } + + return { + contextUrl: null, + documentUrl: url, + document: result as JsonObject, + } +} diff --git a/packages/core/src/modules/vc/__tests__/fixtures.ts b/packages/core/src/modules/vc/__tests__/fixtures.ts new file mode 100644 index 0000000000..a40b8499c1 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/fixtures.ts @@ -0,0 +1,326 @@ +import { CREDENTIALS_CONTEXT_V1_URL, SECURITY_CONTEXT_BBS_URL } from '../constants' + +export const Ed25519Signature2018Fixtures = { + TEST_LD_DOCUMENT: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, + }, + }, + TEST_LD_DOCUMENT_SIGNED: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, + }, + proof: { + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + type: 'Ed25519Signature2018', + created: '2022-04-18T23:13:10Z', + proofPurpose: 'assertionMethod', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..ECQsj_lABelr1jkehSkqaYpc5CBvbSjbi3ZvgiVVKxZFDYfj5xZmeXb_awa4aw_cGEVaoypeN2uCFmeG6WKkBw', + }, + }, + TEST_LD_DOCUMENT_BAD_SIGNED: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, + }, + proof: { + verificationMethod: + 'did:key:z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV#z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV', + type: 'Ed25519Signature2018', + created: '2022-03-28T15:54:59Z', + proofPurpose: 'assertionMethod', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..Ej5aEUBTgeNm3_a4uO_AuNnisldnYTMMGMom4xLb-_TmoYe7467Yo046Bw2QqdfdBja6y-HBbBj4SonOlwswAg', + }, + }, + TEST_VP_DOCUMENT: { + '@context': [CREDENTIALS_CONTEXT_V1_URL], + type: ['VerifiablePresentation'], + verifiableCredential: [ + { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, + }, + proof: { + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + type: 'Ed25519Signature2018', + created: '2022-04-18T23:13:10Z', + proofPurpose: 'assertionMethod', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..ECQsj_lABelr1jkehSkqaYpc5CBvbSjbi3ZvgiVVKxZFDYfj5xZmeXb_awa4aw_cGEVaoypeN2uCFmeG6WKkBw', + }, + }, + ], + }, + TEST_VP_DOCUMENT_SIGNED: { + '@context': [CREDENTIALS_CONTEXT_V1_URL], + type: ['VerifiablePresentation'], + verifiableCredential: [ + { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, + }, + proof: { + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + type: 'Ed25519Signature2018', + created: '2022-04-18T23:13:10Z', + proofPurpose: 'assertionMethod', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..ECQsj_lABelr1jkehSkqaYpc5CBvbSjbi3ZvgiVVKxZFDYfj5xZmeXb_awa4aw_cGEVaoypeN2uCFmeG6WKkBw', + }, + }, + ], + proof: { + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + type: 'Ed25519Signature2018', + created: '2022-04-20T17:31:49Z', + proofPurpose: 'authentication', + challenge: '7bf32d0b-39d4-41f3-96b6-45de52988e4c', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..yNSkNCfVv6_1-P6CtldiqS2bDe_8DPKBIP3Do9qi0LF2DU_d70pWajevJIBH5NZ8K4AawDYx_irlhdz4aiH3Bw', + }, + }, +} + +export const BbsBlsSignature2020Fixtures = { + TEST_LD_DOCUMENT: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: '', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + }, + + TEST_LD_DOCUMENT_SIGNED: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + proof: { + type: 'BbsBlsSignature2020', + created: '2022-04-13T13:47:47Z', + verificationMethod: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + proofPurpose: 'assertionMethod', + proofValue: + 'hoNNnnRIoEoaY9Fvg3pGVG2eWTAHnR1kIM01nObEL2FdI2IkkpM3246jn3VBD8KBYUHlKfzccE4m7waZyoLEkBLFiK2g54Q2i+CdtYBgDdkUDsoULSBMcH1MwGHwdjfXpldFNFrHFx/IAvLVniyeMQ==', + }, + }, + TEST_LD_DOCUMENT_BAD_SIGNED: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + proof: { + type: 'BbsBlsSignature2020', + created: '2022-04-13T13:47:47Z', + verificationMethod: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + proofPurpose: 'assertionMethod', + proofValue: + 'gU44r/fmvGpkOyMRZX4nwRB6IsbrL7zbVTs+yu6bZGeCNJuiJqS5U6fCPuvGQ+iNYUHlKfzccE4m7waZyoLEkBLFiK2g54Q2i+CdtYBgDdkUDsoULSBMcH1MwGHwdjfXpldFNFrHFx/IAvLVniyeMQ==', + }, + }, + + TEST_VALID_DERIVED: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['PermanentResidentCard', 'VerifiableCredential'], + description: 'Government of Example Permanent Resident Card.', + identifier: '83627465', + name: 'Permanent Resident Card', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['Person', 'PermanentResident'], + familyName: 'SMITH', + gender: 'Male', + givenName: 'JOHN', + }, + expirationDate: '2029-12-03T12:19:52Z', + issuanceDate: '2019-12-03T12:19:52Z', + issuer: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + proof: { + type: 'BbsBlsSignatureProof2020', + created: '2022-04-13T13:47:47Z', + nonce: 'GfuRhH8hSAcWm5RWgUQYNQNWjQBsWuVgMCJrhTCD3kSpnHmQOkHcnNAoBsgyMAT4UUI=', + proofPurpose: 'assertionMethod', + proofValue: + 'ABkB/wbvkcCcbPRE5vrXc++orru4MsgrS4ESsZ30RNCs3noqLwm94/RZNp62I6Hyf0Kmht0Vog70HDtnNzbnMAj/zD9oT/N53pOADrtn5v+xZgP3cK4N2d6amg6h3LXem29gidW9hMrROPLit5cWEIL4/HOzxPxQQGYiwEXdW++Aja5ZuwJoMsIx7ysn4C4ekN7JXZtnAAAAdJR/oeDShxRdSBlnCSUHkE4Ol+Z3AhXBKkxb4AxiMKOiNmBreMTjJUGwNAPNU2aKnAAAAAIBUuKV0W0YBQZY/mwLmwCcyOWMiaEpjnVhYip4jhBBZw1aPBe8GzsG9zv3Sf9XAyGEAvVFe3OvwvMwYY5nZYdYoLSR4TLl1aLw0oChiPm2zb6ApXypCEEVd8KhJMATyssTlY48bEljDNixAD2rVDaoAAAACWjyrWp3b62M5Onuwo9EItCrBjPD68xC12q1agqgwFTnOI0+MfEwVGNZsA0IqkCGrZmo3AyRpcRm51IYDWYorM4hued5EcVHeCGd6NrnLSxTFPEu8lnmCoMXcxBWDCZFRGb//M5WlncbsYiz01itHbSs1nmpj3o+DYlF2ZyOYphvLo5A9T4rWVwHRK1+LeCDEawOnI03DWLyN8U4ZpbpcdZNK421IwNjseYY+ptvvL3juZ2uQR84maAZYy/OjMuHNyzqHPXNgsLLqtrvPo0kncefp+x1jgA0J/b5xfT72+vhKZAN1R48/uPf+DySC3avwD3T+YHjePn1bBOidhCWMjwzI9LYO8VvhcWXzH7nBWh5MeUch+Wkl777KrsLhrXnCg==', + verificationMethod: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + }, + }, + + TEST_VP_DOCUMENT: { + '@context': [CREDENTIALS_CONTEXT_V1_URL], + type: ['VerifiablePresentation'], + verifiableCredential: [ + { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['PermanentResidentCard', 'VerifiableCredential'], + description: 'Government of Example Permanent Resident Card.', + identifier: '83627465', + name: 'Permanent Resident Card', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['Person', 'PermanentResident'], + familyName: 'SMITH', + gender: 'Male', + givenName: 'JOHN', + }, + expirationDate: '2029-12-03T12:19:52Z', + issuanceDate: '2019-12-03T12:19:52Z', + issuer: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + proof: { + type: 'BbsBlsSignatureProof2020', + created: '2022-04-13T13:47:47Z', + nonce: 'GfuRhH8hSAcWm5RWgUQYNQNWjQBsWuVgMCJrhTCD3kSpnHmQOkHcnNAoBsgyMAT4UUI=', + proofPurpose: 'assertionMethod', + proofValue: + 'ABkB/wbvkcCcbPRE5vrXc++orru4MsgrS4ESsZ30RNCs3noqLwm94/RZNp62I6Hyf0Kmht0Vog70HDtnNzbnMAj/zD9oT/N53pOADrtn5v+xZgP3cK4N2d6amg6h3LXem29gidW9hMrROPLit5cWEIL4/HOzxPxQQGYiwEXdW++Aja5ZuwJoMsIx7ysn4C4ekN7JXZtnAAAAdJR/oeDShxRdSBlnCSUHkE4Ol+Z3AhXBKkxb4AxiMKOiNmBreMTjJUGwNAPNU2aKnAAAAAIBUuKV0W0YBQZY/mwLmwCcyOWMiaEpjnVhYip4jhBBZw1aPBe8GzsG9zv3Sf9XAyGEAvVFe3OvwvMwYY5nZYdYoLSR4TLl1aLw0oChiPm2zb6ApXypCEEVd8KhJMATyssTlY48bEljDNixAD2rVDaoAAAACWjyrWp3b62M5Onuwo9EItCrBjPD68xC12q1agqgwFTnOI0+MfEwVGNZsA0IqkCGrZmo3AyRpcRm51IYDWYorM4hued5EcVHeCGd6NrnLSxTFPEu8lnmCoMXcxBWDCZFRGb//M5WlncbsYiz01itHbSs1nmpj3o+DYlF2ZyOYphvLo5A9T4rWVwHRK1+LeCDEawOnI03DWLyN8U4ZpbpcdZNK421IwNjseYY+ptvvL3juZ2uQR84maAZYy/OjMuHNyzqHPXNgsLLqtrvPo0kncefp+x1jgA0J/b5xfT72+vhKZAN1R48/uPf+DySC3avwD3T+YHjePn1bBOidhCWMjwzI9LYO8VvhcWXzH7nBWh5MeUch+Wkl777KrsLhrXnCg==', + verificationMethod: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + }, + }, + ], + }, + TEST_VP_DOCUMENT_SIGNED: { + '@context': [CREDENTIALS_CONTEXT_V1_URL], + type: ['VerifiablePresentation'], + verifiableCredential: [ + { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['PermanentResidentCard', 'VerifiableCredential'], + description: 'Government of Example Permanent Resident Card.', + identifier: '83627465', + name: 'Permanent Resident Card', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['Person', 'PermanentResident'], + familyName: 'SMITH', + gender: 'Male', + givenName: 'JOHN', + }, + expirationDate: '2029-12-03T12:19:52Z', + issuanceDate: '2019-12-03T12:19:52Z', + issuer: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + proof: { + type: 'BbsBlsSignatureProof2020', + created: '2022-04-13T13:47:47Z', + nonce: 'GfuRhH8hSAcWm5RWgUQYNQNWjQBsWuVgMCJrhTCD3kSpnHmQOkHcnNAoBsgyMAT4UUI=', + proofPurpose: 'assertionMethod', + proofValue: + 'ABkB/wbvkcCcbPRE5vrXc++orru4MsgrS4ESsZ30RNCs3noqLwm94/RZNp62I6Hyf0Kmht0Vog70HDtnNzbnMAj/zD9oT/N53pOADrtn5v+xZgP3cK4N2d6amg6h3LXem29gidW9hMrROPLit5cWEIL4/HOzxPxQQGYiwEXdW++Aja5ZuwJoMsIx7ysn4C4ekN7JXZtnAAAAdJR/oeDShxRdSBlnCSUHkE4Ol+Z3AhXBKkxb4AxiMKOiNmBreMTjJUGwNAPNU2aKnAAAAAIBUuKV0W0YBQZY/mwLmwCcyOWMiaEpjnVhYip4jhBBZw1aPBe8GzsG9zv3Sf9XAyGEAvVFe3OvwvMwYY5nZYdYoLSR4TLl1aLw0oChiPm2zb6ApXypCEEVd8KhJMATyssTlY48bEljDNixAD2rVDaoAAAACWjyrWp3b62M5Onuwo9EItCrBjPD68xC12q1agqgwFTnOI0+MfEwVGNZsA0IqkCGrZmo3AyRpcRm51IYDWYorM4hued5EcVHeCGd6NrnLSxTFPEu8lnmCoMXcxBWDCZFRGb//M5WlncbsYiz01itHbSs1nmpj3o+DYlF2ZyOYphvLo5A9T4rWVwHRK1+LeCDEawOnI03DWLyN8U4ZpbpcdZNK421IwNjseYY+ptvvL3juZ2uQR84maAZYy/OjMuHNyzqHPXNgsLLqtrvPo0kncefp+x1jgA0J/b5xfT72+vhKZAN1R48/uPf+DySC3avwD3T+YHjePn1bBOidhCWMjwzI9LYO8VvhcWXzH7nBWh5MeUch+Wkl777KrsLhrXnCg==', + verificationMethod: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + }, + }, + ], + proof: { + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + type: 'Ed25519Signature2018', + created: '2022-04-21T10:15:38Z', + proofPurpose: 'authentication', + challenge: 'e950bfe5-d7ec-4303-ad61-6983fb976ac9', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..wGtR9yuTRfhrsvCthUOn-fg_lK0mZIe2IOO2Lv21aOXo5YUAbk50qMBLk4C1iqoOx-Jz6R0g4aa4cuqpdXzkBw', + }, + }, +} diff --git a/packages/core/src/modules/vc/constants.ts b/packages/core/src/modules/vc/constants.ts new file mode 100644 index 0000000000..6b298625df --- /dev/null +++ b/packages/core/src/modules/vc/constants.ts @@ -0,0 +1,14 @@ +export const SECURITY_CONTEXT_V1_URL = 'https://w3id.org/security/v1' +export const SECURITY_CONTEXT_V2_URL = 'https://w3id.org/security/v2' +export const SECURITY_CONTEXT_V3_URL = 'https://w3id.org/security/v3-unstable' +export const SECURITY_CONTEXT_URL = SECURITY_CONTEXT_V2_URL +export const SECURITY_X25519_CONTEXT_URL = 'https://w3id.org/security/suites/x25519-2019/v1' +export const DID_V1_CONTEXT_URL = 'https://www.w3.org/ns/did/v1' +export const CREDENTIALS_CONTEXT_V1_URL = 'https://www.w3.org/2018/credentials/v1' +export const SECURITY_CONTEXT_BBS_URL = 'https://w3id.org/security/bbs/v1' +export const CREDENTIALS_ISSUER_URL = 'https://www.w3.org/2018/credentials#issuer' +export const SECURITY_PROOF_URL = 'https://w3id.org/security#proof' +export const SECURITY_SIGNATURE_URL = 'https://w3id.org/security#signature' +export const VERIFIABLE_CREDENTIAL_TYPE = 'VerifiableCredential' +export const VERIFIABLE_PRESENTATION_TYPE = 'VerifiablePresentation' +export const EXPANDED_TYPE_CREDENTIALS_CONTEXT_V1_VC_TYPE = 'https://www.w3.org/2018/credentials#VerifiableCredential' diff --git a/packages/core/src/modules/vc/index.ts b/packages/core/src/modules/vc/index.ts new file mode 100644 index 0000000000..6aa4d6b1d6 --- /dev/null +++ b/packages/core/src/modules/vc/index.ts @@ -0,0 +1,2 @@ +export * from './W3cCredentialService' +export * from './repository/W3cCredentialRecord' diff --git a/packages/core/src/modules/vc/models/LinkedDataProof.ts b/packages/core/src/modules/vc/models/LinkedDataProof.ts new file mode 100644 index 0000000000..3dd7209dcb --- /dev/null +++ b/packages/core/src/modules/vc/models/LinkedDataProof.ts @@ -0,0 +1,86 @@ +import type { SingleOrArray } from '../../../utils/type' + +import { Transform, TransformationType, plainToInstance, instanceToPlain } from 'class-transformer' +import { IsOptional, IsString } from 'class-validator' + +export interface LinkedDataProofOptions { + type: string + proofPurpose: string + verificationMethod: string + created: string + domain?: string + challenge?: string + jws?: string + proofValue?: string + nonce?: string +} + +/** + * Linked Data Proof + * @see https://w3c.github.io/vc-data-model/#proofs-signatures + * + * @class LinkedDataProof + */ +export class LinkedDataProof { + public constructor(options: LinkedDataProofOptions) { + if (options) { + this.type = options.type + this.proofPurpose = options.proofPurpose + this.verificationMethod = options.verificationMethod + this.created = options.created + this.domain = options.domain + this.challenge = options.challenge + this.jws = options.jws + this.proofValue = options.proofValue + this.nonce = options.nonce + } + } + + @IsString() + public type!: string + + @IsString() + public proofPurpose!: string + + @IsString() + public verificationMethod!: string + + @IsString() + public created!: string + + @IsString() + @IsOptional() + public domain?: string + + @IsString() + @IsOptional() + public challenge?: string + + @IsString() + @IsOptional() + public jws?: string + + @IsString() + @IsOptional() + public proofValue?: string + + @IsString() + @IsOptional() + public nonce?: string +} + +// Custom transformers + +export function LinkedDataProofTransformer() { + return Transform(({ value, type }: { value: SingleOrArray; type: TransformationType }) => { + if (type === TransformationType.PLAIN_TO_CLASS) { + if (Array.isArray(value)) return value.map((v) => plainToInstance(LinkedDataProof, v)) + return plainToInstance(LinkedDataProof, value) + } else if (type === TransformationType.CLASS_TO_PLAIN) { + if (Array.isArray(value)) return value.map((v) => instanceToPlain(v)) + return instanceToPlain(value) + } + // PLAIN_TO_PLAIN + return value + }) +} diff --git a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts new file mode 100644 index 0000000000..b683677576 --- /dev/null +++ b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts @@ -0,0 +1,57 @@ +import type { JsonObject } from '../../../types' +import type { SingleOrArray } from '../../../utils/type' +import type { ProofPurpose } from '../proof-purposes/ProofPurpose' +import type { W3cCredential } from './credential/W3cCredential' +import type { W3cVerifiableCredential } from './credential/W3cVerifiableCredential' +import type { W3cPresentation } from './presentation/W3Presentation' +import type { W3cVerifiablePresentation } from './presentation/W3cVerifiablePresentation' + +export interface SignCredentialOptions { + credential: W3cCredential + proofType: string + verificationMethod: string + proofPurpose?: ProofPurpose + created?: string + domain?: string + challenge?: string + credentialStatus?: { + type: string + } +} + +export interface VerifyCredentialOptions { + credential: W3cVerifiableCredential + proofPurpose?: ProofPurpose +} + +export interface StoreCredentialOptions { + record: W3cVerifiableCredential +} + +export interface CreatePresentationOptions { + credentials: SingleOrArray + id?: string + holderUrl?: string +} + +export interface SignPresentationOptions { + presentation: W3cPresentation + signatureType: string + purpose: ProofPurpose + verificationMethod: string + challenge: string +} + +export interface VerifyPresentationOptions { + presentation: W3cVerifiablePresentation + proofType: string + verificationMethod: string + purpose?: ProofPurpose + challenge?: string +} + +export interface DeriveProofOptions { + credential: W3cVerifiableCredential + revealDocument: JsonObject + verificationMethod: string +} diff --git a/packages/core/src/modules/vc/models/credential/CredentialSchema.ts b/packages/core/src/modules/vc/models/credential/CredentialSchema.ts new file mode 100644 index 0000000000..c98ba30fea --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/CredentialSchema.ts @@ -0,0 +1,23 @@ +import { IsString } from 'class-validator' + +import { IsUri } from '../../../../utils/validators' + +export interface CredentialSchemaOptions { + id: string + type: string +} + +export class CredentialSchema { + public constructor(options: CredentialSchemaOptions) { + if (options) { + this.id = options.id + this.type = options.type + } + } + + @IsUri() + public id!: string + + @IsString() + public type!: string +} diff --git a/packages/core/src/modules/vc/models/credential/CredentialSubject.ts b/packages/core/src/modules/vc/models/credential/CredentialSubject.ts new file mode 100644 index 0000000000..7462a68066 --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/CredentialSubject.ts @@ -0,0 +1,40 @@ +import { Transform, TransformationType, plainToInstance, instanceToPlain } from 'class-transformer' +import { isString } from 'class-validator' + +import { IsUri } from '../../../../utils/validators' + +/** + * TODO: check how to support arbitrary data in class + * @see https://www.w3.org/TR/vc-data-model/#credential-subject + */ + +export interface CredentialSubjectOptions { + id: string +} + +export class CredentialSubject { + public constructor(options: CredentialSubjectOptions) { + if (options) { + this.id = options.id + } + } + + @IsUri() + public id!: string +} + +// Custom transformers + +export function CredentialSubjectTransformer() { + return Transform(({ value, type }: { value: string | CredentialSubjectOptions; type: TransformationType }) => { + if (type === TransformationType.PLAIN_TO_CLASS) { + if (isString(value)) return value + return plainToInstance(CredentialSubject, value) + } else if (type === TransformationType.CLASS_TO_PLAIN) { + if (isString(value)) return value + return instanceToPlain(value) + } + // PLAIN_TO_PLAIN + return value + }) +} diff --git a/packages/core/src/modules/vc/models/credential/Issuer.ts b/packages/core/src/modules/vc/models/credential/Issuer.ts new file mode 100644 index 0000000000..6eb4359087 --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/Issuer.ts @@ -0,0 +1,68 @@ +import type { ValidationOptions } from 'class-validator' + +import { Transform, TransformationType, plainToInstance, instanceToPlain } from 'class-transformer' +import { buildMessage, isInstance, isString, ValidateBy } from 'class-validator' + +import { IsUri, UriValidator } from '../../../../utils/validators' + +/** + * TODO: check how to support arbitrary data in class + * @see https://www.w3.org/TR/vc-data-model/#credential-subject + */ + +export interface IssuerOptions { + id: string +} + +export class Issuer { + public constructor(options: IssuerOptions) { + if (options) { + this.id = options.id + } + } + + @IsUri() + public id!: string +} + +// Custom transformers + +export function IssuerTransformer() { + return Transform(({ value, type }: { value: string | IssuerOptions; type: TransformationType }) => { + if (type === TransformationType.PLAIN_TO_CLASS) { + if (isString(value)) return value + return plainToInstance(Issuer, value) + } else if (type === TransformationType.CLASS_TO_PLAIN) { + if (isString(value)) return value + return instanceToPlain(value) + } + // PLAIN_TO_PLAIN + return value + }) +} + +// Custom validators + +export function IsIssuer(validationOptions?: ValidationOptions): PropertyDecorator { + return ValidateBy( + { + name: 'IsIssuer', + validator: { + validate: (value): boolean => { + if (typeof value === 'string') { + return UriValidator.test(value) + } + if (isInstance(value, Issuer)) { + return UriValidator.test(value.id) + } + return false + }, + defaultMessage: buildMessage( + (eachPrefix) => eachPrefix + '$property must be a string or an object with an id property', + validationOptions + ), + }, + }, + validationOptions + ) +} diff --git a/packages/core/src/modules/vc/models/credential/W3cCredential.ts b/packages/core/src/modules/vc/models/credential/W3cCredential.ts new file mode 100644 index 0000000000..beed50d700 --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/W3cCredential.ts @@ -0,0 +1,128 @@ +import type { JsonObject } from '../../../../types' +import type { CredentialSubjectOptions } from './CredentialSubject' +import type { IssuerOptions } from './Issuer' +import type { ValidationOptions } from 'class-validator' + +import { Expose, Type } from 'class-transformer' +import { buildMessage, IsOptional, IsString, ValidateBy } from 'class-validator' + +import { SingleOrArray } from '../../../../utils/type' +import { IsInstanceOrArrayOfInstances, IsUri } from '../../../../utils/validators' +import { CREDENTIALS_CONTEXT_V1_URL, VERIFIABLE_CREDENTIAL_TYPE } from '../../constants' +import { IsJsonLdContext } from '../../validators' + +import { CredentialSchema } from './CredentialSchema' +import { CredentialSubject } from './CredentialSubject' +import { Issuer, IsIssuer, IssuerTransformer } from './Issuer' + +export interface W3cCredentialOptions { + context: Array | JsonObject + id?: string + type: Array + issuer: string | IssuerOptions + issuanceDate: string + expirationDate?: string + credentialSubject: SingleOrArray +} + +export class W3cCredential { + public constructor(options: W3cCredentialOptions) { + if (options) { + this.context = options.context ?? [CREDENTIALS_CONTEXT_V1_URL] + this.id = options.id + this.type = options.type || [] + this.issuer = options.issuer + this.issuanceDate = options.issuanceDate + this.expirationDate = options.expirationDate + this.credentialSubject = options.credentialSubject + } + } + + @Expose({ name: '@context' }) + @IsJsonLdContext() + public context!: Array | JsonObject + + @IsOptional() + @IsUri() + public id?: string + + @IsCredentialType() + public type!: Array + + @IssuerTransformer() + @IsIssuer() + public issuer!: string | Issuer + + @IsString() + public issuanceDate!: string + + @IsString() + @IsOptional() + public expirationDate?: string + + @Type(() => CredentialSubject) + @IsInstanceOrArrayOfInstances({ classType: CredentialSubject }) + public credentialSubject!: SingleOrArray + + @IsOptional() + @Type(() => CredentialSchema) + @IsInstanceOrArrayOfInstances({ classType: CredentialSchema }) + public credentialSchema?: SingleOrArray + + public get issuerId(): string { + return this.issuer instanceof Issuer ? this.issuer.id : this.issuer + } + + public get credentialSchemaIds(): string[] { + if (!this.credentialSchema) return [] + + if (Array.isArray(this.credentialSchema)) { + return this.credentialSchema.map((credentialSchema) => credentialSchema.id) + } + + return [this.credentialSchema.id] + } + + public get credentialSubjectIds(): string[] { + if (Array.isArray(this.credentialSubject)) { + return this.credentialSubject.map((credentialSubject) => credentialSubject.id) + } + + return [this.credentialSubject.id] + } + + public get contexts(): Array { + if (Array.isArray(this.context)) { + return this.context.filter((x) => typeof x === 'string') + } + + if (typeof this.context === 'string') { + return [this.context] + } + + return [this.context.id as string] + } +} + +// Custom validator + +export function IsCredentialType(validationOptions?: ValidationOptions): PropertyDecorator { + return ValidateBy( + { + name: 'IsVerifiableCredentialType', + validator: { + validate: (value): boolean => { + if (Array.isArray(value)) { + return value.includes(VERIFIABLE_CREDENTIAL_TYPE) + } + return false + }, + defaultMessage: buildMessage( + (eachPrefix) => eachPrefix + '$property must be an array of strings which includes "VerifiableCredential"', + validationOptions + ), + }, + }, + validationOptions + ) +} diff --git a/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts b/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts new file mode 100644 index 0000000000..e2d1b1afe3 --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts @@ -0,0 +1,53 @@ +import type { LinkedDataProofOptions } from '../LinkedDataProof' +import type { W3cCredentialOptions } from './W3cCredential' + +import { instanceToPlain, plainToInstance, Transform, TransformationType } from 'class-transformer' + +import { orArrayToArray } from '../../../../utils' +import { SingleOrArray } from '../../../../utils/type' +import { IsInstanceOrArrayOfInstances } from '../../../../utils/validators' +import { LinkedDataProof, LinkedDataProofTransformer } from '../LinkedDataProof' + +import { W3cCredential } from './W3cCredential' + +export interface W3cVerifiableCredentialOptions extends W3cCredentialOptions { + proof: SingleOrArray +} + +export class W3cVerifiableCredential extends W3cCredential { + public constructor(options: W3cVerifiableCredentialOptions) { + super(options) + if (options) { + this.proof = Array.isArray(options.proof) + ? options.proof.map((proof) => new LinkedDataProof(proof)) + : new LinkedDataProof(options.proof) + } + } + + @LinkedDataProofTransformer() + @IsInstanceOrArrayOfInstances({ classType: LinkedDataProof }) + public proof!: SingleOrArray + + public get proofTypes(): Array { + const proofArray = orArrayToArray(this.proof) + return proofArray?.map((x) => x.type) ?? [] + } +} + +// Custom transformers + +export function VerifiableCredentialTransformer() { + return Transform( + ({ value, type }: { value: SingleOrArray; type: TransformationType }) => { + if (type === TransformationType.PLAIN_TO_CLASS) { + if (Array.isArray(value)) return value.map((v) => plainToInstance(W3cVerifiableCredential, v)) + return plainToInstance(W3cVerifiableCredential, value) + } else if (type === TransformationType.CLASS_TO_PLAIN) { + if (Array.isArray(value)) return value.map((v) => instanceToPlain(v)) + return instanceToPlain(value) + } + // PLAIN_TO_PLAIN + return value + } + ) +} diff --git a/packages/core/src/modules/vc/models/credential/W3cVerifyCredentialResult.ts b/packages/core/src/modules/vc/models/credential/W3cVerifyCredentialResult.ts new file mode 100644 index 0000000000..9f8880467a --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/W3cVerifyCredentialResult.ts @@ -0,0 +1,15 @@ +import type { JsonObject } from '../../../../types' +import type { W3cVerifiableCredential } from './W3cVerifiableCredential' + +export interface VerifyCredentialResult { + credential: W3cVerifiableCredential + verified: boolean + error?: Error +} + +export interface W3cVerifyCredentialResult { + verified: boolean + statusResult: JsonObject + results: Array + error?: Error +} diff --git a/packages/core/src/modules/vc/models/index.ts b/packages/core/src/modules/vc/models/index.ts new file mode 100644 index 0000000000..37c71ef25d --- /dev/null +++ b/packages/core/src/modules/vc/models/index.ts @@ -0,0 +1,3 @@ +export * from './credential/W3cCredential' +export * from './credential/W3cVerifiableCredential' +export * from './credential/W3cVerifyCredentialResult' diff --git a/packages/core/src/modules/vc/models/presentation/VerifyPresentationResult.ts b/packages/core/src/modules/vc/models/presentation/VerifyPresentationResult.ts new file mode 100644 index 0000000000..41a9041e04 --- /dev/null +++ b/packages/core/src/modules/vc/models/presentation/VerifyPresentationResult.ts @@ -0,0 +1,9 @@ +import type { JsonObject } from '../../../../types' +import type { VerifyCredentialResult } from '../credential/W3cVerifyCredentialResult' + +export interface VerifyPresentationResult { + verified: boolean + presentationResult: JsonObject // the precise interface of this object is still unclear + credentialResults: Array + error?: Error +} diff --git a/packages/core/src/modules/vc/models/presentation/W3Presentation.ts b/packages/core/src/modules/vc/models/presentation/W3Presentation.ts new file mode 100644 index 0000000000..1ee6ae677f --- /dev/null +++ b/packages/core/src/modules/vc/models/presentation/W3Presentation.ts @@ -0,0 +1,77 @@ +import type { JsonObject } from '../../../../types' +import type { W3cVerifiableCredentialOptions } from '../credential/W3cVerifiableCredential' +import type { ValidationOptions } from 'class-validator' + +import { Expose } from 'class-transformer' +import { buildMessage, IsOptional, IsString, ValidateBy } from 'class-validator' + +import { SingleOrArray } from '../../../../utils/type' +import { IsUri, IsInstanceOrArrayOfInstances } from '../../../../utils/validators' +import { VERIFIABLE_PRESENTATION_TYPE } from '../../constants' +import { IsJsonLdContext } from '../../validators' +import { VerifiableCredentialTransformer, W3cVerifiableCredential } from '../credential/W3cVerifiableCredential' + +export interface W3cPresentationOptions { + id?: string + context: Array | JsonObject + verifiableCredential: SingleOrArray + type: Array + holder?: string +} + +export class W3cPresentation { + public constructor(options: W3cPresentationOptions) { + if (options) { + this.id = options.id + this.context = options.context + this.type = options.type + this.verifiableCredential = Array.isArray(options.verifiableCredential) + ? options.verifiableCredential.map((vc) => new W3cVerifiableCredential(vc)) + : new W3cVerifiableCredential(options.verifiableCredential) + this.holder = options.holder + } + } + + @Expose({ name: '@context' }) + @IsJsonLdContext() + public context!: Array | JsonObject + + @IsOptional() + @IsUri() + public id?: string + + @IsVerifiablePresentationType() + public type!: Array + + @IsOptional() + @IsString() + @IsUri() + public holder?: string + + @VerifiableCredentialTransformer() + @IsInstanceOrArrayOfInstances({ classType: W3cVerifiableCredential }) + public verifiableCredential!: SingleOrArray +} + +// Custom validators + +export function IsVerifiablePresentationType(validationOptions?: ValidationOptions): PropertyDecorator { + return ValidateBy( + { + name: 'IsVerifiablePresentationType', + validator: { + validate: (value): boolean => { + if (Array.isArray(value)) { + return value.includes(VERIFIABLE_PRESENTATION_TYPE) + } + return false + }, + defaultMessage: buildMessage( + (eachPrefix) => eachPrefix + '$property must be an array of strings which includes "VerifiablePresentation"', + validationOptions + ), + }, + }, + validationOptions + ) +} diff --git a/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts b/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts new file mode 100644 index 0000000000..2645fb9cc5 --- /dev/null +++ b/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts @@ -0,0 +1,25 @@ +import type { LinkedDataProofOptions } from '../LinkedDataProof' +import type { W3cPresentationOptions } from './W3Presentation' + +import { SingleOrArray } from '../../../../utils/type' +import { IsInstanceOrArrayOfInstances } from '../../../../utils/validators' +import { LinkedDataProof, LinkedDataProofTransformer } from '../LinkedDataProof' + +import { W3cPresentation } from './W3Presentation' + +export interface W3cVerifiablePresentationOptions extends W3cPresentationOptions { + proof: LinkedDataProofOptions +} + +export class W3cVerifiablePresentation extends W3cPresentation { + public constructor(options: W3cVerifiablePresentationOptions) { + super(options) + if (options) { + this.proof = new LinkedDataProof(options.proof) + } + } + + @LinkedDataProofTransformer() + @IsInstanceOrArrayOfInstances({ classType: LinkedDataProof }) + public proof!: SingleOrArray +} diff --git a/packages/core/src/modules/vc/module.ts b/packages/core/src/modules/vc/module.ts new file mode 100644 index 0000000000..11e4983e05 --- /dev/null +++ b/packages/core/src/modules/vc/module.ts @@ -0,0 +1,14 @@ +import type { DependencyManager } from '../../plugins' + +import { module } from '../../plugins' + +import { W3cCredentialService } from './W3cCredentialService' +import { W3cCredentialRepository } from './repository/W3cCredentialRepository' + +@module() +export class W3cVcModule { + public static register(dependencyManager: DependencyManager) { + dependencyManager.registerSingleton(W3cCredentialService) + dependencyManager.registerSingleton(W3cCredentialRepository) + } +} diff --git a/packages/core/src/modules/vc/proof-purposes/CredentialIssuancePurpose.ts b/packages/core/src/modules/vc/proof-purposes/CredentialIssuancePurpose.ts new file mode 100644 index 0000000000..c4127374e7 --- /dev/null +++ b/packages/core/src/modules/vc/proof-purposes/CredentialIssuancePurpose.ts @@ -0,0 +1,89 @@ +import type { JsonObject } from '../../../types' +import type { DocumentLoader, Proof } from '../../../utils' + +import jsonld from '../../../../types/jsonld' +import { suites, purposes } from '../../../../types/jsonld-signatures' + +const AssertionProofPurpose = purposes.AssertionProofPurpose +const LinkedDataProof = suites.LinkedDataProof +/** + * Creates a proof purpose that will validate whether or not the verification + * method in a proof was authorized by its declared controller for the + * proof's purpose. + */ +export class CredentialIssuancePurpose extends AssertionProofPurpose { + /** + * @param {object} options - The options to use. + * @param {object} [options.controller] - The description of the controller, + * if it is not to be dereferenced via a `documentLoader`. + * @param {string|Date|number} [options.date] - The expected date for + * the creation of the proof. + * @param {number} [options.maxTimestampDelta=Infinity] - A maximum number + * of seconds that the date on the signature can deviate from. + */ + public constructor(options: { controller?: Record; date: string; maxTimestampDelta?: number }) { + options.maxTimestampDelta = options.maxTimestampDelta || Infinity + super(options) + } + + /** + * Validates the purpose of a proof. This method is called during + * proof verification, after the proof value has been checked against the + * given verification method (in the case of a digital signature, the + * signature has been cryptographically verified against the public key). + * + * @param {object} proof - The proof to validate. + * @param {object} options - The options to use. + * @param {object} options.document - The document whose signature is + * being verified. + * @param {object} options.suite - Signature suite used in + * the proof. + * @param {string} options.verificationMethod - Key id URL to the paired + * public key. + * @param {object} [options.documentLoader] - A document loader. + * @param {object} [options.expansionMap] - An expansion map. + * + * @throws {Error} If verification method not authorized by controller. + * @throws {Error} If proof's created timestamp is out of range. + * + * @returns {Promise<{valid: boolean, error: Error}>} Resolves on completion. + */ + public async validate( + proof: Proof, + options?: { + document: JsonObject + suite: typeof LinkedDataProof + verificationMethod: string + documentLoader?: DocumentLoader + expansionMap?: () => void + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): Promise<{ valid: boolean; error?: any }> { + try { + const result = await super.validate(proof, options) + + if (!result.valid) { + throw result.error + } + + // This @ts-ignore is necessary because the .getValues() method is not part of the public API. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + const issuer = jsonld.util.getValues(options.document, 'issuer') + + if (!issuer || issuer.length === 0) { + throw new Error('Credential issuer is required.') + } + + const issuerId = typeof issuer[0] === 'string' ? issuer[0] : issuer[0].id + + if (result.controller.id !== issuerId) { + throw new Error('Credential issuer must match the verification method controller.') + } + + return { valid: true } + } catch (error) { + return { valid: false, error } + } + } +} diff --git a/packages/core/src/modules/vc/proof-purposes/ProofPurpose.ts b/packages/core/src/modules/vc/proof-purposes/ProofPurpose.ts new file mode 100644 index 0000000000..2695f3276c --- /dev/null +++ b/packages/core/src/modules/vc/proof-purposes/ProofPurpose.ts @@ -0,0 +1 @@ +export type ProofPurpose = any diff --git a/packages/core/src/modules/vc/repository/W3cCredentialRecord.ts b/packages/core/src/modules/vc/repository/W3cCredentialRecord.ts new file mode 100644 index 0000000000..b324b6ee26 --- /dev/null +++ b/packages/core/src/modules/vc/repository/W3cCredentialRecord.ts @@ -0,0 +1,57 @@ +import type { TagsBase } from '../../../storage/BaseRecord' + +import { Type } from 'class-transformer' + +import { BaseRecord } from '../../../storage/BaseRecord' +import { uuid } from '../../../utils/uuid' +import { W3cVerifiableCredential } from '../models/credential/W3cVerifiableCredential' + +export interface W3cCredentialRecordOptions { + id?: string + createdAt?: Date + credential: W3cVerifiableCredential + tags: CustomW3cCredentialTags +} + +export type CustomW3cCredentialTags = TagsBase & { + expandedTypes?: Array +} + +export type DefaultCredentialTags = { + issuerId: string + subjectIds: Array + schemaIds: Array + contexts: Array + proofTypes: Array + givenId?: string +} + +export class W3cCredentialRecord extends BaseRecord { + public static readonly type = 'W3cCredentialRecord' + public readonly type = W3cCredentialRecord.type + + @Type(() => W3cVerifiableCredential) + public credential!: W3cVerifiableCredential + + public constructor(props: W3cCredentialRecordOptions) { + super() + if (props) { + this.id = props.id ?? uuid() + this.createdAt = props.createdAt ?? new Date() + this._tags = props.tags + this.credential = props.credential + } + } + + public getTags() { + return { + ...this._tags, + issuerId: this.credential.issuerId, + subjectIds: this.credential.credentialSubjectIds, + schemaIds: this.credential.credentialSchemaIds, + contexts: this.credential.contexts, + proofTypes: this.credential.proofTypes, + givenId: this.credential.id, + } + } +} diff --git a/packages/core/src/modules/vc/repository/W3cCredentialRepository.ts b/packages/core/src/modules/vc/repository/W3cCredentialRepository.ts new file mode 100644 index 0000000000..cb5a83b735 --- /dev/null +++ b/packages/core/src/modules/vc/repository/W3cCredentialRepository.ts @@ -0,0 +1,17 @@ +import { EventEmitter } from '../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../constants' +import { injectable, inject } from '../../../plugins' +import { Repository } from '../../../storage/Repository' +import { StorageService } from '../../../storage/StorageService' + +import { W3cCredentialRecord } from './W3cCredentialRecord' + +@injectable() +export class W3cCredentialRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(W3cCredentialRecord, storageService, eventEmitter) + } +} diff --git a/packages/core/src/modules/vc/repository/__tests__/W3cCredentialRecord.test.ts b/packages/core/src/modules/vc/repository/__tests__/W3cCredentialRecord.test.ts new file mode 100644 index 0000000000..e35a93b448 --- /dev/null +++ b/packages/core/src/modules/vc/repository/__tests__/W3cCredentialRecord.test.ts @@ -0,0 +1,32 @@ +import { JsonTransformer } from '../../../../utils' +import { Ed25519Signature2018Fixtures } from '../../__tests__/fixtures' +import { W3cVerifiableCredential } from '../../models' +import { W3cCredentialRecord } from '../W3cCredentialRecord' + +describe('W3cCredentialRecord', () => { + describe('getTags', () => { + it('should return default tags', () => { + const credential = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + W3cVerifiableCredential + ) + + const w3cCredentialRecord = new W3cCredentialRecord({ + credential, + tags: { + expandedTypes: ['https://expanded.tag#1'], + }, + }) + + expect(w3cCredentialRecord.getTags()).toEqual({ + issuerId: credential.issuerId, + subjectIds: credential.credentialSubjectIds, + schemaIds: credential.credentialSchemaIds, + contexts: credential.contexts, + proofTypes: credential.proofTypes, + givenId: credential.id, + expandedTypes: ['https://expanded.tag#1'], + }) + }) + }) +}) diff --git a/packages/core/src/modules/vc/repository/index.ts b/packages/core/src/modules/vc/repository/index.ts new file mode 100644 index 0000000000..64aae1fdcb --- /dev/null +++ b/packages/core/src/modules/vc/repository/index.ts @@ -0,0 +1,2 @@ +export * from './W3cCredentialRecord' +export * from './W3cCredentialRepository' diff --git a/packages/core/src/modules/vc/validators.ts b/packages/core/src/modules/vc/validators.ts new file mode 100644 index 0000000000..0bce78fa79 --- /dev/null +++ b/packages/core/src/modules/vc/validators.ts @@ -0,0 +1,32 @@ +import type { ValidationOptions } from 'class-validator' + +import { buildMessage, isString, isURL, ValidateBy } from 'class-validator' + +import { CREDENTIALS_CONTEXT_V1_URL } from './constants' + +export function IsJsonLdContext(validationOptions?: ValidationOptions): PropertyDecorator { + return ValidateBy( + { + name: 'IsJsonLdContext', + validator: { + validate: (value): boolean => { + // If value is an array, check if all items are strings, are URLs and that + // the first entry is a verifiable credential context + if (Array.isArray(value)) { + return value.every((v) => isString(v) && isURL(v)) && value[0] === CREDENTIALS_CONTEXT_V1_URL + } + // If value is not an array, check if it is an object (assuming it's a JSON-LD context definition) + if (typeof value === 'object') { + return true + } + return false + }, + defaultMessage: buildMessage( + (eachPrefix) => eachPrefix + '$property must be an array of strings or a JSON-LD context definition', + validationOptions + ), + }, + }, + validationOptions + ) +} diff --git a/packages/core/src/utils/environment.ts b/packages/core/src/utils/environment.ts new file mode 100644 index 0000000000..b2ebadf0dd --- /dev/null +++ b/packages/core/src/utils/environment.ts @@ -0,0 +1,9 @@ +export function isNodeJS() { + return typeof process !== 'undefined' && process.release && process.release.name === 'node' +} + +export function isReactNative() { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return typeof navigator != 'undefined' && navigator.product == 'ReactNative' +} diff --git a/packages/core/src/utils/error.ts b/packages/core/src/utils/error.ts new file mode 100644 index 0000000000..530264240a --- /dev/null +++ b/packages/core/src/utils/error.ts @@ -0,0 +1 @@ +export const isError = (value: unknown): value is Error => value instanceof Error diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index e9352470ff..318ad5d39f 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -9,3 +9,4 @@ export * from './regex' export * from './indyProofRequest' export * from './VarintEncoder' export * from './Hasher' +export * from './jsonld' diff --git a/packages/core/src/utils/jsonld.ts b/packages/core/src/utils/jsonld.ts new file mode 100644 index 0000000000..86c1ba7aea --- /dev/null +++ b/packages/core/src/utils/jsonld.ts @@ -0,0 +1,156 @@ +import type { GetProofsOptions, GetProofsResult, GetTypeOptions } from '../crypto/signature-suites/bbs' +import type { JsonObject, JsonValue } from '../types' +import type { SingleOrArray } from './type' + +import jsonld from '../../types/jsonld' +import { SECURITY_CONTEXT_URL } from '../modules/vc/constants' + +export type JsonLdDoc = Record +export interface VerificationMethod extends JsonObject { + id: string + [key: string]: JsonValue +} + +export interface Proof extends JsonObject { + verificationMethod: string | VerificationMethod + [key: string]: JsonValue +} + +export interface DocumentLoaderResult { + contextUrl?: string | null + documentUrl: string + document: Record +} + +export type DocumentLoader = (url: string) => Promise + +export const orArrayToArray = (val?: SingleOrArray): Array => { + if (!val) return [] + if (Array.isArray(val)) return val + return [val] +} + +export const _includesContext = (options: { document: JsonLdDoc; contextUrl: string }) => { + const context = options.document['@context'] + + return context === options.contextUrl || (Array.isArray(context) && context.includes(options.contextUrl)) +} + +/* + * The code in this file originated from + * @see https://github.com/digitalbazaar/jsonld-signatures + * Hence the following copyright notice applies + * + * Copyright (c) 2017-2018 Digital Bazaar, Inc. All rights reserved. + */ + +/** + * The property identifying the linked data proof + * Note - this will not work for legacy systems that + * relying on `signature` + */ +const PROOF_PROPERTY = 'proof' + +/** + * Gets a supported linked data proof from a JSON-LD Document + * Note - unless instructed not to the document will be compacted + * against the security v2 context @see https://w3id.org/security/v2 + * + * @param options Options for extracting the proof from the document + * + * @returns {GetProofsResult} An object containing the matched proofs and the JSON-LD document + */ +export const getProofs = async (options: GetProofsOptions): Promise => { + const { proofType, skipProofCompaction, documentLoader, expansionMap } = options + let { document } = options + + let proofs + if (!skipProofCompaction) { + // If we must compact the proof then we must first compact the input + // document to find the proof + document = await jsonld.compact(document, SECURITY_CONTEXT_URL, { + documentLoader, + expansionMap, + compactToRelative: false, + }) + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - needed because getValues is not part of the public API. + proofs = jsonld.getValues(document, PROOF_PROPERTY) + delete document[PROOF_PROPERTY] + + if (typeof proofType === 'string') { + proofs = proofs.filter((_: Record) => _.type == proofType) + } + if (Array.isArray(proofType)) { + proofs = proofs.filter((_: Record) => proofType.includes(_.type)) + } + + proofs = proofs.map((matchedProof: Record) => ({ + '@context': SECURITY_CONTEXT_URL, + ...matchedProof, + })) + + return { + proofs, + document, + } +} + +/** + * Formats an input date to w3c standard date format + * @param date {number|string} Optional if not defined current date is returned + * + * @returns {string} date in a standard format as a string + */ +export const w3cDate = (date?: number | string): string => { + let result = new Date() + if (typeof date === 'number' || typeof date === 'string') { + result = new Date(date) + } + const str = result.toISOString() + return str.substr(0, str.length - 5) + 'Z' +} + +/** + * Gets the JSON-LD type information for a document + * @param document {any} JSON-LD document to extract the type information from + * @param options {GetTypeInfoOptions} Options for extracting the JSON-LD document + * + * @returns {object} Type info for the JSON-LD document + */ +export const getTypeInfo = async ( + document: JsonObject, + options: GetTypeOptions +): Promise<{ types: string[]; alias: string }> => { + const { documentLoader, expansionMap } = options + + // determine `@type` alias, if any + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - needed because getValues is not part of the public API. + const context = jsonld.getValues(document, '@context') + + const compacted = await jsonld.compact({ '@type': '_:b0' }, context, { + documentLoader, + expansionMap, + }) + + delete compacted['@context'] + + const alias = Object.keys(compacted)[0] + + // optimize: expand only `@type` and `type` values + /* eslint-disable prefer-const */ + let toExpand: Record = { '@context': context } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - needed because getValues is not part of the public API. + toExpand['@type'] = jsonld.getValues(document, '@type').concat(jsonld.getValues(document, alias)) + + const expanded = (await jsonld.expand(toExpand, { documentLoader, expansionMap }))[0] || {} + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - needed because getValues is not part of the public API. + return { types: jsonld.getValues(expanded, '@type'), alias } +} diff --git a/packages/core/src/utils/type.ts b/packages/core/src/utils/type.ts index c49bfda4cb..2155975323 100644 --- a/packages/core/src/utils/type.ts +++ b/packages/core/src/utils/type.ts @@ -1,5 +1,7 @@ import type { JsonObject } from '../types' +export type SingleOrArray = T | T[] + export type Optional = Pick, K> & Omit export const isString = (value: unknown): value is string => typeof value === 'string' diff --git a/packages/core/src/utils/validators.ts b/packages/core/src/utils/validators.ts index 3a822fdca9..47997bc482 100644 --- a/packages/core/src/utils/validators.ts +++ b/packages/core/src/utils/validators.ts @@ -3,6 +3,10 @@ import type { ValidationOptions } from 'class-validator' import { isString, ValidateBy, isInstance, buildMessage } from 'class-validator' +export interface IsInstanceOrArrayOfInstancesValidationOptions extends ValidationOptions { + classType: new (...args: any[]) => any +} + /** * Checks if the value is an instance of the specified object. */ @@ -27,3 +31,55 @@ export function IsStringOrInstance(targetType: Constructor, validationOptions?: validationOptions ) } + +export function IsInstanceOrArrayOfInstances( + validationOptions: IsInstanceOrArrayOfInstancesValidationOptions +): PropertyDecorator { + return ValidateBy( + { + name: 'isInstanceOrArrayOfInstances', + validator: { + validate: (value): boolean => { + if (Array.isArray(value)) { + value.forEach((item) => { + if (!isInstance(item, validationOptions.classType)) { + return false + } + }) + return true + } + return isInstance(value, validationOptions.classType) + }, + defaultMessage: buildMessage( + (eachPrefix) => eachPrefix + `$property must be a string or instance of ${validationOptions.classType.name}`, + validationOptions + ), + }, + }, + validationOptions + ) +} + +export function isStringArray(value: any): value is string[] { + return Array.isArray(value) && value.every((v) => typeof v === 'string') +} + +export const UriValidator = /\w+:(\/?\/?)[^\s]+/ + +export function IsUri(validationOptions?: ValidationOptions): PropertyDecorator { + return ValidateBy( + { + name: 'isInstanceOrArrayOfInstances', + validator: { + validate: (value): boolean => { + return UriValidator.test(value) + }, + defaultMessage: buildMessage( + (eachPrefix) => eachPrefix + `$property must be a string that matches regex: ${UriValidator.source}`, + validationOptions + ), + }, + }, + validationOptions + ) +} diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts index 117b4d3e5d..c354d5ef5b 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/core/src/wallet/IndyWallet.ts @@ -20,12 +20,13 @@ import type { import type { default as Indy, WalletStorageConfig } from 'indy-sdk' import { AgentConfig } from '../agent/AgentConfig' -import { KeyType } from '../crypto' import { BbsService } from '../crypto/BbsService' import { Key } from '../crypto/Key' +import { KeyType } from '../crypto/KeyType' import { AriesFrameworkError, IndySdkError, RecordDuplicateError, RecordNotFoundError } from '../error' import { injectable } from '../plugins' import { JsonEncoder, TypedArrayEncoder } from '../utils' +import { isError } from '../utils/error' import { isIndyError } from '../utils/indyError' import { WalletDuplicateError, WalletError, WalletNotFoundError } from './error' @@ -150,6 +151,9 @@ export class IndyWallet implements Wallet { cause: error, }) } else { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + } const errorMessage = `Error creating wallet '${walletConfig.id}'` this.logger.error(errorMessage, { error, @@ -232,6 +236,9 @@ export class IndyWallet implements Wallet { cause: error, }) } else { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + } const errorMessage = `Error opening wallet '${walletConfig.id}': ${error.message}` this.logger.error(errorMessage, { error, @@ -277,6 +284,9 @@ export class IndyWallet implements Wallet { cause: error, }) } else { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + } const errorMessage = `Error deleting wallet '${this.walletConfig.id}': ${error.message}` this.logger.error(errorMessage, { error, @@ -293,6 +303,9 @@ export class IndyWallet implements Wallet { this.logger.debug(`Exporting wallet ${this.walletConfig?.id} to path ${exportConfig.path}`) await this.indy.exportWallet(this.handle, exportConfig) } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + } const errorMessage = `Error exporting wallet: ${error.message}` this.logger.error(errorMessage, { error, @@ -311,6 +324,9 @@ export class IndyWallet implements Wallet { importConfig ) } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + } const errorMessage = `Error importing wallet': ${error.message}` this.logger.error(errorMessage, { error, @@ -341,6 +357,9 @@ export class IndyWallet implements Wallet { cause: error, }) } else { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + } const errorMessage = `Error closing wallet': ${error.message}` this.logger.error(errorMessage, { error, @@ -380,6 +399,10 @@ export class IndyWallet implements Wallet { return masterSecretId } else { + if (!isIndyError(error)) { + throw new AriesFrameworkError('Attempted to throw Indy error, but it was not an Indy error') + } + this.logger.error(`Error creating master secret with id ${masterSecretId}`, { indyError: error.indyName, error, @@ -407,6 +430,9 @@ export class IndyWallet implements Wallet { return { did, verkey } } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + } throw new WalletError('Error creating Did', { cause: error }) } } @@ -440,6 +466,9 @@ export class IndyWallet implements Wallet { return Key.fromPublicKeyBase58(blsKeyPair.publicKeyBase58, keyType) } } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + } throw new WalletError(`Error creating key with key type '${keyType}': ${error.message}`, { cause: error }) } @@ -475,6 +504,9 @@ export class IndyWallet implements Wallet { }) } } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + } throw new WalletError(`Error signing data with verkey ${key.publicKeyBase58}`, { cause: error }) } throw new WalletError(`Unsupported keyType: ${key.keyType}`) @@ -508,6 +540,9 @@ export class IndyWallet implements Wallet { return await BbsService.verify({ signature, publicKey: key.publicKey, messages: data }) } } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + } throw new WalletError(`Error verifying signature of data signed with verkey ${key.publicKeyBase58}`, { cause: error, }) @@ -525,6 +560,9 @@ export class IndyWallet implements Wallet { const packedMessage = await this.indy.packMessage(this.handle, messageRaw, recipientKeys, senderVerkey ?? null) return JsonEncoder.fromBuffer(packedMessage) } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + } throw new WalletError('Error packing message', { cause: error }) } } @@ -539,6 +577,9 @@ export class IndyWallet implements Wallet { plaintextMessage: JsonEncoder.fromString(unpackedMessage.message), } } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + } throw new WalletError('Error unpacking message', { cause: error }) } } @@ -547,6 +588,9 @@ export class IndyWallet implements Wallet { try { return await this.indy.generateNonce() } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + } throw new WalletError('Error generating nonce', { cause: error }) } } diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 047f1ca335..93d9dd32b5 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "allowJs": false, "typeRoots": ["../../node_modules/@types", "src/types"], "types": ["jest"] } diff --git a/packages/core/types/jsonld-signatures.ts b/packages/core/types/jsonld-signatures.ts new file mode 100644 index 0000000000..c930e2bfe6 --- /dev/null +++ b/packages/core/types/jsonld-signatures.ts @@ -0,0 +1,23 @@ +import { + suites as JsonLdSuites, + purposes as JsonLdPurposes, + constants as JsonLdConstants, + //@ts-ignore +} from '@digitalcredentials/jsonld-signatures' + +interface Suites { + LinkedDataSignature: any + LinkedDataProof: any +} + +interface Purposes { + AssertionProofPurpose: any +} + +type Constants = any + +export const suites = JsonLdSuites as Suites + +export const purposes = JsonLdPurposes as Purposes + +export const constants = JsonLdConstants as Constants diff --git a/packages/core/types/jsonld.ts b/packages/core/types/jsonld.ts new file mode 100644 index 0000000000..3e54d97b10 --- /dev/null +++ b/packages/core/types/jsonld.ts @@ -0,0 +1,29 @@ +//@ts-ignore +import jsonld from '@digitalcredentials/jsonld' +//@ts-ignore +import nodeDocumentLoader from '@digitalcredentials/jsonld/lib/documentLoaders/node' +//@ts-ignore +import xhrDocumentLoader from '@digitalcredentials/jsonld/lib/documentLoaders/xhr' + +interface JsonLd { + compact(document: any, context: any, options?: any): any + fromRDF(document: any): any + frame(document: any, revealDocument: any, options?: any): any + canonize(document: any, options?: any): any + expand(document: any, options?: any): any + getValues(document: any, key: string): any + addValue(document: any, key: string, value: any): void +} + +export interface DocumentLoaderResult { + contextUrl?: string | null + documentUrl: string + document: Record +} + +export type DocumentLoader = (url: string) => Promise + +export const documentLoaderXhr = xhrDocumentLoader as () => DocumentLoader +export const documentLoaderNode = nodeDocumentLoader as () => DocumentLoader + +export default jsonld as unknown as JsonLd diff --git a/packages/core/types/vc.ts b/packages/core/types/vc.ts new file mode 100644 index 0000000000..ca6196528d --- /dev/null +++ b/packages/core/types/vc.ts @@ -0,0 +1,12 @@ +// @ts-ignore +import vc from '@digitalcredentials/vc' + +interface VC { + issue(options: any): Promise> + verifyCredential(options: any): Promise> + createPresentation(options: any): Promise> + signPresentation(options: any): Promise> + verify(options: any): Promise> +} + +export default vc as unknown as VC diff --git a/packages/react-native/package.json b/packages/react-native/package.json index cd523a1f87..42390330cf 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -43,6 +43,7 @@ "peerDependencies": { "indy-sdk-react-native": "^0.2.2", "react-native-fs": "^2.18.0", - "react-native-get-random-values": "^1.7.0" + "react-native-get-random-values": "^1.7.0", + "react-native-bbs-signatures": "0.1.0" } } diff --git a/yarn.lock b/yarn.lock index 275981217c..3d866296a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,39 +34,39 @@ dependencies: "@babel/highlight" "^7.16.7" -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.17.0", "@babel/compat-data@^7.17.10": +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.17.10": version "7.17.10" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.10.tgz#711dc726a492dfc8be8220028b1b92482362baab" integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw== "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.10.tgz#74ef0fbf56b7dfc3f198fc2d927f4f03e12f4b05" - integrity sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.12.tgz#b4eb2d7ebc3449b062381644c93050db545b70ee" + integrity sha512-44ODe6O1IVz9s2oJE3rZ4trNNKTX9O7KpQpfAP4t8QII/zwrVRHL7i2pxhqtcY7tqMLrrKfMlBKnm1QlrRFs5w== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.10" + "@babel/generator" "^7.17.12" "@babel/helper-compilation-targets" "^7.17.10" - "@babel/helper-module-transforms" "^7.17.7" + "@babel/helper-module-transforms" "^7.17.12" "@babel/helpers" "^7.17.9" - "@babel/parser" "^7.17.10" + "@babel/parser" "^7.17.12" "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.10" - "@babel/types" "^7.17.10" + "@babel/traverse" "^7.17.12" + "@babel/types" "^7.17.12" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.17.10", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.10.tgz#c281fa35b0c349bbe9d02916f4ae08fc85ed7189" - integrity sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg== +"@babel/generator@^7.17.12", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.12.tgz#5970e6160e9be0428e02f4aba62d8551ec366cc8" + integrity sha512-V49KtZiiiLjH/CnIW6OjJdrenrGoyh6AmKQ3k2AZFKozC1h846Q4NYlZ5nqAigPDUXfGzC88+LOUuG8yKd2kCw== dependencies: - "@babel/types" "^7.17.10" - "@jridgewell/gen-mapping" "^0.1.0" + "@babel/types" "^7.17.12" + "@jridgewell/gen-mapping" "^0.3.0" jsesc "^2.5.1" "@babel/helper-annotate-as-pure@^7.16.7": @@ -94,10 +94,10 @@ browserslist "^4.20.2" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.16.7": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.9.tgz#71835d7fb9f38bd9f1378e40a4c0902fdc2ea49d" - integrity sha512-kUjip3gruz6AJKOq5i3nC6CoCEEF/oHH3cp6tOZhB+IyyyPyW0g1Gfsxn3mkk6S08pIA2y8GQh609v9G/5sHVQ== +"@babel/helper-create-class-features-plugin@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.12.tgz#d4f8393fc4838cbff6b7c199af5229aee16d07cf" + integrity sha512-sZoOeUTkFJMyhqCei2+Z+wtH/BehW8NVKQt7IRUQlRiOARuXymJYfN/FCcI8CvVbR0XVyDM6eLFOlR7YtiXnew== dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-environment-visitor" "^7.16.7" @@ -108,9 +108,9 @@ "@babel/helper-split-export-declaration" "^7.16.7" "@babel/helper-create-regexp-features-plugin@^7.16.7": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz#1dcc7d40ba0c6b6b25618997c5dbfd310f186fe1" - integrity sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.12.tgz#bb37ca467f9694bbe55b884ae7a5cc1e0084e4fd" + integrity sha512-b2aZrV4zvutr9AIa6/gA3wsZKRwTKYoDxYiFKcESS3Ug2GTXzwBEvMuuFLhCQpEnRXs1zng4ISAXSUxxKBIcxw== dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" regexpu-core "^5.0.1" @@ -172,10 +172,10 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-module-transforms@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz#3943c7f777139e7954a5355c815263741a9c1cbd" - integrity sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw== +"@babel/helper-module-transforms@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.12.tgz#bec00139520cb3feb078ef7a4578562480efb77e" + integrity sha512-t5s2BeSWIghhFRPh9XMn6EIGmvn8Lmw5RVASJzkIx1mSemubQQBNIZiQD7WzaFmaHIrjAec4x8z9Yx8SjJ1/LA== dependencies: "@babel/helper-environment-visitor" "^7.16.7" "@babel/helper-module-imports" "^7.16.7" @@ -183,8 +183,8 @@ "@babel/helper-split-export-declaration" "^7.16.7" "@babel/helper-validator-identifier" "^7.16.7" "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.3" - "@babel/types" "^7.17.0" + "@babel/traverse" "^7.17.12" + "@babel/types" "^7.17.12" "@babel/helper-optimise-call-expression@^7.16.7": version "7.16.7" @@ -193,10 +193,10 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" - integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" + integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== "@babel/helper-replace-supers@^7.16.7": version "7.16.7" @@ -250,53 +250,53 @@ "@babel/types" "^7.17.0" "@babel/highlight@^7.10.4", "@babel/highlight@^7.16.7": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.9.tgz#61b2ee7f32ea0454612def4fccdae0de232b73e3" - integrity sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.12.tgz#257de56ee5afbd20451ac0a75686b6b404257351" + integrity sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg== dependencies: "@babel/helper-validator-identifier" "^7.16.7" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.10": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.10.tgz#873b16db82a8909e0fbd7f115772f4b739f6ce78" - integrity sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.12.tgz#36c2ed06944e3691ba82735fc4cf62d12d491a23" + integrity sha512-FLzHmN9V3AJIrWfOpvRlZCeVg/WLdicSnTMsLur6uDj9TT8ymUlG9XxURdW/XvuygK+2CW0poOJABdA4m/YKxA== "@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.1.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz#925cad7b3b1a2fcea7e59ecc8eb5954f961f91b0" - integrity sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.17.12.tgz#84f65c0cc247d46f40a6da99aadd6438315d80a4" + integrity sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-create-class-features-plugin" "^7.17.12" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-proposal-export-default-from@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.16.7.tgz#a40ab158ca55627b71c5513f03d3469026a9e929" - integrity sha512-+cENpW1rgIjExn+o5c8Jw/4BuH4eGKKYvkMB8/0ZxFQ9mC0t4z09VsPIwNg6waF69QYC81zxGeAsREGuqQoKeg== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.17.12.tgz#df785e638618d8ffa14e08c78c44d9695d083b73" + integrity sha512-LpsTRw725eBAXXKUOnJJct+SEaOzwR78zahcLuripD2+dKc2Sj+8Q2DzA+GC/jOpOu/KlDXuxrzG214o1zTauQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-export-default-from" "^7.16.7" "@babel/plugin-proposal-nullish-coalescing-operator@^7.0.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.1.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz#141fc20b6857e59459d430c850a0011e36561d99" - integrity sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.17.12.tgz#1e93079bbc2cbc756f6db6a1925157c4a92b94be" + integrity sha512-ws/g3FSGVzv+VH86+QvgtuJL/kR67xaEIF2x0iPqdDfYW6ra6JF3lKVBkWynRLcNtIC1oCTfDRVxmm2mKzy+ag== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" "@babel/plugin-proposal-object-rest-spread@^7.0.0": - version "7.17.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz#d9eb649a54628a51701aef7e0ea3d17e2b9dd390" - integrity sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.12.tgz#f94a91715a7f2f8cfb3c06af820c776440bc0148" + integrity sha512-6l9cO3YXXRh4yPCPRA776ZyJ3RobG4ZKJZhp7NDRbKIOeV3dBPG8FXCF7ZtiO2RTCIOkQOph1xDDcc01iWVNjQ== dependencies: - "@babel/compat-data" "^7.17.0" - "@babel/helper-compilation-targets" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/compat-data" "^7.17.10" + "@babel/helper-compilation-targets" "^7.17.10" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.16.7" + "@babel/plugin-transform-parameters" "^7.17.12" "@babel/plugin-proposal-optional-catch-binding@^7.0.0": version "7.16.7" @@ -307,11 +307,11 @@ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-proposal-optional-chaining@^7.0.0", "@babel/plugin-proposal-optional-chaining@^7.1.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz#7cd629564724816c0e8a969535551f943c64c39a" - integrity sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.17.12.tgz#f96949e9bacace3a9066323a5cf90cfb9de67174" + integrity sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" @@ -350,12 +350,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-syntax-flow@^7.0.0", "@babel/plugin-syntax-flow@^7.16.7", "@babel/plugin-syntax-flow@^7.2.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.16.7.tgz#202b147e5892b8452bbb0bb269c7ed2539ab8832" - integrity sha512-UDo3YGQO0jH6ytzVwgSLv9i/CzMcUjbKenL67dTrAZPPv6GFAtDhe6jqnvmoKzC/7htNTohhos+onPtDMqJwaQ== +"@babel/plugin-syntax-flow@^7.0.0", "@babel/plugin-syntax-flow@^7.17.12", "@babel/plugin-syntax-flow@^7.2.0": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.17.12.tgz#23d852902acd19f42923fca9d0f196984d124e73" + integrity sha512-B8QIgBvkIG6G2jgsOHQUist7Sm0EBLDCx8sen072IwqNuzMegZNXrYnSv77cYzA8mLDZAfQYqsLIhimiP1s2HQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" @@ -371,12 +371,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665" - integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q== +"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.17.12.tgz#834035b45061983a491f60096f61a2e7c5674a47" + integrity sha512-spyY3E3AURfxh/RHtjx5j6hs8am5NbUBGfcZ2vB3uShSpZdQyXSf5rR5Mk76vbtlAZOelyVQ71Fg0x9SG4fsog== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" @@ -427,19 +427,19 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.16.7", "@babel/plugin-syntax-typescript@^7.7.2": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.10.tgz#80031e6042cad6a95ed753f672ebd23c30933195" - integrity sha512-xJefea1DWXW09pW4Tm9bjwVlPDyYA2it3fWlmEjpYz6alPvTUjL0EOzNzI/FEOyI3r4/J7uVH5UqKgl1TQ5hqQ== +"@babel/plugin-syntax-typescript@^7.17.12", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz#b54fc3be6de734a56b87508f99d6428b5b605a7b" + integrity sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-arrow-functions@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz#44125e653d94b98db76369de9c396dc14bef4154" - integrity sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.17.12.tgz#dddd783b473b1b1537ef46423e3944ff24898c45" + integrity sha512-PHln3CNi/49V+mza4xMwrg+WGYevSF1oaiXaC2EQfdp4HWlSjRsrDXWJiQBKpP7749u6vQ9mcry2uuFOv5CXvA== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-block-scoped-functions@^7.0.0": version "7.16.7" @@ -449,39 +449,39 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-block-scoping@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz#f50664ab99ddeaee5bc681b8f3a6ea9d72ab4f87" - integrity sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.17.12.tgz#68fc3c4b3bb7dfd809d97b7ed19a584052a2725c" + integrity sha512-jw8XW/B1i7Lqwqj2CbrViPcZijSxfguBWZP2aN59NHgxUyO/OcO1mfdCxH13QhN5LbWhPkX+f+brKGhZTiqtZQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-classes@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz#8f4b9562850cd973de3b498f1218796eb181ce00" - integrity sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.17.12.tgz#da889e89a4d38375eeb24985218edeab93af4f29" + integrity sha512-cvO7lc7pZat6BsvH6l/EGaI8zpl8paICaoGk+7x7guvtfak/TbIf66nYmJOH13EuG0H+Xx3M+9LQDtSvZFKXKw== dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.16.7" + "@babel/helper-function-name" "^7.17.9" "@babel/helper-optimise-call-expression" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-replace-supers" "^7.16.7" "@babel/helper-split-export-declaration" "^7.16.7" globals "^11.1.0" "@babel/plugin-transform-computed-properties@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz#66dee12e46f61d2aae7a73710f591eb3df616470" - integrity sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.17.12.tgz#bca616a83679698f3258e892ed422546e531387f" + integrity sha512-a7XINeplB5cQUWMg1E/GI1tFz3LfK021IjV1rj1ypE+R7jHm+pIHmHl25VNkZxtx9uuYp7ThGk8fur1HHG7PgQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-destructuring@^7.0.0": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.7.tgz#49dc2675a7afa9a5e4c6bdee636061136c3408d1" - integrity sha512-XVh0r5yq9sLR4vZ6eVZe8FKfIcSgaTBxVBRSYokRj2qksf6QerYnTxz9/GTuKTH/n/HwLP7t6gtlybHetJ/6hQ== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.12.tgz#0861d61e75e2401aca30f2570d46dfc85caacf35" + integrity sha512-P8pt0YiKtX5UMUL5Xzsc9Oyij+pJE6JuC+F1k0/brq/OOGs5jDa1If3OY0LRWGvJsJhI+8tsiecL3nJLc0WTlg== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-exponentiation-operator@^7.0.0": version "7.16.7" @@ -491,20 +491,20 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-flow-strip-types@^7.0.0", "@babel/plugin-transform-flow-strip-types@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.16.7.tgz#291fb140c78dabbf87f2427e7c7c332b126964b8" - integrity sha512-mzmCq3cNsDpZZu9FADYYyfZJIOrSONmHcop2XEKPdBNMa4PDC4eEvcOvzZaCNcjKu72v0XQlA5y1g58aLRXdYg== +"@babel/plugin-transform-flow-strip-types@^7.0.0", "@babel/plugin-transform-flow-strip-types@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.17.12.tgz#5e070f99a4152194bd9275de140e83a92966cab3" + integrity sha512-g8cSNt+cHCpG/uunPQELdq/TeV3eg1OLJYwxypwHtAWo9+nErH3lQx9CSO2uI9lF74A0mR0t4KoMjs1snSgnTw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-flow" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" + "@babel/plugin-syntax-flow" "^7.17.12" "@babel/plugin-transform-for-of@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz#649d639d4617dff502a9a158c479b3b556728d8c" - integrity sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.17.12.tgz#5397c22554ec737a27918e7e7e0e7b679b05f5ec" + integrity sha512-76lTwYaCxw8ldT7tNmye4LLwSoKDbRCBzu6n/DcK/P3FOR29+38CIIaVIZfwol9By8W/QHORYEnYSLuvcQKrsg== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-function-name@^7.0.0": version "7.16.7" @@ -516,11 +516,11 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-literals@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz#254c9618c5ff749e87cb0c0cef1a0a050c0bdab1" - integrity sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.17.12.tgz#97131fbc6bbb261487105b4b3edbf9ebf9c830ae" + integrity sha512-8iRkvaTjJciWycPIZ9k9duu663FT7VrBdNqNgxnVXEFwOIp55JWcZd23VBRySYbnS3PwQ3rGiabJBBBGj5APmQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-member-expression-literals@^7.0.0": version "7.16.7" @@ -530,12 +530,12 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.1.0": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.9.tgz#274be1a2087beec0254d4abd4d86e52442e1e5b6" - integrity sha512-2TBFd/r2I6VlYn0YRTz2JdazS+FoUuQ2rIFHoAxtyP/0G3D82SBLaRq9rnUkpqlLg03Byfl/+M32mpxjO6KaPw== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.12.tgz#37691c7404320d007288edd5a2d8600bcef61c34" + integrity sha512-tVPs6MImAJz+DiX8Y1xXEMdTk5Lwxu9jiPjlS+nv5M2A59R7+/d1+9A8C/sbuY0b3QjIxqClkj6KAplEtRvzaA== dependencies: - "@babel/helper-module-transforms" "^7.17.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-module-transforms" "^7.17.12" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-simple-access" "^7.17.7" babel-plugin-dynamic-import-node "^2.3.3" @@ -554,12 +554,12 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-replace-supers" "^7.16.7" -"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz#a1721f55b99b736511cb7e0152f61f17688f331f" - integrity sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw== +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.17.12.tgz#eb467cd9586ff5ff115a9880d6fdbd4a846b7766" + integrity sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-property-literals@^7.0.0": version "7.16.7" @@ -576,11 +576,11 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-react-jsx-self@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.16.7.tgz#f432ad0cba14c4a1faf44f0076c69e42a4d4479e" - integrity sha512-oe5VuWs7J9ilH3BCCApGoYjHoSO48vkjX2CbA5bFVhIuO2HKxA3vyF7rleA4o6/4rTDbk6r8hBW7Ul8E+UZrpA== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.17.12.tgz#7f2e9b8c08d6a4204733138d8c29d4dba4bb66c2" + integrity sha512-7S9G2B44EnYOx74mue02t1uD8ckWZ/ee6Uz/qfdzc35uWHX5NgRy9i+iJSb2LFRgMd+QV9zNcStQaazzzZ3n3Q== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-react-jsx-source@^7.0.0": version "7.16.7" @@ -590,15 +590,15 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-react-jsx@^7.0.0": - version "7.17.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz#eac1565da176ccb1a715dae0b4609858808008c1" - integrity sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.12.tgz#2aa20022709cd6a3f40b45d60603d5f269586dba" + integrity sha512-Lcaw8bxd1DKht3thfD4A12dqo1X16he1Lm8rIv8sTwjAYNInRS1qHa9aJoqvzpscItXvftKDCfaEQzwoVyXpEQ== dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-jsx" "^7.16.7" - "@babel/types" "^7.17.0" + "@babel/helper-plugin-utils" "^7.17.12" + "@babel/plugin-syntax-jsx" "^7.17.12" + "@babel/types" "^7.17.12" "@babel/plugin-transform-regenerator@^7.0.0": version "7.17.9" @@ -608,12 +608,12 @@ regenerator-transform "^0.15.0" "@babel/plugin-transform-runtime@^7.0.0": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.10.tgz#b89d821c55d61b5e3d3c3d1d636d8d5a81040ae1" - integrity sha512-6jrMilUAJhktTr56kACL8LnWC5hx3Lf27BS0R0DSyW/OoJfb/iTHeE96V3b1dgKG3FSFdd/0culnYWMkjcKCig== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.12.tgz#5dc79735c4038c6f4fc0490f68f2798ce608cadd" + integrity sha512-xsl5MeGjWnmV6Ui9PfILM2+YRpa3GqLOrczPpXV3N2KCgQGU+sU8OfzuMbjkIdfvZEZIm+3y0V7w58sk0SGzlw== dependencies: "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" babel-plugin-polyfill-corejs2 "^0.3.0" babel-plugin-polyfill-corejs3 "^0.5.0" babel-plugin-polyfill-regenerator "^0.3.0" @@ -627,11 +627,11 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-spread@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz#a303e2122f9f12e0105daeedd0f30fb197d8ff44" - integrity sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.17.12.tgz#c112cad3064299f03ea32afed1d659223935d1f5" + integrity sha512-9pgmuQAtFi3lpNUstvG9nGfk9DkrdmWNp9KeKPFmuZCpEnxRzYlS8JgwPjYj+1AWDOSvoGN0H30p1cBOmT/Svg== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" "@babel/plugin-transform-sticky-regex@^7.0.0": @@ -642,20 +642,20 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-template-literals@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz#f3d1c45d28967c8e80f53666fc9c3e50618217ab" - integrity sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.17.12.tgz#4aec0a18f39dd86c442e1d077746df003e362c6e" + integrity sha512-kAKJ7DX1dSRa2s7WN1xUAuaQmkTpN+uig4wCKWivVXIObqGbVTUlSavHyfI2iZvz89GFAMGm9p2DBJ4Y1Tp0hw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-transform-typescript@^7.16.7", "@babel/plugin-transform-typescript@^7.5.0": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz#591ce9b6b83504903fa9dd3652c357c2ba7a1ee0" - integrity sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ== +"@babel/plugin-transform-typescript@^7.17.12", "@babel/plugin-transform-typescript@^7.5.0": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.17.12.tgz#9654587131bc776ff713218d929fa9a2e98ca16d" + integrity sha512-ICbXZqg6hgenjmwciVI/UfqZtExBrZOrS8sLB5mTHGO/j08Io3MmooULBiijWk9JBknjM3CbbtTc/0ZsqLrjXQ== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-typescript" "^7.16.7" + "@babel/helper-create-class-features-plugin" "^7.17.12" + "@babel/helper-plugin-utils" "^7.17.12" + "@babel/plugin-syntax-typescript" "^7.17.12" "@babel/plugin-transform-unicode-regex@^7.0.0": version "7.16.7" @@ -666,22 +666,22 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/preset-flow@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.16.7.tgz#7fd831323ab25eeba6e4b77a589f680e30581cbd" - integrity sha512-6ceP7IyZdUYQ3wUVqyRSQXztd1YmFHWI4Xv11MIqAlE4WqxBSd/FZ61V9k+TS5Gd4mkHOtQtPp9ymRpxH4y1Ug== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.17.12.tgz#664a5df59190260939eee862800a255bef3bd66f" + integrity sha512-7QDz7k4uiaBdu7N89VKjUn807pJRXmdirQu0KyR9LXnQrr5Jt41eIMKTS7ljej+H29erwmMrwq9Io9mJHLI3Lw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-validator-option" "^7.16.7" - "@babel/plugin-transform-flow-strip-types" "^7.16.7" + "@babel/plugin-transform-flow-strip-types" "^7.17.12" "@babel/preset-typescript@^7.1.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz#ab114d68bb2020afc069cd51b37ff98a046a70b9" - integrity sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.17.12.tgz#40269e0a0084d56fc5731b6c40febe1c9a4a3e8c" + integrity sha512-S1ViF8W2QwAKUGJXxP9NAfNaqGDdEBJKpYkxHf5Yy2C4NPPzXGeR3Lhk7G8xJaaLcFTRfNjVbtbVtm8Gb0mqvg== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-validator-option" "^7.16.7" - "@babel/plugin-transform-typescript" "^7.16.7" + "@babel/plugin-transform-typescript" "^7.17.12" "@babel/register@^7.0.0": version "7.17.7" @@ -710,26 +710,26 @@ "@babel/parser" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.17.10", "@babel/traverse@^7.17.3", "@babel/traverse@^7.17.9", "@babel/traverse@^7.7.2": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.10.tgz#1ee1a5ac39f4eac844e6cf855b35520e5eb6f8b5" - integrity sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.17.12", "@babel/traverse@^7.17.9", "@babel/traverse@^7.7.2": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.12.tgz#011874d2abbca0ccf1adbe38f6f7a4ff1747599c" + integrity sha512-zULPs+TbCvOkIFd4FrG53xrpxvCBwLIgo6tO0tJorY7YV2IWFxUfS/lXDJbGgfyYt9ery/Gxj2niwttNnB0gIw== dependencies: "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.10" + "@babel/generator" "^7.17.12" "@babel/helper-environment-visitor" "^7.16.7" "@babel/helper-function-name" "^7.17.9" "@babel/helper-hoist-variables" "^7.16.7" "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.17.10" - "@babel/types" "^7.17.10" + "@babel/parser" "^7.17.12" + "@babel/types" "^7.17.12" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.17.10", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.10.tgz#d35d7b4467e439fcf06d195f8100e0fea7fc82c4" - integrity sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A== +"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.17.12", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.12.tgz#1210690a516489c0200f355d87619157fbbd69a0" + integrity sha512-rH8i29wcZ6x9xjzI5ILHL/yZkbQnCERdHlogKuIb4PUr7do4iT8DPekrTbBLWTnRQm6U0GYABbTMSzijmEqlAg== dependencies: "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" @@ -759,6 +759,57 @@ dependencies: "@cspotcode/source-map-consumer" "0.8.0" +"@digitalbazaar/security-context@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@digitalbazaar/security-context/-/security-context-1.0.0.tgz#23624692cfadc6d97e1eb787ad38a19635d89297" + integrity sha512-mlj+UmodxTAdMCHGxnGVTRLHcSLyiEOVRiz3J6yiRliJWyrgeXs34wlWjBorDIEMDIjK2JwZrDuFEKO9bS5nKQ== + +"@digitalcredentials/http-client@^1.0.0": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@digitalcredentials/http-client/-/http-client-1.2.2.tgz#8b09ab6f1e3aa8878d91d3ca51946ca8265cc92e" + integrity sha512-YOwaE+vUDSwiDhZT0BbXSWVg+bvp1HA1eg/gEc8OCwCOj9Bn9FRQdu8P9Y/fnYqyFCioDwwTRzGxgJLl50baEg== + dependencies: + ky "^0.25.1" + ky-universal "^0.8.2" + +"@digitalcredentials/jsonld-signatures@^9.3.1": + version "9.3.1" + resolved "https://registry.yarnpkg.com/@digitalcredentials/jsonld-signatures/-/jsonld-signatures-9.3.1.tgz#e00175ab4199c580c9b308effade021da805c695" + integrity sha512-YMh1e1GpTeHDqq2a2Kd+pLcHsMiPeKyE2Zs17NSwqckij7UMRVDQ54S5VQhHvoXZ1mlkpVaI2xtj5M5N6rzylw== + dependencies: + "@digitalbazaar/security-context" "^1.0.0" + "@digitalcredentials/jsonld" "^5.2.1" + fast-text-encoding "^1.0.3" + isomorphic-webcrypto "^2.3.8" + serialize-error "^8.0.1" + +"@digitalcredentials/jsonld@^5.2.1": + version "5.2.1" + resolved "https://registry.yarnpkg.com/@digitalcredentials/jsonld/-/jsonld-5.2.1.tgz#60acf587bec8331e86324819fd19692939118775" + integrity sha512-pDiO1liw8xs+J/43qnMZsxyz0VOWOb7Q2yUlBt/tyjq6SlT9xPo+3716tJPbjGPnou2lQRw3H5/I++z+6oQ07w== + dependencies: + "@digitalcredentials/http-client" "^1.0.0" + "@digitalcredentials/rdf-canonize" "^1.0.0" + canonicalize "^1.0.1" + lru-cache "^6.0.0" + +"@digitalcredentials/rdf-canonize@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@digitalcredentials/rdf-canonize/-/rdf-canonize-1.0.0.tgz#6297d512072004c2be7f280246383a9c4b0877ff" + integrity sha512-z8St0Ex2doecsExCFK1uI4gJC+a5EqYYu1xpRH1pKmqSS9l/nxfuVxexNFyaeEum4dUdg1EetIC2rTwLIFhPRA== + dependencies: + fast-text-encoding "^1.0.3" + isomorphic-webcrypto "^2.3.8" + +"@digitalcredentials/vc@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@digitalcredentials/vc/-/vc-1.1.2.tgz#868a56962f5137c29eb51eea1ba60251ebf69ad1" + integrity sha512-TSgny9XUh+W7uFjdcpvZzN7I35F9YMTv6jVINXr7UaLNgrinIjy6A5RMGQH9ecpcaoLMemKB5XjtLOOOQ3vknQ== + dependencies: + "@digitalcredentials/jsonld" "^5.2.1" + "@digitalcredentials/jsonld-signatures" "^9.3.1" + credentials-context "^2.0.0" + "@eslint/eslintrc@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" @@ -1021,6 +1072,15 @@ "@jridgewell/set-array" "^1.0.0" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9" + integrity sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/resolve-uri@^3.0.3": version "3.0.7" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" @@ -1715,7 +1775,7 @@ npmlog "^4.1.2" write-file-atomic "^3.0.3" -"@mattrglobal/bbs-signatures@^1.0.0": +"@mattrglobal/bbs-signatures@1.0.0", "@mattrglobal/bbs-signatures@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@mattrglobal/bbs-signatures/-/bbs-signatures-1.0.0.tgz#8ff272c6d201aadab7e08bd84dbfd6e0d48ba12d" integrity sha512-FFzybdKqSCrS/e7pl5s6Tl/m/x8ZD5EMBbcTBQaqSOms/lebm91lFukYOIe2qc0a5o+gLhtRKye8OfKwD1Ex/g== @@ -1724,6 +1784,15 @@ optionalDependencies: "@mattrglobal/node-bbs-signatures" "0.13.0" +"@mattrglobal/bls12381-key-pair@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@mattrglobal/bls12381-key-pair/-/bls12381-key-pair-1.0.0.tgz#2959f8663595de0209bebe88517235ae34f1e2b1" + integrity sha512-FbvSkoy1n3t5FHtAPj8cyQJL7Bz+hvvmquCBZW2+bOBBBT26JhGtr//s6EmXE9e4EZk7bAA1yMHI6i1Ky2us0Q== + dependencies: + "@mattrglobal/bbs-signatures" "1.0.0" + bs58 "4.0.1" + rfc4648 "1.4.0" + "@mattrglobal/node-bbs-signatures@0.13.0": version "0.13.0" resolved "https://registry.yarnpkg.com/@mattrglobal/node-bbs-signatures/-/node-bbs-signatures-0.13.0.tgz#3e431b915325d4b139706f8b26fd84b27c192a29" @@ -1929,6 +1998,33 @@ dependencies: "@octokit/openapi-types" "^11.2.0" +"@peculiar/asn1-schema@^2.1.6": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.1.8.tgz#552300a1ed7991b22c9abf789a3920a3cb94c26b" + integrity sha512-u34H/bpqCdDuqrCVZvH0vpwFBT/dNEdNY+eE8u4IuC26yYnhDkXF4+Hliqca88Avbb7hyN2EF/eokyDdyS7G/A== + dependencies: + asn1js "^3.0.4" + pvtsutils "^1.3.2" + tslib "^2.4.0" + +"@peculiar/json-schema@^1.1.12": + version "1.1.12" + resolved "https://registry.yarnpkg.com/@peculiar/json-schema/-/json-schema-1.1.12.tgz#fe61e85259e3b5ba5ad566cb62ca75b3d3cd5339" + integrity sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w== + dependencies: + tslib "^2.0.0" + +"@peculiar/webcrypto@^1.0.22": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.0.tgz#f941bd95285a0f8a3d2af39ccda5197b80cd32bf" + integrity sha512-U58N44b2m3OuTgpmKgf0LPDOmP3bhwNz01vAnj1mBwxBASRhptWYK+M3zG+HBkDqGQM+bFsoIihTW8MdmPXEqg== + dependencies: + "@peculiar/asn1-schema" "^2.1.6" + "@peculiar/json-schema" "^1.1.12" + pvtsutils "^1.3.2" + tslib "^2.4.0" + webcrypto-core "^1.7.4" + "@react-native-community/cli-debugger-ui@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-5.0.1.tgz#6b1f3367b8e5211e899983065ea2e72c1901d75f" @@ -2362,7 +2458,7 @@ "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== "@types/luxon@^1.27.0": version "1.27.1" @@ -2600,6 +2696,21 @@ "@typescript-eslint/types" "4.33.0" eslint-visitor-keys "^2.0.0" +"@unimodules/core@*": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@unimodules/core/-/core-7.1.2.tgz#5181b99586476a5d87afd0958f26a04714c47fa1" + integrity sha512-lY+e2TAFuebD3vshHMIRqru3X4+k7Xkba4Wa7QsDBd+ex4c4N2dHAO61E2SrGD9+TRBD8w/o7mzK6ljbqRnbyg== + dependencies: + compare-versions "^3.4.0" + +"@unimodules/react-native-adapter@*": + version "6.3.9" + resolved "https://registry.yarnpkg.com/@unimodules/react-native-adapter/-/react-native-adapter-6.3.9.tgz#2f4bef6b7532dce5bf9f236e69f96403d0243c30" + integrity sha512-i9/9Si4AQ8awls+YGAKkByFbeAsOPgUNeLoYeh2SQ3ddjxJ5ZJDtq/I74clDnpDcn8zS9pYlcDJ9fgVJa39Glw== + dependencies: + expo-modules-autolinking "^0.0.3" + invariant "^2.2.4" + JSONStream@^1.0.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -2628,7 +2739,7 @@ abort-controller@^3.0.0: absolute-path@^0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/absolute-path/-/absolute-path-0.0.0.tgz#a78762fbdadfb5297be99b15d35a785b2f095bf7" - integrity sha1-p4di+9rftSl76ZsV01p4Wy8JW/c= + integrity sha512-HQiug4c+/s3WOvEnDRxXVmNtSG5s2gJM9r19BTcqjp7BWcE48PB+Y2G6jE65kqI0LpsQeMZygt/b60Gi4KxGyA== accepts@^1.3.7, accepts@~1.3.5, accepts@~1.3.7, accepts@~1.3.8: version "1.3.8" @@ -2674,7 +2785,7 @@ acorn@^8.2.4, acorn@^8.4.1: add-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" - integrity sha1-anmQQ3ynNtXhKI25K9MmbV9csqo= + integrity sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ== agent-base@6, agent-base@^6.0.2: version "6.0.2" @@ -2726,9 +2837,9 @@ anser@^1.4.9: integrity sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww== ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== ansi-escapes@^4.2.1: version "4.3.2" @@ -2749,7 +2860,7 @@ ansi-fragments@^0.2.1: ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== ansi-regex@^4.1.0: version "4.1.1" @@ -2842,7 +2953,7 @@ argparse@^1.0.7: arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== arr-flatten@^1.1.0: version "1.1.0" @@ -2852,14 +2963,14 @@ arr-flatten@^1.1.0: arr-union@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== array-back@^3.0.1, array-back@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== -array-back@^4.0.1: +array-back@^4.0.1, array-back@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== @@ -2872,17 +2983,17 @@ array-differ@^3.0.0: array-filter@~0.0.0: version "0.0.1" resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" - integrity sha1-fajPLiZijtcygDWB/SH2fKzS7uw= + integrity sha512-VW0FpCIhjZdarWjIz8Vpva7U95fl2Jn+b+mmFFMLn8PIVscOQcAgEznwUzTEuUHuqZqIxwzRlcaN/urTFFQoiw== array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== array-ify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" - integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= + integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== array-includes@^3.1.4: version "3.1.5" @@ -2898,12 +3009,12 @@ array-includes@^3.1.4: array-map@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" - integrity sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI= + integrity sha512-123XMszMB01QKVptpDQ7x1m1pP5NmJIG1kbl0JSPPRezvwQChxAN0Gvzo7rvR1IZ2tOL2tmiy7kY/KKgnpVVpg== array-reduce@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" - integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys= + integrity sha512-8jR+StqaC636u7h3ye1co3lQRefgVVUQUhuAmRbDqIMeR2yuXzRvkCNQiQ5J/wbREmoBLNtp13dhaaVpZQDRUw== array-union@^2.1.0: version "2.1.0" @@ -2913,7 +3024,7 @@ array-union@^2.1.0: array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== array.prototype.flat@^1.2.5: version "1.3.0" @@ -2928,7 +3039,7 @@ array.prototype.flat@^1.2.5: arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= + integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== arrify@^2.0.1: version "2.0.1" @@ -2938,7 +3049,12 @@ arrify@^2.0.1: asap@^2.0.0, asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +asmcrypto.js@^0.22.0: + version "0.22.0" + resolved "https://registry.yarnpkg.com/asmcrypto.js/-/asmcrypto.js-0.22.0.tgz#38fc1440884d802c7bd37d1d23c2b26a5cd5d2d2" + integrity sha512-usgMoyXjMbx/ZPdzTSXExhMPur2FTdz/Vo5PVx2gIaBcdAAJNOFlsdgqveM8Cff7W0v+xrf9BwjOV26JSAF9qA== asn1@~0.2.3: version "0.2.6" @@ -2947,15 +3063,24 @@ asn1@~0.2.3: dependencies: safer-buffer "~2.1.0" +asn1js@^3.0.1, asn1js@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.4.tgz#65fece61bd30d0ef1e31b39fcd383810f44c9fb5" + integrity sha512-ZibuNYyfODvHiVyRFs80xLAUjCwBSkLbE+r1TasjlRKwdodENGT4AlLdaN12Pl/EcK3lFMDYXU6lE2g7Sq9VVQ== + dependencies: + pvtsutils "^1.3.2" + pvutils "^1.1.3" + tslib "^2.4.0" + assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== ast-types@0.14.2: version "0.14.2" @@ -2989,7 +3114,7 @@ async@^2.4.0: asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== at-least-node@^1.0.0: version "1.0.0" @@ -3004,13 +3129,27 @@ atob@^2.1.2: aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== aws4@^1.8.0: version "1.11.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +b64-lite@^1.3.1, b64-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/b64-lite/-/b64-lite-1.4.0.tgz#e62442de11f1f21c60e38b74f111ac0242283d3d" + integrity sha512-aHe97M7DXt+dkpa8fHlCcm1CnskAHrJqEfMI0KN7dwqlzml/aUe1AGt6lk51HzrSfVD67xOso84sOpr+0wIe2w== + dependencies: + base-64 "^0.1.0" + +b64u-lite@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/b64u-lite/-/b64u-lite-1.1.0.tgz#a581b7df94cbd4bed7cbb19feae816654f0b1bf0" + integrity sha512-929qWGDVCRph7gQVTC6koHqQIpF4vtVaSbwLltFQo44B1bYUquALswZdBKFfrJCPEnsCOvWkJsPdQYZ/Ukhw8A== + dependencies: + b64-lite "^1.4.0" + babel-core@^7.0.0-bridge.0: version "7.0.0-bridge.0" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" @@ -3154,9 +3293,16 @@ balanced-match@^1.0.0: base-64@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb" - integrity sha1-eAqZyE59YAJgNhURxId2E78k9rs= + integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA== -base64-js@^1.1.2, base64-js@^1.3.1, base64-js@^1.5.1: +base-x@^3.0.2: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + +base64-js@*, base64-js@^1.1.2, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -3177,7 +3323,7 @@ base@^0.11.1: bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== dependencies: tweetnacl "^0.14.3" @@ -3307,6 +3453,13 @@ bs-logger@0.x: dependencies: fast-json-stable-stringify "2.x" +bs58@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== + dependencies: + base-x "^3.0.2" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -3330,12 +3483,12 @@ buffer@^6.0.0, buffer@^6.0.2, buffer@^6.0.3: builtins@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" - integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= + integrity sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ== byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" - integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= + integrity sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q== byte-size@^7.0.0: version "7.0.1" @@ -3345,7 +3498,7 @@ byte-size@^7.0.0: bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== bytes@3.1.2: version "3.1.2" @@ -3402,21 +3555,21 @@ call-bind@^1.0.0, call-bind@^1.0.2: caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + integrity sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ== dependencies: callsites "^2.0.0" caller-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + integrity sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A== dependencies: caller-callsite "^2.0.0" callsites@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + integrity sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ== callsites@^3.0.0: version "3.1.0" @@ -3443,9 +3596,14 @@ camelcase@^6.0.0, camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001332: - version "1.0.30001340" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001340.tgz#029a2f8bfc025d4820fafbfaa6259fd7778340c7" - integrity sha512-jUNz+a9blQTQVu4uFcn17uAD8IDizPzQkIKh3LCJfg9BkyIqExYYdyc/ZSlWUSKb8iYiXxKsxbv4zYSvkqjrxw== + version "1.0.30001341" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001341.tgz#59590c8ffa8b5939cf4161f00827b8873ad72498" + integrity sha512-2SodVrFFtvGENGCv0ChVJIDQ0KPaS1cg7/qtfMaICgeMolDdo/Z2OD32F0Aq9yl6F4YFwGPBS5AaPqNYiW4PoA== + +canonicalize@^1.0.1: + version "1.0.8" + resolved "https://registry.yarnpkg.com/canonicalize/-/canonicalize-1.0.8.tgz#24d1f1a00ed202faafd9bf8e63352cd4450c6df1" + integrity sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A== capture-exit@^2.0.0: version "2.0.0" @@ -3457,7 +3615,7 @@ capture-exit@^2.0.0: caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2: version "2.4.2" @@ -3556,7 +3714,7 @@ clear@^0.1.0: cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== dependencies: restore-cursor "^2.0.0" @@ -3716,13 +3874,13 @@ command-line-commands@^3.0.1: array-back "^4.0.1" command-line-usage@^6.1.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.1.tgz#c908e28686108917758a49f45efb4f02f76bc03f" - integrity sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA== + version "6.1.3" + resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.3.tgz#428fa5acde6a838779dfa30e44686f4b6761d957" + integrity sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw== dependencies: - array-back "^4.0.1" + array-back "^4.0.2" chalk "^2.4.2" - table-layout "^1.0.1" + table-layout "^1.0.2" typical "^5.2.0" commander@^2.15.0, commander@^2.19.0: @@ -3730,6 +3888,11 @@ commander@^2.15.0, commander@^2.19.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" @@ -3753,6 +3916,11 @@ compare-func@^2.0.0: array-ify "^1.0.0" dot-prop "^5.1.0" +compare-versions@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" + integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== + component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -3993,6 +4161,11 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +credentials-context@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/credentials-context/-/credentials-context-2.0.0.tgz#68a9a1a88850c398d3bba4976c8490530af093e8" + integrity sha512-/mFKax6FK26KjgV2KW2D4YqKgoJ5DVJpNt87X2Jc9IxT2HBMy7nEIlc+n7pEi+YFFe721XqrvZPd+jbyyBjsvQ== + cross-fetch@^3.1.2: version "3.1.5" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" @@ -4038,9 +4211,9 @@ cssstyle@^2.3.0: cssom "~0.3.6" csstype@^3.0.2: - version "3.0.11" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" - integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== + version "3.1.0" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2" + integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA== dargs@^7.0.0: version "7.0.0" @@ -4054,6 +4227,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-uri-to-buffer@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" + integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== + data-urls@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" @@ -4411,9 +4589,9 @@ errorhandler@^1.5.0: escape-html "~1.0.3" es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5: - version "1.20.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.0.tgz#b2d526489cceca004588296334726329e0a6bfb6" - integrity sha512-URbD8tgRthKD3YcC39vbvSDrX23upXnPcnGAjQfgxXF5ID75YcENawc9ZX/9iTP9ptUyfCLIxTTuMYoRfiOVKA== + version "1.20.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814" + integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" @@ -4434,7 +4612,7 @@ es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19 object-inspect "^1.12.0" object-keys "^1.1.1" object.assign "^4.1.2" - regexp.prototype.flags "^1.4.1" + regexp.prototype.flags "^1.4.3" string.prototype.trimend "^1.0.5" string.prototype.trimstart "^1.0.5" unbox-primitive "^1.0.2" @@ -4752,6 +4930,24 @@ expect@^27.5.1: jest-matcher-utils "^27.5.1" jest-message-util "^27.5.1" +expo-modules-autolinking@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-0.0.3.tgz#45ba8cb1798f9339347ae35e96e9cc70eafb3727" + integrity sha512-azkCRYj/DxbK4udDuDxA9beYzQTwpJ5a9QA0bBgha2jHtWdFGF4ZZWSY+zNA5mtU3KqzYt8jWHfoqgSvKyu1Aw== + dependencies: + chalk "^4.1.0" + commander "^7.2.0" + fast-glob "^3.2.5" + find-up "~5.0.0" + fs-extra "^9.1.0" + +expo-random@*: + version "12.2.0" + resolved "https://registry.yarnpkg.com/expo-random/-/expo-random-12.2.0.tgz#a3c8a9ce84ef2c85900131d96eea6c7123285482" + integrity sha512-SihCGLmDyDOALzBN8XXpz2hCw0RSx9c4/rvjcS4Bfqhw6luHjL2rHNTLrFYrPrPRmG1jHM6dXXJe/Zm8jdu+2g== + dependencies: + base64-js "^1.3.0" + express@^4.17.1: version "4.18.1" resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" @@ -4857,7 +5053,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^3.2.9: +fast-glob@^3.2.5, fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== @@ -4878,6 +5074,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-text-encoding@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" + integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== + fastq@^1.6.0: version "1.13.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" @@ -4892,6 +5093,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fetch-blob@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" + integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== + ffi-napi@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/ffi-napi/-/ffi-napi-4.0.3.tgz#27a8d42a8ea938457154895c59761fbf1a10f441" @@ -5014,6 +5220,14 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@~5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -5343,14 +5557,14 @@ glob-parent@^5.1.1, glob-parent@^5.1.2: is-glob "^4.0.1" glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" @@ -6078,6 +6292,24 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +isomorphic-webcrypto@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/isomorphic-webcrypto/-/isomorphic-webcrypto-2.3.8.tgz#4a7493b486ef072b9f11b6f8fd66adde856e3eec" + integrity sha512-XddQSI0WYlSCjxtm1AI8kWQOulf7hAN3k3DclF1sxDJZqOe0pcsOt675zvWW91cZH9hYs3nlA3Ev8QK5i80SxQ== + dependencies: + "@peculiar/webcrypto" "^1.0.22" + asmcrypto.js "^0.22.0" + b64-lite "^1.3.1" + b64u-lite "^1.0.1" + msrcrypto "^1.5.6" + str2buf "^1.3.0" + webcrypto-shim "^0.1.4" + optionalDependencies: + "@unimodules/core" "*" + "@unimodules/react-native-adapter" "*" + expo-random "*" + react-native-securerandom "^0.1.1" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -6852,6 +7084,19 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +ky-universal@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/ky-universal/-/ky-universal-0.8.2.tgz#edc398d54cf495d7d6830aa1ab69559a3cc7f824" + integrity sha512-xe0JaOH9QeYxdyGLnzUOVGK4Z6FGvDVzcXFTdrYA1f33MZdEa45sUDaMBy98xQMcsd2XIBrTXRrRYnegcSdgVQ== + dependencies: + abort-controller "^3.0.0" + node-fetch "3.0.0-beta.9" + +ky@^0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/ky/-/ky-0.25.1.tgz#0df0bd872a9cc57e31acd5dbc1443547c881bfbc" + integrity sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA== + lerna@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/lerna/-/lerna-4.0.0.tgz#b139d685d50ea0ca1be87713a7c2f44a5b678e9e" @@ -6919,9 +7164,9 @@ libnpmpublish@^4.0.0: ssri "^8.0.1" libphonenumber-js@^1.9.7: - version "1.9.53" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.53.tgz#f4f3321f8fb0ee62952c2a8df4711236d2626088" - integrity sha512-3cuMrA2CY3TbKVC0wKye5dXYgxmVVi4g13gzotprQSguFHMqf0pIrMM2Z6ZtMsSWqvtIqi5TuQhGjMhxz0O9Mw== + version "1.10.4" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.4.tgz#90397f0ed620262570a32244c9fbc389cc417ce4" + integrity sha512-9QWxEk4GW5RDnFzt8UtyRENfFpAN8u7Sbf9wf32tcXY9tdtnz1dKHIBwW2Wnfx8ypXJb9zUnTpK9aQJ/B8AlnA== lines-and-columns@^1.1.6: version "1.2.4" @@ -6971,6 +7216,13 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" @@ -7536,7 +7788,7 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.2: +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -7683,6 +7935,11 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +msrcrypto@^1.5.6: + version "1.5.8" + resolved "https://registry.yarnpkg.com/msrcrypto/-/msrcrypto-1.5.8.tgz#be419be4945bf134d8af52e9d43be7fa261f4a1c" + integrity sha512-ujZ0TRuozHKKm6eGbKHfXef7f+esIhEckmThVnz7RNyiOJd7a6MXj2JGBoL9cnPDW+JMG16MoTUh5X+XXjI66Q== + multimatch@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" @@ -7794,6 +8051,14 @@ node-fetch@2.6.7, node-fetch@^2.0, node-fetch@^2.2.0, node-fetch@^2.6.0, node-fe dependencies: whatwg-url "^5.0.0" +node-fetch@3.0.0-beta.9: + version "3.0.0-beta.9" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.0.0-beta.9.tgz#0a7554cfb824380dd6812864389923c783c80d9b" + integrity sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg== + dependencies: + data-uri-to-buffer "^3.0.1" + fetch-blob "^2.1.1" + node-gyp-build@^4.2.1: version "4.4.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.4.0.tgz#42e99687ce87ddeaf3a10b99dc06abc11021f3f4" @@ -8283,6 +8548,13 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -8304,6 +8576,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-map-series@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-2.1.0.tgz#7560d4c452d9da0c07e692fdbfe6e2c81a2a91f2" @@ -8693,6 +8972,18 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +pvtsutils@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.2.tgz#9f8570d132cdd3c27ab7d51a2799239bf8d8d5de" + integrity sha512-+Ipe2iNUyrZz+8K/2IOo+kKikdtfhRKzNpQbruF2URmqPtoqAs8g3xS7TJvFF2GcPXjh7DkqMnpVveRFq4PgEQ== + dependencies: + tslib "^2.4.0" + +pvutils@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.3.tgz#f35fc1d27e7cd3dfbd39c0826d173e806a03f5a3" + integrity sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ== + q@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -8814,6 +9105,13 @@ react-native-get-random-values@^1.7.0: dependencies: fast-base64-decode "^1.0.0" +react-native-securerandom@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/react-native-securerandom/-/react-native-securerandom-0.1.1.tgz#f130623a412c338b0afadedbc204c5cbb8bf2070" + integrity sha1-8TBiOkEsM4sK+t7bwgTFy7i/IHA= + dependencies: + base64-js "*" + react-native@0.64.2: version "0.64.2" resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.64.2.tgz#233b6ed84ac4749c8bc2a2d6cf63577a1c437d18" @@ -9076,7 +9374,7 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.4.1: +regexp.prototype.flags@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== @@ -9242,6 +9540,11 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rfc4648@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.4.0.tgz#c75b2856ad2e2d588b6ddb985d556f1f7f2a2abd" + integrity sha512-3qIzGhHlMHA6PoT6+cdPKZ+ZqtxkIvg8DZGKA5z6PQ33/uuhoJ+Ws/D/J9rXW6gXodgH8QYlz2UCl+sdUDmNIg== + rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -9402,6 +9705,13 @@ serialize-error@^2.1.0: resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" integrity sha1-ULZ51WNc34Rme9yOWa9OW4HV9go= +serialize-error@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-8.1.0.tgz#3a069970c712f78634942ddd50fbbc0eaebe2f67" + integrity sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ== + dependencies: + type-fest "^0.20.2" + serve-static@1.15.0, serve-static@^1.13.1: version "1.15.0" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" @@ -9773,6 +10083,11 @@ statuses@~1.5.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +str2buf@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/str2buf/-/str2buf-1.3.0.tgz#a4172afff4310e67235178e738a2dbb573abead0" + integrity sha512-xIBmHIUHYZDP4HyoXGHYNVmxlXLXDrtFHYT0eV6IOdEj3VO9ccaF1Ejl9Oq8iFjITllpT8FhaXb4KsNmw+3EuA== + stream-buffers@2.2.x: version "2.2.0" resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" @@ -9952,7 +10267,7 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -table-layout@^1.0.1: +table-layout@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A== @@ -10180,9 +10495,9 @@ trim-newlines@^3.0.0: integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== ts-jest@^27.0.3: - version "27.1.4" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.4.tgz#84d42cf0f4e7157a52e7c64b1492c46330943e00" - integrity sha512-qjkZlVPWVctAezwsOD1OPzbZ+k7zA5z3oxII4dGdZo5ggX/PL7kvwTM0pXTr10fAtbiVpJaL3bWd502zAhpgSQ== + version "27.1.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.5.tgz#0ddf1b163fbaae3d5b7504a1e65c914a95cff297" + integrity sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA== dependencies: bs-logger "0.x" fast-json-stable-stringify "2.x" @@ -10232,7 +10547,7 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.1, tslib@^2.1.0: +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== @@ -10637,6 +10952,22 @@ web-did-resolver@^2.0.8: cross-fetch "^3.1.2" did-resolver "^3.1.5" +webcrypto-core@^1.7.4: + version "1.7.5" + resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.5.tgz#c02104c953ca7107557f9c165d194c6316587ca4" + integrity sha512-gaExY2/3EHQlRNNNVSrbG2Cg94Rutl7fAaKILS1w8ZDhGxdFOaw6EbCfHIxPy9vt/xwp5o0VQAx9aySPF6hU1A== + dependencies: + "@peculiar/asn1-schema" "^2.1.6" + "@peculiar/json-schema" "^1.1.12" + asn1js "^3.0.1" + pvtsutils "^1.3.2" + tslib "^2.4.0" + +webcrypto-shim@^0.1.4: + version "0.1.7" + resolved "https://registry.yarnpkg.com/webcrypto-shim/-/webcrypto-shim-0.1.7.tgz#da8be23061a0451cf23b424d4a9b61c10f091c12" + integrity sha512-JAvAQR5mRNRxZW2jKigWMjCMkjSdmP5cColRP1U/pTg69VgHXEi1orv5vVpJ55Zc5MIaPc1aaurzd9pjv2bveg== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -10948,3 +11279,8 @@ yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From c751e286aa11a1d2b9424ae23de5647efc5d536f Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 20 May 2022 09:41:32 +0200 Subject: [PATCH 005/125] fix: peer dependency for rn bbs signatures (#785) Signed-off-by: Timo Glastra --- packages/react-native/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 42390330cf..087554b902 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -44,6 +44,6 @@ "indy-sdk-react-native": "^0.2.2", "react-native-fs": "^2.18.0", "react-native-get-random-values": "^1.7.0", - "react-native-bbs-signatures": "0.1.0" + "@animo-id/react-native-bbs-signatures": "^0.1.0" } } From 38cb1065e6fbf46c676c7ad52e160b721cb1b4e6 Mon Sep 17 00:00:00 2001 From: Karim Stekelenburg Date: Fri, 20 May 2022 15:39:15 +0200 Subject: [PATCH 006/125] fix: invalid injection symbols in W3cCredService (#786) Signed-off-by: Karim --- packages/core/src/modules/vc/W3cCredentialService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 8935126083..103df55af5 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -14,6 +14,7 @@ import type { VerifyPresentationResult } from './models/presentation/VerifyPrese import jsonld, { documentLoaderNode, documentLoaderXhr } from '../../../types/jsonld' import vc from '../../../types/vc' +import { InjectionSymbols } from '../../constants' import { createWalletKeyPairClass } from '../../crypto/WalletKeyPair' import { deriveProof } from '../../crypto/signature-suites/bbs' import { AriesFrameworkError } from '../../error' @@ -38,7 +39,7 @@ export class W3cCredentialService { private suiteRegistry: SignatureSuiteRegistry public constructor( - @inject('Wallet') wallet: Wallet, + @inject(InjectionSymbols.Wallet) wallet: Wallet, w3cCredentialRepository: W3cCredentialRepository, didResolver: DidResolverService ) { From a769cb8d21c59cdc0d37db7179ecdd4e8dca4289 Mon Sep 17 00:00:00 2001 From: Karim Date: Tue, 24 May 2022 12:48:44 +0200 Subject: [PATCH 007/125] refactor: change hardcoded URLs to constants Signed-off-by: Karim --- .../core/src/modules/dids/domain/keyDidDocument.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/core/src/modules/dids/domain/keyDidDocument.ts b/packages/core/src/modules/dids/domain/keyDidDocument.ts index 893436aeb3..1ef38b2bd1 100644 --- a/packages/core/src/modules/dids/domain/keyDidDocument.ts +++ b/packages/core/src/modules/dids/domain/keyDidDocument.ts @@ -1,6 +1,8 @@ import type { VerificationMethod } from './verificationMethod/VerificationMethod' import { KeyType, Key } from '../../../crypto' +import { ED25519_SUITE_CONTEXT_URL_2018 } from '../../../crypto/signature-suites/ed25519/constants' +import { SECURITY_CONTEXT_BBS_URL, SECURITY_X25519_CONTEXT_URL } from '../../vc/constants' import { DidDocumentBuilder } from './DidDocumentBuilder' import { getBls12381g1VerificationMethod } from './key-type/bls12381g1' @@ -31,7 +33,7 @@ function getBls12381g1DidDoc(did: string, key: Key) { key, verificationMethod, }) - .addContext('https://w3id.org/security/bbs/v1') + .addContext(SECURITY_CONTEXT_BBS_URL) .build() } @@ -49,7 +51,7 @@ function getBls12381g1g2DidDoc(did: string, key: Key) { .addCapabilityInvocation(verificationMethod.id) } - return didDocumentBuilder.addContext('https://w3id.org/security/bbs/v1').build() + return didDocumentBuilder.addContext(SECURITY_CONTEXT_BBS_URL).build() } function getEd25519DidDoc(did: string, key: Key) { @@ -66,8 +68,8 @@ function getEd25519DidDoc(did: string, key: Key) { const didDocBuilder = getSignatureKeyBase({ did, key, verificationMethod }) didDocBuilder - .addContext('https://w3id.org/security/suites/ed25519-2018/v1') - .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addContext(ED25519_SUITE_CONTEXT_URL_2018) + .addContext(SECURITY_X25519_CONTEXT_URL) .addKeyAgreement(x25519VerificationMethod) return didDocBuilder.build() @@ -78,7 +80,7 @@ function getX25519DidDoc(did: string, key: Key) { const document = new DidDocumentBuilder(did) .addKeyAgreement(verificationMethod) - .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addContext(SECURITY_X25519_CONTEXT_URL) .build() return document @@ -92,7 +94,7 @@ function getBls12381g2DidDoc(did: string, key: Key) { key, verificationMethod, }) - .addContext('https://w3id.org/security/bbs/v1') + .addContext(SECURITY_CONTEXT_BBS_URL) .build() } From 550ea158bd028de8d1c3e5526ed854a98b86bc05 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 31 May 2022 09:18:41 +0200 Subject: [PATCH 008/125] refactor: move signatures suites to vc module (#801) Signed-off-by: Timo Glastra --- .../src/modules/dids/domain/keyDidDocument.ts | 2 +- .../modules/dids/methods/sov/SovDidResolver.ts | 2 +- .../core/src/modules/vc/SignatureSuiteRegistry.ts | 7 ++++--- .../core/src/modules/vc/W3cCredentialService.ts | 15 +++++++++------ .../vc/__tests__/W3cCredentialService.test.ts | 5 +++-- .../src/modules/vc/__tests__/documentLoader.ts | 8 ++------ .../{utils/jsonld.ts => modules/vc/jsonldUtil.ts} | 10 +++++----- .../modules/vc/libraries}/jsonld-signatures.ts | 4 ++++ .../{types => src/modules/vc/libraries}/jsonld.ts | 8 ++++++++ .../{types => src/modules/vc/libraries}/vc.ts | 4 ++++ .../models/credential/W3cVerifiableCredential.ts | 5 ++--- .../proof-purposes/CredentialIssuancePurpose.ts | 5 ++--- .../signature-suites/JwsLinkedDataSignature.ts | 10 +++++----- .../signature-suites/bbs/BbsBlsSignature2020.ts | 15 ++++++++------- .../bbs/BbsBlsSignatureProof2020.ts | 15 ++++++++------- .../vc}/signature-suites/bbs/deriveProof.ts | 11 ++++++----- .../vc}/signature-suites/bbs/index.ts | 0 .../signature-suites/bbs/types/CanonizeOptions.ts | 2 +- .../bbs/types/CreateProofOptions.ts | 6 +++--- .../bbs/types/CreateVerifyDataOptions.ts | 4 ++-- .../bbs/types/DeriveProofOptions.ts | 4 ++-- .../bbs/types/DidDocumentPublicKey.ts | 0 .../bbs/types/GetProofsOptions.ts | 4 ++-- .../signature-suites/bbs/types/GetProofsResult.ts | 2 +- .../signature-suites/bbs/types/GetTypeOptions.ts | 2 +- .../vc}/signature-suites/bbs/types/JsonWebKey.ts | 0 .../signature-suites/bbs/types/KeyPairOptions.ts | 0 .../signature-suites/bbs/types/KeyPairSigner.ts | 0 .../signature-suites/bbs/types/KeyPairVerifier.ts | 0 .../bbs/types/SignatureSuiteOptions.ts | 4 ++-- .../bbs/types/SuiteSignOptions.ts | 4 ++-- .../bbs/types/VerifyProofOptions.ts | 6 +++--- .../bbs/types/VerifyProofResult.ts | 0 .../bbs/types/VerifySignatureOptions.ts | 4 ++-- .../vc}/signature-suites/bbs/types/index.ts | 0 .../ed25519/Ed25519Signature2018.ts | 9 +++++---- .../vc}/signature-suites/ed25519/constants.ts | 0 .../vc}/signature-suites/ed25519/context.ts | 0 .../vc}/signature-suites/index.ts | 0 packages/core/src/utils/index.ts | 3 ++- packages/core/src/utils/validators.ts | 2 ++ 41 files changed, 102 insertions(+), 80 deletions(-) rename packages/core/src/{utils/jsonld.ts => modules/vc/jsonldUtil.ts} (95%) rename packages/core/{types => src/modules/vc/libraries}/jsonld-signatures.ts (72%) rename packages/core/{types => src/modules/vc/libraries}/jsonld.ts (72%) rename packages/core/{types => src/modules/vc/libraries}/vc.ts (71%) rename packages/core/src/{crypto => modules/vc}/signature-suites/JwsLinkedDataSignature.ts (97%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/BbsBlsSignature2020.ts (97%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/BbsBlsSignatureProof2020.ts (97%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/deriveProof.ts (92%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/index.ts (100%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/CanonizeOptions.ts (94%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/CreateProofOptions.ts (85%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/CreateVerifyDataOptions.ts (90%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/DeriveProofOptions.ts (91%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/DidDocumentPublicKey.ts (100%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/GetProofsOptions.ts (91%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/GetProofsResult.ts (93%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/GetTypeOptions.ts (93%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/JsonWebKey.ts (100%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/KeyPairOptions.ts (100%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/KeyPairSigner.ts (100%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/KeyPairVerifier.ts (100%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/SignatureSuiteOptions.ts (92%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/SuiteSignOptions.ts (90%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/VerifyProofOptions.ts (83%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/VerifyProofResult.ts (100%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/VerifySignatureOptions.ts (94%) rename packages/core/src/{crypto => modules/vc}/signature-suites/bbs/types/index.ts (100%) rename packages/core/src/{crypto => modules/vc}/signature-suites/ed25519/Ed25519Signature2018.ts (97%) rename packages/core/src/{crypto => modules/vc}/signature-suites/ed25519/constants.ts (100%) rename packages/core/src/{crypto => modules/vc}/signature-suites/ed25519/context.ts (100%) rename packages/core/src/{crypto => modules/vc}/signature-suites/index.ts (100%) diff --git a/packages/core/src/modules/dids/domain/keyDidDocument.ts b/packages/core/src/modules/dids/domain/keyDidDocument.ts index 1ef38b2bd1..537cb97d3d 100644 --- a/packages/core/src/modules/dids/domain/keyDidDocument.ts +++ b/packages/core/src/modules/dids/domain/keyDidDocument.ts @@ -1,8 +1,8 @@ import type { VerificationMethod } from './verificationMethod/VerificationMethod' import { KeyType, Key } from '../../../crypto' -import { ED25519_SUITE_CONTEXT_URL_2018 } from '../../../crypto/signature-suites/ed25519/constants' import { SECURITY_CONTEXT_BBS_URL, SECURITY_X25519_CONTEXT_URL } from '../../vc/constants' +import { ED25519_SUITE_CONTEXT_URL_2018 } from '../../vc/signature-suites/ed25519/constants' import { DidDocumentBuilder } from './DidDocumentBuilder' import { getBls12381g1VerificationMethod } from './key-type/bls12381g1' diff --git a/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts b/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts index 694987c059..5f02c8dd4c 100644 --- a/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts +++ b/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts @@ -4,10 +4,10 @@ import type { ParsedDid, DidResolutionResult } from '../../types' import { convertPublicKeyToX25519 } from '@stablelib/ed25519' -import { ED25519_SUITE_CONTEXT_URL_2018 } from '../../../../crypto/signature-suites/ed25519/constants' import { TypedArrayEncoder } from '../../../../utils/TypedArrayEncoder' import { getFullVerkey } from '../../../../utils/did' import { SECURITY_X25519_CONTEXT_URL } from '../../../vc/constants' +import { ED25519_SUITE_CONTEXT_URL_2018 } from '../../../vc/signature-suites/ed25519/constants' import { DidDocumentService } from '../../domain' import { DidDocumentBuilder } from '../../domain/DidDocumentBuilder' import { DidCommV1Service } from '../../domain/service/DidCommV1Service' diff --git a/packages/core/src/modules/vc/SignatureSuiteRegistry.ts b/packages/core/src/modules/vc/SignatureSuiteRegistry.ts index 469d0a4aaf..0d5404aa44 100644 --- a/packages/core/src/modules/vc/SignatureSuiteRegistry.ts +++ b/packages/core/src/modules/vc/SignatureSuiteRegistry.ts @@ -1,9 +1,10 @@ -import { suites } from '../../../types/jsonld-signatures' import { KeyType } from '../../crypto' -import { Ed25519Signature2018 } from '../../crypto/signature-suites' -import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from '../../crypto/signature-suites/bbs' import { AriesFrameworkError } from '../../error' +import { suites } from './libraries/jsonld-signatures' +import { Ed25519Signature2018 } from './signature-suites' +import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from './signature-suites/bbs' + const LinkedDataSignature = suites.LinkedDataSignature export interface SuiteInfo { diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 103df55af5..c1bf89d930 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -1,5 +1,5 @@ import type { Key } from '../../crypto/Key' -import type { DocumentLoaderResult } from '../../utils' +import type { DocumentLoaderResult } from './jsonldUtil' import type { W3cVerifyCredentialResult } from './models' import type { CreatePresentationOptions, @@ -12,24 +12,27 @@ import type { } from './models/W3cCredentialServiceOptions' import type { VerifyPresentationResult } from './models/presentation/VerifyPresentationResult' -import jsonld, { documentLoaderNode, documentLoaderXhr } from '../../../types/jsonld' -import vc from '../../../types/vc' +import { inject } from 'tsyringe' + import { InjectionSymbols } from '../../constants' import { createWalletKeyPairClass } from '../../crypto/WalletKeyPair' -import { deriveProof } from '../../crypto/signature-suites/bbs' import { AriesFrameworkError } from '../../error' -import { inject, injectable } from '../../plugins' -import { JsonTransformer, orArrayToArray, w3cDate } from '../../utils' +import { injectable } from '../../plugins' +import { JsonTransformer } from '../../utils' import { isNodeJS, isReactNative } from '../../utils/environment' import { Wallet } from '../../wallet' import { DidResolverService, VerificationMethod } from '../dids' import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type' import { SignatureSuiteRegistry } from './SignatureSuiteRegistry' +import { orArrayToArray, w3cDate } from './jsonldUtil' +import jsonld, { documentLoaderNode, documentLoaderXhr } from './libraries/jsonld' +import vc from './libraries/vc' import { W3cVerifiableCredential } from './models' import { W3cPresentation } from './models/presentation/W3Presentation' import { W3cVerifiablePresentation } from './models/presentation/W3cVerifiablePresentation' import { W3cCredentialRecord, W3cCredentialRepository } from './repository' +import { deriveProof } from './signature-suites/bbs' @injectable() export class W3cCredentialService { diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index 401d158e51..20a28c5e19 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -1,16 +1,17 @@ import type { AgentConfig } from '../../../agent/AgentConfig' import { getAgentConfig } from '../../../../tests/helpers' -import { purposes } from '../../../../types/jsonld-signatures' import { KeyType } from '../../../crypto' import { Key } from '../../../crypto/Key' -import { JsonTransformer, orArrayToArray } from '../../../utils' +import { JsonTransformer } from '../../../utils/JsonTransformer' import { IndyWallet } from '../../../wallet/IndyWallet' import { WalletError } from '../../../wallet/error' import { DidKey, DidResolverService } from '../../dids' import { DidRepository } from '../../dids/repository' import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' import { W3cCredentialService } from '../W3cCredentialService' +import { orArrayToArray } from '../jsonldUtil' +import { purposes } from '../libraries/jsonld-signatures' import { W3cCredential, W3cVerifiableCredential } from '../models' import { LinkedDataProof } from '../models/LinkedDataProof' import { W3cPresentation } from '../models/presentation/W3Presentation' diff --git a/packages/core/src/modules/vc/__tests__/documentLoader.ts b/packages/core/src/modules/vc/__tests__/documentLoader.ts index 91a76bf879..6816beded2 100644 --- a/packages/core/src/modules/vc/__tests__/documentLoader.ts +++ b/packages/core/src/modules/vc/__tests__/documentLoader.ts @@ -1,11 +1,7 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -// eslint-disable-next-line import/no-extraneous-dependencies - import type { JsonObject } from '../../../types' -import type { DocumentLoaderResult } from '../../../utils' +import type { DocumentLoaderResult } from '../jsonldUtil' -import jsonld from '../../../../types/jsonld' +import jsonld from '../libraries/jsonld' import { BBS_V1, EXAMPLES_V1, ODRL, SCHEMA_ORG, VACCINATION_V1 } from './contexts' import { X25519_V1 } from './contexts/X25519_v1' diff --git a/packages/core/src/utils/jsonld.ts b/packages/core/src/modules/vc/jsonldUtil.ts similarity index 95% rename from packages/core/src/utils/jsonld.ts rename to packages/core/src/modules/vc/jsonldUtil.ts index 86c1ba7aea..1581291abf 100644 --- a/packages/core/src/utils/jsonld.ts +++ b/packages/core/src/modules/vc/jsonldUtil.ts @@ -1,9 +1,9 @@ -import type { GetProofsOptions, GetProofsResult, GetTypeOptions } from '../crypto/signature-suites/bbs' -import type { JsonObject, JsonValue } from '../types' -import type { SingleOrArray } from './type' +import type { JsonObject, JsonValue } from '../../types' +import type { SingleOrArray } from '../../utils/type' +import type { GetProofsOptions, GetProofsResult, GetTypeOptions } from './signature-suites/bbs' -import jsonld from '../../types/jsonld' -import { SECURITY_CONTEXT_URL } from '../modules/vc/constants' +import { SECURITY_CONTEXT_URL } from './constants' +import jsonld from './libraries/jsonld' export type JsonLdDoc = Record export interface VerificationMethod extends JsonObject { diff --git a/packages/core/types/jsonld-signatures.ts b/packages/core/src/modules/vc/libraries/jsonld-signatures.ts similarity index 72% rename from packages/core/types/jsonld-signatures.ts rename to packages/core/src/modules/vc/libraries/jsonld-signatures.ts index c930e2bfe6..6257b8e2db 100644 --- a/packages/core/types/jsonld-signatures.ts +++ b/packages/core/src/modules/vc/libraries/jsonld-signatures.ts @@ -1,7 +1,11 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + import { suites as JsonLdSuites, purposes as JsonLdPurposes, constants as JsonLdConstants, + // No type definitions available for this library + // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore } from '@digitalcredentials/jsonld-signatures' diff --git a/packages/core/types/jsonld.ts b/packages/core/src/modules/vc/libraries/jsonld.ts similarity index 72% rename from packages/core/types/jsonld.ts rename to packages/core/src/modules/vc/libraries/jsonld.ts index 3e54d97b10..4e95767af4 100644 --- a/packages/core/types/jsonld.ts +++ b/packages/core/src/modules/vc/libraries/jsonld.ts @@ -1,7 +1,15 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +// No type definitions available for this library +// eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore import jsonld from '@digitalcredentials/jsonld' +// No type definitions available for this library +// eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore import nodeDocumentLoader from '@digitalcredentials/jsonld/lib/documentLoaders/node' +// No type definitions available for this library +// eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore import xhrDocumentLoader from '@digitalcredentials/jsonld/lib/documentLoaders/xhr' diff --git a/packages/core/types/vc.ts b/packages/core/src/modules/vc/libraries/vc.ts similarity index 71% rename from packages/core/types/vc.ts rename to packages/core/src/modules/vc/libraries/vc.ts index ca6196528d..21c4a38df4 100644 --- a/packages/core/types/vc.ts +++ b/packages/core/src/modules/vc/libraries/vc.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +// No type definitions available for this package +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import vc from '@digitalcredentials/vc' diff --git a/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts b/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts index e2d1b1afe3..4fbce4e41e 100644 --- a/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts @@ -3,9 +3,8 @@ import type { W3cCredentialOptions } from './W3cCredential' import { instanceToPlain, plainToInstance, Transform, TransformationType } from 'class-transformer' -import { orArrayToArray } from '../../../../utils' -import { SingleOrArray } from '../../../../utils/type' -import { IsInstanceOrArrayOfInstances } from '../../../../utils/validators' +import { IsInstanceOrArrayOfInstances, SingleOrArray } from '../../../../utils' +import { orArrayToArray } from '../../jsonldUtil' import { LinkedDataProof, LinkedDataProofTransformer } from '../LinkedDataProof' import { W3cCredential } from './W3cCredential' diff --git a/packages/core/src/modules/vc/proof-purposes/CredentialIssuancePurpose.ts b/packages/core/src/modules/vc/proof-purposes/CredentialIssuancePurpose.ts index c4127374e7..305a017fe8 100644 --- a/packages/core/src/modules/vc/proof-purposes/CredentialIssuancePurpose.ts +++ b/packages/core/src/modules/vc/proof-purposes/CredentialIssuancePurpose.ts @@ -1,8 +1,7 @@ import type { JsonObject } from '../../../types' -import type { DocumentLoader, Proof } from '../../../utils' +import type { Proof, DocumentLoader } from '../jsonldUtil' -import jsonld from '../../../../types/jsonld' -import { suites, purposes } from '../../../../types/jsonld-signatures' +import { suites, purposes } from '../libraries/jsonld-signatures' const AssertionProofPurpose = purposes.AssertionProofPurpose const LinkedDataProof = suites.LinkedDataProof diff --git a/packages/core/src/crypto/signature-suites/JwsLinkedDataSignature.ts b/packages/core/src/modules/vc/signature-suites/JwsLinkedDataSignature.ts similarity index 97% rename from packages/core/src/crypto/signature-suites/JwsLinkedDataSignature.ts rename to packages/core/src/modules/vc/signature-suites/JwsLinkedDataSignature.ts index e062d503da..226c0e5ecc 100644 --- a/packages/core/src/crypto/signature-suites/JwsLinkedDataSignature.ts +++ b/packages/core/src/modules/vc/signature-suites/JwsLinkedDataSignature.ts @@ -1,12 +1,12 @@ /*! * Copyright (c) 2020-2021 Digital Bazaar, Inc. All rights reserved. */ -import type { DocumentLoader, Proof, VerificationMethod } from '../../utils' -import type { LdKeyPair } from '../LdKeyPair' +import type { LdKeyPair } from '../../../crypto/LdKeyPair' +import type { DocumentLoader, Proof, VerificationMethod } from '../jsonldUtil' -import { suites } from '../../../types/jsonld-signatures' -import { AriesFrameworkError } from '../../error' -import { TypedArrayEncoder, JsonEncoder } from '../../utils' +import { AriesFrameworkError } from '../../../error' +import { TypedArrayEncoder, JsonEncoder } from '../../../utils' +import { suites } from '../libraries/jsonld-signatures' const LinkedDataSignature = suites.LinkedDataSignature export interface JwsLinkedDataSignatureOptions { diff --git a/packages/core/src/crypto/signature-suites/bbs/BbsBlsSignature2020.ts b/packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignature2020.ts similarity index 97% rename from packages/core/src/crypto/signature-suites/bbs/BbsBlsSignature2020.ts rename to packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignature2020.ts index 094d697e57..789260e8c6 100644 --- a/packages/core/src/crypto/signature-suites/bbs/BbsBlsSignature2020.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignature2020.ts @@ -11,8 +11,8 @@ * limitations under the License. */ -import type { JsonObject } from '../../../types' -import type { DocumentLoader, Proof, VerificationMethod } from '../../../utils' +import type { JsonObject } from '../../../../types' +import type { DocumentLoader, Proof, VerificationMethod } from '../../jsonldUtil' import type { SignatureSuiteOptions, CreateProofOptions, @@ -23,11 +23,12 @@ import type { SuiteSignOptions, } from './types' -import jsonld from '../../../../types/jsonld' -import { suites } from '../../../../types/jsonld-signatures' -import { AriesFrameworkError } from '../../../error' -import { SECURITY_CONTEXT_BBS_URL, SECURITY_CONTEXT_URL } from '../../../modules/vc/constants' -import { w3cDate, TypedArrayEncoder } from '../../../utils' +import { AriesFrameworkError } from '../../../../error' +import { TypedArrayEncoder } from '../../../../utils' +import { SECURITY_CONTEXT_BBS_URL, SECURITY_CONTEXT_URL } from '../../constants' +import { w3cDate } from '../../jsonldUtil' +import jsonld from '../../libraries/jsonld' +import { suites } from '../../libraries/jsonld-signatures' /** * A BBS+ signature suite for use with BLS12-381 key pairs diff --git a/packages/core/src/crypto/signature-suites/bbs/BbsBlsSignatureProof2020.ts b/packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignatureProof2020.ts similarity index 97% rename from packages/core/src/crypto/signature-suites/bbs/BbsBlsSignatureProof2020.ts rename to packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignatureProof2020.ts index c785986435..2eec683b1f 100644 --- a/packages/core/src/crypto/signature-suites/bbs/BbsBlsSignatureProof2020.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignatureProof2020.ts @@ -11,8 +11,9 @@ * limitations under the License. */ -import type { JsonObject } from '../../../types' -import type { DocumentLoader, Proof } from '../../../utils' +import type { JsonObject } from '../../../../types' +import type { Proof } from '../../jsonldUtil' +import type { DocumentLoader } from '../../libraries/jsonld' import type { DeriveProofOptions, VerifyProofOptions, CreateVerifyDataOptions, CanonizeOptions } from './types' import type { VerifyProofResult } from './types/VerifyProofResult' @@ -20,11 +21,11 @@ import { blsCreateProof, blsVerifyProof } from '@mattrglobal/bbs-signatures' import { Bls12381G2KeyPair } from '@mattrglobal/bls12381-key-pair' import { randomBytes } from '@stablelib/random' -import jsonld from '../../../../types/jsonld' -import { suites } from '../../../../types/jsonld-signatures' -import { AriesFrameworkError } from '../../../error' -import { SECURITY_CONTEXT_URL } from '../../../modules/vc/constants' -import { TypedArrayEncoder } from '../../../utils' +import { AriesFrameworkError } from '../../../../error' +import { TypedArrayEncoder } from '../../../../utils' +import { SECURITY_CONTEXT_URL } from '../../constants' +import jsonld from '../../libraries/jsonld' +import { suites } from '../../libraries/jsonld-signatures' import { BbsBlsSignature2020 } from './BbsBlsSignature2020' diff --git a/packages/core/src/crypto/signature-suites/bbs/deriveProof.ts b/packages/core/src/modules/vc/signature-suites/bbs/deriveProof.ts similarity index 92% rename from packages/core/src/crypto/signature-suites/bbs/deriveProof.ts rename to packages/core/src/modules/vc/signature-suites/bbs/deriveProof.ts index 012b15d323..de0ac5a67a 100644 --- a/packages/core/src/crypto/signature-suites/bbs/deriveProof.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/deriveProof.ts @@ -11,12 +11,13 @@ * limitations under the License. */ -import type { JsonObject } from '../../../types' +import type { JsonObject } from '../../../../types' -import jsonld from '../../../../types/jsonld' -import { SECURITY_PROOF_URL } from '../../../modules/vc/constants' -import { W3cVerifiableCredential } from '../../../modules/vc/models' -import { JsonTransformer, getProofs, getTypeInfo } from '../../../utils' +import { JsonTransformer } from '../../../../utils' +import { SECURITY_PROOF_URL } from '../../constants' +import { getProofs, getTypeInfo } from '../../jsonldUtil' +import jsonld from '../../libraries/jsonld' +import { W3cVerifiableCredential } from '../../models' /** * Derives a proof from a document featuring a supported linked data proof diff --git a/packages/core/src/crypto/signature-suites/bbs/index.ts b/packages/core/src/modules/vc/signature-suites/bbs/index.ts similarity index 100% rename from packages/core/src/crypto/signature-suites/bbs/index.ts rename to packages/core/src/modules/vc/signature-suites/bbs/index.ts diff --git a/packages/core/src/crypto/signature-suites/bbs/types/CanonizeOptions.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/CanonizeOptions.ts similarity index 94% rename from packages/core/src/crypto/signature-suites/bbs/types/CanonizeOptions.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/CanonizeOptions.ts index 856baecbde..551dc0f777 100644 --- a/packages/core/src/crypto/signature-suites/bbs/types/CanonizeOptions.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/types/CanonizeOptions.ts @@ -11,7 +11,7 @@ * limitations under the License. */ -import type { DocumentLoader } from '../../../../utils' +import type { DocumentLoader } from '../../../jsonldUtil' /** * Options for canonizing a document diff --git a/packages/core/src/crypto/signature-suites/bbs/types/CreateProofOptions.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/CreateProofOptions.ts similarity index 85% rename from packages/core/src/crypto/signature-suites/bbs/types/CreateProofOptions.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/CreateProofOptions.ts index 60e06c0185..38f54dbceb 100644 --- a/packages/core/src/crypto/signature-suites/bbs/types/CreateProofOptions.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/types/CreateProofOptions.ts @@ -11,9 +11,9 @@ * limitations under the License. */ -import type { ProofPurpose } from '../../../../modules/vc/proof-purposes/ProofPurpose' -import type { JsonObject } from '../../../../types' -import type { DocumentLoader } from '../../../../utils' +import type { JsonObject } from '../../../../../types' +import type { DocumentLoader } from '../../../jsonldUtil' +import type { ProofPurpose } from '../../../proof-purposes/ProofPurpose' /** * Options for creating a proof diff --git a/packages/core/src/crypto/signature-suites/bbs/types/CreateVerifyDataOptions.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/CreateVerifyDataOptions.ts similarity index 90% rename from packages/core/src/crypto/signature-suites/bbs/types/CreateVerifyDataOptions.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/CreateVerifyDataOptions.ts index ba493b44a2..204dafd5e0 100644 --- a/packages/core/src/crypto/signature-suites/bbs/types/CreateVerifyDataOptions.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/types/CreateVerifyDataOptions.ts @@ -11,8 +11,8 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../types' -import type { DocumentLoader } from '../../../../utils' +import type { JsonObject } from '../../../../../types' +import type { DocumentLoader } from '../../../jsonldUtil' /** * Options for creating a proof diff --git a/packages/core/src/crypto/signature-suites/bbs/types/DeriveProofOptions.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/DeriveProofOptions.ts similarity index 91% rename from packages/core/src/crypto/signature-suites/bbs/types/DeriveProofOptions.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/DeriveProofOptions.ts index 68a1322e5f..c726180f9d 100644 --- a/packages/core/src/crypto/signature-suites/bbs/types/DeriveProofOptions.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/types/DeriveProofOptions.ts @@ -11,8 +11,8 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../types' -import type { DocumentLoader, Proof } from '../../../../utils' +import type { JsonObject } from '../../../../../types' +import type { DocumentLoader, Proof } from '../../../jsonldUtil' /** * Options for creating a proof diff --git a/packages/core/src/crypto/signature-suites/bbs/types/DidDocumentPublicKey.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/DidDocumentPublicKey.ts similarity index 100% rename from packages/core/src/crypto/signature-suites/bbs/types/DidDocumentPublicKey.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/DidDocumentPublicKey.ts diff --git a/packages/core/src/crypto/signature-suites/bbs/types/GetProofsOptions.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/GetProofsOptions.ts similarity index 91% rename from packages/core/src/crypto/signature-suites/bbs/types/GetProofsOptions.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/GetProofsOptions.ts index 5dae685de7..41b9fa935f 100644 --- a/packages/core/src/crypto/signature-suites/bbs/types/GetProofsOptions.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/types/GetProofsOptions.ts @@ -11,8 +11,8 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../types' -import type { DocumentLoader } from '../../../../utils' +import type { JsonObject } from '../../../../../types' +import type { DocumentLoader } from '../../../jsonldUtil' /** * Options for getting a proof from a JSON-LD document diff --git a/packages/core/src/crypto/signature-suites/bbs/types/GetProofsResult.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/GetProofsResult.ts similarity index 93% rename from packages/core/src/crypto/signature-suites/bbs/types/GetProofsResult.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/GetProofsResult.ts index d96eb8b814..6e24011b74 100644 --- a/packages/core/src/crypto/signature-suites/bbs/types/GetProofsResult.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/types/GetProofsResult.ts @@ -11,7 +11,7 @@ * limitations under the License. */ -import type { JsonArray, JsonObject } from '../../../../types' +import type { JsonArray, JsonObject } from '../../../../../types' /** * Result for getting proofs from a JSON-LD document diff --git a/packages/core/src/crypto/signature-suites/bbs/types/GetTypeOptions.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/GetTypeOptions.ts similarity index 93% rename from packages/core/src/crypto/signature-suites/bbs/types/GetTypeOptions.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/GetTypeOptions.ts index 5dd396da4b..0dd40cb546 100644 --- a/packages/core/src/crypto/signature-suites/bbs/types/GetTypeOptions.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/types/GetTypeOptions.ts @@ -11,7 +11,7 @@ * limitations under the License. */ -import type { DocumentLoader } from '../../../../utils' +import type { DocumentLoader } from '../../../jsonldUtil' /** * Options for getting the type from a JSON-LD document diff --git a/packages/core/src/crypto/signature-suites/bbs/types/JsonWebKey.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/JsonWebKey.ts similarity index 100% rename from packages/core/src/crypto/signature-suites/bbs/types/JsonWebKey.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/JsonWebKey.ts diff --git a/packages/core/src/crypto/signature-suites/bbs/types/KeyPairOptions.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/KeyPairOptions.ts similarity index 100% rename from packages/core/src/crypto/signature-suites/bbs/types/KeyPairOptions.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/KeyPairOptions.ts diff --git a/packages/core/src/crypto/signature-suites/bbs/types/KeyPairSigner.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/KeyPairSigner.ts similarity index 100% rename from packages/core/src/crypto/signature-suites/bbs/types/KeyPairSigner.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/KeyPairSigner.ts diff --git a/packages/core/src/crypto/signature-suites/bbs/types/KeyPairVerifier.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/KeyPairVerifier.ts similarity index 100% rename from packages/core/src/crypto/signature-suites/bbs/types/KeyPairVerifier.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/KeyPairVerifier.ts diff --git a/packages/core/src/crypto/signature-suites/bbs/types/SignatureSuiteOptions.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/SignatureSuiteOptions.ts similarity index 92% rename from packages/core/src/crypto/signature-suites/bbs/types/SignatureSuiteOptions.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/SignatureSuiteOptions.ts index d3f2ba95ba..34209afdda 100644 --- a/packages/core/src/crypto/signature-suites/bbs/types/SignatureSuiteOptions.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/types/SignatureSuiteOptions.ts @@ -11,8 +11,8 @@ * limitations under the License. */ -import type { JsonArray } from '../../../../types' -import type { LdKeyPair } from '../../../LdKeyPair' +import type { LdKeyPair } from '../../../../../crypto/LdKeyPair' +import type { JsonArray } from '../../../../../types' import type { KeyPairSigner } from './KeyPairSigner' import type { Bls12381G2KeyPair } from '@mattrglobal/bls12381-key-pair' diff --git a/packages/core/src/crypto/signature-suites/bbs/types/SuiteSignOptions.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/SuiteSignOptions.ts similarity index 90% rename from packages/core/src/crypto/signature-suites/bbs/types/SuiteSignOptions.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/SuiteSignOptions.ts index 9bed5d5644..a754325972 100644 --- a/packages/core/src/crypto/signature-suites/bbs/types/SuiteSignOptions.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/types/SuiteSignOptions.ts @@ -11,8 +11,8 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../types' -import type { DocumentLoader } from '../../../../utils' +import type { JsonObject } from '../../../../../types' +import type { DocumentLoader } from '../../../jsonldUtil' /** * Options for signing using a signature suite diff --git a/packages/core/src/crypto/signature-suites/bbs/types/VerifyProofOptions.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/VerifyProofOptions.ts similarity index 83% rename from packages/core/src/crypto/signature-suites/bbs/types/VerifyProofOptions.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/VerifyProofOptions.ts index ba3538e7d4..4bf5f0c953 100644 --- a/packages/core/src/crypto/signature-suites/bbs/types/VerifyProofOptions.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/types/VerifyProofOptions.ts @@ -11,9 +11,9 @@ * limitations under the License. */ -import type { ProofPurpose } from '../../../../modules/vc/proof-purposes/ProofPurpose' -import type { JsonObject } from '../../../../types' -import type { DocumentLoader, Proof } from '../../../../utils' +import type { JsonObject } from '../../../../../types' +import type { DocumentLoader, Proof } from '../../../jsonldUtil' +import type { ProofPurpose } from '../../../proof-purposes/ProofPurpose' /** * Options for verifying a proof diff --git a/packages/core/src/crypto/signature-suites/bbs/types/VerifyProofResult.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/VerifyProofResult.ts similarity index 100% rename from packages/core/src/crypto/signature-suites/bbs/types/VerifyProofResult.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/VerifyProofResult.ts diff --git a/packages/core/src/crypto/signature-suites/bbs/types/VerifySignatureOptions.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/VerifySignatureOptions.ts similarity index 94% rename from packages/core/src/crypto/signature-suites/bbs/types/VerifySignatureOptions.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/VerifySignatureOptions.ts index 02eb2c54b1..a1597b59e8 100644 --- a/packages/core/src/crypto/signature-suites/bbs/types/VerifySignatureOptions.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/types/VerifySignatureOptions.ts @@ -11,8 +11,8 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../types' -import type { DocumentLoader, Proof, VerificationMethod } from '../../../../utils' +import type { JsonObject } from '../../../../../types' +import type { DocumentLoader, Proof, VerificationMethod } from '../../../jsonldUtil' /** * Options for verifying a signature diff --git a/packages/core/src/crypto/signature-suites/bbs/types/index.ts b/packages/core/src/modules/vc/signature-suites/bbs/types/index.ts similarity index 100% rename from packages/core/src/crypto/signature-suites/bbs/types/index.ts rename to packages/core/src/modules/vc/signature-suites/bbs/types/index.ts diff --git a/packages/core/src/crypto/signature-suites/ed25519/Ed25519Signature2018.ts b/packages/core/src/modules/vc/signature-suites/ed25519/Ed25519Signature2018.ts similarity index 97% rename from packages/core/src/crypto/signature-suites/ed25519/Ed25519Signature2018.ts rename to packages/core/src/modules/vc/signature-suites/ed25519/Ed25519Signature2018.ts index d32f747056..18eb3321dc 100644 --- a/packages/core/src/crypto/signature-suites/ed25519/Ed25519Signature2018.ts +++ b/packages/core/src/modules/vc/signature-suites/ed25519/Ed25519Signature2018.ts @@ -1,9 +1,10 @@ -import type { DocumentLoader, JsonLdDoc, Proof, VerificationMethod } from '../../../utils' +import type { DocumentLoader, JsonLdDoc, Proof, VerificationMethod } from '../../jsonldUtil' import type { JwsLinkedDataSignatureOptions } from '../JwsLinkedDataSignature' -import jsonld from '../../../../types/jsonld' -import { CREDENTIALS_CONTEXT_V1_URL, SECURITY_CONTEXT_URL } from '../../../modules/vc/constants' -import { TypedArrayEncoder, MultiBaseEncoder, _includesContext } from '../../../utils' +import { MultiBaseEncoder, TypedArrayEncoder } from '../../../../utils' +import { CREDENTIALS_CONTEXT_V1_URL, SECURITY_CONTEXT_URL } from '../../constants' +import { _includesContext } from '../../jsonldUtil' +import jsonld from '../../libraries/jsonld' import { JwsLinkedDataSignature } from '../JwsLinkedDataSignature' import { ED25519_SUITE_CONTEXT_URL_2018, ED25519_SUITE_CONTEXT_URL_2020 } from './constants' diff --git a/packages/core/src/crypto/signature-suites/ed25519/constants.ts b/packages/core/src/modules/vc/signature-suites/ed25519/constants.ts similarity index 100% rename from packages/core/src/crypto/signature-suites/ed25519/constants.ts rename to packages/core/src/modules/vc/signature-suites/ed25519/constants.ts diff --git a/packages/core/src/crypto/signature-suites/ed25519/context.ts b/packages/core/src/modules/vc/signature-suites/ed25519/context.ts similarity index 100% rename from packages/core/src/crypto/signature-suites/ed25519/context.ts rename to packages/core/src/modules/vc/signature-suites/ed25519/context.ts diff --git a/packages/core/src/crypto/signature-suites/index.ts b/packages/core/src/modules/vc/signature-suites/index.ts similarity index 100% rename from packages/core/src/crypto/signature-suites/index.ts rename to packages/core/src/modules/vc/signature-suites/index.ts diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 318ad5d39f..95ebc0b554 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -9,4 +9,5 @@ export * from './regex' export * from './indyProofRequest' export * from './VarintEncoder' export * from './Hasher' -export * from './jsonld' +export * from './validators' +export * from './type' diff --git a/packages/core/src/utils/validators.ts b/packages/core/src/utils/validators.ts index 47997bc482..e81c5543bf 100644 --- a/packages/core/src/utils/validators.ts +++ b/packages/core/src/utils/validators.ts @@ -4,6 +4,7 @@ import type { ValidationOptions } from 'class-validator' import { isString, ValidateBy, isInstance, buildMessage } from 'class-validator' export interface IsInstanceOrArrayOfInstancesValidationOptions extends ValidationOptions { + // eslint-disable-next-line @typescript-eslint/no-explicit-any classType: new (...args: any[]) => any } @@ -60,6 +61,7 @@ export function IsInstanceOrArrayOfInstances( ) } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function isStringArray(value: any): value is string[] { return Array.isArray(value) && value.every((v) => typeof v === 'string') } From b47cfcba1450cd1d6839bf8192d977bfe33f1bb0 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 6 Jul 2022 16:37:57 +0200 Subject: [PATCH 009/125] refactor!: add agent context (#920) Signed-off-by: Timo Glastra BREAKING CHANGE: To make AFJ multi-tenancy ready, all services and repositories have been made stateless. A new `AgentContext` is introduced that holds the current context, which is passed to each method call. The public API hasn't been affected, but due to the large impact of this change it is marked as breaking. --- packages/core/src/agent/Agent.ts | 49 ++- packages/core/src/agent/AgentConfig.ts | 8 - packages/core/src/agent/AgentContext.ts | 32 ++ packages/core/src/agent/Dispatcher.ts | 20 +- packages/core/src/agent/EnvelopeService.ts | 35 +- packages/core/src/agent/EventEmitter.ts | 26 +- packages/core/src/agent/MessageReceiver.ts | 78 ++-- packages/core/src/agent/MessageSender.ts | 107 +++-- .../core/src/agent/__tests__/Agent.test.ts | 9 +- .../src/agent/__tests__/Dispatcher.test.ts | 17 +- .../src/agent/__tests__/MessageSender.test.ts | 50 ++- packages/core/src/agent/index.ts | 1 + .../src/agent/models/InboundMessageContext.ts | 6 +- packages/core/src/cache/PersistedLruCache.ts | 26 +- .../cache/__tests__/PersistedLruCache.test.ts | 38 +- packages/core/src/constants.ts | 5 +- packages/core/src/crypto/JwsService.ts | 22 +- .../src/crypto/__tests__/JwsService.test.ts | 21 +- .../signature/SignatureDecoratorUtils.test.ts | 2 +- packages/core/src/index.ts | 1 + .../basic-messages/BasicMessagesModule.ts | 14 +- .../__tests__/BasicMessageService.test.ts | 75 ++-- .../services/BasicMessageService.ts | 26 +- .../modules/connections/ConnectionsModule.ts | 102 ++--- .../connections/DidExchangeProtocol.ts | 75 ++-- .../__tests__/ConnectionService.test.ts | 98 +++-- .../handlers/ConnectionRequestHandler.ts | 21 +- .../handlers/ConnectionResponseHandler.ts | 20 +- .../handlers/DidExchangeCompleteHandler.ts | 7 +- .../handlers/DidExchangeRequestHandler.ts | 24 +- .../handlers/DidExchangeResponseHandler.ts | 28 +- .../handlers/TrustPingMessageHandler.ts | 2 +- .../repository/ConnectionRepository.ts | 12 +- .../connections/services/ConnectionService.ts | 179 ++++---- .../modules/credentials/CredentialsModule.ts | 134 +++--- .../formats/CredentialFormatService.ts | 49 ++- .../indy/IndyCredentialFormatService.ts | 192 +++++---- .../V1RevocationNotificationHandler.ts | 2 +- .../V2RevocationNotificationHandler.ts | 2 +- .../services/RevocationNotificationService.ts | 22 +- .../RevocationNotificationService.test.ts | 27 +- .../protocol/v1/V1CredentialService.ts | 395 ++++++++++-------- .../__tests__/V1CredentialServiceCred.test.ts | 139 +++--- .../V1CredentialServiceProposeOffer.test.ts | 38 +- .../v1/__tests__/v1-credentials.e2e.test.ts | 2 +- .../v1/handlers/V1IssueCredentialHandler.ts | 22 +- .../v1/handlers/V1OfferCredentialHandler.ts | 26 +- .../v1/handlers/V1ProposeCredentialHandler.ts | 27 +- .../v1/handlers/V1RequestCredentialHandler.ts | 24 +- .../v2/CredentialFormatCoordinator.ts | 283 +++++++------ .../protocol/v2/V2CredentialService.ts | 395 ++++++++++-------- .../__tests__/V2CredentialServiceCred.test.ts | 133 +++--- .../V2CredentialServiceOffer.test.ts | 28 +- .../v2/__tests__/v2-credentials.e2e.test.ts | 8 +- .../v2/handlers/V2IssueCredentialHandler.ts | 23 +- .../v2/handlers/V2OfferCredentialHandler.ts | 27 +- .../v2/handlers/V2ProposeCredentialHandler.ts | 18 +- .../v2/handlers/V2RequestCredentialHandler.ts | 25 +- .../credentials/services/CredentialService.ts | 130 ++++-- .../src/modules/credentials/services/index.ts | 1 - packages/core/src/modules/dids/DidsModule.ts | 13 +- .../dids/__tests__/DidResolverService.test.ts | 19 +- .../modules/dids/__tests__/peer-did.test.ts | 21 +- .../src/modules/dids/domain/DidResolver.ts | 8 +- .../dids/methods/key/KeyDidResolver.ts | 3 +- .../key/__tests__/KeyDidResolver.test.ts | 20 +- .../dids/methods/peer/PeerDidResolver.ts | 5 +- .../dids/methods/sov/SovDidResolver.ts | 7 +- .../sov/__tests__/SovDidResolver.test.ts | 11 +- .../dids/methods/web/WebDidResolver.ts | 2 + .../modules/dids/repository/DidRepository.ts | 9 +- .../dids/services/DidResolverService.ts | 27 +- .../DiscoverFeaturesModule.ts | 24 +- .../generic-records/GenericRecordsModule.ts | 32 +- .../service/GenericRecordService.ts | 29 +- .../indy/services/IndyHolderService.ts | 99 +++-- .../indy/services/IndyIssuerService.ts | 77 ++-- .../indy/services/IndyRevocationService.ts | 27 +- .../indy/services/IndyUtilitiesService.ts | 23 +- .../indy/services/IndyVerifierService.ts | 37 +- .../services/__mocks__/IndyHolderService.ts | 4 +- .../services/__mocks__/IndyIssuerService.ts | 2 +- packages/core/src/modules/ledger/IndyPool.ts | 19 +- .../core/src/modules/ledger/LedgerModule.ts | 47 ++- .../__tests__/IndyLedgerService.test.ts | 19 +- .../ledger/__tests__/IndyPoolService.test.ts | 55 ++- .../ledger/services/IndyLedgerService.ts | 86 ++-- .../ledger/services/IndyPoolService.ts | 48 ++- .../core/src/modules/oob/OutOfBandModule.ts | 72 ++-- .../core/src/modules/oob/OutOfBandService.ts | 67 +-- .../oob/__tests__/OutOfBandService.test.ts | 57 ++- .../proofs/ProofResponseCoordinator.ts | 20 +- .../core/src/modules/proofs/ProofsModule.ts | 148 ++++--- .../proofs/__tests__/ProofService.test.ts | 24 +- .../proofs/handlers/PresentationHandler.ts | 22 +- .../handlers/ProposePresentationHandler.ts | 29 +- .../handlers/RequestPresentationHandler.ts | 40 +- .../modules/proofs/services/ProofService.ts | 231 ++++++---- .../question-answer/QuestionAnswerModule.ts | 33 +- .../__tests__/QuestionAnswerService.test.ts | 25 +- .../services/QuestionAnswerService.ts | 63 +-- .../src/modules/routing/MediatorModule.ts | 34 +- .../src/modules/routing/RecipientModule.ts | 104 ++--- .../routing/__tests__/mediation.test.ts | 4 +- .../modules/routing/__tests__/pickup.test.ts | 8 +- .../routing/handlers/ForwardHandler.ts | 10 +- .../routing/handlers/KeylistUpdateHandler.ts | 7 +- .../handlers/KeylistUpdateResponseHandler.ts | 5 +- .../routing/handlers/MediationDenyHandler.ts | 5 +- .../routing/handlers/MediationGrantHandler.ts | 5 +- .../handlers/MediationRequestHandler.ts | 19 +- .../pickup/v1/handlers/BatchHandler.ts | 9 +- .../pickup/v1/handlers/BatchPickupHandler.ts | 7 +- .../routing/repository/MediationRepository.ts | 10 +- .../services/MediationRecipientService.ts | 135 +++--- .../routing/services/MediatorService.ts | 123 +++--- .../routing/services/RoutingService.ts | 32 +- .../MediationRecipientService.test.ts | 63 ++- .../__tests__/MediatorService.test.ts | 19 +- .../services/__tests__/RoutingService.test.ts | 18 +- .../__tests__/V2MessagePickupService.test.ts | 22 +- .../src/modules/vc/W3cCredentialService.ts | 163 ++++---- .../vc/__tests__/W3cCredentialService.test.ts | 55 ++- .../src/storage/InMemoryMessageRepository.ts | 10 +- .../core/src/storage/IndyStorageService.ts | 86 ++-- packages/core/src/storage/Repository.ts | 47 ++- packages/core/src/storage/StorageService.ts | 15 +- .../DidCommMessageRepository.test.ts | 35 +- .../__tests__/IndyStorageService.test.ts | 79 ++-- .../src/storage/__tests__/Repository.test.ts | 87 ++-- .../didcomm/DidCommMessageRepository.ts | 36 +- .../storage/migration/StorageUpdateService.ts | 33 +- .../src/storage/migration/UpdateAssistant.ts | 20 +- .../storage/migration/__tests__/0.1.test.ts | 17 +- .../migration/__tests__/backup.test.ts | 27 +- .../0.1-0.2/__tests__/connection.test.ts | 26 +- .../0.1-0.2/__tests__/credential.test.ts | 12 +- .../0.1-0.2/__tests__/mediation.test.ts | 5 +- .../migration/updates/0.1-0.2/connection.ts | 20 +- .../migration/updates/0.1-0.2/credential.ts | 6 +- .../migration/updates/0.1-0.2/mediation.ts | 6 +- .../src/transport/HttpOutboundTransport.ts | 13 +- .../core/src/transport/WsOutboundTransport.ts | 15 +- packages/core/src/wallet/IndyWallet.test.ts | 30 +- packages/core/src/wallet/IndyWallet.ts | 16 +- packages/core/src/wallet/WalletModule.ts | 18 +- .../core/src/wallet/util/assertIndyWallet.ts | 10 + .../core/tests/connectionless-proofs.test.ts | 7 +- packages/core/tests/helpers.ts | 18 + packages/core/tests/ledger.test.ts | 3 +- packages/core/tests/mocks/MockWallet.ts | 74 ++++ packages/core/tests/mocks/index.ts | 1 + .../core/tests/multi-protocol-version.test.ts | 4 +- packages/core/tests/oob.test.ts | 2 +- packages/core/tests/wallet.test.ts | 8 +- .../src/transport/HttpInboundTransport.ts | 7 +- .../node/src/transport/WsInboundTransport.ts | 7 +- samples/extension-module/dummy/DummyApi.ts | 28 +- .../dummy/services/DummyService.ts | 48 ++- samples/mediator.ts | 3 +- tests/InMemoryStorageService.ts | 23 +- tests/e2e-test.ts | 5 +- tests/transport/SubjectInboundTransport.ts | 3 +- tests/transport/SubjectOutboundTransport.ts | 6 +- 164 files changed, 3942 insertions(+), 2843 deletions(-) create mode 100644 packages/core/src/agent/AgentContext.ts create mode 100644 packages/core/src/agent/index.ts create mode 100644 packages/core/src/wallet/util/assertIndyWallet.ts create mode 100644 packages/core/tests/mocks/MockWallet.ts create mode 100644 packages/core/tests/mocks/index.ts diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 16031a144b..49c9e37050 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -2,13 +2,13 @@ import type { Logger } from '../logger' import type { InboundTransport } from '../transport/InboundTransport' import type { OutboundTransport } from '../transport/OutboundTransport' import type { InitConfig } from '../types' -import type { Wallet } from '../wallet/Wallet' import type { AgentDependencies } from './AgentDependencies' import type { AgentMessageReceivedEvent } from './Events' import type { TransportSession } from './TransportService' import type { Subscription } from 'rxjs' import type { DependencyContainer } from 'tsyringe' +import { Subject } from 'rxjs' import { concatMap, takeUntil } from 'rxjs/operators' import { container as baseContainer } from 'tsyringe' @@ -42,6 +42,7 @@ import { WalletModule } from '../wallet/WalletModule' import { WalletError } from '../wallet/error' import { AgentConfig } from './AgentConfig' +import { AgentContext } from './AgentContext' import { Dispatcher } from './Dispatcher' import { EnvelopeService } from './EnvelopeService' import { EventEmitter } from './EventEmitter' @@ -60,8 +61,9 @@ export class Agent { protected messageSender: MessageSender private _isInitialized = false public messageSubscription: Subscription - private walletService: Wallet private routingService: RoutingService + private agentContext: AgentContext + private stop$ = new Subject() public readonly connections: ConnectionsModule public readonly proofs: ProofsModule @@ -112,8 +114,8 @@ export class Agent { this.messageSender = this.dependencyManager.resolve(MessageSender) this.messageReceiver = this.dependencyManager.resolve(MessageReceiver) this.transportService = this.dependencyManager.resolve(TransportService) - this.walletService = this.dependencyManager.resolve(InjectionSymbols.Wallet) this.routingService = this.dependencyManager.resolve(RoutingService) + this.agentContext = this.dependencyManager.resolve(AgentContext) // We set the modules in the constructor because that allows to set them as read-only this.connections = this.dependencyManager.resolve(ConnectionsModule) @@ -134,8 +136,12 @@ export class Agent { this.messageSubscription = this.eventEmitter .observable(AgentEventTypes.AgentMessageReceived) .pipe( - takeUntil(this.agentConfig.stop$), - concatMap((e) => this.messageReceiver.receiveMessage(e.payload.message, { connection: e.payload.connection })) + takeUntil(this.stop$), + concatMap((e) => + this.messageReceiver.receiveMessage(this.agentContext, e.payload.message, { + connection: e.payload.connection, + }) + ) ) .subscribe() } @@ -185,7 +191,7 @@ export class Agent { // Make sure the storage is up to date const storageUpdateService = this.dependencyManager.resolve(StorageUpdateService) - const isStorageUpToDate = await storageUpdateService.isUpToDate() + const isStorageUpToDate = await storageUpdateService.isUpToDate(this.agentContext) this.logger.info(`Agent storage is ${isStorageUpToDate ? '' : 'not '}up to date.`) if (!isStorageUpToDate && this.agentConfig.autoUpdateStorageOnStartup) { @@ -194,7 +200,7 @@ export class Agent { await updateAssistant.initialize() await updateAssistant.update() } else if (!isStorageUpToDate) { - const currentVersion = await storageUpdateService.getCurrentStorageVersion() + const currentVersion = await storageUpdateService.getCurrentStorageVersion(this.agentContext) // Close wallet to prevent un-initialized agent with initialized wallet await this.wallet.close() throw new AriesFrameworkError( @@ -208,9 +214,11 @@ export class Agent { if (publicDidSeed) { // If an agent has publicDid it will be used as routing key. - await this.walletService.initPublicDid({ seed: publicDidSeed }) + await this.agentContext.wallet.initPublicDid({ seed: publicDidSeed }) } + // set the pools on the ledger. + this.ledger.setPools(this.agentContext.config.indyLedgers) // As long as value isn't false we will async connect to all genesis pools on startup if (connectToIndyLedgersOnStartup) { this.ledger.connectToPools().catch((error) => { @@ -243,7 +251,7 @@ export class Agent { public async shutdown() { // All observables use takeUntil with the stop$ observable // this means all observables will stop running if a value is emitted on this observable - this.agentConfig.stop$.next(true) + this.stop$.next(true) // Stop transports const allTransports = [...this.inboundTransports, ...this.outboundTransports] @@ -258,11 +266,11 @@ export class Agent { } public get publicDid() { - return this.walletService.publicDid + return this.agentContext.wallet.publicDid } public async receiveMessage(inboundMessage: unknown, session?: TransportSession) { - return await this.messageReceiver.receiveMessage(inboundMessage, { session }) + return await this.messageReceiver.receiveMessage(this.agentContext, inboundMessage, { session }) } public get injectionContainer() { @@ -273,6 +281,10 @@ export class Agent { return this.agentConfig } + public get context() { + return this.agentContext + } + private async getMediationConnection(mediatorInvitationUrl: string) { const outOfBandInvitation = this.oob.parseInvitation(mediatorInvitationUrl) const outOfBandRecord = await this.oob.findByInvitationId(outOfBandInvitation.id) @@ -281,7 +293,7 @@ export class Agent { if (!connection) { this.logger.debug('Mediation connection does not exist, creating connection') // We don't want to use the current default mediator when connecting to another mediator - const routing = await this.routingService.getRouting({ useDefaultMediator: false }) + const routing = await this.routingService.getRouting(this.agentContext, { useDefaultMediator: false }) this.logger.debug('Routing created', routing) const { connectionRecord: newConnection } = await this.oob.receiveInvitation(outOfBandInvitation, { @@ -303,7 +315,7 @@ export class Agent { } private registerDependencies(dependencyManager: DependencyManager) { - dependencyManager.registerInstance(AgentConfig, this.agentConfig) + const dependencies = this.agentConfig.agentDependencies // Register internal dependencies dependencyManager.registerSingleton(EventEmitter) @@ -318,11 +330,14 @@ export class Agent { dependencyManager.registerSingleton(StorageVersionRepository) dependencyManager.registerSingleton(StorageUpdateService) + dependencyManager.registerInstance(AgentConfig, this.agentConfig) + dependencyManager.registerInstance(InjectionSymbols.AgentDependencies, dependencies) + dependencyManager.registerInstance(InjectionSymbols.FileSystem, new dependencies.FileSystem()) + dependencyManager.registerInstance(InjectionSymbols.Stop$, this.stop$) + // Register possibly already defined services if (!dependencyManager.isRegistered(InjectionSymbols.Wallet)) { - this.dependencyManager.registerSingleton(IndyWallet) - const wallet = this.dependencyManager.resolve(IndyWallet) - dependencyManager.registerInstance(InjectionSymbols.Wallet, wallet) + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndyWallet) } if (!dependencyManager.isRegistered(InjectionSymbols.Logger)) { dependencyManager.registerInstance(InjectionSymbols.Logger, this.logger) @@ -352,5 +367,7 @@ export class Agent { IndyModule, W3cVcModule ) + + dependencyManager.registerInstance(AgentContext, new AgentContext({ dependencyManager })) } } diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index bb8ca24b56..e43b17c183 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -1,10 +1,7 @@ import type { Logger } from '../logger' -import type { FileSystem } from '../storage/FileSystem' import type { InitConfig } from '../types' import type { AgentDependencies } from './AgentDependencies' -import { Subject } from 'rxjs' - import { DID_COMM_TRANSPORT_QUEUE } from '../constants' import { AriesFrameworkError } from '../error' import { ConsoleLogger, LogLevel } from '../logger' @@ -17,17 +14,12 @@ export class AgentConfig { public label: string public logger: Logger public readonly agentDependencies: AgentDependencies - public readonly fileSystem: FileSystem - - // $stop is used for agent shutdown signal - public readonly stop$ = new Subject() public constructor(initConfig: InitConfig, agentDependencies: AgentDependencies) { this.initConfig = initConfig this.label = initConfig.label this.logger = initConfig.logger ?? new ConsoleLogger(LogLevel.off) this.agentDependencies = agentDependencies - this.fileSystem = new agentDependencies.FileSystem() const { mediatorConnectionsInvite, clearDefaultMediator, defaultMediatorId } = this.initConfig diff --git a/packages/core/src/agent/AgentContext.ts b/packages/core/src/agent/AgentContext.ts new file mode 100644 index 0000000000..a8e176d67f --- /dev/null +++ b/packages/core/src/agent/AgentContext.ts @@ -0,0 +1,32 @@ +import type { DependencyManager } from '../plugins' +import type { Wallet } from '../wallet' + +import { InjectionSymbols } from '../constants' + +import { AgentConfig } from './AgentConfig' + +export class AgentContext { + /** + * Dependency manager holds all dependencies for the current context. Possibly a child of a parent dependency manager, + * in which case all singleton dependencies from the parent context are also available to this context. + */ + public readonly dependencyManager: DependencyManager + + public constructor({ dependencyManager }: { dependencyManager: DependencyManager }) { + this.dependencyManager = dependencyManager + } + + /** + * Convenience method to access the agent config for the current context. + */ + public get config() { + return this.dependencyManager.resolve(AgentConfig) + } + + /** + * Convenience method to access the wallet for the current context. + */ + public get wallet() { + return this.dependencyManager.resolve(InjectionSymbols.Wallet) + } +} diff --git a/packages/core/src/agent/Dispatcher.ts b/packages/core/src/agent/Dispatcher.ts index d659da8f44..e55a324f85 100644 --- a/packages/core/src/agent/Dispatcher.ts +++ b/packages/core/src/agent/Dispatcher.ts @@ -1,13 +1,13 @@ -import type { Logger } from '../logger' import type { OutboundMessage, OutboundServiceMessage } from '../types' import type { AgentMessage } from './AgentMessage' import type { AgentMessageProcessedEvent } from './Events' import type { Handler } from './Handler' import type { InboundMessageContext } from './models/InboundMessageContext' -import { AgentConfig } from '../agent/AgentConfig' +import { InjectionSymbols } from '../constants' import { AriesFrameworkError } from '../error/AriesFrameworkError' -import { injectable } from '../plugins' +import { Logger } from '../logger' +import { injectable, inject } from '../plugins' import { canHandleMessageType, parseMessageType } from '../utils/messageType' import { ProblemReportMessage } from './../modules/problem-reports/messages/ProblemReportMessage' @@ -23,10 +23,14 @@ class Dispatcher { private eventEmitter: EventEmitter private logger: Logger - public constructor(messageSender: MessageSender, eventEmitter: EventEmitter, agentConfig: AgentConfig) { + public constructor( + messageSender: MessageSender, + eventEmitter: EventEmitter, + @inject(InjectionSymbols.Logger) logger: Logger + ) { this.messageSender = messageSender this.eventEmitter = eventEmitter - this.logger = agentConfig.logger + this.logger = logger } public registerHandler(handler: Handler) { @@ -70,7 +74,7 @@ class Dispatcher { } if (outboundMessage && isOutboundServiceMessage(outboundMessage)) { - await this.messageSender.sendMessageToService({ + await this.messageSender.sendMessageToService(messageContext.agentContext, { message: outboundMessage.payload, service: outboundMessage.service, senderKey: outboundMessage.senderKey, @@ -78,11 +82,11 @@ class Dispatcher { }) } else if (outboundMessage) { outboundMessage.sessionId = messageContext.sessionId - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(messageContext.agentContext, outboundMessage) } // Emit event that allows to hook into received messages - this.eventEmitter.emit({ + this.eventEmitter.emit(messageContext.agentContext, { type: AgentEventTypes.AgentMessageProcessed, payload: { message: messageContext.message, diff --git a/packages/core/src/agent/EnvelopeService.ts b/packages/core/src/agent/EnvelopeService.ts index de6e7e9e5b..d2ca8e4e51 100644 --- a/packages/core/src/agent/EnvelopeService.ts +++ b/packages/core/src/agent/EnvelopeService.ts @@ -1,14 +1,12 @@ -import type { Logger } from '../logger' import type { EncryptedMessage, PlaintextMessage } from '../types' +import type { AgentContext } from './AgentContext' import type { AgentMessage } from './AgentMessage' import { InjectionSymbols } from '../constants' import { Key, KeyType } from '../crypto' +import { Logger } from '../logger' import { ForwardMessage } from '../modules/routing/messages' import { inject, injectable } from '../plugins' -import { Wallet } from '../wallet/Wallet' - -import { AgentConfig } from './AgentConfig' export interface EnvelopeKeys { recipientKeys: Key[] @@ -18,28 +16,28 @@ export interface EnvelopeKeys { @injectable() export class EnvelopeService { - private wallet: Wallet private logger: Logger - private config: AgentConfig - public constructor(@inject(InjectionSymbols.Wallet) wallet: Wallet, agentConfig: AgentConfig) { - this.wallet = wallet - this.logger = agentConfig.logger - this.config = agentConfig + public constructor(@inject(InjectionSymbols.Logger) logger: Logger) { + this.logger = logger } - public async packMessage(payload: AgentMessage, keys: EnvelopeKeys): Promise { + public async packMessage( + agentContext: AgentContext, + payload: AgentMessage, + keys: EnvelopeKeys + ): Promise { const { recipientKeys, routingKeys, senderKey } = keys let recipientKeysBase58 = recipientKeys.map((key) => key.publicKeyBase58) const routingKeysBase58 = routingKeys.map((key) => key.publicKeyBase58) const senderKeyBase58 = senderKey && senderKey.publicKeyBase58 // pass whether we want to use legacy did sov prefix - const message = payload.toJSON({ useLegacyDidSovPrefix: this.config.useLegacyDidSovPrefix }) + const message = payload.toJSON({ useLegacyDidSovPrefix: agentContext.config.useLegacyDidSovPrefix }) this.logger.debug(`Pack outbound message ${message['@type']}`) - let encryptedMessage = await this.wallet.pack(message, recipientKeysBase58, senderKeyBase58 ?? undefined) + let encryptedMessage = await agentContext.wallet.pack(message, recipientKeysBase58, senderKeyBase58 ?? undefined) // If the message has routing keys (mediator) pack for each mediator for (const routingKeyBase58 of routingKeysBase58) { @@ -51,17 +49,20 @@ export class EnvelopeService { recipientKeysBase58 = [routingKeyBase58] this.logger.debug('Forward message created', forwardMessage) - const forwardJson = forwardMessage.toJSON({ useLegacyDidSovPrefix: this.config.useLegacyDidSovPrefix }) + const forwardJson = forwardMessage.toJSON({ useLegacyDidSovPrefix: agentContext.config.useLegacyDidSovPrefix }) // Forward messages are anon packed - encryptedMessage = await this.wallet.pack(forwardJson, [routingKeyBase58], undefined) + encryptedMessage = await agentContext.wallet.pack(forwardJson, [routingKeyBase58], undefined) } return encryptedMessage } - public async unpackMessage(encryptedMessage: EncryptedMessage): Promise { - const decryptedMessage = await this.wallet.unpack(encryptedMessage) + public async unpackMessage( + agentContext: AgentContext, + encryptedMessage: EncryptedMessage + ): Promise { + const decryptedMessage = await agentContext.wallet.unpack(encryptedMessage) const { recipientKey, senderKey, plaintextMessage } = decryptedMessage return { recipientKey: recipientKey ? Key.fromPublicKeyBase58(recipientKey, KeyType.Ed25519) : undefined, diff --git a/packages/core/src/agent/EventEmitter.ts b/packages/core/src/agent/EventEmitter.ts index 62caae137c..284dcc1709 100644 --- a/packages/core/src/agent/EventEmitter.ts +++ b/packages/core/src/agent/EventEmitter.ts @@ -1,24 +1,30 @@ +import type { AgentContext } from './AgentContext' import type { BaseEvent } from './Events' import type { EventEmitter as NativeEventEmitter } from 'events' -import { fromEventPattern } from 'rxjs' +import { fromEventPattern, Subject } from 'rxjs' import { takeUntil } from 'rxjs/operators' -import { injectable } from '../plugins' +import { InjectionSymbols } from '../constants' +import { injectable, inject } from '../plugins' -import { AgentConfig } from './AgentConfig' +import { AgentDependencies } from './AgentDependencies' @injectable() export class EventEmitter { - private agentConfig: AgentConfig private eventEmitter: NativeEventEmitter - - public constructor(agentConfig: AgentConfig) { - this.agentConfig = agentConfig - this.eventEmitter = new agentConfig.agentDependencies.EventEmitterClass() + private stop$: Subject + + public constructor( + @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, + @inject(InjectionSymbols.Stop$) stop$: Subject + ) { + this.eventEmitter = new agentDependencies.EventEmitterClass() + this.stop$ = stop$ } - public emit(data: T) { + // agentContext is currently not used, but already making required as it will be used soon + public emit(agentContext: AgentContext, data: T) { this.eventEmitter.emit(data.type, data) } @@ -34,6 +40,6 @@ export class EventEmitter { return fromEventPattern( (handler) => this.on(event, handler), (handler) => this.off(event, handler) - ).pipe(takeUntil(this.agentConfig.stop$)) + ).pipe(takeUntil(this.stop$)) } } diff --git a/packages/core/src/agent/MessageReceiver.ts b/packages/core/src/agent/MessageReceiver.ts index 78f18caca5..31282e0252 100644 --- a/packages/core/src/agent/MessageReceiver.ts +++ b/packages/core/src/agent/MessageReceiver.ts @@ -1,20 +1,21 @@ -import type { Logger } from '../logger' import type { ConnectionRecord } from '../modules/connections' import type { InboundTransport } from '../transport' -import type { PlaintextMessage, EncryptedMessage } from '../types' +import type { EncryptedMessage, PlaintextMessage } from '../types' +import type { AgentContext } from './AgentContext' import type { AgentMessage } from './AgentMessage' import type { DecryptedMessageContext } from './EnvelopeService' import type { TransportSession } from './TransportService' +import { InjectionSymbols } from '../constants' import { AriesFrameworkError } from '../error' -import { ConnectionsModule } from '../modules/connections' +import { Logger } from '../logger' +import { ConnectionService } from '../modules/connections' import { ProblemReportError, ProblemReportMessage, ProblemReportReason } from '../modules/problem-reports' -import { injectable } from '../plugins' +import { injectable, inject } from '../plugins' import { isValidJweStructure } from '../utils/JWE' import { JsonTransformer } from '../utils/JsonTransformer' import { canHandleMessageType, parseMessageType, replaceLegacyDidSovPrefixOnMessage } from '../utils/messageType' -import { AgentConfig } from './AgentConfig' import { Dispatcher } from './Dispatcher' import { EnvelopeService } from './EnvelopeService' import { MessageSender } from './MessageSender' @@ -24,30 +25,28 @@ import { InboundMessageContext } from './models/InboundMessageContext' @injectable() export class MessageReceiver { - private config: AgentConfig private envelopeService: EnvelopeService private transportService: TransportService private messageSender: MessageSender private dispatcher: Dispatcher private logger: Logger - private connectionsModule: ConnectionsModule + private connectionService: ConnectionService public readonly inboundTransports: InboundTransport[] = [] public constructor( - config: AgentConfig, envelopeService: EnvelopeService, transportService: TransportService, messageSender: MessageSender, - connectionsModule: ConnectionsModule, - dispatcher: Dispatcher + connectionService: ConnectionService, + dispatcher: Dispatcher, + @inject(InjectionSymbols.Logger) logger: Logger ) { - this.config = config this.envelopeService = envelopeService this.transportService = transportService this.messageSender = messageSender - this.connectionsModule = connectionsModule + this.connectionService = connectionService this.dispatcher = dispatcher - this.logger = this.config.logger + this.logger = logger } public registerInboundTransport(inboundTransport: InboundTransport) { @@ -61,27 +60,36 @@ export class MessageReceiver { * @param inboundMessage the message to receive and handle */ public async receiveMessage( + agentContext: AgentContext, inboundMessage: unknown, { session, connection }: { session?: TransportSession; connection?: ConnectionRecord } ) { - this.logger.debug(`Agent ${this.config.label} received message`) + this.logger.debug(`Agent ${agentContext.config.label} received message`) if (this.isEncryptedMessage(inboundMessage)) { - await this.receiveEncryptedMessage(inboundMessage as EncryptedMessage, session) + await this.receiveEncryptedMessage(agentContext, inboundMessage as EncryptedMessage, session) } else if (this.isPlaintextMessage(inboundMessage)) { - await this.receivePlaintextMessage(inboundMessage, connection) + await this.receivePlaintextMessage(agentContext, inboundMessage, connection) } else { throw new AriesFrameworkError('Unable to parse incoming message: unrecognized format') } } - private async receivePlaintextMessage(plaintextMessage: PlaintextMessage, connection?: ConnectionRecord) { - const message = await this.transformAndValidate(plaintextMessage) - const messageContext = new InboundMessageContext(message, { connection }) + private async receivePlaintextMessage( + agentContext: AgentContext, + plaintextMessage: PlaintextMessage, + connection?: ConnectionRecord + ) { + const message = await this.transformAndValidate(agentContext, plaintextMessage) + const messageContext = new InboundMessageContext(message, { connection, agentContext }) await this.dispatcher.dispatch(messageContext) } - private async receiveEncryptedMessage(encryptedMessage: EncryptedMessage, session?: TransportSession) { - const decryptedMessage = await this.decryptMessage(encryptedMessage) + private async receiveEncryptedMessage( + agentContext: AgentContext, + encryptedMessage: EncryptedMessage, + session?: TransportSession + ) { + const decryptedMessage = await this.decryptMessage(agentContext, encryptedMessage) const { plaintextMessage, senderKey, recipientKey } = decryptedMessage this.logger.info( @@ -89,9 +97,9 @@ export class MessageReceiver { plaintextMessage ) - const connection = await this.findConnectionByMessageKeys(decryptedMessage) + const connection = await this.findConnectionByMessageKeys(agentContext, decryptedMessage) - const message = await this.transformAndValidate(plaintextMessage, connection) + const message = await this.transformAndValidate(agentContext, plaintextMessage, connection) const messageContext = new InboundMessageContext(message, { // Only make the connection available in message context if the connection is ready @@ -100,6 +108,7 @@ export class MessageReceiver { connection: connection?.isReady ? connection : undefined, senderKey, recipientKey, + agentContext, }) // We want to save a session if there is a chance of returning outbound message via inbound transport. @@ -133,9 +142,12 @@ export class MessageReceiver { * * @param message the received inbound message to decrypt */ - private async decryptMessage(message: EncryptedMessage): Promise { + private async decryptMessage( + agentContext: AgentContext, + message: EncryptedMessage + ): Promise { try { - return await this.envelopeService.unpackMessage(message) + return await this.envelopeService.unpackMessage(agentContext, message) } catch (error) { this.logger.error('Error while decrypting message', { error, @@ -160,6 +172,7 @@ export class MessageReceiver { } private async transformAndValidate( + agentContext: AgentContext, plaintextMessage: PlaintextMessage, connection?: ConnectionRecord | null ): Promise { @@ -167,21 +180,21 @@ export class MessageReceiver { try { message = await this.transformMessage(plaintextMessage) } catch (error) { - if (connection) await this.sendProblemReportMessage(error.message, connection, plaintextMessage) + if (connection) await this.sendProblemReportMessage(agentContext, error.message, connection, plaintextMessage) throw error } return message } - private async findConnectionByMessageKeys({ - recipientKey, - senderKey, - }: DecryptedMessageContext): Promise { + private async findConnectionByMessageKeys( + agentContext: AgentContext, + { recipientKey, senderKey }: DecryptedMessageContext + ): Promise { // We only fetch connections that are sent in AuthCrypt mode if (!recipientKey || !senderKey) return null // Try to find the did records that holds the sender and recipient keys - return this.connectionsModule.findByKeys({ + return this.connectionService.findByKeys(agentContext, { senderKey, recipientKey, }) @@ -228,6 +241,7 @@ export class MessageReceiver { * @param plaintextMessage received inbound message */ private async sendProblemReportMessage( + agentContext: AgentContext, message: string, connection: ConnectionRecord, plaintextMessage: PlaintextMessage @@ -247,7 +261,7 @@ export class MessageReceiver { }) const outboundMessage = createOutboundMessage(connection, problemReportMessage) if (outboundMessage) { - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(agentContext, outboundMessage) } } } diff --git a/packages/core/src/agent/MessageSender.ts b/packages/core/src/agent/MessageSender.ts index 46fe9a86a2..a817124e31 100644 --- a/packages/core/src/agent/MessageSender.ts +++ b/packages/core/src/agent/MessageSender.ts @@ -4,6 +4,7 @@ import type { DidDocument } from '../modules/dids' import type { OutOfBandRecord } from '../modules/oob/repository' import type { OutboundTransport } from '../transport/OutboundTransport' import type { OutboundMessage, OutboundPackage, EncryptedMessage } from '../types' +import type { AgentContext } from './AgentContext' import type { AgentMessage } from './AgentMessage' import type { EnvelopeKeys } from './EnvelopeService' import type { TransportSession } from './TransportService' @@ -65,16 +66,19 @@ export class MessageSender { this.outboundTransports.push(outboundTransport) } - public async packMessage({ - keys, - message, - endpoint, - }: { - keys: EnvelopeKeys - message: AgentMessage - endpoint: string - }): Promise { - const encryptedMessage = await this.envelopeService.packMessage(message, keys) + public async packMessage( + agentContext: AgentContext, + { + keys, + message, + endpoint, + }: { + keys: EnvelopeKeys + message: AgentMessage + endpoint: string + } + ): Promise { + const encryptedMessage = await this.envelopeService.packMessage(agentContext, message, keys) return { payload: encryptedMessage, @@ -83,24 +87,27 @@ export class MessageSender { } } - private async sendMessageToSession(session: TransportSession, message: AgentMessage) { + private async sendMessageToSession(agentContext: AgentContext, session: TransportSession, message: AgentMessage) { this.logger.debug(`Existing ${session.type} transport session has been found.`) if (!session.keys) { throw new AriesFrameworkError(`There are no keys for the given ${session.type} transport session.`) } - const encryptedMessage = await this.envelopeService.packMessage(message, session.keys) + const encryptedMessage = await this.envelopeService.packMessage(agentContext, message, session.keys) await session.send(encryptedMessage) } - public async sendPackage({ - connection, - encryptedMessage, - options, - }: { - connection: ConnectionRecord - encryptedMessage: EncryptedMessage - options?: { transportPriority?: TransportPriorityOptions } - }) { + public async sendPackage( + agentContext: AgentContext, + { + connection, + encryptedMessage, + options, + }: { + connection: ConnectionRecord + encryptedMessage: EncryptedMessage + options?: { transportPriority?: TransportPriorityOptions } + } + ) { const errors: Error[] = [] // Try to send to already open session @@ -116,7 +123,11 @@ export class MessageSender { } // Retrieve DIDComm services - const { services, queueService } = await this.retrieveServicesByConnection(connection, options?.transportPriority) + const { services, queueService } = await this.retrieveServicesByConnection( + agentContext, + connection, + options?.transportPriority + ) if (this.outboundTransports.length === 0 && !queueService) { throw new AriesFrameworkError('Agent has no outbound transport!') @@ -167,6 +178,7 @@ export class MessageSender { } public async sendMessage( + agentContext: AgentContext, outboundMessage: OutboundMessage, options?: { transportPriority?: TransportPriorityOptions @@ -193,7 +205,7 @@ export class MessageSender { if (session?.inboundMessage?.hasReturnRouting(payload.threadId)) { this.logger.debug(`Found session with return routing for message '${payload.id}' (connection '${connection.id}'`) try { - await this.sendMessageToSession(session, payload) + await this.sendMessageToSession(agentContext, session, payload) return } catch (error) { errors.push(error) @@ -203,6 +215,7 @@ export class MessageSender { // Retrieve DIDComm services const { services, queueService } = await this.retrieveServicesByConnection( + agentContext, connection, options?.transportPriority, outOfBand @@ -215,7 +228,7 @@ export class MessageSender { ) } - const ourDidDocument = await this.didResolverService.resolveDidDocument(connection.did) + const ourDidDocument = await this.didResolverService.resolveDidDocument(agentContext, connection.did) const ourAuthenticationKeys = getAuthenticationKeys(ourDidDocument) // TODO We're selecting just the first authentication key. Is it ok? @@ -236,7 +249,7 @@ export class MessageSender { for await (const service of services) { try { // Enable return routing if the our did document does not have any inbound endpoint for given sender key - await this.sendMessageToService({ + await this.sendMessageToService(agentContext, { message: payload, service, senderKey: firstOurAuthenticationKey, @@ -267,7 +280,7 @@ export class MessageSender { senderKey: firstOurAuthenticationKey, } - const encryptedMessage = await this.envelopeService.packMessage(payload, keys) + const encryptedMessage = await this.envelopeService.packMessage(agentContext, payload, keys) await this.messageRepository.add(connection.id, encryptedMessage) return } @@ -281,19 +294,22 @@ export class MessageSender { throw new AriesFrameworkError(`Message is undeliverable to connection ${connection.id} (${connection.theirLabel})`) } - public async sendMessageToService({ - message, - service, - senderKey, - returnRoute, - connectionId, - }: { - message: AgentMessage - service: ResolvedDidCommService - senderKey: Key - returnRoute?: boolean - connectionId?: string - }) { + public async sendMessageToService( + agentContext: AgentContext, + { + message, + service, + senderKey, + returnRoute, + connectionId, + }: { + message: AgentMessage + service: ResolvedDidCommService + senderKey: Key + returnRoute?: boolean + connectionId?: string + } + ) { if (this.outboundTransports.length === 0) { throw new AriesFrameworkError('Agent has no outbound transport!') } @@ -328,7 +344,7 @@ export class MessageSender { throw error } - const outboundPackage = await this.packMessage({ message, keys, endpoint: service.serviceEndpoint }) + const outboundPackage = await this.packMessage(agentContext, { message, keys, endpoint: service.serviceEndpoint }) outboundPackage.endpoint = service.serviceEndpoint outboundPackage.connectionId = connectionId for (const transport of this.outboundTransports) { @@ -343,9 +359,9 @@ export class MessageSender { throw new AriesFrameworkError(`Unable to send message to service: ${service.serviceEndpoint}`) } - private async retrieveServicesFromDid(did: string) { + private async retrieveServicesFromDid(agentContext: AgentContext, did: string) { this.logger.debug(`Resolving services for did ${did}.`) - const didDocument = await this.didResolverService.resolveDidDocument(did) + const didDocument = await this.didResolverService.resolveDidDocument(agentContext, did) const didCommServices: ResolvedDidCommService[] = [] @@ -364,7 +380,7 @@ export class MessageSender { // Resolve dids to DIDDocs to retrieve routingKeys const routingKeys = [] for (const routingKey of didCommService.routingKeys ?? []) { - const routingDidDocument = await this.didResolverService.resolveDidDocument(routingKey) + const routingDidDocument = await this.didResolverService.resolveDidDocument(agentContext, routingKey) routingKeys.push(keyReferenceToKey(routingDidDocument, routingKey)) } @@ -387,6 +403,7 @@ export class MessageSender { } private async retrieveServicesByConnection( + agentContext: AgentContext, connection: ConnectionRecord, transportPriority?: TransportPriorityOptions, outOfBand?: OutOfBandRecord @@ -400,14 +417,14 @@ export class MessageSender { if (connection.theirDid) { this.logger.debug(`Resolving services for connection theirDid ${connection.theirDid}.`) - didCommServices = await this.retrieveServicesFromDid(connection.theirDid) + didCommServices = await this.retrieveServicesFromDid(agentContext, connection.theirDid) } else if (outOfBand) { this.logger.debug(`Resolving services from out-of-band record ${outOfBand?.id}.`) if (connection.isRequester) { for (const service of outOfBand.outOfBandInvitation.services) { // Resolve dids to DIDDocs to retrieve services if (typeof service === 'string') { - didCommServices = await this.retrieveServicesFromDid(service) + didCommServices = await this.retrieveServicesFromDid(agentContext, service) } else { // Out of band inline service contains keys encoded as did:key references didCommServices.push({ diff --git a/packages/core/src/agent/__tests__/Agent.test.ts b/packages/core/src/agent/__tests__/Agent.test.ts index 653066b9fe..558f267e14 100644 --- a/packages/core/src/agent/__tests__/Agent.test.ts +++ b/packages/core/src/agent/__tests__/Agent.test.ts @@ -1,5 +1,3 @@ -import type { Wallet } from '../../wallet/Wallet' - import { getBaseConfig } from '../../../tests/helpers' import { InjectionSymbols } from '../../constants' import { BasicMessageRepository, BasicMessageService } from '../../modules/basic-messages' @@ -23,7 +21,6 @@ import { } from '../../modules/routing' import { InMemoryMessageRepository } from '../../storage/InMemoryMessageRepository' import { IndyStorageService } from '../../storage/IndyStorageService' -import { IndyWallet } from '../../wallet/IndyWallet' import { WalletError } from '../../wallet/error' import { Agent } from '../Agent' import { Dispatcher } from '../Dispatcher' @@ -38,7 +35,7 @@ describe('Agent', () => { let agent: Agent afterEach(async () => { - const wallet = agent.dependencyManager.resolve(InjectionSymbols.Wallet) + const wallet = agent.context.wallet if (wallet.isInitialized) { await wallet.delete() @@ -59,7 +56,7 @@ describe('Agent', () => { expect.assertions(4) agent = new Agent(config, dependencies) - const wallet = agent.dependencyManager.resolve(InjectionSymbols.Wallet) + const wallet = agent.context.wallet expect(agent.isInitialized).toBe(false) expect(wallet.isInitialized).toBe(false) @@ -139,7 +136,6 @@ describe('Agent', () => { expect(container.resolve(IndyLedgerService)).toBeInstanceOf(IndyLedgerService) // Symbols, interface based - expect(container.resolve(InjectionSymbols.Wallet)).toBeInstanceOf(IndyWallet) expect(container.resolve(InjectionSymbols.Logger)).toBe(config.logger) expect(container.resolve(InjectionSymbols.MessageRepository)).toBeInstanceOf(InMemoryMessageRepository) expect(container.resolve(InjectionSymbols.StorageService)).toBeInstanceOf(IndyStorageService) @@ -182,7 +178,6 @@ describe('Agent', () => { expect(container.resolve(IndyLedgerService)).toBe(container.resolve(IndyLedgerService)) // Symbols, interface based - expect(container.resolve(InjectionSymbols.Wallet)).toBe(container.resolve(InjectionSymbols.Wallet)) expect(container.resolve(InjectionSymbols.Logger)).toBe(container.resolve(InjectionSymbols.Logger)) expect(container.resolve(InjectionSymbols.MessageRepository)).toBe( container.resolve(InjectionSymbols.MessageRepository) diff --git a/packages/core/src/agent/__tests__/Dispatcher.test.ts b/packages/core/src/agent/__tests__/Dispatcher.test.ts index ec5f60160f..5a735449c6 100644 --- a/packages/core/src/agent/__tests__/Dispatcher.test.ts +++ b/packages/core/src/agent/__tests__/Dispatcher.test.ts @@ -1,6 +1,8 @@ import type { Handler } from '../Handler' -import { getAgentConfig } from '../../../tests/helpers' +import { Subject } from 'rxjs' + +import { getAgentConfig, getAgentContext } from '../../../tests/helpers' import { parseMessageType } from '../../utils/messageType' import { AgentMessage } from '../AgentMessage' import { Dispatcher } from '../Dispatcher' @@ -48,8 +50,9 @@ class TestHandler implements Handler { describe('Dispatcher', () => { const agentConfig = getAgentConfig('DispatcherTest') + const agentContext = getAgentContext() const MessageSenderMock = MessageSender as jest.Mock - const eventEmitter = new EventEmitter(agentConfig) + const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) const fakeProtocolHandler = new TestHandler([CustomProtocolMessage]) const connectionHandler = new TestHandler([ ConnectionInvitationTestMessage, @@ -57,7 +60,7 @@ describe('Dispatcher', () => { ConnectionResponseTestMessage, ]) - const dispatcher = new Dispatcher(new MessageSenderMock(), eventEmitter, agentConfig) + const dispatcher = new Dispatcher(new MessageSenderMock(), eventEmitter, agentConfig.logger) dispatcher.registerHandler(connectionHandler) dispatcher.registerHandler(new TestHandler([NotificationAckTestMessage])) @@ -138,9 +141,9 @@ describe('Dispatcher', () => { describe('dispatch()', () => { it('calls the handle method of the handler', async () => { - const dispatcher = new Dispatcher(new MessageSenderMock(), eventEmitter, agentConfig) + const dispatcher = new Dispatcher(new MessageSenderMock(), eventEmitter, agentConfig.logger) const customProtocolMessage = new CustomProtocolMessage() - const inboundMessageContext = new InboundMessageContext(customProtocolMessage) + const inboundMessageContext = new InboundMessageContext(customProtocolMessage, { agentContext }) const mockHandle = jest.fn() dispatcher.registerHandler({ supportedMessages: [CustomProtocolMessage], handle: mockHandle }) @@ -151,9 +154,9 @@ describe('Dispatcher', () => { }) it('throws an error if no handler for the message could be found', async () => { - const dispatcher = new Dispatcher(new MessageSenderMock(), eventEmitter, agentConfig) + const dispatcher = new Dispatcher(new MessageSenderMock(), eventEmitter, agentConfig.logger) const customProtocolMessage = new CustomProtocolMessage() - const inboundMessageContext = new InboundMessageContext(customProtocolMessage) + const inboundMessageContext = new InboundMessageContext(customProtocolMessage, { agentContext }) const mockHandle = jest.fn() dispatcher.registerHandler({ supportedMessages: [], handle: mockHandle }) diff --git a/packages/core/src/agent/__tests__/MessageSender.test.ts b/packages/core/src/agent/__tests__/MessageSender.test.ts index d7158a9f47..96adad3bdd 100644 --- a/packages/core/src/agent/__tests__/MessageSender.test.ts +++ b/packages/core/src/agent/__tests__/MessageSender.test.ts @@ -6,7 +6,7 @@ import type { OutboundMessage, EncryptedMessage } from '../../types' import type { ResolvedDidCommService } from '../MessageSender' import { TestMessage } from '../../../tests/TestMessage' -import { getAgentConfig, getMockConnection, mockFunction } from '../../../tests/helpers' +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../tests/helpers' import testLogger from '../../../tests/logger' import { Key, KeyType } from '../../crypto' import { ReturnRouteTypes } from '../../decorators/transport/TransportDecorator' @@ -117,6 +117,8 @@ describe('MessageSender', () => { let messageRepository: MessageRepository let connection: ConnectionRecord let outboundMessage: OutboundMessage + const agentConfig = getAgentConfig('MessageSender') + const agentContext = getAgentContext() describe('sendMessage', () => { beforeEach(() => { @@ -124,7 +126,7 @@ describe('MessageSender', () => { DidResolverServiceMock.mockClear() outboundTransport = new DummyHttpOutboundTransport() - messageRepository = new InMemoryMessageRepository(getAgentConfig('MessageSender')) + messageRepository = new InMemoryMessageRepository(agentConfig.logger) messageSender = new MessageSender( enveloperService, transportService, @@ -154,7 +156,9 @@ describe('MessageSender', () => { }) test('throw error when there is no outbound transport', async () => { - await expect(messageSender.sendMessage(outboundMessage)).rejects.toThrow(/Message is undeliverable to connection/) + await expect(messageSender.sendMessage(agentContext, outboundMessage)).rejects.toThrow( + /Message is undeliverable to connection/ + ) }) test('throw error when there is no service or queue', async () => { @@ -162,7 +166,7 @@ describe('MessageSender', () => { didResolverServiceResolveMock.mockResolvedValue(getMockDidDocument({ service: [] })) - await expect(messageSender.sendMessage(outboundMessage)).rejects.toThrow( + await expect(messageSender.sendMessage(agentContext, outboundMessage)).rejects.toThrow( `Message is undeliverable to connection test-123 (Test 123)` ) }) @@ -175,7 +179,7 @@ describe('MessageSender', () => { messageSender.registerOutboundTransport(outboundTransport) const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') - await messageSender.sendMessage(outboundMessage) + await messageSender.sendMessage(agentContext, outboundMessage) expect(sendMessageSpy).toHaveBeenCalledWith({ connectionId: 'test-123', @@ -191,9 +195,9 @@ describe('MessageSender', () => { const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') - await messageSender.sendMessage(outboundMessage) + await messageSender.sendMessage(agentContext, outboundMessage) - expect(didResolverServiceResolveMock).toHaveBeenCalledWith(connection.theirDid) + expect(didResolverServiceResolveMock).toHaveBeenCalledWith(agentContext, connection.theirDid) expect(sendMessageSpy).toHaveBeenCalledWith({ connectionId: 'test-123', payload: encryptedMessage, @@ -210,7 +214,7 @@ describe('MessageSender', () => { new Error(`Unable to resolve did document for did '${connection.theirDid}': notFound`) ) - await expect(messageSender.sendMessage(outboundMessage)).rejects.toThrowError( + await expect(messageSender.sendMessage(agentContext, outboundMessage)).rejects.toThrowError( `Unable to resolve did document for did '${connection.theirDid}': notFound` ) }) @@ -222,7 +226,7 @@ describe('MessageSender', () => { messageSender.registerOutboundTransport(outboundTransport) const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') - await messageSender.sendMessage(outboundMessage) + await messageSender.sendMessage(agentContext, outboundMessage) expect(sendMessageSpy).toHaveBeenCalledWith({ connectionId: 'test-123', @@ -239,7 +243,7 @@ describe('MessageSender', () => { const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') const sendMessageToServiceSpy = jest.spyOn(messageSender, 'sendMessageToService') - await messageSender.sendMessage({ ...outboundMessage, sessionId: 'session-123' }) + await messageSender.sendMessage(agentContext, { ...outboundMessage, sessionId: 'session-123' }) expect(session.send).toHaveBeenCalledTimes(1) expect(session.send).toHaveBeenNthCalledWith(1, encryptedMessage) @@ -253,9 +257,9 @@ describe('MessageSender', () => { const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') const sendMessageToServiceSpy = jest.spyOn(messageSender, 'sendMessageToService') - await messageSender.sendMessage(outboundMessage) + await messageSender.sendMessage(agentContext, outboundMessage) - const [[sendMessage]] = sendMessageToServiceSpy.mock.calls + const [[, sendMessage]] = sendMessageToServiceSpy.mock.calls expect(sendMessage).toMatchObject({ connectionId: 'test-123', @@ -283,9 +287,9 @@ describe('MessageSender', () => { // Simulate the case when the first call fails sendMessageSpy.mockRejectedValueOnce(new Error()) - await messageSender.sendMessage(outboundMessage) + await messageSender.sendMessage(agentContext, outboundMessage) - const [, [sendMessage]] = sendMessageToServiceSpy.mock.calls + const [, [, sendMessage]] = sendMessageToServiceSpy.mock.calls expect(sendMessage).toMatchObject({ connectionId: 'test-123', message: outboundMessage.payload, @@ -306,7 +310,9 @@ describe('MessageSender', () => { test('throw error when message endpoint is not supported by outbound transport schemes', async () => { messageSender.registerOutboundTransport(new DummyWsOutboundTransport()) - await expect(messageSender.sendMessage(outboundMessage)).rejects.toThrow(/Message is undeliverable to connection/) + await expect(messageSender.sendMessage(agentContext, outboundMessage)).rejects.toThrow( + /Message is undeliverable to connection/ + ) }) }) @@ -324,7 +330,7 @@ describe('MessageSender', () => { messageSender = new MessageSender( enveloperService, transportService, - new InMemoryMessageRepository(getAgentConfig('MessageSenderTest')), + new InMemoryMessageRepository(agentConfig.logger), logger, didResolverService ) @@ -338,7 +344,7 @@ describe('MessageSender', () => { test('throws error when there is no outbound transport', async () => { await expect( - messageSender.sendMessageToService({ + messageSender.sendMessageToService(agentContext, { message: new TestMessage(), senderKey, service, @@ -350,7 +356,7 @@ describe('MessageSender', () => { messageSender.registerOutboundTransport(outboundTransport) const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') - await messageSender.sendMessageToService({ + await messageSender.sendMessageToService(agentContext, { message: new TestMessage(), senderKey, service, @@ -371,7 +377,7 @@ describe('MessageSender', () => { const message = new TestMessage() message.setReturnRouting(ReturnRouteTypes.all) - await messageSender.sendMessageToService({ + await messageSender.sendMessageToService(agentContext, { message, senderKey, service, @@ -388,7 +394,7 @@ describe('MessageSender', () => { test('throw error when message endpoint is not supported by outbound transport schemes', async () => { messageSender.registerOutboundTransport(new DummyWsOutboundTransport()) await expect( - messageSender.sendMessageToService({ + messageSender.sendMessageToService(agentContext, { message: new TestMessage(), senderKey, service, @@ -400,7 +406,7 @@ describe('MessageSender', () => { describe('packMessage', () => { beforeEach(() => { outboundTransport = new DummyHttpOutboundTransport() - messageRepository = new InMemoryMessageRepository(getAgentConfig('PackMessage')) + messageRepository = new InMemoryMessageRepository(agentConfig.logger) messageSender = new MessageSender( enveloperService, transportService, @@ -426,7 +432,7 @@ describe('MessageSender', () => { routingKeys: [], senderKey: senderKey, } - const result = await messageSender.packMessage({ message, keys, endpoint }) + const result = await messageSender.packMessage(agentContext, { message, keys, endpoint }) expect(result).toEqual({ payload: encryptedMessage, diff --git a/packages/core/src/agent/index.ts b/packages/core/src/agent/index.ts new file mode 100644 index 0000000000..615455eb43 --- /dev/null +++ b/packages/core/src/agent/index.ts @@ -0,0 +1 @@ +export * from './AgentContext' diff --git a/packages/core/src/agent/models/InboundMessageContext.ts b/packages/core/src/agent/models/InboundMessageContext.ts index be7e1d4eb9..a31d7a8614 100644 --- a/packages/core/src/agent/models/InboundMessageContext.ts +++ b/packages/core/src/agent/models/InboundMessageContext.ts @@ -1,5 +1,6 @@ import type { Key } from '../../crypto' import type { ConnectionRecord } from '../../modules/connections' +import type { AgentContext } from '../AgentContext' import type { AgentMessage } from '../AgentMessage' import { AriesFrameworkError } from '../../error' @@ -9,6 +10,7 @@ export interface MessageContextParams { sessionId?: string senderKey?: Key recipientKey?: Key + agentContext: AgentContext } export class InboundMessageContext { @@ -17,13 +19,15 @@ export class InboundMessageContext { public sessionId?: string public senderKey?: Key public recipientKey?: Key + public readonly agentContext: AgentContext - public constructor(message: T, context: MessageContextParams = {}) { + public constructor(message: T, context: MessageContextParams) { this.message = message this.recipientKey = context.recipientKey this.senderKey = context.senderKey this.connection = context.connection this.sessionId = context.sessionId + this.agentContext = context.agentContext } /** diff --git a/packages/core/src/cache/PersistedLruCache.ts b/packages/core/src/cache/PersistedLruCache.ts index d680a2dca1..ab00e0d14e 100644 --- a/packages/core/src/cache/PersistedLruCache.ts +++ b/packages/core/src/cache/PersistedLruCache.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../agent' import type { CacheRepository } from './CacheRepository' import { LRUMap } from 'lru_map' @@ -16,22 +17,22 @@ export class PersistedLruCache { this.cacheRepository = cacheRepository } - public async get(key: string) { - const cache = await this.getCache() + public async get(agentContext: AgentContext, key: string) { + const cache = await this.getCache(agentContext) return cache.get(key) } - public async set(key: string, value: CacheValue) { - const cache = await this.getCache() + public async set(agentContext: AgentContext, key: string, value: CacheValue) { + const cache = await this.getCache(agentContext) cache.set(key, value) - await this.persistCache() + await this.persistCache(agentContext) } - private async getCache() { + private async getCache(agentContext: AgentContext) { if (!this._cache) { - const cacheRecord = await this.fetchCacheRecord() + const cacheRecord = await this.fetchCacheRecord(agentContext) this._cache = this.lruFromRecord(cacheRecord) } @@ -45,8 +46,8 @@ export class PersistedLruCache { ) } - private async fetchCacheRecord() { - let cacheRecord = await this.cacheRepository.findById(this.cacheId) + private async fetchCacheRecord(agentContext: AgentContext) { + let cacheRecord = await this.cacheRepository.findById(agentContext, this.cacheId) if (!cacheRecord) { cacheRecord = new CacheRecord({ @@ -54,16 +55,17 @@ export class PersistedLruCache { entries: [], }) - await this.cacheRepository.save(cacheRecord) + await this.cacheRepository.save(agentContext, cacheRecord) } return cacheRecord } - private async persistCache() { - const cache = await this.getCache() + private async persistCache(agentContext: AgentContext) { + const cache = await this.getCache(agentContext) await this.cacheRepository.update( + agentContext, new CacheRecord({ entries: cache.toJSON(), id: this.cacheId, diff --git a/packages/core/src/cache/__tests__/PersistedLruCache.test.ts b/packages/core/src/cache/__tests__/PersistedLruCache.test.ts index dc75ce6c1f..c7b893108d 100644 --- a/packages/core/src/cache/__tests__/PersistedLruCache.test.ts +++ b/packages/core/src/cache/__tests__/PersistedLruCache.test.ts @@ -1,4 +1,4 @@ -import { mockFunction } from '../../../tests/helpers' +import { getAgentContext, mockFunction } from '../../../tests/helpers' import { CacheRecord } from '../CacheRecord' import { CacheRepository } from '../CacheRepository' import { PersistedLruCache } from '../PersistedLruCache' @@ -6,6 +6,8 @@ import { PersistedLruCache } from '../PersistedLruCache' jest.mock('../CacheRepository') const CacheRepositoryMock = CacheRepository as jest.Mock +const agentContext = getAgentContext() + describe('PersistedLruCache', () => { let cacheRepository: CacheRepository let cache: PersistedLruCache @@ -30,42 +32,42 @@ describe('PersistedLruCache', () => { }) ) - expect(await cache.get('doesnotexist')).toBeUndefined() - expect(await cache.get('test')).toBe('somevalue') - expect(findMock).toHaveBeenCalledWith('cacheId') + expect(await cache.get(agentContext, 'doesnotexist')).toBeUndefined() + expect(await cache.get(agentContext, 'test')).toBe('somevalue') + expect(findMock).toHaveBeenCalledWith(agentContext, 'cacheId') }) it('should set the value in the persisted record', async () => { const updateMock = mockFunction(cacheRepository.update).mockResolvedValue() - await cache.set('test', 'somevalue') - const [[cacheRecord]] = updateMock.mock.calls + await cache.set(agentContext, 'test', 'somevalue') + const [[, cacheRecord]] = updateMock.mock.calls expect(cacheRecord.entries.length).toBe(1) expect(cacheRecord.entries[0].key).toBe('test') expect(cacheRecord.entries[0].value).toBe('somevalue') - expect(await cache.get('test')).toBe('somevalue') + expect(await cache.get(agentContext, 'test')).toBe('somevalue') }) it('should remove least recently used entries if entries are added that exceed the limit', async () => { // Set first value in cache, resolves fine - await cache.set('one', 'valueone') - expect(await cache.get('one')).toBe('valueone') + await cache.set(agentContext, 'one', 'valueone') + expect(await cache.get(agentContext, 'one')).toBe('valueone') // Set two more entries in the cache. Third item // exceeds limit, so first item gets removed - await cache.set('two', 'valuetwo') - await cache.set('three', 'valuethree') - expect(await cache.get('one')).toBeUndefined() - expect(await cache.get('two')).toBe('valuetwo') - expect(await cache.get('three')).toBe('valuethree') + await cache.set(agentContext, 'two', 'valuetwo') + await cache.set(agentContext, 'three', 'valuethree') + expect(await cache.get(agentContext, 'one')).toBeUndefined() + expect(await cache.get(agentContext, 'two')).toBe('valuetwo') + expect(await cache.get(agentContext, 'three')).toBe('valuethree') // Get two from the cache, meaning three will be removed first now // because it is not recently used - await cache.get('two') - await cache.set('four', 'valuefour') - expect(await cache.get('three')).toBeUndefined() - expect(await cache.get('two')).toBe('valuetwo') + await cache.get(agentContext, 'two') + await cache.set(agentContext, 'four', 'valuefour') + expect(await cache.get(agentContext, 'three')).toBeUndefined() + expect(await cache.get(agentContext, 'two')).toBe('valuetwo') }) }) diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index 4b2eb6f0ea..9d7fdcbc61 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -1,8 +1,11 @@ export const InjectionSymbols = { - Wallet: Symbol('Wallet'), MessageRepository: Symbol('MessageRepository'), StorageService: Symbol('StorageService'), Logger: Symbol('Logger'), + AgentDependencies: Symbol('AgentDependencies'), + Stop$: Symbol('Stop$'), + FileSystem: Symbol('FileSystem'), + Wallet: Symbol('Wallet'), } export const DID_COMM_TRANSPORT_QUEUE = 'didcomm:transport/queue' diff --git a/packages/core/src/crypto/JwsService.ts b/packages/core/src/crypto/JwsService.ts index 8e631d4185..29a7f390e0 100644 --- a/packages/core/src/crypto/JwsService.ts +++ b/packages/core/src/crypto/JwsService.ts @@ -1,11 +1,10 @@ +import type { AgentContext } from '../agent' import type { Buffer } from '../utils' import type { Jws, JwsGeneralFormat } from './JwsTypes' -import { InjectionSymbols } from '../constants' import { AriesFrameworkError } from '../error' -import { inject, injectable } from '../plugins' +import { injectable } from '../plugins' import { JsonEncoder, TypedArrayEncoder } from '../utils' -import { Wallet } from '../wallet' import { WalletError } from '../wallet/error' import { Key } from './Key' @@ -18,19 +17,16 @@ const JWS_ALG = 'EdDSA' @injectable() export class JwsService { - private wallet: Wallet - - public constructor(@inject(InjectionSymbols.Wallet) wallet: Wallet) { - this.wallet = wallet - } - - public async createJws({ payload, verkey, header }: CreateJwsOptions): Promise { + public async createJws( + agentContext: AgentContext, + { payload, verkey, header }: CreateJwsOptions + ): Promise { const base64Payload = TypedArrayEncoder.toBase64URL(payload) const base64Protected = JsonEncoder.toBase64URL(this.buildProtected(verkey)) const key = Key.fromPublicKeyBase58(verkey, KeyType.Ed25519) const signature = TypedArrayEncoder.toBase64URL( - await this.wallet.sign({ data: TypedArrayEncoder.fromString(`${base64Protected}.${base64Payload}`), key }) + await agentContext.wallet.sign({ data: TypedArrayEncoder.fromString(`${base64Protected}.${base64Payload}`), key }) ) return { @@ -43,7 +39,7 @@ export class JwsService { /** * Verify a JWS */ - public async verifyJws({ jws, payload }: VerifyJwsOptions): Promise { + public async verifyJws(agentContext: AgentContext, { jws, payload }: VerifyJwsOptions): Promise { const base64Payload = TypedArrayEncoder.toBase64URL(payload) const signatures = 'signatures' in jws ? jws.signatures : [jws] @@ -71,7 +67,7 @@ export class JwsService { signerVerkeys.push(verkey) try { - const isValid = await this.wallet.verify({ key, data, signature }) + const isValid = await agentContext.wallet.verify({ key, data, signature }) if (!isValid) { return { diff --git a/packages/core/src/crypto/__tests__/JwsService.test.ts b/packages/core/src/crypto/__tests__/JwsService.test.ts index 87ced7bd95..d3371200ff 100644 --- a/packages/core/src/crypto/__tests__/JwsService.test.ts +++ b/packages/core/src/crypto/__tests__/JwsService.test.ts @@ -1,6 +1,7 @@ +import type { AgentContext } from '../../agent' import type { Wallet } from '@aries-framework/core' -import { getAgentConfig } from '../../../tests/helpers' +import { getAgentConfig, getAgentContext } from '../../../tests/helpers' import { DidKey } from '../../modules/dids' import { Buffer, JsonEncoder } from '../../utils' import { IndyWallet } from '../../wallet/IndyWallet' @@ -13,15 +14,19 @@ import * as didJwsz6Mkv from './__fixtures__/didJwsz6Mkv' describe('JwsService', () => { let wallet: Wallet + let agentContext: AgentContext let jwsService: JwsService beforeAll(async () => { const config = getAgentConfig('JwsService') - wallet = new IndyWallet(config) + wallet = new IndyWallet(config.agentDependencies, config.logger) + agentContext = getAgentContext({ + wallet, + }) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) - jwsService = new JwsService(wallet) + jwsService = new JwsService() }) afterAll(async () => { @@ -36,7 +41,7 @@ describe('JwsService', () => { const key = Key.fromPublicKeyBase58(verkey, KeyType.Ed25519) const kid = new DidKey(key).did - const jws = await jwsService.createJws({ + const jws = await jwsService.createJws(agentContext, { payload, verkey, header: { kid }, @@ -50,7 +55,7 @@ describe('JwsService', () => { it('returns true if the jws signature matches the payload', async () => { const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON) - const { isValid, signerVerkeys } = await jwsService.verifyJws({ + const { isValid, signerVerkeys } = await jwsService.verifyJws(agentContext, { payload, jws: didJwsz6Mkf.JWS_JSON, }) @@ -62,7 +67,7 @@ describe('JwsService', () => { it('returns all verkeys that signed the jws', async () => { const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON) - const { isValid, signerVerkeys } = await jwsService.verifyJws({ + const { isValid, signerVerkeys } = await jwsService.verifyJws(agentContext, { payload, jws: { signatures: [didJwsz6Mkf.JWS_JSON, didJwsz6Mkv.JWS_JSON] }, }) @@ -74,7 +79,7 @@ describe('JwsService', () => { it('returns false if the jws signature does not match the payload', async () => { const payload = JsonEncoder.toBuffer({ ...didJwsz6Mkf.DATA_JSON, did: 'another_did' }) - const { isValid, signerVerkeys } = await jwsService.verifyJws({ + const { isValid, signerVerkeys } = await jwsService.verifyJws(agentContext, { payload, jws: didJwsz6Mkf.JWS_JSON, }) @@ -85,7 +90,7 @@ describe('JwsService', () => { it('throws an error if the jws signatures array does not contain a JWS', async () => { await expect( - jwsService.verifyJws({ + jwsService.verifyJws(agentContext, { payload: new Buffer([]), jws: { signatures: [] }, }) diff --git a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts index 749332603f..0f216a372b 100644 --- a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts +++ b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts @@ -41,7 +41,7 @@ describe('Decorators | Signature | SignatureDecoratorUtils', () => { beforeAll(async () => { const config = getAgentConfig('SignatureDecoratorUtilsTest') - wallet = new IndyWallet(config) + wallet = new IndyWallet(config.agentDependencies, config.logger) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) }) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c5b562eb37..c2de657677 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,6 +1,7 @@ // reflect-metadata used for class-transformer + class-validator import 'reflect-metadata' +export { AgentContext } from './agent/AgentContext' export { Agent } from './agent/Agent' export { EventEmitter } from './agent/EventEmitter' export { Handler, HandlerInboundMessage } from './agent/Handler' diff --git a/packages/core/src/modules/basic-messages/BasicMessagesModule.ts b/packages/core/src/modules/basic-messages/BasicMessagesModule.ts index 8d38643c4b..796ffa2334 100644 --- a/packages/core/src/modules/basic-messages/BasicMessagesModule.ts +++ b/packages/core/src/modules/basic-messages/BasicMessagesModule.ts @@ -1,6 +1,7 @@ import type { DependencyManager } from '../../plugins' import type { BasicMessageTags } from './repository/BasicMessageRecord' +import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' @@ -17,29 +18,32 @@ export class BasicMessagesModule { private basicMessageService: BasicMessageService private messageSender: MessageSender private connectionService: ConnectionService + private agentContext: AgentContext public constructor( dispatcher: Dispatcher, basicMessageService: BasicMessageService, messageSender: MessageSender, - connectionService: ConnectionService + connectionService: ConnectionService, + agentContext: AgentContext ) { this.basicMessageService = basicMessageService this.messageSender = messageSender this.connectionService = connectionService + this.agentContext = agentContext this.registerHandlers(dispatcher) } public async sendMessage(connectionId: string, message: string) { - const connection = await this.connectionService.getById(connectionId) + const connection = await this.connectionService.getById(this.agentContext, connectionId) - const basicMessage = await this.basicMessageService.createMessage(message, connection) + const basicMessage = await this.basicMessageService.createMessage(this.agentContext, message, connection) const outboundMessage = createOutboundMessage(connection, basicMessage) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) } public async findAllByQuery(query: Partial) { - return this.basicMessageService.findAllByQuery(query) + return this.basicMessageService.findAllByQuery(this.agentContext, query) } private registerHandlers(dispatcher: Dispatcher) { diff --git a/packages/core/src/modules/basic-messages/__tests__/BasicMessageService.test.ts b/packages/core/src/modules/basic-messages/__tests__/BasicMessageService.test.ts index 8b64f2e50c..ad2fbfa547 100644 --- a/packages/core/src/modules/basic-messages/__tests__/BasicMessageService.test.ts +++ b/packages/core/src/modules/basic-messages/__tests__/BasicMessageService.test.ts @@ -1,66 +1,69 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' -import type { StorageService } from '../../../storage/StorageService' -import type { BasicMessageStateChangedEvent } from '../BasicMessageEvents' - -import { getAgentConfig, getMockConnection } from '../../../../tests/helpers' +import { getAgentContext, getMockConnection } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import { IndyStorageService } from '../../../storage/IndyStorageService' -import { Repository } from '../../../storage/Repository' -import { IndyWallet } from '../../../wallet/IndyWallet' -import { BasicMessageEventTypes } from '../BasicMessageEvents' import { BasicMessageRole } from '../BasicMessageRole' import { BasicMessage } from '../messages' import { BasicMessageRecord } from '../repository/BasicMessageRecord' +import { BasicMessageRepository } from '../repository/BasicMessageRepository' import { BasicMessageService } from '../services' +jest.mock('../repository/BasicMessageRepository') +const BasicMessageRepositoryMock = BasicMessageRepository as jest.Mock +const basicMessageRepository = new BasicMessageRepositoryMock() + +jest.mock('../../../agent/EventEmitter') +const EventEmitterMock = EventEmitter as jest.Mock +const eventEmitter = new EventEmitterMock() + +const agentContext = getAgentContext() + describe('BasicMessageService', () => { + let basicMessageService: BasicMessageService const mockConnectionRecord = getMockConnection({ id: 'd3849ac3-c981-455b-a1aa-a10bea6cead8', did: 'did:sov:C2SsBf5QUQpqSAQfhu3sd2', }) - let wallet: IndyWallet - let storageService: StorageService - let agentConfig: AgentConfig - - beforeAll(async () => { - agentConfig = getAgentConfig('BasicMessageServiceTest') - wallet = new IndyWallet(agentConfig) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(agentConfig.walletConfig!) - storageService = new IndyStorageService(wallet, agentConfig) + beforeEach(() => { + basicMessageService = new BasicMessageService(basicMessageRepository, eventEmitter) }) - afterAll(async () => { - await wallet.delete() - }) + describe('createMessage', () => { + it(`creates message and record, and emits message and basic message record`, async () => { + const message = await basicMessageService.createMessage(agentContext, 'hello', mockConnectionRecord) - describe('save', () => { - let basicMessageRepository: Repository - let basicMessageService: BasicMessageService - let eventEmitter: EventEmitter + expect(message.content).toBe('hello') - beforeEach(() => { - eventEmitter = new EventEmitter(agentConfig) - basicMessageRepository = new Repository(BasicMessageRecord, storageService, eventEmitter) - basicMessageService = new BasicMessageService(basicMessageRepository, eventEmitter) + expect(basicMessageRepository.save).toHaveBeenCalledWith(agentContext, expect.any(BasicMessageRecord)) + expect(eventEmitter.emit).toHaveBeenCalledWith(agentContext, { + type: 'BasicMessageStateChanged', + payload: { + basicMessageRecord: expect.objectContaining({ + connectionId: mockConnectionRecord.id, + id: expect.any(String), + sentTime: expect.any(String), + content: 'hello', + role: BasicMessageRole.Sender, + }), + message, + }, + }) }) + }) - it(`emits newMessage with message and basic message record`, async () => { - const eventListenerMock = jest.fn() - eventEmitter.on(BasicMessageEventTypes.BasicMessageStateChanged, eventListenerMock) - + describe('save', () => { + it(`stores record and emits message and basic message record`, async () => { const basicMessage = new BasicMessage({ id: '123', content: 'message', }) - const messageContext = new InboundMessageContext(basicMessage) + const messageContext = new InboundMessageContext(basicMessage, { agentContext }) await basicMessageService.save(messageContext, mockConnectionRecord) - expect(eventListenerMock).toHaveBeenCalledWith({ + expect(basicMessageRepository.save).toHaveBeenCalledWith(agentContext, expect.any(BasicMessageRecord)) + expect(eventEmitter.emit).toHaveBeenCalledWith(agentContext, { type: 'BasicMessageStateChanged', payload: { basicMessageRecord: expect.objectContaining({ diff --git a/packages/core/src/modules/basic-messages/services/BasicMessageService.ts b/packages/core/src/modules/basic-messages/services/BasicMessageService.ts index 749258deda..dff23b0f7e 100644 --- a/packages/core/src/modules/basic-messages/services/BasicMessageService.ts +++ b/packages/core/src/modules/basic-messages/services/BasicMessageService.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../../../agent' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { ConnectionRecord } from '../../connections/repository/ConnectionRecord' import type { BasicMessageStateChangedEvent } from '../BasicMessageEvents' @@ -21,8 +22,7 @@ export class BasicMessageService { this.eventEmitter = eventEmitter } - public async createMessage(message: string, connectionRecord: ConnectionRecord) { - connectionRecord.assertReady() + public async createMessage(agentContext: AgentContext, message: string, connectionRecord: ConnectionRecord) { const basicMessage = new BasicMessage({ content: message }) const basicMessageRecord = new BasicMessageRecord({ @@ -32,8 +32,8 @@ export class BasicMessageService { role: BasicMessageRole.Sender, }) - await this.basicMessageRepository.save(basicMessageRecord) - this.emitStateChangedEvent(basicMessageRecord, basicMessage) + await this.basicMessageRepository.save(agentContext, basicMessageRecord) + this.emitStateChangedEvent(agentContext, basicMessageRecord, basicMessage) return basicMessage } @@ -41,7 +41,7 @@ export class BasicMessageService { /** * @todo use connection from message context */ - public async save({ message }: InboundMessageContext, connection: ConnectionRecord) { + public async save({ message, agentContext }: InboundMessageContext, connection: ConnectionRecord) { const basicMessageRecord = new BasicMessageRecord({ sentTime: message.sentTime.toISOString(), content: message.content, @@ -49,19 +49,23 @@ export class BasicMessageService { role: BasicMessageRole.Receiver, }) - await this.basicMessageRepository.save(basicMessageRecord) - this.emitStateChangedEvent(basicMessageRecord, message) + await this.basicMessageRepository.save(agentContext, basicMessageRecord) + this.emitStateChangedEvent(agentContext, basicMessageRecord, message) } - private emitStateChangedEvent(basicMessageRecord: BasicMessageRecord, basicMessage: BasicMessage) { + private emitStateChangedEvent( + agentContext: AgentContext, + basicMessageRecord: BasicMessageRecord, + basicMessage: BasicMessage + ) { const clonedBasicMessageRecord = JsonTransformer.clone(basicMessageRecord) - this.eventEmitter.emit({ + this.eventEmitter.emit(agentContext, { type: BasicMessageEventTypes.BasicMessageStateChanged, payload: { message: basicMessage, basicMessageRecord: clonedBasicMessageRecord }, }) } - public async findAllByQuery(query: Partial) { - return this.basicMessageRepository.findByQuery(query) + public async findAllByQuery(agentContext: AgentContext, query: Partial) { + return this.basicMessageRepository.findByQuery(agentContext, query) } } diff --git a/packages/core/src/modules/connections/ConnectionsModule.ts b/packages/core/src/modules/connections/ConnectionsModule.ts index 1685c183c4..d8a4094c6e 100644 --- a/packages/core/src/modules/connections/ConnectionsModule.ts +++ b/packages/core/src/modules/connections/ConnectionsModule.ts @@ -1,10 +1,9 @@ -import type { Key } from '../../crypto' import type { DependencyManager } from '../../plugins' import type { OutOfBandRecord } from '../oob/repository' import type { ConnectionRecord } from './repository/ConnectionRecord' import type { Routing } from './services' -import { AgentConfig } from '../../agent/AgentConfig' +import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' @@ -35,7 +34,6 @@ import { TrustPingService } from './services/TrustPingService' @module() @injectable() export class ConnectionsModule { - private agentConfig: AgentConfig private didExchangeProtocol: DidExchangeProtocol private connectionService: ConnectionService private outOfBandService: OutOfBandService @@ -44,10 +42,10 @@ export class ConnectionsModule { private routingService: RoutingService private didRepository: DidRepository private didResolverService: DidResolverService + private agentContext: AgentContext public constructor( dispatcher: Dispatcher, - agentConfig: AgentConfig, didExchangeProtocol: DidExchangeProtocol, connectionService: ConnectionService, outOfBandService: OutOfBandService, @@ -55,9 +53,9 @@ export class ConnectionsModule { routingService: RoutingService, didRepository: DidRepository, didResolverService: DidResolverService, - messageSender: MessageSender + messageSender: MessageSender, + agentContext: AgentContext ) { - this.agentConfig = agentConfig this.didExchangeProtocol = didExchangeProtocol this.connectionService = connectionService this.outOfBandService = outOfBandService @@ -66,6 +64,8 @@ export class ConnectionsModule { this.didRepository = didRepository this.messageSender = messageSender this.didResolverService = didResolverService + this.agentContext = agentContext + this.registerHandlers(dispatcher) } @@ -82,18 +82,20 @@ export class ConnectionsModule { ) { const { protocol, label, alias, imageUrl, autoAcceptConnection } = config - const routing = config.routing || (await this.routingService.getRouting({ mediatorId: outOfBandRecord.mediatorId })) + const routing = + config.routing || + (await this.routingService.getRouting(this.agentContext, { mediatorId: outOfBandRecord.mediatorId })) let result if (protocol === HandshakeProtocol.DidExchange) { - result = await this.didExchangeProtocol.createRequest(outOfBandRecord, { + result = await this.didExchangeProtocol.createRequest(this.agentContext, outOfBandRecord, { label, alias, routing, autoAcceptConnection, }) } else if (protocol === HandshakeProtocol.Connections) { - result = await this.connectionService.createRequest(outOfBandRecord, { + result = await this.connectionService.createRequest(this.agentContext, outOfBandRecord, { label, alias, imageUrl, @@ -106,7 +108,7 @@ export class ConnectionsModule { const { message, connectionRecord } = result const outboundMessage = createOutboundMessage(connectionRecord, message, outOfBandRecord) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return connectionRecord } @@ -118,7 +120,7 @@ export class ConnectionsModule { * @returns connection record */ public async acceptRequest(connectionId: string): Promise { - const connectionRecord = await this.connectionService.findById(connectionId) + const connectionRecord = await this.connectionService.findById(this.agentContext, connectionId) if (!connectionRecord) { throw new AriesFrameworkError(`Connection record ${connectionId} not found.`) } @@ -126,21 +128,29 @@ export class ConnectionsModule { throw new AriesFrameworkError(`Connection record ${connectionId} does not have out-of-band record.`) } - const outOfBandRecord = await this.outOfBandService.findById(connectionRecord.outOfBandId) + const outOfBandRecord = await this.outOfBandService.findById(this.agentContext, connectionRecord.outOfBandId) if (!outOfBandRecord) { throw new AriesFrameworkError(`Out-of-band record ${connectionRecord.outOfBandId} not found.`) } let outboundMessage if (connectionRecord.protocol === HandshakeProtocol.DidExchange) { - const message = await this.didExchangeProtocol.createResponse(connectionRecord, outOfBandRecord) + const message = await this.didExchangeProtocol.createResponse( + this.agentContext, + connectionRecord, + outOfBandRecord + ) outboundMessage = createOutboundMessage(connectionRecord, message) } else { - const { message } = await this.connectionService.createResponse(connectionRecord, outOfBandRecord) + const { message } = await this.connectionService.createResponse( + this.agentContext, + connectionRecord, + outOfBandRecord + ) outboundMessage = createOutboundMessage(connectionRecord, message) } - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return connectionRecord } @@ -152,26 +162,30 @@ export class ConnectionsModule { * @returns connection record */ public async acceptResponse(connectionId: string): Promise { - const connectionRecord = await this.connectionService.getById(connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, connectionId) let outboundMessage if (connectionRecord.protocol === HandshakeProtocol.DidExchange) { if (!connectionRecord.outOfBandId) { throw new AriesFrameworkError(`Connection ${connectionRecord.id} does not have outOfBandId!`) } - const outOfBandRecord = await this.outOfBandService.findById(connectionRecord.outOfBandId) + const outOfBandRecord = await this.outOfBandService.findById(this.agentContext, connectionRecord.outOfBandId) if (!outOfBandRecord) { throw new AriesFrameworkError( `OutOfBand record for connection ${connectionRecord.id} with outOfBandId ${connectionRecord.outOfBandId} not found!` ) } - const message = await this.didExchangeProtocol.createComplete(connectionRecord, outOfBandRecord) + const message = await this.didExchangeProtocol.createComplete( + this.agentContext, + connectionRecord, + outOfBandRecord + ) // 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 message.setReturnRouting(ReturnRouteTypes.none) outboundMessage = createOutboundMessage(connectionRecord, message) } else { - const { message } = await this.connectionService.createTrustPing(connectionRecord, { + const { message } = await this.connectionService.createTrustPing(this.agentContext, connectionRecord, { responseRequested: false, }) // Disable return routing as we don't want to receive a response for this message over the same channel @@ -180,12 +194,12 @@ export class ConnectionsModule { outboundMessage = createOutboundMessage(connectionRecord, message) } - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return connectionRecord } public async returnWhenIsConnected(connectionId: string, options?: { timeoutMs: number }): Promise { - return this.connectionService.returnWhenIsConnected(connectionId, options?.timeoutMs) + return this.connectionService.returnWhenIsConnected(this.agentContext, connectionId, options?.timeoutMs) } /** @@ -194,7 +208,7 @@ export class ConnectionsModule { * @returns List containing all connection records */ public getAll() { - return this.connectionService.getAll() + return this.connectionService.getAll(this.agentContext) } /** @@ -206,7 +220,7 @@ export class ConnectionsModule { * */ public getById(connectionId: string): Promise { - return this.connectionService.getById(connectionId) + return this.connectionService.getById(this.agentContext, connectionId) } /** @@ -216,7 +230,7 @@ export class ConnectionsModule { * @returns The connection record or null if not found */ public findById(connectionId: string): Promise { - return this.connectionService.findById(connectionId) + return this.connectionService.findById(this.agentContext, connectionId) } /** @@ -225,31 +239,11 @@ export class ConnectionsModule { * @param connectionId the connection record id */ public async deleteById(connectionId: string) { - return this.connectionService.deleteById(connectionId) - } - - public async findByKeys({ senderKey, recipientKey }: { senderKey: Key; recipientKey: Key }) { - const theirDidRecord = await this.didRepository.findByRecipientKey(senderKey) - if (theirDidRecord) { - const ourDidRecord = await this.didRepository.findByRecipientKey(recipientKey) - if (ourDidRecord) { - const connectionRecord = await this.connectionService.findSingleByQuery({ - did: ourDidRecord.id, - theirDid: theirDidRecord.id, - }) - if (connectionRecord && connectionRecord.isReady) return connectionRecord - } - } - - this.agentConfig.logger.debug( - `No connection record found for encrypted message with recipient key ${recipientKey.fingerprint} and sender key ${senderKey.fingerprint}` - ) - - return null + return this.connectionService.deleteById(this.agentContext, connectionId) } public async findAllByOutOfBandId(outOfBandId: string) { - return this.connectionService.findAllByOutOfBandId(outOfBandId) + return this.connectionService.findAllByOutOfBandId(this.agentContext, outOfBandId) } /** @@ -261,21 +255,20 @@ export class ConnectionsModule { * @returns The connection record */ public getByThreadId(threadId: string): Promise { - return this.connectionService.getByThreadId(threadId) + return this.connectionService.getByThreadId(this.agentContext, threadId) } public async findByDid(did: string): Promise { - return this.connectionService.findByTheirDid(did) + return this.connectionService.findByTheirDid(this.agentContext, did) } public async findByInvitationDid(invitationDid: string): Promise { - return this.connectionService.findByInvitationDid(invitationDid) + return this.connectionService.findByInvitationDid(this.agentContext, invitationDid) } private registerHandlers(dispatcher: Dispatcher) { dispatcher.registerHandler( new ConnectionRequestHandler( - this.agentConfig, this.connectionService, this.outOfBandService, this.routingService, @@ -283,12 +276,7 @@ export class ConnectionsModule { ) ) dispatcher.registerHandler( - new ConnectionResponseHandler( - this.agentConfig, - this.connectionService, - this.outOfBandService, - this.didResolverService - ) + new ConnectionResponseHandler(this.connectionService, this.outOfBandService, this.didResolverService) ) dispatcher.registerHandler(new AckMessageHandler(this.connectionService)) dispatcher.registerHandler(new TrustPingMessageHandler(this.trustPingService, this.connectionService)) @@ -296,7 +284,6 @@ export class ConnectionsModule { dispatcher.registerHandler( new DidExchangeRequestHandler( - this.agentConfig, this.didExchangeProtocol, this.outOfBandService, this.routingService, @@ -306,7 +293,6 @@ export class ConnectionsModule { dispatcher.registerHandler( new DidExchangeResponseHandler( - this.agentConfig, this.didExchangeProtocol, this.outOfBandService, this.connectionService, diff --git a/packages/core/src/modules/connections/DidExchangeProtocol.ts b/packages/core/src/modules/connections/DidExchangeProtocol.ts index e5e8554a9f..a1a865ccd2 100644 --- a/packages/core/src/modules/connections/DidExchangeProtocol.ts +++ b/packages/core/src/modules/connections/DidExchangeProtocol.ts @@ -1,18 +1,19 @@ +import type { AgentContext } from '../../agent' import type { ResolvedDidCommService } from '../../agent/MessageSender' import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' -import type { Logger } from '../../logger' import type { ParsedMessageType } from '../../utils/messageType' import type { OutOfBandDidCommService } from '../oob/domain/OutOfBandDidCommService' import type { OutOfBandRecord } from '../oob/repository' import type { ConnectionRecord } from './repository' import type { Routing } from './services/ConnectionService' -import { AgentConfig } from '../../agent/AgentConfig' +import { InjectionSymbols } from '../../constants' import { Key, KeyType } from '../../crypto' import { JwsService } from '../../crypto/JwsService' import { Attachment, AttachmentData } from '../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../error' -import { injectable } from '../../plugins' +import { Logger } from '../../logger' +import { inject, injectable } from '../../plugins' import { JsonEncoder } from '../../utils/JsonEncoder' import { JsonTransformer } from '../../utils/JsonTransformer' import { DidDocument } from '../dids' @@ -23,7 +24,7 @@ import { didKeyToInstanceOfKey } from '../dids/helpers' import { DidKey } from '../dids/methods/key/DidKey' import { getNumAlgoFromPeerDid, PeerDidNumAlgo } from '../dids/methods/peer/didPeer' import { didDocumentJsonToNumAlgo1Did } from '../dids/methods/peer/peerDidNumAlgo1' -import { DidRepository, DidRecord } from '../dids/repository' +import { DidRecord, DidRepository } from '../dids/repository' import { OutOfBandRole } from '../oob/domain/OutOfBandRole' import { OutOfBandState } from '../oob/domain/OutOfBandState' @@ -32,7 +33,7 @@ import { DidExchangeProblemReportError, DidExchangeProblemReportReason } from '. import { DidExchangeCompleteMessage } from './messages/DidExchangeCompleteMessage' import { DidExchangeRequestMessage } from './messages/DidExchangeRequestMessage' import { DidExchangeResponseMessage } from './messages/DidExchangeResponseMessage' -import { HandshakeProtocol, DidExchangeRole, DidExchangeState } from './models' +import { DidExchangeRole, DidExchangeState, HandshakeProtocol } from './models' import { ConnectionService } from './services' interface DidExchangeRequestParams { @@ -46,26 +47,25 @@ interface DidExchangeRequestParams { @injectable() export class DidExchangeProtocol { - private config: AgentConfig private connectionService: ConnectionService private jwsService: JwsService private didRepository: DidRepository private logger: Logger public constructor( - config: AgentConfig, connectionService: ConnectionService, didRepository: DidRepository, - jwsService: JwsService + jwsService: JwsService, + @inject(InjectionSymbols.Logger) logger: Logger ) { - this.config = config this.connectionService = connectionService this.didRepository = didRepository this.jwsService = jwsService - this.logger = config.logger + this.logger = logger } public async createRequest( + agentContext: AgentContext, outOfBandRecord: OutOfBandRecord, params: DidExchangeRequestParams ): Promise<{ message: DidExchangeRequestMessage; connectionRecord: ConnectionRecord }> { @@ -81,7 +81,7 @@ export class DidExchangeProtocol { // We take just the first one for now. const [invitationDid] = outOfBandInvitation.invitationDids - const connectionRecord = await this.connectionService.createConnection({ + const connectionRecord = await this.connectionService.createConnection(agentContext, { protocol: HandshakeProtocol.DidExchange, role: DidExchangeRole.Requester, alias, @@ -96,15 +96,17 @@ export class DidExchangeProtocol { DidExchangeStateMachine.assertCreateMessageState(DidExchangeRequestMessage.type, connectionRecord) // Create message - const label = params.label ?? this.config.label - const didDocument = await this.createPeerDidDoc(this.routingToServices(routing)) + const label = params.label ?? agentContext.config.label + const didDocument = await this.createPeerDidDoc(agentContext, this.routingToServices(routing)) const parentThreadId = outOfBandInvitation.id const message = new DidExchangeRequestMessage({ label, parentThreadId, did: didDocument.id, goal, goalCode }) // Create sign attachment containing didDoc if (getNumAlgoFromPeerDid(didDocument.id) === PeerDidNumAlgo.GenesisDoc) { - const didDocAttach = await this.createSignedAttachment(didDocument, [routing.recipientKey.publicKeyBase58]) + const didDocAttach = await this.createSignedAttachment(agentContext, didDocument, [ + routing.recipientKey.publicKeyBase58, + ]) message.didDoc = didDocAttach } @@ -115,7 +117,7 @@ export class DidExchangeProtocol { connectionRecord.autoAcceptConnection = autoAcceptConnection } - await this.updateState(DidExchangeRequestMessage.type, connectionRecord) + await this.updateState(agentContext, DidExchangeRequestMessage.type, connectionRecord) this.logger.debug(`Create message ${DidExchangeRequestMessage.type.messageTypeUri} end`, { connectionRecord, message, @@ -163,7 +165,7 @@ export class DidExchangeProtocol { ) } - const didDocument = await this.extractDidDocument(message) + const didDocument = await this.extractDidDocument(messageContext.agentContext, message) const didRecord = new DidRecord({ id: message.did, role: DidDocumentRole.Received, @@ -184,9 +186,9 @@ export class DidExchangeProtocol { didDocument: 'omitted...', }) - await this.didRepository.save(didRecord) + await this.didRepository.save(messageContext.agentContext, didRecord) - const connectionRecord = await this.connectionService.createConnection({ + const connectionRecord = await this.connectionService.createConnection(messageContext.agentContext, { protocol: HandshakeProtocol.DidExchange, role: DidExchangeRole.Responder, state: DidExchangeState.RequestReceived, @@ -198,12 +200,13 @@ export class DidExchangeProtocol { outOfBandId: outOfBandRecord.id, }) - await this.updateState(DidExchangeRequestMessage.type, connectionRecord) + await this.updateState(messageContext.agentContext, DidExchangeRequestMessage.type, connectionRecord) this.logger.debug(`Process message ${DidExchangeRequestMessage.type.messageTypeUri} end`, connectionRecord) return connectionRecord } public async createResponse( + agentContext: AgentContext, connectionRecord: ConnectionRecord, outOfBandRecord: OutOfBandRecord, routing?: Routing @@ -233,11 +236,12 @@ export class DidExchangeProtocol { })) } - const didDocument = await this.createPeerDidDoc(services) + const didDocument = await this.createPeerDidDoc(agentContext, services) const message = new DidExchangeResponseMessage({ did: didDocument.id, threadId }) if (getNumAlgoFromPeerDid(didDocument.id) === PeerDidNumAlgo.GenesisDoc) { const didDocAttach = await this.createSignedAttachment( + agentContext, didDocument, Array.from( new Set( @@ -253,7 +257,7 @@ export class DidExchangeProtocol { connectionRecord.did = didDocument.id - await this.updateState(DidExchangeResponseMessage.type, connectionRecord) + await this.updateState(agentContext, DidExchangeResponseMessage.type, connectionRecord) this.logger.debug(`Create message ${DidExchangeResponseMessage.type.messageTypeUri} end`, { connectionRecord, message, @@ -299,6 +303,7 @@ export class DidExchangeProtocol { } const didDocument = await this.extractDidDocument( + messageContext.agentContext, message, outOfBandRecord.outOfBandInvitation.getRecipientKeys().map((key) => key.publicKeyBase58) ) @@ -320,16 +325,17 @@ export class DidExchangeProtocol { didDocument: 'omitted...', }) - await this.didRepository.save(didRecord) + await this.didRepository.save(messageContext.agentContext, didRecord) connectionRecord.theirDid = message.did - await this.updateState(DidExchangeResponseMessage.type, connectionRecord) + await this.updateState(messageContext.agentContext, DidExchangeResponseMessage.type, connectionRecord) this.logger.debug(`Process message ${DidExchangeResponseMessage.type.messageTypeUri} end`, connectionRecord) return connectionRecord } public async createComplete( + agentContext: AgentContext, connectionRecord: ConnectionRecord, outOfBandRecord: OutOfBandRecord ): Promise { @@ -351,7 +357,7 @@ export class DidExchangeProtocol { const message = new DidExchangeCompleteMessage({ threadId, parentThreadId }) - await this.updateState(DidExchangeCompleteMessage.type, connectionRecord) + await this.updateState(agentContext, DidExchangeCompleteMessage.type, connectionRecord) this.logger.debug(`Create message ${DidExchangeCompleteMessage.type.messageTypeUri} end`, { connectionRecord, message, @@ -384,18 +390,22 @@ export class DidExchangeProtocol { }) } - await this.updateState(DidExchangeCompleteMessage.type, connectionRecord) + await this.updateState(messageContext.agentContext, DidExchangeCompleteMessage.type, connectionRecord) this.logger.debug(`Process message ${DidExchangeCompleteMessage.type.messageTypeUri} end`, { connectionRecord }) return connectionRecord } - private async updateState(messageType: ParsedMessageType, connectionRecord: ConnectionRecord) { + private async updateState( + agentContext: AgentContext, + messageType: ParsedMessageType, + connectionRecord: ConnectionRecord + ) { this.logger.debug(`Updating state`, { connectionRecord }) const nextState = DidExchangeStateMachine.nextState(messageType, connectionRecord) - return this.connectionService.updateState(connectionRecord, nextState) + return this.connectionService.updateState(agentContext, connectionRecord, nextState) } - private async createPeerDidDoc(services: ResolvedDidCommService[]) { + private async createPeerDidDoc(agentContext: AgentContext, services: ResolvedDidCommService[]) { const didDocument = createDidDocumentFromServices(services) const peerDid = didDocumentJsonToNumAlgo1Did(didDocument.toJSON()) @@ -419,12 +429,12 @@ export class DidExchangeProtocol { didDocument: 'omitted...', }) - await this.didRepository.save(didRecord) + await this.didRepository.save(agentContext, didRecord) this.logger.debug('Did record created.', didRecord) return didDocument } - private async createSignedAttachment(didDoc: DidDocument, verkeys: string[]) { + private async createSignedAttachment(agentContext: AgentContext, didDoc: DidDocument, verkeys: string[]) { const didDocAttach = new Attachment({ mimeType: 'application/json', data: new AttachmentData({ @@ -438,7 +448,7 @@ export class DidExchangeProtocol { const kid = new DidKey(key).did const payload = JsonEncoder.toBuffer(didDoc) - const jws = await this.jwsService.createJws({ + const jws = await this.jwsService.createJws(agentContext, { payload, verkey, header: { @@ -460,6 +470,7 @@ export class DidExchangeProtocol { * @returns verified DID document content from message attachment */ private async extractDidDocument( + agentContext: AgentContext, message: DidExchangeRequestMessage | DidExchangeResponseMessage, invitationKeysBase58: string[] = [] ): Promise { @@ -485,7 +496,7 @@ export class DidExchangeProtocol { this.logger.trace('DidDocument JSON', json) const payload = JsonEncoder.toBuffer(json) - const { isValid, signerVerkeys } = await this.jwsService.verifyJws({ jws, payload }) + const { isValid, signerVerkeys } = await this.jwsService.verifyJws(agentContext, { jws, payload }) const didDocument = JsonTransformer.fromJSON(json, DidDocument) const didDocumentKeysBase58 = didDocument.authentication diff --git a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index b27ec3fed1..9f16191403 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -1,7 +1,16 @@ +import type { AgentContext } from '../../../agent' import type { Wallet } from '../../../wallet/Wallet' import type { Routing } from '../services/ConnectionService' -import { getAgentConfig, getMockConnection, getMockOutOfBand, mockFunction } from '../../../../tests/helpers' +import { Subject } from 'rxjs' + +import { + getAgentConfig, + getAgentContext, + getMockConnection, + getMockOutOfBand, + mockFunction, +} from '../../../../tests/helpers' import { AgentMessage } from '../../../agent/AgentMessage' import { EventEmitter } from '../../../agent/EventEmitter' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' @@ -39,21 +48,23 @@ const DidRepositoryMock = DidRepository as jest.Mock const connectionImageUrl = 'https://example.com/image.png' -describe('ConnectionService', () => { - const agentConfig = getAgentConfig('ConnectionServiceTest', { - endpoints: ['http://agent.com:8080'], - connectionImageUrl, - }) +const agentConfig = getAgentConfig('ConnectionServiceTest', { + endpoints: ['http://agent.com:8080'], + connectionImageUrl, +}) +describe('ConnectionService', () => { let wallet: Wallet let connectionRepository: ConnectionRepository let didRepository: DidRepository let connectionService: ConnectionService let eventEmitter: EventEmitter let myRouting: Routing + let agentContext: AgentContext beforeAll(async () => { - wallet = new IndyWallet(agentConfig) + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger) + agentContext = getAgentContext({ wallet, agentConfig }) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) }) @@ -63,10 +74,10 @@ describe('ConnectionService', () => { }) beforeEach(async () => { - eventEmitter = new EventEmitter(agentConfig) + eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) connectionRepository = new ConnectionRepositoryMock() didRepository = new DidRepositoryMock() - connectionService = new ConnectionService(wallet, agentConfig, connectionRepository, didRepository, eventEmitter) + connectionService = new ConnectionService(agentConfig.logger, connectionRepository, didRepository, eventEmitter) myRouting = { recipientKey: Key.fromFingerprint('z6MkwFkSP4uv5PhhKJCGehtjuZedkotC7VF64xtMsxuM8R3W'), endpoints: agentConfig.endpoints ?? [], @@ -82,7 +93,7 @@ describe('ConnectionService', () => { const outOfBand = getMockOutOfBand({ state: OutOfBandState.PrepareResponse }) const config = { routing: myRouting } - const { connectionRecord, message } = await connectionService.createRequest(outOfBand, config) + const { connectionRecord, message } = await connectionService.createRequest(agentContext, outOfBand, config) expect(connectionRecord.state).toBe(DidExchangeState.RequestSent) expect(message.label).toBe(agentConfig.label) @@ -119,7 +130,7 @@ describe('ConnectionService', () => { const outOfBand = getMockOutOfBand({ state: OutOfBandState.PrepareResponse }) const config = { label: 'Custom label', routing: myRouting } - const { message } = await connectionService.createRequest(outOfBand, config) + const { message } = await connectionService.createRequest(agentContext, outOfBand, config) expect(message.label).toBe('Custom label') }) @@ -130,7 +141,7 @@ describe('ConnectionService', () => { const outOfBand = getMockOutOfBand({ state: OutOfBandState.PrepareResponse, imageUrl: connectionImageUrl }) const config = { label: 'Custom label', routing: myRouting } - const { connectionRecord } = await connectionService.createRequest(outOfBand, config) + const { connectionRecord } = await connectionService.createRequest(agentContext, outOfBand, config) expect(connectionRecord.imageUrl).toBe(connectionImageUrl) }) @@ -141,7 +152,7 @@ describe('ConnectionService', () => { const outOfBand = getMockOutOfBand({ state: OutOfBandState.PrepareResponse }) const config = { imageUrl: 'custom-image-url', routing: myRouting } - const { message } = await connectionService.createRequest(outOfBand, config) + const { message } = await connectionService.createRequest(agentContext, outOfBand, config) expect(message.imageUrl).toBe('custom-image-url') }) @@ -152,7 +163,7 @@ describe('ConnectionService', () => { const outOfBand = getMockOutOfBand({ role: OutOfBandRole.Sender, state: OutOfBandState.PrepareResponse }) const config = { routing: myRouting } - return expect(connectionService.createRequest(outOfBand, config)).rejects.toThrowError( + return expect(connectionService.createRequest(agentContext, outOfBand, config)).rejects.toThrowError( `Invalid out-of-band record role ${OutOfBandRole.Sender}, expected is ${OutOfBandRole.Receiver}.` ) }) @@ -166,7 +177,7 @@ describe('ConnectionService', () => { const outOfBand = getMockOutOfBand({ state }) const config = { routing: myRouting } - return expect(connectionService.createRequest(outOfBand, config)).rejects.toThrowError( + return expect(connectionService.createRequest(agentContext, outOfBand, config)).rejects.toThrowError( `Invalid out-of-band record state ${state}, valid states are: ${OutOfBandState.PrepareResponse}.` ) } @@ -208,6 +219,7 @@ describe('ConnectionService', () => { }) const messageContext = new InboundMessageContext(connectionRequest, { + agentContext, senderKey: theirKey, recipientKey: Key.fromPublicKeyBase58('8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K', KeyType.Ed25519), }) @@ -265,6 +277,7 @@ describe('ConnectionService', () => { }) const messageContext = new InboundMessageContext(connectionRequest, { + agentContext, connection: connectionRecord, senderKey: theirKey, recipientKey: Key.fromPublicKeyBase58('8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K', KeyType.Ed25519), @@ -297,6 +310,7 @@ describe('ConnectionService', () => { }) const messageContext = new InboundMessageContext(connectionRequest, { + agentContext, recipientKey: Key.fromPublicKeyBase58('8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K', KeyType.Ed25519), senderKey: Key.fromPublicKeyBase58('79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ', KeyType.Ed25519), }) @@ -312,6 +326,7 @@ describe('ConnectionService', () => { expect.assertions(1) const inboundMessage = new InboundMessageContext(jest.fn()(), { + agentContext, recipientKey: Key.fromPublicKeyBase58('8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K', KeyType.Ed25519), senderKey: Key.fromPublicKeyBase58('79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ', KeyType.Ed25519), }) @@ -329,7 +344,7 @@ describe('ConnectionService', () => { (state) => { expect.assertions(1) - const inboundMessage = new InboundMessageContext(jest.fn()(), {}) + const inboundMessage = new InboundMessageContext(jest.fn()(), { agentContext }) const outOfBand = getMockOutOfBand({ role: OutOfBandRole.Sender, state }) return expect(connectionService.processRequest(inboundMessage, outOfBand)).rejects.toThrowError( @@ -376,6 +391,7 @@ describe('ConnectionService', () => { }) const { message, connectionRecord: connectionRecord } = await connectionService.createResponse( + agentContext, mockConnection, outOfBand ) @@ -398,7 +414,7 @@ describe('ConnectionService', () => { state: DidExchangeState.RequestReceived, }) const outOfBand = getMockOutOfBand() - return expect(connectionService.createResponse(connection, outOfBand)).rejects.toThrowError( + return expect(connectionService.createResponse(agentContext, connection, outOfBand)).rejects.toThrowError( `Connection record has invalid role ${DidExchangeRole.Requester}. Expected role ${DidExchangeRole.Responder}.` ) }) @@ -420,7 +436,7 @@ describe('ConnectionService', () => { const connection = getMockConnection({ state }) const outOfBand = getMockOutOfBand() - return expect(connectionService.createResponse(connection, outOfBand)).rejects.toThrowError( + return expect(connectionService.createResponse(agentContext, connection, outOfBand)).rejects.toThrowError( `Connection record is in invalid state ${state}. Valid states are: ${DidExchangeState.RequestReceived}.` ) } @@ -478,6 +494,7 @@ describe('ConnectionService', () => { recipientKeys: [new DidKey(theirKey).did], }) const messageContext = new InboundMessageContext(connectionResponse, { + agentContext, connection: connectionRecord, senderKey: theirKey, recipientKey: Key.fromPublicKeyBase58(verkey, KeyType.Ed25519), @@ -501,6 +518,7 @@ describe('ConnectionService', () => { state: DidExchangeState.RequestSent, }) const messageContext = new InboundMessageContext(jest.fn()(), { + agentContext, connection: connectionRecord, recipientKey: Key.fromPublicKeyBase58('8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K', KeyType.Ed25519), senderKey: Key.fromPublicKeyBase58('79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ', KeyType.Ed25519), @@ -561,6 +579,7 @@ describe('ConnectionService', () => { recipientKeys: [new DidKey(Key.fromPublicKeyBase58(verkey, KeyType.Ed25519)).did], }) const messageContext = new InboundMessageContext(connectionResponse, { + agentContext, connection: connectionRecord, senderKey: theirKey, recipientKey: Key.fromPublicKeyBase58(verkey, KeyType.Ed25519), @@ -594,6 +613,7 @@ describe('ConnectionService', () => { const outOfBandRecord = getMockOutOfBand({ recipientKeys: [new DidKey(theirKey).did] }) const messageContext = new InboundMessageContext(connectionResponse, { + agentContext, connection: connectionRecord, recipientKey: Key.fromPublicKeyBase58('8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K', KeyType.Ed25519), senderKey: Key.fromPublicKeyBase58('79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ', KeyType.Ed25519), @@ -611,7 +631,10 @@ describe('ConnectionService', () => { const mockConnection = getMockConnection({ state: DidExchangeState.ResponseReceived }) - const { message, connectionRecord: connectionRecord } = await connectionService.createTrustPing(mockConnection) + const { message, connectionRecord: connectionRecord } = await connectionService.createTrustPing( + agentContext, + mockConnection + ) expect(connectionRecord.state).toBe(DidExchangeState.Completed) expect(message).toEqual(expect.any(TrustPingMessage)) @@ -632,7 +655,7 @@ describe('ConnectionService', () => { expect.assertions(1) const connection = getMockConnection({ state }) - return expect(connectionService.createTrustPing(connection)).rejects.toThrowError( + return expect(connectionService.createTrustPing(agentContext, connection)).rejects.toThrowError( `Connection record is in invalid state ${state}. Valid states are: ${DidExchangeState.ResponseReceived}, ${DidExchangeState.Completed}.` ) } @@ -648,7 +671,7 @@ describe('ConnectionService', () => { threadId: 'thread-id', }) - const messageContext = new InboundMessageContext(ack, {}) + const messageContext = new InboundMessageContext(ack, { agentContext }) return expect(connectionService.processAck(messageContext)).rejects.toThrowError( 'Unable to process connection ack: connection for recipient key undefined not found' @@ -668,7 +691,7 @@ describe('ConnectionService', () => { threadId: 'thread-id', }) - const messageContext = new InboundMessageContext(ack, { connection }) + const messageContext = new InboundMessageContext(ack, { agentContext, connection }) const updatedConnection = await connectionService.processAck(messageContext) @@ -688,7 +711,7 @@ describe('ConnectionService', () => { threadId: 'thread-id', }) - const messageContext = new InboundMessageContext(ack, { connection }) + const messageContext = new InboundMessageContext(ack, { agentContext, connection }) const updatedConnection = await connectionService.processAck(messageContext) @@ -701,6 +724,7 @@ describe('ConnectionService', () => { expect.assertions(1) const messageContext = new InboundMessageContext(new AgentMessage(), { + agentContext, connection: getMockConnection({ state: DidExchangeState.Completed }), }) @@ -711,6 +735,7 @@ describe('ConnectionService', () => { expect.assertions(1) const messageContext = new InboundMessageContext(new AgentMessage(), { + agentContext, connection: getMockConnection({ state: DidExchangeState.InvitationReceived }), }) @@ -728,7 +753,7 @@ describe('ConnectionService', () => { serviceEndpoint: '', routingKeys: [], }) - const messageContext = new InboundMessageContext(message) + const messageContext = new InboundMessageContext(message, { agentContext }) expect(() => connectionService.assertConnectionOrServiceDecorator(messageContext)).not.toThrow() }) @@ -759,7 +784,7 @@ describe('ConnectionService', () => { serviceEndpoint: '', routingKeys: [], }) - const messageContext = new InboundMessageContext(message, { recipientKey, senderKey }) + const messageContext = new InboundMessageContext(message, { agentContext, recipientKey, senderKey }) expect(() => connectionService.assertConnectionOrServiceDecorator(messageContext, { @@ -780,7 +805,7 @@ describe('ConnectionService', () => { }) const message = new AgentMessage() - const messageContext = new InboundMessageContext(message) + const messageContext = new InboundMessageContext(message, { agentContext }) expect(() => connectionService.assertConnectionOrServiceDecorator(messageContext, { @@ -802,7 +827,7 @@ describe('ConnectionService', () => { }) const message = new AgentMessage() - const messageContext = new InboundMessageContext(message, { recipientKey }) + const messageContext = new InboundMessageContext(message, { agentContext, recipientKey }) expect(() => connectionService.assertConnectionOrServiceDecorator(messageContext, { @@ -824,7 +849,7 @@ describe('ConnectionService', () => { }) const message = new AgentMessage() - const messageContext = new InboundMessageContext(message) + const messageContext = new InboundMessageContext(message, { agentContext }) expect(() => connectionService.assertConnectionOrServiceDecorator(messageContext, { @@ -847,6 +872,7 @@ describe('ConnectionService', () => { const message = new AgentMessage() const messageContext = new InboundMessageContext(message, { + agentContext, senderKey: Key.fromPublicKeyBase58(senderKey, KeyType.Ed25519), }) @@ -864,8 +890,8 @@ describe('ConnectionService', () => { it('getById should return value from connectionRepository.getById', async () => { const expected = getMockConnection() mockFunction(connectionRepository.getById).mockReturnValue(Promise.resolve(expected)) - const result = await connectionService.getById(expected.id) - expect(connectionRepository.getById).toBeCalledWith(expected.id) + const result = await connectionService.getById(agentContext, expected.id) + expect(connectionRepository.getById).toBeCalledWith(agentContext, expected.id) expect(result).toBe(expected) }) @@ -873,8 +899,8 @@ describe('ConnectionService', () => { it('getByThreadId should return value from connectionRepository.getSingleByQuery', async () => { const expected = getMockConnection() mockFunction(connectionRepository.getByThreadId).mockReturnValue(Promise.resolve(expected)) - const result = await connectionService.getByThreadId('threadId') - expect(connectionRepository.getByThreadId).toBeCalledWith('threadId') + const result = await connectionService.getByThreadId(agentContext, 'threadId') + expect(connectionRepository.getByThreadId).toBeCalledWith(agentContext, 'threadId') expect(result).toBe(expected) }) @@ -882,8 +908,8 @@ describe('ConnectionService', () => { it('findById should return value from connectionRepository.findById', async () => { const expected = getMockConnection() mockFunction(connectionRepository.findById).mockReturnValue(Promise.resolve(expected)) - const result = await connectionService.findById(expected.id) - expect(connectionRepository.findById).toBeCalledWith(expected.id) + const result = await connectionService.findById(agentContext, expected.id) + expect(connectionRepository.findById).toBeCalledWith(agentContext, expected.id) expect(result).toBe(expected) }) @@ -892,8 +918,8 @@ describe('ConnectionService', () => { const expected = [getMockConnection(), getMockConnection()] mockFunction(connectionRepository.getAll).mockReturnValue(Promise.resolve(expected)) - const result = await connectionService.getAll() - expect(connectionRepository.getAll).toBeCalledWith() + const result = await connectionService.getAll(agentContext) + expect(connectionRepository.getAll).toBeCalledWith(agentContext) expect(result).toEqual(expect.arrayContaining(expected)) }) diff --git a/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts index b9197814c1..1f55bea49a 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts @@ -1,4 +1,3 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { DidRepository } from '../../dids/repository' import type { OutOfBandService } from '../../oob/OutOfBandService' @@ -10,7 +9,6 @@ import { AriesFrameworkError } from '../../../error/AriesFrameworkError' import { ConnectionRequestMessage } from '../messages' export class ConnectionRequestHandler implements Handler { - private agentConfig: AgentConfig private connectionService: ConnectionService private outOfBandService: OutOfBandService private routingService: RoutingService @@ -18,13 +16,11 @@ export class ConnectionRequestHandler implements Handler { public supportedMessages = [ConnectionRequestMessage] public constructor( - agentConfig: AgentConfig, connectionService: ConnectionService, outOfBandService: OutOfBandService, routingService: RoutingService, didRepository: DidRepository ) { - this.agentConfig = agentConfig this.connectionService = connectionService this.outOfBandService = outOfBandService this.routingService = routingService @@ -38,7 +34,7 @@ export class ConnectionRequestHandler implements Handler { throw new AriesFrameworkError('Unable to process connection request without senderVerkey or recipientKey') } - const outOfBandRecord = await this.outOfBandService.findByRecipientKey(recipientKey) + const outOfBandRecord = await this.outOfBandService.findByRecipientKey(messageContext.agentContext, recipientKey) if (!outOfBandRecord) { throw new AriesFrameworkError(`Out-of-band record for recipient key ${recipientKey.fingerprint} was not found.`) @@ -50,18 +46,25 @@ export class ConnectionRequestHandler implements Handler { ) } - const didRecord = await this.didRepository.findByRecipientKey(senderKey) + const didRecord = await this.didRepository.findByRecipientKey(messageContext.agentContext, senderKey) if (didRecord) { throw new AriesFrameworkError(`Did record for sender key ${senderKey.fingerprint} already exists.`) } const connectionRecord = await this.connectionService.processRequest(messageContext, outOfBandRecord) - if (connectionRecord?.autoAcceptConnection ?? this.agentConfig.autoAcceptConnections) { + if (connectionRecord?.autoAcceptConnection ?? messageContext.agentContext.config.autoAcceptConnections) { // TODO: Allow rotation of keys used in the invitation for new ones not only when out-of-band is reusable - const routing = outOfBandRecord.reusable ? await this.routingService.getRouting() : undefined + const routing = outOfBandRecord.reusable + ? await this.routingService.getRouting(messageContext.agentContext) + : undefined - const { message } = await this.connectionService.createResponse(connectionRecord, outOfBandRecord, routing) + const { message } = await this.connectionService.createResponse( + messageContext.agentContext, + connectionRecord, + outOfBandRecord, + routing + ) return createOutboundMessage(connectionRecord, message, outOfBandRecord) } } diff --git a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts index 48ca476cb4..3d36029345 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts @@ -1,4 +1,3 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { DidResolverService } from '../../dids' import type { OutOfBandService } from '../../oob/OutOfBandService' @@ -10,7 +9,6 @@ import { AriesFrameworkError } from '../../../error' import { ConnectionResponseMessage } from '../messages' export class ConnectionResponseHandler implements Handler { - private agentConfig: AgentConfig private connectionService: ConnectionService private outOfBandService: OutOfBandService private didResolverService: DidResolverService @@ -18,12 +16,10 @@ export class ConnectionResponseHandler implements Handler { public supportedMessages = [ConnectionResponseMessage] public constructor( - agentConfig: AgentConfig, connectionService: ConnectionService, outOfBandService: OutOfBandService, didResolverService: DidResolverService ) { - this.agentConfig = agentConfig this.connectionService = connectionService this.outOfBandService = outOfBandService this.didResolverService = didResolverService @@ -36,7 +32,7 @@ export class ConnectionResponseHandler implements Handler { throw new AriesFrameworkError('Unable to process connection response without senderKey or recipientKey') } - const connectionRecord = await this.connectionService.getByThreadId(message.threadId) + const connectionRecord = await this.connectionService.getByThreadId(messageContext.agentContext, message.threadId) if (!connectionRecord) { throw new AriesFrameworkError(`Connection for thread ID ${message.threadId} not found!`) } @@ -45,7 +41,10 @@ export class ConnectionResponseHandler implements Handler { throw new AriesFrameworkError(`Connection record ${connectionRecord.id} has no 'did'`) } - const ourDidDocument = await this.didResolverService.resolveDidDocument(connectionRecord.did) + const ourDidDocument = await this.didResolverService.resolveDidDocument( + messageContext.agentContext, + connectionRecord.did + ) if (!ourDidDocument) { throw new AriesFrameworkError(`Did document for did ${connectionRecord.did} was not resolved!`) } @@ -59,7 +58,8 @@ export class ConnectionResponseHandler implements Handler { } const outOfBandRecord = - connectionRecord.outOfBandId && (await this.outOfBandService.findById(connectionRecord.outOfBandId)) + connectionRecord.outOfBandId && + (await this.outOfBandService.findById(messageContext.agentContext, connectionRecord.outOfBandId)) if (!outOfBandRecord) { throw new AriesFrameworkError(`Out-of-band record ${connectionRecord.outOfBandId} was not found.`) @@ -72,8 +72,10 @@ export class ConnectionResponseHandler implements Handler { // TODO: should we only send ping message in case of autoAcceptConnection or always? // In AATH we have a separate step to send the ping. So for now we'll only do it // if auto accept is enable - if (connection.autoAcceptConnection ?? this.agentConfig.autoAcceptConnections) { - const { message } = await this.connectionService.createTrustPing(connection, { responseRequested: false }) + if (connection.autoAcceptConnection ?? messageContext.agentContext.config.autoAcceptConnections) { + const { message } = await this.connectionService.createTrustPing(messageContext.agentContext, connection, { + responseRequested: false, + }) // 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 diff --git a/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts index d3f4a6eae6..e138dfc49e 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts @@ -35,14 +35,17 @@ export class DidExchangeCompleteHandler implements Handler { if (!message.thread?.parentThreadId) { throw new AriesFrameworkError(`Message does not contain pthid attribute`) } - const outOfBandRecord = await this.outOfBandService.findByInvitationId(message.thread?.parentThreadId) + const outOfBandRecord = await this.outOfBandService.findByInvitationId( + messageContext.agentContext, + message.thread?.parentThreadId + ) if (!outOfBandRecord) { throw new AriesFrameworkError(`OutOfBand record for message ID ${message.thread?.parentThreadId} not found!`) } if (!outOfBandRecord.reusable) { - await this.outOfBandService.updateState(outOfBandRecord, OutOfBandState.Done) + await this.outOfBandService.updateState(messageContext.agentContext, outOfBandRecord, OutOfBandState.Done) } await this.didExchangeProtocol.processComplete(messageContext, outOfBandRecord) } diff --git a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts index c7fdc5699c..3c18a8dc84 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts @@ -1,4 +1,3 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { DidRepository } from '../../dids/repository' import type { OutOfBandService } from '../../oob/OutOfBandService' @@ -13,19 +12,16 @@ import { DidExchangeRequestMessage } from '../messages' export class DidExchangeRequestHandler implements Handler { private didExchangeProtocol: DidExchangeProtocol private outOfBandService: OutOfBandService - private agentConfig: AgentConfig private routingService: RoutingService private didRepository: DidRepository public supportedMessages = [DidExchangeRequestMessage] public constructor( - agentConfig: AgentConfig, didExchangeProtocol: DidExchangeProtocol, outOfBandService: OutOfBandService, routingService: RoutingService, didRepository: DidRepository ) { - this.agentConfig = agentConfig this.didExchangeProtocol = didExchangeProtocol this.outOfBandService = outOfBandService this.routingService = routingService @@ -42,7 +38,10 @@ export class DidExchangeRequestHandler implements Handler { if (!message.thread?.parentThreadId) { throw new AriesFrameworkError(`Message does not contain 'pthid' attribute`) } - const outOfBandRecord = await this.outOfBandService.findByInvitationId(message.thread.parentThreadId) + const outOfBandRecord = await this.outOfBandService.findByInvitationId( + messageContext.agentContext, + message.thread.parentThreadId + ) if (!outOfBandRecord) { throw new AriesFrameworkError(`OutOfBand record for message ID ${message.thread?.parentThreadId} not found!`) @@ -54,7 +53,7 @@ export class DidExchangeRequestHandler implements Handler { ) } - const didRecord = await this.didRepository.findByRecipientKey(senderKey) + const didRecord = await this.didRepository.findByRecipientKey(messageContext.agentContext, senderKey) if (didRecord) { throw new AriesFrameworkError(`Did record for sender key ${senderKey.fingerprint} already exists.`) } @@ -69,12 +68,19 @@ export class DidExchangeRequestHandler implements Handler { const connectionRecord = await this.didExchangeProtocol.processRequest(messageContext, outOfBandRecord) - if (connectionRecord?.autoAcceptConnection ?? this.agentConfig.autoAcceptConnections) { + if (connectionRecord?.autoAcceptConnection ?? messageContext.agentContext.config.autoAcceptConnections) { // TODO We should add an option to not pass routing and therefore do not rotate keys and use the keys from the invitation // TODO: Allow rotation of keys used in the invitation for new ones not only when out-of-band is reusable - const routing = outOfBandRecord.reusable ? await this.routingService.getRouting() : undefined + const routing = outOfBandRecord.reusable + ? await this.routingService.getRouting(messageContext.agentContext) + : undefined - const message = await this.didExchangeProtocol.createResponse(connectionRecord, outOfBandRecord, routing) + const message = await this.didExchangeProtocol.createResponse( + messageContext.agentContext, + connectionRecord, + outOfBandRecord, + routing + ) return createOutboundMessage(connectionRecord, message, outOfBandRecord) } } diff --git a/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts index c1587ac27f..c43ca94a79 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts @@ -1,4 +1,3 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { DidResolverService } from '../../dids' import type { OutOfBandService } from '../../oob/OutOfBandService' @@ -13,7 +12,6 @@ import { DidExchangeResponseMessage } from '../messages' import { HandshakeProtocol } from '../models' export class DidExchangeResponseHandler implements Handler { - private agentConfig: AgentConfig private didExchangeProtocol: DidExchangeProtocol private outOfBandService: OutOfBandService private connectionService: ConnectionService @@ -21,13 +19,11 @@ export class DidExchangeResponseHandler implements Handler { public supportedMessages = [DidExchangeResponseMessage] public constructor( - agentConfig: AgentConfig, didExchangeProtocol: DidExchangeProtocol, outOfBandService: OutOfBandService, connectionService: ConnectionService, didResolverService: DidResolverService ) { - this.agentConfig = agentConfig this.didExchangeProtocol = didExchangeProtocol this.outOfBandService = outOfBandService this.connectionService = connectionService @@ -41,7 +37,7 @@ export class DidExchangeResponseHandler implements Handler { throw new AriesFrameworkError('Unable to process connection response without sender key or recipient key') } - const connectionRecord = await this.connectionService.getByThreadId(message.threadId) + const connectionRecord = await this.connectionService.getByThreadId(messageContext.agentContext, message.threadId) if (!connectionRecord) { throw new AriesFrameworkError(`Connection for thread ID ${message.threadId} not found!`) } @@ -50,7 +46,10 @@ export class DidExchangeResponseHandler implements Handler { throw new AriesFrameworkError(`Connection record ${connectionRecord.id} has no 'did'`) } - const ourDidDocument = await this.didResolverService.resolveDidDocument(connectionRecord.did) + const ourDidDocument = await this.didResolverService.resolveDidDocument( + messageContext.agentContext, + connectionRecord.did + ) if (!ourDidDocument) { throw new AriesFrameworkError(`Did document for did ${connectionRecord.did} was not resolved`) } @@ -74,7 +73,10 @@ export class DidExchangeResponseHandler implements Handler { throw new AriesFrameworkError(`Connection ${connectionRecord.id} does not have outOfBandId!`) } - const outOfBandRecord = await this.outOfBandService.findById(connectionRecord.outOfBandId) + const outOfBandRecord = await this.outOfBandService.findById( + messageContext.agentContext, + connectionRecord.outOfBandId + ) if (!outOfBandRecord) { throw new AriesFrameworkError( @@ -95,15 +97,19 @@ export class DidExchangeResponseHandler implements Handler { // TODO: should we only send complete message in case of autoAcceptConnection or always? // In AATH we have a separate step to send the complete. So for now we'll only do it - // if auto accept is enable - if (connection.autoAcceptConnection ?? this.agentConfig.autoAcceptConnections) { - const message = await this.didExchangeProtocol.createComplete(connection, outOfBandRecord) + // if auto accept is enabled + if (connection.autoAcceptConnection ?? messageContext.agentContext.config.autoAcceptConnections) { + const message = await this.didExchangeProtocol.createComplete( + messageContext.agentContext, + connection, + outOfBandRecord + ) // 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 message.setReturnRouting(ReturnRouteTypes.none) if (!outOfBandRecord.reusable) { - await this.outOfBandService.updateState(outOfBandRecord, OutOfBandState.Done) + await this.outOfBandService.updateState(messageContext.agentContext, outOfBandRecord, OutOfBandState.Done) } return createOutboundMessage(connection, message) } diff --git a/packages/core/src/modules/connections/handlers/TrustPingMessageHandler.ts b/packages/core/src/modules/connections/handlers/TrustPingMessageHandler.ts index 6a37fee4b6..aec2f74ea5 100644 --- a/packages/core/src/modules/connections/handlers/TrustPingMessageHandler.ts +++ b/packages/core/src/modules/connections/handlers/TrustPingMessageHandler.ts @@ -25,7 +25,7 @@ export class TrustPingMessageHandler implements Handler { // TODO: This is better addressed in a middleware of some kind because // any message can transition the state to complete, not just an ack or trust ping if (connection.state === DidExchangeState.ResponseSent) { - await this.connectionService.updateState(connection, DidExchangeState.Completed) + await this.connectionService.updateState(messageContext.agentContext, connection, DidExchangeState.Completed) } return this.trustPingService.processPing(messageContext, connection) diff --git a/packages/core/src/modules/connections/repository/ConnectionRepository.ts b/packages/core/src/modules/connections/repository/ConnectionRepository.ts index 2ede9851c7..504b9ea655 100644 --- a/packages/core/src/modules/connections/repository/ConnectionRepository.ts +++ b/packages/core/src/modules/connections/repository/ConnectionRepository.ts @@ -1,6 +1,8 @@ +import type { AgentContext } from '../../../agent' + import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' -import { inject, injectable } from '../../../plugins' +import { injectable, inject } from '../../../plugins' import { Repository } from '../../../storage/Repository' import { StorageService } from '../../../storage/StorageService' @@ -15,14 +17,14 @@ export class ConnectionRepository extends Repository { super(ConnectionRecord, storageService, eventEmitter) } - public async findByDids({ ourDid, theirDid }: { ourDid: string; theirDid: string }) { - return this.findSingleByQuery({ + public async findByDids(agentContext: AgentContext, { ourDid, theirDid }: { ourDid: string; theirDid: string }) { + return this.findSingleByQuery(agentContext, { did: ourDid, theirDid, }) } - public getByThreadId(threadId: string): Promise { - return this.getSingleByQuery({ threadId }) + public getByThreadId(agentContext: AgentContext, threadId: string): Promise { + return this.getSingleByQuery(agentContext, { threadId }) } } diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index 07fa99743b..8af388010c 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -1,6 +1,6 @@ +import type { AgentContext } from '../../../agent' import type { AgentMessage } from '../../../agent/AgentMessage' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import type { Logger } from '../../../logger' import type { AckMessage } from '../../common' import type { OutOfBandDidCommService } from '../../oob/domain/OutOfBandDidCommService' import type { OutOfBandRecord } from '../../oob/repository' @@ -11,21 +11,20 @@ import type { ConnectionRecordProps } from '../repository/ConnectionRecord' import { firstValueFrom, ReplaySubject } from 'rxjs' import { first, map, timeout } from 'rxjs/operators' -import { AgentConfig } from '../../../agent/AgentConfig' import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' import { Key } from '../../../crypto' import { signData, unpackAndVerifySignatureDecorator } from '../../../decorators/signature/SignatureDecoratorUtils' import { AriesFrameworkError } from '../../../error' +import { Logger } from '../../../logger' import { inject, injectable } from '../../../plugins' import { JsonTransformer } from '../../../utils/JsonTransformer' import { indyDidFromPublicKeyBase58 } from '../../../utils/did' -import { Wallet } from '../../../wallet/Wallet' import { DidKey, IndyAgentService } from '../../dids' import { DidDocumentRole } from '../../dids/domain/DidDocumentRole' import { didKeyToVerkey } from '../../dids/helpers' import { didDocumentJsonToNumAlgo1Did } from '../../dids/methods/peer/peerDidNumAlgo1' -import { DidRepository, DidRecord } from '../../dids/repository' +import { DidRecord, DidRepository } from '../../dids/repository' import { DidRecordMetadataKeys } from '../../dids/repository/didRecordMetadataTypes' import { OutOfBandRole } from '../../oob/domain/OutOfBandRole' import { OutOfBandState } from '../../oob/domain/OutOfBandState' @@ -33,14 +32,14 @@ import { ConnectionEventTypes } from '../ConnectionEvents' import { ConnectionProblemReportError, ConnectionProblemReportReason } from '../errors' import { ConnectionRequestMessage, ConnectionResponseMessage, TrustPingMessage } from '../messages' import { - DidExchangeRole, - DidExchangeState, + authenticationTypes, Connection, DidDoc, + DidExchangeRole, + DidExchangeState, Ed25119Sig2018, HandshakeProtocol, ReferencedAuthentication, - authenticationTypes, } from '../models' import { ConnectionRecord } from '../repository/ConnectionRecord' import { ConnectionRepository } from '../repository/ConnectionRepository' @@ -57,26 +56,21 @@ export interface ConnectionRequestParams { @injectable() export class ConnectionService { - private wallet: Wallet - private config: AgentConfig private connectionRepository: ConnectionRepository private didRepository: DidRepository private eventEmitter: EventEmitter private logger: Logger public constructor( - @inject(InjectionSymbols.Wallet) wallet: Wallet, - config: AgentConfig, + @inject(InjectionSymbols.Logger) logger: Logger, connectionRepository: ConnectionRepository, didRepository: DidRepository, eventEmitter: EventEmitter ) { - this.wallet = wallet - this.config = config this.connectionRepository = connectionRepository this.didRepository = didRepository this.eventEmitter = eventEmitter - this.logger = config.logger + this.logger = logger } /** @@ -87,6 +81,7 @@ export class ConnectionService { * @returns outbound message containing connection request */ public async createRequest( + agentContext: AgentContext, outOfBandRecord: OutOfBandRecord, config: ConnectionRequestParams ): Promise> { @@ -105,12 +100,12 @@ export class ConnectionService { // We take just the first one for now. const [invitationDid] = outOfBandInvitation.invitationDids - const { did: peerDid } = await this.createDid({ + const { did: peerDid } = await this.createDid(agentContext, { role: DidDocumentRole.Created, didDoc, }) - const connectionRecord = await this.createConnection({ + const connectionRecord = await this.createConnection(agentContext, { protocol: HandshakeProtocol.Connections, role: DidExchangeRole.Requester, state: DidExchangeState.InvitationReceived, @@ -127,10 +122,10 @@ export class ConnectionService { const { label, imageUrl, autoAcceptConnection } = config const connectionRequest = new ConnectionRequestMessage({ - label: label ?? this.config.label, + label: label ?? agentContext.config.label, did: didDoc.id, didDoc, - imageUrl: imageUrl ?? this.config.connectionImageUrl, + imageUrl: imageUrl ?? agentContext.config.connectionImageUrl, }) if (autoAcceptConnection !== undefined || autoAcceptConnection !== null) { @@ -138,7 +133,7 @@ export class ConnectionService { } connectionRecord.threadId = connectionRequest.id - await this.updateState(connectionRecord, DidExchangeState.RequestSent) + await this.updateState(agentContext, connectionRecord, DidExchangeState.RequestSent) return { connectionRecord, @@ -150,7 +145,9 @@ export class ConnectionService { messageContext: InboundMessageContext, outOfBandRecord: OutOfBandRecord ): Promise { - this.logger.debug(`Process message ${ConnectionRequestMessage.type.messageTypeUri} start`, messageContext) + this.logger.debug(`Process message ${ConnectionRequestMessage.type.messageTypeUri} start`, { + message: messageContext.message, + }) outOfBandRecord.assertRole(OutOfBandRole.Sender) outOfBandRecord.assertState(OutOfBandState.AwaitResponse) @@ -163,12 +160,12 @@ export class ConnectionService { }) } - const { did: peerDid } = await this.createDid({ + const { did: peerDid } = await this.createDid(messageContext.agentContext, { role: DidDocumentRole.Received, didDoc: message.connection.didDoc, }) - const connectionRecord = await this.createConnection({ + const connectionRecord = await this.createConnection(messageContext.agentContext, { protocol: HandshakeProtocol.Connections, role: DidExchangeRole.Responder, state: DidExchangeState.RequestReceived, @@ -181,8 +178,8 @@ export class ConnectionService { autoAcceptConnection: outOfBandRecord.autoAcceptConnection, }) - await this.connectionRepository.update(connectionRecord) - this.emitStateChangedEvent(connectionRecord, null) + await this.connectionRepository.update(messageContext.agentContext, connectionRecord) + this.emitStateChangedEvent(messageContext.agentContext, connectionRecord, null) this.logger.debug(`Process message ${ConnectionRequestMessage.type.messageTypeUri} end`, connectionRecord) return connectionRecord @@ -195,6 +192,7 @@ export class ConnectionService { * @returns outbound message containing connection response */ public async createResponse( + agentContext: AgentContext, connectionRecord: ConnectionRecord, outOfBandRecord: OutOfBandRecord, routing?: Routing @@ -211,7 +209,7 @@ export class ConnectionService { ) ) - const { did: peerDid } = await this.createDid({ + const { did: peerDid } = await this.createDid(agentContext, { role: DidDocumentRole.Created, didDoc, }) @@ -231,11 +229,11 @@ export class ConnectionService { const connectionResponse = new ConnectionResponseMessage({ threadId: connectionRecord.threadId, - connectionSig: await signData(connectionJson, this.wallet, signingKey), + connectionSig: await signData(connectionJson, agentContext.wallet, signingKey), }) connectionRecord.did = peerDid - await this.updateState(connectionRecord, DidExchangeState.ResponseSent) + await this.updateState(agentContext, connectionRecord, DidExchangeState.ResponseSent) this.logger.debug(`Create message ${ConnectionResponseMessage.type.messageTypeUri} end`, { connectionRecord, @@ -260,7 +258,9 @@ export class ConnectionService { messageContext: InboundMessageContext, outOfBandRecord: OutOfBandRecord ): Promise { - this.logger.debug(`Process message ${ConnectionResponseMessage.type.messageTypeUri} start`, messageContext) + this.logger.debug(`Process message ${ConnectionResponseMessage.type.messageTypeUri} start`, { + message: messageContext.message, + }) const { connection: connectionRecord, message, recipientKey, senderKey } = messageContext if (!recipientKey || !senderKey) { @@ -276,7 +276,10 @@ export class ConnectionService { let connectionJson = null try { - connectionJson = await unpackAndVerifySignatureDecorator(message.connectionSig, this.wallet) + connectionJson = await unpackAndVerifySignatureDecorator( + message.connectionSig, + messageContext.agentContext.wallet + ) } catch (error) { if (error instanceof AriesFrameworkError) { throw new ConnectionProblemReportError(error.message, { @@ -305,7 +308,7 @@ export class ConnectionService { throw new AriesFrameworkError('DID Document is missing.') } - const { did: peerDid } = await this.createDid({ + const { did: peerDid } = await this.createDid(messageContext.agentContext, { role: DidDocumentRole.Received, didDoc: connection.didDoc, }) @@ -313,7 +316,7 @@ export class ConnectionService { connectionRecord.theirDid = peerDid connectionRecord.threadId = message.threadId - await this.updateState(connectionRecord, DidExchangeState.ResponseReceived) + await this.updateState(messageContext.agentContext, connectionRecord, DidExchangeState.ResponseReceived) return connectionRecord } @@ -328,6 +331,7 @@ export class ConnectionService { * @returns outbound message containing trust ping message */ public async createTrustPing( + agentContext: AgentContext, connectionRecord: ConnectionRecord, config: { responseRequested?: boolean; comment?: string } = {} ): Promise> { @@ -340,7 +344,7 @@ export class ConnectionService { // Only update connection record and emit an event if the state is not already 'Complete' if (connectionRecord.state !== DidExchangeState.Completed) { - await this.updateState(connectionRecord, DidExchangeState.Completed) + await this.updateState(agentContext, connectionRecord, DidExchangeState.Completed) } return { @@ -368,7 +372,7 @@ export class ConnectionService { // TODO: This is better addressed in a middleware of some kind because // any message can transition the state to complete, not just an ack or trust ping if (connection.state === DidExchangeState.ResponseSent && connection.role === DidExchangeRole.Responder) { - await this.updateState(connection, DidExchangeState.Completed) + await this.updateState(messageContext.agentContext, connection, DidExchangeState.Completed) } return connection @@ -393,9 +397,9 @@ export class ConnectionService { } let connectionRecord - const ourDidRecords = await this.didRepository.findAllByRecipientKey(recipientKey) + const ourDidRecords = await this.didRepository.findAllByRecipientKey(messageContext.agentContext, recipientKey) for (const ourDidRecord of ourDidRecords) { - connectionRecord = await this.findByOurDid(ourDidRecord.id) + connectionRecord = await this.findByOurDid(messageContext.agentContext, ourDidRecord.id) } if (!connectionRecord) { @@ -404,7 +408,9 @@ export class ConnectionService { ) } - const theirDidRecord = connectionRecord.theirDid && (await this.didRepository.findById(connectionRecord.theirDid)) + const theirDidRecord = + connectionRecord.theirDid && + (await this.didRepository.findById(messageContext.agentContext, connectionRecord.theirDid)) if (!theirDidRecord) { throw new AriesFrameworkError(`Did record with id ${connectionRecord.theirDid} not found.`) } @@ -416,7 +422,7 @@ export class ConnectionService { } connectionRecord.errorMessage = `${connectionProblemReportMessage.description.code} : ${connectionProblemReportMessage.description.en}` - await this.update(connectionRecord) + await this.update(messageContext.agentContext, connectionRecord) return connectionRecord } @@ -494,19 +500,23 @@ export class ConnectionService { } } - public async updateState(connectionRecord: ConnectionRecord, newState: DidExchangeState) { + public async updateState(agentContext: AgentContext, connectionRecord: ConnectionRecord, newState: DidExchangeState) { const previousState = connectionRecord.state connectionRecord.state = newState - await this.connectionRepository.update(connectionRecord) + await this.connectionRepository.update(agentContext, connectionRecord) - this.emitStateChangedEvent(connectionRecord, previousState) + this.emitStateChangedEvent(agentContext, connectionRecord, previousState) } - private emitStateChangedEvent(connectionRecord: ConnectionRecord, previousState: DidExchangeState | null) { + private emitStateChangedEvent( + agentContext: AgentContext, + connectionRecord: ConnectionRecord, + previousState: DidExchangeState | null + ) { // Connection record in event should be static const clonedConnection = JsonTransformer.clone(connectionRecord) - this.eventEmitter.emit({ + this.eventEmitter.emit(agentContext, { type: ConnectionEventTypes.ConnectionStateChanged, payload: { connectionRecord: clonedConnection, @@ -515,8 +525,8 @@ export class ConnectionService { }) } - public update(connectionRecord: ConnectionRecord) { - return this.connectionRepository.update(connectionRecord) + public update(agentContext: AgentContext, connectionRecord: ConnectionRecord) { + return this.connectionRepository.update(agentContext, connectionRecord) } /** @@ -524,8 +534,8 @@ export class ConnectionService { * * @returns List containing all connection records */ - public getAll() { - return this.connectionRepository.getAll() + public getAll(agentContext: AgentContext) { + return this.connectionRepository.getAll(agentContext) } /** @@ -536,8 +546,8 @@ export class ConnectionService { * @return The connection record * */ - public getById(connectionId: string): Promise { - return this.connectionRepository.getById(connectionId) + public getById(agentContext: AgentContext, connectionId: string): Promise { + return this.connectionRepository.getById(agentContext, connectionId) } /** @@ -546,8 +556,8 @@ export class ConnectionService { * @param connectionId the connection record id * @returns The connection record or null if not found */ - public findById(connectionId: string): Promise { - return this.connectionRepository.findById(connectionId) + public findById(agentContext: AgentContext, connectionId: string): Promise { + return this.connectionRepository.findById(agentContext, connectionId) } /** @@ -555,13 +565,13 @@ export class ConnectionService { * * @param connectionId the connection record id */ - public async deleteById(connectionId: string) { - const connectionRecord = await this.getById(connectionId) - return this.connectionRepository.delete(connectionRecord) + public async deleteById(agentContext: AgentContext, connectionId: string) { + const connectionRecord = await this.getById(agentContext, connectionId) + return this.connectionRepository.delete(agentContext, connectionRecord) } - public async findSingleByQuery(query: { did: string; theirDid: string }) { - return this.connectionRepository.findSingleByQuery(query) + public async findByDids(agentContext: AgentContext, query: { ourDid: string; theirDid: string }) { + return this.connectionRepository.findByDids(agentContext, query) } /** @@ -572,33 +582,56 @@ export class ConnectionService { * @throws {RecordDuplicateError} If multiple records are found * @returns The connection record */ - public getByThreadId(threadId: string): Promise { - return this.connectionRepository.getByThreadId(threadId) + public async getByThreadId(agentContext: AgentContext, threadId: string): Promise { + return this.connectionRepository.getByThreadId(agentContext, threadId) } - public async findByTheirDid(did: string): Promise { - return this.connectionRepository.findSingleByQuery({ theirDid: did }) + public async findByTheirDid(agentContext: AgentContext, theirDid: string): Promise { + return this.connectionRepository.findSingleByQuery(agentContext, { theirDid }) } - public async findByOurDid(did: string): Promise { - return this.connectionRepository.findSingleByQuery({ did }) + public async findByOurDid(agentContext: AgentContext, ourDid: string): Promise { + return this.connectionRepository.findSingleByQuery(agentContext, { did: ourDid }) } - public async findAllByOutOfBandId(outOfBandId: string) { - return this.connectionRepository.findByQuery({ outOfBandId }) + public async findAllByOutOfBandId(agentContext: AgentContext, outOfBandId: string) { + return this.connectionRepository.findByQuery(agentContext, { outOfBandId }) } - public async findByInvitationDid(invitationDid: string) { - return this.connectionRepository.findByQuery({ invitationDid }) + public async findByInvitationDid(agentContext: AgentContext, invitationDid: string) { + return this.connectionRepository.findByQuery(agentContext, { invitationDid }) } - public async createConnection(options: ConnectionRecordProps): Promise { + public async findByKeys( + agentContext: AgentContext, + { senderKey, recipientKey }: { senderKey: Key; recipientKey: Key } + ) { + const theirDidRecord = await this.didRepository.findByRecipientKey(agentContext, senderKey) + if (theirDidRecord) { + const ourDidRecord = await this.didRepository.findByRecipientKey(agentContext, recipientKey) + if (ourDidRecord) { + const connectionRecord = await this.findByDids(agentContext, { + ourDid: ourDidRecord.id, + theirDid: theirDidRecord.id, + }) + if (connectionRecord && connectionRecord.isReady) return connectionRecord + } + } + + this.logger.debug( + `No connection record found for encrypted message with recipient key ${recipientKey.fingerprint} and sender key ${senderKey.fingerprint}` + ) + + return null + } + + public async createConnection(agentContext: AgentContext, options: ConnectionRecordProps): Promise { const connectionRecord = new ConnectionRecord(options) - await this.connectionRepository.save(connectionRecord) + await this.connectionRepository.save(agentContext, connectionRecord) return connectionRecord } - private async createDid({ role, didDoc }: { role: DidDocumentRole; didDoc: DidDoc }) { + private async createDid(agentContext: AgentContext, { role, didDoc }: { role: DidDocumentRole; didDoc: DidDoc }) { // Convert the legacy did doc to a new did document const didDocument = convertToNewDidDocument(didDoc) @@ -629,7 +662,7 @@ export class ConnectionService { didDocument: 'omitted...', }) - await this.didRepository.save(didRecord) + await this.didRepository.save(agentContext, didRecord) this.logger.debug('Did record created.', didRecord) return { did: peerDid, didDocument } } @@ -700,7 +733,11 @@ export class ConnectionService { }) } - public async returnWhenIsConnected(connectionId: string, timeoutMs = 20000): Promise { + public async returnWhenIsConnected( + agentContext: AgentContext, + connectionId: string, + timeoutMs = 20000 + ): Promise { const isConnected = (connection: ConnectionRecord) => { return connection.id === connectionId && connection.state === DidExchangeState.Completed } @@ -718,7 +755,7 @@ export class ConnectionService { ) .subscribe(subject) - const connection = await this.getById(connectionId) + const connection = await this.getById(agentContext, connectionId) if (isConnected(connection)) { subject.next(connection) } diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index 6c74598b0b..5aefd21ee2 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -1,5 +1,4 @@ import type { AgentMessage } from '../../agent/AgentMessage' -import type { Logger } from '../../logger' import type { DependencyManager } from '../../plugins' import type { DeleteCredentialOptions } from './CredentialServiceOptions' import type { @@ -7,30 +6,32 @@ import type { AcceptOfferOptions, AcceptProposalOptions, AcceptRequestOptions, - NegotiateOfferOptions, - NegotiateProposalOptions, - OfferCredentialOptions, - ProposeCredentialOptions, - ServiceMap, CreateOfferOptions, - FindOfferMessageReturn, - FindRequestMessageReturn, FindCredentialMessageReturn, + FindOfferMessageReturn, FindProposalMessageReturn, + FindRequestMessageReturn, GetFormatDataReturn, + NegotiateOfferOptions, + NegotiateProposalOptions, + OfferCredentialOptions, + ProposeCredentialOptions, SendProblemReportOptions, + ServiceMap, } from './CredentialsModuleOptions' import type { CredentialFormat } from './formats' import type { IndyCredentialFormat } from './formats/indy/IndyCredentialFormat' import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' import type { CredentialService } from './services/CredentialService' -import { AgentConfig } from '../../agent/AgentConfig' +import { AgentContext } from '../../agent' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' +import { InjectionSymbols } from '../../constants' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' -import { injectable, module } from '../../plugins' +import { Logger } from '../../logger' +import { inject, injectable, module } from '../../plugins' import { DidCommMessageRole } from '../../storage' import { DidCommMessageRepository } from '../../storage/didcomm/DidCommMessageRepository' import { ConnectionService } from '../connections/services' @@ -38,10 +39,10 @@ import { RoutingService } from '../routing/services/RoutingService' import { IndyCredentialFormatService } from './formats' import { CredentialState } from './models/CredentialState' +import { RevocationNotificationService } from './protocol/revocation-notification/services' import { V1CredentialService } from './protocol/v1/V1CredentialService' import { V2CredentialService } from './protocol/v2/V2CredentialService' import { CredentialRepository } from './repository/CredentialRepository' -import { RevocationNotificationService } from './services' export interface CredentialsModule[]> { // Proposal methods @@ -95,7 +96,7 @@ export class CredentialsModule< private connectionService: ConnectionService private messageSender: MessageSender private credentialRepository: CredentialRepository - private agentConfig: AgentConfig + private agentContext: AgentContext private didCommMessageRepo: DidCommMessageRepository private routingService: RoutingService private logger: Logger @@ -104,7 +105,8 @@ export class CredentialsModule< public constructor( messageSender: MessageSender, connectionService: ConnectionService, - agentConfig: AgentConfig, + agentContext: AgentContext, + @inject(InjectionSymbols.Logger) logger: Logger, credentialRepository: CredentialRepository, mediationRecipientService: RoutingService, didCommMessageRepository: DidCommMessageRepository, @@ -117,10 +119,10 @@ export class CredentialsModule< this.messageSender = messageSender this.connectionService = connectionService this.credentialRepository = credentialRepository - this.agentConfig = agentConfig this.routingService = mediationRecipientService + this.agentContext = agentContext this.didCommMessageRepo = didCommMessageRepository - this.logger = agentConfig.logger + this.logger = logger // Dynamically build service map. This will be extracted once services are registered dynamically this.serviceMap = [v1Service, v2Service].reduce( @@ -131,7 +133,7 @@ export class CredentialsModule< {} ) as ServiceMap - this.logger.debug(`Initializing Credentials Module for agent ${this.agentConfig.label}`) + this.logger.debug(`Initializing Credentials Module for agent ${this.agentContext.config.label}`) } public getService(protocolVersion: PVT): CredentialService { @@ -155,10 +157,10 @@ export class CredentialsModule< this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) - const connection = await this.connectionService.getById(options.connectionId) + const connection = await this.connectionService.getById(this.agentContext, options.connectionId) // will get back a credential record -> map to Credential Exchange Record - const { credentialRecord, message } = await service.createProposal({ + const { credentialRecord, message } = await service.createProposal(this.agentContext, { connection, credentialFormats: options.credentialFormats, comment: options.comment, @@ -171,7 +173,7 @@ export class CredentialsModule< const outbound = createOutboundMessage(connection, message) this.logger.debug('In proposeCredential: Send Proposal to Issuer') - await this.messageSender.sendMessage(outbound) + await this.messageSender.sendMessage(this.agentContext, outbound) return credentialRecord } @@ -196,7 +198,7 @@ export class CredentialsModule< const service = this.getService(credentialRecord.protocolVersion) // will get back a credential record -> map to Credential Exchange Record - const { message } = await service.acceptProposal({ + const { message } = await service.acceptProposal(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -204,9 +206,9 @@ export class CredentialsModule< }) // send the message - const connection = await this.connectionService.getById(credentialRecord.connectionId) + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) const outbound = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outbound) + await this.messageSender.sendMessage(this.agentContext, outbound) return credentialRecord } @@ -231,16 +233,16 @@ export class CredentialsModule< // with version we can get the Service const service = this.getService(credentialRecord.protocolVersion) - const { message } = await service.negotiateProposal({ + const { message } = await service.negotiateProposal(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, autoAcceptCredential: options.autoAcceptCredential, }) - const connection = await this.connectionService.getById(credentialRecord.connectionId) + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return credentialRecord } @@ -253,12 +255,12 @@ export class CredentialsModule< * @returns Credential exchange record associated with the sent credential offer message */ public async offerCredential(options: OfferCredentialOptions): Promise { - const connection = await this.connectionService.getById(options.connectionId) + const connection = await this.connectionService.getById(this.agentContext, options.connectionId) const service = this.getService(options.protocolVersion) this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) - const { message, credentialRecord } = await service.createOffer({ + const { message, credentialRecord } = await service.createOffer(this.agentContext, { credentialFormats: options.credentialFormats, autoAcceptCredential: options.autoAcceptCredential, comment: options.comment, @@ -267,7 +269,7 @@ export class CredentialsModule< this.logger.debug('Offer Message successfully created; message= ', message) const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return credentialRecord } @@ -285,13 +287,13 @@ export class CredentialsModule< const service = this.getService(credentialRecord.protocolVersion) this.logger.debug(`Got a CredentialService object for this version; version = ${service.version}`) - const offerMessage = await service.findOfferMessage(credentialRecord.id) + const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) // Use connection if present if (credentialRecord.connectionId) { - const connection = await this.connectionService.getById(credentialRecord.connectionId) + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const { message } = await service.acceptOffer({ + const { message } = await service.acceptOffer(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -299,14 +301,14 @@ export class CredentialsModule< }) const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return credentialRecord } // Use ~service decorator otherwise else if (offerMessage?.service) { // Create ~service decorator - const routing = await this.routingService.getRouting() + const routing = await this.routingService.getRouting(this.agentContext) const ourService = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], @@ -314,7 +316,7 @@ export class CredentialsModule< }) const recipientService = offerMessage.service - const { message } = await service.acceptOffer({ + const { message } = await service.acceptOffer(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -323,13 +325,13 @@ export class CredentialsModule< // Set and save ~service decorator to record (to remember our verkey) message.service = ourService - await this.didCommMessageRepo.saveOrUpdateAgentMessage({ + await this.didCommMessageRepo.saveOrUpdateAgentMessage(this.agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, }) - await this.messageSender.sendMessageToService({ + await this.messageSender.sendMessageToService(this.agentContext, { message, service: recipientService.resolvedDidCommService, senderKey: ourService.resolvedDidCommService.recipientKeys[0], @@ -352,7 +354,7 @@ export class CredentialsModule< // with version we can get the Service const service = this.getService(credentialRecord.protocolVersion) - await service.updateState(credentialRecord, CredentialState.Declined) + await service.updateState(this.agentContext, credentialRecord, CredentialState.Declined) return credentialRecord } @@ -361,7 +363,7 @@ export class CredentialsModule< const credentialRecord = await this.getById(options.credentialRecordId) const service = this.getService(credentialRecord.protocolVersion) - const { message } = await service.negotiateOffer({ + const { message } = await service.negotiateOffer(this.agentContext, { credentialFormats: options.credentialFormats, credentialRecord, comment: options.comment, @@ -374,9 +376,9 @@ export class CredentialsModule< ) } - const connection = await this.connectionService.getById(credentialRecord.connectionId) + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return credentialRecord } @@ -394,7 +396,7 @@ export class CredentialsModule< const service = this.getService(options.protocolVersion) this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) - const { message, credentialRecord } = await service.createOffer({ + const { message, credentialRecord } = await service.createOffer(this.agentContext, { credentialFormats: options.credentialFormats, comment: options.comment, autoAcceptCredential: options.autoAcceptCredential, @@ -420,7 +422,7 @@ export class CredentialsModule< this.logger.debug(`Got a CredentialService object for version ${credentialRecord.protocolVersion}`) - const { message } = await service.acceptRequest({ + const { message } = await service.acceptRequest(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -428,14 +430,14 @@ export class CredentialsModule< }) this.logger.debug('We have a credential message (sending outbound): ', message) - const requestMessage = await service.findRequestMessage(credentialRecord.id) - const offerMessage = await service.findOfferMessage(credentialRecord.id) + const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) + const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) // Use connection if present if (credentialRecord.connectionId) { - const connection = await this.connectionService.getById(credentialRecord.connectionId) + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return credentialRecord } @@ -445,13 +447,13 @@ export class CredentialsModule< const ourService = offerMessage.service message.service = ourService - await this.didCommMessageRepo.saveOrUpdateAgentMessage({ + await this.didCommMessageRepo.saveOrUpdateAgentMessage(this.agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, }) - await this.messageSender.sendMessageToService({ + await this.messageSender.sendMessageToService(this.agentContext, { message, service: recipientService.resolvedDidCommService, senderKey: ourService.resolvedDidCommService.recipientKeys[0], @@ -484,18 +486,18 @@ export class CredentialsModule< this.logger.debug(`Got a CredentialService object for version ${credentialRecord.protocolVersion}`) - const { message } = await service.acceptCredential({ + const { message } = await service.acceptCredential(this.agentContext, { credentialRecord, }) - const requestMessage = await service.findRequestMessage(credentialRecord.id) - const credentialMessage = await service.findCredentialMessage(credentialRecord.id) + const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) + const credentialMessage = await service.findCredentialMessage(this.agentContext, credentialRecord.id) if (credentialRecord.connectionId) { - const connection = await this.connectionService.getById(credentialRecord.connectionId) + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return credentialRecord } @@ -504,7 +506,7 @@ export class CredentialsModule< const recipientService = credentialMessage.service const ourService = requestMessage.service - await this.messageSender.sendMessageToService({ + await this.messageSender.sendMessageToService(this.agentContext, { message, service: recipientService.resolvedDidCommService, senderKey: ourService.resolvedDidCommService.recipientKeys[0], @@ -532,15 +534,15 @@ export class CredentialsModule< if (!credentialRecord.connectionId) { throw new AriesFrameworkError(`No connectionId found for credential record '${credentialRecord.id}'.`) } - const connection = await this.connectionService.getById(credentialRecord.connectionId) + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) const service = this.getService(credentialRecord.protocolVersion) - const problemReportMessage = service.createProblemReport({ message: options.message }) + const problemReportMessage = service.createProblemReport(this.agentContext, { message: options.message }) problemReportMessage.setThread({ threadId: credentialRecord.threadId, }) const outboundMessage = createOutboundMessage(connection, problemReportMessage) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return credentialRecord } @@ -549,7 +551,7 @@ export class CredentialsModule< const credentialRecord = await this.getById(credentialRecordId) const service = this.getService(credentialRecord.protocolVersion) - return service.getFormatData(credentialRecordId) + return service.getFormatData(this.agentContext, credentialRecordId) } /** @@ -561,7 +563,7 @@ export class CredentialsModule< * */ public getById(credentialRecordId: string): Promise { - return this.credentialRepository.getById(credentialRecordId) + return this.credentialRepository.getById(this.agentContext, credentialRecordId) } /** @@ -570,7 +572,7 @@ export class CredentialsModule< * @returns List containing all credential records */ public getAll(): Promise { - return this.credentialRepository.getAll() + return this.credentialRepository.getAll(this.agentContext) } /** @@ -580,7 +582,7 @@ export class CredentialsModule< * @returns The credential record or null if not found */ public findById(credentialRecordId: string): Promise { - return this.credentialRepository.findById(credentialRecordId) + return this.credentialRepository.findById(this.agentContext, credentialRecordId) } /** @@ -592,31 +594,31 @@ export class CredentialsModule< public async deleteById(credentialId: string, options?: DeleteCredentialOptions) { const credentialRecord = await this.getById(credentialId) const service = this.getService(credentialRecord.protocolVersion) - return service.delete(credentialRecord, options) + return service.delete(this.agentContext, credentialRecord, options) } public async findProposalMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findProposalMessage(credentialExchangeId) + return service.findProposalMessage(this.agentContext, credentialExchangeId) } public async findOfferMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findOfferMessage(credentialExchangeId) + return service.findOfferMessage(this.agentContext, credentialExchangeId) } public async findRequestMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findRequestMessage(credentialExchangeId) + return service.findRequestMessage(this.agentContext, credentialExchangeId) } public async findCredentialMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findCredentialMessage(credentialExchangeId) + return service.findCredentialMessage(this.agentContext, credentialExchangeId) } private async getServiceForCredentialExchangeId(credentialExchangeId: string) { diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts index bd2e8fc34c..9d3f6f5da9 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../../../agent' import type { EventEmitter } from '../../../agent/EventEmitter' import type { CredentialRepository } from '../repository' import type { CredentialFormat } from './CredentialFormat' @@ -34,30 +35,48 @@ export abstract class CredentialFormatService): Promise - abstract processProposal(options: FormatProcessOptions): Promise - abstract acceptProposal(options: FormatAcceptProposalOptions): Promise + abstract createProposal( + agentContext: AgentContext, + options: FormatCreateProposalOptions + ): Promise + abstract processProposal(agentContext: AgentContext, options: FormatProcessOptions): Promise + abstract acceptProposal( + agentContext: AgentContext, + options: FormatAcceptProposalOptions + ): Promise // offer methods - abstract createOffer(options: FormatCreateOfferOptions): Promise - abstract processOffer(options: FormatProcessOptions): Promise - abstract acceptOffer(options: FormatAcceptOfferOptions): Promise + abstract createOffer( + agentContext: AgentContext, + options: FormatCreateOfferOptions + ): Promise + abstract processOffer(agentContext: AgentContext, options: FormatProcessOptions): Promise + abstract acceptOffer(agentContext: AgentContext, options: FormatAcceptOfferOptions): Promise // request methods - abstract createRequest(options: FormatCreateRequestOptions): Promise - abstract processRequest(options: FormatProcessOptions): Promise - abstract acceptRequest(options: FormatAcceptRequestOptions): Promise + abstract createRequest( + agentContext: AgentContext, + options: FormatCreateRequestOptions + ): Promise + abstract processRequest(agentContext: AgentContext, options: FormatProcessOptions): Promise + abstract acceptRequest( + agentContext: AgentContext, + options: FormatAcceptRequestOptions + ): Promise // credential methods - abstract processCredential(options: FormatProcessOptions): Promise + abstract processCredential(agentContext: AgentContext, options: FormatProcessOptions): Promise // auto accept methods - abstract shouldAutoRespondToProposal(options: FormatAutoRespondProposalOptions): boolean - abstract shouldAutoRespondToOffer(options: FormatAutoRespondOfferOptions): boolean - abstract shouldAutoRespondToRequest(options: FormatAutoRespondRequestOptions): boolean - abstract shouldAutoRespondToCredential(options: FormatAutoRespondCredentialOptions): boolean + abstract shouldAutoRespondToProposal(agentContext: AgentContext, options: FormatAutoRespondProposalOptions): boolean + abstract shouldAutoRespondToOffer(agentContext: AgentContext, options: FormatAutoRespondOfferOptions): boolean + abstract shouldAutoRespondToRequest(agentContext: AgentContext, options: FormatAutoRespondRequestOptions): boolean + abstract shouldAutoRespondToCredential( + agentContext: AgentContext, + options: FormatAutoRespondCredentialOptions + ): boolean - abstract deleteCredentialById(credentialId: string): Promise + abstract deleteCredentialById(agentContext: AgentContext, credentialId: string): Promise abstract supportsFormat(format: string): boolean diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts index fea9cb1bda..8f9e380b3c 100644 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts @@ -1,36 +1,35 @@ +import type { AgentContext } from '../../../../agent' import type { Attachment } from '../../../../decorators/attachment/Attachment' -import type { Logger } from '../../../../logger' import type { LinkedAttachment } from '../../../../utils/LinkedAttachment' import type { CredentialPreviewAttributeOptions } from '../../models/CredentialPreviewAttribute' import type { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' import type { - FormatAutoRespondCredentialOptions, FormatAcceptOfferOptions, FormatAcceptProposalOptions, FormatAcceptRequestOptions, + FormatAutoRespondCredentialOptions, + FormatAutoRespondOfferOptions, + FormatAutoRespondProposalOptions, + FormatAutoRespondRequestOptions, FormatCreateOfferOptions, FormatCreateOfferReturn, FormatCreateProposalOptions, FormatCreateProposalReturn, FormatCreateReturn, FormatProcessOptions, - FormatAutoRespondOfferOptions, - FormatAutoRespondProposalOptions, - FormatAutoRespondRequestOptions, } from '../CredentialFormatServiceOptions' import type { IndyCredentialFormat } from './IndyCredentialFormat' import type * as Indy from 'indy-sdk' -import { AgentConfig } from '../../../../agent/AgentConfig' import { EventEmitter } from '../../../../agent/EventEmitter' import { InjectionSymbols } from '../../../../constants' import { AriesFrameworkError } from '../../../../error' +import { Logger } from '../../../../logger' import { inject, injectable } from '../../../../plugins' import { JsonTransformer } from '../../../../utils/JsonTransformer' import { MessageValidator } from '../../../../utils/MessageValidator' import { getIndyDidFromVerificationMethod } from '../../../../utils/did' import { uuid } from '../../../../utils/uuid' -import { Wallet } from '../../../../wallet/Wallet' import { ConnectionService } from '../../../connections' import { DidResolverService, findVerificationMethodByKeyType } from '../../../dids' import { IndyHolderService, IndyIssuerService } from '../../../indy' @@ -57,7 +56,6 @@ export class IndyCredentialFormatService extends CredentialFormatService): Promise { + public async createProposal( + agentContext: AgentContext, + { credentialFormats, credentialRecord }: FormatCreateProposalOptions + ): Promise { const format = new CredentialFormatSpec({ format: INDY_CRED_FILTER, }) @@ -133,7 +129,7 @@ export class IndyCredentialFormatService extends CredentialFormatService { + public async processProposal(agentContext: AgentContext, { attachment }: FormatProcessOptions): Promise { const credProposalJson = attachment.getDataAsJson() if (!credProposalJson) { @@ -144,12 +140,15 @@ export class IndyCredentialFormatService extends CredentialFormatService): Promise { + public async acceptProposal( + agentContext: AgentContext, + { + attachId, + credentialFormats, + credentialRecord, + proposalAttachment, + }: FormatAcceptProposalOptions + ): Promise { const indyFormat = credentialFormats?.indy const credentialProposal = JsonTransformer.fromJSON(proposalAttachment.getDataAsJson(), IndyCredPropose) @@ -167,7 +166,7 @@ export class IndyCredentialFormatService extends CredentialFormatService): Promise { + public async createOffer( + agentContext: AgentContext, + { credentialFormats, credentialRecord, attachId }: FormatCreateOfferOptions + ): Promise { const indyFormat = credentialFormats.indy if (!indyFormat) { throw new AriesFrameworkError('Missing indy credentialFormat data') } - const { format, attachment, previewAttributes } = await this.createIndyOffer({ + const { format, attachment, previewAttributes } = await this.createIndyOffer(agentContext, { credentialRecord, attachId, attributes: indyFormat.attributes, @@ -208,7 +206,7 @@ export class IndyCredentialFormatService extends CredentialFormatService() @@ -220,24 +218,28 @@ export class IndyCredentialFormatService extends CredentialFormatService): Promise { + public async acceptOffer( + agentContext: AgentContext, + { credentialFormats, credentialRecord, attachId, offerAttachment }: FormatAcceptOfferOptions + ): Promise { const indyFormat = credentialFormats?.indy - const holderDid = indyFormat?.holderDid ?? (await this.getIndyHolderDid(credentialRecord)) + const holderDid = indyFormat?.holderDid ?? (await this.getIndyHolderDid(agentContext, credentialRecord)) const credentialOffer = offerAttachment.getDataAsJson() - const credentialDefinition = await this.indyLedgerService.getCredentialDefinition(credentialOffer.cred_def_id) + const credentialDefinition = await this.indyLedgerService.getCredentialDefinition( + agentContext, + credentialOffer.cred_def_id + ) - const [credentialRequest, credentialRequestMetadata] = await this.indyHolderService.createCredentialRequest({ - holderDid, - credentialOffer, - credentialDefinition, - }) + const [credentialRequest, credentialRequestMetadata] = await this.indyHolderService.createCredentialRequest( + agentContext, + { + holderDid, + credentialOffer, + credentialDefinition, + } + ) credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, credentialRequestMetadata) credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { credentialDefinitionId: credentialOffer.cred_def_id, @@ -264,16 +266,14 @@ export class IndyCredentialFormatService extends CredentialFormatService { + public async processRequest(agentContext: AgentContext, options: FormatProcessOptions): Promise { // not needed for Indy } - public async acceptRequest({ - credentialRecord, - attachId, - offerAttachment, - requestAttachment, - }: FormatAcceptRequestOptions): Promise { + public async acceptRequest( + agentContext: AgentContext, + { credentialRecord, attachId, offerAttachment, requestAttachment }: FormatAcceptRequestOptions + ): Promise { // Assert credential attributes const credentialAttributes = credentialRecord.credentialAttributes if (!credentialAttributes) { @@ -290,7 +290,7 @@ export class IndyCredentialFormatService extends CredentialFormatService { + public async processCredential( + agentContext: AgentContext, + { credentialRecord, attachment }: FormatProcessOptions + ): Promise { const credentialRequestMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyRequest) if (!credentialRequestMetadata) { @@ -328,9 +331,12 @@ export class IndyCredentialFormatService extends CredentialFormatService() - const credentialDefinition = await this.indyLedgerService.getCredentialDefinition(indyCredential.cred_def_id) + const credentialDefinition = await this.indyLedgerService.getCredentialDefinition( + agentContext, + indyCredential.cred_def_id + ) const revocationRegistry = indyCredential.rev_reg_id - ? await this.indyLedgerService.getRevocationRegistryDefinition(indyCredential.rev_reg_id) + ? await this.indyLedgerService.getRevocationRegistryDefinition(agentContext, indyCredential.rev_reg_id) : null if (!credentialRecord.credentialAttributes) { @@ -343,7 +349,7 @@ export class IndyCredentialFormatService extends CredentialFormatService { - await this.indyHolderService.deleteCredential(credentialRecordId) + public async deleteCredentialById(agentContext: AgentContext, credentialRecordId: string): Promise { + await this.indyHolderService.deleteCredential(agentContext, credentialRecordId) } - public shouldAutoRespondToProposal({ offerAttachment, proposalAttachment }: FormatAutoRespondProposalOptions) { + public shouldAutoRespondToProposal( + agentContext: AgentContext, + { offerAttachment, proposalAttachment }: FormatAutoRespondProposalOptions + ) { const credentialProposalJson = proposalAttachment.getDataAsJson() const credentialProposal = JsonTransformer.fromJSON(credentialProposalJson, IndyCredPropose) @@ -406,7 +415,10 @@ export class IndyCredentialFormatService extends CredentialFormatService() const credentialRequestJson = requestAttachment.getDataAsJson() return credentialOfferJson.cred_def_id == credentialRequestJson.cred_def_id } - public shouldAutoRespondToCredential({ - credentialRecord, - requestAttachment, - credentialAttachment, - }: FormatAutoRespondCredentialOptions) { + public shouldAutoRespondToCredential( + agentContext: AgentContext, + { credentialRecord, requestAttachment, credentialAttachment }: FormatAutoRespondCredentialOptions + ) { const credentialJson = credentialAttachment.getDataAsJson() const credentialRequestJson = requestAttachment.getDataAsJson() @@ -444,33 +458,36 @@ export class IndyCredentialFormatService extends CredentialFormatService { + private async createIndyOffer( + agentContext: AgentContext, + { + credentialRecord, + attachId, + credentialDefinitionId, + attributes, + linkedAttachments, + }: { + credentialDefinitionId: string + credentialRecord: CredentialExchangeRecord + attachId?: string + attributes: CredentialPreviewAttributeOptions[] + linkedAttachments?: LinkedAttachment[] + } + ): Promise { // if the proposal has an attachment Id use that, otherwise the generated id of the formats object const format = new CredentialFormatSpec({ attachId: attachId, format: INDY_CRED_ABSTRACT, }) - const offer = await this.indyIssuerService.createCredentialOffer(credentialDefinitionId) + const offer = await this.indyIssuerService.createCredentialOffer(agentContext, credentialDefinitionId) const { previewAttributes } = this.getCredentialLinkedAttachments(attributes, linkedAttachments) if (!previewAttributes) { throw new AriesFrameworkError('Missing required preview attributes for indy offer') } - await this.assertPreviewAttributesMatchSchemaAttributes(offer, previewAttributes) + await this.assertPreviewAttributesMatchSchemaAttributes(agentContext, offer, previewAttributes) credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { schemaId: offer.schema_id, @@ -483,22 +500,23 @@ export class IndyCredentialFormatService extends CredentialFormatService { - const schema = await this.indyLedgerService.getSchema(offer.schema_id) + const schema = await this.indyLedgerService.getSchema(agentContext, offer.schema_id) IndyCredentialUtils.checkAttributesMatch(schema, attributes) } - private async getIndyHolderDid(credentialRecord: CredentialExchangeRecord) { + private async getIndyHolderDid(agentContext: AgentContext, credentialRecord: CredentialExchangeRecord) { // If we have a connection id we try to extract the did from the connection did document. if (credentialRecord.connectionId) { - const connection = await this.connectionService.getById(credentialRecord.connectionId) + const connection = await this.connectionService.getById(agentContext, credentialRecord.connectionId) if (!connection.did) { throw new AriesFrameworkError(`Connection record ${connection.id} has no 'did'`) } - const resolved = await this.didResolver.resolve(connection.did) + const resolved = await this.didResolver.resolve(agentContext, connection.did) if (resolved.didDocument) { const verificationMethod = await findVerificationMethodByKeyType( @@ -515,7 +533,7 @@ export class IndyCredentialFormatService extends CredentialFormatService({ + this.eventEmitter.emit(agentContext, { type: CredentialEventTypes.RevocationNotificationReceived, payload: { credentialRecord: clonedCredentialRecord, @@ -91,6 +93,7 @@ export class RevocationNotificationService { const connection = messageContext.assertReadyConnection() await this.processRevocationNotification( + messageContext.agentContext, indyRevocationRegistryId, indyCredentialRevocationId, connection, @@ -132,6 +135,7 @@ export class RevocationNotificationService { const comment = messageContext.message.comment const connection = messageContext.assertReadyConnection() await this.processRevocationNotification( + messageContext.agentContext, indyRevocationRegistryId, indyCredentialRevocationId, connection, diff --git a/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts b/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts index bea53b40e1..9222b3fdcf 100644 --- a/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts +++ b/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts @@ -1,7 +1,10 @@ +import type { AgentContext } from '../../../../../../agent' import type { RevocationNotificationReceivedEvent } from '../../../../CredentialEvents' +import { Subject } from 'rxjs' + import { CredentialExchangeRecord, CredentialState, InboundMessageContext } from '../../../../../..' -import { getAgentConfig, getMockConnection, mockFunction } from '../../../../../../../tests/helpers' +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../../tests/helpers' import { Dispatcher } from '../../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../../agent/EventEmitter' import { DidExchangeState } from '../../../../../connections' @@ -25,6 +28,7 @@ const connection = getMockConnection({ describe('RevocationNotificationService', () => { let revocationNotificationService: RevocationNotificationService + let agentContext: AgentContext let eventEmitter: EventEmitter beforeEach(() => { @@ -32,12 +36,14 @@ describe('RevocationNotificationService', () => { indyLedgers: [], }) - eventEmitter = new EventEmitter(agentConfig) + agentContext = getAgentContext() + + eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) revocationNotificationService = new RevocationNotificationService( credentialRepository, eventEmitter, - agentConfig, - dispatcher + dispatcher, + agentConfig.logger ) }) @@ -82,6 +88,7 @@ describe('RevocationNotificationService', () => { }) const messageContext = new InboundMessageContext(revocationNotificationMessage, { connection, + agentContext, }) await revocationNotificationService.v1ProcessRevocationNotification(messageContext) @@ -123,7 +130,7 @@ describe('RevocationNotificationService', () => { issueThread: revocationNotificationThreadId, comment: 'Credential has been revoked', }) - const messageContext = new InboundMessageContext(revocationNotificationMessage, { connection }) + const messageContext = new InboundMessageContext(revocationNotificationMessage, { connection, agentContext }) await revocationNotificationService.v1ProcessRevocationNotification(messageContext) @@ -143,7 +150,7 @@ describe('RevocationNotificationService', () => { issueThread: revocationNotificationThreadId, comment: 'Credential has been revoked', }) - const messageContext = new InboundMessageContext(revocationNotificationMessage) + const messageContext = new InboundMessageContext(revocationNotificationMessage, { agentContext }) await revocationNotificationService.v1ProcessRevocationNotification(messageContext) @@ -187,9 +194,7 @@ describe('RevocationNotificationService', () => { revocationFormat: 'indy-anoncreds', comment: 'Credential has been revoked', }) - const messageContext = new InboundMessageContext(revocationNotificationMessage, { - connection, - }) + const messageContext = new InboundMessageContext(revocationNotificationMessage, { agentContext, connection }) await revocationNotificationService.v2ProcessRevocationNotification(messageContext) @@ -231,7 +236,7 @@ describe('RevocationNotificationService', () => { revocationFormat: 'indy-anoncreds', comment: 'Credential has been revoked', }) - const messageContext = new InboundMessageContext(revocationNotificationMessage, { connection }) + const messageContext = new InboundMessageContext(revocationNotificationMessage, { connection, agentContext }) await revocationNotificationService.v2ProcessRevocationNotification(messageContext) @@ -252,7 +257,7 @@ describe('RevocationNotificationService', () => { revocationFormat: 'indy-anoncreds', comment: 'Credential has been revoked', }) - const messageContext = new InboundMessageContext(revocationNotificationMessage) + const messageContext = new InboundMessageContext(revocationNotificationMessage, { agentContext }) await revocationNotificationService.v2ProcessRevocationNotification(messageContext) diff --git a/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts b/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts index 1d52e4b197..938dbf7dd6 100644 --- a/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts +++ b/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts @@ -1,5 +1,5 @@ +import type { AgentContext } from '../../../../agent' import type { AgentMessage } from '../../../../agent/AgentMessage' -import type { HandlerInboundMessage } from '../../../../agent/Handler' import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' import type { ProblemReportMessage } from '../../../problem-reports' import type { @@ -18,12 +18,13 @@ import type { GetFormatDataReturn } from '../../CredentialsModuleOptions' import type { CredentialFormat } from '../../formats' import type { IndyCredentialFormat } from '../../formats/indy/IndyCredentialFormat' -import { AgentConfig } from '../../../../agent/AgentConfig' import { Dispatcher } from '../../../../agent/Dispatcher' import { EventEmitter } from '../../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../../constants' import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../../error' -import { injectable } from '../../../../plugins' +import { Logger } from '../../../../logger' +import { inject, injectable } from '../../../../plugins' import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' import { JsonTransformer } from '../../../../utils' import { isLinkedAttachment } from '../../../../utils/attachment' @@ -36,7 +37,7 @@ import { IndyCredentialFormatService } from '../../formats/indy/IndyCredentialFo import { IndyCredPropose } from '../../formats/indy/models' import { AutoAcceptCredential } from '../../models/CredentialAutoAcceptType' import { CredentialState } from '../../models/CredentialState' -import { CredentialRepository, CredentialExchangeRecord } from '../../repository' +import { CredentialExchangeRecord, CredentialRepository } from '../../repository' import { CredentialService } from '../../services' import { composeAutoAccept } from '../../util/composeAutoAccept' import { arePreviewAttributesEqual } from '../../util/previewAttributes' @@ -71,17 +72,16 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat public constructor( connectionService: ConnectionService, didCommMessageRepository: DidCommMessageRepository, - agentConfig: AgentConfig, + @inject(InjectionSymbols.Logger) logger: Logger, routingService: RoutingService, dispatcher: Dispatcher, eventEmitter: EventEmitter, credentialRepository: CredentialRepository, formatService: IndyCredentialFormatService ) { - super(credentialRepository, didCommMessageRepository, eventEmitter, dispatcher, agentConfig) + super(credentialRepository, didCommMessageRepository, eventEmitter, dispatcher, logger) this.connectionService = connectionService this.formatService = formatService - this.didCommMessageRepository = didCommMessageRepository this.routingService = routingService this.registerHandlers() @@ -110,12 +110,10 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat * @returns Object containing proposal message and associated credential record * */ - public async createProposal({ - connection, - credentialFormats, - comment, - autoAcceptCredential, - }: CreateProposalOptions<[IndyCredentialFormat]>): Promise> { + public async createProposal( + agentContext: AgentContext, + { connection, credentialFormats, comment, autoAcceptCredential }: CreateProposalOptions<[IndyCredentialFormat]> + ): Promise> { this.assertOnlyIndyFormat(credentialFormats) if (!credentialFormats.indy) { @@ -137,7 +135,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat }) // call create proposal for validation of the proposal and addition of linked attachments - const { previewAttributes, attachment } = await this.formatService.createProposal({ + const { previewAttributes, attachment } = await this.formatService.createProposal(agentContext, { credentialFormats, credentialRecord, }) @@ -159,15 +157,15 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat comment, }) - await this.didCommMessageRepository.saveAgentMessage({ + await this.didCommMessageRepository.saveAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, }) credentialRecord.credentialAttributes = previewAttributes - await this.credentialRepository.save(credentialRecord) - this.emitStateChangedEvent(credentialRecord, null) + await this.credentialRepository.save(agentContext, credentialRecord) + this.emitStateChangedEvent(agentContext, credentialRecord, null) return { credentialRecord, message } } @@ -189,7 +187,11 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat this.logger.debug(`Processing credential proposal with message id ${proposalMessage.id}`) - let credentialRecord = await this.findByThreadAndConnectionId(proposalMessage.threadId, connection?.id) + let credentialRecord = await this.findByThreadAndConnectionId( + messageContext.agentContext, + proposalMessage.threadId, + connection?.id + ) // Credential record already exists, this is a response to an earlier message sent by us if (credentialRecord) { @@ -199,11 +201,14 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferSent) - const proposalCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ - associatedRecordId: credentialRecord.id, - messageClass: V1ProposeCredentialMessage, - }) - const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + const proposalCredentialMessage = await this.didCommMessageRepository.findAgentMessage( + messageContext.agentContext, + { + associatedRecordId: credentialRecord.id, + messageClass: V1ProposeCredentialMessage, + } + ) + const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) @@ -213,7 +218,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat previousSentMessage: offerCredentialMessage ?? undefined, }) - await this.formatService.processProposal({ + await this.formatService.processProposal(messageContext.agentContext, { credentialRecord, attachment: new Attachment({ data: new AttachmentData({ @@ -223,8 +228,8 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat }) // Update record - await this.updateState(credentialRecord, CredentialState.ProposalReceived) - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.updateState(messageContext.agentContext, credentialRecord, CredentialState.ProposalReceived) + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: proposalMessage, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, @@ -244,10 +249,10 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat this.connectionService.assertConnectionOrServiceDecorator(messageContext) // Save record - await this.credentialRepository.save(credentialRecord) - this.emitStateChangedEvent(credentialRecord, null) + await this.credentialRepository.save(messageContext.agentContext, credentialRecord) + this.emitStateChangedEvent(messageContext.agentContext, credentialRecord, null) - await this.didCommMessageRepository.saveAgentMessage({ + await this.didCommMessageRepository.saveAgentMessage(messageContext.agentContext, { agentMessage: proposalMessage, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, @@ -261,20 +266,21 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat * @param options The object containing config options * @returns Object containing proposal message and associated credential record */ - public async acceptProposal({ - credentialRecord, - credentialFormats, - comment, - autoAcceptCredential, - }: AcceptProposalOptions<[IndyCredentialFormat]>): Promise< - CredentialProtocolMsgReturnType - > { + public async acceptProposal( + agentContext: AgentContext, + { + credentialRecord, + credentialFormats, + comment, + autoAcceptCredential, + }: AcceptProposalOptions<[IndyCredentialFormat]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalReceived) if (credentialFormats) this.assertOnlyIndyFormat(credentialFormats) - const proposalMessage = await this.didCommMessageRepository.getAgentMessage({ + const proposalMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1ProposeCredentialMessage, }) @@ -284,7 +290,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat // if the user provided other attributes in the credentialFormats array. credentialRecord.credentialAttributes = proposalMessage.credentialPreview?.attributes - const { attachment, previewAttributes } = await this.formatService.acceptProposal({ + const { attachment, previewAttributes } = await this.formatService.acceptProposal(agentContext, { attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, credentialFormats, credentialRecord, @@ -312,9 +318,9 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat credentialRecord.credentialAttributes = previewAttributes credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential - await this.updateState(credentialRecord, CredentialState.OfferSent) + await this.updateState(agentContext, credentialRecord, CredentialState.OfferSent) - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -331,20 +337,21 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat * @returns Credential record associated with the credential offer and the corresponding new offer message * */ - public async negotiateProposal({ - credentialFormats, - credentialRecord, - comment, - autoAcceptCredential, - }: NegotiateProposalOptions<[IndyCredentialFormat]>): Promise< - CredentialProtocolMsgReturnType - > { + public async negotiateProposal( + agentContext: AgentContext, + { + credentialFormats, + credentialRecord, + comment, + autoAcceptCredential, + }: NegotiateProposalOptions<[IndyCredentialFormat]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalReceived) if (credentialFormats) this.assertOnlyIndyFormat(credentialFormats) - const { attachment, previewAttributes } = await this.formatService.createOffer({ + const { attachment, previewAttributes } = await this.formatService.createOffer(agentContext, { attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, credentialFormats, credentialRecord, @@ -366,9 +373,9 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat credentialRecord.credentialAttributes = previewAttributes credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential - await this.updateState(credentialRecord, CredentialState.OfferSent) + await this.updateState(agentContext, credentialRecord, CredentialState.OfferSent) - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -385,12 +392,10 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat * @returns Object containing offer message and associated credential record * */ - public async createOffer({ - credentialFormats, - autoAcceptCredential, - comment, - connection, - }: CreateOfferOptions<[IndyCredentialFormat]>): Promise> { + public async createOffer( + agentContext: AgentContext, + { credentialFormats, autoAcceptCredential, comment, connection }: CreateOfferOptions<[IndyCredentialFormat]> + ): Promise> { // Assert if (credentialFormats) this.assertOnlyIndyFormat(credentialFormats) @@ -410,7 +415,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat protocolVersion: 'v1', }) - const { attachment, previewAttributes } = await this.formatService.createOffer({ + const { attachment, previewAttributes } = await this.formatService.createOffer(agentContext, { attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, credentialFormats, credentialRecord, @@ -431,15 +436,15 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat attachments: credentialFormats.indy.linkedAttachments?.map((linkedAttachments) => linkedAttachments.attachment), }) - await this.didCommMessageRepository.saveAgentMessage({ + await this.didCommMessageRepository.saveAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, agentMessage: message, role: DidCommMessageRole.Sender, }) credentialRecord.credentialAttributes = previewAttributes - await this.credentialRepository.save(credentialRecord) - this.emitStateChangedEvent(credentialRecord, null) + await this.credentialRepository.save(agentContext, credentialRecord) + this.emitStateChangedEvent(agentContext, credentialRecord, null) return { message, credentialRecord } } @@ -455,13 +460,17 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat * */ public async processOffer( - messageContext: HandlerInboundMessage + messageContext: InboundMessageContext ): Promise { const { message: offerMessage, connection } = messageContext this.logger.debug(`Processing credential offer with id ${offerMessage.id}`) - let credentialRecord = await this.findByThreadAndConnectionId(offerMessage.threadId, connection?.id) + let credentialRecord = await this.findByThreadAndConnectionId( + messageContext.agentContext, + offerMessage.threadId, + connection?.id + ) const offerAttachment = offerMessage.getOfferAttachmentById(INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) if (!offerAttachment) { @@ -471,11 +480,14 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat } if (credentialRecord) { - const proposalCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ - associatedRecordId: credentialRecord.id, - messageClass: V1ProposeCredentialMessage, - }) - const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + const proposalCredentialMessage = await this.didCommMessageRepository.findAgentMessage( + messageContext.agentContext, + { + associatedRecordId: credentialRecord.id, + messageClass: V1ProposeCredentialMessage, + } + ) + const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) @@ -488,17 +500,17 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat previousSentMessage: proposalCredentialMessage ?? undefined, }) - await this.formatService.processOffer({ + await this.formatService.processOffer(messageContext.agentContext, { credentialRecord, attachment: offerAttachment, }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: offerMessage, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, }) - await this.updateState(credentialRecord, CredentialState.OfferReceived) + await this.updateState(messageContext.agentContext, credentialRecord, CredentialState.OfferReceived) return credentialRecord } else { @@ -513,19 +525,19 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat // Assert this.connectionService.assertConnectionOrServiceDecorator(messageContext) - await this.formatService.processOffer({ + await this.formatService.processOffer(messageContext.agentContext, { credentialRecord, attachment: offerAttachment, }) // Save in repository - await this.didCommMessageRepository.saveAgentMessage({ + await this.didCommMessageRepository.saveAgentMessage(messageContext.agentContext, { agentMessage: offerMessage, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, }) - await this.credentialRepository.save(credentialRecord) - this.emitStateChangedEvent(credentialRecord, null) + await this.credentialRepository.save(messageContext.agentContext, credentialRecord) + this.emitStateChangedEvent(messageContext.agentContext, credentialRecord, null) return credentialRecord } @@ -538,17 +550,15 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat * @returns Object containing request message and associated credential record * */ - public async acceptOffer({ - credentialRecord, - credentialFormats, - comment, - autoAcceptCredential, - }: AcceptOfferOptions<[IndyCredentialFormat]>): Promise> { + public async acceptOffer( + agentContext: AgentContext, + { credentialRecord, credentialFormats, comment, autoAcceptCredential }: AcceptOfferOptions<[IndyCredentialFormat]> + ): Promise> { // Assert credential credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferReceived) - const offerMessage = await this.didCommMessageRepository.getAgentMessage({ + const offerMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) @@ -560,7 +570,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat ) } - const { attachment } = await this.formatService.acceptOffer({ + const { attachment } = await this.formatService.acceptOffer(agentContext, { credentialRecord, credentialFormats, attachId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, @@ -580,12 +590,12 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat isLinkedAttachment(attachment) ) - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: requestMessage, associatedRecordId: credentialRecord.id, role: DidCommMessageRole.Sender, }) - await this.updateState(credentialRecord, CredentialState.RequestSent) + await this.updateState(agentContext, credentialRecord, CredentialState.RequestSent) return { message: requestMessage, credentialRecord } } @@ -600,12 +610,15 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat * @returns credential record associated with the credential request message * */ - public async negotiateOffer({ - credentialFormats, - credentialRecord, - autoAcceptCredential, - comment, - }: NegotiateOfferOptions<[IndyCredentialFormat]>): Promise> { + public async negotiateOffer( + agentContext: AgentContext, + { + credentialFormats, + credentialRecord, + autoAcceptCredential, + comment, + }: NegotiateOfferOptions<[IndyCredentialFormat]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferReceived) @@ -624,7 +637,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat // call create proposal for validation of the proposal and addition of linked attachments // As the format is different for v1 of the issue credential protocol we won't be using the attachment - const { previewAttributes, attachment } = await this.formatService.createProposal({ + const { previewAttributes, attachment } = await this.formatService.createProposal(agentContext, { credentialFormats, credentialRecord, }) @@ -647,7 +660,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat message.setThread({ threadId: credentialRecord.threadId }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -657,7 +670,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat credentialRecord.credentialAttributes = previewAttributes credentialRecord.linkedAttachments = linkedAttachments?.map((attachment) => attachment.attachment) credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential - await this.updateState(credentialRecord, CredentialState.ProposalSent) + await this.updateState(agentContext, credentialRecord, CredentialState.ProposalSent) return { credentialRecord, message } } @@ -688,14 +701,18 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat this.logger.debug(`Processing credential request with id ${requestMessage.id}`) - const credentialRecord = await this.getByThreadAndConnectionId(requestMessage.threadId, connection?.id) + const credentialRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + requestMessage.threadId, + connection?.id + ) this.logger.trace('Credential record found when processing credential request', credentialRecord) - const proposalMessage = await this.didCommMessageRepository.findAgentMessage({ + const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1ProposeCredentialMessage, }) - const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + const offerMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) @@ -716,18 +733,18 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat ) } - await this.formatService.processRequest({ + await this.formatService.processRequest(messageContext.agentContext, { credentialRecord, attachment: requestAttachment, }) - await this.didCommMessageRepository.saveAgentMessage({ + await this.didCommMessageRepository.saveAgentMessage(messageContext.agentContext, { agentMessage: requestMessage, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, }) - await this.updateState(credentialRecord, CredentialState.RequestReceived) + await this.updateState(messageContext.agentContext, credentialRecord, CredentialState.RequestReceived) return credentialRecord } @@ -738,21 +755,19 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat * @returns Object containing issue credential message and associated credential record * */ - public async acceptRequest({ - credentialRecord, - credentialFormats, - comment, - autoAcceptCredential, - }: AcceptRequestOptions<[IndyCredentialFormat]>): Promise> { + public async acceptRequest( + agentContext: AgentContext, + { credentialRecord, credentialFormats, comment, autoAcceptCredential }: AcceptRequestOptions<[IndyCredentialFormat]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.RequestReceived) - const offerMessage = await this.didCommMessageRepository.getAgentMessage({ + const offerMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) - const requestMessage = await this.didCommMessageRepository.getAgentMessage({ + const requestMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1RequestCredentialMessage, }) @@ -766,7 +781,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat ) } - const { attachment: credentialsAttach } = await this.formatService.acceptRequest({ + const { attachment: credentialsAttach } = await this.formatService.acceptRequest(agentContext, { credentialRecord, requestAttachment, offerAttachment, @@ -783,14 +798,14 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat issueMessage.setThread({ threadId: credentialRecord.threadId }) issueMessage.setPleaseAck() - await this.didCommMessageRepository.saveAgentMessage({ + await this.didCommMessageRepository.saveAgentMessage(agentContext, { agentMessage: issueMessage, associatedRecordId: credentialRecord.id, role: DidCommMessageRole.Sender, }) credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential - await this.updateState(credentialRecord, CredentialState.CredentialIssued) + await this.updateState(agentContext, credentialRecord, CredentialState.CredentialIssued) return { message: issueMessage, credentialRecord } } @@ -809,13 +824,17 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat this.logger.debug(`Processing credential with id ${issueMessage.id}`) - const credentialRecord = await this.getByThreadAndConnectionId(issueMessage.threadId, connection?.id) + const credentialRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + issueMessage.threadId, + connection?.id + ) - const requestCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + const requestCredentialMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1RequestCredentialMessage, }) - const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) @@ -833,18 +852,18 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat throw new AriesFrameworkError('Missing indy credential attachment in processCredential') } - await this.formatService.processCredential({ + await this.formatService.processCredential(messageContext.agentContext, { attachment: issueAttachment, credentialRecord, }) - await this.didCommMessageRepository.saveAgentMessage({ + await this.didCommMessageRepository.saveAgentMessage(messageContext.agentContext, { agentMessage: issueMessage, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, }) - await this.updateState(credentialRecord, CredentialState.CredentialReceived) + await this.updateState(messageContext.agentContext, credentialRecord, CredentialState.CredentialReceived) return credentialRecord } @@ -856,9 +875,10 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat * @returns Object containing credential acknowledgement message and associated credential record * */ - public async acceptCredential({ - credentialRecord, - }: AcceptCredentialOptions): Promise> { + public async acceptCredential( + agentContext: AgentContext, + { credentialRecord }: AcceptCredentialOptions + ): Promise> { credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.CredentialReceived) @@ -868,7 +888,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat threadId: credentialRecord.threadId, }) - await this.updateState(credentialRecord, CredentialState.Done) + await this.updateState(agentContext, credentialRecord, CredentialState.Done) return { message: ackMessage, credentialRecord } } @@ -887,13 +907,17 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat this.logger.debug(`Processing credential ack with id ${ackMessage.id}`) - const credentialRecord = await this.getByThreadAndConnectionId(ackMessage.threadId, connection?.id) + const credentialRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + ackMessage.threadId, + connection?.id + ) - const requestCredentialMessage = await this.didCommMessageRepository.getAgentMessage({ + const requestCredentialMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1RequestCredentialMessage, }) - const issueCredentialMessage = await this.didCommMessageRepository.getAgentMessage({ + const issueCredentialMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1IssueCredentialMessage, }) @@ -907,7 +931,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat }) // Update record - await this.updateState(credentialRecord, CredentialState.Done) + await this.updateState(messageContext.agentContext, credentialRecord, CredentialState.Done) return credentialRecord } @@ -919,7 +943,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat * @returns a {@link V1CredentialProblemReportMessage} * */ - public createProblemReport(options: CreateProblemReportOptions): ProblemReportMessage { + public createProblemReport(agentContext: AgentContext, options: CreateProblemReportOptions): ProblemReportMessage { return new V1CredentialProblemReportMessage({ description: { en: options.message, @@ -929,18 +953,24 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat } // AUTO RESPOND METHODS - public async shouldAutoRespondToProposal(options: { - credentialRecord: CredentialExchangeRecord - proposalMessage: V1ProposeCredentialMessage - }): Promise { + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + options: { + credentialRecord: CredentialExchangeRecord + proposalMessage: V1ProposeCredentialMessage + } + ): Promise { const { credentialRecord, proposalMessage } = options - const autoAccept = composeAutoAccept(credentialRecord.autoAcceptCredential, this.agentConfig.autoAcceptCredentials) + const autoAccept = composeAutoAccept( + credentialRecord.autoAcceptCredential, + agentContext.config.autoAcceptCredentials + ) // Handle always / never cases if (autoAccept === AutoAcceptCredential.Always) return true if (autoAccept === AutoAcceptCredential.Never) return false - const offerMessage = await this.findOfferMessage(credentialRecord.id) + const offerMessage = await this.findOfferMessage(agentContext, credentialRecord.id) // Do not auto accept if missing properties if (!offerMessage || !offerMessage.credentialPreview) return false @@ -959,18 +989,24 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat ) } - public async shouldAutoRespondToOffer(options: { - credentialRecord: CredentialExchangeRecord - offerMessage: V1OfferCredentialMessage - }) { + public async shouldAutoRespondToOffer( + agentContext: AgentContext, + options: { + credentialRecord: CredentialExchangeRecord + offerMessage: V1OfferCredentialMessage + } + ) { const { credentialRecord, offerMessage } = options - const autoAccept = composeAutoAccept(credentialRecord.autoAcceptCredential, this.agentConfig.autoAcceptCredentials) + const autoAccept = composeAutoAccept( + credentialRecord.autoAcceptCredential, + agentContext.config.autoAcceptCredentials + ) // Handle always / never cases if (autoAccept === AutoAcceptCredential.Always) return true if (autoAccept === AutoAcceptCredential.Never) return false - const proposalMessage = await this.findProposalMessage(credentialRecord.id) + const proposalMessage = await this.findProposalMessage(agentContext, credentialRecord.id) // Do not auto accept if missing properties if (!offerMessage.credentialPreview) return false @@ -989,18 +1025,24 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat ) } - public async shouldAutoRespondToRequest(options: { - credentialRecord: CredentialExchangeRecord - requestMessage: V1RequestCredentialMessage - }) { + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + options: { + credentialRecord: CredentialExchangeRecord + requestMessage: V1RequestCredentialMessage + } + ) { const { credentialRecord, requestMessage } = options - const autoAccept = composeAutoAccept(credentialRecord.autoAcceptCredential, this.agentConfig.autoAcceptCredentials) + const autoAccept = composeAutoAccept( + credentialRecord.autoAcceptCredential, + agentContext.config.autoAcceptCredentials + ) // Handle always / never cases if (autoAccept === AutoAcceptCredential.Always) return true if (autoAccept === AutoAcceptCredential.Never) return false - const offerMessage = await this.findOfferMessage(credentialRecord.id) + const offerMessage = await this.findOfferMessage(agentContext, credentialRecord.id) if (!offerMessage) return false const offerAttachment = offerMessage.getOfferAttachmentById(INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) @@ -1008,26 +1050,32 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat if (!offerAttachment || !requestAttachment) return false - return this.formatService.shouldAutoRespondToRequest({ + return this.formatService.shouldAutoRespondToRequest(agentContext, { credentialRecord, offerAttachment, requestAttachment, }) } - public async shouldAutoRespondToCredential(options: { - credentialRecord: CredentialExchangeRecord - credentialMessage: V1IssueCredentialMessage - }) { + public async shouldAutoRespondToCredential( + agentContext: AgentContext, + options: { + credentialRecord: CredentialExchangeRecord + credentialMessage: V1IssueCredentialMessage + } + ) { const { credentialRecord, credentialMessage } = options - const autoAccept = composeAutoAccept(credentialRecord.autoAcceptCredential, this.agentConfig.autoAcceptCredentials) + const autoAccept = composeAutoAccept( + credentialRecord.autoAcceptCredential, + agentContext.config.autoAcceptCredentials + ) // Handle always / never cases if (autoAccept === AutoAcceptCredential.Always) return true if (autoAccept === AutoAcceptCredential.Never) return false - const requestMessage = await this.findRequestMessage(credentialRecord.id) - const offerMessage = await this.findOfferMessage(credentialRecord.id) + const requestMessage = await this.findRequestMessage(agentContext, credentialRecord.id) + const offerMessage = await this.findOfferMessage(agentContext, credentialRecord.id) const credentialAttachment = credentialMessage.getCredentialAttachmentById(INDY_CREDENTIAL_ATTACHMENT_ID) if (!credentialAttachment) return false @@ -1037,7 +1085,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat const offerAttachment = offerMessage?.getOfferAttachmentById(INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) - return this.formatService.shouldAutoRespondToCredential({ + return this.formatService.shouldAutoRespondToCredential(agentContext, { credentialRecord, credentialAttachment, requestAttachment, @@ -1045,41 +1093,44 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat }) } - public async findProposalMessage(credentialExchangeId: string) { - return await this.didCommMessageRepository.findAgentMessage({ + public async findProposalMessage(agentContext: AgentContext, credentialExchangeId: string) { + return await this.didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: credentialExchangeId, messageClass: V1ProposeCredentialMessage, }) } - public async findOfferMessage(credentialExchangeId: string) { - return await this.didCommMessageRepository.findAgentMessage({ + public async findOfferMessage(agentContext: AgentContext, credentialExchangeId: string) { + return await this.didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: credentialExchangeId, messageClass: V1OfferCredentialMessage, }) } - public async findRequestMessage(credentialExchangeId: string) { - return await this.didCommMessageRepository.findAgentMessage({ + public async findRequestMessage(agentContext: AgentContext, credentialExchangeId: string) { + return await this.didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: credentialExchangeId, messageClass: V1RequestCredentialMessage, }) } - public async findCredentialMessage(credentialExchangeId: string) { - return await this.didCommMessageRepository.findAgentMessage({ + public async findCredentialMessage(agentContext: AgentContext, credentialExchangeId: string) { + return await this.didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: credentialExchangeId, messageClass: V1IssueCredentialMessage, }) } - public async getFormatData(credentialExchangeId: string): Promise> { + public async getFormatData( + agentContext: AgentContext, + credentialExchangeId: string + ): Promise> { // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. const [proposalMessage, offerMessage, requestMessage, credentialMessage] = await Promise.all([ - this.findProposalMessage(credentialExchangeId), - this.findOfferMessage(credentialExchangeId), - this.findRequestMessage(credentialExchangeId), - this.findCredentialMessage(credentialExchangeId), + this.findProposalMessage(agentContext, credentialExchangeId), + this.findOfferMessage(agentContext, credentialExchangeId), + this.findRequestMessage(agentContext, credentialExchangeId), + this.findCredentialMessage(agentContext, credentialExchangeId), ]) const indyProposal = proposalMessage @@ -1117,14 +1168,12 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat } protected registerHandlers() { - this.dispatcher.registerHandler(new V1ProposeCredentialHandler(this, this.agentConfig)) - this.dispatcher.registerHandler( - new V1OfferCredentialHandler(this, this.agentConfig, this.routingService, this.didCommMessageRepository) - ) + this.dispatcher.registerHandler(new V1ProposeCredentialHandler(this, this.logger)) this.dispatcher.registerHandler( - new V1RequestCredentialHandler(this, this.agentConfig, this.didCommMessageRepository) + new V1OfferCredentialHandler(this, this.routingService, this.didCommMessageRepository, this.logger) ) - this.dispatcher.registerHandler(new V1IssueCredentialHandler(this, this.agentConfig, this.didCommMessageRepository)) + this.dispatcher.registerHandler(new V1RequestCredentialHandler(this, this.didCommMessageRepository, this.logger)) + this.dispatcher.registerHandler(new V1IssueCredentialHandler(this, this.didCommMessageRepository, this.logger)) this.dispatcher.registerHandler(new V1CredentialAckHandler(this)) this.dispatcher.registerHandler(new V1CredentialProblemReportHandler(this)) } diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts index e0188dd352..7f2760a501 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../../../../../agent' import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { GetAgentMessageOptions } from '../../../../../storage/didcomm/DidCommMessageRepository' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' @@ -5,7 +6,9 @@ import type { IndyCredentialViewMetadata } from '../../../formats/indy/models' import type { CredentialPreviewAttribute } from '../../../models' import type { CustomCredentialTags } from '../../../repository/CredentialExchangeRecord' -import { getAgentConfig, getMockConnection, mockFunction } from '../../../../../../tests/helpers' +import { Subject } from 'rxjs' + +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' import { Dispatcher } from '../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../agent/EventEmitter' import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' @@ -35,12 +38,12 @@ import { INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, V1CredentialAckMessage, - V1CredentialPreview, V1CredentialProblemReportMessage, V1IssueCredentialMessage, V1OfferCredentialMessage, V1ProposeCredentialMessage, V1RequestCredentialMessage, + V1CredentialPreview, } from '../messages' // Mock classes @@ -132,7 +135,7 @@ const didCommMessageRecord = new DidCommMessageRecord({ }) // eslint-disable-next-line @typescript-eslint/no-explicit-any -const getAgentMessageMock = async (options: GetAgentMessageOptions) => { +const getAgentMessageMock = async (agentContext: AgentContext, options: GetAgentMessageOptions) => { if (options.messageClass === V1ProposeCredentialMessage) { return credentialProposalMessage } @@ -218,12 +221,14 @@ const mockCredentialRecord = ({ describe('V1CredentialService', () => { let eventEmitter: EventEmitter let agentConfig: AgentConfig + let agentContext: AgentContext let credentialService: V1CredentialService beforeEach(async () => { // real objects agentConfig = getAgentConfig('V1CredentialServiceCredTest') - eventEmitter = new EventEmitter(agentConfig) + agentContext = getAgentContext() + eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) // mock function implementations mockFunction(connectionService.getById).mockResolvedValue(connection) @@ -238,7 +243,7 @@ describe('V1CredentialService', () => { credentialService = new V1CredentialService( connectionService, didCommMessageRepository, - agentConfig, + agentConfig.logger, routingService, dispatcher, eventEmitter, @@ -276,7 +281,7 @@ describe('V1CredentialService', () => { }) // when - const { message } = await credentialService.acceptOffer({ + const { message } = await credentialService.acceptOffer(agentContext, { comment: 'hello', autoAcceptCredential: AutoAcceptCredential.Never, credentialRecord, @@ -299,7 +304,7 @@ describe('V1CredentialService', () => { 'requests~attach': [JsonTransformer.toJSON(requestAttachment)], }) expect(credentialRepository.update).toHaveBeenCalledTimes(1) - expect(indyCredentialFormatService.acceptOffer).toHaveBeenCalledWith({ + expect(indyCredentialFormatService.acceptOffer).toHaveBeenCalledWith(agentContext, { credentialRecord, attachId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, offerAttachment, @@ -309,7 +314,7 @@ describe('V1CredentialService', () => { }, }, }) - expect(didCommMessageRepository.saveOrUpdateAgentMessage).toHaveBeenCalledWith({ + expect(didCommMessageRepository.saveOrUpdateAgentMessage).toHaveBeenCalledWith(agentContext, { agentMessage: message, associatedRecordId: '84353745-8bd9-42e1-8d81-238ca77c29d2', role: DidCommMessageRole.Sender, @@ -333,12 +338,12 @@ describe('V1CredentialService', () => { }) // when - await credentialService.acceptOffer({ + await credentialService.acceptOffer(agentContext, { credentialRecord, }) // then - expect(updateStateSpy).toHaveBeenCalledWith(credentialRecord, CredentialState.RequestSent) + expect(updateStateSpy).toHaveBeenCalledWith(agentContext, credentialRecord, CredentialState.RequestSent) }) const validState = CredentialState.OfferReceived @@ -347,7 +352,7 @@ describe('V1CredentialService', () => { await Promise.all( invalidCredentialStates.map(async (state) => { await expect( - credentialService.acceptOffer({ credentialRecord: mockCredentialRecord({ state }) }) + credentialService.acceptOffer(agentContext, { credentialRecord: mockCredentialRecord({ state }) }) ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) }) ) @@ -366,6 +371,7 @@ describe('V1CredentialService', () => { }) credentialRequest.setThread({ threadId: 'somethreadid' }) messageContext = new InboundMessageContext(credentialRequest, { + agentContext, connection, }) }) @@ -380,7 +386,7 @@ describe('V1CredentialService', () => { const returnedCredentialRecord = await credentialService.processRequest(messageContext) // then - expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, { + expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { threadId: 'somethreadid', connectionId: connection.id, }) @@ -397,7 +403,7 @@ describe('V1CredentialService', () => { const returnedCredentialRecord = await credentialService.processRequest(messageContext) // then - expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, { + expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { threadId: 'somethreadid', connectionId: connection.id, }) @@ -438,10 +444,11 @@ describe('V1CredentialService', () => { }) // when - await credentialService.acceptRequest({ credentialRecord }) + await credentialService.acceptRequest(agentContext, { credentialRecord }) // then expect(credentialRepository.update).toHaveBeenCalledWith( + agentContext, expect.objectContaining({ state: CredentialState.CredentialIssued, }) @@ -470,7 +477,7 @@ describe('V1CredentialService', () => { eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // when - await credentialService.acceptRequest({ credentialRecord }) + await credentialService.acceptRequest(agentContext, { credentialRecord }) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -504,7 +511,7 @@ describe('V1CredentialService', () => { }) // when - const { message } = await credentialService.acceptRequest({ credentialRecord, comment }) + const { message } = await credentialService.acceptRequest(agentContext, { credentialRecord, comment }) // then expect(message.toJSON()).toMatchObject({ @@ -518,7 +525,7 @@ describe('V1CredentialService', () => { '~please_ack': expect.any(Object), }) - expect(indyCredentialFormatService.acceptRequest).toHaveBeenCalledWith({ + expect(indyCredentialFormatService.acceptRequest).toHaveBeenCalledWith(agentContext, { credentialRecord, requestAttachment, offerAttachment, @@ -538,9 +545,7 @@ describe('V1CredentialService', () => { credentialAttachments: [credentialAttachment], }) credentialResponse.setThread({ threadId: 'somethreadid' }) - const messageContext = new InboundMessageContext(credentialResponse, { - connection, - }) + const messageContext = new InboundMessageContext(credentialResponse, { agentContext, connection }) mockFunction(credentialRepository.getSingleByQuery).mockResolvedValue(credentialRecord) @@ -548,18 +553,18 @@ describe('V1CredentialService', () => { await credentialService.processCredential(messageContext) // then - expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, { + expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { threadId: 'somethreadid', connectionId: connection.id, }) - expect(didCommMessageRepository.saveAgentMessage).toHaveBeenCalledWith({ + expect(didCommMessageRepository.saveAgentMessage).toHaveBeenCalledWith(agentContext, { agentMessage: credentialResponse, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, }) - expect(indyCredentialFormatService.processCredential).toHaveBeenNthCalledWith(1, { + expect(indyCredentialFormatService.processCredential).toHaveBeenNthCalledWith(1, agentContext, { attachment: credentialAttachment, credentialRecord, }) @@ -583,11 +588,11 @@ describe('V1CredentialService', () => { const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') // when - await credentialService.acceptCredential({ credentialRecord: credential }) + await credentialService.acceptCredential(agentContext, { credentialRecord: credential }) // then expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) - const [[updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls + const [[, updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls expect(updatedCredentialRecord).toMatchObject({ state: CredentialState.Done, }) @@ -598,7 +603,7 @@ describe('V1CredentialService', () => { eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // when - await credentialService.acceptCredential({ credentialRecord: credential }) + await credentialService.acceptCredential(agentContext, { credentialRecord: credential }) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -617,7 +622,9 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) // when - const { message: ackMessage } = await credentialService.acceptCredential({ credentialRecord: credential }) + const { message: ackMessage } = await credentialService.acceptCredential(agentContext, { + credentialRecord: credential, + }) // then expect(ackMessage.toJSON()).toMatchObject({ @@ -635,7 +642,7 @@ describe('V1CredentialService', () => { await Promise.all( invalidCredentialStates.map(async (state) => { await expect( - credentialService.acceptCredential({ + credentialService.acceptCredential(agentContext, { credentialRecord: mockCredentialRecord({ state, threadId, @@ -661,9 +668,7 @@ describe('V1CredentialService', () => { status: AckStatus.OK, threadId: 'somethreadid', }) - messageContext = new InboundMessageContext(credentialRequest, { - connection, - }) + messageContext = new InboundMessageContext(credentialRequest, { agentContext, connection }) }) test(`updates state to ${CredentialState.Done} and returns credential record`, async () => { @@ -679,12 +684,12 @@ describe('V1CredentialService', () => { const expectedCredentialRecord = { state: CredentialState.Done, } - expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, { + expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { threadId: 'somethreadid', connectionId: connection.id, }) expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) - const [[updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls + const [[, updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls expect(updatedCredentialRecord).toMatchObject(expectedCredentialRecord) expect(returnedCredentialRecord).toMatchObject(expectedCredentialRecord) }) @@ -708,7 +713,7 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) // when - const credentialProblemReportMessage = credentialService.createProblemReport({ message }) + const credentialProblemReportMessage = credentialService.createProblemReport(agentContext, { message }) credentialProblemReportMessage.setThread({ threadId }) // then @@ -742,9 +747,7 @@ describe('V1CredentialService', () => { }, }) credentialProblemReportMessage.setThread({ threadId: 'somethreadid' }) - messageContext = new InboundMessageContext(credentialProblemReportMessage, { - connection, - }) + messageContext = new InboundMessageContext(credentialProblemReportMessage, { agentContext, connection }) }) test(`updates problem report error message and returns credential record`, async () => { @@ -760,12 +763,12 @@ describe('V1CredentialService', () => { const expectedCredentialRecord = { errorMessage: 'issuance-abandoned: Indy error', } - expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, { + expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { threadId: 'somethreadid', connectionId: connection.id, }) expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) - const [[updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls + const [[, updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls expect(updatedCredentialRecord).toMatchObject(expectedCredentialRecord) expect(returnedCredentialRecord).toMatchObject(expectedCredentialRecord) }) @@ -775,8 +778,8 @@ describe('V1CredentialService', () => { it('getById should return value from credentialRepository.getById', async () => { const expected = mockCredentialRecord() mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.getById(expected.id) - expect(credentialRepository.getById).toBeCalledWith(expected.id) + const result = await credentialService.getById(agentContext, expected.id) + expect(credentialRepository.getById).toBeCalledWith(agentContext, expected.id) expect(result).toBe(expected) }) @@ -784,8 +787,8 @@ describe('V1CredentialService', () => { it('getById should return value from credentialRepository.getSingleByQuery', async () => { const expected = mockCredentialRecord() mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.getByThreadAndConnectionId('threadId', 'connectionId') - expect(credentialRepository.getSingleByQuery).toBeCalledWith({ + const result = await credentialService.getByThreadAndConnectionId(agentContext, 'threadId', 'connectionId') + expect(credentialRepository.getSingleByQuery).toBeCalledWith(agentContext, { threadId: 'threadId', connectionId: 'connectionId', }) @@ -796,8 +799,8 @@ describe('V1CredentialService', () => { it('findById should return value from credentialRepository.findById', async () => { const expected = mockCredentialRecord() mockFunction(credentialRepository.findById).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.findById(expected.id) - expect(credentialRepository.findById).toBeCalledWith(expected.id) + const result = await credentialService.findById(agentContext, expected.id) + expect(credentialRepository.findById).toBeCalledWith(agentContext, expected.id) expect(result).toBe(expected) }) @@ -806,8 +809,8 @@ describe('V1CredentialService', () => { const expected = [mockCredentialRecord(), mockCredentialRecord()] mockFunction(credentialRepository.getAll).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.getAll() - expect(credentialRepository.getAll).toBeCalledWith() + const result = await credentialService.getAll(agentContext) + expect(credentialRepository.getAll).toBeCalledWith(agentContext) expect(result).toEqual(expect.arrayContaining(expected)) }) @@ -819,8 +822,8 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credentialRecord)) const repositoryDeleteSpy = jest.spyOn(credentialRepository, 'delete') - await credentialService.delete(credentialRecord) - expect(repositoryDeleteSpy).toHaveBeenNthCalledWith(1, credentialRecord) + await credentialService.delete(agentContext, credentialRecord) + expect(repositoryDeleteSpy).toHaveBeenNthCalledWith(1, agentContext, credentialRecord) }) it('should call deleteCredentialById in indyCredentialFormatService if deleteAssociatedCredential is true', async () => { @@ -829,12 +832,16 @@ describe('V1CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(credentialRecord, { + await credentialService.delete(agentContext, credentialRecord, { deleteAssociatedCredentials: true, deleteAssociatedDidCommMessages: false, }) - expect(deleteCredentialMock).toHaveBeenNthCalledWith(1, credentialRecord.credentials[0].credentialRecordId) + expect(deleteCredentialMock).toHaveBeenNthCalledWith( + 1, + agentContext, + credentialRecord.credentials[0].credentialRecordId + ) }) it('should not call deleteCredentialById in indyCredentialFormatService if deleteAssociatedCredential is false', async () => { @@ -843,7 +850,7 @@ describe('V1CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(credentialRecord, { + await credentialService.delete(agentContext, credentialRecord, { deleteAssociatedCredentials: false, deleteAssociatedDidCommMessages: false, }) @@ -857,9 +864,13 @@ describe('V1CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(credentialRecord) + await credentialService.delete(agentContext, credentialRecord) - expect(deleteCredentialMock).toHaveBeenNthCalledWith(1, credentialRecord.credentials[0].credentialRecordId) + expect(deleteCredentialMock).toHaveBeenNthCalledWith( + 1, + agentContext, + credentialRecord.credentials[0].credentialRecordId + ) }) it('deleteAssociatedDidCommMessages should default to true', async () => { const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) @@ -867,9 +878,13 @@ describe('V1CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(credentialRecord) + await credentialService.delete(agentContext, credentialRecord) - expect(deleteCredentialMock).toHaveBeenNthCalledWith(1, credentialRecord.credentials[0].credentialRecordId) + expect(deleteCredentialMock).toHaveBeenNthCalledWith( + 1, + agentContext, + credentialRecord.credentials[0].credentialRecordId + ) expect(didCommMessageRepository.delete).toHaveBeenCalledTimes(3) }) }) @@ -890,14 +905,18 @@ describe('V1CredentialService', () => { const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') // when - await credentialService.declineOffer(credential) + await credentialService.declineOffer(agentContext, credential) // then const expectedCredentialState = { state: CredentialState.Declined, } expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) - expect(repositoryUpdateSpy).toHaveBeenNthCalledWith(1, expect.objectContaining(expectedCredentialState)) + expect(repositoryUpdateSpy).toHaveBeenNthCalledWith( + 1, + agentContext, + expect.objectContaining(expectedCredentialState) + ) }) test(`emits stateChange event from ${CredentialState.OfferReceived} to ${CredentialState.Declined}`, async () => { @@ -908,7 +927,7 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) // when - await credentialService.declineOffer(credential) + await credentialService.declineOffer(agentContext, credential) // then expect(eventListenerMock).toHaveBeenCalledTimes(1) @@ -930,7 +949,7 @@ describe('V1CredentialService', () => { await Promise.all( invalidCredentialStates.map(async (state) => { await expect( - credentialService.declineOffer(mockCredentialRecord({ state, tags: { threadId } })) + credentialService.declineOffer(agentContext, mockCredentialRecord({ state, tags: { threadId } })) ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) }) ) diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts index 92c3434bd8..7ebe9467aa 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts @@ -1,9 +1,10 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' import type { CreateOfferOptions, CreateProposalOptions } from '../../../CredentialServiceOptions' import type { IndyCredentialFormat } from '../../../formats/indy/IndyCredentialFormat' -import { getAgentConfig, getMockConnection, mockFunction } from '../../../../../../tests/helpers' +import { Subject } from 'rxjs' + +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' import { Dispatcher } from '../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../agent/EventEmitter' import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' @@ -51,6 +52,9 @@ const indyCredentialFormatService = new IndyCredentialFormatServiceMock() const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() +const agentConfig = getAgentConfig('V1CredentialServiceProposeOfferTest') +const agentContext = getAgentContext() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore indyCredentialFormatService.credentialRecordType = 'indy' @@ -89,13 +93,11 @@ const proposalAttachment = new Attachment({ describe('V1CredentialServiceProposeOffer', () => { let eventEmitter: EventEmitter - let agentConfig: AgentConfig + let credentialService: V1CredentialService beforeEach(async () => { - // real objects - agentConfig = getAgentConfig('V1CredentialServiceProposeOfferTest') - eventEmitter = new EventEmitter(agentConfig) + eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) // mock function implementations mockFunction(connectionService.getById).mockResolvedValue(connection) @@ -107,7 +109,7 @@ describe('V1CredentialServiceProposeOffer', () => { credentialService = new V1CredentialService( connectionService, didCommMessageRepository, - agentConfig, + agentConfig.logger, routingService, dispatcher, eventEmitter, @@ -148,11 +150,12 @@ describe('V1CredentialServiceProposeOffer', () => { }), }) - await credentialService.createProposal(proposeOptions) + await credentialService.createProposal(agentContext, proposeOptions) // then expect(repositorySaveSpy).toHaveBeenNthCalledWith( 1, + agentContext, expect.objectContaining({ type: CredentialExchangeRecord.type, id: expect.any(String), @@ -175,7 +178,7 @@ describe('V1CredentialServiceProposeOffer', () => { }), }) - await credentialService.createProposal(proposeOptions) + await credentialService.createProposal(agentContext, proposeOptions) expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', @@ -198,7 +201,7 @@ describe('V1CredentialServiceProposeOffer', () => { previewAttributes: credentialPreview.attributes, }) - const { message } = await credentialService.createProposal(proposeOptions) + const { message } = await credentialService.createProposal(agentContext, proposeOptions) expect(message.toJSON()).toMatchObject({ '@id': expect.any(String), @@ -253,12 +256,12 @@ describe('V1CredentialServiceProposeOffer', () => { const repositorySaveSpy = jest.spyOn(credentialRepository, 'save') - await credentialService.createOffer(offerOptions) + await credentialService.createOffer(agentContext, offerOptions) // then expect(repositorySaveSpy).toHaveBeenCalledTimes(1) - const [[createdCredentialRecord]] = repositorySaveSpy.mock.calls + const [[, createdCredentialRecord]] = repositorySaveSpy.mock.calls expect(createdCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), @@ -282,7 +285,7 @@ describe('V1CredentialServiceProposeOffer', () => { previewAttributes: credentialPreview.attributes, }) - await credentialService.createOffer(offerOptions) + await credentialService.createOffer(agentContext, offerOptions) expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', @@ -304,7 +307,7 @@ describe('V1CredentialServiceProposeOffer', () => { }), }) - await expect(credentialService.createOffer(offerOptions)).rejects.toThrowError( + await expect(credentialService.createOffer(agentContext, offerOptions)).rejects.toThrowError( 'Missing required credential preview from indy format service' ) }) @@ -319,7 +322,7 @@ describe('V1CredentialServiceProposeOffer', () => { previewAttributes: credentialPreview.attributes, }) - const { message: credentialOffer } = await credentialService.createOffer(offerOptions) + const { message: credentialOffer } = await credentialService.createOffer(agentContext, offerOptions) expect(credentialOffer.toJSON()).toMatchObject({ '@id': expect.any(String), '@type': 'https://didcomm.org/issue-credential/1.0/offer-credential', @@ -350,9 +353,7 @@ describe('V1CredentialServiceProposeOffer', () => { credentialPreview: credentialPreview, offerAttachments: [offerAttachment], }) - const messageContext = new InboundMessageContext(credentialOfferMessage, { - connection, - }) + const messageContext = new InboundMessageContext(credentialOfferMessage, { agentContext, connection }) test(`creates and return credential record in ${CredentialState.OfferReceived} state with offer, thread ID`, async () => { // when @@ -361,6 +362,7 @@ describe('V1CredentialServiceProposeOffer', () => { // then expect(credentialRepository.save).toHaveBeenNthCalledWith( 1, + agentContext, expect.objectContaining({ type: CredentialExchangeRecord.type, id: expect.any(String), diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials.e2e.test.ts index 6ee984bca7..1d25498a3a 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials.e2e.test.ts @@ -96,7 +96,7 @@ describe('v1 credentials', () => { }) const didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - const offerMessageRecord = await didCommMessageRepository.findAgentMessage({ + const offerMessageRecord = await didCommMessageRepository.findAgentMessage(faberAgent.context, { associatedRecordId: faberCredentialRecord.id, messageClass: V1OfferCredentialMessage, }) diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts index 0a213e9c30..bf12db449a 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts @@ -1,5 +1,5 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { Logger } from '../../../../../logger' import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialService } from '../V1CredentialService' @@ -9,24 +9,24 @@ import { V1IssueCredentialMessage, V1RequestCredentialMessage } from '../message export class V1IssueCredentialHandler implements Handler { private credentialService: V1CredentialService - private agentConfig: AgentConfig private didCommMessageRepository: DidCommMessageRepository + private logger: Logger public supportedMessages = [V1IssueCredentialMessage] public constructor( credentialService: V1CredentialService, - agentConfig: AgentConfig, - didCommMessageRepository: DidCommMessageRepository + didCommMessageRepository: DidCommMessageRepository, + logger: Logger ) { this.credentialService = credentialService - this.agentConfig = agentConfig this.didCommMessageRepository = didCommMessageRepository + this.logger = logger } public async handle(messageContext: HandlerInboundMessage) { const credentialRecord = await this.credentialService.processCredential(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToCredential({ + const shouldAutoRespond = await this.credentialService.shouldAutoRespondToCredential(messageContext.agentContext, { credentialRecord, credentialMessage: messageContext.message, }) @@ -40,14 +40,14 @@ export class V1IssueCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: HandlerInboundMessage ) { - this.agentConfig.logger.info( - `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + this.logger.info( + `Automatically sending acknowledgement with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` ) - const { message } = await this.credentialService.acceptCredential({ + const { message } = await this.credentialService.acceptCredential(messageContext.agentContext, { credentialRecord, }) - const requestMessage = await this.didCommMessageRepository.getAgentMessage({ + const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1RequestCredentialMessage, }) @@ -65,6 +65,6 @@ export class V1IssueCredentialHandler implements Handler { }) } - this.agentConfig.logger.error(`Could not automatically create credential ack`) + this.logger.error(`Could not automatically create credential ack`) } } diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts index fab851c7df..207cbff379 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts @@ -1,5 +1,5 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { Logger } from '../../../../../logger' import type { DidCommMessageRepository } from '../../../../../storage' import type { RoutingService } from '../../../../routing/services/RoutingService' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' @@ -12,27 +12,27 @@ import { V1OfferCredentialMessage } from '../messages' export class V1OfferCredentialHandler implements Handler { private credentialService: V1CredentialService - private agentConfig: AgentConfig private routingService: RoutingService private didCommMessageRepository: DidCommMessageRepository + private logger: Logger public supportedMessages = [V1OfferCredentialMessage] public constructor( credentialService: V1CredentialService, - agentConfig: AgentConfig, routingService: RoutingService, - didCommMessageRepository: DidCommMessageRepository + didCommMessageRepository: DidCommMessageRepository, + logger: Logger ) { this.credentialService = credentialService - this.agentConfig = agentConfig this.routingService = routingService this.didCommMessageRepository = didCommMessageRepository + this.logger = logger } public async handle(messageContext: HandlerInboundMessage) { const credentialRecord = await this.credentialService.processOffer(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToOffer({ + const shouldAutoRespond = await this.credentialService.shouldAutoRespondToOffer(messageContext.agentContext, { credentialRecord, offerMessage: messageContext.message, }) @@ -46,15 +46,15 @@ export class V1OfferCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: HandlerInboundMessage ) { - this.agentConfig.logger.info( - `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + this.logger.info( + `Automatically sending request with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` ) if (messageContext.connection) { - const { message } = await this.credentialService.acceptOffer({ credentialRecord }) + const { message } = await this.credentialService.acceptOffer(messageContext.agentContext, { credentialRecord }) return createOutboundMessage(messageContext.connection, message) } else if (messageContext.message.service) { - const routing = await this.routingService.getRouting() + const routing = await this.routingService.getRouting(messageContext.agentContext) const ourService = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], @@ -62,7 +62,7 @@ export class V1OfferCredentialHandler implements Handler { }) const recipientService = messageContext.message.service - const { message } = await this.credentialService.acceptOffer({ + const { message } = await this.credentialService.acceptOffer(messageContext.agentContext, { credentialRecord, credentialFormats: { indy: { @@ -73,7 +73,7 @@ export class V1OfferCredentialHandler implements Handler { // Set and save ~service decorator to record (to remember our verkey) message.service = ourService - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -86,6 +86,6 @@ export class V1OfferCredentialHandler implements Handler { }) } - this.agentConfig.logger.error(`Could not automatically create credential request`) + this.logger.error(`Could not automatically create credential request`) } } diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts index ed5992e136..38c32018d7 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts @@ -1,5 +1,5 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { Logger } from '../../../../../logger' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialService } from '../V1CredentialService' @@ -8,21 +8,24 @@ import { V1ProposeCredentialMessage } from '../messages' export class V1ProposeCredentialHandler implements Handler { private credentialService: V1CredentialService - private agentConfig: AgentConfig + private logger: Logger public supportedMessages = [V1ProposeCredentialMessage] - public constructor(credentialService: V1CredentialService, agentConfig: AgentConfig) { + public constructor(credentialService: V1CredentialService, logger: Logger) { this.credentialService = credentialService - this.agentConfig = agentConfig + this.logger = logger } public async handle(messageContext: HandlerInboundMessage) { const credentialRecord = await this.credentialService.processProposal(messageContext) - const shouldAutoAcceptProposal = await this.credentialService.shouldAutoRespondToProposal({ - credentialRecord, - proposalMessage: messageContext.message, - }) + const shouldAutoAcceptProposal = await this.credentialService.shouldAutoRespondToProposal( + messageContext.agentContext, + { + credentialRecord, + proposalMessage: messageContext.message, + } + ) if (shouldAutoAcceptProposal) { return await this.acceptProposal(credentialRecord, messageContext) @@ -33,16 +36,16 @@ export class V1ProposeCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: HandlerInboundMessage ) { - this.agentConfig.logger.info( - `Automatically sending offer with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + this.logger.info( + `Automatically sending offer with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` ) if (!messageContext.connection) { - this.agentConfig.logger.error('No connection on the messageContext, aborting auto accept') + this.logger.error('No connection on the messageContext, aborting auto accept') return } - const { message } = await this.credentialService.acceptProposal({ + const { message } = await this.credentialService.acceptProposal(messageContext.agentContext, { credentialRecord, }) diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts index 7e91d61e25..a5eb94ad41 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts @@ -1,5 +1,5 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { Logger } from '../../../../../logger' import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialService } from '../V1CredentialService' @@ -9,25 +9,25 @@ import { DidCommMessageRole } from '../../../../../storage' import { V1RequestCredentialMessage } from '../messages' export class V1RequestCredentialHandler implements Handler { - private agentConfig: AgentConfig private credentialService: V1CredentialService private didCommMessageRepository: DidCommMessageRepository + private logger: Logger public supportedMessages = [V1RequestCredentialMessage] public constructor( credentialService: V1CredentialService, - agentConfig: AgentConfig, - didCommMessageRepository: DidCommMessageRepository + didCommMessageRepository: DidCommMessageRepository, + logger: Logger ) { this.credentialService = credentialService - this.agentConfig = agentConfig + this.logger = logger this.didCommMessageRepository = didCommMessageRepository } public async handle(messageContext: HandlerInboundMessage) { const credentialRecord = await this.credentialService.processRequest(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToRequest({ + const shouldAutoRespond = await this.credentialService.shouldAutoRespondToRequest(messageContext.agentContext, { credentialRecord, requestMessage: messageContext.message, }) @@ -41,13 +41,13 @@ export class V1RequestCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: HandlerInboundMessage ) { - this.agentConfig.logger.info( - `Automatically sending credential with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + this.logger.info( + `Automatically sending credential with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` ) - const offerMessage = await this.credentialService.findOfferMessage(credentialRecord.id) + const offerMessage = await this.credentialService.findOfferMessage(messageContext.agentContext, credentialRecord.id) - const { message } = await this.credentialService.acceptRequest({ + const { message } = await this.credentialService.acceptRequest(messageContext.agentContext, { credentialRecord, }) @@ -60,7 +60,7 @@ export class V1RequestCredentialHandler implements Handler { // Set ~service, update message in record (for later use) message.setService(ourService) - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -73,6 +73,6 @@ export class V1RequestCredentialHandler implements Handler { }) } - this.agentConfig.logger.error(`Could not automatically create credential request`) + this.logger.error(`Could not automatically create credential request`) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts index b63635ffe0..0ee7ea021c 100644 --- a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts +++ b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../../../../agent' import type { Attachment } from '../../../../decorators/attachment/Attachment' import type { DidCommMessageRepository } from '../../../../storage' import type { CredentialFormat, CredentialFormatPayload, CredentialFormatService } from '../../formats' @@ -28,24 +29,27 @@ export class CredentialFormatCoordinator { * @returns The created {@link V2ProposeCredentialMessage} * */ - public async createProposal({ - credentialFormats, - formatServices, - credentialRecord, - comment, - }: { - formatServices: CredentialFormatService[] - credentialFormats: CredentialFormatPayload - credentialRecord: CredentialExchangeRecord - comment?: string - }): Promise { + public async createProposal( + agentContext: AgentContext, + { + credentialFormats, + formatServices, + credentialRecord, + comment, + }: { + formatServices: CredentialFormatService[] + credentialFormats: CredentialFormatPayload + credentialRecord: CredentialExchangeRecord + comment?: string + } + ): Promise { // create message. there are two arrays in each message, one for formats the other for attachments const formats: CredentialFormatSpec[] = [] const proposalAttachments: Attachment[] = [] let credentialPreview: V2CredentialPreview | undefined for (const formatService of formatServices) { - const { format, attachment, previewAttributes } = await formatService.createProposal({ + const { format, attachment, previewAttributes } = await formatService.createProposal(agentContext, { credentialFormats, credentialRecord, }) @@ -72,7 +76,7 @@ export class CredentialFormatCoordinator { message.setThread({ threadId: credentialRecord.threadId }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -81,48 +85,54 @@ export class CredentialFormatCoordinator { return message } - public async processProposal({ - credentialRecord, - message, - formatServices, - }: { - credentialRecord: CredentialExchangeRecord - message: V2ProposeCredentialMessage - formatServices: CredentialFormatService[] - }) { + public async processProposal( + agentContext: AgentContext, + { + credentialRecord, + message, + formatServices, + }: { + credentialRecord: CredentialExchangeRecord + message: V2ProposeCredentialMessage + formatServices: CredentialFormatService[] + } + ) { for (const formatService of formatServices) { const attachment = this.getAttachmentForService(formatService, message.formats, message.proposalAttachments) - await formatService.processProposal({ + await formatService.processProposal(agentContext, { attachment, credentialRecord, }) } - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, }) } - public async acceptProposal({ - credentialRecord, - credentialFormats, - formatServices, - comment, - }: { - credentialRecord: CredentialExchangeRecord - credentialFormats?: CredentialFormatPayload - formatServices: CredentialFormatService[] - comment?: string - }) { + public async acceptProposal( + agentContext: AgentContext, + { + credentialRecord, + credentialFormats, + formatServices, + comment, + }: { + credentialRecord: CredentialExchangeRecord + credentialFormats?: CredentialFormatPayload + formatServices: CredentialFormatService[] + comment?: string + } + ) { // create message. there are two arrays in each message, one for formats the other for attachments const formats: CredentialFormatSpec[] = [] const offerAttachments: Attachment[] = [] let credentialPreview: V2CredentialPreview | undefined - const proposalMessage = await this.didCommMessageRepository.getAgentMessage({ + const proposalMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V2ProposeCredentialMessage, }) @@ -139,7 +149,7 @@ export class CredentialFormatCoordinator { proposalMessage.proposalAttachments ) - const { attachment, format, previewAttributes } = await formatService.acceptProposal({ + const { attachment, format, previewAttributes } = await formatService.acceptProposal(agentContext, { credentialRecord, credentialFormats, proposalAttachment, @@ -174,7 +184,7 @@ export class CredentialFormatCoordinator { message.setThread({ threadId: credentialRecord.threadId }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, associatedRecordId: credentialRecord.id, role: DidCommMessageRole.Sender, @@ -190,24 +200,27 @@ export class CredentialFormatCoordinator { * @returns The created {@link V2OfferCredentialMessage} * */ - public async createOffer({ - credentialFormats, - formatServices, - credentialRecord, - comment, - }: { - formatServices: CredentialFormatService[] - credentialFormats: CredentialFormatPayload - credentialRecord: CredentialExchangeRecord - comment?: string - }): Promise { + public async createOffer( + agentContext: AgentContext, + { + credentialFormats, + formatServices, + credentialRecord, + comment, + }: { + formatServices: CredentialFormatService[] + credentialFormats: CredentialFormatPayload + credentialRecord: CredentialExchangeRecord + comment?: string + } + ): Promise { // create message. there are two arrays in each message, one for formats the other for attachments const formats: CredentialFormatSpec[] = [] const offerAttachments: Attachment[] = [] let credentialPreview: V2CredentialPreview | undefined for (const formatService of formatServices) { - const { format, attachment, previewAttributes } = await formatService.createOffer({ + const { format, attachment, previewAttributes } = await formatService.createOffer(agentContext, { credentialFormats, credentialRecord, }) @@ -241,7 +254,7 @@ export class CredentialFormatCoordinator { message.setThread({ threadId: credentialRecord.threadId }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -250,43 +263,49 @@ export class CredentialFormatCoordinator { return message } - public async processOffer({ - credentialRecord, - message, - formatServices, - }: { - credentialRecord: CredentialExchangeRecord - message: V2OfferCredentialMessage - formatServices: CredentialFormatService[] - }) { + public async processOffer( + agentContext: AgentContext, + { + credentialRecord, + message, + formatServices, + }: { + credentialRecord: CredentialExchangeRecord + message: V2OfferCredentialMessage + formatServices: CredentialFormatService[] + } + ) { for (const formatService of formatServices) { const attachment = this.getAttachmentForService(formatService, message.formats, message.offerAttachments) - await formatService.processOffer({ + await formatService.processOffer(agentContext, { attachment, credentialRecord, }) } - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, }) } - public async acceptOffer({ - credentialRecord, - credentialFormats, - formatServices, - comment, - }: { - credentialRecord: CredentialExchangeRecord - credentialFormats?: CredentialFormatPayload - formatServices: CredentialFormatService[] - comment?: string - }) { - const offerMessage = await this.didCommMessageRepository.getAgentMessage({ + public async acceptOffer( + agentContext: AgentContext, + { + credentialRecord, + credentialFormats, + formatServices, + comment, + }: { + credentialRecord: CredentialExchangeRecord + credentialFormats?: CredentialFormatPayload + formatServices: CredentialFormatService[] + comment?: string + } + ) { + const offerMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V2OfferCredentialMessage, }) @@ -302,7 +321,7 @@ export class CredentialFormatCoordinator { offerMessage.offerAttachments ) - const { attachment, format } = await formatService.acceptOffer({ + const { attachment, format } = await formatService.acceptOffer(agentContext, { offerAttachment, credentialRecord, credentialFormats, @@ -322,7 +341,7 @@ export class CredentialFormatCoordinator { message.setThread({ threadId: credentialRecord.threadId }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, associatedRecordId: credentialRecord.id, role: DidCommMessageRole.Sender, @@ -338,23 +357,26 @@ export class CredentialFormatCoordinator { * @returns The created {@link V2RequestCredentialMessage} * */ - public async createRequest({ - credentialFormats, - formatServices, - credentialRecord, - comment, - }: { - formatServices: CredentialFormatService[] - credentialFormats: CredentialFormatPayload - credentialRecord: CredentialExchangeRecord - comment?: string - }): Promise { + public async createRequest( + agentContext: AgentContext, + { + credentialFormats, + formatServices, + credentialRecord, + comment, + }: { + formatServices: CredentialFormatService[] + credentialFormats: CredentialFormatPayload + credentialRecord: CredentialExchangeRecord + comment?: string + } + ): Promise { // create message. there are two arrays in each message, one for formats the other for attachments const formats: CredentialFormatSpec[] = [] const requestAttachments: Attachment[] = [] for (const formatService of formatServices) { - const { format, attachment } = await formatService.createRequest({ + const { format, attachment } = await formatService.createRequest(agentContext, { credentialFormats, credentialRecord, }) @@ -371,7 +393,7 @@ export class CredentialFormatCoordinator { message.setThread({ threadId: credentialRecord.threadId }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -380,48 +402,54 @@ export class CredentialFormatCoordinator { return message } - public async processRequest({ - credentialRecord, - message, - formatServices, - }: { - credentialRecord: CredentialExchangeRecord - message: V2RequestCredentialMessage - formatServices: CredentialFormatService[] - }) { + public async processRequest( + agentContext: AgentContext, + { + credentialRecord, + message, + formatServices, + }: { + credentialRecord: CredentialExchangeRecord + message: V2RequestCredentialMessage + formatServices: CredentialFormatService[] + } + ) { for (const formatService of formatServices) { const attachment = this.getAttachmentForService(formatService, message.formats, message.requestAttachments) - await formatService.processRequest({ + await formatService.processRequest(agentContext, { attachment, credentialRecord, }) } - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, }) } - public async acceptRequest({ - credentialRecord, - credentialFormats, - formatServices, - comment, - }: { - credentialRecord: CredentialExchangeRecord - credentialFormats?: CredentialFormatPayload - formatServices: CredentialFormatService[] - comment?: string - }) { - const requestMessage = await this.didCommMessageRepository.getAgentMessage({ + public async acceptRequest( + agentContext: AgentContext, + { + credentialRecord, + credentialFormats, + formatServices, + comment, + }: { + credentialRecord: CredentialExchangeRecord + credentialFormats?: CredentialFormatPayload + formatServices: CredentialFormatService[] + comment?: string + } + ) { + const requestMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V2RequestCredentialMessage, }) - const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + const offerMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V2OfferCredentialMessage, }) @@ -441,7 +469,7 @@ export class CredentialFormatCoordinator { ? this.getAttachmentForService(formatService, offerMessage.formats, offerMessage.offerAttachments) : undefined - const { attachment, format } = await formatService.acceptRequest({ + const { attachment, format } = await formatService.acceptRequest(agentContext, { requestAttachment, offerAttachment, credentialRecord, @@ -461,7 +489,7 @@ export class CredentialFormatCoordinator { message.setThread({ threadId: credentialRecord.threadId }) message.setPleaseAck() - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, associatedRecordId: credentialRecord.id, role: DidCommMessageRole.Sender, @@ -470,25 +498,28 @@ export class CredentialFormatCoordinator { return message } - public async processCredential({ - credentialRecord, - message, - formatServices, - }: { - credentialRecord: CredentialExchangeRecord - message: V2IssueCredentialMessage - formatServices: CredentialFormatService[] - }) { + public async processCredential( + agentContext: AgentContext, + { + credentialRecord, + message, + formatServices, + }: { + credentialRecord: CredentialExchangeRecord + message: V2IssueCredentialMessage + formatServices: CredentialFormatService[] + } + ) { for (const formatService of formatServices) { const attachment = this.getAttachmentForService(formatService, message.formats, message.credentialAttachments) - await formatService.processCredential({ + await formatService.processCredential(agentContext, { attachment, credentialRecord, }) } - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts index 0cea805e08..75023e3ddc 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts @@ -1,21 +1,22 @@ +import type { AgentContext } from '../../../../agent' import type { AgentMessage } from '../../../../agent/AgentMessage' import type { HandlerInboundMessage } from '../../../../agent/Handler' import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' import type { ProblemReportMessage } from '../../../problem-reports' import type { - CreateProposalOptions, - CredentialProtocolMsgReturnType, + AcceptCredentialOptions, + AcceptOfferOptions, AcceptProposalOptions, - NegotiateProposalOptions, + AcceptRequestOptions, CreateOfferOptions, - AcceptOfferOptions, - NegotiateOfferOptions, + CreateProposalOptions, CreateRequestOptions, - AcceptRequestOptions, - AcceptCredentialOptions, - GetFormatDataReturn, + CredentialProtocolMsgReturnType, FormatDataMessagePayload, CreateProblemReportOptions, + GetFormatDataReturn, + NegotiateOfferOptions, + NegotiateProposalOptions, } from '../../CredentialServiceOptions' import type { CredentialFormat, @@ -25,11 +26,12 @@ import type { } from '../../formats' import type { CredentialFormatSpec } from '../../models' -import { AgentConfig } from '../../../../agent/AgentConfig' import { Dispatcher } from '../../../../agent/Dispatcher' import { EventEmitter } from '../../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../../constants' import { AriesFrameworkError } from '../../../../error' -import { injectable } from '../../../../plugins' +import { Logger } from '../../../../logger' +import { injectable, inject } from '../../../../plugins' import { DidCommMessageRepository } from '../../../../storage' import { uuid } from '../../../../utils/uuid' import { AckStatus } from '../../../common' @@ -37,8 +39,8 @@ import { ConnectionService } from '../../../connections' import { RoutingService } from '../../../routing/services/RoutingService' import { CredentialProblemReportReason } from '../../errors' import { IndyCredentialFormatService } from '../../formats/indy/IndyCredentialFormatService' -import { CredentialState, AutoAcceptCredential } from '../../models' -import { CredentialRepository, CredentialExchangeRecord } from '../../repository' +import { AutoAcceptCredential, CredentialState } from '../../models' +import { CredentialExchangeRecord, CredentialRepository } from '../../repository' import { CredentialService } from '../../services/CredentialService' import { composeAutoAccept } from '../../util/composeAutoAccept' import { arePreviewAttributesEqual } from '../../util/previewAttributes' @@ -65,23 +67,21 @@ import { export class V2CredentialService extends CredentialService { private connectionService: ConnectionService private credentialFormatCoordinator: CredentialFormatCoordinator - protected didCommMessageRepository: DidCommMessageRepository private routingService: RoutingService private formatServiceMap: { [key: string]: CredentialFormatService } public constructor( connectionService: ConnectionService, didCommMessageRepository: DidCommMessageRepository, - agentConfig: AgentConfig, routingService: RoutingService, dispatcher: Dispatcher, eventEmitter: EventEmitter, credentialRepository: CredentialRepository, - indyCredentialFormatService: IndyCredentialFormatService + indyCredentialFormatService: IndyCredentialFormatService, + @inject(InjectionSymbols.Logger) logger: Logger ) { - super(credentialRepository, didCommMessageRepository, eventEmitter, dispatcher, agentConfig) + super(credentialRepository, didCommMessageRepository, eventEmitter, dispatcher, logger) this.connectionService = connectionService - this.didCommMessageRepository = didCommMessageRepository this.routingService = routingService this.credentialFormatCoordinator = new CredentialFormatCoordinator(didCommMessageRepository) @@ -121,12 +121,10 @@ export class V2CredentialService): Promise> { + public async createProposal( + agentContext: AgentContext, + { connection, credentialFormats, comment, autoAcceptCredential }: CreateProposalOptions + ): Promise> { this.logger.debug('Get the Format Service and Create Proposal Message') const formatServices = this.getFormatServices(credentialFormats) @@ -142,7 +140,7 @@ export class V2CredentialService): Promise> { + public async acceptProposal( + agentContext: AgentContext, + { credentialRecord, credentialFormats, autoAcceptCredential, comment }: AcceptProposalOptions + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v2') credentialRecord.assertState(CredentialState.ProposalReceived) @@ -246,7 +249,7 @@ export class V2CredentialService): Promise> { + public async negotiateProposal( + agentContext: AgentContext, + { credentialRecord, credentialFormats, autoAcceptCredential, comment }: NegotiateProposalOptions + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v2') credentialRecord.assertState(CredentialState.ProposalReceived) @@ -304,7 +305,7 @@ export class V2CredentialService): Promise> { + public async createOffer( + agentContext: AgentContext, + { credentialFormats, autoAcceptCredential, comment, connection }: CreateOfferOptions + ): Promise> { const formatServices = this.getFormatServices(credentialFormats) if (formatServices.length === 0) { throw new AriesFrameworkError(`Unable to create offer. No supported formats`) @@ -345,7 +344,7 @@ export class V2CredentialService) { + public async acceptOffer( + agentContext: AgentContext, + { credentialRecord, autoAcceptCredential, comment, credentialFormats }: AcceptOfferOptions + ) { // Assert credentialRecord.assertProtocolVersion('v2') credentialRecord.assertState(CredentialState.OfferReceived) @@ -449,7 +453,7 @@ export class V2CredentialService): Promise> { + public async negotiateOffer( + agentContext: AgentContext, + { credentialRecord, credentialFormats, autoAcceptCredential, comment }: NegotiateOfferOptions + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v2') credentialRecord.assertState(CredentialState.OfferReceived) @@ -507,7 +509,7 @@ export class V2CredentialService): Promise> { + public async createRequest( + agentContext: AgentContext, + { credentialFormats, autoAcceptCredential, comment, connection }: CreateRequestOptions + ): Promise> { const formatServices = this.getFormatServices(credentialFormats) if (formatServices.length === 0) { throw new AriesFrameworkError(`Unable to create request. No supported formats`) @@ -544,7 +544,7 @@ export class V2CredentialService) { + public async acceptRequest( + agentContext: AgentContext, + { credentialRecord, autoAcceptCredential, comment, credentialFormats }: AcceptRequestOptions + ) { // Assert credentialRecord.assertProtocolVersion('v2') credentialRecord.assertState(CredentialState.RequestReceived) @@ -654,7 +656,7 @@ export class V2CredentialService> { + public async acceptCredential( + agentContext: AgentContext, + { credentialRecord }: AcceptCredentialOptions + ): Promise> { credentialRecord.assertProtocolVersion('v2') credentialRecord.assertState(CredentialState.CredentialReceived) @@ -755,7 +762,7 @@ export class V2CredentialService { + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + options: { + credentialRecord: CredentialExchangeRecord + proposalMessage: V2ProposeCredentialMessage + } + ): Promise { const { credentialRecord, proposalMessage } = options - const autoAccept = composeAutoAccept(credentialRecord.autoAcceptCredential, this.agentConfig.autoAcceptCredentials) + const autoAccept = composeAutoAccept( + credentialRecord.autoAcceptCredential, + agentContext.config.autoAcceptCredentials + ) // Handle always / never cases if (autoAccept === AutoAcceptCredential.Always) return true if (autoAccept === AutoAcceptCredential.Never) return false - const offerMessage = await this.findOfferMessage(credentialRecord.id) + const offerMessage = await this.findOfferMessage(agentContext, credentialRecord.id) if (!offerMessage) return false // NOTE: we take the formats from the offerMessage so we always check all services that we last sent @@ -850,7 +867,7 @@ export class V2CredentialService { + public async shouldAutoRespondToOffer( + agentContext: AgentContext, + options: { + credentialRecord: CredentialExchangeRecord + offerMessage: V2OfferCredentialMessage + } + ): Promise { const { credentialRecord, offerMessage } = options - const autoAccept = composeAutoAccept(credentialRecord.autoAcceptCredential, this.agentConfig.autoAcceptCredentials) + const autoAccept = composeAutoAccept( + credentialRecord.autoAcceptCredential, + agentContext.config.autoAcceptCredentials + ) // Handle always / never cases if (autoAccept === AutoAcceptCredential.Always) return true if (autoAccept === AutoAcceptCredential.Never) return false - const proposalMessage = await this.findProposalMessage(credentialRecord.id) + const proposalMessage = await this.findProposalMessage(agentContext, credentialRecord.id) if (!proposalMessage) return false // NOTE: we take the formats from the proposalMessage so we always check all services that we last sent @@ -908,7 +931,7 @@ export class V2CredentialService { + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + options: { + credentialRecord: CredentialExchangeRecord + requestMessage: V2RequestCredentialMessage + } + ): Promise { const { credentialRecord, requestMessage } = options - const autoAccept = composeAutoAccept(credentialRecord.autoAcceptCredential, this.agentConfig.autoAcceptCredentials) + const autoAccept = composeAutoAccept( + credentialRecord.autoAcceptCredential, + agentContext.config.autoAcceptCredentials + ) // Handle always / never cases if (autoAccept === AutoAcceptCredential.Always) return true if (autoAccept === AutoAcceptCredential.Never) return false - const proposalMessage = await this.findProposalMessage(credentialRecord.id) + const proposalMessage = await this.findProposalMessage(agentContext, credentialRecord.id) - const offerMessage = await this.findOfferMessage(credentialRecord.id) + const offerMessage = await this.findOfferMessage(agentContext, credentialRecord.id) if (!offerMessage) return false // NOTE: we take the formats from the offerMessage so we always check all services that we last sent @@ -976,7 +1005,7 @@ export class V2CredentialService { + public async shouldAutoRespondToCredential( + agentContext: AgentContext, + options: { + credentialRecord: CredentialExchangeRecord + credentialMessage: V2IssueCredentialMessage + } + ): Promise { const { credentialRecord, credentialMessage } = options - const autoAccept = composeAutoAccept(credentialRecord.autoAcceptCredential, this.agentConfig.autoAcceptCredentials) + const autoAccept = composeAutoAccept( + credentialRecord.autoAcceptCredential, + agentContext.config.autoAcceptCredentials + ) // Handle always / never cases if (autoAccept === AutoAcceptCredential.Always) return true if (autoAccept === AutoAcceptCredential.Never) return false - const proposalMessage = await this.findProposalMessage(credentialRecord.id) - const offerMessage = await this.findOfferMessage(credentialRecord.id) + const proposalMessage = await this.findProposalMessage(agentContext, credentialRecord.id) + const offerMessage = await this.findOfferMessage(agentContext, credentialRecord.id) - const requestMessage = await this.findRequestMessage(credentialRecord.id) + const requestMessage = await this.findRequestMessage(agentContext, credentialRecord.id) if (!requestMessage) return false // NOTE: we take the formats from the requestMessage so we always check all services that we last sent @@ -1041,7 +1076,7 @@ export class V2CredentialService { + public async getFormatData(agentContext: AgentContext, credentialExchangeId: string): Promise { // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. const [proposalMessage, offerMessage, requestMessage, credentialMessage] = await Promise.all([ - this.findProposalMessage(credentialExchangeId), - this.findOfferMessage(credentialExchangeId), - this.findRequestMessage(credentialExchangeId), - this.findCredentialMessage(credentialExchangeId), + this.findProposalMessage(agentContext, credentialExchangeId), + this.findOfferMessage(agentContext, credentialExchangeId), + this.findRequestMessage(agentContext, credentialExchangeId), + this.findCredentialMessage(agentContext, credentialExchangeId), ]) // Create object with the keys and the message formats/attachments. We can then loop over this in a generic @@ -1134,17 +1169,15 @@ export class V2CredentialService) => { +const getAgentMessageMock = async (agentContext: AgentContext, options: GetAgentMessageOptions) => { if (options.messageClass === V2ProposeCredentialMessage) { return credentialProposalMessage } @@ -231,12 +236,11 @@ const mockCredentialRecord = ({ describe('CredentialService', () => { let eventEmitter: EventEmitter - let agentConfig: AgentConfig + let credentialService: V2CredentialService beforeEach(async () => { - agentConfig = getAgentConfig('V2CredentialServiceCredTest') - eventEmitter = new EventEmitter(agentConfig) + eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) // mock function implementations mockFunction(connectionService.getById).mockResolvedValue(connection) @@ -251,12 +255,12 @@ describe('CredentialService', () => { credentialService = new V2CredentialService( connectionService, didCommMessageRepository, - agentConfig, routingService, dispatcher, eventEmitter, credentialRepository, - indyCredentialFormatService + indyCredentialFormatService, + agentConfig.logger ) }) @@ -279,7 +283,7 @@ describe('CredentialService', () => { }) // when - await credentialService.acceptOffer({ + await credentialService.acceptOffer(agentContext, { credentialRecord, credentialFormats: { indy: { @@ -292,6 +296,7 @@ describe('CredentialService', () => { // then expect(credentialRepository.update).toHaveBeenNthCalledWith( 1, + agentContext, expect.objectContaining({ state: CredentialState.RequestSent, }) @@ -315,7 +320,10 @@ describe('CredentialService', () => { }) // when - const { message: credentialRequest } = await credentialService.acceptOffer({ credentialRecord, comment }) + const { message: credentialRequest } = await credentialService.acceptOffer(agentContext, { + credentialRecord, + comment, + }) // then expect(credentialRequest.toJSON()).toMatchObject({ @@ -336,7 +344,7 @@ describe('CredentialService', () => { await Promise.all( invalidCredentialStates.map(async (state) => { await expect( - credentialService.acceptOffer({ credentialRecord: mockCredentialRecord({ state }) }) + credentialService.acceptOffer(agentContext, { credentialRecord: mockCredentialRecord({ state }) }) ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) }) ) @@ -350,6 +358,7 @@ describe('CredentialService', () => { const credentialRecord = mockCredentialRecord({ state: CredentialState.OfferSent }) const messageContext = new InboundMessageContext(credentialRequestMessage, { connection, + agentContext, }) // given @@ -359,7 +368,7 @@ describe('CredentialService', () => { const returnedCredentialRecord = await credentialService.processRequest(messageContext) // then - expect(credentialRepository.findSingleByQuery).toHaveBeenNthCalledWith(1, { + expect(credentialRepository.findSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { threadId: 'somethreadid', connectionId: connection.id, }) @@ -373,6 +382,7 @@ describe('CredentialService', () => { const credentialRecord = mockCredentialRecord({ state: CredentialState.OfferSent }) const messageContext = new InboundMessageContext(credentialRequestMessage, { connection, + agentContext, }) const eventListenerMock = jest.fn() @@ -383,10 +393,15 @@ describe('CredentialService', () => { const returnedCredentialRecord = await credentialService.processRequest(messageContext) // then - expect(credentialRepository.findSingleByQuery).toHaveBeenNthCalledWith(1, { - threadId: 'somethreadid', - connectionId: connection.id, - }) + expect(credentialRepository.findSingleByQuery).toHaveBeenNthCalledWith( + 1, + agentContext, + + { + threadId: 'somethreadid', + connectionId: connection.id, + } + ) expect(eventListenerMock).toHaveBeenCalled() expect(returnedCredentialRecord.state).toEqual(CredentialState.RequestReceived) }) @@ -398,6 +413,7 @@ describe('CredentialService', () => { const messageContext = new InboundMessageContext(credentialRequestMessage, { connection, + agentContext, }) await Promise.all( @@ -426,7 +442,7 @@ describe('CredentialService', () => { connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - await credentialService.acceptRequest({ + await credentialService.acceptRequest(agentContext, { credentialRecord, comment: 'credential response comment', }) @@ -434,6 +450,7 @@ describe('CredentialService', () => { // then expect(credentialRepository.update).toHaveBeenNthCalledWith( 1, + agentContext, expect.objectContaining({ state: CredentialState.CredentialIssued, }) @@ -459,7 +476,7 @@ describe('CredentialService', () => { eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // when - await credentialService.acceptRequest({ + await credentialService.acceptRequest(agentContext, { credentialRecord, comment: 'credential response comment', }) @@ -493,7 +510,7 @@ describe('CredentialService', () => { const comment = 'credential response comment' // when - const { message: credentialResponse } = await credentialService.acceptRequest({ + const { message: credentialResponse } = await credentialService.acceptRequest(agentContext, { comment: 'credential response comment', credentialRecord, }) @@ -523,6 +540,7 @@ describe('CredentialService', () => { const messageContext = new InboundMessageContext(credentialIssueMessage, { connection, + agentContext, }) // given @@ -544,11 +562,12 @@ describe('CredentialService', () => { }) // when - await credentialService.acceptCredential({ credentialRecord }) + await credentialService.acceptCredential(agentContext, { credentialRecord }) // then expect(credentialRepository.update).toHaveBeenNthCalledWith( 1, + agentContext, expect.objectContaining({ state: CredentialState.Done, }) @@ -566,7 +585,7 @@ describe('CredentialService', () => { eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // when - await credentialService.acceptCredential({ credentialRecord }) + await credentialService.acceptCredential(agentContext, { credentialRecord }) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -591,7 +610,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) // when - const { message: ackMessage } = await credentialService.acceptCredential({ credentialRecord }) + const { message: ackMessage } = await credentialService.acceptCredential(agentContext, { credentialRecord }) // then expect(ackMessage.toJSON()).toMatchObject({ @@ -609,7 +628,7 @@ describe('CredentialService', () => { await Promise.all( invalidCredentialStates.map(async (state) => { await expect( - credentialService.acceptCredential({ + credentialService.acceptCredential(agentContext, { credentialRecord: mockCredentialRecord({ state, threadId: 'somethreadid', @@ -627,9 +646,7 @@ describe('CredentialService', () => { status: AckStatus.OK, threadId: 'somethreadid', }) - const messageContext = new InboundMessageContext(credentialRequest, { - connection, - }) + const messageContext = new InboundMessageContext(credentialRequest, { agentContext, connection }) test(`updates state to ${CredentialState.Done} and returns credential record`, async () => { const credentialRecord = mockCredentialRecord({ @@ -642,7 +659,7 @@ describe('CredentialService', () => { // when const returnedCredentialRecord = await credentialService.processAck(messageContext) - expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, { + expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { threadId: 'somethreadid', connectionId: connection.id, }) @@ -663,7 +680,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) // when - const credentialProblemReportMessage = credentialService.createProblemReport({ message }) + const credentialProblemReportMessage = credentialService.createProblemReport(agentContext, { message }) credentialProblemReportMessage.setThread({ threadId: 'somethreadid' }) // then @@ -691,6 +708,7 @@ describe('CredentialService', () => { credentialProblemReportMessage.setThread({ threadId: 'somethreadid' }) const messageContext = new InboundMessageContext(credentialProblemReportMessage, { connection, + agentContext, }) test(`updates problem report error message and returns credential record`, async () => { @@ -706,7 +724,7 @@ describe('CredentialService', () => { // then - expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, { + expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { threadId: 'somethreadid', connectionId: connection.id, }) @@ -719,8 +737,8 @@ describe('CredentialService', () => { it('getById should return value from credentialRepository.getById', async () => { const expected = mockCredentialRecord() mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.getById(expected.id) - expect(credentialRepository.getById).toBeCalledWith(expected.id) + const result = await credentialService.getById(agentContext, expected.id) + expect(credentialRepository.getById).toBeCalledWith(agentContext, expected.id) expect(result).toBe(expected) }) @@ -728,8 +746,8 @@ describe('CredentialService', () => { it('getById should return value from credentialRepository.getSingleByQuery', async () => { const expected = mockCredentialRecord() mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.getByThreadAndConnectionId('threadId', 'connectionId') - expect(credentialRepository.getSingleByQuery).toBeCalledWith({ + const result = await credentialService.getByThreadAndConnectionId(agentContext, 'threadId', 'connectionId') + expect(credentialRepository.getSingleByQuery).toBeCalledWith(agentContext, { threadId: 'threadId', connectionId: 'connectionId', }) @@ -740,8 +758,8 @@ describe('CredentialService', () => { it('findById should return value from credentialRepository.findById', async () => { const expected = mockCredentialRecord() mockFunction(credentialRepository.findById).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.findById(expected.id) - expect(credentialRepository.findById).toBeCalledWith(expected.id) + const result = await credentialService.findById(agentContext, expected.id) + expect(credentialRepository.findById).toBeCalledWith(agentContext, expected.id) expect(result).toBe(expected) }) @@ -750,8 +768,8 @@ describe('CredentialService', () => { const expected = [mockCredentialRecord(), mockCredentialRecord()] mockFunction(credentialRepository.getAll).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.getAll() - expect(credentialRepository.getAll).toBeCalledWith() + const result = await credentialService.getAll(agentContext) + expect(credentialRepository.getAll).toBeCalledWith(agentContext) expect(result).toEqual(expect.arrayContaining(expected)) }) @@ -763,8 +781,8 @@ describe('CredentialService', () => { mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credentialRecord)) const repositoryDeleteSpy = jest.spyOn(credentialRepository, 'delete') - await credentialService.delete(credentialRecord) - expect(repositoryDeleteSpy).toHaveBeenNthCalledWith(1, credentialRecord) + await credentialService.delete(agentContext, credentialRecord) + expect(repositoryDeleteSpy).toHaveBeenNthCalledWith(1, agentContext, credentialRecord) }) it('should call deleteCredentialById in indyCredentialFormatService if deleteAssociatedCredential is true', async () => { @@ -773,12 +791,16 @@ describe('CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(credentialRecord, { + await credentialService.delete(agentContext, credentialRecord, { deleteAssociatedCredentials: true, deleteAssociatedDidCommMessages: false, }) - expect(deleteCredentialMock).toHaveBeenNthCalledWith(1, credentialRecord.credentials[0].credentialRecordId) + expect(deleteCredentialMock).toHaveBeenNthCalledWith( + 1, + agentContext, + credentialRecord.credentials[0].credentialRecordId + ) }) it('should not call deleteCredentialById in indyCredentialFormatService if deleteAssociatedCredential is false', async () => { @@ -787,7 +809,7 @@ describe('CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(credentialRecord, { + await credentialService.delete(agentContext, credentialRecord, { deleteAssociatedCredentials: false, deleteAssociatedDidCommMessages: false, }) @@ -801,9 +823,13 @@ describe('CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(credentialRecord) + await credentialService.delete(agentContext, credentialRecord) - expect(deleteCredentialMock).toHaveBeenNthCalledWith(1, credentialRecord.credentials[0].credentialRecordId) + expect(deleteCredentialMock).toHaveBeenNthCalledWith( + 1, + agentContext, + credentialRecord.credentials[0].credentialRecordId + ) }) it('deleteAssociatedDidCommMessages should default to true', async () => { const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) @@ -811,9 +837,13 @@ describe('CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(credentialRecord) + await credentialService.delete(agentContext, credentialRecord) - expect(deleteCredentialMock).toHaveBeenNthCalledWith(1, credentialRecord.credentials[0].credentialRecordId) + expect(deleteCredentialMock).toHaveBeenNthCalledWith( + 1, + agentContext, + credentialRecord.credentials[0].credentialRecordId + ) expect(didCommMessageRepository.delete).toHaveBeenCalledTimes(3) }) }) @@ -825,12 +855,13 @@ describe('CredentialService', () => { }) // when - await credentialService.declineOffer(credentialRecord) + await credentialService.declineOffer(agentContext, credentialRecord) // then expect(credentialRepository.update).toHaveBeenNthCalledWith( 1, + agentContext, expect.objectContaining({ state: CredentialState.Declined, }) @@ -849,7 +880,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.getSingleByQuery).mockResolvedValue(credentialRecord) // when - await credentialService.declineOffer(credentialRecord) + await credentialService.declineOffer(agentContext, credentialRecord) // then expect(eventListenerMock).toHaveBeenCalledTimes(1) @@ -870,9 +901,9 @@ describe('CredentialService', () => { test(`throws an error when state transition is invalid`, async () => { await Promise.all( invalidCredentialStates.map(async (state) => { - await expect(credentialService.declineOffer(mockCredentialRecord({ state }))).rejects.toThrowError( - `Credential record is in invalid state ${state}. Valid states are: ${validState}.` - ) + await expect( + credentialService.declineOffer(agentContext, mockCredentialRecord({ state })) + ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) }) ) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts index 0a2aaf7aef..89670711fe 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts @@ -1,9 +1,10 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' import type { CreateOfferOptions } from '../../../CredentialServiceOptions' import type { IndyCredentialFormat } from '../../../formats/indy/IndyCredentialFormat' -import { getAgentConfig, getMockConnection, mockFunction } from '../../../../../../tests/helpers' +import { Subject } from 'rxjs' + +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' import { Dispatcher } from '../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../agent/EventEmitter' import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' @@ -55,6 +56,9 @@ const connectionService = new ConnectionServiceMock() // @ts-ignore indyCredentialFormatService.formatKey = 'indy' +const agentConfig = getAgentConfig('V2CredentialServiceOfferTest') +const agentContext = getAgentContext() + const connection = getMockConnection({ id: '123', state: DidExchangeState.Completed, @@ -80,13 +84,11 @@ const offerAttachment = new Attachment({ describe('V2CredentialServiceOffer', () => { let eventEmitter: EventEmitter - let agentConfig: AgentConfig let credentialService: V2CredentialService beforeEach(async () => { // real objects - agentConfig = getAgentConfig('V2CredentialServiceOfferTest') - eventEmitter = new EventEmitter(agentConfig) + eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) // mock function implementations mockFunction(connectionService.getById).mockResolvedValue(connection) @@ -96,12 +98,12 @@ describe('V2CredentialServiceOffer', () => { credentialService = new V2CredentialService( connectionService, didCommMessageRepository, - agentConfig, routingService, dispatcher, eventEmitter, credentialRepository, - indyCredentialFormatService + indyCredentialFormatService, + agentConfig.logger ) }) @@ -129,11 +131,12 @@ describe('V2CredentialServiceOffer', () => { }) // when - await credentialService.createOffer(offerOptions) + await credentialService.createOffer(agentContext, offerOptions) // then expect(credentialRepository.save).toHaveBeenNthCalledWith( 1, + agentContext, expect.objectContaining({ type: CredentialExchangeRecord.type, id: expect.any(String), @@ -154,7 +157,7 @@ describe('V2CredentialServiceOffer', () => { const eventListenerMock = jest.fn() eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) - await credentialService.createOffer(offerOptions) + await credentialService.createOffer(agentContext, offerOptions) expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', @@ -175,7 +178,7 @@ describe('V2CredentialServiceOffer', () => { previewAttributes: credentialPreview.attributes, }) - const { message: credentialOffer } = await credentialService.createOffer(offerOptions) + const { message: credentialOffer } = await credentialService.createOffer(agentContext, offerOptions) expect(credentialOffer.toJSON()).toMatchObject({ '@id': expect.any(String), @@ -210,9 +213,7 @@ describe('V2CredentialServiceOffer', () => { offerAttachments: [offerAttachment], }) - const messageContext = new InboundMessageContext(credentialOfferMessage, { - connection, - }) + const messageContext = new InboundMessageContext(credentialOfferMessage, { agentContext, connection }) test(`creates and return credential record in ${CredentialState.OfferReceived} state with offer, thread ID`, async () => { mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) @@ -223,6 +224,7 @@ describe('V2CredentialServiceOffer', () => { // then expect(credentialRepository.save).toHaveBeenNthCalledWith( 1, + agentContext, expect.objectContaining({ type: CredentialExchangeRecord.type, id: expect.any(String), diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts index e8c7905e73..afc83def5a 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts @@ -124,7 +124,7 @@ describe('v2 credentials', () => { }) const didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - const offerMessage = await didCommMessageRepository.findAgentMessage({ + const offerMessage = await didCommMessageRepository.findAgentMessage(faberAgent.context, { associatedRecordId: faberCredentialRecord.id, messageClass: V2OfferCredentialMessage, }) @@ -236,7 +236,11 @@ describe('v2 credentials', () => { deleteAssociatedCredentials: true, deleteAssociatedDidCommMessages: true, }) - expect(deleteCredentialSpy).toHaveBeenNthCalledWith(1, holderCredential.credentials[0].credentialRecordId) + expect(deleteCredentialSpy).toHaveBeenNthCalledWith( + 1, + aliceAgent.context, + holderCredential.credentials[0].credentialRecordId + ) return expect(aliceAgent.credentials.getById(holderCredential.id)).rejects.toThrowError( `CredentialRecord: record with id ${holderCredential.id} not found.` diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts index 402d6c6047..9329fa298a 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts @@ -1,6 +1,6 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import type { Logger } from '../../../../../logger' import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V2CredentialService } from '../V2CredentialService' @@ -11,24 +11,25 @@ import { V2RequestCredentialMessage } from '../messages/V2RequestCredentialMessa export class V2IssueCredentialHandler implements Handler { private credentialService: V2CredentialService - private agentConfig: AgentConfig private didCommMessageRepository: DidCommMessageRepository + private logger: Logger public supportedMessages = [V2IssueCredentialMessage] public constructor( credentialService: V2CredentialService, - agentConfig: AgentConfig, - didCommMessageRepository: DidCommMessageRepository + didCommMessageRepository: DidCommMessageRepository, + logger: Logger ) { this.credentialService = credentialService - this.agentConfig = agentConfig this.didCommMessageRepository = didCommMessageRepository + this.logger = logger } + public async handle(messageContext: InboundMessageContext) { const credentialRecord = await this.credentialService.processCredential(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToCredential({ + const shouldAutoRespond = await this.credentialService.shouldAutoRespondToCredential(messageContext.agentContext, { credentialRecord, credentialMessage: messageContext.message, }) @@ -42,16 +43,16 @@ export class V2IssueCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: HandlerInboundMessage ) { - this.agentConfig.logger.info( - `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + this.logger.info( + `Automatically sending acknowledgement with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` ) - const requestMessage = await this.didCommMessageRepository.findAgentMessage({ + const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V2RequestCredentialMessage, }) - const { message } = await this.credentialService.acceptCredential({ + const { message } = await this.credentialService.acceptCredential(messageContext.agentContext, { credentialRecord, }) @@ -68,6 +69,6 @@ export class V2IssueCredentialHandler implements Handler { }) } - this.agentConfig.logger.error(`Could not automatically create credential ack`) + this.logger.error(`Could not automatically create credential ack`) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts index d938f2a19b..7d3c3b6419 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts @@ -1,6 +1,6 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import type { Logger } from '../../../../../logger' import type { DidCommMessageRepository } from '../../../../../storage' import type { RoutingService } from '../../../../routing/services/RoutingService' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' @@ -13,27 +13,28 @@ import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' export class V2OfferCredentialHandler implements Handler { private credentialService: V2CredentialService - private agentConfig: AgentConfig private routingService: RoutingService + private logger: Logger + public supportedMessages = [V2OfferCredentialMessage] private didCommMessageRepository: DidCommMessageRepository public constructor( credentialService: V2CredentialService, - agentConfig: AgentConfig, routingService: RoutingService, - didCommMessageRepository: DidCommMessageRepository + didCommMessageRepository: DidCommMessageRepository, + logger: Logger ) { this.credentialService = credentialService - this.agentConfig = agentConfig this.routingService = routingService this.didCommMessageRepository = didCommMessageRepository + this.logger = logger } public async handle(messageContext: InboundMessageContext) { const credentialRecord = await this.credentialService.processOffer(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToOffer({ + const shouldAutoRespond = await this.credentialService.shouldAutoRespondToOffer(messageContext.agentContext, { credentialRecord, offerMessage: messageContext.message, }) @@ -48,17 +49,17 @@ export class V2OfferCredentialHandler implements Handler { messageContext: HandlerInboundMessage, offerMessage?: V2OfferCredentialMessage ) { - this.agentConfig.logger.info( - `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + this.logger.info( + `Automatically sending request with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` ) if (messageContext.connection) { - const { message } = await this.credentialService.acceptOffer({ + const { message } = await this.credentialService.acceptOffer(messageContext.agentContext, { credentialRecord, }) return createOutboundMessage(messageContext.connection, message) } else if (offerMessage?.service) { - const routing = await this.routingService.getRouting() + const routing = await this.routingService.getRouting(messageContext.agentContext) const ourService = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], @@ -66,14 +67,14 @@ export class V2OfferCredentialHandler implements Handler { }) const recipientService = offerMessage.service - const { message } = await this.credentialService.acceptOffer({ + const { message } = await this.credentialService.acceptOffer(messageContext.agentContext, { credentialRecord, }) // Set and save ~service decorator to record (to remember our verkey) message.service = ourService - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -86,6 +87,6 @@ export class V2OfferCredentialHandler implements Handler { }) } - this.agentConfig.logger.error(`Could not automatically create credential request`) + this.logger.error(`Could not automatically create credential request`) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts index 27a181ed67..9c63943302 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts @@ -1,6 +1,6 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import type { Logger } from '../../../../../logger' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V2CredentialService } from '../V2CredentialService' @@ -9,19 +9,19 @@ import { V2ProposeCredentialMessage } from '../messages/V2ProposeCredentialMessa export class V2ProposeCredentialHandler implements Handler { private credentialService: V2CredentialService - private agentConfig: AgentConfig + private logger: Logger public supportedMessages = [V2ProposeCredentialMessage] - public constructor(credentialService: V2CredentialService, agentConfig: AgentConfig) { + public constructor(credentialService: V2CredentialService, logger: Logger) { this.credentialService = credentialService - this.agentConfig = agentConfig + this.logger = logger } public async handle(messageContext: InboundMessageContext) { const credentialRecord = await this.credentialService.processProposal(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToProposal({ + const shouldAutoRespond = await this.credentialService.shouldAutoRespondToProposal(messageContext.agentContext, { credentialRecord, proposalMessage: messageContext.message, }) @@ -35,16 +35,16 @@ export class V2ProposeCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: HandlerInboundMessage ) { - this.agentConfig.logger.info( - `Automatically sending offer with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + this.logger.info( + `Automatically sending offer with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` ) if (!messageContext.connection) { - this.agentConfig.logger.error('No connection on the messageContext, aborting auto accept') + this.logger.error('No connection on the messageContext, aborting auto accept') return } - const { message } = await this.credentialService.acceptProposal({ credentialRecord }) + const { message } = await this.credentialService.acceptProposal(messageContext.agentContext, { credentialRecord }) return createOutboundMessage(messageContext.connection, message) } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts index 7b137d8955..6f5145dedd 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts @@ -1,6 +1,6 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { Handler } from '../../../../../agent/Handler' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import type { Logger } from '../../../../../logger/Logger' import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository' import type { V2CredentialService } from '../V2CredentialService' @@ -12,24 +12,25 @@ import { V2RequestCredentialMessage } from '../messages/V2RequestCredentialMessa export class V2RequestCredentialHandler implements Handler { private credentialService: V2CredentialService - private agentConfig: AgentConfig private didCommMessageRepository: DidCommMessageRepository + private logger: Logger + public supportedMessages = [V2RequestCredentialMessage] public constructor( credentialService: V2CredentialService, - agentConfig: AgentConfig, - didCommMessageRepository: DidCommMessageRepository + didCommMessageRepository: DidCommMessageRepository, + logger: Logger ) { this.credentialService = credentialService - this.agentConfig = agentConfig this.didCommMessageRepository = didCommMessageRepository + this.logger = logger } public async handle(messageContext: InboundMessageContext) { const credentialRecord = await this.credentialService.processRequest(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToRequest({ + const shouldAutoRespond = await this.credentialService.shouldAutoRespondToRequest(messageContext.agentContext, { credentialRecord, requestMessage: messageContext.message, }) @@ -43,16 +44,16 @@ export class V2RequestCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: InboundMessageContext ) { - this.agentConfig.logger.info( - `Automatically sending credential with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + this.logger.info( + `Automatically sending credential with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` ) - const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + const offerMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V2OfferCredentialMessage, }) - const { message } = await this.credentialService.acceptRequest({ + const { message } = await this.credentialService.acceptRequest(messageContext.agentContext, { credentialRecord, }) @@ -64,7 +65,7 @@ export class V2RequestCredentialHandler implements Handler { // Set ~service, update message in record (for later use) message.setService(ourService) - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: message, associatedRecordId: credentialRecord.id, role: DidCommMessageRole.Sender, @@ -77,6 +78,6 @@ export class V2RequestCredentialHandler implements Handler { }) } - this.agentConfig.logger.error(`Could not automatically create credential request`) + this.logger.error(`Could not automatically create credential request`) } } diff --git a/packages/core/src/modules/credentials/services/CredentialService.ts b/packages/core/src/modules/credentials/services/CredentialService.ts index 2e305864ef..7642e4c4c5 100644 --- a/packages/core/src/modules/credentials/services/CredentialService.ts +++ b/packages/core/src/modules/credentials/services/CredentialService.ts @@ -1,4 +1,4 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' +import type { AgentContext } from '../../../agent' import type { AgentMessage } from '../../../agent/AgentMessage' import type { Dispatcher } from '../../../agent/Dispatcher' import type { EventEmitter } from '../../../agent/EventEmitter' @@ -35,7 +35,6 @@ export abstract class CredentialService // methods for proposal - abstract createProposal(options: CreateProposalOptions): Promise> + abstract createProposal( + agentContext: AgentContext, + options: CreateProposalOptions + ): Promise> abstract processProposal(messageContext: InboundMessageContext): Promise - abstract acceptProposal(options: AcceptProposalOptions): Promise> + abstract acceptProposal( + agentContext: AgentContext, + options: AcceptProposalOptions + ): Promise> abstract negotiateProposal( + agentContext: AgentContext, options: NegotiateProposalOptions ): Promise> // methods for offer - abstract createOffer(options: CreateOfferOptions): Promise> + abstract createOffer( + agentContext: AgentContext, + options: CreateOfferOptions + ): Promise> abstract processOffer(messageContext: InboundMessageContext): Promise - abstract acceptOffer(options: AcceptOfferOptions): Promise> - abstract negotiateOffer(options: NegotiateOfferOptions): Promise> + abstract acceptOffer( + agentContext: AgentContext, + options: AcceptOfferOptions + ): Promise> + abstract negotiateOffer( + agentContext: AgentContext, + options: NegotiateOfferOptions + ): Promise> // methods for request - abstract createRequest(options: CreateRequestOptions): Promise> + abstract createRequest( + agentContext: AgentContext, + options: CreateRequestOptions + ): Promise> abstract processRequest(messageContext: InboundMessageContext): Promise - abstract acceptRequest(options: AcceptRequestOptions): Promise> + abstract acceptRequest( + agentContext: AgentContext, + options: AcceptRequestOptions + ): Promise> // methods for issue abstract processCredential(messageContext: InboundMessageContext): Promise - abstract acceptCredential(options: AcceptCredentialOptions): Promise> + abstract acceptCredential( + agentContext: AgentContext, + options: AcceptCredentialOptions + ): Promise> // methods for ack abstract processAck(messageContext: InboundMessageContext): Promise // methods for problem-report - abstract createProblemReport(options: CreateProblemReportOptions): ProblemReportMessage + abstract createProblemReport(agentContext: AgentContext, options: CreateProblemReportOptions): ProblemReportMessage - abstract findProposalMessage(credentialExchangeId: string): Promise - abstract findOfferMessage(credentialExchangeId: string): Promise - abstract findRequestMessage(credentialExchangeId: string): Promise - abstract findCredentialMessage(credentialExchangeId: string): Promise - abstract getFormatData(credentialExchangeId: string): Promise> + abstract findProposalMessage(agentContext: AgentContext, credentialExchangeId: string): Promise + abstract findOfferMessage(agentContext: AgentContext, credentialExchangeId: string): Promise + abstract findRequestMessage(agentContext: AgentContext, credentialExchangeId: string): Promise + abstract findCredentialMessage(agentContext: AgentContext, credentialExchangeId: string): Promise + abstract getFormatData(agentContext: AgentContext, credentialExchangeId: string): Promise> /** * Decline a credential offer * @param credentialRecord The credential to be declined */ - public async declineOffer(credentialRecord: CredentialExchangeRecord): Promise { + public async declineOffer( + agentContext: AgentContext, + credentialRecord: CredentialExchangeRecord + ): Promise { credentialRecord.assertState(CredentialState.OfferReceived) - await this.updateState(credentialRecord, CredentialState.Declined) + await this.updateState(agentContext, credentialRecord, CredentialState.Declined) return credentialRecord } @@ -122,13 +148,14 @@ export abstract class CredentialService({ + this.eventEmitter.emit(agentContext, { type: CredentialEventTypes.CredentialStateChanged, payload: { credentialRecord: clonedCredential, @@ -172,8 +207,8 @@ export abstract class CredentialService { - return this.credentialRepository.getById(credentialRecordId) + public getById(agentContext: AgentContext, credentialRecordId: string): Promise { + return this.credentialRepository.getById(agentContext, credentialRecordId) } /** @@ -181,8 +216,8 @@ export abstract class CredentialService { - return this.credentialRepository.getAll() + public getAll(agentContext: AgentContext): Promise { + return this.credentialRepository.getAll(agentContext) } /** @@ -191,12 +226,16 @@ export abstract class CredentialService { - return this.credentialRepository.findById(connectionId) + public findById(agentContext: AgentContext, connectionId: string): Promise { + return this.credentialRepository.findById(agentContext, connectionId) } - public async delete(credentialRecord: CredentialExchangeRecord, options?: DeleteCredentialOptions): Promise { - await this.credentialRepository.delete(credentialRecord) + public async delete( + agentContext: AgentContext, + credentialRecord: CredentialExchangeRecord, + options?: DeleteCredentialOptions + ): Promise { + await this.credentialRepository.delete(agentContext, credentialRecord) const deleteAssociatedCredentials = options?.deleteAssociatedCredentials ?? true const deleteAssociatedDidCommMessages = options?.deleteAssociatedDidCommMessages ?? true @@ -204,16 +243,16 @@ export abstract class CredentialService { - return this.credentialRepository.getSingleByQuery({ + public getByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise { + return this.credentialRepository.getSingleByQuery(agentContext, { connectionId, threadId, }) @@ -242,16 +285,17 @@ export abstract class CredentialService { - return this.credentialRepository.findSingleByQuery({ + return this.credentialRepository.findSingleByQuery(agentContext, { connectionId, threadId, }) } - public async update(credentialRecord: CredentialExchangeRecord) { - return await this.credentialRepository.update(credentialRecord) + public async update(agentContext: AgentContext, credentialRecord: CredentialExchangeRecord) { + return await this.credentialRepository.update(agentContext, credentialRecord) } } diff --git a/packages/core/src/modules/credentials/services/index.ts b/packages/core/src/modules/credentials/services/index.ts index 05da1a90b5..3ef45ad8eb 100644 --- a/packages/core/src/modules/credentials/services/index.ts +++ b/packages/core/src/modules/credentials/services/index.ts @@ -1,2 +1 @@ export * from './CredentialService' -export * from '../protocol/revocation-notification/services/RevocationNotificationService' diff --git a/packages/core/src/modules/dids/DidsModule.ts b/packages/core/src/modules/dids/DidsModule.ts index 7fe57d25d6..d10ff463f1 100644 --- a/packages/core/src/modules/dids/DidsModule.ts +++ b/packages/core/src/modules/dids/DidsModule.ts @@ -2,6 +2,7 @@ import type { Key } from '../../crypto' import type { DependencyManager } from '../../plugins' import type { DidResolutionOptions } from './types' +import { AgentContext } from '../../agent' import { injectable, module } from '../../plugins' import { DidRepository } from './repository' @@ -12,26 +13,28 @@ import { DidResolverService } from './services/DidResolverService' export class DidsModule { private resolverService: DidResolverService private didRepository: DidRepository + private agentContext: AgentContext - public constructor(resolverService: DidResolverService, didRepository: DidRepository) { + public constructor(resolverService: DidResolverService, didRepository: DidRepository, agentContext: AgentContext) { this.resolverService = resolverService this.didRepository = didRepository + this.agentContext = agentContext } public resolve(didUrl: string, options?: DidResolutionOptions) { - return this.resolverService.resolve(didUrl, options) + return this.resolverService.resolve(this.agentContext, didUrl, options) } public resolveDidDocument(didUrl: string) { - return this.resolverService.resolveDidDocument(didUrl) + return this.resolverService.resolveDidDocument(this.agentContext, didUrl) } public findByRecipientKey(recipientKey: Key) { - return this.didRepository.findByRecipientKey(recipientKey) + return this.didRepository.findByRecipientKey(this.agentContext, recipientKey) } public findAllByRecipientKey(recipientKey: Key) { - return this.didRepository.findAllByRecipientKey(recipientKey) + return this.didRepository.findAllByRecipientKey(this.agentContext, recipientKey) } /** diff --git a/packages/core/src/modules/dids/__tests__/DidResolverService.test.ts b/packages/core/src/modules/dids/__tests__/DidResolverService.test.ts index 785c30d00c..7dff728532 100644 --- a/packages/core/src/modules/dids/__tests__/DidResolverService.test.ts +++ b/packages/core/src/modules/dids/__tests__/DidResolverService.test.ts @@ -1,7 +1,7 @@ import type { IndyLedgerService } from '../../ledger' import type { DidRepository } from '../repository' -import { getAgentConfig, mockProperty } from '../../../../tests/helpers' +import { getAgentConfig, getAgentContext, mockProperty } from '../../../../tests/helpers' import { JsonTransformer } from '../../../utils/JsonTransformer' import { DidDocument } from '../domain' import { parseDid } from '../domain/parse' @@ -13,11 +13,16 @@ import didKeyEd25519Fixture from './__fixtures__/didKeyEd25519.json' jest.mock('../methods/key/KeyDidResolver') const agentConfig = getAgentConfig('DidResolverService') +const agentContext = getAgentContext() describe('DidResolverService', () => { const indyLedgerServiceMock = jest.fn() as unknown as IndyLedgerService const didDocumentRepositoryMock = jest.fn() as unknown as DidRepository - const didResolverService = new DidResolverService(agentConfig, indyLedgerServiceMock, didDocumentRepositoryMock) + const didResolverService = new DidResolverService( + indyLedgerServiceMock, + didDocumentRepositoryMock, + agentConfig.logger + ) it('should correctly find and call the correct resolver for a specified did', async () => { const didKeyResolveSpy = jest.spyOn(KeyDidResolver.prototype, 'resolve') @@ -32,17 +37,19 @@ describe('DidResolverService', () => { } didKeyResolveSpy.mockResolvedValue(returnValue) - const result = await didResolverService.resolve('did:key:xxxx', { someKey: 'string' }) + const result = await didResolverService.resolve(agentContext, 'did:key:xxxx', { someKey: 'string' }) expect(result).toEqual(returnValue) expect(didKeyResolveSpy).toHaveBeenCalledTimes(1) - expect(didKeyResolveSpy).toHaveBeenCalledWith('did:key:xxxx', parseDid('did:key:xxxx'), { someKey: 'string' }) + expect(didKeyResolveSpy).toHaveBeenCalledWith(agentContext, 'did:key:xxxx', parseDid('did:key:xxxx'), { + someKey: 'string', + }) }) it("should return an error with 'invalidDid' if the did string couldn't be parsed", async () => { const did = 'did:__Asd:asdfa' - const result = await didResolverService.resolve(did) + const result = await didResolverService.resolve(agentContext, did) expect(result).toEqual({ didDocument: null, @@ -56,7 +63,7 @@ describe('DidResolverService', () => { it("should return an error with 'unsupportedDidMethod' if the did has no resolver", async () => { const did = 'did:example:asdfa' - const result = await didResolverService.resolve(did) + const result = await didResolverService.resolve(agentContext, did) expect(result).toEqual({ didDocument: null, diff --git a/packages/core/src/modules/dids/__tests__/peer-did.test.ts b/packages/core/src/modules/dids/__tests__/peer-did.test.ts index c5205f1e60..98a772fb07 100644 --- a/packages/core/src/modules/dids/__tests__/peer-did.test.ts +++ b/packages/core/src/modules/dids/__tests__/peer-did.test.ts @@ -1,6 +1,9 @@ +import type { AgentContext } from '../../../agent' import type { IndyLedgerService } from '../../ledger' -import { getAgentConfig } from '../../../../tests/helpers' +import { Subject } from 'rxjs' + +import { getAgentConfig, getAgentContext } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' import { Key, KeyType } from '../../../crypto' import { IndyStorageService } from '../../../storage/IndyStorageService' @@ -24,19 +27,21 @@ describe('peer dids', () => { let didRepository: DidRepository let didResolverService: DidResolverService let wallet: IndyWallet + let agentContext: AgentContext let eventEmitter: EventEmitter beforeEach(async () => { - wallet = new IndyWallet(config) + wallet = new IndyWallet(config.agentDependencies, config.logger) + agentContext = getAgentContext({ wallet }) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) - const storageService = new IndyStorageService(wallet, config) - eventEmitter = new EventEmitter(config) + const storageService = new IndyStorageService(config.agentDependencies) + eventEmitter = new EventEmitter(config.agentDependencies, new Subject()) didRepository = new DidRepository(storageService, eventEmitter) // Mocking IndyLedgerService as we're only interested in the did:peer resolver - didResolverService = new DidResolverService(config, {} as unknown as IndyLedgerService, didRepository) + didResolverService = new DidResolverService({} as unknown as IndyLedgerService, didRepository, config.logger) }) afterEach(async () => { @@ -124,7 +129,7 @@ describe('peer dids', () => { }, }) - await didRepository.save(didDocumentRecord) + await didRepository.save(agentContext, didDocumentRecord) }) test('receive a did and did document', async () => { @@ -161,13 +166,13 @@ describe('peer dids', () => { }, }) - await didRepository.save(didDocumentRecord) + await didRepository.save(agentContext, didDocumentRecord) // Then we save the did (not the did document) in the connection record // connectionRecord.theirDid = didPeer.did // Then when we want to send a message we can resolve the did document - const { didDocument: resolvedDidDocument } = await didResolverService.resolve(did) + const { didDocument: resolvedDidDocument } = await didResolverService.resolve(agentContext, did) expect(resolvedDidDocument).toBeInstanceOf(DidDocument) expect(resolvedDidDocument?.toJSON()).toMatchObject(didPeer1zQmY) }) diff --git a/packages/core/src/modules/dids/domain/DidResolver.ts b/packages/core/src/modules/dids/domain/DidResolver.ts index 6e0a98537f..050ea2cd97 100644 --- a/packages/core/src/modules/dids/domain/DidResolver.ts +++ b/packages/core/src/modules/dids/domain/DidResolver.ts @@ -1,6 +1,12 @@ +import type { AgentContext } from '../../../agent' import type { ParsedDid, DidResolutionResult, DidResolutionOptions } from '../types' export interface DidResolver { readonly supportedMethods: string[] - resolve(did: string, parsed: ParsedDid, didResolutionOptions: DidResolutionOptions): Promise + resolve( + agentContext: AgentContext, + did: string, + parsed: ParsedDid, + didResolutionOptions: DidResolutionOptions + ): Promise } diff --git a/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts b/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts index eb7d4ee5ae..41f4a0e221 100644 --- a/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts +++ b/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../../../../agent' import type { DidResolver } from '../../domain/DidResolver' import type { DidResolutionResult } from '../../types' @@ -6,7 +7,7 @@ import { DidKey } from './DidKey' export class KeyDidResolver implements DidResolver { public readonly supportedMethods = ['key'] - public async resolve(did: string): Promise { + public async resolve(agentContext: AgentContext, did: string): Promise { const didDocumentMetadata = {} try { diff --git a/packages/core/src/modules/dids/methods/key/__tests__/KeyDidResolver.test.ts b/packages/core/src/modules/dids/methods/key/__tests__/KeyDidResolver.test.ts index 7c12e9f110..08157cbdcb 100644 --- a/packages/core/src/modules/dids/methods/key/__tests__/KeyDidResolver.test.ts +++ b/packages/core/src/modules/dids/methods/key/__tests__/KeyDidResolver.test.ts @@ -1,3 +1,6 @@ +import type { AgentContext } from '../../../../../agent' + +import { getAgentContext } from '../../../../../../tests/helpers' import { JsonTransformer } from '../../../../../utils/JsonTransformer' import didKeyEd25519Fixture from '../../../__tests__/__fixtures__/didKeyEd25519.json' import { DidKey } from '../DidKey' @@ -6,14 +9,19 @@ import { KeyDidResolver } from '../KeyDidResolver' describe('DidResolver', () => { describe('KeyDidResolver', () => { let keyDidResolver: KeyDidResolver + let agentContext: AgentContext beforeEach(() => { keyDidResolver = new KeyDidResolver() + agentContext = getAgentContext() }) it('should correctly resolve a did:key document', async () => { const fromDidSpy = jest.spyOn(DidKey, 'fromDid') - const result = await keyDidResolver.resolve('did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th') + const result = await keyDidResolver.resolve( + agentContext, + 'did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th' + ) expect(JsonTransformer.toJSON(result)).toMatchObject({ didDocument: didKeyEd25519Fixture, @@ -26,7 +34,10 @@ describe('DidResolver', () => { }) it('should return did resolution metadata with error if the did contains an unsupported multibase', async () => { - const result = await keyDidResolver.resolve('did:key:asdfkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th') + const result = await keyDidResolver.resolve( + agentContext, + 'did:key:asdfkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th' + ) expect(result).toEqual({ didDocument: null, @@ -39,7 +50,10 @@ describe('DidResolver', () => { }) it('should return did resolution metadata with error if the did contains an unsupported multibase', async () => { - const result = await keyDidResolver.resolve('did:key:z6MkmjYasdfasfd8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th') + const result = await keyDidResolver.resolve( + agentContext, + 'did:key:z6MkmjYasdfasfd8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th' + ) expect(result).toEqual({ didDocument: null, diff --git a/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts index 6aebfda5f2..85fad84c54 100644 --- a/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts +++ b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../../../../agent' import type { DidDocument } from '../../domain' import type { DidResolver } from '../../domain/DidResolver' import type { DidRepository } from '../../repository' @@ -18,7 +19,7 @@ export class PeerDidResolver implements DidResolver { this.didRepository = didRepository } - public async resolve(did: string): Promise { + public async resolve(agentContext: AgentContext, did: string): Promise { const didDocumentMetadata = {} try { @@ -36,7 +37,7 @@ export class PeerDidResolver implements DidResolver { } // For Method 1, retrieve from storage else if (numAlgo === PeerDidNumAlgo.GenesisDoc) { - const didDocumentRecord = await this.didRepository.getById(did) + const didDocumentRecord = await this.didRepository.getById(agentContext, did) if (!didDocumentRecord.didDocument) { throw new AriesFrameworkError(`Found did record for method 1 peer did (${did}), but no did document.`) diff --git a/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts b/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts index 5f02c8dd4c..325b5cf185 100644 --- a/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts +++ b/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../../../../agent' import type { IndyEndpointAttrib, IndyLedgerService } from '../../../ledger' import type { DidResolver } from '../../domain/DidResolver' import type { ParsedDid, DidResolutionResult } from '../../types' @@ -22,12 +23,12 @@ export class SovDidResolver implements DidResolver { public readonly supportedMethods = ['sov'] - public async resolve(did: string, parsed: ParsedDid): Promise { + public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { const didDocumentMetadata = {} try { - const nym = await this.indyLedgerService.getPublicDid(parsed.id) - const endpoints = await this.indyLedgerService.getEndpointsForDid(did) + const nym = await this.indyLedgerService.getPublicDid(agentContext, parsed.id) + const endpoints = await this.indyLedgerService.getEndpointsForDid(agentContext, did) const verificationMethodId = `${parsed.did}#key-1` const keyAgreementId = `${parsed.did}#key-agreement-1` diff --git a/packages/core/src/modules/dids/methods/sov/__tests__/SovDidResolver.test.ts b/packages/core/src/modules/dids/methods/sov/__tests__/SovDidResolver.test.ts index ec20ac80be..b1dd46280f 100644 --- a/packages/core/src/modules/dids/methods/sov/__tests__/SovDidResolver.test.ts +++ b/packages/core/src/modules/dids/methods/sov/__tests__/SovDidResolver.test.ts @@ -1,7 +1,8 @@ +import type { AgentContext } from '../../../../../agent' import type { IndyEndpointAttrib } from '../../../../ledger/services/IndyLedgerService' import type { GetNymResponse } from 'indy-sdk' -import { mockFunction } from '../../../../../../tests/helpers' +import { getAgentContext, mockFunction } from '../../../../../../tests/helpers' import { JsonTransformer } from '../../../../../utils/JsonTransformer' import { IndyLedgerService } from '../../../../ledger/services/IndyLedgerService' import didSovR1xKJw17sUoXhejEpugMYJFixture from '../../../__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json' @@ -16,10 +17,12 @@ describe('DidResolver', () => { describe('SovDidResolver', () => { let ledgerService: IndyLedgerService let sovDidResolver: SovDidResolver + let agentContext: AgentContext beforeEach(() => { ledgerService = new IndyLedgerServiceMock() sovDidResolver = new SovDidResolver(ledgerService) + agentContext = getAgentContext() }) it('should correctly resolve a did:sov document', async () => { @@ -40,7 +43,7 @@ describe('DidResolver', () => { mockFunction(ledgerService.getPublicDid).mockResolvedValue(nymResponse) mockFunction(ledgerService.getEndpointsForDid).mockResolvedValue(endpoints) - const result = await sovDidResolver.resolve(did, parseDid(did)) + const result = await sovDidResolver.resolve(agentContext, did, parseDid(did)) expect(JsonTransformer.toJSON(result)).toMatchObject({ didDocument: didSovR1xKJw17sUoXhejEpugMYJFixture, @@ -69,7 +72,7 @@ describe('DidResolver', () => { mockFunction(ledgerService.getPublicDid).mockReturnValue(Promise.resolve(nymResponse)) mockFunction(ledgerService.getEndpointsForDid).mockReturnValue(Promise.resolve(endpoints)) - const result = await sovDidResolver.resolve(did, parseDid(did)) + const result = await sovDidResolver.resolve(agentContext, did, parseDid(did)) expect(JsonTransformer.toJSON(result)).toMatchObject({ didDocument: didSovWJz9mHyW9BZksioQnRsrAoFixture, @@ -85,7 +88,7 @@ describe('DidResolver', () => { mockFunction(ledgerService.getPublicDid).mockRejectedValue(new Error('Error retrieving did')) - const result = await sovDidResolver.resolve(did, parseDid(did)) + const result = await sovDidResolver.resolve(agentContext, did, parseDid(did)) expect(result).toMatchObject({ didDocument: null, diff --git a/packages/core/src/modules/dids/methods/web/WebDidResolver.ts b/packages/core/src/modules/dids/methods/web/WebDidResolver.ts index 628b2eb177..77d9b1e295 100644 --- a/packages/core/src/modules/dids/methods/web/WebDidResolver.ts +++ b/packages/core/src/modules/dids/methods/web/WebDidResolver.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../../../../agent' import type { DidResolver } from '../../domain/DidResolver' import type { ParsedDid, DidResolutionResult, DidResolutionOptions } from '../../types' @@ -19,6 +20,7 @@ export class WebDidResolver implements DidResolver { } public async resolve( + agentContext: AgentContext, did: string, parsed: ParsedDid, didResolutionOptions: DidResolutionOptions diff --git a/packages/core/src/modules/dids/repository/DidRepository.ts b/packages/core/src/modules/dids/repository/DidRepository.ts index cb397cd1fe..3384558c7a 100644 --- a/packages/core/src/modules/dids/repository/DidRepository.ts +++ b/packages/core/src/modules/dids/repository/DidRepository.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../../../agent' import type { Key } from '../../../crypto' import { EventEmitter } from '../../../agent/EventEmitter' @@ -17,11 +18,11 @@ export class DidRepository extends Repository { super(DidRecord, storageService, eventEmitter) } - public findByRecipientKey(recipientKey: Key) { - return this.findSingleByQuery({ recipientKeyFingerprints: [recipientKey.fingerprint] }) + public findByRecipientKey(agentContext: AgentContext, recipientKey: Key) { + return this.findSingleByQuery(agentContext, { recipientKeyFingerprints: [recipientKey.fingerprint] }) } - public findAllByRecipientKey(recipientKey: Key) { - return this.findByQuery({ recipientKeyFingerprints: [recipientKey.fingerprint] }) + public findAllByRecipientKey(agentContext: AgentContext, recipientKey: Key) { + return this.findByQuery(agentContext, { recipientKeyFingerprints: [recipientKey.fingerprint] }) } } diff --git a/packages/core/src/modules/dids/services/DidResolverService.ts b/packages/core/src/modules/dids/services/DidResolverService.ts index 83ab576e50..3a0020a8b5 100644 --- a/packages/core/src/modules/dids/services/DidResolverService.ts +++ b/packages/core/src/modules/dids/services/DidResolverService.ts @@ -1,10 +1,11 @@ -import type { Logger } from '../../../logger' +import type { AgentContext } from '../../../agent' import type { DidResolver } from '../domain/DidResolver' import type { DidResolutionOptions, DidResolutionResult, ParsedDid } from '../types' -import { AgentConfig } from '../../../agent/AgentConfig' +import { InjectionSymbols } from '../../../constants' import { AriesFrameworkError } from '../../../error' -import { injectable } from '../../../plugins' +import { Logger } from '../../../logger' +import { injectable, inject } from '../../../plugins' import { IndyLedgerService } from '../../ledger' import { parseDid } from '../domain/parse' import { KeyDidResolver } from '../methods/key/KeyDidResolver' @@ -18,8 +19,12 @@ export class DidResolverService { private logger: Logger private resolvers: DidResolver[] - public constructor(agentConfig: AgentConfig, indyLedgerService: IndyLedgerService, didRepository: DidRepository) { - this.logger = agentConfig.logger + public constructor( + indyLedgerService: IndyLedgerService, + didRepository: DidRepository, + @inject(InjectionSymbols.Logger) logger: Logger + ) { + this.logger = logger this.resolvers = [ new SovDidResolver(indyLedgerService), @@ -29,7 +34,11 @@ export class DidResolverService { ] } - public async resolve(didUrl: string, options: DidResolutionOptions = {}): Promise { + public async resolve( + agentContext: AgentContext, + didUrl: string, + options: DidResolutionOptions = {} + ): Promise { this.logger.debug(`resolving didUrl ${didUrl}`) const result = { @@ -56,14 +65,14 @@ export class DidResolverService { } } - return resolver.resolve(parsed.did, parsed, options) + return resolver.resolve(agentContext, parsed.did, parsed, options) } - public async resolveDidDocument(did: string) { + public async resolveDidDocument(agentContext: AgentContext, did: string) { const { didDocument, didResolutionMetadata: { error, message }, - } = await this.resolve(did) + } = await this.resolve(agentContext, did) if (!didDocument) { throw new AriesFrameworkError(`Unable to resolve did document for did '${did}': ${error} ${message}`) diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts index b722ab8501..f557d186dd 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts @@ -2,16 +2,17 @@ import type { AgentMessageProcessedEvent } from '../../agent/Events' import type { DependencyManager } from '../../plugins' import type { ParsedMessageType } from '../../utils/messageType' -import { firstValueFrom, of, ReplaySubject } from 'rxjs' -import { filter, takeUntil, timeout, catchError, map } from 'rxjs/operators' +import { firstValueFrom, of, ReplaySubject, Subject } from 'rxjs' +import { catchError, filter, map, takeUntil, timeout } from 'rxjs/operators' -import { AgentConfig } from '../../agent/AgentConfig' +import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { EventEmitter } from '../../agent/EventEmitter' import { AgentEventTypes } from '../../agent/Events' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' -import { injectable, module } from '../../plugins' +import { InjectionSymbols } from '../../constants' +import { inject, injectable, module } from '../../plugins' import { canHandleMessageType, parseMessageType } from '../../utils/messageType' import { ConnectionService } from '../connections/services' @@ -26,7 +27,8 @@ export class DiscoverFeaturesModule { private messageSender: MessageSender private discoverFeaturesService: DiscoverFeaturesService private eventEmitter: EventEmitter - private agentConfig: AgentConfig + private stop$: Subject + private agentContext: AgentContext public constructor( dispatcher: Dispatcher, @@ -34,14 +36,16 @@ export class DiscoverFeaturesModule { messageSender: MessageSender, discoverFeaturesService: DiscoverFeaturesService, eventEmitter: EventEmitter, - agentConfig: AgentConfig + @inject(InjectionSymbols.Stop$) stop$: Subject, + agentContext: AgentContext ) { this.connectionService = connectionService this.messageSender = messageSender this.discoverFeaturesService = discoverFeaturesService this.registerHandlers(dispatcher) this.eventEmitter = eventEmitter - this.agentConfig = agentConfig + this.stop$ = stop$ + this.agentContext = agentContext } public async isProtocolSupported(connectionId: string, message: { type: ParsedMessageType }) { @@ -53,7 +57,7 @@ export class DiscoverFeaturesModule { .observable(AgentEventTypes.AgentMessageProcessed) .pipe( // Stop when the agent shuts down - takeUntil(this.agentConfig.stop$), + takeUntil(this.stop$), // filter by connection id and query disclose message type filter( (e) => @@ -83,12 +87,12 @@ export class DiscoverFeaturesModule { } public async queryFeatures(connectionId: string, options: { query: string; comment?: string }) { - const connection = await this.connectionService.getById(connectionId) + const connection = await this.connectionService.getById(this.agentContext, connectionId) const queryMessage = await this.discoverFeaturesService.createQuery(options) const outbound = createOutboundMessage(connection, queryMessage) - await this.messageSender.sendMessage(outbound) + await this.messageSender.sendMessage(this.agentContext, outbound) } private registerHandlers(dispatcher: Dispatcher) { diff --git a/packages/core/src/modules/generic-records/GenericRecordsModule.ts b/packages/core/src/modules/generic-records/GenericRecordsModule.ts index 579536848f..9ce523adde 100644 --- a/packages/core/src/modules/generic-records/GenericRecordsModule.ts +++ b/packages/core/src/modules/generic-records/GenericRecordsModule.ts @@ -1,9 +1,10 @@ -import type { Logger } from '../../logger' import type { DependencyManager } from '../../plugins' import type { GenericRecord, GenericRecordTags, SaveGenericRecordOption } from './repository/GenericRecord' -import { AgentConfig } from '../../agent/AgentConfig' -import { injectable, module } from '../../plugins' +import { AgentContext } from '../../agent' +import { InjectionSymbols } from '../../constants' +import { Logger } from '../../logger' +import { inject, injectable, module } from '../../plugins' import { GenericRecordsRepository } from './repository/GenericRecordsRepository' import { GenericRecordService } from './service/GenericRecordService' @@ -17,14 +18,21 @@ export type ContentType = { export class GenericRecordsModule { private genericRecordsService: GenericRecordService private logger: Logger - public constructor(agentConfig: AgentConfig, genericRecordsService: GenericRecordService) { + private agentContext: AgentContext + + public constructor( + genericRecordsService: GenericRecordService, + @inject(InjectionSymbols.Logger) logger: Logger, + agentContext: AgentContext + ) { this.genericRecordsService = genericRecordsService - this.logger = agentConfig.logger + this.logger = logger + this.agentContext = agentContext } public async save({ content, tags, id }: SaveGenericRecordOption) { try { - const record = await this.genericRecordsService.save({ + const record = await this.genericRecordsService.save(this.agentContext, { id, content, tags, @@ -42,7 +50,7 @@ export class GenericRecordsModule { public async delete(record: GenericRecord): Promise { try { - await this.genericRecordsService.delete(record) + await this.genericRecordsService.delete(this.agentContext, record) } catch (error) { this.logger.error('Error while saving generic-record', { error, @@ -54,12 +62,12 @@ export class GenericRecordsModule { } public async deleteById(id: string): Promise { - await this.genericRecordsService.deleteById(id) + await this.genericRecordsService.deleteById(this.agentContext, id) } public async update(record: GenericRecord): Promise { try { - await this.genericRecordsService.update(record) + await this.genericRecordsService.update(this.agentContext, record) } catch (error) { this.logger.error('Error while update generic-record', { error, @@ -71,15 +79,15 @@ export class GenericRecordsModule { } public async findById(id: string) { - return this.genericRecordsService.findById(id) + return this.genericRecordsService.findById(this.agentContext, id) } public async findAllByQuery(query: Partial): Promise { - return this.genericRecordsService.findAllByQuery(query) + return this.genericRecordsService.findAllByQuery(this.agentContext, query) } public async getAll(): Promise { - return this.genericRecordsService.getAll() + return this.genericRecordsService.getAll(this.agentContext) } /** diff --git a/packages/core/src/modules/generic-records/service/GenericRecordService.ts b/packages/core/src/modules/generic-records/service/GenericRecordService.ts index e27f818e86..861f0a002f 100644 --- a/packages/core/src/modules/generic-records/service/GenericRecordService.ts +++ b/packages/core/src/modules/generic-records/service/GenericRecordService.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../../../agent' import type { GenericRecordTags, SaveGenericRecordOption } from '../repository/GenericRecord' import { AriesFrameworkError } from '../../../error' @@ -13,7 +14,7 @@ export class GenericRecordService { this.genericRecordsRepository = genericRecordsRepository } - public async save({ content, tags, id }: SaveGenericRecordOption) { + public async save(agentContext: AgentContext, { content, tags, id }: SaveGenericRecordOption) { const genericRecord = new GenericRecord({ id, content, @@ -21,7 +22,7 @@ export class GenericRecordService { }) try { - await this.genericRecordsRepository.save(genericRecord) + await this.genericRecordsRepository.save(agentContext, genericRecord) return genericRecord } catch (error) { throw new AriesFrameworkError( @@ -30,35 +31,35 @@ export class GenericRecordService { } } - public async delete(record: GenericRecord): Promise { + public async delete(agentContext: AgentContext, record: GenericRecord): Promise { try { - await this.genericRecordsRepository.delete(record) + await this.genericRecordsRepository.delete(agentContext, record) } catch (error) { throw new AriesFrameworkError(`Unable to delete the genericRecord record with id ${record.id}. Message: ${error}`) } } - public async deleteById(id: string): Promise { - await this.genericRecordsRepository.deleteById(id) + public async deleteById(agentContext: AgentContext, id: string): Promise { + await this.genericRecordsRepository.deleteById(agentContext, id) } - public async update(record: GenericRecord): Promise { + public async update(agentContext: AgentContext, record: GenericRecord): Promise { try { - await this.genericRecordsRepository.update(record) + await this.genericRecordsRepository.update(agentContext, record) } catch (error) { throw new AriesFrameworkError(`Unable to update the genericRecord record with id ${record.id}. Message: ${error}`) } } - public async findAllByQuery(query: Partial) { - return this.genericRecordsRepository.findByQuery(query) + public async findAllByQuery(agentContext: AgentContext, query: Partial) { + return this.genericRecordsRepository.findByQuery(agentContext, query) } - public async findById(id: string): Promise { - return this.genericRecordsRepository.findById(id) + public async findById(agentContext: AgentContext, id: string): Promise { + return this.genericRecordsRepository.findById(agentContext, id) } - public async getAll() { - return this.genericRecordsRepository.getAll() + public async getAll(agentContext: AgentContext) { + return this.genericRecordsRepository.getAll(agentContext) } } diff --git a/packages/core/src/modules/indy/services/IndyHolderService.ts b/packages/core/src/modules/indy/services/IndyHolderService.ts index 763841c4ef..e92b20896f 100644 --- a/packages/core/src/modules/indy/services/IndyHolderService.ts +++ b/packages/core/src/modules/indy/services/IndyHolderService.ts @@ -1,12 +1,14 @@ -import type { Logger } from '../../../logger' +import type { AgentContext } from '../../../agent' import type { RequestedCredentials } from '../../proofs' import type * as Indy from 'indy-sdk' -import { AgentConfig } from '../../../agent/AgentConfig' +import { AgentDependencies } from '../../../agent/AgentDependencies' +import { InjectionSymbols } from '../../../constants' import { IndySdkError } from '../../../error/IndySdkError' -import { injectable } from '../../../plugins' +import { Logger } from '../../../logger' +import { injectable, inject } from '../../../plugins' import { isIndyError } from '../../../utils/indyError' -import { IndyWallet } from '../../../wallet/IndyWallet' +import { assertIndyWallet } from '../../../wallet/util/assertIndyWallet' import { IndyRevocationService } from './IndyRevocationService' @@ -14,14 +16,16 @@ import { IndyRevocationService } from './IndyRevocationService' export class IndyHolderService { private indy: typeof Indy private logger: Logger - private wallet: IndyWallet private indyRevocationService: IndyRevocationService - public constructor(agentConfig: AgentConfig, indyRevocationService: IndyRevocationService, wallet: IndyWallet) { - this.indy = agentConfig.agentDependencies.indy - this.wallet = wallet + public constructor( + indyRevocationService: IndyRevocationService, + @inject(InjectionSymbols.Logger) logger: Logger, + @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies + ) { + this.indy = agentDependencies.indy this.indyRevocationService = indyRevocationService - this.logger = agentConfig.logger + this.logger = logger } /** @@ -36,24 +40,24 @@ export class IndyHolderService { * * @todo support attribute non_revoked fields */ - public async createProof({ - proofRequest, - requestedCredentials, - schemas, - credentialDefinitions, - }: CreateProofOptions): Promise { + public async createProof( + agentContext: AgentContext, + { proofRequest, requestedCredentials, schemas, credentialDefinitions }: CreateProofOptions + ): Promise { + assertIndyWallet(agentContext.wallet) try { this.logger.debug('Creating Indy Proof') const revocationStates: Indy.RevStates = await this.indyRevocationService.createRevocationState( + agentContext, proofRequest, requestedCredentials ) const indyProof: Indy.IndyProof = await this.indy.proverCreateProof( - this.wallet.handle, + agentContext.wallet.handle, proofRequest, requestedCredentials.toJSON(), - this.wallet.masterSecretId, + agentContext.wallet.masterSecretId, schemas, credentialDefinitions, revocationStates @@ -80,16 +84,20 @@ export class IndyHolderService { * * @returns The credential id */ - public async storeCredential({ - credentialRequestMetadata, - credential, - credentialDefinition, - credentialId, - revocationRegistryDefinition, - }: StoreCredentialOptions): Promise { + public async storeCredential( + agentContext: AgentContext, + { + credentialRequestMetadata, + credential, + credentialDefinition, + credentialId, + revocationRegistryDefinition, + }: StoreCredentialOptions + ): Promise { + assertIndyWallet(agentContext.wallet) try { return await this.indy.proverStoreCredential( - this.wallet.handle, + agentContext.wallet.handle, credentialId ?? null, credentialRequestMetadata, credential, @@ -114,9 +122,13 @@ export class IndyHolderService { * * @todo handle record not found */ - public async getCredential(credentialId: Indy.CredentialId): Promise { + public async getCredential( + agentContext: AgentContext, + credentialId: Indy.CredentialId + ): Promise { + assertIndyWallet(agentContext.wallet) try { - return await this.indy.proverGetCredential(this.wallet.handle, credentialId) + return await this.indy.proverGetCredential(agentContext.wallet.handle, credentialId) } catch (error) { this.logger.error(`Error getting Indy Credential '${credentialId}'`, { error, @@ -131,18 +143,18 @@ export class IndyHolderService { * * @returns The credential request and the credential request metadata */ - public async createCredentialRequest({ - holderDid, - credentialOffer, - credentialDefinition, - }: CreateCredentialRequestOptions): Promise<[Indy.CredReq, Indy.CredReqMetadata]> { + public async createCredentialRequest( + agentContext: AgentContext, + { holderDid, credentialOffer, credentialDefinition }: CreateCredentialRequestOptions + ): Promise<[Indy.CredReq, Indy.CredReqMetadata]> { + assertIndyWallet(agentContext.wallet) try { return await this.indy.proverCreateCredentialReq( - this.wallet.handle, + agentContext.wallet.handle, holderDid, credentialOffer, credentialDefinition, - this.wallet.masterSecretId + agentContext.wallet.masterSecretId ) } catch (error) { this.logger.error(`Error creating Indy Credential Request`, { @@ -165,17 +177,15 @@ export class IndyHolderService { * @returns List of credentials that are available for building a proof for the given proof request * */ - public async getCredentialsForProofRequest({ - proofRequest, - attributeReferent, - start = 0, - limit = 256, - extraQuery, - }: GetCredentialForProofRequestOptions): Promise { + public async getCredentialsForProofRequest( + agentContext: AgentContext, + { proofRequest, attributeReferent, start = 0, limit = 256, extraQuery }: GetCredentialForProofRequestOptions + ): Promise { + assertIndyWallet(agentContext.wallet) try { // Open indy credential search const searchHandle = await this.indy.proverSearchCredentialsForProofReq( - this.wallet.handle, + agentContext.wallet.handle, proofRequest, extraQuery ?? null ) @@ -210,9 +220,10 @@ export class IndyHolderService { * @param credentialId the id (referent) of the credential * */ - public async deleteCredential(credentialId: Indy.CredentialId): Promise { + public async deleteCredential(agentContext: AgentContext, credentialId: Indy.CredentialId): Promise { + assertIndyWallet(agentContext.wallet) try { - return await this.indy.proverDeleteCredential(this.wallet.handle, credentialId) + return await this.indy.proverDeleteCredential(agentContext.wallet.handle, credentialId) } catch (error) { this.logger.error(`Error deleting Indy Credential from Wallet`, { error, diff --git a/packages/core/src/modules/indy/services/IndyIssuerService.ts b/packages/core/src/modules/indy/services/IndyIssuerService.ts index 9c0ed580a6..58e9917cf0 100644 --- a/packages/core/src/modules/indy/services/IndyIssuerService.ts +++ b/packages/core/src/modules/indy/services/IndyIssuerService.ts @@ -1,37 +1,37 @@ -import type { FileSystem } from '../../../storage/FileSystem' +import type { AgentContext } from '../../../agent' import type { - default as Indy, - CredDef, - Schema, Cred, + CredDef, CredDefId, CredOffer, CredReq, CredRevocId, CredValues, + default as Indy, + Schema, } from 'indy-sdk' -import { AgentConfig } from '../../../agent/AgentConfig' +import { AgentDependencies } from '../../../agent/AgentDependencies' +import { InjectionSymbols } from '../../../constants' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' import { IndySdkError } from '../../../error/IndySdkError' -import { injectable } from '../../../plugins' +import { injectable, inject } from '../../../plugins' import { isIndyError } from '../../../utils/indyError' -import { IndyWallet } from '../../../wallet/IndyWallet' +import { assertIndyWallet } from '../../../wallet/util/assertIndyWallet' import { IndyUtilitiesService } from './IndyUtilitiesService' @injectable() export class IndyIssuerService { private indy: typeof Indy - private wallet: IndyWallet private indyUtilitiesService: IndyUtilitiesService - private fileSystem: FileSystem - public constructor(agentConfig: AgentConfig, wallet: IndyWallet, indyUtilitiesService: IndyUtilitiesService) { - this.indy = agentConfig.agentDependencies.indy - this.wallet = wallet + public constructor( + indyUtilitiesService: IndyUtilitiesService, + @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies + ) { + this.indy = agentDependencies.indy this.indyUtilitiesService = indyUtilitiesService - this.fileSystem = agentConfig.fileSystem } /** @@ -39,7 +39,11 @@ export class IndyIssuerService { * * @returns the schema. */ - public async createSchema({ originDid, name, version, attributes }: CreateSchemaOptions): Promise { + public async createSchema( + agentContext: AgentContext, + { originDid, name, version, attributes }: CreateSchemaOptions + ): Promise { + assertIndyWallet(agentContext.wallet) try { const [, schema] = await this.indy.issuerCreateSchema(originDid, name, version, attributes) @@ -54,16 +58,20 @@ export class IndyIssuerService { * * @returns the credential definition. */ - public async createCredentialDefinition({ - issuerDid, - schema, - tag = 'default', - signatureType = 'CL', - supportRevocation = false, - }: CreateCredentialDefinitionOptions): Promise { + public async createCredentialDefinition( + agentContext: AgentContext, + { + issuerDid, + schema, + tag = 'default', + signatureType = 'CL', + supportRevocation = false, + }: CreateCredentialDefinitionOptions + ): Promise { + assertIndyWallet(agentContext.wallet) try { const [, credentialDefinition] = await this.indy.issuerCreateAndStoreCredentialDef( - this.wallet.handle, + agentContext.wallet.handle, issuerDid, schema, tag, @@ -85,9 +93,10 @@ export class IndyIssuerService { * @param credentialDefinitionId The credential definition to create an offer for * @returns The created credential offer */ - public async createCredentialOffer(credentialDefinitionId: CredDefId) { + public async createCredentialOffer(agentContext: AgentContext, credentialDefinitionId: CredDefId) { + assertIndyWallet(agentContext.wallet) try { - return await this.indy.issuerCreateCredentialOffer(this.wallet.handle, credentialDefinitionId) + return await this.indy.issuerCreateCredentialOffer(agentContext.wallet.handle, credentialDefinitionId) } catch (error) { throw isIndyError(error) ? new IndySdkError(error) : error } @@ -98,13 +107,17 @@ export class IndyIssuerService { * * @returns Credential and revocation id */ - public async createCredential({ - credentialOffer, - credentialRequest, - credentialValues, - revocationRegistryId, - tailsFilePath, - }: CreateCredentialOptions): Promise<[Cred, CredRevocId]> { + public async createCredential( + agentContext: AgentContext, + { + credentialOffer, + credentialRequest, + credentialValues, + revocationRegistryId, + tailsFilePath, + }: CreateCredentialOptions + ): Promise<[Cred, CredRevocId]> { + assertIndyWallet(agentContext.wallet) try { // Indy SDK requires tailsReaderHandle. Use null if no tailsFilePath is present const tailsReaderHandle = tailsFilePath ? await this.indyUtilitiesService.createTailsReader(tailsFilePath) : 0 @@ -114,7 +127,7 @@ export class IndyIssuerService { } const [credential, credentialRevocationId] = await this.indy.issuerCreateCredential( - this.wallet.handle, + agentContext.wallet.handle, credentialOffer, credentialRequest, credentialValues, diff --git a/packages/core/src/modules/indy/services/IndyRevocationService.ts b/packages/core/src/modules/indy/services/IndyRevocationService.ts index 4431df8dde..fa84997876 100644 --- a/packages/core/src/modules/indy/services/IndyRevocationService.ts +++ b/packages/core/src/modules/indy/services/IndyRevocationService.ts @@ -1,15 +1,15 @@ -import type { Logger } from '../../../logger' -import type { FileSystem } from '../../../storage/FileSystem' +import type { AgentContext } from '../../../agent' import type { IndyRevocationInterval } from '../../credentials' import type { RequestedCredentials } from '../../proofs' import type { default as Indy } from 'indy-sdk' -import { AgentConfig } from '../../../agent/AgentConfig' +import { AgentDependencies } from '../../../agent/AgentDependencies' +import { InjectionSymbols } from '../../../constants' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' import { IndySdkError } from '../../../error/IndySdkError' -import { injectable } from '../../../plugins' +import { Logger } from '../../../logger' +import { injectable, inject } from '../../../plugins' import { isIndyError } from '../../../utils/indyError' -import { IndyWallet } from '../../../wallet/IndyWallet' import { IndyLedgerService } from '../../ledger' import { IndyUtilitiesService } from './IndyUtilitiesService' @@ -23,26 +23,23 @@ enum RequestReferentType { export class IndyRevocationService { private indy: typeof Indy private indyUtilitiesService: IndyUtilitiesService - private fileSystem: FileSystem private ledgerService: IndyLedgerService private logger: Logger - private wallet: IndyWallet public constructor( - agentConfig: AgentConfig, indyUtilitiesService: IndyUtilitiesService, ledgerService: IndyLedgerService, - wallet: IndyWallet + @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, + @inject(InjectionSymbols.Logger) logger: Logger ) { - this.fileSystem = agentConfig.fileSystem - this.indy = agentConfig.agentDependencies.indy + this.indy = agentDependencies.indy this.indyUtilitiesService = indyUtilitiesService - this.logger = agentConfig.logger + this.logger = logger this.ledgerService = ledgerService - this.wallet = wallet } public async createRevocationState( + agentContext: AgentContext, proofRequest: Indy.IndyProofRequest, requestedCredentials: RequestedCredentials ): Promise { @@ -100,10 +97,12 @@ export class IndyRevocationService { this.assertRevocationInterval(requestRevocationInterval) const { revocationRegistryDefinition } = await this.ledgerService.getRevocationRegistryDefinition( + agentContext, revocationRegistryId ) const { revocationRegistryDelta, deltaTimestamp } = await this.ledgerService.getRevocationRegistryDelta( + agentContext, revocationRegistryId, requestRevocationInterval?.to, 0 @@ -147,6 +146,7 @@ export class IndyRevocationService { // Get revocation status for credential (given a from-to) // Note from-to interval details: https://github.com/hyperledger/indy-hipe/blob/master/text/0011-cred-revocation/README.md#indy-node-revocation-registry-intervals public async getRevocationStatus( + agentContext: AgentContext, credentialRevocationId: string, revocationRegistryDefinitionId: string, requestRevocationInterval: IndyRevocationInterval @@ -158,6 +158,7 @@ export class IndyRevocationService { this.assertRevocationInterval(requestRevocationInterval) const { revocationRegistryDelta, deltaTimestamp } = await this.ledgerService.getRevocationRegistryDelta( + agentContext, revocationRegistryDefinitionId, requestRevocationInterval.to, 0 diff --git a/packages/core/src/modules/indy/services/IndyUtilitiesService.ts b/packages/core/src/modules/indy/services/IndyUtilitiesService.ts index 74784f2182..eef01ccfd2 100644 --- a/packages/core/src/modules/indy/services/IndyUtilitiesService.ts +++ b/packages/core/src/modules/indy/services/IndyUtilitiesService.ts @@ -1,11 +1,12 @@ -import type { Logger } from '../../../logger' -import type { FileSystem } from '../../../storage/FileSystem' -import type { default as Indy, BlobReaderHandle } from 'indy-sdk' +import type { BlobReaderHandle, default as Indy } from 'indy-sdk' -import { AgentConfig } from '../../../agent/AgentConfig' +import { AgentDependencies } from '../../../agent/AgentDependencies' +import { InjectionSymbols } from '../../../constants' import { AriesFrameworkError } from '../../../error' import { IndySdkError } from '../../../error/IndySdkError' -import { injectable } from '../../../plugins' +import { Logger } from '../../../logger' +import { injectable, inject } from '../../../plugins' +import { FileSystem } from '../../../storage/FileSystem' import { isIndyError } from '../../../utils/indyError' import { getDirFromFilePath } from '../../../utils/path' @@ -15,10 +16,14 @@ export class IndyUtilitiesService { private logger: Logger private fileSystem: FileSystem - public constructor(agentConfig: AgentConfig) { - this.indy = agentConfig.agentDependencies.indy - this.logger = agentConfig.logger - this.fileSystem = agentConfig.fileSystem + public constructor( + @inject(InjectionSymbols.Logger) logger: Logger, + @inject(InjectionSymbols.FileSystem) fileSystem: FileSystem, + @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies + ) { + this.indy = agentDependencies.indy + this.logger = logger + this.fileSystem = fileSystem } /** diff --git a/packages/core/src/modules/indy/services/IndyVerifierService.ts b/packages/core/src/modules/indy/services/IndyVerifierService.ts index b480288acc..b6cc387c31 100644 --- a/packages/core/src/modules/indy/services/IndyVerifierService.ts +++ b/packages/core/src/modules/indy/services/IndyVerifierService.ts @@ -1,8 +1,10 @@ +import type { AgentContext } from '../../../agent' import type * as Indy from 'indy-sdk' -import { AgentConfig } from '../../../agent/AgentConfig' +import { AgentDependencies } from '../../../agent/AgentDependencies' +import { InjectionSymbols } from '../../../constants' import { IndySdkError } from '../../../error' -import { injectable } from '../../../plugins' +import { injectable, inject } from '../../../plugins' import { isIndyError } from '../../../utils/indyError' import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' @@ -11,19 +13,23 @@ export class IndyVerifierService { private indy: typeof Indy private ledgerService: IndyLedgerService - public constructor(agentConfig: AgentConfig, ledgerService: IndyLedgerService) { - this.indy = agentConfig.agentDependencies.indy + public constructor( + ledgerService: IndyLedgerService, + @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies + ) { + this.indy = agentDependencies.indy this.ledgerService = ledgerService } - public async verifyProof({ - proofRequest, - proof, - schemas, - credentialDefinitions, - }: VerifyProofOptions): Promise { + public async verifyProof( + agentContext: AgentContext, + { proofRequest, proof, schemas, credentialDefinitions }: VerifyProofOptions + ): Promise { try { - const { revocationRegistryDefinitions, revocationRegistryStates } = await this.getRevocationRegistries(proof) + const { revocationRegistryDefinitions, revocationRegistryStates } = await this.getRevocationRegistries( + agentContext, + proof + ) return await this.indy.verifierVerifyProof( proofRequest, @@ -38,7 +44,7 @@ export class IndyVerifierService { } } - private async getRevocationRegistries(proof: Indy.IndyProof) { + private async getRevocationRegistries(agentContext: AgentContext, proof: Indy.IndyProof) { const revocationRegistryDefinitions: Indy.RevocRegDefs = {} const revocationRegistryStates: Indy.RevStates = Object.create(null) for (const identifier of proof.identifiers) { @@ -48,6 +54,7 @@ export class IndyVerifierService { //Fetch Revocation Registry Definition if not already fetched if (revocationRegistryId && !revocationRegistryDefinitions[revocationRegistryId]) { const { revocationRegistryDefinition } = await this.ledgerService.getRevocationRegistryDefinition( + agentContext, revocationRegistryId ) revocationRegistryDefinitions[revocationRegistryId] = revocationRegistryDefinition @@ -58,7 +65,11 @@ export class IndyVerifierService { if (!revocationRegistryStates[revocationRegistryId]) { revocationRegistryStates[revocationRegistryId] = Object.create(null) } - const { revocationRegistry } = await this.ledgerService.getRevocationRegistry(revocationRegistryId, timestamp) + const { revocationRegistry } = await this.ledgerService.getRevocationRegistry( + agentContext, + revocationRegistryId, + timestamp + ) revocationRegistryStates[revocationRegistryId][timestamp] = revocationRegistry } } diff --git a/packages/core/src/modules/indy/services/__mocks__/IndyHolderService.ts b/packages/core/src/modules/indy/services/__mocks__/IndyHolderService.ts index 1d6ed433b6..35afdc14ab 100644 --- a/packages/core/src/modules/indy/services/__mocks__/IndyHolderService.ts +++ b/packages/core/src/modules/indy/services/__mocks__/IndyHolderService.ts @@ -1,11 +1,11 @@ import type { CreateCredentialRequestOptions, StoreCredentialOptions } from '../IndyHolderService' export const IndyHolderService = jest.fn(() => ({ - storeCredential: jest.fn(({ credentialId }: StoreCredentialOptions) => + storeCredential: jest.fn((_, { credentialId }: StoreCredentialOptions) => Promise.resolve(credentialId ?? 'some-random-uuid') ), deleteCredential: jest.fn(() => Promise.resolve()), - createCredentialRequest: jest.fn(({ holderDid, credentialDefinition }: CreateCredentialRequestOptions) => + createCredentialRequest: jest.fn((_, { holderDid, credentialDefinition }: CreateCredentialRequestOptions) => Promise.resolve([ { prover_did: holderDid, diff --git a/packages/core/src/modules/indy/services/__mocks__/IndyIssuerService.ts b/packages/core/src/modules/indy/services/__mocks__/IndyIssuerService.ts index b9b23337ba..823e961a15 100644 --- a/packages/core/src/modules/indy/services/__mocks__/IndyIssuerService.ts +++ b/packages/core/src/modules/indy/services/__mocks__/IndyIssuerService.ts @@ -13,7 +13,7 @@ export const IndyIssuerService = jest.fn(() => ({ ]) ), - createCredentialOffer: jest.fn((credentialDefinitionId: string) => + createCredentialOffer: jest.fn((_, credentialDefinitionId: string) => Promise.resolve({ schema_id: 'aaa', cred_def_id: credentialDefinitionId, diff --git a/packages/core/src/modules/ledger/IndyPool.ts b/packages/core/src/modules/ledger/IndyPool.ts index 19127dc946..a6bd99c6e0 100644 --- a/packages/core/src/modules/ledger/IndyPool.ts +++ b/packages/core/src/modules/ledger/IndyPool.ts @@ -1,7 +1,8 @@ -import type { AgentConfig } from '../../agent/AgentConfig' +import type { AgentDependencies } from '../../agent/AgentDependencies' import type { Logger } from '../../logger' import type { FileSystem } from '../../storage/FileSystem' import type * as Indy from 'indy-sdk' +import type { Subject } from 'rxjs' import { AriesFrameworkError, IndySdkError } from '../../error' import { isIndyError } from '../../utils/indyError' @@ -31,14 +32,20 @@ export class IndyPool { private poolConnected?: Promise public authorAgreement?: AuthorAgreement | null - public constructor(agentConfig: AgentConfig, poolConfig: IndyPoolConfig) { - this.indy = agentConfig.agentDependencies.indy + public constructor( + poolConfig: IndyPoolConfig, + agentDependencies: AgentDependencies, + logger: Logger, + stop$: Subject, + fileSystem: FileSystem + ) { + this.indy = agentDependencies.indy + this.fileSystem = fileSystem this.poolConfig = poolConfig - this.fileSystem = agentConfig.fileSystem - this.logger = agentConfig.logger + this.logger = logger // Listen to stop$ (shutdown) and close pool - agentConfig.stop$.subscribe(async () => { + stop$.subscribe(async () => { if (this._poolHandle) { await this.close() } diff --git a/packages/core/src/modules/ledger/LedgerModule.ts b/packages/core/src/modules/ledger/LedgerModule.ts index cceaf5210e..9262511c8c 100644 --- a/packages/core/src/modules/ledger/LedgerModule.ts +++ b/packages/core/src/modules/ledger/LedgerModule.ts @@ -1,23 +1,27 @@ import type { DependencyManager } from '../../plugins' -import type { SchemaTemplate, CredentialDefinitionTemplate } from './services' +import type { IndyPoolConfig } from './IndyPool' +import type { CredentialDefinitionTemplate, SchemaTemplate } from './services' import type { NymRole } from 'indy-sdk' -import { InjectionSymbols } from '../../constants' +import { AgentContext } from '../../agent' import { AriesFrameworkError } from '../../error' -import { injectable, module, inject } from '../../plugins' -import { Wallet } from '../../wallet/Wallet' +import { injectable, module } from '../../plugins' -import { IndyPoolService, IndyLedgerService } from './services' +import { IndyLedgerService, IndyPoolService } from './services' @module() @injectable() export class LedgerModule { private ledgerService: IndyLedgerService - private wallet: Wallet + private agentContext: AgentContext - public constructor(@inject(InjectionSymbols.Wallet) wallet: Wallet, ledgerService: IndyLedgerService) { + public constructor(ledgerService: IndyLedgerService, agentContext: AgentContext) { this.ledgerService = ledgerService - this.wallet = wallet + this.agentContext = agentContext + } + + public setPools(poolConfigs: IndyPoolConfig[]) { + return this.ledgerService.setPools(poolConfigs) } /** @@ -28,54 +32,54 @@ export class LedgerModule { } public async registerPublicDid(did: string, verkey: string, alias: string, role?: NymRole) { - const myPublicDid = this.wallet.publicDid?.did + const myPublicDid = this.agentContext.wallet.publicDid?.did if (!myPublicDid) { throw new AriesFrameworkError('Agent has no public DID.') } - return this.ledgerService.registerPublicDid(myPublicDid, did, verkey, alias, role) + return this.ledgerService.registerPublicDid(this.agentContext, myPublicDid, did, verkey, alias, role) } public async getPublicDid(did: string) { - return this.ledgerService.getPublicDid(did) + return this.ledgerService.getPublicDid(this.agentContext, did) } public async registerSchema(schema: SchemaTemplate) { - const did = this.wallet.publicDid?.did + const did = this.agentContext.wallet.publicDid?.did if (!did) { throw new AriesFrameworkError('Agent has no public DID.') } - return this.ledgerService.registerSchema(did, schema) + return this.ledgerService.registerSchema(this.agentContext, did, schema) } public async getSchema(id: string) { - return this.ledgerService.getSchema(id) + return this.ledgerService.getSchema(this.agentContext, id) } public async registerCredentialDefinition( credentialDefinitionTemplate: Omit ) { - const did = this.wallet.publicDid?.did + const did = this.agentContext.wallet.publicDid?.did if (!did) { throw new AriesFrameworkError('Agent has no public DID.') } - return this.ledgerService.registerCredentialDefinition(did, { + return this.ledgerService.registerCredentialDefinition(this.agentContext, did, { ...credentialDefinitionTemplate, signatureType: 'CL', }) } public async getCredentialDefinition(id: string) { - return this.ledgerService.getCredentialDefinition(id) + return this.ledgerService.getCredentialDefinition(this.agentContext, id) } public async getRevocationRegistryDefinition(revocationRegistryDefinitionId: string) { - return this.ledgerService.getRevocationRegistryDefinition(revocationRegistryDefinitionId) + return this.ledgerService.getRevocationRegistryDefinition(this.agentContext, revocationRegistryDefinitionId) } public async getRevocationRegistryDelta( @@ -83,7 +87,12 @@ export class LedgerModule { fromSeconds = 0, toSeconds = new Date().getTime() ) { - return this.ledgerService.getRevocationRegistryDelta(revocationRegistryDefinitionId, fromSeconds, toSeconds) + return this.ledgerService.getRevocationRegistryDelta( + this.agentContext, + revocationRegistryDefinitionId, + fromSeconds, + toSeconds + ) } /** diff --git a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts b/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts index 0929cad3dd..85d29438a3 100644 --- a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts +++ b/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts @@ -1,7 +1,11 @@ +import type { AgentContext } from '../../../agent' import type { IndyPoolConfig } from '../IndyPool' import type { LedgerReadReplyResponse, LedgerWriteReplyResponse } from 'indy-sdk' -import { getAgentConfig, mockFunction } from '../../../../tests/helpers' +import { Subject } from 'rxjs' + +import { NodeFileSystem } from '../../../../../node/src/NodeFileSystem' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../tests/helpers' import { CacheRepository } from '../../../cache/CacheRepository' import { IndyWallet } from '../../../wallet/IndyWallet' import { IndyIssuerService } from '../../indy/services/IndyIssuerService' @@ -30,13 +34,15 @@ describe('IndyLedgerService', () => { indyLedgers: pools, }) let wallet: IndyWallet + let agentContext: AgentContext let poolService: IndyPoolService let cacheRepository: CacheRepository let indyIssuerService: IndyIssuerService let ledgerService: IndyLedgerService beforeAll(async () => { - wallet = new IndyWallet(config) + wallet = new IndyWallet(config.agentDependencies, config.logger) + agentContext = getAgentContext() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) }) @@ -50,7 +56,7 @@ describe('IndyLedgerService', () => { mockFunction(cacheRepository.findById).mockResolvedValue(null) indyIssuerService = new IndyIssuerServiceMock() poolService = new IndyPoolServiceMock() - const pool = new IndyPool(config, pools[0]) + const pool = new IndyPool(pools[0], config.agentDependencies, config.logger, new Subject(), new NodeFileSystem()) jest.spyOn(pool, 'submitWriteRequest').mockResolvedValue({} as LedgerWriteReplyResponse) jest.spyOn(pool, 'submitReadRequest').mockResolvedValue({} as LedgerReadReplyResponse) jest.spyOn(pool, 'connect').mockResolvedValue(0) @@ -58,7 +64,7 @@ describe('IndyLedgerService', () => { // @ts-ignore poolService.ledgerWritePool = pool - ledgerService = new IndyLedgerService(wallet, config, indyIssuerService, poolService) + ledgerService = new IndyLedgerService(config.agentDependencies, config.logger, indyIssuerService, poolService) }) describe('LedgerServiceWrite', () => { @@ -78,6 +84,7 @@ describe('IndyLedgerService', () => { } as never) await expect( ledgerService.registerPublicDid( + agentContext, 'BBPoJqRKatdcfLEAFL7exC', 'N8NQHLtCKfPmWMgCSdfa7h', 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', @@ -104,6 +111,7 @@ describe('IndyLedgerService', () => { } as never) await expect( ledgerService.registerPublicDid( + agentContext, 'BBPoJqRKatdcfLEAFL7exC', 'N8NQHLtCKfPmWMgCSdfa7h', 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', @@ -118,7 +126,7 @@ describe('IndyLedgerService', () => { poolService.ledgerWritePool.authorAgreement = undefined poolService.ledgerWritePool.config.transactionAuthorAgreement = undefined - ledgerService = new IndyLedgerService(wallet, config, indyIssuerService, poolService) + ledgerService = new IndyLedgerService(config.agentDependencies, config.logger, indyIssuerService, poolService) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore jest.spyOn(ledgerService, 'getTransactionAuthorAgreement').mockResolvedValue({ @@ -134,6 +142,7 @@ describe('IndyLedgerService', () => { } as never) await expect( ledgerService.registerPublicDid( + agentContext, 'BBPoJqRKatdcfLEAFL7exC', 'N8NQHLtCKfPmWMgCSdfa7h', 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', diff --git a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts b/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts index cf72f71cf7..eebbe4332c 100644 --- a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts +++ b/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts @@ -1,7 +1,11 @@ +import type { AgentContext } from '../../../agent' import type { IndyPoolConfig } from '../IndyPool' import type { CachedDidResponse } from '../services/IndyPoolService' -import { getAgentConfig, mockFunction } from '../../../../tests/helpers' +import { Subject } from 'rxjs' + +import { NodeFileSystem } from '../../../../../node/src/NodeFileSystem' +import { agentDependencies, getAgentConfig, getAgentContext, mockFunction } from '../../../../tests/helpers' import { CacheRecord } from '../../../cache' import { CacheRepository } from '../../../cache/CacheRepository' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' @@ -53,12 +57,14 @@ describe('IndyPoolService', () => { const config = getAgentConfig('IndyPoolServiceTest', { indyLedgers: pools, }) + let agentContext: AgentContext let wallet: IndyWallet let poolService: IndyPoolService let cacheRepository: CacheRepository beforeAll(async () => { - wallet = new IndyWallet(config) + wallet = new IndyWallet(config.agentDependencies, config.logger) + agentContext = getAgentContext() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) }) @@ -71,7 +77,15 @@ describe('IndyPoolService', () => { cacheRepository = new CacheRepositoryMock() mockFunction(cacheRepository.findById).mockResolvedValue(null) - poolService = new IndyPoolService(config, cacheRepository) + poolService = new IndyPoolService( + cacheRepository, + agentDependencies, + config.logger, + new Subject(), + new NodeFileSystem() + ) + + poolService.setPools(pools) }) describe('ledgerWritePool', () => { @@ -79,20 +93,18 @@ describe('IndyPoolService', () => { expect(poolService.ledgerWritePool).toBe(poolService.pools[0]) }) - it('should throw a LedgerNotConfiguredError error if no pools are configured on the agent', async () => { - const config = getAgentConfig('IndyPoolServiceTest', { indyLedgers: [] }) - poolService = new IndyPoolService(config, cacheRepository) + it('should throw a LedgerNotConfiguredError error if no pools are configured on the pool service', async () => { + poolService.setPools([]) expect(() => poolService.ledgerWritePool).toThrow(LedgerNotConfiguredError) }) }) describe('getPoolForDid', () => { - it('should throw a LedgerNotConfiguredError error if no pools are configured on the agent', async () => { - const config = getAgentConfig('IndyPoolServiceTest', { indyLedgers: [] }) - poolService = new IndyPoolService(config, cacheRepository) + it('should throw a LedgerNotConfiguredError error if no pools are configured on the pool service', async () => { + poolService.setPools([]) - expect(poolService.getPoolForDid('some-did')).rejects.toThrow(LedgerNotConfiguredError) + expect(poolService.getPoolForDid(agentContext, 'some-did')).rejects.toThrow(LedgerNotConfiguredError) }) it('should throw a LedgerError if all ledger requests throw an error other than NotFoundError', async () => { @@ -103,7 +115,7 @@ describe('IndyPoolService', () => { spy.mockImplementationOnce(() => Promise.reject(new AriesFrameworkError('Something went wrong'))) }) - expect(poolService.getPoolForDid(did)).rejects.toThrowError(LedgerError) + expect(poolService.getPoolForDid(agentContext, did)).rejects.toThrowError(LedgerError) }) it('should throw a LedgerNotFoundError if all pools did not find the did on the ledger', async () => { @@ -116,7 +128,7 @@ describe('IndyPoolService', () => { spy.mockImplementationOnce(responses[index]) }) - expect(poolService.getPoolForDid(did)).rejects.toThrowError(LedgerNotFoundError) + expect(poolService.getPoolForDid(agentContext, did)).rejects.toThrowError(LedgerNotFoundError) }) it('should return the pool if the did was only found on one ledger', async () => { @@ -131,7 +143,7 @@ describe('IndyPoolService', () => { spy.mockImplementationOnce(responses[index]) }) - const { pool } = await poolService.getPoolForDid(did) + const { pool } = await poolService.getPoolForDid(agentContext, did) expect(pool.config.id).toBe('sovrinMain') }) @@ -150,7 +162,7 @@ describe('IndyPoolService', () => { spy.mockImplementationOnce(responses[index]) }) - const { pool } = await poolService.getPoolForDid(did) + const { pool } = await poolService.getPoolForDid(agentContext, did) expect(pool.config.id).toBe('sovrinBuilder') }) @@ -168,7 +180,7 @@ describe('IndyPoolService', () => { spy.mockImplementationOnce(responses[index]) }) - const { pool } = await poolService.getPoolForDid(did) + const { pool } = await poolService.getPoolForDid(agentContext, did) expect(pool.config.id).toBe('indicioMain') }) @@ -186,7 +198,7 @@ describe('IndyPoolService', () => { spy.mockImplementationOnce(responses[index]) }) - const { pool } = await poolService.getPoolForDid(did) + const { pool } = await poolService.getPoolForDid(agentContext, did) expect(pool.config.id).toBe('sovrinMain') }) @@ -205,7 +217,7 @@ describe('IndyPoolService', () => { spy.mockImplementationOnce(responses[index]) }) - const { pool } = await poolService.getPoolForDid(did) + const { pool } = await poolService.getPoolForDid(agentContext, did) expect(pool.config.id).toBe('sovrinBuilder') }) @@ -238,9 +250,7 @@ describe('IndyPoolService', () => { }) ) - poolService = new IndyPoolService(config, cacheRepository) - - const { pool } = await poolService.getPoolForDid(did) + const { pool } = await poolService.getPoolForDid(agentContext, did) expect(pool.config.id).toBe(pool.id) }) @@ -261,17 +271,16 @@ describe('IndyPoolService', () => { const spy = mockFunction(cacheRepository.update).mockResolvedValue() - poolService = new IndyPoolService(config, cacheRepository) poolService.pools.forEach((pool, index) => { const spy = jest.spyOn(pool, 'submitReadRequest') spy.mockImplementationOnce(responses[index]) }) - const { pool } = await poolService.getPoolForDid(did) + const { pool } = await poolService.getPoolForDid(agentContext, did) expect(pool.config.id).toBe('sovrinBuilder') - const cacheRecord = spy.mock.calls[0][0] + const cacheRecord = spy.mock.calls[0][1] expect(cacheRecord.entries.length).toBe(1) expect(cacheRecord.entries[0].key).toBe(did) expect(cacheRecord.entries[0].value).toEqual({ diff --git a/packages/core/src/modules/ledger/services/IndyLedgerService.ts b/packages/core/src/modules/ledger/services/IndyLedgerService.ts index 60363f628e..99142004ef 100644 --- a/packages/core/src/modules/ledger/services/IndyLedgerService.ts +++ b/packages/core/src/modules/ledger/services/IndyLedgerService.ts @@ -1,8 +1,8 @@ -import type { Logger } from '../../../logger' -import type { AcceptanceMechanisms, AuthorAgreement, IndyPool } from '../IndyPool' +import type { AgentContext } from '../../../agent' +import type { AcceptanceMechanisms, AuthorAgreement, IndyPool, IndyPoolConfig } from '../IndyPool' import type { - default as Indy, CredDef, + default as Indy, LedgerReadReplyResponse, LedgerRequest, LedgerWriteReplyResponse, @@ -10,16 +10,18 @@ import type { Schema, } from 'indy-sdk' -import { AgentConfig } from '../../../agent/AgentConfig' +import { AgentDependencies } from '../../../agent/AgentDependencies' +import { InjectionSymbols } from '../../../constants' import { IndySdkError } from '../../../error/IndySdkError' -import { injectable } from '../../../plugins' +import { Logger } from '../../../logger' +import { injectable, inject } from '../../../plugins' import { - didFromSchemaId, didFromCredentialDefinitionId, didFromRevocationRegistryDefinitionId, + didFromSchemaId, } from '../../../utils/did' import { isIndyError } from '../../../utils/indyError' -import { IndyWallet } from '../../../wallet/IndyWallet' +import { assertIndyWallet } from '../../../wallet/util/assertIndyWallet' import { IndyIssuerService } from '../../indy/services/IndyIssuerService' import { LedgerError } from '../error/LedgerError' @@ -27,7 +29,6 @@ import { IndyPoolService } from './IndyPoolService' @injectable() export class IndyLedgerService { - private wallet: IndyWallet private indy: typeof Indy private logger: Logger @@ -35,23 +36,27 @@ export class IndyLedgerService { private indyPoolService: IndyPoolService public constructor( - wallet: IndyWallet, - agentConfig: AgentConfig, + @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, + @inject(InjectionSymbols.Logger) logger: Logger, indyIssuer: IndyIssuerService, indyPoolService: IndyPoolService ) { - this.wallet = wallet - this.indy = agentConfig.agentDependencies.indy - this.logger = agentConfig.logger + this.indy = agentDependencies.indy + this.logger = logger this.indyIssuer = indyIssuer this.indyPoolService = indyPoolService } + public setPools(poolConfigs: IndyPoolConfig[]) { + return this.indyPoolService.setPools(poolConfigs) + } + public async connectToPools() { return this.indyPoolService.connectToPools() } public async registerPublicDid( + agentContext: AgentContext, submitterDid: string, targetDid: string, verkey: string, @@ -65,7 +70,7 @@ export class IndyLedgerService { const request = await this.indy.buildNymRequest(submitterDid, targetDid, verkey, alias, role || null) - const response = await this.submitWriteRequest(pool, request, submitterDid) + const response = await this.submitWriteRequest(agentContext, pool, request, submitterDid) this.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.id}'`, { response, @@ -87,15 +92,15 @@ export class IndyLedgerService { } } - public async getPublicDid(did: string) { + public async getPublicDid(agentContext: AgentContext, did: string) { // Getting the pool for a did also retrieves the DID. We can just use that - const { did: didResponse } = await this.indyPoolService.getPoolForDid(did) + const { did: didResponse } = await this.indyPoolService.getPoolForDid(agentContext, did) return didResponse } - public async getEndpointsForDid(did: string) { - const { pool } = await this.indyPoolService.getPoolForDid(did) + public async getEndpointsForDid(agentContext: AgentContext, did: string) { + const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) try { this.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.id}'`) @@ -123,17 +128,21 @@ export class IndyLedgerService { } } - public async registerSchema(did: string, schemaTemplate: SchemaTemplate): Promise { + public async registerSchema( + agentContext: AgentContext, + did: string, + schemaTemplate: SchemaTemplate + ): Promise { const pool = this.indyPoolService.ledgerWritePool try { this.logger.debug(`Register schema on ledger '${pool.id}' with did '${did}'`, schemaTemplate) const { name, attributes, version } = schemaTemplate - const schema = await this.indyIssuer.createSchema({ originDid: did, name, version, attributes }) + const schema = await this.indyIssuer.createSchema(agentContext, { originDid: did, name, version, attributes }) const request = await this.indy.buildSchemaRequest(did, schema) - const response = await this.submitWriteRequest(pool, request, did) + const response = await this.submitWriteRequest(agentContext, pool, request, did) this.logger.debug(`Registered schema '${schema.id}' on ledger '${pool.id}'`, { response, schema, @@ -153,9 +162,9 @@ export class IndyLedgerService { } } - public async getSchema(schemaId: string) { + public async getSchema(agentContext: AgentContext, schemaId: string) { const did = didFromSchemaId(schemaId) - const { pool } = await this.indyPoolService.getPoolForDid(did) + const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) try { this.logger.debug(`Getting schema '${schemaId}' from ledger '${pool.id}'`) @@ -186,6 +195,7 @@ export class IndyLedgerService { } public async registerCredentialDefinition( + agentContext: AgentContext, did: string, credentialDefinitionTemplate: CredentialDefinitionTemplate ): Promise { @@ -198,7 +208,7 @@ export class IndyLedgerService { ) const { schema, tag, signatureType, supportRevocation } = credentialDefinitionTemplate - const credentialDefinition = await this.indyIssuer.createCredentialDefinition({ + const credentialDefinition = await this.indyIssuer.createCredentialDefinition(agentContext, { issuerDid: did, schema, tag, @@ -208,7 +218,7 @@ export class IndyLedgerService { const request = await this.indy.buildCredDefRequest(did, credentialDefinition) - const response = await this.submitWriteRequest(pool, request, did) + const response = await this.submitWriteRequest(agentContext, pool, request, did) this.logger.debug(`Registered credential definition '${credentialDefinition.id}' on ledger '${pool.id}'`, { response, @@ -230,9 +240,9 @@ export class IndyLedgerService { } } - public async getCredentialDefinition(credentialDefinitionId: string) { + public async getCredentialDefinition(agentContext: AgentContext, credentialDefinitionId: string) { const did = didFromCredentialDefinitionId(credentialDefinitionId) - const { pool } = await this.indyPoolService.getPoolForDid(did) + const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) this.logger.debug(`Using ledger '${pool.id}' to retrieve credential definition '${credentialDefinitionId}'`) @@ -266,10 +276,11 @@ export class IndyLedgerService { } public async getRevocationRegistryDefinition( + agentContext: AgentContext, revocationRegistryDefinitionId: string ): Promise { const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) - const { pool } = await this.indyPoolService.getPoolForDid(did) + const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) this.logger.debug( `Using ledger '${pool.id}' to retrieve revocation registry definition '${revocationRegistryDefinitionId}'` @@ -313,15 +324,16 @@ export class IndyLedgerService { } } - //Retrieves the accumulated state of a revocation registry by id given a revocation interval from & to (used primarily for proof creation) + // Retrieves the accumulated state of a revocation registry by id given a revocation interval from & to (used primarily for proof creation) public async getRevocationRegistryDelta( + agentContext: AgentContext, revocationRegistryDefinitionId: string, to: number = new Date().getTime(), from = 0 ): Promise { //TODO - implement a cache const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) - const { pool } = await this.indyPoolService.getPoolForDid(did) + const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) this.logger.debug( `Using ledger '${pool.id}' to retrieve revocation registry delta with revocation registry definition id: '${revocationRegistryDefinitionId}'`, @@ -369,14 +381,15 @@ export class IndyLedgerService { } } - //Retrieves the accumulated state of a revocation registry by id given a timestamp (used primarily for verification) + // Retrieves the accumulated state of a revocation registry by id given a timestamp (used primarily for verification) public async getRevocationRegistry( + agentContext: AgentContext, revocationRegistryDefinitionId: string, timestamp: number ): Promise { //TODO - implement a cache const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) - const { pool } = await this.indyPoolService.getPoolForDid(did) + const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) this.logger.debug( `Using ledger '${pool.id}' to retrieve revocation registry accumulated state with revocation registry definition id: '${revocationRegistryDefinitionId}'`, @@ -417,13 +430,14 @@ export class IndyLedgerService { } private async submitWriteRequest( + agentContext: AgentContext, pool: IndyPool, request: LedgerRequest, signDid: string ): Promise { try { const requestWithTaa = await this.appendTaa(pool, request) - const signedRequestWithTaa = await this.signRequest(signDid, requestWithTaa) + const signedRequestWithTaa = await this.signRequest(agentContext, signDid, requestWithTaa) const response = await pool.submitWriteRequest(signedRequestWithTaa) @@ -443,9 +457,11 @@ export class IndyLedgerService { } } - private async signRequest(did: string, request: LedgerRequest): Promise { + private async signRequest(agentContext: AgentContext, did: string, request: LedgerRequest): Promise { + assertIndyWallet(agentContext.wallet) + try { - return this.indy.signRequest(this.wallet.handle, did, request) + return this.indy.signRequest(agentContext.wallet.handle, did, request) } catch (error) { throw isIndyError(error) ? new IndySdkError(error) : error } diff --git a/packages/core/src/modules/ledger/services/IndyPoolService.ts b/packages/core/src/modules/ledger/services/IndyPoolService.ts index bf0b461176..f45c338f2b 100644 --- a/packages/core/src/modules/ledger/services/IndyPoolService.ts +++ b/packages/core/src/modules/ledger/services/IndyPoolService.ts @@ -1,10 +1,16 @@ -import type { Logger } from '../../../logger/Logger' +import type { AgentContext } from '../../../agent' +import type { IndyPoolConfig } from '../IndyPool' import type * as Indy from 'indy-sdk' -import { AgentConfig } from '../../../agent/AgentConfig' +import { Subject } from 'rxjs' + +import { AgentDependencies } from '../../../agent/AgentDependencies' import { CacheRepository, PersistedLruCache } from '../../../cache' +import { InjectionSymbols } from '../../../constants' import { IndySdkError } from '../../../error/IndySdkError' -import { injectable } from '../../../plugins' +import { Logger } from '../../../logger/Logger' +import { injectable, inject } from '../../../plugins' +import { FileSystem } from '../../../storage/FileSystem' import { isSelfCertifiedDid } from '../../../utils/did' import { isIndyError } from '../../../utils/indyError' import { allSettled, onlyFulfilled, onlyRejected } from '../../../utils/promises' @@ -21,19 +27,36 @@ export interface CachedDidResponse { } @injectable() export class IndyPoolService { - public readonly pools: IndyPool[] + public pools: IndyPool[] = [] private logger: Logger private indy: typeof Indy + private agentDependencies: AgentDependencies + private stop$: Subject + private fileSystem: FileSystem private didCache: PersistedLruCache - public constructor(agentConfig: AgentConfig, cacheRepository: CacheRepository) { - this.pools = agentConfig.indyLedgers.map((poolConfig) => new IndyPool(agentConfig, poolConfig)) - this.logger = agentConfig.logger - this.indy = agentConfig.agentDependencies.indy + public constructor( + cacheRepository: CacheRepository, + @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, + @inject(InjectionSymbols.Logger) logger: Logger, + @inject(InjectionSymbols.Stop$) stop$: Subject, + @inject(InjectionSymbols.FileSystem) fileSystem: FileSystem + ) { + this.logger = logger + this.indy = agentDependencies.indy + this.agentDependencies = agentDependencies + this.fileSystem = fileSystem + this.stop$ = stop$ this.didCache = new PersistedLruCache(DID_POOL_CACHE_ID, DID_POOL_CACHE_LIMIT, cacheRepository) } + public setPools(poolConfigs: IndyPoolConfig[]) { + this.pools = poolConfigs.map( + (poolConfig) => new IndyPool(poolConfig, this.agentDependencies, this.logger, this.stop$, this.fileSystem) + ) + } + /** * Create connections to all ledger pools */ @@ -67,7 +90,10 @@ export class IndyPoolService { * Get the most appropriate pool for the given did. The algorithm is based on the approach as described in this document: * https://docs.google.com/document/d/109C_eMsuZnTnYe2OAd02jAts1vC4axwEKIq7_4dnNVA/edit */ - public async getPoolForDid(did: string): Promise<{ pool: IndyPool; did: Indy.GetNymResponse }> { + public async getPoolForDid( + agentContext: AgentContext, + did: string + ): Promise<{ pool: IndyPool; did: Indy.GetNymResponse }> { const pools = this.pools if (pools.length === 0) { @@ -76,7 +102,7 @@ export class IndyPoolService { ) } - const cachedNymResponse = await this.didCache.get(did) + const cachedNymResponse = await this.didCache.get(agentContext, did) const pool = this.pools.find((pool) => pool.id === cachedNymResponse?.poolId) // If we have the nym response with associated pool in the cache, we'll use that @@ -123,7 +149,7 @@ export class IndyPoolService { value = productionOrNonProduction[0].value } - await this.didCache.set(did, { + await this.didCache.set(agentContext, did, { nymResponse: value.did, poolId: value.pool.id, }) diff --git a/packages/core/src/modules/oob/OutOfBandModule.ts b/packages/core/src/modules/oob/OutOfBandModule.ts index 18796d7876..0a5ac5b55b 100644 --- a/packages/core/src/modules/oob/OutOfBandModule.ts +++ b/packages/core/src/modules/oob/OutOfBandModule.ts @@ -2,7 +2,6 @@ import type { AgentMessage } from '../../agent/AgentMessage' import type { AgentMessageReceivedEvent } from '../../agent/Events' import type { Key } from '../../crypto' import type { Attachment } from '../../decorators/attachment/Attachment' -import type { Logger } from '../../logger' import type { ConnectionInvitationMessage, ConnectionRecord, Routing } from '../../modules/connections' import type { DependencyManager } from '../../plugins' import type { PlaintextMessage } from '../../types' @@ -10,16 +9,18 @@ import type { HandshakeReusedEvent } from './domain/OutOfBandEvents' import { catchError, EmptyError, first, firstValueFrom, map, of, timeout } from 'rxjs' -import { AgentConfig } from '../../agent/AgentConfig' +import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { EventEmitter } from '../../agent/EventEmitter' import { AgentEventTypes } from '../../agent/Events' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' +import { InjectionSymbols } from '../../constants' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' +import { Logger } from '../../logger' import { ConnectionsModule, DidExchangeState, HandshakeProtocol } from '../../modules/connections' -import { injectable, module } from '../../plugins' +import { inject, injectable, module } from '../../plugins' import { DidCommMessageRepository, DidCommMessageRole } from '../../storage' import { JsonEncoder, JsonTransformer } from '../../utils' import { parseMessageType, supportsIncomingMessageType } from '../../utils/messageType' @@ -87,22 +88,23 @@ export class OutOfBandModule { private dispatcher: Dispatcher private messageSender: MessageSender private eventEmitter: EventEmitter - private agentConfig: AgentConfig + private agentContext: AgentContext private logger: Logger public constructor( dispatcher: Dispatcher, - agentConfig: AgentConfig, outOfBandService: OutOfBandService, routingService: RoutingService, connectionsModule: ConnectionsModule, didCommMessageRepository: DidCommMessageRepository, messageSender: MessageSender, - eventEmitter: EventEmitter + eventEmitter: EventEmitter, + @inject(InjectionSymbols.Logger) logger: Logger, + agentContext: AgentContext ) { this.dispatcher = dispatcher - this.agentConfig = agentConfig - this.logger = agentConfig.logger + this.agentContext = agentContext + this.logger = logger this.outOfBandService = outOfBandService this.routingService = routingService this.connectionsModule = connectionsModule @@ -131,11 +133,11 @@ export class OutOfBandModule { const multiUseInvitation = config.multiUseInvitation ?? false const handshake = config.handshake ?? true const customHandshakeProtocols = config.handshakeProtocols - const autoAcceptConnection = config.autoAcceptConnection ?? this.agentConfig.autoAcceptConnections + const autoAcceptConnection = config.autoAcceptConnection ?? this.agentContext.config.autoAcceptConnections // We don't want to treat an empty array as messages being provided const messages = config.messages && config.messages.length > 0 ? config.messages : undefined - const label = config.label ?? this.agentConfig.label - const imageUrl = config.imageUrl ?? this.agentConfig.connectionImageUrl + const label = config.label ?? this.agentContext.config.label + const imageUrl = config.imageUrl ?? this.agentContext.config.connectionImageUrl const appendedAttachments = config.appendedAttachments && config.appendedAttachments.length > 0 ? config.appendedAttachments : undefined @@ -167,7 +169,7 @@ export class OutOfBandModule { } } - const routing = config.routing ?? (await this.routingService.getRouting({})) + const routing = config.routing ?? (await this.routingService.getRouting(this.agentContext, {})) const services = routing.endpoints.map((endpoint, index) => { return new OutOfBandDidCommService({ @@ -209,8 +211,8 @@ export class OutOfBandModule { autoAcceptConnection, }) - await this.outOfBandService.save(outOfBandRecord) - this.outOfBandService.emitStateChangedEvent(outOfBandRecord, null) + await this.outOfBandService.save(this.agentContext, outOfBandRecord) + this.outOfBandService.emitStateChangedEvent(this.agentContext, outOfBandRecord, null) return outOfBandRecord } @@ -239,7 +241,7 @@ export class OutOfBandModule { domain: string }): Promise<{ message: Message; invitationUrl: string }> { // Create keys (and optionally register them at the mediator) - const routing = await this.routingService.getRouting() + const routing = await this.routingService.getRouting(this.agentContext) // Set the service on the message config.message.service = new ServiceDecorator({ @@ -250,7 +252,7 @@ export class OutOfBandModule { // We need to update the message with the new service, so we can // retrieve it from storage later on. - await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + await this.didCommMessageRepository.saveOrUpdateAgentMessage(this.agentContext, { agentMessage: config.message, associatedRecordId: config.recordId, role: DidCommMessageRole.Sender, @@ -296,7 +298,7 @@ export class OutOfBandModule { * @returns OutOfBandInvitation */ public async parseInvitationShortUrl(invitation: string): Promise { - return await parseInvitationShortUrl(invitation, this.agentConfig.agentDependencies) + return await parseInvitationShortUrl(invitation, this.agentContext.config.agentDependencies) } /** @@ -330,9 +332,9 @@ export class OutOfBandModule { const autoAcceptInvitation = config.autoAcceptInvitation ?? true const autoAcceptConnection = config.autoAcceptConnection ?? true const reuseConnection = config.reuseConnection ?? false - const label = config.label ?? this.agentConfig.label + const label = config.label ?? this.agentContext.config.label const alias = config.alias - const imageUrl = config.imageUrl ?? this.agentConfig.connectionImageUrl + const imageUrl = config.imageUrl ?? this.agentContext.config.connectionImageUrl const messages = outOfBandInvitation.getRequests() @@ -356,8 +358,8 @@ export class OutOfBandModule { outOfBandInvitation: outOfBandInvitation, autoAcceptConnection, }) - await this.outOfBandService.save(outOfBandRecord) - this.outOfBandService.emitStateChangedEvent(outOfBandRecord, null) + await this.outOfBandService.save(this.agentContext, outOfBandRecord) + this.outOfBandService.emitStateChangedEvent(this.agentContext, outOfBandRecord, null) if (autoAcceptInvitation) { return await this.acceptInvitation(outOfBandRecord.id, { @@ -399,7 +401,7 @@ export class OutOfBandModule { routing?: Routing } ) { - const outOfBandRecord = await this.outOfBandService.getById(outOfBandId) + const outOfBandRecord = await this.outOfBandService.getById(this.agentContext, outOfBandId) const { outOfBandInvitation } = outOfBandRecord const { label, alias, imageUrl, autoAcceptConnection, reuseConnection, routing } = config @@ -408,7 +410,7 @@ export class OutOfBandModule { const existingConnection = await this.findExistingConnection(services) - await this.outOfBandService.updateState(outOfBandRecord, OutOfBandState.PrepareResponse) + await this.outOfBandService.updateState(this.agentContext, outOfBandRecord, OutOfBandState.PrepareResponse) if (handshakeProtocols) { this.logger.debug('Out of band message contains handshake protocols.') @@ -490,11 +492,11 @@ export class OutOfBandModule { } public async findByRecipientKey(recipientKey: Key) { - return this.outOfBandService.findByRecipientKey(recipientKey) + return this.outOfBandService.findByRecipientKey(this.agentContext, recipientKey) } public async findByInvitationId(invitationId: string) { - return this.outOfBandService.findByInvitationId(invitationId) + return this.outOfBandService.findByInvitationId(this.agentContext, invitationId) } /** @@ -503,7 +505,7 @@ export class OutOfBandModule { * @returns List containing all out of band records */ public getAll() { - return this.outOfBandService.getAll() + return this.outOfBandService.getAll(this.agentContext) } /** @@ -515,7 +517,7 @@ export class OutOfBandModule { * */ public getById(outOfBandId: string): Promise { - return this.outOfBandService.getById(outOfBandId) + return this.outOfBandService.getById(this.agentContext, outOfBandId) } /** @@ -525,7 +527,7 @@ export class OutOfBandModule { * @returns The out of band record or null if not found */ public findById(outOfBandId: string): Promise { - return this.outOfBandService.findById(outOfBandId) + return this.outOfBandService.findById(this.agentContext, outOfBandId) } /** @@ -534,7 +536,7 @@ export class OutOfBandModule { * @param outOfBandId the out of band record id */ public async deleteById(outOfBandId: string) { - return this.outOfBandService.deleteById(outOfBandId) + return this.outOfBandService.deleteById(this.agentContext, outOfBandId) } private assertHandshakeProtocols(handshakeProtocols: HandshakeProtocol[]) { @@ -618,7 +620,7 @@ export class OutOfBandModule { this.logger.debug(`Message with type ${plaintextMessage['@type']} can be processed.`) - this.eventEmitter.emit({ + this.eventEmitter.emit(this.agentContext, { type: AgentEventTypes.AgentMessageReceived, payload: { message: plaintextMessage, @@ -660,7 +662,7 @@ export class OutOfBandModule { }) plaintextMessage['~service'] = JsonTransformer.toJSON(serviceDecorator) - this.eventEmitter.emit({ + this.eventEmitter.emit(this.agentContext, { type: AgentEventTypes.AgentMessageReceived, payload: { message: plaintextMessage, @@ -669,7 +671,11 @@ export class OutOfBandModule { } private async handleHandshakeReuse(outOfBandRecord: OutOfBandRecord, connectionRecord: ConnectionRecord) { - const reuseMessage = await this.outOfBandService.createHandShakeReuse(outOfBandRecord, connectionRecord) + const reuseMessage = await this.outOfBandService.createHandShakeReuse( + this.agentContext, + outOfBandRecord, + connectionRecord + ) const reuseAcceptedEventPromise = firstValueFrom( this.eventEmitter.observable(OutOfBandEventTypes.HandshakeReused).pipe( @@ -690,7 +696,7 @@ export class OutOfBandModule { ) const outbound = createOutboundMessage(connectionRecord, reuseMessage) - await this.messageSender.sendMessage(outbound) + await this.messageSender.sendMessage(this.agentContext, outbound) return reuseAcceptedEventPromise } diff --git a/packages/core/src/modules/oob/OutOfBandService.ts b/packages/core/src/modules/oob/OutOfBandService.ts index ce64b5513d..0c78517f99 100644 --- a/packages/core/src/modules/oob/OutOfBandService.ts +++ b/packages/core/src/modules/oob/OutOfBandService.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../../agent' import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' import type { Key } from '../../crypto' import type { ConnectionRecord } from '../connections' @@ -34,7 +35,7 @@ export class OutOfBandService { throw new AriesFrameworkError('handshake-reuse message must have a parent thread id') } - const outOfBandRecord = await this.findByInvitationId(parentThreadId) + const outOfBandRecord = await this.findByInvitationId(messageContext.agentContext, parentThreadId) if (!outOfBandRecord) { throw new AriesFrameworkError('No out of band record found for handshake-reuse message') } @@ -49,7 +50,7 @@ export class OutOfBandService { } const reusedConnection = messageContext.assertReadyConnection() - this.eventEmitter.emit({ + this.eventEmitter.emit(messageContext.agentContext, { type: OutOfBandEventTypes.HandshakeReused, payload: { reuseThreadId: reuseMessage.threadId, @@ -60,7 +61,7 @@ export class OutOfBandService { // If the out of band record is not reusable we can set the state to done if (!outOfBandRecord.reusable) { - await this.updateState(outOfBandRecord, OutOfBandState.Done) + await this.updateState(messageContext.agentContext, outOfBandRecord, OutOfBandState.Done) } const reuseAcceptedMessage = new HandshakeReuseAcceptedMessage({ @@ -79,7 +80,7 @@ export class OutOfBandService { throw new AriesFrameworkError('handshake-reuse-accepted message must have a parent thread id') } - const outOfBandRecord = await this.findByInvitationId(parentThreadId) + const outOfBandRecord = await this.findByInvitationId(messageContext.agentContext, parentThreadId) if (!outOfBandRecord) { throw new AriesFrameworkError('No out of band record found for handshake-reuse-accepted message') } @@ -100,7 +101,7 @@ export class OutOfBandService { throw new AriesFrameworkError('handshake-reuse-accepted is not in response to a handshake-reuse message.') } - this.eventEmitter.emit({ + this.eventEmitter.emit(messageContext.agentContext, { type: OutOfBandEventTypes.HandshakeReused, payload: { reuseThreadId: reuseAcceptedMessage.threadId, @@ -110,35 +111,43 @@ export class OutOfBandService { }) // receiver role is never reusable, so we can set the state to done - await this.updateState(outOfBandRecord, OutOfBandState.Done) + await this.updateState(messageContext.agentContext, outOfBandRecord, OutOfBandState.Done) } - public async createHandShakeReuse(outOfBandRecord: OutOfBandRecord, connectionRecord: ConnectionRecord) { + public async createHandShakeReuse( + agentContext: AgentContext, + outOfBandRecord: OutOfBandRecord, + connectionRecord: ConnectionRecord + ) { const reuseMessage = new HandshakeReuseMessage({ parentThreadId: outOfBandRecord.outOfBandInvitation.id }) // Store the reuse connection id outOfBandRecord.reuseConnectionId = connectionRecord.id - await this.outOfBandRepository.update(outOfBandRecord) + await this.outOfBandRepository.update(agentContext, outOfBandRecord) return reuseMessage } - public async save(outOfBandRecord: OutOfBandRecord) { - return this.outOfBandRepository.save(outOfBandRecord) + public async save(agentContext: AgentContext, outOfBandRecord: OutOfBandRecord) { + return this.outOfBandRepository.save(agentContext, outOfBandRecord) } - public async updateState(outOfBandRecord: OutOfBandRecord, newState: OutOfBandState) { + public async updateState(agentContext: AgentContext, outOfBandRecord: OutOfBandRecord, newState: OutOfBandState) { const previousState = outOfBandRecord.state outOfBandRecord.state = newState - await this.outOfBandRepository.update(outOfBandRecord) + await this.outOfBandRepository.update(agentContext, outOfBandRecord) - this.emitStateChangedEvent(outOfBandRecord, previousState) + this.emitStateChangedEvent(agentContext, outOfBandRecord, previousState) } - public emitStateChangedEvent(outOfBandRecord: OutOfBandRecord, previousState: OutOfBandState | null) { + public emitStateChangedEvent( + agentContext: AgentContext, + outOfBandRecord: OutOfBandRecord, + previousState: OutOfBandState | null + ) { const clonedOutOfBandRecord = JsonTransformer.clone(outOfBandRecord) - this.eventEmitter.emit({ + this.eventEmitter.emit(agentContext, { type: OutOfBandEventTypes.OutOfBandStateChanged, payload: { outOfBandRecord: clonedOutOfBandRecord, @@ -147,28 +156,30 @@ export class OutOfBandService { }) } - public async findById(outOfBandRecordId: string) { - return this.outOfBandRepository.findById(outOfBandRecordId) + public async findById(agentContext: AgentContext, outOfBandRecordId: string) { + return this.outOfBandRepository.findById(agentContext, outOfBandRecordId) } - public async getById(outOfBandRecordId: string) { - return this.outOfBandRepository.getById(outOfBandRecordId) + public async getById(agentContext: AgentContext, outOfBandRecordId: string) { + return this.outOfBandRepository.getById(agentContext, outOfBandRecordId) } - public async findByInvitationId(invitationId: string) { - return this.outOfBandRepository.findSingleByQuery({ invitationId }) + public async findByInvitationId(agentContext: AgentContext, invitationId: string) { + return this.outOfBandRepository.findSingleByQuery(agentContext, { invitationId }) } - public async findByRecipientKey(recipientKey: Key) { - return this.outOfBandRepository.findSingleByQuery({ recipientKeyFingerprints: [recipientKey.fingerprint] }) + public async findByRecipientKey(agentContext: AgentContext, recipientKey: Key) { + return this.outOfBandRepository.findSingleByQuery(agentContext, { + recipientKeyFingerprints: [recipientKey.fingerprint], + }) } - public async getAll() { - return this.outOfBandRepository.getAll() + public async getAll(agentContext: AgentContext) { + return this.outOfBandRepository.getAll(agentContext) } - public async deleteById(outOfBandId: string) { - const outOfBandRecord = await this.getById(outOfBandId) - return this.outOfBandRepository.delete(outOfBandRecord) + public async deleteById(agentContext: AgentContext, outOfBandId: string) { + const outOfBandRecord = await this.getById(agentContext, outOfBandId) + return this.outOfBandRepository.delete(agentContext, outOfBandRecord) } } diff --git a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts index dd1c98098b..d5c1e358ad 100644 --- a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts +++ b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts @@ -1,6 +1,16 @@ +import type { AgentContext } from '../../../agent' import type { Wallet } from '../../../wallet/Wallet' -import { getAgentConfig, getMockConnection, getMockOutOfBand, mockFunction } from '../../../../tests/helpers' +import { Subject } from 'rxjs' + +import { + agentDependencies, + getAgentConfig, + getAgentContext, + getMockConnection, + getMockOutOfBand, + mockFunction, +} from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import { KeyType, Key } from '../../../crypto' @@ -26,9 +36,11 @@ describe('OutOfBandService', () => { let outOfBandRepository: OutOfBandRepository let outOfBandService: OutOfBandService let eventEmitter: EventEmitter + let agentContext: AgentContext beforeAll(async () => { - wallet = new IndyWallet(agentConfig) + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger) + agentContext = getAgentContext() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) }) @@ -38,7 +50,7 @@ describe('OutOfBandService', () => { }) beforeEach(async () => { - eventEmitter = new EventEmitter(agentConfig) + eventEmitter = new EventEmitter(agentDependencies, new Subject()) outOfBandRepository = new OutOfBandRepositoryMock() outOfBandService = new OutOfBandService(outOfBandRepository, eventEmitter) }) @@ -54,6 +66,7 @@ describe('OutOfBandService', () => { }) const messageContext = new InboundMessageContext(reuseMessage, { + agentContext, senderKey: key, recipientKey: key, }) @@ -69,6 +82,7 @@ describe('OutOfBandService', () => { }) const messageContext = new InboundMessageContext(reuseMessage, { + agentContext, senderKey: key, recipientKey: key, }) @@ -84,6 +98,7 @@ describe('OutOfBandService', () => { }) const messageContext = new InboundMessageContext(reuseMessage, { + agentContext, senderKey: key, recipientKey: key, }) @@ -112,6 +127,7 @@ describe('OutOfBandService', () => { }) const messageContext = new InboundMessageContext(reuseMessage, { + agentContext, senderKey: key, recipientKey: key, }) @@ -134,6 +150,7 @@ describe('OutOfBandService', () => { }) const messageContext = new InboundMessageContext(reuseMessage, { + agentContext, senderKey: key, recipientKey: key, }) @@ -158,6 +175,7 @@ describe('OutOfBandService', () => { const connection = getMockConnection({ state: DidExchangeState.Completed }) const messageContext = new InboundMessageContext(reuseMessage, { + agentContext, senderKey: key, recipientKey: key, connection, @@ -192,6 +210,7 @@ describe('OutOfBandService', () => { }) const messageContext = new InboundMessageContext(reuseMessage, { + agentContext, senderKey: key, recipientKey: key, connection: getMockConnection({ state: DidExchangeState.Completed }), @@ -213,7 +232,7 @@ describe('OutOfBandService', () => { // Non-reusable should update state mockOob.reusable = false await outOfBandService.processHandshakeReuse(messageContext) - expect(updateStateSpy).toHaveBeenCalledWith(mockOob, OutOfBandState.Done) + expect(updateStateSpy).toHaveBeenCalledWith(agentContext, mockOob, OutOfBandState.Done) }) it('returns a handshake-reuse-accepted message', async () => { @@ -222,6 +241,7 @@ describe('OutOfBandService', () => { }) const messageContext = new InboundMessageContext(reuseMessage, { + agentContext, senderKey: key, recipientKey: key, connection: getMockConnection({ state: DidExchangeState.Completed }), @@ -255,6 +275,7 @@ describe('OutOfBandService', () => { }) const messageContext = new InboundMessageContext(reuseAcceptedMessage, { + agentContext, senderKey: key, recipientKey: key, }) @@ -271,6 +292,7 @@ describe('OutOfBandService', () => { }) const messageContext = new InboundMessageContext(reuseAcceptedMessage, { + agentContext, senderKey: key, recipientKey: key, }) @@ -287,6 +309,7 @@ describe('OutOfBandService', () => { }) const messageContext = new InboundMessageContext(reuseAcceptedMessage, { + agentContext, senderKey: key, recipientKey: key, }) @@ -316,6 +339,7 @@ describe('OutOfBandService', () => { }) const messageContext = new InboundMessageContext(reuseAcceptedMessage, { + agentContext, senderKey: key, recipientKey: key, }) @@ -338,6 +362,7 @@ describe('OutOfBandService', () => { }) const messageContext = new InboundMessageContext(reuseAcceptedMessage, { + agentContext, senderKey: key, recipientKey: key, connection: getMockConnection({ state: DidExchangeState.Completed, id: 'connectionId' }), @@ -365,6 +390,7 @@ describe('OutOfBandService', () => { const connection = getMockConnection({ state: DidExchangeState.Completed, id: 'connectionId' }) const messageContext = new InboundMessageContext(reuseAcceptedMessage, { + agentContext, senderKey: key, recipientKey: key, connection, @@ -401,6 +427,7 @@ describe('OutOfBandService', () => { }) const messageContext = new InboundMessageContext(reuseAcceptedMessage, { + agentContext, senderKey: key, recipientKey: key, connection: getMockConnection({ state: DidExchangeState.Completed, id: 'connectionId' }), @@ -417,7 +444,7 @@ describe('OutOfBandService', () => { const updateStateSpy = jest.spyOn(outOfBandService, 'updateState') await outOfBandService.processHandshakeReuseAccepted(messageContext) - expect(updateStateSpy).toHaveBeenCalledWith(mockOob, OutOfBandState.Done) + expect(updateStateSpy).toHaveBeenCalledWith(agentContext, mockOob, OutOfBandState.Done) }) }) @@ -427,7 +454,7 @@ describe('OutOfBandService', () => { state: OutOfBandState.Initial, }) - await outOfBandService.updateState(mockOob, OutOfBandState.Done) + await outOfBandService.updateState(agentContext, mockOob, OutOfBandState.Done) expect(mockOob.state).toEqual(OutOfBandState.Done) }) @@ -437,9 +464,9 @@ describe('OutOfBandService', () => { state: OutOfBandState.Initial, }) - await outOfBandService.updateState(mockOob, OutOfBandState.Done) + await outOfBandService.updateState(agentContext, mockOob, OutOfBandState.Done) - expect(outOfBandRepository.update).toHaveBeenCalledWith(mockOob) + expect(outOfBandRepository.update).toHaveBeenCalledWith(agentContext, mockOob) }) test('emits an OutOfBandStateChangedEvent', async () => { @@ -450,7 +477,7 @@ describe('OutOfBandService', () => { }) eventEmitter.on(OutOfBandEventTypes.OutOfBandStateChanged, stateChangedListener) - await outOfBandService.updateState(mockOob, OutOfBandState.Done) + await outOfBandService.updateState(agentContext, mockOob, OutOfBandState.Done) eventEmitter.off(OutOfBandEventTypes.OutOfBandStateChanged, stateChangedListener) expect(stateChangedListener).toHaveBeenCalledTimes(1) @@ -470,8 +497,8 @@ describe('OutOfBandService', () => { it('getById should return value from outOfBandRepository.getById', async () => { const expected = getMockOutOfBand() mockFunction(outOfBandRepository.getById).mockReturnValue(Promise.resolve(expected)) - const result = await outOfBandService.getById(expected.id) - expect(outOfBandRepository.getById).toBeCalledWith(expected.id) + const result = await outOfBandService.getById(agentContext, expected.id) + expect(outOfBandRepository.getById).toBeCalledWith(agentContext, expected.id) expect(result).toBe(expected) }) @@ -479,8 +506,8 @@ describe('OutOfBandService', () => { it('findById should return value from outOfBandRepository.findById', async () => { const expected = getMockOutOfBand() mockFunction(outOfBandRepository.findById).mockReturnValue(Promise.resolve(expected)) - const result = await outOfBandService.findById(expected.id) - expect(outOfBandRepository.findById).toBeCalledWith(expected.id) + const result = await outOfBandService.findById(agentContext, expected.id) + expect(outOfBandRepository.findById).toBeCalledWith(agentContext, expected.id) expect(result).toBe(expected) }) @@ -489,8 +516,8 @@ describe('OutOfBandService', () => { const expected = [getMockOutOfBand(), getMockOutOfBand()] mockFunction(outOfBandRepository.getAll).mockReturnValue(Promise.resolve(expected)) - const result = await outOfBandService.getAll() - expect(outOfBandRepository.getAll).toBeCalledWith() + const result = await outOfBandService.getAll(agentContext) + expect(outOfBandRepository.getAll).toBeCalledWith(agentContext) expect(result).toEqual(expect.arrayContaining(expected)) }) diff --git a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts index d839edb646..7e95e73682 100644 --- a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts +++ b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts @@ -1,6 +1,6 @@ +import type { AgentContext } from '../../agent/AgentContext' import type { ProofRecord } from './repository' -import { AgentConfig } from '../../agent/AgentConfig' import { injectable } from '../../plugins' import { AutoAcceptProof } from './ProofAutoAcceptType' @@ -11,12 +11,6 @@ import { AutoAcceptProof } from './ProofAutoAcceptType' */ @injectable() export class ProofResponseCoordinator { - private agentConfig: AgentConfig - - public constructor(agentConfig: AgentConfig) { - this.agentConfig = agentConfig - } - /** * Returns the proof auto accept config based on priority: * - The record config takes first priority @@ -33,10 +27,10 @@ export class ProofResponseCoordinator { /** * Checks whether it should automatically respond to a proposal */ - public shouldAutoRespondToProposal(proofRecord: ProofRecord) { + public shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, - this.agentConfig.autoAcceptProofs + agentContext.config.autoAcceptProofs ) if (autoAccept === AutoAcceptProof.Always) { @@ -48,10 +42,10 @@ export class ProofResponseCoordinator { /** * Checks whether it should automatically respond to a request */ - public shouldAutoRespondToRequest(proofRecord: ProofRecord) { + public shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, - this.agentConfig.autoAcceptProofs + agentContext.config.autoAcceptProofs ) if ( @@ -67,10 +61,10 @@ export class ProofResponseCoordinator { /** * Checks whether it should automatically respond to a presentation of proof */ - public shouldAutoRespondToPresentation(proofRecord: ProofRecord) { + public shouldAutoRespondToPresentation(agentContext: AgentContext, proofRecord: ProofRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, - this.agentConfig.autoAcceptProofs + agentContext.config.autoAcceptProofs ) if ( diff --git a/packages/core/src/modules/proofs/ProofsModule.ts b/packages/core/src/modules/proofs/ProofsModule.ts index 613a4928ca..9594c44108 100644 --- a/packages/core/src/modules/proofs/ProofsModule.ts +++ b/packages/core/src/modules/proofs/ProofsModule.ts @@ -5,24 +5,26 @@ import type { RequestedCredentials, RetrievedCredentials } from './models' import type { ProofRequestOptions } from './models/ProofRequest' import type { ProofRecord } from './repository/ProofRecord' -import { AgentConfig } from '../../agent/AgentConfig' +import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' +import { InjectionSymbols } from '../../constants' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' -import { injectable, module } from '../../plugins' +import { Logger } from '../../logger' +import { inject, injectable, module } from '../../plugins' import { ConnectionService } from '../connections/services/ConnectionService' import { RoutingService } from '../routing/services/RoutingService' import { ProofResponseCoordinator } from './ProofResponseCoordinator' import { PresentationProblemReportReason } from './errors' import { - ProposePresentationHandler, - RequestPresentationHandler, PresentationAckHandler, PresentationHandler, PresentationProblemReportHandler, + ProposePresentationHandler, + RequestPresentationHandler, } from './handlers' import { PresentationProblemReportMessage } from './messages/PresentationProblemReportMessage' import { ProofRequest } from './models/ProofRequest' @@ -36,24 +38,27 @@ export class ProofsModule { private connectionService: ConnectionService private messageSender: MessageSender private routingService: RoutingService - private agentConfig: AgentConfig + private agentContext: AgentContext private proofResponseCoordinator: ProofResponseCoordinator + private logger: Logger public constructor( dispatcher: Dispatcher, proofService: ProofService, connectionService: ConnectionService, routingService: RoutingService, - agentConfig: AgentConfig, + agentContext: AgentContext, messageSender: MessageSender, - proofResponseCoordinator: ProofResponseCoordinator + proofResponseCoordinator: ProofResponseCoordinator, + @inject(InjectionSymbols.Logger) logger: Logger ) { this.proofService = proofService this.connectionService = connectionService this.messageSender = messageSender this.routingService = routingService - this.agentConfig = agentConfig + this.agentContext = agentContext this.proofResponseCoordinator = proofResponseCoordinator + this.logger = logger this.registerHandlers(dispatcher) } @@ -76,12 +81,17 @@ export class ProofsModule { parentThreadId?: string } ): Promise { - const connection = await this.connectionService.getById(connectionId) + const connection = await this.connectionService.getById(this.agentContext, connectionId) - const { message, proofRecord } = await this.proofService.createProposal(connection, presentationProposal, config) + const { message, proofRecord } = await this.proofService.createProposal( + this.agentContext, + connection, + presentationProposal, + config + ) const outbound = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outbound) + await this.messageSender.sendMessage(this.agentContext, outbound) return proofRecord } @@ -106,7 +116,7 @@ export class ProofsModule { comment?: string } ): Promise { - const proofRecord = await this.proofService.getById(proofRecordId) + const proofRecord = await this.proofService.getById(this.agentContext, proofRecordId) if (!proofRecord.connectionId) { throw new AriesFrameworkError( @@ -114,25 +124,29 @@ export class ProofsModule { ) } - const connection = await this.connectionService.getById(proofRecord.connectionId) + const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) const presentationProposal = proofRecord.proposalMessage?.presentationProposal if (!presentationProposal) { throw new AriesFrameworkError(`Proof record with id ${proofRecordId} is missing required presentation proposal`) } - const proofRequest = await this.proofService.createProofRequestFromProposal(presentationProposal, { - name: config?.request?.name ?? 'proof-request', - version: config?.request?.version ?? '1.0', - nonce: config?.request?.nonce, - }) + const proofRequest = await this.proofService.createProofRequestFromProposal( + this.agentContext, + presentationProposal, + { + name: config?.request?.name ?? 'proof-request', + version: config?.request?.version ?? '1.0', + nonce: config?.request?.nonce, + } + ) - const { message } = await this.proofService.createRequestAsResponse(proofRecord, proofRequest, { + const { message } = await this.proofService.createRequestAsResponse(this.agentContext, proofRecord, proofRequest, { comment: config?.comment, }) const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return proofRecord } @@ -151,9 +165,9 @@ export class ProofsModule { proofRequestOptions: CreateProofRequestOptions, config?: ProofRequestConfig ): Promise { - const connection = await this.connectionService.getById(connectionId) + const connection = await this.connectionService.getById(this.agentContext, connectionId) - const nonce = proofRequestOptions.nonce ?? (await this.proofService.generateProofRequestNonce()) + const nonce = proofRequestOptions.nonce ?? (await this.proofService.generateProofRequestNonce(this.agentContext)) const proofRequest = new ProofRequest({ name: proofRequestOptions.name ?? 'proof-request', @@ -163,10 +177,15 @@ export class ProofsModule { requestedPredicates: proofRequestOptions.requestedPredicates, }) - const { message, proofRecord } = await this.proofService.createRequest(proofRequest, connection, config) + const { message, proofRecord } = await this.proofService.createRequest( + this.agentContext, + proofRequest, + connection, + config + ) const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return proofRecord } @@ -186,7 +205,7 @@ export class ProofsModule { requestMessage: RequestPresentationMessage proofRecord: ProofRecord }> { - const nonce = proofRequestOptions.nonce ?? (await this.proofService.generateProofRequestNonce()) + const nonce = proofRequestOptions.nonce ?? (await this.proofService.generateProofRequestNonce(this.agentContext)) const proofRequest = new ProofRequest({ name: proofRequestOptions.name ?? 'proof-request', @@ -196,10 +215,15 @@ export class ProofsModule { requestedPredicates: proofRequestOptions.requestedPredicates, }) - const { message, proofRecord } = await this.proofService.createRequest(proofRequest, undefined, config) + const { message, proofRecord } = await this.proofService.createRequest( + this.agentContext, + proofRequest, + undefined, + config + ) // Create and set ~service decorator - const routing = await this.routingService.getRouting() + const routing = await this.routingService.getRouting(this.agentContext) message.service = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], @@ -208,7 +232,7 @@ export class ProofsModule { // Save ~service decorator to record (to remember our verkey) proofRecord.requestMessage = message - await this.proofService.update(proofRecord) + await this.proofService.update(this.agentContext, proofRecord) return { proofRecord, requestMessage: message } } @@ -230,22 +254,27 @@ export class ProofsModule { comment?: string } ): Promise { - const record = await this.proofService.getById(proofRecordId) - const { message, proofRecord } = await this.proofService.createPresentation(record, requestedCredentials, config) + const record = await this.proofService.getById(this.agentContext, proofRecordId) + const { message, proofRecord } = await this.proofService.createPresentation( + this.agentContext, + record, + requestedCredentials, + config + ) // Use connection if present if (proofRecord.connectionId) { - const connection = await this.connectionService.getById(proofRecord.connectionId) + const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return proofRecord } // Use ~service decorator otherwise else if (proofRecord.requestMessage?.service) { // Create ~service decorator - const routing = await this.routingService.getRouting() + const routing = await this.routingService.getRouting(this.agentContext) const ourService = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], @@ -257,9 +286,9 @@ export class ProofsModule { // Set and save ~service decorator to record (to remember our verkey) message.service = ourService proofRecord.presentationMessage = message - await this.proofService.update(proofRecord) + await this.proofService.update(this.agentContext, proofRecord) - await this.messageSender.sendMessageToService({ + await this.messageSender.sendMessageToService(this.agentContext, { message, service: recipientService.resolvedDidCommService, senderKey: ourService.resolvedDidCommService.recipientKeys[0], @@ -282,8 +311,8 @@ export class ProofsModule { * @returns proof record that was declined */ public async declineRequest(proofRecordId: string) { - const proofRecord = await this.proofService.getById(proofRecordId) - await this.proofService.declineRequest(proofRecord) + const proofRecord = await this.proofService.getById(this.agentContext, proofRecordId) + await this.proofService.declineRequest(this.agentContext, proofRecord) return proofRecord } @@ -296,21 +325,21 @@ export class ProofsModule { * */ public async acceptPresentation(proofRecordId: string): Promise { - const record = await this.proofService.getById(proofRecordId) - const { message, proofRecord } = await this.proofService.createAck(record) + const record = await this.proofService.getById(this.agentContext, proofRecordId) + const { message, proofRecord } = await this.proofService.createAck(this.agentContext, record) // Use connection if present if (proofRecord.connectionId) { - const connection = await this.connectionService.getById(proofRecord.connectionId) + const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) } // Use ~service decorator otherwise else if (proofRecord.requestMessage?.service && proofRecord.presentationMessage?.service) { const recipientService = proofRecord.presentationMessage?.service const ourService = proofRecord.requestMessage.service - await this.messageSender.sendMessageToService({ + await this.messageSender.sendMessageToService(this.agentContext, { message, service: recipientService.resolvedDidCommService, senderKey: ourService.resolvedDidCommService.recipientKeys[0], @@ -344,7 +373,7 @@ export class ProofsModule { proofRecordId: string, config?: GetRequestedCredentialsConfig ): Promise { - const proofRecord = await this.proofService.getById(proofRecordId) + const proofRecord = await this.proofService.getById(this.agentContext, proofRecordId) const indyProofRequest = proofRecord.requestMessage?.indyProofRequest const presentationPreview = config?.filterByPresentationPreview @@ -357,7 +386,7 @@ export class ProofsModule { ) } - return this.proofService.getRequestedCredentialsForProofRequest(indyProofRequest, { + return this.proofService.getRequestedCredentialsForProofRequest(this.agentContext, indyProofRequest, { presentationProposal: presentationPreview, filterByNonRevocationRequirements: config?.filterByNonRevocationRequirements ?? true, }) @@ -384,11 +413,11 @@ export class ProofsModule { * @returns proof record associated with the proof problem report message */ public async sendProblemReport(proofRecordId: string, message: string) { - const record = await this.proofService.getById(proofRecordId) + const record = await this.proofService.getById(this.agentContext, proofRecordId) if (!record.connectionId) { throw new AriesFrameworkError(`No connectionId found for proof record '${record.id}'.`) } - const connection = await this.connectionService.getById(record.connectionId) + const connection = await this.connectionService.getById(this.agentContext, record.connectionId) const presentationProblemReportMessage = new PresentationProblemReportMessage({ description: { en: message, @@ -400,7 +429,7 @@ export class ProofsModule { parentThreadId: record.parentThreadId, }) const outboundMessage = createOutboundMessage(connection, presentationProblemReportMessage) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return record } @@ -411,7 +440,7 @@ export class ProofsModule { * @returns List containing all proof records */ public getAll(): Promise { - return this.proofService.getAll() + return this.proofService.getAll(this.agentContext) } /** @@ -424,7 +453,7 @@ export class ProofsModule { * */ public async getById(proofRecordId: string): Promise { - return this.proofService.getById(proofRecordId) + return this.proofService.getById(this.agentContext, proofRecordId) } /** @@ -435,7 +464,7 @@ export class ProofsModule { * */ public async findById(proofRecordId: string): Promise { - return this.proofService.findById(proofRecordId) + return this.proofService.findById(this.agentContext, proofRecordId) } /** @@ -444,7 +473,7 @@ export class ProofsModule { * @param proofId the proof record id */ public async deleteById(proofId: string) { - return this.proofService.deleteById(proofId) + return this.proofService.deleteById(this.agentContext, proofId) } /** @@ -457,7 +486,7 @@ export class ProofsModule { * @returns The proof record */ public async getByThreadAndConnectionId(threadId: string, connectionId?: string): Promise { - return this.proofService.getByThreadAndConnectionId(threadId, connectionId) + return this.proofService.getByThreadAndConnectionId(this.agentContext, threadId, connectionId) } /** @@ -468,24 +497,17 @@ export class ProofsModule { * @returns List containing all proof records matching the given query */ public async getByParentThreadAndConnectionId(parentThreadId: string, connectionId?: string): Promise { - return this.proofService.getByParentThreadAndConnectionId(parentThreadId, connectionId) + return this.proofService.getByParentThreadAndConnectionId(this.agentContext, parentThreadId, connectionId) } private registerHandlers(dispatcher: Dispatcher) { dispatcher.registerHandler( - new ProposePresentationHandler(this.proofService, this.agentConfig, this.proofResponseCoordinator) - ) - dispatcher.registerHandler( - new RequestPresentationHandler( - this.proofService, - this.agentConfig, - this.proofResponseCoordinator, - this.routingService - ) + new ProposePresentationHandler(this.proofService, this.proofResponseCoordinator, this.logger) ) dispatcher.registerHandler( - new PresentationHandler(this.proofService, this.agentConfig, this.proofResponseCoordinator) + new RequestPresentationHandler(this.proofService, this.proofResponseCoordinator, this.routingService, this.logger) ) + dispatcher.registerHandler(new PresentationHandler(this.proofService, this.proofResponseCoordinator, this.logger)) dispatcher.registerHandler(new PresentationAckHandler(this.proofService)) dispatcher.registerHandler(new PresentationProblemReportHandler(this.proofService)) } diff --git a/packages/core/src/modules/proofs/__tests__/ProofService.test.ts b/packages/core/src/modules/proofs/__tests__/ProofService.test.ts index d654dd924a..554856172e 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofService.test.ts +++ b/packages/core/src/modules/proofs/__tests__/ProofService.test.ts @@ -1,9 +1,11 @@ -import type { Wallet } from '../../../wallet/Wallet' +import type { AgentContext } from '../../../agent' import type { CredentialRepository } from '../../credentials/repository' import type { ProofStateChangedEvent } from '../ProofEvents' import type { CustomProofTags } from './../repository/ProofRecord' -import { getAgentConfig, getMockConnection, mockFunction } from '../../../../tests/helpers' +import { Subject } from 'rxjs' + +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' @@ -93,13 +95,13 @@ describe('ProofService', () => { let proofRepository: ProofRepository let proofService: ProofService let ledgerService: IndyLedgerService - let wallet: Wallet let indyVerifierService: IndyVerifierService let indyHolderService: IndyHolderService let indyRevocationService: IndyRevocationService let eventEmitter: EventEmitter let credentialRepository: CredentialRepository let connectionService: ConnectionService + let agentContext: AgentContext beforeEach(() => { const agentConfig = getAgentConfig('ProofServiceTest') @@ -108,20 +110,20 @@ describe('ProofService', () => { indyHolderService = new IndyHolderServiceMock() indyRevocationService = new IndyRevocationServiceMock() ledgerService = new IndyLedgerServiceMock() - eventEmitter = new EventEmitter(agentConfig) + eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) connectionService = new connectionServiceMock() + agentContext = getAgentContext() proofService = new ProofService( proofRepository, ledgerService, - wallet, - agentConfig, indyHolderService, indyVerifierService, indyRevocationService, connectionService, eventEmitter, - credentialRepository + credentialRepository, + agentConfig.logger ) mockFunction(ledgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) @@ -138,6 +140,7 @@ describe('ProofService', () => { }) messageContext = new InboundMessageContext(presentationRequest, { connection, + agentContext, }) }) @@ -157,7 +160,7 @@ describe('ProofService', () => { connectionId: connection.id, } expect(repositorySaveSpy).toHaveBeenCalledTimes(1) - const [[createdProofRecord]] = repositorySaveSpy.mock.calls + const [[, createdProofRecord]] = repositorySaveSpy.mock.calls expect(createdProofRecord).toMatchObject(expectedProofRecord) expect(returnedProofRecord).toMatchObject(expectedProofRecord) }) @@ -236,6 +239,7 @@ describe('ProofService', () => { presentationProblemReportMessage.setThread({ threadId: 'somethreadid' }) messageContext = new InboundMessageContext(presentationProblemReportMessage, { connection, + agentContext, }) }) @@ -252,12 +256,12 @@ describe('ProofService', () => { const expectedCredentialRecord = { errorMessage: 'abandoned: Indy error', } - expect(proofRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, { + expect(proofRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { threadId: 'somethreadid', connectionId: connection.id, }) expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) - const [[updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls + const [[, updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls expect(updatedCredentialRecord).toMatchObject(expectedCredentialRecord) expect(returnedCredentialRecord).toMatchObject(expectedCredentialRecord) }) diff --git a/packages/core/src/modules/proofs/handlers/PresentationHandler.ts b/packages/core/src/modules/proofs/handlers/PresentationHandler.ts index c00fa139c7..991a3a550d 100644 --- a/packages/core/src/modules/proofs/handlers/PresentationHandler.ts +++ b/packages/core/src/modules/proofs/handlers/PresentationHandler.ts @@ -1,5 +1,5 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { Logger } from '../../../logger' import type { ProofResponseCoordinator } from '../ProofResponseCoordinator' import type { ProofRecord } from '../repository' import type { ProofService } from '../services' @@ -9,34 +9,30 @@ import { PresentationMessage } from '../messages' export class PresentationHandler implements Handler { private proofService: ProofService - private agentConfig: AgentConfig private proofResponseCoordinator: ProofResponseCoordinator + private logger: Logger public supportedMessages = [PresentationMessage] - public constructor( - proofService: ProofService, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator - ) { + public constructor(proofService: ProofService, proofResponseCoordinator: ProofResponseCoordinator, logger: Logger) { this.proofService = proofService - this.agentConfig = agentConfig this.proofResponseCoordinator = proofResponseCoordinator + this.logger = logger } public async handle(messageContext: HandlerInboundMessage) { const proofRecord = await this.proofService.processPresentation(messageContext) - if (this.proofResponseCoordinator.shouldAutoRespondToPresentation(proofRecord)) { + if (this.proofResponseCoordinator.shouldAutoRespondToPresentation(messageContext.agentContext, proofRecord)) { return await this.createAck(proofRecord, messageContext) } } private async createAck(record: ProofRecord, messageContext: HandlerInboundMessage) { - this.agentConfig.logger.info( - `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptProofs}` + this.logger.info( + `Automatically sending acknowledgement with autoAccept on ${messageContext.agentContext.config.autoAcceptProofs}` ) - const { message, proofRecord } = await this.proofService.createAck(record) + const { message, proofRecord } = await this.proofService.createAck(messageContext.agentContext, record) if (messageContext.connection) { return createOutboundMessage(messageContext.connection, message) @@ -51,6 +47,6 @@ export class PresentationHandler implements Handler { }) } - this.agentConfig.logger.error(`Could not automatically create presentation ack`) + this.logger.error(`Could not automatically create presentation ack`) } } diff --git a/packages/core/src/modules/proofs/handlers/ProposePresentationHandler.ts b/packages/core/src/modules/proofs/handlers/ProposePresentationHandler.ts index de29cc2e1d..6ab1879fdb 100644 --- a/packages/core/src/modules/proofs/handlers/ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/handlers/ProposePresentationHandler.ts @@ -1,5 +1,5 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { Logger } from '../../../logger' import type { ProofResponseCoordinator } from '../ProofResponseCoordinator' import type { ProofRecord } from '../repository' import type { ProofService } from '../services' @@ -9,24 +9,20 @@ import { ProposePresentationMessage } from '../messages' export class ProposePresentationHandler implements Handler { private proofService: ProofService - private agentConfig: AgentConfig private proofResponseCoordinator: ProofResponseCoordinator + private logger: Logger public supportedMessages = [ProposePresentationMessage] - public constructor( - proofService: ProofService, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator - ) { + public constructor(proofService: ProofService, proofResponseCoordinator: ProofResponseCoordinator, logger: Logger) { this.proofService = proofService - this.agentConfig = agentConfig this.proofResponseCoordinator = proofResponseCoordinator + this.logger = logger } public async handle(messageContext: HandlerInboundMessage) { const proofRecord = await this.proofService.processProposal(messageContext) - if (this.proofResponseCoordinator.shouldAutoRespondToProposal(proofRecord)) { + if (this.proofResponseCoordinator.shouldAutoRespondToProposal(messageContext.agentContext, proofRecord)) { return await this.createRequest(proofRecord, messageContext) } } @@ -35,19 +31,20 @@ export class ProposePresentationHandler implements Handler { proofRecord: ProofRecord, messageContext: HandlerInboundMessage ) { - this.agentConfig.logger.info( - `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptProofs}` + this.logger.info( + `Automatically sending request with autoAccept on ${messageContext.agentContext.config.autoAcceptProofs}` ) if (!messageContext.connection) { - this.agentConfig.logger.error('No connection on the messageContext') + this.logger.error('No connection on the messageContext') return } if (!proofRecord.proposalMessage) { - this.agentConfig.logger.error(`Proof record with id ${proofRecord.id} is missing required credential proposal`) + this.logger.error(`Proof record with id ${proofRecord.id} is missing required credential proposal`) return } const proofRequest = await this.proofService.createProofRequestFromProposal( + messageContext.agentContext, proofRecord.proposalMessage.presentationProposal, { name: 'proof-request', @@ -55,7 +52,11 @@ export class ProposePresentationHandler implements Handler { } ) - const { message } = await this.proofService.createRequestAsResponse(proofRecord, proofRequest) + const { message } = await this.proofService.createRequestAsResponse( + messageContext.agentContext, + proofRecord, + proofRequest + ) return createOutboundMessage(messageContext.connection, message) } diff --git a/packages/core/src/modules/proofs/handlers/RequestPresentationHandler.ts b/packages/core/src/modules/proofs/handlers/RequestPresentationHandler.ts index e5b1ca8280..87b1445d94 100644 --- a/packages/core/src/modules/proofs/handlers/RequestPresentationHandler.ts +++ b/packages/core/src/modules/proofs/handlers/RequestPresentationHandler.ts @@ -1,5 +1,5 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { Logger } from '../../../logger' import type { RoutingService } from '../../routing/services/RoutingService' import type { ProofResponseCoordinator } from '../ProofResponseCoordinator' import type { ProofRecord } from '../repository' @@ -11,27 +11,27 @@ import { RequestPresentationMessage } from '../messages' export class RequestPresentationHandler implements Handler { private proofService: ProofService - private agentConfig: AgentConfig private proofResponseCoordinator: ProofResponseCoordinator private routingService: RoutingService + private logger: Logger public supportedMessages = [RequestPresentationMessage] public constructor( proofService: ProofService, - agentConfig: AgentConfig, proofResponseCoordinator: ProofResponseCoordinator, - routingService: RoutingService + routingService: RoutingService, + logger: Logger ) { this.proofService = proofService - this.agentConfig = agentConfig this.proofResponseCoordinator = proofResponseCoordinator this.routingService = routingService + this.logger = logger } public async handle(messageContext: HandlerInboundMessage) { const proofRecord = await this.proofService.processRequest(messageContext) - if (this.proofResponseCoordinator.shouldAutoRespondToRequest(proofRecord)) { + if (this.proofResponseCoordinator.shouldAutoRespondToRequest(messageContext.agentContext, proofRecord)) { return await this.createPresentation(proofRecord, messageContext) } } @@ -43,28 +43,36 @@ export class RequestPresentationHandler implements Handler { const indyProofRequest = record.requestMessage?.indyProofRequest const presentationProposal = record.proposalMessage?.presentationProposal - this.agentConfig.logger.info( - `Automatically sending presentation with autoAccept on ${this.agentConfig.autoAcceptProofs}` + this.logger.info( + `Automatically sending presentation with autoAccept on ${messageContext.agentContext.config.autoAcceptProofs}` ) if (!indyProofRequest) { - this.agentConfig.logger.error('Proof request is undefined.') + this.logger.error('Proof request is undefined.') return } - const retrievedCredentials = await this.proofService.getRequestedCredentialsForProofRequest(indyProofRequest, { - presentationProposal, - }) + const retrievedCredentials = await this.proofService.getRequestedCredentialsForProofRequest( + messageContext.agentContext, + indyProofRequest, + { + presentationProposal, + } + ) const requestedCredentials = this.proofService.autoSelectCredentialsForProofRequest(retrievedCredentials) - const { message, proofRecord } = await this.proofService.createPresentation(record, requestedCredentials) + const { message, proofRecord } = await this.proofService.createPresentation( + messageContext.agentContext, + record, + requestedCredentials + ) if (messageContext.connection) { return createOutboundMessage(messageContext.connection, message) } else if (proofRecord.requestMessage?.service) { // Create ~service decorator - const routing = await this.routingService.getRouting() + const routing = await this.routingService.getRouting(messageContext.agentContext) const ourService = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], @@ -76,7 +84,7 @@ export class RequestPresentationHandler implements Handler { // Set and save ~service decorator to record (to remember our verkey) message.service = ourService proofRecord.presentationMessage = message - await this.proofService.update(proofRecord) + await this.proofService.update(messageContext.agentContext, proofRecord) return createOutboundServiceMessage({ payload: message, @@ -85,6 +93,6 @@ export class RequestPresentationHandler implements Handler { }) } - this.agentConfig.logger.error(`Could not automatically create presentation`) + this.logger.error(`Could not automatically create presentation`) } } diff --git a/packages/core/src/modules/proofs/services/ProofService.ts b/packages/core/src/modules/proofs/services/ProofService.ts index 0f1a721c6a..5105a17a97 100644 --- a/packages/core/src/modules/proofs/services/ProofService.ts +++ b/packages/core/src/modules/proofs/services/ProofService.ts @@ -1,6 +1,6 @@ +import type { AgentContext } from '../../../agent' import type { AgentMessage } from '../../../agent/AgentMessage' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import type { Logger } from '../../../logger' import type { ConnectionRecord } from '../../connections' import type { AutoAcceptProof } from '../ProofAutoAcceptType' import type { ProofStateChangedEvent } from '../ProofEvents' @@ -10,22 +10,21 @@ import type { CredDef, IndyProof, Schema } from 'indy-sdk' import { validateOrReject } from 'class-validator' -import { AgentConfig } from '../../../agent/AgentConfig' import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../error' +import { Logger } from '../../../logger' import { inject, injectable } from '../../../plugins' import { JsonEncoder } from '../../../utils/JsonEncoder' import { JsonTransformer } from '../../../utils/JsonTransformer' import { checkProofRequestForDuplicates } from '../../../utils/indyProofRequest' import { uuid } from '../../../utils/uuid' -import { Wallet } from '../../../wallet/Wallet' import { AckStatus } from '../../common' import { ConnectionService } from '../../connections' -import { IndyCredential, CredentialRepository, IndyCredentialInfo } from '../../credentials' +import { CredentialRepository, IndyCredential, IndyCredentialInfo } from '../../credentials' import { IndyCredentialUtils } from '../../credentials/formats/indy/IndyCredentialUtils' -import { IndyHolderService, IndyVerifierService, IndyRevocationService } from '../../indy' +import { IndyHolderService, IndyRevocationService, IndyVerifierService } from '../../indy' import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' import { ProofEventTypes } from '../ProofEvents' import { ProofState } from '../ProofState' @@ -62,7 +61,6 @@ export class ProofService { private proofRepository: ProofRepository private credentialRepository: CredentialRepository private ledgerService: IndyLedgerService - private wallet: Wallet private logger: Logger private indyHolderService: IndyHolderService private indyVerifierService: IndyVerifierService @@ -73,20 +71,18 @@ export class ProofService { public constructor( proofRepository: ProofRepository, ledgerService: IndyLedgerService, - @inject(InjectionSymbols.Wallet) wallet: Wallet, - agentConfig: AgentConfig, indyHolderService: IndyHolderService, indyVerifierService: IndyVerifierService, indyRevocationService: IndyRevocationService, connectionService: ConnectionService, eventEmitter: EventEmitter, - credentialRepository: CredentialRepository + credentialRepository: CredentialRepository, + @inject(InjectionSymbols.Logger) logger: Logger ) { this.proofRepository = proofRepository this.credentialRepository = credentialRepository this.ledgerService = ledgerService - this.wallet = wallet - this.logger = agentConfig.logger + this.logger = logger this.indyHolderService = indyHolderService this.indyVerifierService = indyVerifierService this.indyRevocationService = indyRevocationService @@ -105,6 +101,7 @@ export class ProofService { * */ public async createProposal( + agentContext: AgentContext, connectionRecord: ConnectionRecord, presentationProposal: PresentationPreview, config?: { @@ -132,8 +129,8 @@ export class ProofService { proposalMessage, autoAcceptProof: config?.autoAcceptProof, }) - await this.proofRepository.save(proofRecord) - this.emitStateChangedEvent(proofRecord, null) + await this.proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) return { message: proposalMessage, proofRecord } } @@ -149,6 +146,7 @@ export class ProofService { * */ public async createProposalAsResponse( + agentContext: AgentContext, proofRecord: ProofRecord, presentationProposal: PresentationPreview, config?: { @@ -167,7 +165,7 @@ export class ProofService { // Update record proofRecord.proposalMessage = proposalMessage - await this.updateState(proofRecord, ProofState.ProposalSent) + await this.updateState(agentContext, proofRecord, ProofState.ProposalSent) return { message: proposalMessage, proofRecord } } @@ -176,10 +174,10 @@ export class ProofService { * Decline a proof request * @param proofRecord The proof request to be declined */ - public async declineRequest(proofRecord: ProofRecord): Promise { + public async declineRequest(agentContext: AgentContext, proofRecord: ProofRecord): Promise { proofRecord.assertState(ProofState.RequestReceived) - await this.updateState(proofRecord, ProofState.Declined) + await this.updateState(agentContext, proofRecord, ProofState.Declined) return proofRecord } @@ -204,7 +202,11 @@ export class ProofService { try { // Proof record already exists - proofRecord = await this.getByThreadAndConnectionId(proposalMessage.threadId, connection?.id) + proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + proposalMessage.threadId, + connection?.id + ) // Assert proofRecord.assertState(ProofState.RequestSent) @@ -215,7 +217,7 @@ export class ProofService { // Update record proofRecord.proposalMessage = proposalMessage - await this.updateState(proofRecord, ProofState.ProposalReceived) + await this.updateState(messageContext.agentContext, proofRecord, ProofState.ProposalReceived) } catch { // No proof record exists with thread id proofRecord = new ProofRecord({ @@ -230,8 +232,8 @@ export class ProofService { this.connectionService.assertConnectionOrServiceDecorator(messageContext) // Save record - await this.proofRepository.save(proofRecord) - this.emitStateChangedEvent(proofRecord, null) + await this.proofRepository.save(messageContext.agentContext, proofRecord) + this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) } return proofRecord @@ -248,6 +250,7 @@ export class ProofService { * */ public async createRequestAsResponse( + agentContext: AgentContext, proofRecord: ProofRecord, proofRequest: ProofRequest, config?: { @@ -278,7 +281,7 @@ export class ProofService { // Update record proofRecord.requestMessage = requestPresentationMessage - await this.updateState(proofRecord, ProofState.RequestSent) + await this.updateState(agentContext, proofRecord, ProofState.RequestSent) return { message: requestPresentationMessage, proofRecord } } @@ -293,6 +296,7 @@ export class ProofService { * */ public async createRequest( + agentContext: AgentContext, proofRequest: ProofRequest, connectionRecord?: ConnectionRecord, config?: { @@ -333,8 +337,8 @@ export class ProofService { autoAcceptProof: config?.autoAcceptProof, }) - await this.proofRepository.save(proofRecord) - this.emitStateChangedEvent(proofRecord, null) + await this.proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) return { message: requestPresentationMessage, proofRecord } } @@ -373,7 +377,11 @@ export class ProofService { try { // Proof record already exists - proofRecord = await this.getByThreadAndConnectionId(proofRequestMessage.threadId, connection?.id) + proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + proofRequestMessage.threadId, + connection?.id + ) // Assert proofRecord.assertState(ProofState.ProposalSent) @@ -384,7 +392,7 @@ export class ProofService { // Update record proofRecord.requestMessage = proofRequestMessage - await this.updateState(proofRecord, ProofState.RequestReceived) + await this.updateState(messageContext.agentContext, proofRecord, ProofState.RequestReceived) } catch { // No proof record exists with thread id proofRecord = new ProofRecord({ @@ -399,8 +407,8 @@ export class ProofService { this.connectionService.assertConnectionOrServiceDecorator(messageContext) // Save in repository - await this.proofRepository.save(proofRecord) - this.emitStateChangedEvent(proofRecord, null) + await this.proofRepository.save(messageContext.agentContext, proofRecord) + this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) } return proofRecord @@ -416,6 +424,7 @@ export class ProofService { * */ public async createPresentation( + agentContext: AgentContext, proofRecord: ProofRecord, requestedCredentials: RequestedCredentials, config?: { @@ -437,12 +446,13 @@ export class ProofService { // Get the matching attachments to the requested credentials const attachments = await this.getRequestedAttachmentsForRequestedCredentials( + agentContext, indyProofRequest, requestedCredentials ) // Create proof - const proof = await this.createProof(indyProofRequest, requestedCredentials) + const proof = await this.createProof(agentContext, indyProofRequest, requestedCredentials) // Create message const attachment = new Attachment({ @@ -462,7 +472,7 @@ export class ProofService { // Update record proofRecord.presentationMessage = presentationMessage - await this.updateState(proofRecord, ProofState.PresentationSent) + await this.updateState(agentContext, proofRecord, ProofState.PresentationSent) return { message: presentationMessage, proofRecord } } @@ -482,7 +492,11 @@ export class ProofService { this.logger.debug(`Processing presentation with id ${presentationMessage.id}`) - const proofRecord = await this.getByThreadAndConnectionId(presentationMessage.threadId, connection?.id) + const proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + presentationMessage.threadId, + connection?.id + ) // Assert proofRecord.assertState(ProofState.RequestSent) @@ -509,12 +523,12 @@ export class ProofService { ) } - const isValid = await this.verifyProof(indyProofJson, indyProofRequest) + const isValid = await this.verifyProof(messageContext.agentContext, indyProofJson, indyProofRequest) // Update record proofRecord.isVerified = isValid proofRecord.presentationMessage = presentationMessage - await this.updateState(proofRecord, ProofState.PresentationReceived) + await this.updateState(messageContext.agentContext, proofRecord, ProofState.PresentationReceived) return proofRecord } @@ -526,7 +540,10 @@ export class ProofService { * @returns Object containing presentation acknowledgement message and associated proof record * */ - public async createAck(proofRecord: ProofRecord): Promise> { + public async createAck( + agentContext: AgentContext, + proofRecord: ProofRecord + ): Promise> { this.logger.debug(`Creating presentation ack for proof record with id ${proofRecord.id}`) // Assert @@ -539,7 +556,7 @@ export class ProofService { }) // Update record - await this.updateState(proofRecord, ProofState.Done) + await this.updateState(agentContext, proofRecord, ProofState.Done) return { message: ackMessage, proofRecord } } @@ -556,7 +573,11 @@ export class ProofService { this.logger.debug(`Processing presentation ack with id ${presentationAckMessage.id}`) - const proofRecord = await this.getByThreadAndConnectionId(presentationAckMessage.threadId, connection?.id) + const proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + presentationAckMessage.threadId, + connection?.id + ) // Assert proofRecord.assertState(ProofState.PresentationSent) @@ -566,7 +587,7 @@ export class ProofService { }) // Update record - await this.updateState(proofRecord, ProofState.Done) + await this.updateState(messageContext.agentContext, proofRecord, ProofState.Done) return proofRecord } @@ -587,15 +608,19 @@ export class ProofService { this.logger.debug(`Processing problem report with id ${presentationProblemReportMessage.id}`) - const proofRecord = await this.getByThreadAndConnectionId(presentationProblemReportMessage.threadId, connection?.id) + const proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + presentationProblemReportMessage.threadId, + connection?.id + ) proofRecord.errorMessage = `${presentationProblemReportMessage.description.code}: ${presentationProblemReportMessage.description.en}` - await this.update(proofRecord) + await this.update(messageContext.agentContext, proofRecord) return proofRecord } - public async generateProofRequestNonce() { - return this.wallet.generateNonce() + public async generateProofRequestNonce(agentContext: AgentContext) { + return agentContext.wallet.generateNonce() } /** @@ -608,10 +633,11 @@ export class ProofService { * */ public async createProofRequestFromProposal( + agentContext: AgentContext, presentationProposal: PresentationPreview, config: { name: string; version: string; nonce?: string } ): Promise { - const nonce = config.nonce ?? (await this.generateProofRequestNonce()) + const nonce = config.nonce ?? (await this.generateProofRequestNonce(agentContext)) const proofRequest = new ProofRequest({ name: config.name, @@ -691,6 +717,7 @@ export class ProofService { * @returns a list of attachments that are linked to the requested credentials */ public async getRequestedAttachmentsForRequestedCredentials( + agentContext: AgentContext, indyProofRequest: ProofRequest, requestedCredentials: RequestedCredentials ): Promise { @@ -708,7 +735,10 @@ export class ProofService { //Get credentialInfo if (!requestedAttribute.credentialInfo) { - const indyCredentialInfo = await this.indyHolderService.getCredential(requestedAttribute.credentialId) + const indyCredentialInfo = await this.indyHolderService.getCredential( + agentContext, + requestedAttribute.credentialId + ) requestedAttribute.credentialInfo = JsonTransformer.fromJSON(indyCredentialInfo, IndyCredentialInfo) } @@ -724,7 +754,9 @@ export class ProofService { for (const credentialId of credentialIds) { // Get the credentialRecord that matches the ID - const credentialRecord = await this.credentialRepository.getSingleByQuery({ credentialIds: [credentialId] }) + const credentialRecord = await this.credentialRepository.getSingleByQuery(agentContext, { + credentialIds: [credentialId], + }) if (credentialRecord.linkedAttachments) { // Get the credentials that have a hashlink as value and are requested @@ -758,6 +790,7 @@ export class ProofService { * @returns RetrievedCredentials object */ public async getRequestedCredentialsForProofRequest( + agentContext: AgentContext, proofRequest: ProofRequest, config: { presentationProposal?: PresentationPreview @@ -768,7 +801,7 @@ export class ProofService { for (const [referent, requestedAttribute] of proofRequest.requestedAttributes.entries()) { let credentialMatch: IndyCredential[] = [] - const credentials = await this.getCredentialsForProofRequest(proofRequest, referent) + const credentials = await this.getCredentialsForProofRequest(agentContext, proofRequest, referent) // If we have exactly one credential, or no proposal to pick preferences // on the credentials to use, we will use the first one @@ -797,7 +830,7 @@ export class ProofService { retrievedCredentials.requestedAttributes[referent] = await Promise.all( credentialMatch.map(async (credential: IndyCredential) => { - const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem({ + const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { proofRequest, requestedItem: requestedAttribute, credential, @@ -823,11 +856,11 @@ export class ProofService { } for (const [referent, requestedPredicate] of proofRequest.requestedPredicates.entries()) { - const credentials = await this.getCredentialsForProofRequest(proofRequest, referent) + const credentials = await this.getCredentialsForProofRequest(agentContext, proofRequest, referent) retrievedCredentials.requestedPredicates[referent] = await Promise.all( credentials.map(async (credential) => { - const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem({ + const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { proofRequest, requestedItem: requestedPredicate, credential, @@ -898,7 +931,11 @@ export class ProofService { * @returns Boolean whether the proof is valid * */ - public async verifyProof(proofJson: IndyProof, proofRequest: ProofRequest): Promise { + public async verifyProof( + agentContext: AgentContext, + proofJson: IndyProof, + proofRequest: ProofRequest + ): Promise { const proof = JsonTransformer.fromJSON(proofJson, PartialProof) for (const [referent, attribute] of proof.requestedProof.revealedAttributes.entries()) { @@ -916,12 +953,13 @@ export class ProofService { // I'm not 100% sure how much indy does. Also if it checks whether the proof requests matches the proof // @see https://github.com/hyperledger/aries-cloudagent-python/blob/master/aries_cloudagent/indy/sdk/verifier.py#L79-L164 - const schemas = await this.getSchemas(new Set(proof.identifiers.map((i) => i.schemaId))) + const schemas = await this.getSchemas(agentContext, new Set(proof.identifiers.map((i) => i.schemaId))) const credentialDefinitions = await this.getCredentialDefinitions( + agentContext, new Set(proof.identifiers.map((i) => i.credentialDefinitionId)) ) - return await this.indyVerifierService.verifyProof({ + return await this.indyVerifierService.verifyProof(agentContext, { proofRequest: proofRequest.toJSON(), proof: proofJson, schemas, @@ -934,8 +972,8 @@ export class ProofService { * * @returns List containing all proof records */ - public async getAll(): Promise { - return this.proofRepository.getAll() + public async getAll(agentContext: AgentContext): Promise { + return this.proofRepository.getAll(agentContext) } /** @@ -946,8 +984,8 @@ export class ProofService { * @return The proof record * */ - public async getById(proofRecordId: string): Promise { - return this.proofRepository.getById(proofRecordId) + public async getById(agentContext: AgentContext, proofRecordId: string): Promise { + return this.proofRepository.getById(agentContext, proofRecordId) } /** @@ -957,8 +995,8 @@ export class ProofService { * @return The proof record or null if not found * */ - public async findById(proofRecordId: string): Promise { - return this.proofRepository.findById(proofRecordId) + public async findById(agentContext: AgentContext, proofRecordId: string): Promise { + return this.proofRepository.findById(agentContext, proofRecordId) } /** @@ -966,9 +1004,9 @@ export class ProofService { * * @param proofId the proof record id */ - public async deleteById(proofId: string) { - const proofRecord = await this.getById(proofId) - return this.proofRepository.delete(proofRecord) + public async deleteById(agentContext: AgentContext, proofId: string) { + const proofRecord = await this.getById(agentContext, proofId) + return this.proofRepository.delete(agentContext, proofRecord) } /** @@ -980,8 +1018,12 @@ export class ProofService { * @throws {RecordDuplicateError} If multiple records are found * @returns The proof record */ - public async getByThreadAndConnectionId(threadId: string, connectionId?: string): Promise { - return this.proofRepository.getSingleByQuery({ threadId, connectionId }) + public async getByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise { + return this.proofRepository.getSingleByQuery(agentContext, { threadId, connectionId }) } /** @@ -991,12 +1033,16 @@ export class ProofService { * @param parentThreadId The parent thread id * @returns List containing all proof records matching the given query */ - public async getByParentThreadAndConnectionId(parentThreadId: string, connectionId?: string): Promise { - return this.proofRepository.findByQuery({ parentThreadId, connectionId }) + public async getByParentThreadAndConnectionId( + agentContext: AgentContext, + parentThreadId: string, + connectionId?: string + ): Promise { + return this.proofRepository.findByQuery(agentContext, { parentThreadId, connectionId }) } - public update(proofRecord: ProofRecord) { - return this.proofRepository.update(proofRecord) + public update(agentContext: AgentContext, proofRecord: ProofRecord) { + return this.proofRepository.update(agentContext, proofRecord) } /** @@ -1007,6 +1053,7 @@ export class ProofService { * @returns indy proof object */ private async createProof( + agentContext: AgentContext, proofRequest: ProofRequest, requestedCredentials: RequestedCredentials ): Promise { @@ -1018,17 +1065,18 @@ export class ProofService { if (c.credentialInfo) { return c.credentialInfo } - const credentialInfo = await this.indyHolderService.getCredential(c.credentialId) + const credentialInfo = await this.indyHolderService.getCredential(agentContext, c.credentialId) return JsonTransformer.fromJSON(credentialInfo, IndyCredentialInfo) }) ) - const schemas = await this.getSchemas(new Set(credentialObjects.map((c) => c.schemaId))) + const schemas = await this.getSchemas(agentContext, new Set(credentialObjects.map((c) => c.schemaId))) const credentialDefinitions = await this.getCredentialDefinitions( + agentContext, new Set(credentialObjects.map((c) => c.credentialDefinitionId)) ) - return this.indyHolderService.createProof({ + return this.indyHolderService.createProof(agentContext, { proofRequest: proofRequest.toJSON(), requestedCredentials: requestedCredentials, schemas, @@ -1037,10 +1085,11 @@ export class ProofService { } private async getCredentialsForProofRequest( + agentContext: AgentContext, proofRequest: ProofRequest, attributeReferent: string ): Promise { - const credentialsJson = await this.indyHolderService.getCredentialsForProofRequest({ + const credentialsJson = await this.indyHolderService.getCredentialsForProofRequest(agentContext, { proofRequest: proofRequest.toJSON(), attributeReferent, }) @@ -1048,15 +1097,18 @@ export class ProofService { return JsonTransformer.fromJSON(credentialsJson, IndyCredential) as unknown as IndyCredential[] } - private async getRevocationStatusForRequestedItem({ - proofRequest, - requestedItem, - credential, - }: { - proofRequest: ProofRequest - requestedItem: ProofAttributeInfo | ProofPredicateInfo - credential: IndyCredential - }) { + private async getRevocationStatusForRequestedItem( + agentContext: AgentContext, + { + proofRequest, + requestedItem, + credential, + }: { + proofRequest: ProofRequest + requestedItem: ProofAttributeInfo | ProofPredicateInfo + credential: IndyCredential + } + ) { const requestNonRevoked = requestedItem.nonRevoked ?? proofRequest.nonRevoked const credentialRevocationId = credential.credentialInfo.credentialRevocationId const revocationRegistryId = credential.credentialInfo.revocationRegistryId @@ -1074,6 +1126,7 @@ export class ProofService { // Note presentation from-to's vs ledger from-to's: https://github.com/hyperledger/indy-hipe/blob/master/text/0011-cred-revocation/README.md#indy-node-revocation-registry-intervals const status = await this.indyRevocationService.getRevocationStatus( + agentContext, credentialRevocationId, revocationRegistryId, requestNonRevoked @@ -1093,18 +1146,22 @@ export class ProofService { * @param newState The state to update to * */ - private async updateState(proofRecord: ProofRecord, newState: ProofState) { + private async updateState(agentContext: AgentContext, proofRecord: ProofRecord, newState: ProofState) { const previousState = proofRecord.state proofRecord.state = newState - await this.proofRepository.update(proofRecord) + await this.proofRepository.update(agentContext, proofRecord) - this.emitStateChangedEvent(proofRecord, previousState) + this.emitStateChangedEvent(agentContext, proofRecord, previousState) } - private emitStateChangedEvent(proofRecord: ProofRecord, previousState: ProofState | null) { + private emitStateChangedEvent( + agentContext: AgentContext, + proofRecord: ProofRecord, + previousState: ProofState | null + ) { const clonedProof = JsonTransformer.clone(proofRecord) - this.eventEmitter.emit({ + this.eventEmitter.emit(agentContext, { type: ProofEventTypes.ProofStateChanged, payload: { proofRecord: clonedProof, @@ -1122,11 +1179,11 @@ export class ProofService { * @returns Object containing schemas for specified schema ids * */ - private async getSchemas(schemaIds: Set) { + private async getSchemas(agentContext: AgentContext, schemaIds: Set) { const schemas: { [key: string]: Schema } = {} for (const schemaId of schemaIds) { - const schema = await this.ledgerService.getSchema(schemaId) + const schema = await this.ledgerService.getSchema(agentContext, schemaId) schemas[schemaId] = schema } @@ -1142,11 +1199,11 @@ export class ProofService { * @returns Object containing credential definitions for specified credential definition ids * */ - private async getCredentialDefinitions(credentialDefinitionIds: Set) { + private async getCredentialDefinitions(agentContext: AgentContext, credentialDefinitionIds: Set) { const credentialDefinitions: { [key: string]: CredDef } = {} for (const credDefId of credentialDefinitionIds) { - const credDef = await this.ledgerService.getCredentialDefinition(credDefId) + const credDef = await this.ledgerService.getCredentialDefinition(agentContext, credDefId) credentialDefinitions[credDefId] = credDef } diff --git a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts b/packages/core/src/modules/question-answer/QuestionAnswerModule.ts index 6eac5b48d4..bc17fe8ada 100644 --- a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts +++ b/packages/core/src/modules/question-answer/QuestionAnswerModule.ts @@ -1,6 +1,7 @@ import type { DependencyManager } from '../../plugins' import type { ValidResponse } from './models' +import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' @@ -17,16 +18,19 @@ export class QuestionAnswerModule { private questionAnswerService: QuestionAnswerService private messageSender: MessageSender private connectionService: ConnectionService + private agentContext: AgentContext public constructor( dispatcher: Dispatcher, questionAnswerService: QuestionAnswerService, messageSender: MessageSender, - connectionService: ConnectionService + connectionService: ConnectionService, + agentContext: AgentContext ) { this.questionAnswerService = questionAnswerService this.messageSender = messageSender this.connectionService = connectionService + this.agentContext = agentContext this.registerHandlers(dispatcher) } @@ -46,16 +50,20 @@ export class QuestionAnswerModule { detail?: string } ) { - const connection = await this.connectionService.getById(connectionId) + const connection = await this.connectionService.getById(this.agentContext, connectionId) connection.assertReady() - const { questionMessage, questionAnswerRecord } = await this.questionAnswerService.createQuestion(connectionId, { - question: config.question, - validResponses: config.validResponses, - detail: config?.detail, - }) + const { questionMessage, questionAnswerRecord } = await this.questionAnswerService.createQuestion( + this.agentContext, + connectionId, + { + question: config.question, + validResponses: config.validResponses, + detail: config?.detail, + } + ) const outboundMessage = createOutboundMessage(connection, questionMessage) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return questionAnswerRecord } @@ -68,17 +76,18 @@ export class QuestionAnswerModule { * @returns QuestionAnswer record */ public async sendAnswer(questionRecordId: string, response: string) { - const questionRecord = await this.questionAnswerService.getById(questionRecordId) + const questionRecord = await this.questionAnswerService.getById(this.agentContext, questionRecordId) const { answerMessage, questionAnswerRecord } = await this.questionAnswerService.createAnswer( + this.agentContext, questionRecord, response ) - const connection = await this.connectionService.getById(questionRecord.connectionId) + const connection = await this.connectionService.getById(this.agentContext, questionRecord.connectionId) const outboundMessage = createOutboundMessage(connection, answerMessage) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return questionAnswerRecord } @@ -89,7 +98,7 @@ export class QuestionAnswerModule { * @returns list containing all QuestionAnswer records */ public getAll() { - return this.questionAnswerService.getAll() + return this.questionAnswerService.getAll(this.agentContext) } private registerHandlers(dispatcher: Dispatcher) { diff --git a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts index 3b7f3982a1..c940c1e30c 100644 --- a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts +++ b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts @@ -1,9 +1,18 @@ +import type { AgentContext } from '../../../agent' import type { AgentConfig } from '../../../agent/AgentConfig' import type { Repository } from '../../../storage/Repository' import type { QuestionAnswerStateChangedEvent } from '../QuestionAnswerEvents' import type { ValidResponse } from '../models' -import { getAgentConfig, getMockConnection, mockFunction } from '../../../../tests/helpers' +import { Subject } from 'rxjs' + +import { + agentDependencies, + getAgentConfig, + getAgentContext, + getMockConnection, + mockFunction, +} from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' import { IndyWallet } from '../../../wallet/IndyWallet' import { QuestionAnswerEventTypes } from '../QuestionAnswerEvents' @@ -27,6 +36,7 @@ describe('QuestionAnswerService', () => { let questionAnswerRepository: Repository let questionAnswerService: QuestionAnswerService let eventEmitter: EventEmitter + let agentContext: AgentContext const mockQuestionAnswerRecord = (options: { questionText: string @@ -52,15 +62,16 @@ describe('QuestionAnswerService', () => { beforeAll(async () => { agentConfig = getAgentConfig('QuestionAnswerServiceTest') - wallet = new IndyWallet(agentConfig) + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger) + agentContext = getAgentContext() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) }) beforeEach(async () => { questionAnswerRepository = new QuestionAnswerRepositoryMock() - eventEmitter = new EventEmitter(agentConfig) - questionAnswerService = new QuestionAnswerService(questionAnswerRepository, eventEmitter, agentConfig) + eventEmitter = new EventEmitter(agentDependencies, new Subject()) + questionAnswerService = new QuestionAnswerService(questionAnswerRepository, eventEmitter, agentConfig.logger) }) afterAll(async () => { @@ -81,7 +92,7 @@ describe('QuestionAnswerService', () => { validResponses: [{ text: 'Yes' }, { text: 'No' }], }) - await questionAnswerService.createQuestion(mockConnectionRecord.id, { + await questionAnswerService.createQuestion(agentContext, mockConnectionRecord.id, { question: questionMessage.questionText, validResponses: questionMessage.validResponses, }) @@ -117,7 +128,7 @@ describe('QuestionAnswerService', () => { }) it(`throws an error when invalid response is provided`, async () => { - expect(questionAnswerService.createAnswer(mockRecord, 'Maybe')).rejects.toThrowError( + expect(questionAnswerService.createAnswer(agentContext, mockRecord, 'Maybe')).rejects.toThrowError( `Response does not match valid responses` ) }) @@ -131,7 +142,7 @@ describe('QuestionAnswerService', () => { mockFunction(questionAnswerRepository.getSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) - await questionAnswerService.createAnswer(mockRecord, 'Yes') + await questionAnswerService.createAnswer(agentContext, mockRecord, 'Yes') expect(eventListenerMock).toHaveBeenCalledWith({ type: 'QuestionAnswerStateChanged', diff --git a/packages/core/src/modules/question-answer/services/QuestionAnswerService.ts b/packages/core/src/modules/question-answer/services/QuestionAnswerService.ts index e1ef6093e1..01539adf57 100644 --- a/packages/core/src/modules/question-answer/services/QuestionAnswerService.ts +++ b/packages/core/src/modules/question-answer/services/QuestionAnswerService.ts @@ -1,16 +1,17 @@ +import type { AgentContext } from '../../../agent' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import type { Logger } from '../../../logger' import type { QuestionAnswerStateChangedEvent } from '../QuestionAnswerEvents' import type { ValidResponse } from '../models' import type { QuestionAnswerTags } from '../repository' -import { AgentConfig } from '../../../agent/AgentConfig' import { EventEmitter } from '../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../constants' import { AriesFrameworkError } from '../../../error' -import { injectable } from '../../../plugins' +import { Logger } from '../../../logger' +import { injectable, inject } from '../../../plugins' import { QuestionAnswerEventTypes } from '../QuestionAnswerEvents' import { QuestionAnswerRole } from '../QuestionAnswerRole' -import { QuestionMessage, AnswerMessage } from '../messages' +import { AnswerMessage, QuestionMessage } from '../messages' import { QuestionAnswerState } from '../models' import { QuestionAnswerRecord, QuestionAnswerRepository } from '../repository' @@ -23,11 +24,11 @@ export class QuestionAnswerService { public constructor( questionAnswerRepository: QuestionAnswerRepository, eventEmitter: EventEmitter, - agentConfig: AgentConfig + @inject(InjectionSymbols.Logger) logger: Logger ) { this.questionAnswerRepository = questionAnswerRepository this.eventEmitter = eventEmitter - this.logger = agentConfig.logger + this.logger = logger } /** * Create a question message and a new QuestionAnswer record for the questioner role @@ -39,6 +40,7 @@ export class QuestionAnswerService { * @returns question message and QuestionAnswer record */ public async createQuestion( + agentContext: AgentContext, connectionId: string, config: { question: string @@ -64,9 +66,9 @@ export class QuestionAnswerService { validResponses: questionMessage.validResponses, }) - await this.questionAnswerRepository.save(questionAnswerRecord) + await this.questionAnswerRepository.save(agentContext, questionAnswerRecord) - this.eventEmitter.emit({ + this.eventEmitter.emit(agentContext, { type: QuestionAnswerEventTypes.QuestionAnswerStateChanged, payload: { previousState: null, questionAnswerRecord }, }) @@ -88,7 +90,7 @@ export class QuestionAnswerService { this.logger.debug(`Receiving question message with id ${questionMessage.id}`) const connection = messageContext.assertReadyConnection() - const questionRecord = await this.getById(questionMessage.id) + const questionRecord = await this.getById(messageContext.agentContext, questionMessage.id) questionRecord.assertState(QuestionAnswerState.QuestionSent) const questionAnswerRecord = await this.createRecord({ @@ -102,9 +104,9 @@ export class QuestionAnswerService { validResponses: questionMessage.validResponses, }) - await this.questionAnswerRepository.save(questionAnswerRecord) + await this.questionAnswerRepository.save(messageContext.agentContext, questionAnswerRecord) - this.eventEmitter.emit({ + this.eventEmitter.emit(messageContext.agentContext, { type: QuestionAnswerEventTypes.QuestionAnswerStateChanged, payload: { previousState: null, questionAnswerRecord }, }) @@ -119,7 +121,7 @@ export class QuestionAnswerService { * @param response response used in answer message * @returns answer message and QuestionAnswer record */ - public async createAnswer(questionAnswerRecord: QuestionAnswerRecord, response: string) { + public async createAnswer(agentContext: AgentContext, questionAnswerRecord: QuestionAnswerRecord, response: string) { const answerMessage = new AnswerMessage({ response: response, threadId: questionAnswerRecord.threadId }) questionAnswerRecord.assertState(QuestionAnswerState.QuestionReceived) @@ -127,7 +129,7 @@ export class QuestionAnswerService { questionAnswerRecord.response = response if (questionAnswerRecord.validResponses.some((e) => e.text === response)) { - await this.updateState(questionAnswerRecord, QuestionAnswerState.AnswerSent) + await this.updateState(agentContext, questionAnswerRecord, QuestionAnswerState.AnswerSent) } else { throw new AriesFrameworkError(`Response does not match valid responses`) } @@ -146,17 +148,18 @@ export class QuestionAnswerService { this.logger.debug(`Receiving answer message with id ${answerMessage.id}`) const connection = messageContext.assertReadyConnection() - const answerRecord = await this.getById(answerMessage.id) + const answerRecord = await this.getById(messageContext.agentContext, answerMessage.id) answerRecord.assertState(QuestionAnswerState.AnswerSent) const questionAnswerRecord: QuestionAnswerRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, answerMessage.threadId, connection?.id ) questionAnswerRecord.response = answerMessage.response - await this.updateState(questionAnswerRecord, QuestionAnswerState.AnswerReceived) + await this.updateState(messageContext.agentContext, questionAnswerRecord, QuestionAnswerState.AnswerReceived) return questionAnswerRecord } @@ -169,12 +172,16 @@ export class QuestionAnswerService { * @param newState The state to update to * */ - private async updateState(questionAnswerRecord: QuestionAnswerRecord, newState: QuestionAnswerState) { + private async updateState( + agentContext: AgentContext, + questionAnswerRecord: QuestionAnswerRecord, + newState: QuestionAnswerState + ) { const previousState = questionAnswerRecord.state questionAnswerRecord.state = newState - await this.questionAnswerRepository.update(questionAnswerRecord) + await this.questionAnswerRepository.update(agentContext, questionAnswerRecord) - this.eventEmitter.emit({ + this.eventEmitter.emit(agentContext, { type: QuestionAnswerEventTypes.QuestionAnswerStateChanged, payload: { previousState, @@ -216,8 +223,12 @@ export class QuestionAnswerService { * @throws {RecordDuplicateError} If multiple records are found * @returns The credential record */ - public getByThreadAndConnectionId(connectionId: string, threadId: string): Promise { - return this.questionAnswerRepository.getSingleByQuery({ + public getByThreadAndConnectionId( + agentContext: AgentContext, + connectionId: string, + threadId: string + ): Promise { + return this.questionAnswerRepository.getSingleByQuery(agentContext, { connectionId, threadId, }) @@ -231,8 +242,8 @@ export class QuestionAnswerService { * @return The connection record * */ - public getById(questionAnswerId: string): Promise { - return this.questionAnswerRepository.getById(questionAnswerId) + public getById(agentContext: AgentContext, questionAnswerId: string): Promise { + return this.questionAnswerRepository.getById(agentContext, questionAnswerId) } /** @@ -240,11 +251,11 @@ export class QuestionAnswerService { * * @returns List containing all QuestionAnswer records */ - public getAll() { - return this.questionAnswerRepository.getAll() + public getAll(agentContext: AgentContext) { + return this.questionAnswerRepository.getAll(agentContext) } - public async findAllByQuery(query: Partial) { - return this.questionAnswerRepository.findByQuery(query) + public async findAllByQuery(agentContext: AgentContext, query: Partial) { + return this.questionAnswerRepository.findByQuery(agentContext, query) } } diff --git a/packages/core/src/modules/routing/MediatorModule.ts b/packages/core/src/modules/routing/MediatorModule.ts index 7de7020b3b..daf43e65d4 100644 --- a/packages/core/src/modules/routing/MediatorModule.ts +++ b/packages/core/src/modules/routing/MediatorModule.ts @@ -2,10 +2,9 @@ import type { DependencyManager } from '../../plugins' import type { EncryptedMessage } from '../../types' import type { MediationRecord } from './repository' -import { AgentConfig } from '../../agent/AgentConfig' +import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { EventEmitter } from '../../agent/EventEmitter' -import { MessageReceiver } from '../../agent/MessageReceiver' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' import { injectable, module } from '../../plugins' @@ -23,7 +22,7 @@ export class MediatorModule { private messagePickupService: MessagePickupService private messageSender: MessageSender public eventEmitter: EventEmitter - public agentConfig: AgentConfig + public agentContext: AgentContext public connectionService: ConnectionService public constructor( @@ -31,32 +30,43 @@ export class MediatorModule { mediationService: MediatorService, messagePickupService: MessagePickupService, messageSender: MessageSender, - messageReceiver: MessageReceiver, eventEmitter: EventEmitter, - agentConfig: AgentConfig, + agentContext: AgentContext, connectionService: ConnectionService ) { this.mediatorService = mediationService this.messagePickupService = messagePickupService this.messageSender = messageSender this.eventEmitter = eventEmitter - this.agentConfig = agentConfig this.connectionService = connectionService + this.agentContext = agentContext this.registerHandlers(dispatcher) } public async initialize() { - await this.mediatorService.initialize() + this.agentContext.config.logger.debug('Mediator routing record not loaded yet, retrieving from storage') + const routingRecord = await this.mediatorService.findMediatorRoutingRecord(this.agentContext) + + // If we don't have a routing record yet for this tenant, create it + if (!routingRecord) { + this.agentContext.config.logger.debug( + 'Mediator routing record does not exist yet, creating routing keys and record' + ) + await this.mediatorService.createMediatorRoutingRecord(this.agentContext) + } } public async grantRequestedMediation(mediatorId: string): Promise { - const record = await this.mediatorService.getById(mediatorId) - const connectionRecord = await this.connectionService.getById(record.connectionId) + const record = await this.mediatorService.getById(this.agentContext, mediatorId) + const connectionRecord = await this.connectionService.getById(this.agentContext, record.connectionId) - const { message, mediationRecord } = await this.mediatorService.createGrantMediationMessage(record) + const { message, mediationRecord } = await this.mediatorService.createGrantMediationMessage( + this.agentContext, + record + ) const outboundMessage = createOutboundMessage(connectionRecord, message) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) return mediationRecord } @@ -68,7 +78,7 @@ export class MediatorModule { private registerHandlers(dispatcher: Dispatcher) { dispatcher.registerHandler(new KeylistUpdateHandler(this.mediatorService)) dispatcher.registerHandler(new ForwardHandler(this.mediatorService, this.connectionService, this.messageSender)) - dispatcher.registerHandler(new MediationRequestHandler(this.mediatorService, this.agentConfig)) + dispatcher.registerHandler(new MediationRequestHandler(this.mediatorService)) } /** diff --git a/packages/core/src/modules/routing/RecipientModule.ts b/packages/core/src/modules/routing/RecipientModule.ts index c7801b26c1..c1a60bd472 100644 --- a/packages/core/src/modules/routing/RecipientModule.ts +++ b/packages/core/src/modules/routing/RecipientModule.ts @@ -1,4 +1,3 @@ -import type { Logger } from '../../logger' import type { DependencyManager } from '../../plugins' import type { OutboundWebSocketClosedEvent } from '../../transport' import type { OutboundMessage } from '../../types' @@ -7,16 +6,18 @@ import type { MediationStateChangedEvent } from './RoutingEvents' import type { MediationRecord } from './index' import type { GetRoutingOptions } from './services/RoutingService' -import { firstValueFrom, interval, ReplaySubject, timer } from 'rxjs' -import { filter, first, takeUntil, throttleTime, timeout, tap, delayWhen } from 'rxjs/operators' +import { firstValueFrom, interval, ReplaySubject, Subject, timer } from 'rxjs' +import { delayWhen, filter, first, takeUntil, tap, throttleTime, timeout } from 'rxjs/operators' -import { AgentConfig } from '../../agent/AgentConfig' +import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { EventEmitter } from '../../agent/EventEmitter' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' +import { InjectionSymbols } from '../../constants' import { AriesFrameworkError } from '../../error' -import { injectable, module } from '../../plugins' +import { Logger } from '../../logger' +import { inject, injectable, module } from '../../plugins' import { TransportEventTypes } from '../../transport' import { ConnectionService } from '../connections/services' import { DidsModule } from '../dids' @@ -36,7 +37,6 @@ import { RoutingService } from './services/RoutingService' @module() @injectable() export class RecipientModule { - private agentConfig: AgentConfig private mediationRecipientService: MediationRecipientService private connectionService: ConnectionService private dids: DidsModule @@ -46,10 +46,11 @@ export class RecipientModule { private discoverFeaturesModule: DiscoverFeaturesModule private mediationRepository: MediationRepository private routingService: RoutingService + private agentContext: AgentContext + private stop$: Subject public constructor( dispatcher: Dispatcher, - agentConfig: AgentConfig, mediationRecipientService: MediationRecipientService, connectionService: ConnectionService, dids: DidsModule, @@ -57,32 +58,36 @@ export class RecipientModule { eventEmitter: EventEmitter, discoverFeaturesModule: DiscoverFeaturesModule, mediationRepository: MediationRepository, - routingService: RoutingService + routingService: RoutingService, + @inject(InjectionSymbols.Logger) logger: Logger, + agentContext: AgentContext, + @inject(InjectionSymbols.Stop$) stop$: Subject ) { - this.agentConfig = agentConfig this.connectionService = connectionService this.dids = dids this.mediationRecipientService = mediationRecipientService this.messageSender = messageSender this.eventEmitter = eventEmitter - this.logger = agentConfig.logger + this.logger = logger this.discoverFeaturesModule = discoverFeaturesModule this.mediationRepository = mediationRepository this.routingService = routingService + this.agentContext = agentContext + this.stop$ = stop$ this.registerHandlers(dispatcher) } public async initialize() { - const { defaultMediatorId, clearDefaultMediator } = this.agentConfig + const { defaultMediatorId, clearDefaultMediator } = this.agentContext.config // Set default mediator by id if (defaultMediatorId) { - const mediatorRecord = await this.mediationRecipientService.getById(defaultMediatorId) - await this.mediationRecipientService.setDefaultMediator(mediatorRecord) + const mediatorRecord = await this.mediationRecipientService.getById(this.agentContext, defaultMediatorId) + await this.mediationRecipientService.setDefaultMediator(this.agentContext, mediatorRecord) } // Clear the stored default mediator else if (clearDefaultMediator) { - await this.mediationRecipientService.clearDefaultMediator() + await this.mediationRecipientService.clearDefaultMediator(this.agentContext) } // Poll for messages from mediator @@ -93,13 +98,13 @@ export class RecipientModule { } private async sendMessage(outboundMessage: OutboundMessage, pickupStrategy?: MediatorPickupStrategy) { - const mediatorPickupStrategy = pickupStrategy ?? this.agentConfig.mediatorPickupStrategy + const mediatorPickupStrategy = pickupStrategy ?? this.agentContext.config.mediatorPickupStrategy const transportPriority = mediatorPickupStrategy === MediatorPickupStrategy.Implicit ? { schemes: ['wss', 'ws'], restrictive: true } : undefined - await this.messageSender.sendMessage(outboundMessage, { + await this.messageSender.sendMessage(this.agentContext, outboundMessage, { transportPriority, // TODO: add keepAlive: true to enforce through the public api // we need to keep the socket alive. It already works this way, but would @@ -110,8 +115,8 @@ export class RecipientModule { } private async openMediationWebSocket(mediator: MediationRecord) { - const connection = await this.connectionService.getById(mediator.connectionId) - const { message, connectionRecord } = await this.connectionService.createTrustPing(connection, { + const connection = await this.connectionService.getById(this.agentContext, mediator.connectionId) + const { message, connectionRecord } = await this.connectionService.createTrustPing(this.agentContext, connection, { responseRequested: false, }) @@ -124,7 +129,7 @@ export class RecipientModule { throw new AriesFrameworkError('Cannot open websocket to connection without websocket service endpoint') } - await this.messageSender.sendMessage(createOutboundMessage(connectionRecord, message), { + await this.messageSender.sendMessage(this.agentContext, createOutboundMessage(connectionRecord, message), { transportPriority: { schemes: websocketSchemes, restrictive: true, @@ -148,7 +153,7 @@ export class RecipientModule { .observable(TransportEventTypes.OutboundWebSocketClosedEvent) .pipe( // Stop when the agent shuts down - takeUntil(this.agentConfig.stop$), + takeUntil(this.stop$), filter((e) => e.payload.connectionId === mediator.connectionId), // Make sure we're not reconnecting multiple times throttleTime(interval), @@ -182,21 +187,21 @@ export class RecipientModule { } public async initiateMessagePickup(mediator: MediationRecord) { - const { mediatorPollingInterval } = this.agentConfig + const { mediatorPollingInterval } = this.agentContext.config const mediatorPickupStrategy = await this.getPickupStrategyForMediator(mediator) - const mediatorConnection = await this.connectionService.getById(mediator.connectionId) + const mediatorConnection = await this.connectionService.getById(this.agentContext, mediator.connectionId) switch (mediatorPickupStrategy) { case MediatorPickupStrategy.PickUpV2: - this.agentConfig.logger.info(`Starting pickup of messages from mediator '${mediator.id}'`) + this.logger.info(`Starting pickup of messages from mediator '${mediator.id}'`) await this.openWebSocketAndPickUp(mediator, mediatorPickupStrategy) await this.sendStatusRequest({ mediatorId: mediator.id }) break case MediatorPickupStrategy.PickUpV1: { // Explicit means polling every X seconds with batch message - this.agentConfig.logger.info(`Starting explicit (batch) pickup of messages from mediator '${mediator.id}'`) + this.logger.info(`Starting explicit (batch) pickup of messages from mediator '${mediator.id}'`) const subscription = interval(mediatorPollingInterval) - .pipe(takeUntil(this.agentConfig.stop$)) + .pipe(takeUntil(this.stop$)) .subscribe(async () => { await this.pickupMessages(mediatorConnection) }) @@ -205,29 +210,30 @@ export class RecipientModule { case MediatorPickupStrategy.Implicit: // Implicit means sending ping once and keeping connection open. This requires a long-lived transport // such as WebSockets to work - this.agentConfig.logger.info(`Starting implicit pickup of messages from mediator '${mediator.id}'`) + this.logger.info(`Starting implicit pickup of messages from mediator '${mediator.id}'`) await this.openWebSocketAndPickUp(mediator, mediatorPickupStrategy) break default: - this.agentConfig.logger.info( - `Skipping pickup of messages from mediator '${mediator.id}' due to pickup strategy none` - ) + this.logger.info(`Skipping pickup of messages from mediator '${mediator.id}' due to pickup strategy none`) } } private async sendStatusRequest(config: { mediatorId: string; recipientKey?: string }) { - const mediationRecord = await this.mediationRecipientService.getById(config.mediatorId) + const mediationRecord = await this.mediationRecipientService.getById(this.agentContext, config.mediatorId) const statusRequestMessage = await this.mediationRecipientService.createStatusRequest(mediationRecord, { recipientKey: config.recipientKey, }) - const mediatorConnection = await this.connectionService.getById(mediationRecord.connectionId) - return this.messageSender.sendMessage(createOutboundMessage(mediatorConnection, statusRequestMessage)) + const mediatorConnection = await this.connectionService.getById(this.agentContext, mediationRecord.connectionId) + return this.messageSender.sendMessage( + this.agentContext, + createOutboundMessage(mediatorConnection, statusRequestMessage) + ) } private async getPickupStrategyForMediator(mediator: MediationRecord) { - let mediatorPickupStrategy = mediator.pickupStrategy ?? this.agentConfig.mediatorPickupStrategy + let mediatorPickupStrategy = mediator.pickupStrategy ?? this.agentContext.config.mediatorPickupStrategy // If mediator pickup strategy is not configured we try to query if batch pickup // is supported through the discover features protocol @@ -252,14 +258,14 @@ export class RecipientModule { // Store the result so it can be reused next time mediator.pickupStrategy = mediatorPickupStrategy - await this.mediationRepository.update(mediator) + await this.mediationRepository.update(this.agentContext, mediator) } return mediatorPickupStrategy } public async discoverMediation() { - return this.mediationRecipientService.discoverMediation() + return this.mediationRecipientService.discoverMediation(this.agentContext) } public async pickupMessages(mediatorConnection: ConnectionRecord, pickupStrategy?: MediatorPickupStrategy) { @@ -274,11 +280,14 @@ export class RecipientModule { } public async setDefaultMediator(mediatorRecord: MediationRecord) { - return this.mediationRecipientService.setDefaultMediator(mediatorRecord) + return this.mediationRecipientService.setDefaultMediator(this.agentContext, mediatorRecord) } public async requestMediation(connection: ConnectionRecord): Promise { - const { mediationRecord, message } = await this.mediationRecipientService.createRequest(connection) + const { mediationRecord, message } = await this.mediationRecipientService.createRequest( + this.agentContext, + connection + ) const outboundMessage = createOutboundMessage(connection, message) await this.sendMessage(outboundMessage) @@ -292,29 +301,32 @@ export class RecipientModule { } public async findByConnectionId(connectionId: string) { - return await this.mediationRecipientService.findByConnectionId(connectionId) + return await this.mediationRecipientService.findByConnectionId(this.agentContext, connectionId) } public async getMediators() { - return await this.mediationRecipientService.getMediators() + return await this.mediationRecipientService.getMediators(this.agentContext) } public async findDefaultMediator(): Promise { - return this.mediationRecipientService.findDefaultMediator() + return this.mediationRecipientService.findDefaultMediator(this.agentContext) } public async findDefaultMediatorConnection(): Promise { const mediatorRecord = await this.findDefaultMediator() if (mediatorRecord) { - return this.connectionService.getById(mediatorRecord.connectionId) + return this.connectionService.getById(this.agentContext, mediatorRecord.connectionId) } return null } public async requestAndAwaitGrant(connection: ConnectionRecord, timeoutMs = 10000): Promise { - const { mediationRecord, message } = await this.mediationRecipientService.createRequest(connection) + const { mediationRecord, message } = await this.mediationRecipientService.createRequest( + this.agentContext, + connection + ) // Create observable for event const observable = this.eventEmitter.observable(RoutingEventTypes.MediationStateChanged) @@ -354,22 +366,20 @@ export class RecipientModule { let mediation = await this.findByConnectionId(connection.id) if (!mediation) { - this.agentConfig.logger.info(`Requesting mediation for connection ${connection.id}`) + this.logger.info(`Requesting mediation for connection ${connection.id}`) mediation = await this.requestAndAwaitGrant(connection, 60000) // TODO: put timeout as a config parameter this.logger.debug('Mediation granted, setting as default mediator') await this.setDefaultMediator(mediation) this.logger.debug('Default mediator set') } else { - this.agentConfig.logger.debug( - `Mediator invitation has already been ${mediation.isReady ? 'granted' : 'requested'}` - ) + this.logger.debug(`Mediator invitation has already been ${mediation.isReady ? 'granted' : 'requested'}`) } return mediation } public async getRouting(options: GetRoutingOptions) { - return this.routingService.getRouting(options) + return this.routingService.getRouting(this.agentContext, options) } // Register handlers for the several messages for the mediator. diff --git a/packages/core/src/modules/routing/__tests__/mediation.test.ts b/packages/core/src/modules/routing/__tests__/mediation.test.ts index 42265474fa..c616bdd522 100644 --- a/packages/core/src/modules/routing/__tests__/mediation.test.ts +++ b/packages/core/src/modules/routing/__tests__/mediation.test.ts @@ -7,6 +7,7 @@ import { SubjectInboundTransport } from '../../../../../../tests/transport/Subje import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' import { getBaseConfig, waitForBasicMessage } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' +import { InjectionSymbols } from '../../../constants' import { sleep } from '../../../utils/sleep' import { ConnectionRecord, HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' @@ -35,7 +36,8 @@ describe('mediator establishment', () => { // We want to stop the mediator polling before the agent is shutdown. // FIXME: add a way to stop mediator polling from the public api, and make sure this is // being handled in the agent shutdown so we don't get any errors with wallets being closed. - recipientAgent.config.stop$.next(true) + const stop$ = recipientAgent.injectionContainer.resolve>(InjectionSymbols.Stop$) + stop$.next(true) await sleep(1000) await recipientAgent?.shutdown() diff --git a/packages/core/src/modules/routing/__tests__/pickup.test.ts b/packages/core/src/modules/routing/__tests__/pickup.test.ts index 0869fd5c53..9320ea85da 100644 --- a/packages/core/src/modules/routing/__tests__/pickup.test.ts +++ b/packages/core/src/modules/routing/__tests__/pickup.test.ts @@ -7,21 +7,17 @@ import { SubjectInboundTransport } from '../../../../../../tests/transport/Subje import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' import { getBaseConfig, waitForBasicMessage } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' -import { ConsoleLogger, LogLevel } from '../../../logger' import { HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' -const logger = new ConsoleLogger(LogLevel.info) -const recipientConfig = getBaseConfig('Mediation: Recipient', { +const recipientConfig = getBaseConfig('Mediation: Recipient Pickup', { autoAcceptConnections: true, indyLedgers: [], - logger, }) -const mediatorConfig = getBaseConfig('Mediation: Mediator', { +const mediatorConfig = getBaseConfig('Mediation: Mediator Pickup', { autoAcceptConnections: true, endpoints: ['rxjs:mediator'], indyLedgers: [], - logger, }) describe('E2E Pick Up protocol', () => { diff --git a/packages/core/src/modules/routing/handlers/ForwardHandler.ts b/packages/core/src/modules/routing/handlers/ForwardHandler.ts index 8755f8c1f1..4407480175 100644 --- a/packages/core/src/modules/routing/handlers/ForwardHandler.ts +++ b/packages/core/src/modules/routing/handlers/ForwardHandler.ts @@ -25,10 +25,16 @@ export class ForwardHandler implements Handler { public async handle(messageContext: HandlerInboundMessage) { const { encryptedMessage, mediationRecord } = await this.mediatorService.processForwardMessage(messageContext) - const connectionRecord = await this.connectionService.getById(mediationRecord.connectionId) + const connectionRecord = await this.connectionService.getById( + messageContext.agentContext, + mediationRecord.connectionId + ) // The message inside the forward message is packed so we just send the packed // message to the connection associated with it - await this.messageSender.sendPackage({ connection: connectionRecord, encryptedMessage }) + await this.messageSender.sendPackage(messageContext.agentContext, { + connection: connectionRecord, + encryptedMessage, + }) } } diff --git a/packages/core/src/modules/routing/handlers/KeylistUpdateHandler.ts b/packages/core/src/modules/routing/handlers/KeylistUpdateHandler.ts index 9d68d453ca..09dc1398bc 100644 --- a/packages/core/src/modules/routing/handlers/KeylistUpdateHandler.ts +++ b/packages/core/src/modules/routing/handlers/KeylistUpdateHandler.ts @@ -2,7 +2,6 @@ import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { MediatorService } from '../services/MediatorService' import { createOutboundMessage } from '../../../agent/helpers' -import { AriesFrameworkError } from '../../../error' import { KeylistUpdateMessage } from '../messages' export class KeylistUpdateHandler implements Handler { @@ -14,11 +13,7 @@ export class KeylistUpdateHandler implements Handler { } public async handle(messageContext: HandlerInboundMessage) { - const { message, connection } = messageContext - - if (!connection) { - throw new AriesFrameworkError(`No connection associated with incoming message with id ${message.id}`) - } + const connection = messageContext.assertReadyConnection() const response = await this.mediatorService.processKeylistUpdateRequest(messageContext) return createOutboundMessage(connection, response) diff --git a/packages/core/src/modules/routing/handlers/KeylistUpdateResponseHandler.ts b/packages/core/src/modules/routing/handlers/KeylistUpdateResponseHandler.ts index 23a0c4a96f..1b637dbd6f 100644 --- a/packages/core/src/modules/routing/handlers/KeylistUpdateResponseHandler.ts +++ b/packages/core/src/modules/routing/handlers/KeylistUpdateResponseHandler.ts @@ -12,9 +12,8 @@ export class KeylistUpdateResponseHandler implements Handler { } public async handle(messageContext: HandlerInboundMessage) { - if (!messageContext.connection) { - throw new Error(`Connection for verkey ${messageContext.recipientKey} not found!`) - } + messageContext.assertReadyConnection() + return await this.mediationRecipientService.processKeylistUpdateResults(messageContext) } } diff --git a/packages/core/src/modules/routing/handlers/MediationDenyHandler.ts b/packages/core/src/modules/routing/handlers/MediationDenyHandler.ts index fa32169a7b..ca6e163c11 100644 --- a/packages/core/src/modules/routing/handlers/MediationDenyHandler.ts +++ b/packages/core/src/modules/routing/handlers/MediationDenyHandler.ts @@ -12,9 +12,8 @@ export class MediationDenyHandler implements Handler { } public async handle(messageContext: HandlerInboundMessage) { - if (!messageContext.connection) { - throw new Error(`Connection for verkey ${messageContext.recipientKey} not found!`) - } + messageContext.assertReadyConnection() + await this.mediationRecipientService.processMediationDeny(messageContext) } } diff --git a/packages/core/src/modules/routing/handlers/MediationGrantHandler.ts b/packages/core/src/modules/routing/handlers/MediationGrantHandler.ts index 5706216fbb..5ac69e7c3f 100644 --- a/packages/core/src/modules/routing/handlers/MediationGrantHandler.ts +++ b/packages/core/src/modules/routing/handlers/MediationGrantHandler.ts @@ -12,9 +12,8 @@ export class MediationGrantHandler implements Handler { } public async handle(messageContext: HandlerInboundMessage) { - if (!messageContext.connection) { - throw new Error(`Connection for key ${messageContext.recipientKey} not found!`) - } + messageContext.assertReadyConnection() + await this.mediationRecipientService.processMediationGrant(messageContext) } } diff --git a/packages/core/src/modules/routing/handlers/MediationRequestHandler.ts b/packages/core/src/modules/routing/handlers/MediationRequestHandler.ts index 9a4b90ca7c..2cc2944668 100644 --- a/packages/core/src/modules/routing/handlers/MediationRequestHandler.ts +++ b/packages/core/src/modules/routing/handlers/MediationRequestHandler.ts @@ -1,31 +1,28 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { MediatorService } from '../services/MediatorService' import { createOutboundMessage } from '../../../agent/helpers' -import { AriesFrameworkError } from '../../../error' import { MediationRequestMessage } from '../messages/MediationRequestMessage' export class MediationRequestHandler implements Handler { private mediatorService: MediatorService - private agentConfig: AgentConfig public supportedMessages = [MediationRequestMessage] - public constructor(mediatorService: MediatorService, agentConfig: AgentConfig) { + public constructor(mediatorService: MediatorService) { this.mediatorService = mediatorService - this.agentConfig = agentConfig } public async handle(messageContext: HandlerInboundMessage) { - if (!messageContext.connection) { - throw new AriesFrameworkError(`Connection for verkey ${messageContext.recipientKey} not found!`) - } + const connection = messageContext.assertReadyConnection() const mediationRecord = await this.mediatorService.processMediationRequest(messageContext) - if (this.agentConfig.autoAcceptMediationRequests) { - const { message } = await this.mediatorService.createGrantMediationMessage(mediationRecord) - return createOutboundMessage(messageContext.connection, message) + if (messageContext.agentContext.config.autoAcceptMediationRequests) { + const { message } = await this.mediatorService.createGrantMediationMessage( + messageContext.agentContext, + mediationRecord + ) + return createOutboundMessage(connection, message) } } } diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts index 53791f4335..b1449f6af5 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts @@ -3,7 +3,6 @@ import type { AgentMessageReceivedEvent } from '../../../../../../agent/Events' import type { Handler, HandlerInboundMessage } from '../../../../../../agent/Handler' import { AgentEventTypes } from '../../../../../../agent/Events' -import { AriesFrameworkError } from '../../../../../../error' import { BatchMessage } from '../messages' export class BatchHandler implements Handler { @@ -15,15 +14,13 @@ export class BatchHandler implements Handler { } public async handle(messageContext: HandlerInboundMessage) { - const { message, connection } = messageContext + const { message } = messageContext - if (!connection) { - throw new AriesFrameworkError(`No connection associated with incoming message with id ${message.id}`) - } + messageContext.assertReadyConnection() const forwardedMessages = message.messages forwardedMessages.forEach((message) => { - this.eventEmitter.emit({ + this.eventEmitter.emit(messageContext.agentContext, { type: AgentEventTypes.AgentMessageReceived, payload: { message: message.message, diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchPickupHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchPickupHandler.ts index f5d8839ece..e854d23f42 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchPickupHandler.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchPickupHandler.ts @@ -1,7 +1,6 @@ import type { Handler, HandlerInboundMessage } from '../../../../../../agent/Handler' import type { MessagePickupService } from '../MessagePickupService' -import { AriesFrameworkError } from '../../../../../../error' import { BatchPickupMessage } from '../messages' export class BatchPickupHandler implements Handler { @@ -13,11 +12,7 @@ export class BatchPickupHandler implements Handler { } public async handle(messageContext: HandlerInboundMessage) { - const { message, connection } = messageContext - - if (!connection) { - throw new AriesFrameworkError(`No connection associated with incoming message with id ${message.id}`) - } + messageContext.assertReadyConnection() return this.messagePickupService.batch(messageContext) } diff --git a/packages/core/src/modules/routing/repository/MediationRepository.ts b/packages/core/src/modules/routing/repository/MediationRepository.ts index 9f149f46e0..e89c04aa11 100644 --- a/packages/core/src/modules/routing/repository/MediationRepository.ts +++ b/packages/core/src/modules/routing/repository/MediationRepository.ts @@ -1,3 +1,5 @@ +import type { AgentContext } from '../../../agent' + import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' import { inject, injectable } from '../../../plugins' @@ -15,13 +17,13 @@ export class MediationRepository extends Repository { super(MediationRecord, storageService, eventEmitter) } - public getSingleByRecipientKey(recipientKey: string) { - return this.getSingleByQuery({ + public getSingleByRecipientKey(agentContext: AgentContext, recipientKey: string) { + return this.getSingleByQuery(agentContext, { recipientKeys: [recipientKey], }) } - public async getByConnectionId(connectionId: string): Promise { - return this.getSingleByQuery({ connectionId }) + public async getByConnectionId(agentContext: AgentContext, connectionId: string): Promise { + return this.getSingleByQuery(agentContext, { connectionId }) } } diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index dc25dcb514..f9a8b50be9 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../../../agent' import type { AgentMessage } from '../../../agent/AgentMessage' import type { AgentMessageReceivedEvent } from '../../../agent/Events' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' @@ -12,7 +13,6 @@ import type { GetRoutingOptions } from './RoutingService' import { firstValueFrom, ReplaySubject } from 'rxjs' import { filter, first, timeout } from 'rxjs/operators' -import { AgentConfig } from '../../../agent/AgentConfig' import { EventEmitter } from '../../../agent/EventEmitter' import { AgentEventTypes } from '../../../agent/Events' import { MessageSender } from '../../../agent/MessageSender' @@ -22,6 +22,7 @@ import { AriesFrameworkError } from '../../../error' import { injectable } from '../../../plugins' import { JsonTransformer } from '../../../utils' import { ConnectionService } from '../../connections/services/ConnectionService' +import { didKeyToVerkey } from '../../dids/helpers' import { ProblemReportError } from '../../problem-reports' import { RoutingEventTypes } from '../RoutingEvents' import { RoutingProblemReportReason } from '../error' @@ -31,7 +32,6 @@ import { MediationRole, MediationState } from '../models' import { DeliveryRequestMessage, MessagesReceivedMessage, StatusRequestMessage } from '../protocol/pickup/v2/messages' import { MediationRecord } from '../repository/MediationRecord' import { MediationRepository } from '../repository/MediationRepository' -import { didKeyToVerkey } from '../../dids/helpers' @injectable() export class MediationRecipientService { @@ -39,16 +39,13 @@ export class MediationRecipientService { private eventEmitter: EventEmitter private connectionService: ConnectionService private messageSender: MessageSender - private config: AgentConfig public constructor( connectionService: ConnectionService, messageSender: MessageSender, - config: AgentConfig, mediatorRepository: MediationRepository, eventEmitter: EventEmitter ) { - this.config = config this.mediationRepository = mediatorRepository this.eventEmitter = eventEmitter this.connectionService = connectionService @@ -73,6 +70,7 @@ export class MediationRecipientService { } public async createRequest( + agentContext: AgentContext, connection: ConnectionRecord ): Promise> { const message = new MediationRequestMessage({}) @@ -83,8 +81,8 @@ export class MediationRecipientService { role: MediationRole.Recipient, connectionId: connection.id, }) - await this.mediationRepository.save(mediationRecord) - this.emitStateChangedEvent(mediationRecord, null) + await this.mediationRepository.save(agentContext, mediationRecord) + this.emitStateChangedEvent(agentContext, mediationRecord, null) return { mediationRecord, message } } @@ -94,7 +92,7 @@ export class MediationRecipientService { const connection = messageContext.assertReadyConnection() // Mediation record must already exists to be updated to granted status - const mediationRecord = await this.mediationRepository.getByConnectionId(connection.id) + const mediationRecord = await this.mediationRepository.getByConnectionId(messageContext.agentContext, connection.id) // Assert mediationRecord.assertState(MediationState.Requested) @@ -106,14 +104,14 @@ export class MediationRecipientService { // According to RFC 0211 keys should be a did key, but base58 encoded verkey was used before // RFC was accepted. This converts the key to a public key base58 if it is a did key. mediationRecord.routingKeys = messageContext.message.routingKeys.map(didKeyToVerkey) - return await this.updateState(mediationRecord, MediationState.Granted) + return await this.updateState(messageContext.agentContext, mediationRecord, MediationState.Granted) } public async processKeylistUpdateResults(messageContext: InboundMessageContext) { // Assert ready connection const connection = messageContext.assertReadyConnection() - const mediationRecord = await this.mediationRepository.getByConnectionId(connection.id) + const mediationRecord = await this.mediationRepository.getByConnectionId(messageContext.agentContext, connection.id) // Assert mediationRecord.assertReady() @@ -130,8 +128,8 @@ export class MediationRecipientService { } } - await this.mediationRepository.update(mediationRecord) - this.eventEmitter.emit({ + await this.mediationRepository.update(messageContext.agentContext, mediationRecord) + this.eventEmitter.emit(messageContext.agentContext, { type: RoutingEventTypes.RecipientKeylistUpdated, payload: { mediationRecord, @@ -141,12 +139,13 @@ export class MediationRecipientService { } public async keylistUpdateAndAwait( + agentContext: AgentContext, mediationRecord: MediationRecord, verKey: string, timeoutMs = 15000 // TODO: this should be a configurable value in agent config ): Promise { const message = this.createKeylistUpdateMessage(verKey) - const connection = await this.connectionService.getById(mediationRecord.connectionId) + const connection = await this.connectionService.getById(agentContext, mediationRecord.connectionId) mediationRecord.assertReady() mediationRecord.assertRole(MediationRole.Recipient) @@ -168,7 +167,7 @@ export class MediationRecipientService { .subscribe(subject) const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outboundMessage) + await this.messageSender.sendMessage(agentContext, outboundMessage) const keylistUpdate = await firstValueFrom(subject) return keylistUpdate.payload.mediationRecord @@ -187,24 +186,29 @@ export class MediationRecipientService { } public async addMediationRouting( + agentContext: AgentContext, routing: Routing, { mediatorId, useDefaultMediator = true }: GetRoutingOptions = {} ): Promise { let mediationRecord: MediationRecord | null = null if (mediatorId) { - mediationRecord = await this.getById(mediatorId) + mediationRecord = await this.getById(agentContext, mediatorId) } else if (useDefaultMediator) { // If no mediatorId is provided, and useDefaultMediator is true (default) // We use the default mediator if available - mediationRecord = await this.findDefaultMediator() + mediationRecord = await this.findDefaultMediator(agentContext) } // Return early if no mediation record if (!mediationRecord) return routing // new did has been created and mediator needs to be updated with the public key. - mediationRecord = await this.keylistUpdateAndAwait(mediationRecord, routing.recipientKey.publicKeyBase58) + mediationRecord = await this.keylistUpdateAndAwait( + agentContext, + mediationRecord, + routing.recipientKey.publicKeyBase58 + ) return { ...routing, @@ -217,7 +221,7 @@ export class MediationRecipientService { const connection = messageContext.assertReadyConnection() // Mediation record already exists - const mediationRecord = await this.findByConnectionId(connection.id) + const mediationRecord = await this.findByConnectionId(messageContext.agentContext, connection.id) if (!mediationRecord) { throw new Error(`No mediation has been requested for this connection id: ${connection.id}`) @@ -228,7 +232,7 @@ export class MediationRecipientService { mediationRecord.assertState(MediationState.Requested) // Update record - await this.updateState(mediationRecord, MediationState.Denied) + await this.updateState(messageContext.agentContext, mediationRecord, MediationState.Denied) return mediationRecord } @@ -240,26 +244,34 @@ export class MediationRecipientService { //No messages to be sent if (messageCount === 0) { - const { message, connectionRecord } = await this.connectionService.createTrustPing(connection, { - responseRequested: false, - }) + const { message, connectionRecord } = await this.connectionService.createTrustPing( + messageContext.agentContext, + connection, + { + responseRequested: false, + } + ) const websocketSchemes = ['ws', 'wss'] - await this.messageSender.sendMessage(createOutboundMessage(connectionRecord, message), { - transportPriority: { - schemes: websocketSchemes, - restrictive: true, - // TODO: add keepAlive: true to enforce through the public api - // we need to keep the socket alive. It already works this way, but would - // be good to make more explicit from the public facing API. - // This would also make it easier to change the internal API later on. - // keepAlive: true, - }, - }) + await this.messageSender.sendMessage( + messageContext.agentContext, + createOutboundMessage(connectionRecord, message), + { + transportPriority: { + schemes: websocketSchemes, + restrictive: true, + // TODO: add keepAlive: true to enforce through the public api + // we need to keep the socket alive. It already works this way, but would + // be good to make more explicit from the public facing API. + // This would also make it easier to change the internal API later on. + // keepAlive: true, + }, + } + ) return null } - const { maximumMessagePickup } = this.config + const { maximumMessagePickup } = messageContext.agentContext.config const limit = messageCount < maximumMessagePickup ? messageCount : maximumMessagePickup const deliveryRequestMessage = new DeliveryRequestMessage({ @@ -284,7 +296,7 @@ export class MediationRecipientService { for (const attachment of appendedAttachments) { ids.push(attachment.id) - this.eventEmitter.emit({ + this.eventEmitter.emit(messageContext.agentContext, { type: AgentEventTypes.AgentMessageReceived, payload: { message: attachment.getDataAsJson(), @@ -305,18 +317,22 @@ export class MediationRecipientService { * @param newState The state to update to * */ - private async updateState(mediationRecord: MediationRecord, newState: MediationState) { + private async updateState(agentContext: AgentContext, mediationRecord: MediationRecord, newState: MediationState) { const previousState = mediationRecord.state mediationRecord.state = newState - await this.mediationRepository.update(mediationRecord) + await this.mediationRepository.update(agentContext, mediationRecord) - this.emitStateChangedEvent(mediationRecord, previousState) + this.emitStateChangedEvent(agentContext, mediationRecord, previousState) return mediationRecord } - private emitStateChangedEvent(mediationRecord: MediationRecord, previousState: MediationState | null) { + private emitStateChangedEvent( + agentContext: AgentContext, + mediationRecord: MediationRecord, + previousState: MediationState | null + ) { const clonedMediationRecord = JsonTransformer.clone(mediationRecord) - this.eventEmitter.emit({ + this.eventEmitter.emit(agentContext, { type: RoutingEventTypes.MediationStateChanged, payload: { mediationRecord: clonedMediationRecord, @@ -325,29 +341,32 @@ export class MediationRecipientService { }) } - public async getById(id: string): Promise { - return this.mediationRepository.getById(id) + public async getById(agentContext: AgentContext, id: string): Promise { + return this.mediationRepository.getById(agentContext, id) } - public async findByConnectionId(connectionId: string): Promise { - return this.mediationRepository.findSingleByQuery({ connectionId }) + public async findByConnectionId(agentContext: AgentContext, connectionId: string): Promise { + return this.mediationRepository.findSingleByQuery(agentContext, { connectionId }) } - public async getMediators(): Promise { - return this.mediationRepository.getAll() + public async getMediators(agentContext: AgentContext): Promise { + return this.mediationRepository.getAll(agentContext) } - public async findDefaultMediator(): Promise { - return this.mediationRepository.findSingleByQuery({ default: true }) + public async findDefaultMediator(agentContext: AgentContext): Promise { + return this.mediationRepository.findSingleByQuery(agentContext, { default: true }) } - public async discoverMediation(mediatorId?: string): Promise { + public async discoverMediation( + agentContext: AgentContext, + mediatorId?: string + ): Promise { // If mediatorId is passed, always use it (and error if it is not found) if (mediatorId) { - return this.mediationRepository.getById(mediatorId) + return this.mediationRepository.getById(agentContext, mediatorId) } - const defaultMediator = await this.findDefaultMediator() + const defaultMediator = await this.findDefaultMediator(agentContext) if (defaultMediator) { if (defaultMediator.state !== MediationState.Granted) { throw new AriesFrameworkError( @@ -359,25 +378,25 @@ export class MediationRecipientService { } } - public async setDefaultMediator(mediator: MediationRecord) { - const mediationRecords = await this.mediationRepository.findByQuery({ default: true }) + public async setDefaultMediator(agentContext: AgentContext, mediator: MediationRecord) { + const mediationRecords = await this.mediationRepository.findByQuery(agentContext, { default: true }) for (const record of mediationRecords) { record.setTag('default', false) - await this.mediationRepository.update(record) + await this.mediationRepository.update(agentContext, record) } // Set record coming in tag to true and then update. mediator.setTag('default', true) - await this.mediationRepository.update(mediator) + await this.mediationRepository.update(agentContext, mediator) } - public async clearDefaultMediator() { - const mediationRecord = await this.findDefaultMediator() + public async clearDefaultMediator(agentContext: AgentContext) { + const mediationRecord = await this.findDefaultMediator(agentContext) if (mediationRecord) { mediationRecord.setTag('default', false) - await this.mediationRepository.update(mediationRecord) + await this.mediationRepository.update(agentContext, mediationRecord) } } } diff --git a/packages/core/src/modules/routing/services/MediatorService.ts b/packages/core/src/modules/routing/services/MediatorService.ts index 8e3e76db95..ed0d3a5485 100644 --- a/packages/core/src/modules/routing/services/MediatorService.ts +++ b/packages/core/src/modules/routing/services/MediatorService.ts @@ -1,23 +1,23 @@ +import type { AgentContext } from '../../../agent' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { EncryptedMessage } from '../../../types' import type { MediationStateChangedEvent } from '../RoutingEvents' import type { ForwardMessage, KeylistUpdateMessage, MediationRequestMessage } from '../messages' -import { AgentConfig } from '../../../agent/AgentConfig' import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' import { AriesFrameworkError } from '../../../error' -import { inject, injectable } from '../../../plugins' +import { Logger } from '../../../logger' +import { injectable, inject } from '../../../plugins' import { JsonTransformer } from '../../../utils/JsonTransformer' -import { Wallet } from '../../../wallet/Wallet' import { didKeyToVerkey } from '../../dids/helpers' import { RoutingEventTypes } from '../RoutingEvents' import { KeylistUpdateAction, - KeylistUpdateResult, KeylistUpdated, - MediationGrantMessage, KeylistUpdateResponseMessage, + KeylistUpdateResult, + MediationGrantMessage, } from '../messages' import { MediationRole } from '../models/MediationRole' import { MediationState } from '../models/MediationState' @@ -28,56 +28,32 @@ import { MediatorRoutingRepository } from '../repository/MediatorRoutingReposito @injectable() export class MediatorService { - private agentConfig: AgentConfig + private logger: Logger private mediationRepository: MediationRepository private mediatorRoutingRepository: MediatorRoutingRepository - private wallet: Wallet private eventEmitter: EventEmitter - private _mediatorRoutingRecord?: MediatorRoutingRecord public constructor( mediationRepository: MediationRepository, mediatorRoutingRepository: MediatorRoutingRepository, - agentConfig: AgentConfig, - @inject(InjectionSymbols.Wallet) wallet: Wallet, - eventEmitter: EventEmitter + eventEmitter: EventEmitter, + @inject(InjectionSymbols.Logger) logger: Logger ) { this.mediationRepository = mediationRepository this.mediatorRoutingRepository = mediatorRoutingRepository - this.agentConfig = agentConfig - this.wallet = wallet this.eventEmitter = eventEmitter + this.logger = logger } - public async initialize() { - this.agentConfig.logger.debug('Mediator routing record not loaded yet, retrieving from storage') - let routingRecord = await this.mediatorRoutingRepository.findById( - this.mediatorRoutingRepository.MEDIATOR_ROUTING_RECORD_ID - ) - - // If we don't have a routing record yet, create it - if (!routingRecord) { - this.agentConfig.logger.debug('Mediator routing record does not exist yet, creating routing keys and record') - const { verkey } = await this.wallet.createDid() - - routingRecord = new MediatorRoutingRecord({ - id: this.mediatorRoutingRepository.MEDIATOR_ROUTING_RECORD_ID, - routingKeys: [verkey], - }) - - await this.mediatorRoutingRepository.save(routingRecord) - } - - this._mediatorRoutingRecord = routingRecord - } + private async getRoutingKeys(agentContext: AgentContext) { + const mediatorRoutingRecord = await this.findMediatorRoutingRecord(agentContext) - private getRoutingKeys() { - if (this._mediatorRoutingRecord) { + if (mediatorRoutingRecord) { // Return the routing keys - this.agentConfig.logger.debug(`Returning mediator routing keys ${this._mediatorRoutingRecord.routingKeys}`) - return this._mediatorRoutingRecord.routingKeys + this.logger.debug(`Returning mediator routing keys ${mediatorRoutingRecord.routingKeys}`) + return mediatorRoutingRecord.routingKeys } - throw new AriesFrameworkError(`Mediation service has not been initialized yet.`) + throw new AriesFrameworkError(`Mediator has not been initialized yet.`) } public async processForwardMessage( @@ -90,7 +66,10 @@ export class MediatorService { throw new AriesFrameworkError('Invalid Message: Missing required attribute "to"') } - const mediationRecord = await this.mediationRepository.getSingleByRecipientKey(message.to) + const mediationRecord = await this.mediationRepository.getSingleByRecipientKey( + messageContext.agentContext, + message.to + ) // Assert mediation record is ready to be used mediationRecord.assertReady() @@ -109,7 +88,7 @@ export class MediatorService { const { message } = messageContext const keylist: KeylistUpdated[] = [] - const mediationRecord = await this.mediationRepository.getByConnectionId(connection.id) + const mediationRecord = await this.mediationRepository.getByConnectionId(messageContext.agentContext, connection.id) mediationRecord.assertReady() mediationRecord.assertRole(MediationRole.Mediator) @@ -137,21 +116,21 @@ export class MediatorService { } } - await this.mediationRepository.update(mediationRecord) + await this.mediationRepository.update(messageContext.agentContext, mediationRecord) return new KeylistUpdateResponseMessage({ keylist, threadId: message.threadId }) } - public async createGrantMediationMessage(mediationRecord: MediationRecord) { + public async createGrantMediationMessage(agentContext: AgentContext, mediationRecord: MediationRecord) { // Assert mediationRecord.assertState(MediationState.Requested) mediationRecord.assertRole(MediationRole.Mediator) - await this.updateState(mediationRecord, MediationState.Granted) + await this.updateState(agentContext, mediationRecord, MediationState.Granted) const message = new MediationGrantMessage({ - endpoint: this.agentConfig.endpoints[0], - routingKeys: this.getRoutingKeys(), + endpoint: agentContext.config.endpoints[0], + routingKeys: await this.getRoutingKeys(agentContext), threadId: mediationRecord.threadId, }) @@ -169,37 +148,63 @@ export class MediatorService { threadId: messageContext.message.threadId, }) - await this.mediationRepository.save(mediationRecord) - this.emitStateChangedEvent(mediationRecord, null) + await this.mediationRepository.save(messageContext.agentContext, mediationRecord) + this.emitStateChangedEvent(messageContext.agentContext, mediationRecord, null) return mediationRecord } - public async findById(mediatorRecordId: string): Promise { - return this.mediationRepository.findById(mediatorRecordId) + public async findById(agentContext: AgentContext, mediatorRecordId: string): Promise { + return this.mediationRepository.findById(agentContext, mediatorRecordId) } - public async getById(mediatorRecordId: string): Promise { - return this.mediationRepository.getById(mediatorRecordId) + public async getById(agentContext: AgentContext, mediatorRecordId: string): Promise { + return this.mediationRepository.getById(agentContext, mediatorRecordId) } - public async getAll(): Promise { - return await this.mediationRepository.getAll() + public async getAll(agentContext: AgentContext): Promise { + return await this.mediationRepository.getAll(agentContext) } - private async updateState(mediationRecord: MediationRecord, newState: MediationState) { + public async findMediatorRoutingRecord(agentContext: AgentContext): Promise { + const routingRecord = await this.mediatorRoutingRepository.findById( + agentContext, + this.mediatorRoutingRepository.MEDIATOR_ROUTING_RECORD_ID + ) + + return routingRecord + } + + public async createMediatorRoutingRecord(agentContext: AgentContext): Promise { + const { verkey } = await agentContext.wallet.createDid() + + const routingRecord = new MediatorRoutingRecord({ + id: this.mediatorRoutingRepository.MEDIATOR_ROUTING_RECORD_ID, + routingKeys: [verkey], + }) + + await this.mediatorRoutingRepository.save(agentContext, routingRecord) + + return routingRecord + } + + private async updateState(agentContext: AgentContext, mediationRecord: MediationRecord, newState: MediationState) { const previousState = mediationRecord.state mediationRecord.state = newState - await this.mediationRepository.update(mediationRecord) + await this.mediationRepository.update(agentContext, mediationRecord) - this.emitStateChangedEvent(mediationRecord, previousState) + this.emitStateChangedEvent(agentContext, mediationRecord, previousState) } - private emitStateChangedEvent(mediationRecord: MediationRecord, previousState: MediationState | null) { + private emitStateChangedEvent( + agentContext: AgentContext, + mediationRecord: MediationRecord, + previousState: MediationState | null + ) { const clonedMediationRecord = JsonTransformer.clone(mediationRecord) - this.eventEmitter.emit({ + this.eventEmitter.emit(agentContext, { type: RoutingEventTypes.MediationStateChanged, payload: { mediationRecord: clonedMediationRecord, diff --git a/packages/core/src/modules/routing/services/RoutingService.ts b/packages/core/src/modules/routing/services/RoutingService.ts index 134507b528..357cb05d3d 100644 --- a/packages/core/src/modules/routing/services/RoutingService.ts +++ b/packages/core/src/modules/routing/services/RoutingService.ts @@ -1,12 +1,10 @@ +import type { AgentContext } from '../../../agent' import type { Routing } from '../../connections' import type { RoutingCreatedEvent } from '../RoutingEvents' -import { AgentConfig } from '../../../agent/AgentConfig' import { EventEmitter } from '../../../agent/EventEmitter' -import { InjectionSymbols } from '../../../constants' import { Key, KeyType } from '../../../crypto' -import { inject, injectable } from '../../../plugins' -import { Wallet } from '../../../wallet' +import { injectable } from '../../../plugins' import { RoutingEventTypes } from '../RoutingEvents' import { MediationRecipientService } from './MediationRecipientService' @@ -14,42 +12,38 @@ import { MediationRecipientService } from './MediationRecipientService' @injectable() export class RoutingService { private mediationRecipientService: MediationRecipientService - private agentConfig: AgentConfig - private wallet: Wallet + private eventEmitter: EventEmitter - public constructor( - mediationRecipientService: MediationRecipientService, - agentConfig: AgentConfig, - @inject(InjectionSymbols.Wallet) wallet: Wallet, - eventEmitter: EventEmitter - ) { + public constructor(mediationRecipientService: MediationRecipientService, eventEmitter: EventEmitter) { this.mediationRecipientService = mediationRecipientService - this.agentConfig = agentConfig - this.wallet = wallet + this.eventEmitter = eventEmitter } - public async getRouting({ mediatorId, useDefaultMediator = true }: GetRoutingOptions = {}): Promise { + public async getRouting( + agentContext: AgentContext, + { mediatorId, useDefaultMediator = true }: GetRoutingOptions = {} + ): Promise { // Create and store new key - const { verkey: publicKeyBase58 } = await this.wallet.createDid() + const { verkey: publicKeyBase58 } = await agentContext.wallet.createDid() const recipientKey = Key.fromPublicKeyBase58(publicKeyBase58, KeyType.Ed25519) let routing: Routing = { - endpoints: this.agentConfig.endpoints, + endpoints: agentContext.config.endpoints, routingKeys: [], recipientKey, } // Extend routing with mediator keys (if applicable) - routing = await this.mediationRecipientService.addMediationRouting(routing, { + routing = await this.mediationRecipientService.addMediationRouting(agentContext, routing, { mediatorId, useDefaultMediator, }) // Emit event so other parts of the framework can react on keys created - this.eventEmitter.emit({ + this.eventEmitter.emit(agentContext, { type: RoutingEventTypes.RoutingCreatedEvent, payload: { routing, diff --git a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts index d266aabb20..7e4263512b 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts @@ -1,7 +1,8 @@ +import type { AgentContext } from '../../../../agent' import type { Wallet } from '../../../../wallet/Wallet' import type { Routing } from '../../../connections/services/ConnectionService' -import { getAgentConfig, getMockConnection, mockFunction } from '../../../../../tests/helpers' +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../tests/helpers' import { EventEmitter } from '../../../../agent/EventEmitter' import { AgentEventTypes } from '../../../../agent/Events' import { MessageSender } from '../../../../agent/MessageSender' @@ -58,9 +59,13 @@ describe('MediationRecipientService', () => { let messageSender: MessageSender let mediationRecipientService: MediationRecipientService let mediationRecord: MediationRecord + let agentContext: AgentContext beforeAll(async () => { - wallet = new IndyWallet(config) + wallet = new IndyWallet(config.agentDependencies, config.logger) + agentContext = getAgentContext({ + agentConfig: config, + }) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) }) @@ -73,7 +78,7 @@ describe('MediationRecipientService', () => { eventEmitter = new EventEmitterMock() connectionRepository = new ConnectionRepositoryMock() didRepository = new DidRepositoryMock() - connectionService = new ConnectionService(wallet, config, connectionRepository, didRepository, eventEmitter) + connectionService = new ConnectionService(config.logger, connectionRepository, didRepository, eventEmitter) mediationRepository = new MediationRepositoryMock() messageSender = new MessageSenderMock() @@ -89,7 +94,6 @@ describe('MediationRecipientService', () => { mediationRecipientService = new MediationRecipientService( connectionService, messageSender, - config, mediationRepository, eventEmitter ) @@ -104,7 +108,7 @@ describe('MediationRecipientService', () => { threadId: 'threadId', }) - const messageContext = new InboundMessageContext(mediationGrant, { connection: mockConnection }) + const messageContext = new InboundMessageContext(mediationGrant, { connection: mockConnection, agentContext }) await mediationRecipientService.processMediationGrant(messageContext) @@ -119,7 +123,7 @@ describe('MediationRecipientService', () => { threadId: 'threadId', }) - const messageContext = new InboundMessageContext(mediationGrant, { connection: mockConnection }) + const messageContext = new InboundMessageContext(mediationGrant, { connection: mockConnection, agentContext }) await mediationRecipientService.processMediationGrant(messageContext) @@ -138,20 +142,6 @@ describe('MediationRecipientService', () => { recipientKey: 'a-key', }) }) - - it('it throws an error when the mediation record has incorrect role or state', async () => { - mediationRecord.role = MediationRole.Mediator - await expect(mediationRecipientService.createStatusRequest(mediationRecord)).rejects.toThrowError( - 'Mediation record has invalid role MEDIATOR. Expected role RECIPIENT.' - ) - - mediationRecord.role = MediationRole.Recipient - mediationRecord.state = MediationState.Requested - - await expect(mediationRecipientService.createStatusRequest(mediationRecord)).rejects.toThrowError( - 'Mediation record is not ready to be used. Expected granted, found invalid state requested' - ) - }) }) describe('processStatus', () => { @@ -161,7 +151,7 @@ describe('MediationRecipientService', () => { messageCount: 0, }) - const messageContext = new InboundMessageContext(status, { connection: mockConnection }) + const messageContext = new InboundMessageContext(status, { connection: mockConnection, agentContext }) const deliveryRequestMessage = await mediationRecipientService.processStatus(messageContext) expect(deliveryRequestMessage).toBeNull() }) @@ -171,7 +161,7 @@ describe('MediationRecipientService', () => { threadId: uuid(), messageCount: 1, }) - const messageContext = new InboundMessageContext(status, { connection: mockConnection }) + const messageContext = new InboundMessageContext(status, { connection: mockConnection, agentContext }) const deliveryRequestMessage = await mediationRecipientService.processStatus(messageContext) expect(deliveryRequestMessage) @@ -181,7 +171,10 @@ describe('MediationRecipientService', () => { describe('processDelivery', () => { it('if the delivery has no attachments expect an error', async () => { - const messageContext = new InboundMessageContext({} as MessageDeliveryMessage, { connection: mockConnection }) + const messageContext = new InboundMessageContext({} as MessageDeliveryMessage, { + connection: mockConnection, + agentContext, + }) await expect(mediationRecipientService.processDelivery(messageContext)).rejects.toThrowError( new AriesFrameworkError('Error processing attachments') @@ -202,7 +195,10 @@ describe('MediationRecipientService', () => { }), ], }) - const messageContext = new InboundMessageContext(messageDeliveryMessage, { connection: mockConnection }) + const messageContext = new InboundMessageContext(messageDeliveryMessage, { + connection: mockConnection, + agentContext, + }) const messagesReceivedMessage = await mediationRecipientService.processDelivery(messageContext) @@ -236,18 +232,21 @@ describe('MediationRecipientService', () => { }), ], }) - const messageContext = new InboundMessageContext(messageDeliveryMessage, { connection: mockConnection }) + const messageContext = new InboundMessageContext(messageDeliveryMessage, { + connection: mockConnection, + agentContext, + }) await mediationRecipientService.processDelivery(messageContext) expect(eventEmitter.emit).toHaveBeenCalledTimes(2) - expect(eventEmitter.emit).toHaveBeenNthCalledWith(1, { + expect(eventEmitter.emit).toHaveBeenNthCalledWith(1, agentContext, { type: AgentEventTypes.AgentMessageReceived, payload: { message: { first: 'value' }, }, }) - expect(eventEmitter.emit).toHaveBeenNthCalledWith(2, { + expect(eventEmitter.emit).toHaveBeenNthCalledWith(2, agentContext, { type: AgentEventTypes.AgentMessageReceived, payload: { message: { second: 'value' }, @@ -281,7 +280,7 @@ describe('MediationRecipientService', () => { test('adds mediation routing id mediator id is passed', async () => { mockFunction(mediationRepository.getById).mockResolvedValue(mediationRecord) - const extendedRouting = await mediationRecipientService.addMediationRouting(routing, { + const extendedRouting = await mediationRecipientService.addMediationRouting(agentContext, routing, { mediatorId: 'mediator-id', }) @@ -289,14 +288,14 @@ describe('MediationRecipientService', () => { endpoints: ['https://a-mediator-endpoint.com'], routingKeys: [routingKey], }) - expect(mediationRepository.getById).toHaveBeenCalledWith('mediator-id') + expect(mediationRepository.getById).toHaveBeenCalledWith(agentContext, 'mediator-id') }) test('adds mediation routing if useDefaultMediator is true and default mediation is found', async () => { mockFunction(mediationRepository.findSingleByQuery).mockResolvedValue(mediationRecord) jest.spyOn(mediationRecipientService, 'keylistUpdateAndAwait').mockResolvedValue(mediationRecord) - const extendedRouting = await mediationRecipientService.addMediationRouting(routing, { + const extendedRouting = await mediationRecipientService.addMediationRouting(agentContext, routing, { useDefaultMediator: true, }) @@ -304,14 +303,14 @@ describe('MediationRecipientService', () => { endpoints: ['https://a-mediator-endpoint.com'], routingKeys: [routingKey], }) - expect(mediationRepository.findSingleByQuery).toHaveBeenCalledWith({ default: true }) + expect(mediationRepository.findSingleByQuery).toHaveBeenCalledWith(agentContext, { default: true }) }) test('does not add mediation routing if no mediation is found', async () => { mockFunction(mediationRepository.findSingleByQuery).mockResolvedValue(mediationRecord) jest.spyOn(mediationRecipientService, 'keylistUpdateAndAwait').mockResolvedValue(mediationRecord) - const extendedRouting = await mediationRecipientService.addMediationRouting(routing, { + const extendedRouting = await mediationRecipientService.addMediationRouting(agentContext, routing, { useDefaultMediator: false, }) diff --git a/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts index bd3d315294..2d963ec106 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts @@ -1,4 +1,6 @@ -import { getAgentConfig, getMockConnection, mockFunction } from '../../../../../tests/helpers' +import { Subject } from 'rxjs' + +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../tests/helpers' import { EventEmitter } from '../../../../agent/EventEmitter' import { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' import { IndyWallet } from '../../../../wallet/IndyWallet' @@ -21,17 +23,18 @@ const MediatorRoutingRepositoryMock = MediatorRoutingRepository as jest.Mock +const agentContext = getAgentContext({ + wallet: new WalletMock(), +}) + const mediationRepository = new MediationRepositoryMock() const mediatorRoutingRepository = new MediatorRoutingRepositoryMock() -const wallet = new WalletMock() - const mediatorService = new MediatorService( mediationRepository, mediatorRoutingRepository, - agentConfig, - wallet, - new EventEmitter(agentConfig) + new EventEmitter(agentConfig.agentDependencies, new Subject()), + agentConfig.logger ) const mockConnection = getMockConnection({ @@ -64,7 +67,7 @@ describe('MediatorService', () => { ], }) - const messageContext = new InboundMessageContext(keyListUpdate, { connection: mockConnection }) + const messageContext = new InboundMessageContext(keyListUpdate, { connection: mockConnection, agentContext }) await mediatorService.processKeylistUpdateRequest(messageContext) expect(mediationRecord.recipientKeys).toEqual(['79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ']) @@ -94,7 +97,7 @@ describe('MediatorService', () => { ], }) - const messageContext = new InboundMessageContext(keyListUpdate, { connection: mockConnection }) + const messageContext = new InboundMessageContext(keyListUpdate, { connection: mockConnection, agentContext }) await mediatorService.processKeylistUpdateRequest(messageContext) expect(mediationRecord.recipientKeys).toEqual(['79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ']) diff --git a/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts b/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts index 4a674a7f6d..c1360ed1df 100644 --- a/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts @@ -1,4 +1,6 @@ -import { getAgentConfig, mockFunction } from '../../../../../tests/helpers' +import { Subject } from 'rxjs' + +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' import { EventEmitter } from '../../../../agent/EventEmitter' import { Key } from '../../../../crypto' import { IndyWallet } from '../../../../wallet/IndyWallet' @@ -15,10 +17,14 @@ const MediationRecipientServiceMock = MediationRecipientService as jest.Mock { describe('getRouting', () => { test('calls mediation recipient service', async () => { - const routing = await routingService.getRouting({ + const routing = await routingService.getRouting(agentContext, { mediatorId: 'mediator-id', useDefaultMediator: true, }) - expect(mediationRecipientService.addMediationRouting).toHaveBeenCalledWith(routing, { + expect(mediationRecipientService.addMediationRouting).toHaveBeenCalledWith(agentContext, routing, { mediatorId: 'mediator-id', useDefaultMediator: true, }) @@ -55,7 +61,7 @@ describe('RoutingService', () => { const routingListener = jest.fn() eventEmitter.on(RoutingEventTypes.RoutingCreatedEvent, routingListener) - const routing = await routingService.getRouting() + const routing = await routingService.getRouting(agentContext) expect(routing).toEqual(routing) expect(routingListener).toHaveBeenCalledWith({ diff --git a/packages/core/src/modules/routing/services/__tests__/V2MessagePickupService.test.ts b/packages/core/src/modules/routing/services/__tests__/V2MessagePickupService.test.ts index c88bb7e7b2..e72ce7c425 100644 --- a/packages/core/src/modules/routing/services/__tests__/V2MessagePickupService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/V2MessagePickupService.test.ts @@ -1,7 +1,7 @@ import type { MessageRepository } from '../../../../storage/MessageRepository' import type { EncryptedMessage } from '../../../../types' -import { getMockConnection, mockFunction } from '../../../../../tests/helpers' +import { getAgentContext, getMockConnection, mockFunction } from '../../../../../tests/helpers' import { Dispatcher } from '../../../../agent/Dispatcher' import { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' import { InMemoryMessageRepository } from '../../../../storage/InMemoryMessageRepository' @@ -30,6 +30,8 @@ const MediationRecipientServiceMock = MediationRecipientService as jest.Mock const InMessageRepositoryMock = InMemoryMessageRepository as jest.Mock +const agentContext = getAgentContext() + const encryptedMessage: EncryptedMessage = { protected: 'base64url', iv: 'base64url', @@ -56,7 +58,7 @@ describe('V2MessagePickupService', () => { const statusRequest = new StatusRequestMessage({}) - const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection }) + const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) const { connection, payload } = await pickupService.processStatusRequest(messageContext) @@ -75,7 +77,7 @@ describe('V2MessagePickupService', () => { mockFunction(messageRepository.getAvailableMessageCount).mockResolvedValue(5) const statusRequest = new StatusRequestMessage({}) - const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection }) + const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) const { connection, payload } = await pickupService.processStatusRequest(messageContext) @@ -97,7 +99,7 @@ describe('V2MessagePickupService', () => { recipientKey: 'recipientKey', }) - const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection }) + const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) await expect(pickupService.processStatusRequest(messageContext)).rejects.toThrowError( 'recipient_key parameter not supported' @@ -111,7 +113,7 @@ describe('V2MessagePickupService', () => { const deliveryRequest = new DeliveryRequestMessage({ limit: 10 }) - const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection }) + const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection, agentContext }) const { connection, payload } = await pickupService.processDeliveryRequest(messageContext) @@ -131,7 +133,7 @@ describe('V2MessagePickupService', () => { const deliveryRequest = new DeliveryRequestMessage({ limit: 10 }) - const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection }) + const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection, agentContext }) const { connection, payload } = await pickupService.processDeliveryRequest(messageContext) @@ -158,7 +160,7 @@ describe('V2MessagePickupService', () => { const deliveryRequest = new DeliveryRequestMessage({ limit: 2 }) - const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection }) + const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection, agentContext }) const { connection, payload } = await pickupService.processDeliveryRequest(messageContext) @@ -188,7 +190,7 @@ describe('V2MessagePickupService', () => { recipientKey: 'recipientKey', }) - const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection }) + const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) await expect(pickupService.processStatusRequest(messageContext)).rejects.toThrowError( 'recipient_key parameter not supported' @@ -205,7 +207,7 @@ describe('V2MessagePickupService', () => { messageIdList: ['1', '2'], }) - const messageContext = new InboundMessageContext(messagesReceived, { connection: mockConnection }) + const messageContext = new InboundMessageContext(messagesReceived, { connection: mockConnection, agentContext }) const { connection, payload } = await pickupService.processMessagesReceived(messageContext) @@ -229,7 +231,7 @@ describe('V2MessagePickupService', () => { messageIdList: ['1', '2'], }) - const messageContext = new InboundMessageContext(messagesReceived, { connection: mockConnection }) + const messageContext = new InboundMessageContext(messagesReceived, { connection: mockConnection, agentContext }) const { connection, payload } = await pickupService.processMessagesReceived(messageContext) diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index c1bf89d930..5c724388a3 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -1,5 +1,6 @@ +import type { AgentContext } from '../..' import type { Key } from '../../crypto/Key' -import type { DocumentLoaderResult } from './jsonldUtil' +import type { DocumentLoader } from './jsonldUtil' import type { W3cVerifyCredentialResult } from './models' import type { CreatePresentationOptions, @@ -12,15 +13,11 @@ import type { } from './models/W3cCredentialServiceOptions' import type { VerifyPresentationResult } from './models/presentation/VerifyPresentationResult' -import { inject } from 'tsyringe' - -import { InjectionSymbols } from '../../constants' import { createWalletKeyPairClass } from '../../crypto/WalletKeyPair' import { AriesFrameworkError } from '../../error' import { injectable } from '../../plugins' import { JsonTransformer } from '../../utils' import { isNodeJS, isReactNative } from '../../utils/environment' -import { Wallet } from '../../wallet' import { DidResolverService, VerificationMethod } from '../dids' import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type' @@ -36,17 +33,11 @@ import { deriveProof } from './signature-suites/bbs' @injectable() export class W3cCredentialService { - private wallet: Wallet private w3cCredentialRepository: W3cCredentialRepository private didResolver: DidResolverService private suiteRegistry: SignatureSuiteRegistry - public constructor( - @inject(InjectionSymbols.Wallet) wallet: Wallet, - w3cCredentialRepository: W3cCredentialRepository, - didResolver: DidResolverService - ) { - this.wallet = wallet + public constructor(w3cCredentialRepository: W3cCredentialRepository, didResolver: DidResolverService) { this.w3cCredentialRepository = w3cCredentialRepository this.didResolver = didResolver this.suiteRegistry = new SignatureSuiteRegistry() @@ -58,10 +49,13 @@ export class W3cCredentialService { * @param credential the credential to be signed * @returns the signed credential */ - public async signCredential(options: SignCredentialOptions): Promise { - const WalletKeyPair = createWalletKeyPairClass(this.wallet) + public async signCredential( + agentContext: AgentContext, + options: SignCredentialOptions + ): Promise { + const WalletKeyPair = createWalletKeyPairClass(agentContext.wallet) - const signingKey = await this.getPublicKeyFromVerificationMethod(options.verificationMethod) + const signingKey = await this.getPublicKeyFromVerificationMethod(agentContext, options.verificationMethod) const suiteInfo = this.suiteRegistry.getByProofType(options.proofType) if (signingKey.keyType !== suiteInfo.keyType) { @@ -72,7 +66,7 @@ export class W3cCredentialService { controller: options.credential.issuerId, // should we check this against the verificationMethod.controller? id: options.verificationMethod, key: signingKey, - wallet: this.wallet, + wallet: agentContext.wallet, }) const SuiteClass = suiteInfo.suiteClass @@ -91,7 +85,7 @@ export class W3cCredentialService { credential: JsonTransformer.toJSON(options.credential), suite: suite, purpose: options.proofPurpose, - documentLoader: this.documentLoader, + documentLoader: this.documentLoaderWithContext(agentContext), }) return JsonTransformer.fromJSON(result, W3cVerifiableCredential) @@ -103,13 +97,16 @@ export class W3cCredentialService { * @param credential the credential to be verified * @returns the verification result */ - public async verifyCredential(options: VerifyCredentialOptions): Promise { - const suites = this.getSignatureSuitesForCredential(options.credential) + public async verifyCredential( + agentContext: AgentContext, + options: VerifyCredentialOptions + ): Promise { + const suites = this.getSignatureSuitesForCredential(agentContext, options.credential) const verifyOptions: Record = { credential: JsonTransformer.toJSON(options.credential), suite: suites, - documentLoader: this.documentLoader, + documentLoader: this.documentLoaderWithContext(agentContext), } // this is a hack because vcjs throws if purpose is passed as undefined or null @@ -152,9 +149,12 @@ export class W3cCredentialService { * @param presentation the presentation to be signed * @returns the signed presentation */ - public async signPresentation(options: SignPresentationOptions): Promise { + public async signPresentation( + agentContext: AgentContext, + options: SignPresentationOptions + ): Promise { // create keyPair - const WalletKeyPair = createWalletKeyPairClass(this.wallet) + const WalletKeyPair = createWalletKeyPairClass(agentContext.wallet) const suiteInfo = this.suiteRegistry.getByProofType(options.signatureType) @@ -162,13 +162,14 @@ export class W3cCredentialService { throw new AriesFrameworkError(`The requested proofType ${options.signatureType} is not supported`) } - const signingKey = await this.getPublicKeyFromVerificationMethod(options.verificationMethod) + const signingKey = await this.getPublicKeyFromVerificationMethod(agentContext, options.verificationMethod) if (signingKey.keyType !== suiteInfo.keyType) { throw new AriesFrameworkError('The key type of the verification method does not match the suite') } - const verificationMethodObject = (await this.documentLoader(options.verificationMethod)).document as Record< + const documentLoader = this.documentLoaderWithContext(agentContext) + const verificationMethodObject = (await documentLoader(options.verificationMethod)).document as Record< string, unknown > @@ -177,7 +178,7 @@ export class W3cCredentialService { controller: verificationMethodObject['controller'] as string, id: options.verificationMethod, key: signingKey, - wallet: this.wallet, + wallet: agentContext.wallet, }) const suite = new suiteInfo.suiteClass({ @@ -194,7 +195,7 @@ export class W3cCredentialService { presentation: JsonTransformer.toJSON(options.presentation), suite: suite, challenge: options.challenge, - documentLoader: this.documentLoader, + documentLoader: this.documentLoaderWithContext(agentContext), }) return JsonTransformer.fromJSON(result, W3cVerifiablePresentation) @@ -206,9 +207,12 @@ export class W3cCredentialService { * @param presentation the presentation to be verified * @returns the verification result */ - public async verifyPresentation(options: VerifyPresentationOptions): Promise { + public async verifyPresentation( + agentContext: AgentContext, + options: VerifyPresentationOptions + ): Promise { // create keyPair - const WalletKeyPair = createWalletKeyPairClass(this.wallet) + const WalletKeyPair = createWalletKeyPairClass(agentContext.wallet) let proofs = options.presentation.proof @@ -235,14 +239,16 @@ export class W3cCredentialService { ? options.presentation.verifiableCredential : [options.presentation.verifiableCredential] - const credentialSuites = credentials.map((credential) => this.getSignatureSuitesForCredential(credential)) + const credentialSuites = credentials.map((credential) => + this.getSignatureSuitesForCredential(agentContext, credential) + ) const allSuites = presentationSuites.concat(...credentialSuites) const verifyOptions: Record = { presentation: JsonTransformer.toJSON(options.presentation), suite: allSuites, challenge: options.challenge, - documentLoader: this.documentLoader, + documentLoader: this.documentLoaderWithContext(agentContext), } // this is a hack because vcjs throws if purpose is passed as undefined or null @@ -255,7 +261,7 @@ export class W3cCredentialService { return result as unknown as VerifyPresentationResult } - public async deriveProof(options: DeriveProofOptions): Promise { + public async deriveProof(agentContext: AgentContext, options: DeriveProofOptions): Promise { const suiteInfo = this.suiteRegistry.getByProofType('BbsBlsSignatureProof2020') const SuiteClass = suiteInfo.suiteClass @@ -263,48 +269,54 @@ export class W3cCredentialService { const proof = await deriveProof(JsonTransformer.toJSON(options.credential), options.revealDocument, { suite: suite, - documentLoader: this.documentLoader, + documentLoader: this.documentLoaderWithContext(agentContext), }) return proof } - public documentLoader = async (url: string): Promise => { - if (url.startsWith('did:')) { - const result = await this.didResolver.resolve(url) + public documentLoaderWithContext = (agentContext: AgentContext): DocumentLoader => { + return async (url: string) => { + if (url.startsWith('did:')) { + const result = await this.didResolver.resolve(agentContext, url) - if (result.didResolutionMetadata.error || !result.didDocument) { - throw new AriesFrameworkError(`Unable to resolve DID: ${url}`) - } + if (result.didResolutionMetadata.error || !result.didDocument) { + throw new AriesFrameworkError(`Unable to resolve DID: ${url}`) + } - const framed = await jsonld.frame(result.didDocument.toJSON(), { - '@context': result.didDocument.context, - '@embed': '@never', - id: url, - }) + const framed = await jsonld.frame(result.didDocument.toJSON(), { + '@context': result.didDocument.context, + '@embed': '@never', + id: url, + }) - return { - contextUrl: null, - documentUrl: url, - document: framed, + return { + contextUrl: null, + documentUrl: url, + document: framed, + } } - } - let loader + let loader - if (isNodeJS()) { - loader = documentLoaderNode.apply(jsonld, []) - } else if (isReactNative()) { - loader = documentLoaderXhr.apply(jsonld, []) - } else { - throw new AriesFrameworkError('Unsupported environment') - } + if (isNodeJS()) { + loader = documentLoaderNode.apply(jsonld, []) + } else if (isReactNative()) { + loader = documentLoaderXhr.apply(jsonld, []) + } else { + throw new AriesFrameworkError('Unsupported environment') + } - return await loader(url) + return await loader(url) + } } - private async getPublicKeyFromVerificationMethod(verificationMethod: string): Promise { - const verificationMethodObject = await this.documentLoader(verificationMethod) + private async getPublicKeyFromVerificationMethod( + agentContext: AgentContext, + verificationMethod: string + ): Promise { + const documentLoader = this.documentLoaderWithContext(agentContext) + const verificationMethodObject = await documentLoader(verificationMethod) const verificationMethodClass = JsonTransformer.fromJSON(verificationMethodObject.document, VerificationMethod) const key = getKeyDidMappingByVerificationMethod(verificationMethodClass) @@ -318,10 +330,15 @@ export class W3cCredentialService { * @param record the credential to be stored * @returns the credential record that was written to storage */ - public async storeCredential(options: StoreCredentialOptions): Promise { + public async storeCredential( + agentContext: AgentContext, + options: StoreCredentialOptions + ): Promise { // Get the expanded types const expandedTypes = ( - await jsonld.expand(JsonTransformer.toJSON(options.record), { documentLoader: this.documentLoader }) + await jsonld.expand(JsonTransformer.toJSON(options.record), { + documentLoader: this.documentLoaderWithContext(agentContext), + }) )[0]['@type'] // Create an instance of the w3cCredentialRecord @@ -331,36 +348,38 @@ export class W3cCredentialService { }) // Store the w3c credential record - await this.w3cCredentialRepository.save(w3cCredentialRecord) + await this.w3cCredentialRepository.save(agentContext, w3cCredentialRecord) return w3cCredentialRecord } - public async getAllCredentials(): Promise { - const allRecords = await this.w3cCredentialRepository.getAll() + public async getAllCredentials(agentContext: AgentContext): Promise { + const allRecords = await this.w3cCredentialRepository.getAll(agentContext) return allRecords.map((record) => record.credential) } - public async getCredentialById(id: string): Promise { - return (await this.w3cCredentialRepository.getById(id)).credential + public async getCredentialById(agentContext: AgentContext, id: string): Promise { + return (await this.w3cCredentialRepository.getById(agentContext, id)).credential } public async findCredentialsByQuery( - query: Parameters[0] + agentContext: AgentContext, + query: Parameters[1] ): Promise { - const result = await this.w3cCredentialRepository.findByQuery(query) + const result = await this.w3cCredentialRepository.findByQuery(agentContext, query) return result.map((record) => record.credential) } public async findSingleCredentialByQuery( - query: Parameters[0] + agentContext: AgentContext, + query: Parameters[1] ): Promise { - const result = await this.w3cCredentialRepository.findSingleByQuery(query) + const result = await this.w3cCredentialRepository.findSingleByQuery(agentContext, query) return result?.credential } - private getSignatureSuitesForCredential(credential: W3cVerifiableCredential) { - const WalletKeyPair = createWalletKeyPairClass(this.wallet) + private getSignatureSuitesForCredential(agentContext: AgentContext, credential: W3cVerifiableCredential) { + const WalletKeyPair = createWalletKeyPairClass(agentContext.wallet) let proofs = credential.proof diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index 20a28c5e19..b9a8e9d2a8 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -1,6 +1,6 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' +import type { AgentContext } from '../../../agent' -import { getAgentConfig } from '../../../../tests/helpers' +import { getAgentConfig, getAgentContext } from '../../../../tests/helpers' import { KeyType } from '../../../crypto' import { Key } from '../../../crypto/Key' import { JsonTransformer } from '../../../utils/JsonTransformer' @@ -30,23 +30,32 @@ const DidRepositoryMock = DidRepository as unknown as jest.Mock jest.mock('../repository/W3cCredentialRepository') const W3cCredentialRepositoryMock = W3cCredentialRepository as jest.Mock +const agentConfig = getAgentConfig('W3cCredentialServiceTest') + describe('W3cCredentialService', () => { let wallet: IndyWallet - let agentConfig: AgentConfig + let agentContext: AgentContext let didResolverService: DidResolverService let w3cCredentialService: W3cCredentialService let w3cCredentialRepository: W3cCredentialRepository const seed = 'testseed000000000000000000000001' beforeAll(async () => { - agentConfig = getAgentConfig('W3cCredentialServiceTest') - wallet = new IndyWallet(agentConfig) + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) - didResolverService = new DidResolverService(agentConfig, new IndyLedgerServiceMock(), new DidRepositoryMock()) + agentContext = getAgentContext({ + agentConfig, + wallet, + }) + didResolverService = new DidResolverService( + new IndyLedgerServiceMock(), + new DidRepositoryMock(), + agentConfig.logger + ) w3cCredentialRepository = new W3cCredentialRepositoryMock() - w3cCredentialService = new W3cCredentialService(wallet, w3cCredentialRepository, didResolverService) - w3cCredentialService.documentLoader = customDocumentLoader + w3cCredentialService = new W3cCredentialService(w3cCredentialRepository, didResolverService) + w3cCredentialService.documentLoaderWithContext = () => customDocumentLoader }) afterAll(async () => { @@ -69,7 +78,7 @@ describe('W3cCredentialService', () => { const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) - const vc = await w3cCredentialService.signCredential({ + const vc = await w3cCredentialService.signCredential(agentContext, { credential, proofType: 'Ed25519Signature2018', verificationMethod: verificationMethod, @@ -91,7 +100,7 @@ describe('W3cCredentialService', () => { const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) expect(async () => { - await w3cCredentialService.signCredential({ + await w3cCredentialService.signCredential(agentContext, { credential, proofType: 'Ed25519Signature2018', verificationMethod: @@ -106,7 +115,7 @@ describe('W3cCredentialService', () => { Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, W3cVerifiableCredential ) - const result = await w3cCredentialService.verifyCredential({ credential: vc }) + const result = await w3cCredentialService.verifyCredential(agentContext, { credential: vc }) expect(result.verified).toBe(true) expect(result.error).toBeUndefined() @@ -121,7 +130,7 @@ describe('W3cCredentialService', () => { Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_BAD_SIGNED, W3cVerifiableCredential ) - const result = await w3cCredentialService.verifyCredential({ credential: vc }) + const result = await w3cCredentialService.verifyCredential(agentContext, { credential: vc }) expect(result.verified).toBe(false) expect(result.error).toBeDefined() @@ -141,7 +150,7 @@ describe('W3cCredentialService', () => { } const vc = JsonTransformer.fromJSON(vcJson, W3cVerifiableCredential) - const result = await w3cCredentialService.verifyCredential({ credential: vc }) + const result = await w3cCredentialService.verifyCredential(agentContext, { credential: vc }) expect(result.verified).toBe(false) @@ -163,7 +172,7 @@ describe('W3cCredentialService', () => { } const vc = JsonTransformer.fromJSON(vcJson, W3cVerifiableCredential) - const result = await w3cCredentialService.verifyCredential({ credential: vc }) + const result = await w3cCredentialService.verifyCredential(agentContext, { credential: vc }) expect(result.verified).toBe(false) @@ -220,7 +229,7 @@ describe('W3cCredentialService', () => { date: new Date().toISOString(), }) - const verifiablePresentation = await w3cCredentialService.signPresentation({ + const verifiablePresentation = await w3cCredentialService.signPresentation(agentContext, { presentation: presentation, purpose: purpose, signatureType: 'Ed25519Signature2018', @@ -238,7 +247,7 @@ describe('W3cCredentialService', () => { W3cVerifiablePresentation ) - const result = await w3cCredentialService.verifyPresentation({ + const result = await w3cCredentialService.verifyPresentation(agentContext, { presentation: vp, proofType: 'Ed25519Signature2018', challenge: '7bf32d0b-39d4-41f3-96b6-45de52988e4c', @@ -256,7 +265,7 @@ describe('W3cCredentialService', () => { W3cVerifiableCredential ) - const w3cCredentialRecord = await w3cCredentialService.storeCredential({ record: credential }) + const w3cCredentialRecord = await w3cCredentialService.storeCredential(agentContext, { record: credential }) expect(w3cCredentialRecord).toMatchObject({ type: 'W3cCredentialRecord', @@ -290,7 +299,7 @@ describe('W3cCredentialService', () => { const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) - const vc = await w3cCredentialService.signCredential({ + const vc = await w3cCredentialService.signCredential(agentContext, { credential, proofType: 'BbsBlsSignature2020', verificationMethod: verificationMethod, @@ -307,7 +316,7 @@ describe('W3cCredentialService', () => { }) describe('verifyCredential', () => { it('should verify the credential successfully', async () => { - const result = await w3cCredentialService.verifyCredential({ + const result = await w3cCredentialService.verifyCredential(agentContext, { credential: JsonTransformer.fromJSON( BbsBlsSignature2020Fixtures.TEST_LD_DOCUMENT_SIGNED, W3cVerifiableCredential @@ -340,7 +349,7 @@ describe('W3cCredentialService', () => { }, } - const result = await w3cCredentialService.deriveProof({ + const result = await w3cCredentialService.deriveProof(agentContext, { credential: vc, revealDocument: revealDocument, verificationMethod: verificationMethod, @@ -354,7 +363,7 @@ describe('W3cCredentialService', () => { }) describe('verifyDerived', () => { it('should verify the derived proof successfully', async () => { - const result = await w3cCredentialService.verifyCredential({ + const result = await w3cCredentialService.verifyCredential(agentContext, { credential: JsonTransformer.fromJSON(BbsBlsSignature2020Fixtures.TEST_VALID_DERIVED, W3cVerifiableCredential), proofPurpose: new purposes.AssertionProofPurpose(), }) @@ -388,7 +397,7 @@ describe('W3cCredentialService', () => { date: new Date().toISOString(), }) - const verifiablePresentation = await w3cCredentialService.signPresentation({ + const verifiablePresentation = await w3cCredentialService.signPresentation(agentContext, { presentation: presentation, purpose: purpose, signatureType: 'Ed25519Signature2018', @@ -406,7 +415,7 @@ describe('W3cCredentialService', () => { W3cVerifiablePresentation ) - const result = await w3cCredentialService.verifyPresentation({ + const result = await w3cCredentialService.verifyPresentation(agentContext, { presentation: vp, proofType: 'Ed25519Signature2018', challenge: 'e950bfe5-d7ec-4303-ad61-6983fb976ac9', diff --git a/packages/core/src/storage/InMemoryMessageRepository.ts b/packages/core/src/storage/InMemoryMessageRepository.ts index c73a873476..a1f01b6515 100644 --- a/packages/core/src/storage/InMemoryMessageRepository.ts +++ b/packages/core/src/storage/InMemoryMessageRepository.ts @@ -1,17 +1,17 @@ -import type { Logger } from '../logger' import type { EncryptedMessage } from '../types' import type { MessageRepository } from './MessageRepository' -import { AgentConfig } from '../agent/AgentConfig' -import { injectable } from '../plugins' +import { InjectionSymbols } from '../constants' +import { Logger } from '../logger' +import { injectable, inject } from '../plugins' @injectable() export class InMemoryMessageRepository implements MessageRepository { private logger: Logger private messages: { [key: string]: EncryptedMessage[] } = {} - public constructor(agentConfig: AgentConfig) { - this.logger = agentConfig.logger + public constructor(@inject(InjectionSymbols.Logger) logger: Logger) { + this.logger = logger } public getAvailableMessageCount(connectionId: string): number | Promise { diff --git a/packages/core/src/storage/IndyStorageService.ts b/packages/core/src/storage/IndyStorageService.ts index f478dc1fea..a66cb579fd 100644 --- a/packages/core/src/storage/IndyStorageService.ts +++ b/packages/core/src/storage/IndyStorageService.ts @@ -1,18 +1,20 @@ +import type { AgentContext } from '../agent' +import type { IndyWallet } from '../wallet/IndyWallet' import type { BaseRecord, TagsBase } from './BaseRecord' -import type { StorageService, BaseRecordConstructor, Query } from './StorageService' +import type { BaseRecordConstructor, Query, StorageService } from './StorageService' import type { default as Indy, WalletQuery, WalletRecord, WalletSearchOptions } from 'indy-sdk' -import { AgentConfig } from '../agent/AgentConfig' -import { RecordNotFoundError, RecordDuplicateError, IndySdkError } from '../error' -import { injectable } from '../plugins' +import { AgentDependencies } from '../agent/AgentDependencies' +import { InjectionSymbols } from '../constants' +import { IndySdkError, RecordDuplicateError, RecordNotFoundError } from '../error' +import { injectable, inject } from '../plugins' import { JsonTransformer } from '../utils/JsonTransformer' import { isIndyError } from '../utils/indyError' import { isBoolean } from '../utils/type' -import { IndyWallet } from '../wallet/IndyWallet' +import { assertIndyWallet } from '../wallet/util/assertIndyWallet' @injectable() export class IndyStorageService implements StorageService { - private wallet: IndyWallet private indy: typeof Indy private static DEFAULT_QUERY_OPTIONS = { @@ -20,9 +22,8 @@ export class IndyStorageService implements StorageService< retrieveTags: true, } - public constructor(wallet: IndyWallet, agentConfig: AgentConfig) { - this.wallet = wallet - this.indy = agentConfig.agentDependencies.indy + public constructor(@inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies) { + this.indy = agentDependencies.indy } private transformToRecordTagValues(tags: { [key: number]: string | undefined }): TagsBase { @@ -133,12 +134,14 @@ export class IndyStorageService implements StorageService< } /** @inheritDoc */ - public async save(record: T) { + public async save(agentContext: AgentContext, record: T) { + assertIndyWallet(agentContext.wallet) + const value = JsonTransformer.serialize(record) const tags = this.transformFromRecordTagValues(record.getTags()) as Record try { - await this.indy.addWalletRecord(this.wallet.handle, record.type, record.id, value, tags) + await this.indy.addWalletRecord(agentContext.wallet.handle, record.type, record.id, value, tags) } catch (error) { // Record already exists if (isIndyError(error, 'WalletItemAlreadyExists')) { @@ -150,13 +153,15 @@ export class IndyStorageService implements StorageService< } /** @inheritDoc */ - public async update(record: T): Promise { + public async update(agentContext: AgentContext, record: T): Promise { + assertIndyWallet(agentContext.wallet) + const value = JsonTransformer.serialize(record) const tags = this.transformFromRecordTagValues(record.getTags()) as Record try { - await this.indy.updateWalletRecordValue(this.wallet.handle, record.type, record.id, value) - await this.indy.updateWalletRecordTags(this.wallet.handle, record.type, record.id, tags) + await this.indy.updateWalletRecordValue(agentContext.wallet.handle, record.type, record.id, value) + await this.indy.updateWalletRecordTags(agentContext.wallet.handle, record.type, record.id, tags) } catch (error) { // Record does not exist if (isIndyError(error, 'WalletItemNotFound')) { @@ -171,9 +176,11 @@ export class IndyStorageService implements StorageService< } /** @inheritDoc */ - public async delete(record: T) { + public async delete(agentContext: AgentContext, record: T) { + assertIndyWallet(agentContext.wallet) + try { - await this.indy.deleteWalletRecord(this.wallet.handle, record.type, record.id) + await this.indy.deleteWalletRecord(agentContext.wallet.handle, record.type, record.id) } catch (error) { // Record does not exist if (isIndyError(error, 'WalletItemNotFound')) { @@ -188,9 +195,15 @@ export class IndyStorageService implements StorageService< } /** @inheritDoc */ - public async deleteById(recordClass: BaseRecordConstructor, id: string): Promise { + public async deleteById( + agentContext: AgentContext, + recordClass: BaseRecordConstructor, + id: string + ): Promise { + assertIndyWallet(agentContext.wallet) + try { - await this.indy.deleteWalletRecord(this.wallet.handle, recordClass.type, id) + await this.indy.deleteWalletRecord(agentContext.wallet.handle, recordClass.type, id) } catch (error) { if (isIndyError(error, 'WalletItemNotFound')) { throw new RecordNotFoundError(`record with id ${id} not found.`, { @@ -204,10 +217,12 @@ export class IndyStorageService implements StorageService< } /** @inheritDoc */ - public async getById(recordClass: BaseRecordConstructor, id: string): Promise { + public async getById(agentContext: AgentContext, recordClass: BaseRecordConstructor, id: string): Promise { + assertIndyWallet(agentContext.wallet) + try { const record = await this.indy.getWalletRecord( - this.wallet.handle, + agentContext.wallet.handle, recordClass.type, id, IndyStorageService.DEFAULT_QUERY_OPTIONS @@ -226,8 +241,15 @@ export class IndyStorageService implements StorageService< } /** @inheritDoc */ - public async getAll(recordClass: BaseRecordConstructor): Promise { - const recordIterator = this.search(recordClass.type, {}, IndyStorageService.DEFAULT_QUERY_OPTIONS) + public async getAll(agentContext: AgentContext, recordClass: BaseRecordConstructor): Promise { + assertIndyWallet(agentContext.wallet) + + const recordIterator = this.search( + agentContext.wallet, + recordClass.type, + {}, + IndyStorageService.DEFAULT_QUERY_OPTIONS + ) const records = [] for await (const record of recordIterator) { records.push(this.recordToInstance(record, recordClass)) @@ -236,10 +258,21 @@ export class IndyStorageService implements StorageService< } /** @inheritDoc */ - public async findByQuery(recordClass: BaseRecordConstructor, query: Query): Promise { + public async findByQuery( + agentContext: AgentContext, + recordClass: BaseRecordConstructor, + query: Query + ): Promise { + assertIndyWallet(agentContext.wallet) + const indyQuery = this.indyQueryFromSearchQuery(query) - const recordIterator = this.search(recordClass.type, indyQuery, IndyStorageService.DEFAULT_QUERY_OPTIONS) + const recordIterator = this.search( + agentContext.wallet, + recordClass.type, + indyQuery, + IndyStorageService.DEFAULT_QUERY_OPTIONS + ) const records = [] for await (const record of recordIterator) { records.push(this.recordToInstance(record, recordClass)) @@ -248,12 +281,13 @@ export class IndyStorageService implements StorageService< } private async *search( + wallet: IndyWallet, type: string, query: WalletQuery, { limit = Infinity, ...options }: WalletSearchOptions & { limit?: number } ) { try { - const searchHandle = await this.indy.openWalletSearch(this.wallet.handle, type, query, options) + const searchHandle = await this.indy.openWalletSearch(wallet.handle, type, query, options) let records: Indy.WalletRecord[] = [] @@ -263,7 +297,7 @@ export class IndyStorageService implements StorageService< // Loop while limit not reached (or no limit specified) while (!limit || records.length < limit) { // Retrieve records - const recordsJson = await this.indy.fetchWalletSearchNextRecords(this.wallet.handle, searchHandle, chunk) + const recordsJson = await this.indy.fetchWalletSearchNextRecords(wallet.handle, searchHandle, chunk) if (recordsJson.records) { records = [...records, ...recordsJson.records] diff --git a/packages/core/src/storage/Repository.ts b/packages/core/src/storage/Repository.ts index 2b6b845c90..674d2e3e7a 100644 --- a/packages/core/src/storage/Repository.ts +++ b/packages/core/src/storage/Repository.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../agent' import type { EventEmitter } from '../agent/EventEmitter' import type { BaseRecord } from './BaseRecord' import type { RecordSavedEvent, RecordUpdatedEvent, RecordDeletedEvent } from './RepositoryEvents' @@ -25,12 +26,12 @@ export class Repository> { } /** @inheritDoc {StorageService#save} */ - public async save(record: T): Promise { - await this.storageService.save(record) + public async save(agentContext: AgentContext, record: T): Promise { + await this.storageService.save(agentContext, record) // Record in event should be static const clonedRecord = JsonTransformer.clone(record) - this.eventEmitter.emit>({ + this.eventEmitter.emit>(agentContext, { type: RepositoryEventTypes.RecordSaved, payload: { record: clonedRecord, @@ -39,12 +40,12 @@ export class Repository> { } /** @inheritDoc {StorageService#update} */ - public async update(record: T): Promise { - await this.storageService.update(record) + public async update(agentContext: AgentContext, record: T): Promise { + await this.storageService.update(agentContext, record) // Record in event should be static const clonedRecord = JsonTransformer.clone(record) - this.eventEmitter.emit>({ + this.eventEmitter.emit>(agentContext, { type: RepositoryEventTypes.RecordUpdated, payload: { record: clonedRecord, @@ -53,12 +54,12 @@ export class Repository> { } /** @inheritDoc {StorageService#delete} */ - public async delete(record: T): Promise { - await this.storageService.delete(record) + public async delete(agentContext: AgentContext, record: T): Promise { + await this.storageService.delete(agentContext, record) // Record in event should be static const clonedRecord = JsonTransformer.clone(record) - this.eventEmitter.emit>({ + this.eventEmitter.emit>(agentContext, { type: RepositoryEventTypes.RecordDeleted, payload: { record: clonedRecord, @@ -71,13 +72,13 @@ export class Repository> { * @param id the id of the record to delete * @returns */ - public async deleteById(id: string): Promise { - await this.storageService.deleteById(this.recordClass, id) + public async deleteById(agentContext: AgentContext, id: string): Promise { + await this.storageService.deleteById(agentContext, this.recordClass, id) } /** @inheritDoc {StorageService#getById} */ - public async getById(id: string): Promise { - return this.storageService.getById(this.recordClass, id) + public async getById(agentContext: AgentContext, id: string): Promise { + return this.storageService.getById(agentContext, this.recordClass, id) } /** @@ -85,9 +86,9 @@ export class Repository> { * @param id the id of the record to retrieve * @returns */ - public async findById(id: string): Promise { + public async findById(agentContext: AgentContext, id: string): Promise { try { - return await this.storageService.getById(this.recordClass, id) + return await this.storageService.getById(agentContext, this.recordClass, id) } catch (error) { if (error instanceof RecordNotFoundError) return null @@ -96,13 +97,13 @@ export class Repository> { } /** @inheritDoc {StorageService#getAll} */ - public async getAll(): Promise { - return this.storageService.getAll(this.recordClass) + public async getAll(agentContext: AgentContext): Promise { + return this.storageService.getAll(agentContext, this.recordClass) } /** @inheritDoc {StorageService#findByQuery} */ - public async findByQuery(query: Query): Promise { - return this.storageService.findByQuery(this.recordClass, query) + public async findByQuery(agentContext: AgentContext, query: Query): Promise { + return this.storageService.findByQuery(agentContext, this.recordClass, query) } /** @@ -111,8 +112,8 @@ export class Repository> { * @returns the record, or null if not found * @throws {RecordDuplicateError} if multiple records are found for the given query */ - public async findSingleByQuery(query: Query): Promise { - const records = await this.findByQuery(query) + public async findSingleByQuery(agentContext: AgentContext, query: Query): Promise { + const records = await this.findByQuery(agentContext, query) if (records.length > 1) { throw new RecordDuplicateError(`Multiple records found for given query '${JSON.stringify(query)}'`, { @@ -134,8 +135,8 @@ export class Repository> { * @throws {RecordDuplicateError} if multiple records are found for the given query * @throws {RecordNotFoundError} if no record is found for the given query */ - public async getSingleByQuery(query: Query): Promise { - const record = await this.findSingleByQuery(query) + public async getSingleByQuery(agentContext: AgentContext, query: Query): Promise { + const record = await this.findSingleByQuery(agentContext, query) if (!record) { throw new RecordNotFoundError(`No record found for given query '${JSON.stringify(query)}'`, { diff --git a/packages/core/src/storage/StorageService.ts b/packages/core/src/storage/StorageService.ts index 87360ab330..790a470aa2 100644 --- a/packages/core/src/storage/StorageService.ts +++ b/packages/core/src/storage/StorageService.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../agent' import type { Constructor } from '../utils/mixins' import type { BaseRecord, TagsBase } from './BaseRecord' @@ -24,7 +25,7 @@ export interface StorageService> { * @param record the record to store * @throws {RecordDuplicateError} if a record with this id already exists */ - save(record: T): Promise + save(agentContext: AgentContext, record: T): Promise /** * Update record in storage @@ -32,7 +33,7 @@ export interface StorageService> { * @param record the record to update * @throws {RecordNotFoundError} if a record with this id and type does not exist */ - update(record: T): Promise + update(agentContext: AgentContext, record: T): Promise /** * Delete record from storage @@ -40,7 +41,7 @@ export interface StorageService> { * @param record the record to delete * @throws {RecordNotFoundError} if a record with this id and type does not exist */ - delete(record: T): Promise + delete(agentContext: AgentContext, record: T): Promise /** * Delete record by id. @@ -49,7 +50,7 @@ export interface StorageService> { * @param id the id of the record to delete from storage * @throws {RecordNotFoundError} if a record with this id and type does not exist */ - deleteById(recordClass: BaseRecordConstructor, id: string): Promise + deleteById(agentContext: AgentContext, recordClass: BaseRecordConstructor, id: string): Promise /** * Get record by id. @@ -58,14 +59,14 @@ export interface StorageService> { * @param id the id of the record to retrieve from storage * @throws {RecordNotFoundError} if a record with this id and type does not exist */ - getById(recordClass: BaseRecordConstructor, id: string): Promise + getById(agentContext: AgentContext, recordClass: BaseRecordConstructor, id: string): Promise /** * Get all records by specified record class. * * @param recordClass the record class to get records for */ - getAll(recordClass: BaseRecordConstructor): Promise + getAll(agentContext: AgentContext, recordClass: BaseRecordConstructor): Promise /** * Find all records by specified record class and query. @@ -73,5 +74,5 @@ export interface StorageService> { * @param recordClass the record class to find records for * @param query the query to use for finding records */ - findByQuery(recordClass: BaseRecordConstructor, query: Query): Promise + findByQuery(agentContext: AgentContext, recordClass: BaseRecordConstructor, query: Query): Promise } diff --git a/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts b/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts index 3b6816e546..067a290dcb 100644 --- a/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts +++ b/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts @@ -1,4 +1,6 @@ -import { getAgentConfig, mockFunction } from '../../../tests/helpers' +import { Subject } from 'rxjs' + +import { getAgentConfig, getAgentContext, mockFunction } from '../../../tests/helpers' import { EventEmitter } from '../../agent/EventEmitter' import { ConnectionInvitationMessage } from '../../modules/connections' import { JsonTransformer } from '../../utils/JsonTransformer' @@ -17,14 +19,17 @@ const invitationJson = { label: 'test', } -describe('Repository', () => { +const config = getAgentConfig('DidCommMessageRepository') +const agentContext = getAgentContext() + +describe('DidCommMessageRepository', () => { let repository: DidCommMessageRepository let storageMock: IndyStorageService let eventEmitter: EventEmitter beforeEach(async () => { storageMock = new StorageMock() - eventEmitter = new EventEmitter(getAgentConfig('DidCommMessageRepositoryTest')) + eventEmitter = new EventEmitter(config.agentDependencies, new Subject()) repository = new DidCommMessageRepository(storageMock, eventEmitter) }) @@ -42,12 +47,12 @@ describe('Repository', () => { const record = getRecord({ id: 'test-id' }) mockFunction(storageMock.findByQuery).mockReturnValue(Promise.resolve([record])) - const invitation = await repository.findAgentMessage({ + const invitation = await repository.findAgentMessage(agentContext, { messageClass: ConnectionInvitationMessage, associatedRecordId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', }) - expect(storageMock.findByQuery).toBeCalledWith(DidCommMessageRecord, { + expect(storageMock.findByQuery).toBeCalledWith(agentContext, DidCommMessageRecord, { associatedRecordId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', messageName: 'invitation', protocolName: 'connections', @@ -61,12 +66,12 @@ describe('Repository', () => { const record = getRecord({ id: 'test-id' }) mockFunction(storageMock.findByQuery).mockReturnValue(Promise.resolve([record])) - const invitation = await repository.findAgentMessage({ + const invitation = await repository.findAgentMessage(agentContext, { messageClass: ConnectionInvitationMessage, associatedRecordId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', }) - expect(storageMock.findByQuery).toBeCalledWith(DidCommMessageRecord, { + expect(storageMock.findByQuery).toBeCalledWith(agentContext, DidCommMessageRecord, { associatedRecordId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', messageName: 'invitation', protocolName: 'connections', @@ -78,12 +83,12 @@ describe('Repository', () => { it("should return null because the record doesn't exist", async () => { mockFunction(storageMock.findByQuery).mockReturnValue(Promise.resolve([])) - const invitation = await repository.findAgentMessage({ + const invitation = await repository.findAgentMessage(agentContext, { messageClass: ConnectionInvitationMessage, associatedRecordId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', }) - expect(storageMock.findByQuery).toBeCalledWith(DidCommMessageRecord, { + expect(storageMock.findByQuery).toBeCalledWith(agentContext, DidCommMessageRecord, { associatedRecordId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', messageName: 'invitation', protocolName: 'connections', @@ -95,13 +100,14 @@ describe('Repository', () => { describe('saveAgentMessage()', () => { it('should transform and save the agent message', async () => { - await repository.saveAgentMessage({ + await repository.saveAgentMessage(agentContext, { role: DidCommMessageRole.Receiver, agentMessage: JsonTransformer.fromJSON(invitationJson, ConnectionInvitationMessage), associatedRecordId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', }) expect(storageMock.save).toBeCalledWith( + agentContext, expect.objectContaining({ role: DidCommMessageRole.Receiver, message: invitationJson, @@ -114,13 +120,14 @@ describe('Repository', () => { describe('saveOrUpdateAgentMessage()', () => { it('should transform and save the agent message', async () => { mockFunction(storageMock.findByQuery).mockReturnValue(Promise.resolve([])) - await repository.saveOrUpdateAgentMessage({ + await repository.saveOrUpdateAgentMessage(agentContext, { role: DidCommMessageRole.Receiver, agentMessage: JsonTransformer.fromJSON(invitationJson, ConnectionInvitationMessage), associatedRecordId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', }) expect(storageMock.save).toBeCalledWith( + agentContext, expect.objectContaining({ role: DidCommMessageRole.Receiver, message: invitationJson, @@ -132,19 +139,19 @@ describe('Repository', () => { it('should transform and update the agent message', async () => { const record = getRecord({ id: 'test-id' }) mockFunction(storageMock.findByQuery).mockReturnValue(Promise.resolve([record])) - await repository.saveOrUpdateAgentMessage({ + await repository.saveOrUpdateAgentMessage(agentContext, { role: DidCommMessageRole.Receiver, agentMessage: JsonTransformer.fromJSON(invitationJson, ConnectionInvitationMessage), associatedRecordId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', }) - expect(storageMock.findByQuery).toBeCalledWith(DidCommMessageRecord, { + expect(storageMock.findByQuery).toBeCalledWith(agentContext, DidCommMessageRecord, { associatedRecordId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', messageName: 'invitation', protocolName: 'connections', protocolMajorVersion: '1', }) - expect(storageMock.update).toBeCalledWith(record) + expect(storageMock.update).toBeCalledWith(agentContext, record) }) }) }) diff --git a/packages/core/src/storage/__tests__/IndyStorageService.test.ts b/packages/core/src/storage/__tests__/IndyStorageService.test.ts index dde5754cc4..08f2df3fa7 100644 --- a/packages/core/src/storage/__tests__/IndyStorageService.test.ts +++ b/packages/core/src/storage/__tests__/IndyStorageService.test.ts @@ -1,8 +1,8 @@ +import type { AgentContext } from '../../agent' import type { TagsBase } from '../BaseRecord' import type * as Indy from 'indy-sdk' -import { agentDependencies, getAgentConfig } from '../../../tests/helpers' -import { AgentConfig } from '../../agent/AgentConfig' +import { agentDependencies, getAgentConfig, getAgentContext } from '../../../tests/helpers' import { RecordDuplicateError, RecordNotFoundError } from '../../error' import { IndyWallet } from '../../wallet/IndyWallet' import { IndyStorageService } from '../IndyStorageService' @@ -13,14 +13,19 @@ describe('IndyStorageService', () => { let wallet: IndyWallet let indy: typeof Indy let storageService: IndyStorageService + let agentContext: AgentContext beforeEach(async () => { - const config = getAgentConfig('IndyStorageServiceTest') - indy = config.agentDependencies.indy - wallet = new IndyWallet(config) + const agentConfig = getAgentConfig('IndyStorageServiceTest') + indy = agentConfig.agentDependencies.indy + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger) + agentContext = getAgentContext({ + wallet, + agentConfig, + }) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(config.walletConfig!) - storageService = new IndyStorageService(wallet, config) + await wallet.createAndOpen(agentConfig.walletConfig!) + storageService = new IndyStorageService(agentConfig.agentDependencies) }) afterEach(async () => { @@ -34,7 +39,7 @@ describe('IndyStorageService', () => { tags: tags ?? { myTag: 'foobar' }, } const record = new TestRecord(props) - await storageService.save(record) + await storageService.save(agentContext, record) return record } @@ -81,7 +86,7 @@ describe('IndyStorageService', () => { anotherStringNumberValue: 'n__0', }) - const record = await storageService.getById(TestRecord, 'some-id') + const record = await storageService.getById(agentContext, TestRecord, 'some-id') expect(record.getTags()).toEqual({ someBoolean: true, @@ -98,12 +103,12 @@ describe('IndyStorageService', () => { it('should throw RecordDuplicateError if a record with the id already exists', async () => { const record = await insertRecord({ id: 'test-id' }) - return expect(() => storageService.save(record)).rejects.toThrowError(RecordDuplicateError) + return expect(() => storageService.save(agentContext, record)).rejects.toThrowError(RecordDuplicateError) }) it('should save the record', async () => { const record = await insertRecord({ id: 'test-id' }) - const found = await storageService.getById(TestRecord, 'test-id') + const found = await storageService.getById(agentContext, TestRecord, 'test-id') expect(record).toEqual(found) }) @@ -111,14 +116,14 @@ describe('IndyStorageService', () => { describe('getById()', () => { it('should throw RecordNotFoundError if the record does not exist', async () => { - return expect(() => storageService.getById(TestRecord, 'does-not-exist')).rejects.toThrowError( + return expect(() => storageService.getById(agentContext, TestRecord, 'does-not-exist')).rejects.toThrowError( RecordNotFoundError ) }) it('should return the record by id', async () => { const record = await insertRecord({ id: 'test-id' }) - const found = await storageService.getById(TestRecord, 'test-id') + const found = await storageService.getById(agentContext, TestRecord, 'test-id') expect(found).toEqual(record) }) @@ -132,7 +137,7 @@ describe('IndyStorageService', () => { tags: { some: 'tag' }, }) - return expect(() => storageService.update(record)).rejects.toThrowError(RecordNotFoundError) + return expect(() => storageService.update(agentContext, record)).rejects.toThrowError(RecordNotFoundError) }) it('should update the record', async () => { @@ -140,9 +145,9 @@ describe('IndyStorageService', () => { record.replaceTags({ ...record.getTags(), foo: 'bar' }) record.foo = 'foobaz' - await storageService.update(record) + await storageService.update(agentContext, record) - const retrievedRecord = await storageService.getById(TestRecord, record.id) + const retrievedRecord = await storageService.getById(agentContext, TestRecord, record.id) expect(retrievedRecord).toEqual(record) }) }) @@ -155,14 +160,16 @@ describe('IndyStorageService', () => { tags: { some: 'tag' }, }) - return expect(() => storageService.delete(record)).rejects.toThrowError(RecordNotFoundError) + return expect(() => storageService.delete(agentContext, record)).rejects.toThrowError(RecordNotFoundError) }) it('should delete the record', async () => { const record = await insertRecord({ id: 'test-id' }) - await storageService.delete(record) + await storageService.delete(agentContext, record) - return expect(() => storageService.getById(TestRecord, record.id)).rejects.toThrowError(RecordNotFoundError) + return expect(() => storageService.getById(agentContext, TestRecord, record.id)).rejects.toThrowError( + RecordNotFoundError + ) }) }) @@ -174,7 +181,7 @@ describe('IndyStorageService', () => { .map((_, index) => insertRecord({ id: `record-${index}` })) ) - const records = await storageService.getAll(TestRecord) + const records = await storageService.getAll(agentContext, TestRecord) expect(records).toEqual(expect.arrayContaining(createdRecords)) }) @@ -185,7 +192,7 @@ describe('IndyStorageService', () => { const expectedRecord = await insertRecord({ tags: { myTag: 'foobar' } }) await insertRecord({ tags: { myTag: 'notfoobar' } }) - const records = await storageService.findByQuery(TestRecord, { myTag: 'foobar' }) + const records = await storageService.findByQuery(agentContext, TestRecord, { myTag: 'foobar' }) expect(records.length).toBe(1) expect(records[0]).toEqual(expectedRecord) @@ -195,7 +202,7 @@ describe('IndyStorageService', () => { const expectedRecord = await insertRecord({ tags: { myTag: 'foo', anotherTag: 'bar' } }) await insertRecord({ tags: { myTag: 'notfoobar' } }) - const records = await storageService.findByQuery(TestRecord, { + const records = await storageService.findByQuery(agentContext, TestRecord, { $and: [{ myTag: 'foo' }, { anotherTag: 'bar' }], }) @@ -208,7 +215,7 @@ describe('IndyStorageService', () => { const expectedRecord2 = await insertRecord({ tags: { anotherTag: 'bar' } }) await insertRecord({ tags: { myTag: 'notfoobar' } }) - const records = await storageService.findByQuery(TestRecord, { + const records = await storageService.findByQuery(agentContext, TestRecord, { $or: [{ myTag: 'foo' }, { anotherTag: 'bar' }], }) @@ -221,7 +228,7 @@ describe('IndyStorageService', () => { const expectedRecord2 = await insertRecord({ tags: { anotherTag: 'bar' } }) await insertRecord({ tags: { myTag: 'notfoobar' } }) - const records = await storageService.findByQuery(TestRecord, { + const records = await storageService.findByQuery(agentContext, TestRecord, { $not: { myTag: 'notfoobar' }, }) @@ -231,22 +238,16 @@ describe('IndyStorageService', () => { it('correctly transforms an advanced query into a valid WQL query', async () => { const indySpy = jest.fn() - const storageServiceWithoutIndy = new IndyStorageService( - wallet, - new AgentConfig( - { label: 'hello' }, - { - ...agentDependencies, - indy: { - openWalletSearch: indySpy, - fetchWalletSearchNextRecords: jest.fn(() => ({ records: undefined })), - closeWalletSearch: jest.fn(), - } as unknown as typeof Indy, - } - ) - ) + const storageServiceWithoutIndy = new IndyStorageService({ + ...agentDependencies, + indy: { + openWalletSearch: indySpy, + fetchWalletSearchNextRecords: jest.fn(() => ({ records: undefined })), + closeWalletSearch: jest.fn(), + } as unknown as typeof Indy, + }) - await storageServiceWithoutIndy.findByQuery(TestRecord, { + await storageServiceWithoutIndy.findByQuery(agentContext, TestRecord, { $and: [ { $or: [{ myTag: true }, { myTag: false }], diff --git a/packages/core/src/storage/__tests__/Repository.test.ts b/packages/core/src/storage/__tests__/Repository.test.ts index fa4ac12f97..27faf1346a 100644 --- a/packages/core/src/storage/__tests__/Repository.test.ts +++ b/packages/core/src/storage/__tests__/Repository.test.ts @@ -1,7 +1,10 @@ +import type { AgentContext } from '../../agent' import type { TagsBase } from '../BaseRecord' import type { RecordDeletedEvent, RecordSavedEvent, RecordUpdatedEvent } from '../RepositoryEvents' -import { getAgentConfig, mockFunction } from '../../../tests/helpers' +import { Subject } from 'rxjs' + +import { getAgentConfig, getAgentContext, mockFunction } from '../../../tests/helpers' import { EventEmitter } from '../../agent/EventEmitter' import { AriesFrameworkError, RecordDuplicateError, RecordNotFoundError } from '../../error' import { IndyStorageService } from '../IndyStorageService' @@ -14,15 +17,19 @@ jest.mock('../IndyStorageService') const StorageMock = IndyStorageService as unknown as jest.Mock> +const config = getAgentConfig('Repository') + describe('Repository', () => { let repository: Repository let storageMock: IndyStorageService + let agentContext: AgentContext let eventEmitter: EventEmitter beforeEach(async () => { storageMock = new StorageMock() - eventEmitter = new EventEmitter(getAgentConfig('RepositoryTest')) + eventEmitter = new EventEmitter(config.agentDependencies, new Subject()) repository = new Repository(TestRecord, storageMock, eventEmitter) + agentContext = getAgentContext() }) const getRecord = ({ id, tags }: { id?: string; tags?: TagsBase } = {}) => { @@ -36,9 +43,9 @@ describe('Repository', () => { describe('save()', () => { it('should save the record using the storage service', async () => { const record = getRecord({ id: 'test-id' }) - await repository.save(record) + await repository.save(agentContext, record) - expect(storageMock.save).toBeCalledWith(record) + expect(storageMock.save).toBeCalledWith(agentContext, record) }) it(`should emit saved event`, async () => { @@ -49,7 +56,7 @@ describe('Repository', () => { const record = getRecord({ id: 'test-id' }) // when - await repository.save(record) + await repository.save(agentContext, record) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -66,9 +73,9 @@ describe('Repository', () => { describe('update()', () => { it('should update the record using the storage service', async () => { const record = getRecord({ id: 'test-id' }) - await repository.update(record) + await repository.update(agentContext, record) - expect(storageMock.update).toBeCalledWith(record) + expect(storageMock.update).toBeCalledWith(agentContext, record) }) it(`should emit updated event`, async () => { @@ -79,7 +86,7 @@ describe('Repository', () => { const record = getRecord({ id: 'test-id' }) // when - await repository.update(record) + await repository.update(agentContext, record) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -96,9 +103,9 @@ describe('Repository', () => { describe('delete()', () => { it('should delete the record using the storage service', async () => { const record = getRecord({ id: 'test-id' }) - await repository.delete(record) + await repository.delete(agentContext, record) - expect(storageMock.delete).toBeCalledWith(record) + expect(storageMock.delete).toBeCalledWith(agentContext, record) }) it(`should emit deleted event`, async () => { @@ -109,7 +116,7 @@ describe('Repository', () => { const record = getRecord({ id: 'test-id' }) // when - await repository.delete(record) + await repository.delete(agentContext, record) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -125,9 +132,9 @@ describe('Repository', () => { describe('deleteById()', () => { it('should delete the record by record id', async () => { - await repository.deleteById('test-id') + await repository.deleteById(agentContext, 'test-id') - expect(storageMock.deleteById).toBeCalledWith(TestRecord, 'test-id') + expect(storageMock.deleteById).toBeCalledWith(agentContext, TestRecord, 'test-id') }) }) @@ -136,9 +143,9 @@ describe('Repository', () => { const record = getRecord({ id: 'test-id' }) mockFunction(storageMock.getById).mockReturnValue(Promise.resolve(record)) - const returnValue = await repository.getById('test-id') + const returnValue = await repository.getById(agentContext, 'test-id') - expect(storageMock.getById).toBeCalledWith(TestRecord, 'test-id') + expect(storageMock.getById).toBeCalledWith(agentContext, TestRecord, 'test-id') expect(returnValue).toBe(record) }) }) @@ -148,9 +155,9 @@ describe('Repository', () => { const record = getRecord({ id: 'test-id' }) mockFunction(storageMock.getById).mockReturnValue(Promise.resolve(record)) - const returnValue = await repository.findById('test-id') + const returnValue = await repository.findById(agentContext, 'test-id') - expect(storageMock.getById).toBeCalledWith(TestRecord, 'test-id') + expect(storageMock.getById).toBeCalledWith(agentContext, TestRecord, 'test-id') expect(returnValue).toBe(record) }) @@ -159,17 +166,17 @@ describe('Repository', () => { Promise.reject(new RecordNotFoundError('Not found', { recordType: TestRecord.type })) ) - const returnValue = await repository.findById('test-id') + const returnValue = await repository.findById(agentContext, 'test-id') - expect(storageMock.getById).toBeCalledWith(TestRecord, 'test-id') + expect(storageMock.getById).toBeCalledWith(agentContext, TestRecord, 'test-id') expect(returnValue).toBeNull() }) it('should return null if the storage service throws an error that is not RecordNotFoundError', async () => { mockFunction(storageMock.getById).mockReturnValue(Promise.reject(new AriesFrameworkError('Not found'))) - expect(repository.findById('test-id')).rejects.toThrowError(AriesFrameworkError) - expect(storageMock.getById).toBeCalledWith(TestRecord, 'test-id') + expect(repository.findById(agentContext, 'test-id')).rejects.toThrowError(AriesFrameworkError) + expect(storageMock.getById).toBeCalledWith(agentContext, TestRecord, 'test-id') }) }) @@ -179,9 +186,9 @@ describe('Repository', () => { const record2 = getRecord({ id: 'test-id2' }) mockFunction(storageMock.getAll).mockReturnValue(Promise.resolve([record, record2])) - const returnValue = await repository.getAll() + const returnValue = await repository.getAll(agentContext) - expect(storageMock.getAll).toBeCalledWith(TestRecord) + expect(storageMock.getAll).toBeCalledWith(agentContext, TestRecord) expect(returnValue).toEqual(expect.arrayContaining([record, record2])) }) }) @@ -192,9 +199,9 @@ describe('Repository', () => { const record2 = getRecord({ id: 'test-id2' }) mockFunction(storageMock.findByQuery).mockReturnValue(Promise.resolve([record, record2])) - const returnValue = await repository.findByQuery({ something: 'interesting' }) + const returnValue = await repository.findByQuery(agentContext, { something: 'interesting' }) - expect(storageMock.findByQuery).toBeCalledWith(TestRecord, { something: 'interesting' }) + expect(storageMock.findByQuery).toBeCalledWith(agentContext, TestRecord, { something: 'interesting' }) expect(returnValue).toEqual(expect.arrayContaining([record, record2])) }) }) @@ -204,18 +211,18 @@ describe('Repository', () => { const record = getRecord({ id: 'test-id' }) mockFunction(storageMock.findByQuery).mockReturnValue(Promise.resolve([record])) - const returnValue = await repository.findSingleByQuery({ something: 'interesting' }) + const returnValue = await repository.findSingleByQuery(agentContext, { something: 'interesting' }) - expect(storageMock.findByQuery).toBeCalledWith(TestRecord, { something: 'interesting' }) + expect(storageMock.findByQuery).toBeCalledWith(agentContext, TestRecord, { something: 'interesting' }) expect(returnValue).toBe(record) }) it('should return null if the no records are returned by the storage service', async () => { mockFunction(storageMock.findByQuery).mockReturnValue(Promise.resolve([])) - const returnValue = await repository.findSingleByQuery({ something: 'interesting' }) + const returnValue = await repository.findSingleByQuery(agentContext, { something: 'interesting' }) - expect(storageMock.findByQuery).toBeCalledWith(TestRecord, { something: 'interesting' }) + expect(storageMock.findByQuery).toBeCalledWith(agentContext, TestRecord, { something: 'interesting' }) expect(returnValue).toBeNull() }) @@ -224,8 +231,10 @@ describe('Repository', () => { const record2 = getRecord({ id: 'test-id2' }) mockFunction(storageMock.findByQuery).mockReturnValue(Promise.resolve([record, record2])) - expect(repository.findSingleByQuery({ something: 'interesting' })).rejects.toThrowError(RecordDuplicateError) - expect(storageMock.findByQuery).toBeCalledWith(TestRecord, { something: 'interesting' }) + expect(repository.findSingleByQuery(agentContext, { something: 'interesting' })).rejects.toThrowError( + RecordDuplicateError + ) + expect(storageMock.findByQuery).toBeCalledWith(agentContext, TestRecord, { something: 'interesting' }) }) }) @@ -234,17 +243,19 @@ describe('Repository', () => { const record = getRecord({ id: 'test-id' }) mockFunction(storageMock.findByQuery).mockReturnValue(Promise.resolve([record])) - const returnValue = await repository.getSingleByQuery({ something: 'interesting' }) + const returnValue = await repository.getSingleByQuery(agentContext, { something: 'interesting' }) - expect(storageMock.findByQuery).toBeCalledWith(TestRecord, { something: 'interesting' }) + expect(storageMock.findByQuery).toBeCalledWith(agentContext, TestRecord, { something: 'interesting' }) expect(returnValue).toBe(record) }) it('should throw RecordNotFoundError if no records are returned by the storage service', async () => { mockFunction(storageMock.findByQuery).mockReturnValue(Promise.resolve([])) - expect(repository.getSingleByQuery({ something: 'interesting' })).rejects.toThrowError(RecordNotFoundError) - expect(storageMock.findByQuery).toBeCalledWith(TestRecord, { something: 'interesting' }) + expect(repository.getSingleByQuery(agentContext, { something: 'interesting' })).rejects.toThrowError( + RecordNotFoundError + ) + expect(storageMock.findByQuery).toBeCalledWith(agentContext, TestRecord, { something: 'interesting' }) }) it('should throw RecordDuplicateError if more than one record is returned by the storage service', async () => { @@ -252,8 +263,10 @@ describe('Repository', () => { const record2 = getRecord({ id: 'test-id2' }) mockFunction(storageMock.findByQuery).mockReturnValue(Promise.resolve([record, record2])) - expect(repository.getSingleByQuery({ something: 'interesting' })).rejects.toThrowError(RecordDuplicateError) - expect(storageMock.findByQuery).toBeCalledWith(TestRecord, { something: 'interesting' }) + expect(repository.getSingleByQuery(agentContext, { something: 'interesting' })).rejects.toThrowError( + RecordDuplicateError + ) + expect(storageMock.findByQuery).toBeCalledWith(agentContext, TestRecord, { something: 'interesting' }) }) }) }) diff --git a/packages/core/src/storage/didcomm/DidCommMessageRepository.ts b/packages/core/src/storage/didcomm/DidCommMessageRepository.ts index f964c62554..db2db2d04f 100644 --- a/packages/core/src/storage/didcomm/DidCommMessageRepository.ts +++ b/packages/core/src/storage/didcomm/DidCommMessageRepository.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../../agent' import type { AgentMessage, ConstructableAgentMessage } from '../../agent/AgentMessage' import type { JsonObject } from '../../types' import type { DidCommMessageRole } from './DidCommMessageRole' @@ -20,20 +21,23 @@ export class DidCommMessageRepository extends Repository { super(DidCommMessageRecord, storageService, eventEmitter) } - public async saveAgentMessage({ role, agentMessage, associatedRecordId }: SaveAgentMessageOptions) { + public async saveAgentMessage( + agentContext: AgentContext, + { role, agentMessage, associatedRecordId }: SaveAgentMessageOptions + ) { const didCommMessageRecord = new DidCommMessageRecord({ message: agentMessage.toJSON() as JsonObject, role, associatedRecordId, }) - await this.save(didCommMessageRecord) + await this.save(agentContext, didCommMessageRecord) } - public async saveOrUpdateAgentMessage(options: SaveAgentMessageOptions) { + public async saveOrUpdateAgentMessage(agentContext: AgentContext, options: SaveAgentMessageOptions) { const { messageName, protocolName, protocolMajorVersion } = parseMessageType(options.agentMessage.type) - const record = await this.findSingleByQuery({ + const record = await this.findSingleByQuery(agentContext, { associatedRecordId: options.associatedRecordId, messageName: messageName, protocolName: protocolName, @@ -43,18 +47,18 @@ export class DidCommMessageRepository extends Repository { if (record) { record.message = options.agentMessage.toJSON() as JsonObject record.role = options.role - await this.update(record) + await this.update(agentContext, record) return } - await this.saveAgentMessage(options) + await this.saveAgentMessage(agentContext, options) } - public async getAgentMessage({ - associatedRecordId, - messageClass, - }: GetAgentMessageOptions): Promise> { - const record = await this.getSingleByQuery({ + public async getAgentMessage( + agentContext: AgentContext, + { associatedRecordId, messageClass }: GetAgentMessageOptions + ): Promise> { + const record = await this.getSingleByQuery(agentContext, { associatedRecordId, messageName: messageClass.type.messageName, protocolName: messageClass.type.protocolName, @@ -63,11 +67,11 @@ export class DidCommMessageRepository extends Repository { return record.getMessageInstance(messageClass) } - public async findAgentMessage({ - associatedRecordId, - messageClass, - }: GetAgentMessageOptions): Promise | null> { - const record = await this.findSingleByQuery({ + public async findAgentMessage( + agentContext: AgentContext, + { associatedRecordId, messageClass }: GetAgentMessageOptions + ): Promise | null> { + const record = await this.findSingleByQuery(agentContext, { associatedRecordId, messageName: messageClass.type.messageName, protocolName: messageClass.type.protocolName, diff --git a/packages/core/src/storage/migration/StorageUpdateService.ts b/packages/core/src/storage/migration/StorageUpdateService.ts index 8d755dd133..2c8991d319 100644 --- a/packages/core/src/storage/migration/StorageUpdateService.ts +++ b/packages/core/src/storage/migration/StorageUpdateService.ts @@ -1,8 +1,9 @@ -import type { Logger } from '../../logger' +import type { AgentContext } from '../../agent' import type { VersionString } from '../../utils/version' -import { AgentConfig } from '../../agent/AgentConfig' -import { injectable } from '../../plugins' +import { InjectionSymbols } from '../../constants' +import { Logger } from '../../logger' +import { injectable, inject } from '../../plugins' import { StorageVersionRecord } from './repository/StorageVersionRecord' import { StorageVersionRepository } from './repository/StorageVersionRepository' @@ -15,34 +16,39 @@ export class StorageUpdateService { private logger: Logger private storageVersionRepository: StorageVersionRepository - public constructor(agentConfig: AgentConfig, storageVersionRepository: StorageVersionRepository) { + public constructor( + @inject(InjectionSymbols.Logger) logger: Logger, + storageVersionRepository: StorageVersionRepository + ) { + this.logger = logger this.storageVersionRepository = storageVersionRepository - this.logger = agentConfig.logger } - public async isUpToDate() { - const currentStorageVersion = await this.getCurrentStorageVersion() + public async isUpToDate(agentContext: AgentContext) { + const currentStorageVersion = await this.getCurrentStorageVersion(agentContext) const isUpToDate = CURRENT_FRAMEWORK_STORAGE_VERSION === currentStorageVersion return isUpToDate } - public async getCurrentStorageVersion(): Promise { - const storageVersionRecord = await this.getStorageVersionRecord() + public async getCurrentStorageVersion(agentContext: AgentContext): Promise { + const storageVersionRecord = await this.getStorageVersionRecord(agentContext) return storageVersionRecord.storageVersion } - public async setCurrentStorageVersion(storageVersion: VersionString) { + public async setCurrentStorageVersion(agentContext: AgentContext, storageVersion: VersionString) { this.logger.debug(`Setting current agent storage version to ${storageVersion}`) const storageVersionRecord = await this.storageVersionRepository.findById( + agentContext, StorageUpdateService.STORAGE_VERSION_RECORD_ID ) if (!storageVersionRecord) { this.logger.trace('Storage upgrade record does not exist yet. Creating.') await this.storageVersionRepository.save( + agentContext, new StorageVersionRecord({ id: StorageUpdateService.STORAGE_VERSION_RECORD_ID, storageVersion, @@ -51,7 +57,7 @@ export class StorageUpdateService { } else { this.logger.trace('Storage upgrade record already exists. Updating.') storageVersionRecord.storageVersion = storageVersion - await this.storageVersionRepository.update(storageVersionRecord) + await this.storageVersionRepository.update(agentContext, storageVersionRecord) } } @@ -61,8 +67,9 @@ export class StorageUpdateService { * The storageVersion will be set to the INITIAL_STORAGE_VERSION if it doesn't exist yet, * as we can assume the wallet was created before the udpate record existed */ - public async getStorageVersionRecord() { + public async getStorageVersionRecord(agentContext: AgentContext) { let storageVersionRecord = await this.storageVersionRepository.findById( + agentContext, StorageUpdateService.STORAGE_VERSION_RECORD_ID ) @@ -71,7 +78,7 @@ export class StorageUpdateService { id: StorageUpdateService.STORAGE_VERSION_RECORD_ID, storageVersion: INITIAL_STORAGE_VERSION, }) - await this.storageVersionRepository.save(storageVersionRecord) + await this.storageVersionRepository.save(agentContext, storageVersionRecord) } return storageVersionRecord diff --git a/packages/core/src/storage/migration/UpdateAssistant.ts b/packages/core/src/storage/migration/UpdateAssistant.ts index 084a72b584..cb07798529 100644 --- a/packages/core/src/storage/migration/UpdateAssistant.ts +++ b/packages/core/src/storage/migration/UpdateAssistant.ts @@ -1,6 +1,9 @@ import type { Agent } from '../../agent/Agent' +import type { FileSystem } from '../FileSystem' import type { UpdateConfig } from './updates' +import { AgentContext } from '../../agent' +import { InjectionSymbols } from '../../constants' import { AriesFrameworkError } from '../../error' import { isFirstVersionHigherThanSecond, parseVersionString } from '../../utils/version' import { WalletError } from '../../wallet/error/WalletError' @@ -13,12 +16,16 @@ export class UpdateAssistant { private agent: Agent private storageUpdateService: StorageUpdateService private updateConfig: UpdateConfig + private agentContext: AgentContext + private fileSystem: FileSystem public constructor(agent: Agent, updateConfig: UpdateConfig) { this.agent = agent this.updateConfig = updateConfig this.storageUpdateService = this.agent.dependencyManager.resolve(StorageUpdateService) + this.agentContext = this.agent.dependencyManager.resolve(AgentContext) + this.fileSystem = this.agent.dependencyManager.resolve(InjectionSymbols.FileSystem) } public async initialize() { @@ -39,11 +46,11 @@ export class UpdateAssistant { } public async isUpToDate() { - return this.storageUpdateService.isUpToDate() + return this.storageUpdateService.isUpToDate(this.agentContext) } public async getCurrentAgentStorageVersion() { - return this.storageUpdateService.getCurrentStorageVersion() + return this.storageUpdateService.getCurrentStorageVersion(this.agentContext) } public static get frameworkStorageVersion() { @@ -51,7 +58,9 @@ export class UpdateAssistant { } public async getNeededUpdates() { - const currentStorageVersion = parseVersionString(await this.storageUpdateService.getCurrentStorageVersion()) + const currentStorageVersion = parseVersionString( + await this.storageUpdateService.getCurrentStorageVersion(this.agentContext) + ) // Filter updates. We don't want older updates we already applied // or aren't needed because the wallet was created after the update script was made @@ -104,7 +113,7 @@ export class UpdateAssistant { await update.doUpdate(this.agent, this.updateConfig) // Update the framework version in storage - await this.storageUpdateService.setCurrentStorageVersion(update.toVersion) + await this.storageUpdateService.setCurrentStorageVersion(this.agentContext, update.toVersion) this.agent.config.logger.info( `Successfully updated agent storage from version ${update.fromVersion} to version ${update.toVersion}` ) @@ -132,8 +141,7 @@ export class UpdateAssistant { } private getBackupPath(backupIdentifier: string) { - const fileSystem = this.agent.config.fileSystem - return `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` + return `${this.fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` } private async createBackup(backupIdentifier: string) { diff --git a/packages/core/src/storage/migration/__tests__/0.1.test.ts b/packages/core/src/storage/migration/__tests__/0.1.test.ts index 8ee624300b..dffe2b5cf6 100644 --- a/packages/core/src/storage/migration/__tests__/0.1.test.ts +++ b/packages/core/src/storage/migration/__tests__/0.1.test.ts @@ -1,3 +1,4 @@ +import type { FileSystem } from '../../../../src' import type { DidInfo, DidConfig } from '../../../wallet' import type { V0_1ToV0_2UpdateConfig } from '../updates/0.1-0.2' @@ -63,6 +64,8 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { container ) + const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) + const updateAssistant = new UpdateAssistant(agent, { v0_1ToV0_2: { mediationRoleUpdateStrategy, @@ -90,7 +93,7 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { expect(storageService.records).toMatchSnapshot(mediationRoleUpdateStrategy) // Need to remove backupFiles after each run so we don't get IOErrors - const backupPath = `${agent.config.fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` + const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` unlinkSync(backupPath) await agent.shutdown() @@ -122,6 +125,8 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { container ) + const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) + const updateAssistant = new UpdateAssistant(agent, { v0_1ToV0_2: { mediationRoleUpdateStrategy: 'doNotChange', @@ -150,7 +155,7 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { expect(storageService.records).toMatchSnapshot() // Need to remove backupFiles after each run so we don't get IOErrors - const backupPath = `${agent.config.fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` + const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` unlinkSync(backupPath) await agent.shutdown() @@ -184,6 +189,8 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { container ) + const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) + // We need to manually initialize the wallet as we're using the in memory wallet service // When we call agent.initialize() it will create the wallet and store the current framework // version in the in memory storage service. We need to manually set the records between initializing @@ -199,7 +206,7 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { expect(storageService.records).toMatchSnapshot() // Need to remove backupFiles after each run so we don't get IOErrors - const backupPath = `${agent.config.fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` + const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` unlinkSync(backupPath) await agent.shutdown() @@ -233,6 +240,8 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { container ) + const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) + // We need to manually initialize the wallet as we're using the in memory wallet service // When we call agent.initialize() it will create the wallet and store the current framework // version in the in memory storage service. We need to manually set the records between initializing @@ -248,7 +257,7 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { expect(storageService.records).toMatchSnapshot() // Need to remove backupFiles after each run so we don't get IOErrors - const backupPath = `${agent.config.fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` + const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` unlinkSync(backupPath) await agent.shutdown() diff --git a/packages/core/src/storage/migration/__tests__/backup.test.ts b/packages/core/src/storage/migration/__tests__/backup.test.ts index 02033a656d..557e521549 100644 --- a/packages/core/src/storage/migration/__tests__/backup.test.ts +++ b/packages/core/src/storage/migration/__tests__/backup.test.ts @@ -1,3 +1,4 @@ +import type { FileSystem } from '../../FileSystem' import type { StorageUpdateError } from '../error/StorageUpdateError' import { readFileSync, unlinkSync } from 'fs' @@ -5,6 +6,7 @@ import path from 'path' import { getBaseConfig } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' +import { InjectionSymbols } from '../../../constants' import { AriesFrameworkError } from '../../../error' import { CredentialExchangeRecord, CredentialRepository } from '../../../modules/credentials' import { JsonTransformer } from '../../../utils' @@ -29,13 +31,14 @@ describe('UpdateAssistant | Backup', () => { beforeEach(async () => { agent = new Agent(config, agentDependencies) - backupPath = `${agent.config.fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` + const fileSystem = agent.dependencyManager.resolve(InjectionSymbols.FileSystem) + backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` // If tests fail it's possible the cleanup has been skipped. So remove before running tests - if (await agent.config.fileSystem.exists(backupPath)) { + if (await fileSystem.exists(backupPath)) { unlinkSync(backupPath) } - if (await agent.config.fileSystem.exists(`${backupPath}-error`)) { + if (await fileSystem.exists(`${backupPath}-error`)) { unlinkSync(`${backupPath}-error`) } @@ -69,14 +72,14 @@ describe('UpdateAssistant | Backup', () => { // Add 0.1 data and set version to 0.1 for (const credentialRecord of aliceCredentialRecords) { - await credentialRepository.save(credentialRecord) + await credentialRepository.save(agent.context, credentialRecord) } - await storageUpdateService.setCurrentStorageVersion('0.1') + await storageUpdateService.setCurrentStorageVersion(agent.context, '0.1') // Expect an update is needed expect(await updateAssistant.isUpToDate()).toBe(false) - const fileSystem = agent.config.fileSystem + const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) // Backup should not exist before update expect(await fileSystem.exists(backupPath)).toBe(false) @@ -86,7 +89,9 @@ describe('UpdateAssistant | Backup', () => { // Backup should exist after update expect(await fileSystem.exists(backupPath)).toBe(true) - expect((await credentialRepository.getAll()).sort((a, b) => a.id.localeCompare(b.id))).toMatchSnapshot() + expect( + (await credentialRepository.getAll(agent.context)).sort((a, b) => a.id.localeCompare(b.id)) + ).toMatchSnapshot() }) it('should restore the backup if an error occurs during the update', async () => { @@ -105,9 +110,9 @@ describe('UpdateAssistant | Backup', () => { // Add 0.1 data and set version to 0.1 for (const credentialRecord of aliceCredentialRecords) { - await credentialRepository.save(credentialRecord) + await credentialRepository.save(agent.context, credentialRecord) } - await storageUpdateService.setCurrentStorageVersion('0.1') + await storageUpdateService.setCurrentStorageVersion(agent.context, '0.1') // Expect an update is needed expect(await updateAssistant.isUpToDate()).toBe(false) @@ -121,7 +126,7 @@ describe('UpdateAssistant | Backup', () => { }, ]) - const fileSystem = agent.config.fileSystem + const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) // Backup should not exist before update expect(await fileSystem.exists(backupPath)).toBe(false) @@ -140,7 +145,7 @@ describe('UpdateAssistant | Backup', () => { expect(await fileSystem.exists(`${backupPath}-error`)).toBe(true) // Wallet should be same as when we started because of backup - expect((await credentialRepository.getAll()).sort((a, b) => a.id.localeCompare(b.id))).toEqual( + expect((await credentialRepository.getAll(agent.context)).sort((a, b) => a.id.localeCompare(b.id))).toEqual( aliceCredentialRecords.sort((a, b) => a.id.localeCompare(b.id)) ) }) diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts index c68f5e14d1..520bf571aa 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts @@ -1,4 +1,4 @@ -import { getAgentConfig, mockFunction } from '../../../../../../tests/helpers' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../../tests/helpers' import { Agent } from '../../../../../agent/Agent' import { ConnectionRecord, @@ -24,6 +24,7 @@ import legacyDidPeer4kgVt6CidfKgo1MoWMqsQX from './__fixtures__/legacyDidPeer4kg import legacyDidPeerR1xKJw17sUoXhejEpugMYJ from './__fixtures__/legacyDidPeerR1xKJw17sUoXhejEpugMYJ.json' const agentConfig = getAgentConfig('Migration ConnectionRecord 0.1-0.2') +const agentContext = getAgentContext() jest.mock('../../../../../modules/connections/repository/ConnectionRepository') const ConnectionRepositoryMock = ConnectionRepository as jest.Mock @@ -41,6 +42,7 @@ jest.mock('../../../../../agent/Agent', () => { return { Agent: jest.fn(() => ({ config: agentConfig, + context: agentContext, dependencyManager: { resolve: jest.fn((cls) => { if (cls === ConnectionRepository) { @@ -122,7 +124,7 @@ describe('0.1-0.2 | Connection', () => { expect(connectionRepository.getAll).toHaveBeenCalledTimes(1) expect(connectionRepository.update).toHaveBeenCalledTimes(records.length) - const [[updatedConnectionRecord]] = mockFunction(connectionRepository.update).mock.calls + const [[, updatedConnectionRecord]] = mockFunction(connectionRepository.update).mock.calls // Check first object is transformed correctly. // - removed invitation, theirDidDoc, didDoc @@ -210,7 +212,7 @@ describe('0.1-0.2 | Connection', () => { expect(didRepository.save).toHaveBeenCalledTimes(2) - const [[didRecord], [theirDidRecord]] = mockFunction(didRepository.save).mock.calls + const [[, didRecord], [, theirDidRecord]] = mockFunction(didRepository.save).mock.calls expect(didRecord.toJSON()).toMatchObject({ id: didPeerR1xKJw17sUoXhejEpugMYJ.id, @@ -314,15 +316,15 @@ describe('0.1-0.2 | Connection', () => { ) // Both did records already exist - mockFunction(didRepository.findById).mockImplementation((id) => + mockFunction(didRepository.findById).mockImplementation((_, id) => Promise.resolve(id === didPeerR1xKJw17sUoXhejEpugMYJ.id ? didRecord : theirDidRecord) ) await testModule.extractDidDocument(agent, connectionRecord) expect(didRepository.save).not.toHaveBeenCalled() - expect(didRepository.findById).toHaveBeenNthCalledWith(1, didPeerR1xKJw17sUoXhejEpugMYJ.id) - expect(didRepository.findById).toHaveBeenNthCalledWith(2, didPeer4kgVt6CidfKgo1MoWMqsQX.id) + expect(didRepository.findById).toHaveBeenNthCalledWith(1, agentContext, didPeerR1xKJw17sUoXhejEpugMYJ.id) + expect(didRepository.findById).toHaveBeenNthCalledWith(2, agentContext, didPeer4kgVt6CidfKgo1MoWMqsQX.id) expect(connectionRecord.toJSON()).toEqual({ _tags: {}, @@ -376,7 +378,7 @@ describe('0.1-0.2 | Connection', () => { await testModule.migrateToOobRecord(agent, connectionRecord) - const [[outOfBandRecord]] = mockFunction(outOfBandRepository.save).mock.calls + const [[, outOfBandRecord]] = mockFunction(outOfBandRepository.save).mock.calls expect(outOfBandRepository.save).toHaveBeenCalledTimes(1) expect(connectionRecord.outOfBandId).toEqual(outOfBandRecord.id) @@ -419,7 +421,7 @@ describe('0.1-0.2 | Connection', () => { await testModule.migrateToOobRecord(agent, connectionRecord) expect(outOfBandRepository.findByQuery).toHaveBeenCalledTimes(1) - expect(outOfBandRepository.findByQuery).toHaveBeenNthCalledWith(1, { + expect(outOfBandRepository.findByQuery).toHaveBeenNthCalledWith(1, agentContext, { invitationId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', recipientKeyFingerprints: ['z6MksYU4MHtfmNhNm1uGMvANr9j4CBv2FymjiJtRgA36bSVH'], }) @@ -469,7 +471,7 @@ describe('0.1-0.2 | Connection', () => { await testModule.migrateToOobRecord(agent, connectionRecord) expect(outOfBandRepository.findByQuery).toHaveBeenCalledTimes(1) - expect(outOfBandRepository.findByQuery).toHaveBeenNthCalledWith(1, { + expect(outOfBandRepository.findByQuery).toHaveBeenNthCalledWith(1, agentContext, { invitationId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', recipientKeyFingerprints: ['z6MksYU4MHtfmNhNm1uGMvANr9j4CBv2FymjiJtRgA36bSVH'], }) @@ -535,13 +537,13 @@ describe('0.1-0.2 | Connection', () => { await testModule.migrateToOobRecord(agent, connectionRecord) expect(outOfBandRepository.findByQuery).toHaveBeenCalledTimes(1) - expect(outOfBandRepository.findByQuery).toHaveBeenNthCalledWith(1, { + expect(outOfBandRepository.findByQuery).toHaveBeenNthCalledWith(1, agentContext, { invitationId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', recipientKeyFingerprints: ['z6MksYU4MHtfmNhNm1uGMvANr9j4CBv2FymjiJtRgA36bSVH'], }) expect(outOfBandRepository.save).not.toHaveBeenCalled() - expect(outOfBandRepository.update).toHaveBeenCalledWith(outOfBandRecord) - expect(connectionRepository.delete).toHaveBeenCalledWith(connectionRecord) + expect(outOfBandRepository.update).toHaveBeenCalledWith(agentContext, outOfBandRecord) + expect(connectionRepository.delete).toHaveBeenCalledWith(agentContext, connectionRecord) expect(outOfBandRecord.toJSON()).toEqual({ id: '3c52cc26-577d-4200-8753-05f1f425c342', diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/credential.test.ts b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/credential.test.ts index 00df7457da..c4c3434b77 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/credential.test.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/credential.test.ts @@ -1,7 +1,7 @@ import type { CredentialRecordBinding } from '../../../../../../src/modules/credentials' import { CredentialExchangeRecord, CredentialState } from '../../../../../../src/modules/credentials' -import { getAgentConfig, mockFunction } from '../../../../../../tests/helpers' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../../tests/helpers' import { Agent } from '../../../../../agent/Agent' import { CredentialRepository } from '../../../../../modules/credentials/repository/CredentialRepository' import { JsonTransformer } from '../../../../../utils' @@ -10,6 +10,7 @@ import { DidCommMessageRepository } from '../../../../didcomm/DidCommMessageRepo import * as testModule from '../credential' const agentConfig = getAgentConfig('Migration CredentialRecord 0.1-0.2') +const agentContext = getAgentContext() jest.mock('../../../../../modules/credentials/repository/CredentialRepository') const CredentialRepositoryMock = CredentialRepository as jest.Mock @@ -23,6 +24,7 @@ jest.mock('../../../../../agent/Agent', () => { return { Agent: jest.fn(() => ({ config: agentConfig, + context: agentContext, dependencyManager: { resolve: jest.fn((token) => token === CredentialRepositoryMock ? credentialRepository : didCommMessageRepository @@ -75,7 +77,7 @@ describe('0.1-0.2 | Credential', () => { expect(credentialRepository.getAll).toHaveBeenCalledTimes(1) expect(credentialRepository.update).toHaveBeenCalledTimes(records.length) - const updatedRecord = mockFunction(credentialRepository.update).mock.calls[0][0] + const updatedRecord = mockFunction(credentialRepository.update).mock.calls[0][1] // Check first object is transformed correctly expect(updatedRecord.toJSON()).toMatchObject({ @@ -277,7 +279,7 @@ describe('0.1-0.2 | Credential', () => { await testModule.moveDidCommMessages(agent, credentialRecord) expect(didCommMessageRepository.save).toHaveBeenCalledTimes(4) - const [[proposalMessageRecord], [offerMessageRecord], [requestMessageRecord], [credentialMessageRecord]] = + const [[, proposalMessageRecord], [, offerMessageRecord], [, requestMessageRecord], [, credentialMessageRecord]] = mockFunction(didCommMessageRepository.save).mock.calls expect(proposalMessageRecord).toMatchObject({ @@ -340,7 +342,7 @@ describe('0.1-0.2 | Credential', () => { await testModule.moveDidCommMessages(agent, credentialRecord) expect(didCommMessageRepository.save).toHaveBeenCalledTimes(2) - const [[proposalMessageRecord], [offerMessageRecord]] = mockFunction(didCommMessageRepository.save).mock.calls + const [[, proposalMessageRecord], [, offerMessageRecord]] = mockFunction(didCommMessageRepository.save).mock.calls expect(proposalMessageRecord).toMatchObject({ role: DidCommMessageRole.Sender, @@ -388,7 +390,7 @@ describe('0.1-0.2 | Credential', () => { await testModule.moveDidCommMessages(agent, credentialRecord) expect(didCommMessageRepository.save).toHaveBeenCalledTimes(4) - const [[proposalMessageRecord], [offerMessageRecord], [requestMessageRecord], [credentialMessageRecord]] = + const [[, proposalMessageRecord], [, offerMessageRecord], [, requestMessageRecord], [, credentialMessageRecord]] = mockFunction(didCommMessageRepository.save).mock.calls expect(proposalMessageRecord).toMatchObject({ diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/mediation.test.ts b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/mediation.test.ts index 9f0ccd49f7..b5616578e2 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/mediation.test.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/mediation.test.ts @@ -1,4 +1,4 @@ -import { getAgentConfig, mockFunction } from '../../../../../../tests/helpers' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../../tests/helpers' import { Agent } from '../../../../../agent/Agent' import { MediationRole, MediationRecord } from '../../../../../modules/routing' import { MediationRepository } from '../../../../../modules/routing/repository/MediationRepository' @@ -6,6 +6,7 @@ import { JsonTransformer } from '../../../../../utils' import * as testModule from '../mediation' const agentConfig = getAgentConfig('Migration MediationRecord 0.1-0.2') +const agentContext = getAgentContext() jest.mock('../../../../../modules/routing/repository/MediationRepository') const MediationRepositoryMock = MediationRepository as jest.Mock @@ -15,6 +16,7 @@ jest.mock('../../../../../agent/Agent', () => { return { Agent: jest.fn(() => ({ config: agentConfig, + context: agentContext, dependencyManager: { resolve: jest.fn(() => mediationRepository), }, @@ -57,6 +59,7 @@ describe('0.1-0.2 | Mediation', () => { // Check second object is transformed correctly expect(mediationRepository.update).toHaveBeenNthCalledWith( 2, + agentContext, getMediationRecord({ role: MediationRole.Mediator, endpoint: 'secondEndpoint', diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts b/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts index 30d5058729..0c66521d5c 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts @@ -36,7 +36,7 @@ export async function migrateConnectionRecordToV0_2(agent: Agent) { const connectionRepository = agent.dependencyManager.resolve(ConnectionRepository) agent.config.logger.debug(`Fetching all connection records from storage`) - const allConnections = await connectionRepository.getAll() + const allConnections = await connectionRepository.getAll(agent.context) agent.config.logger.debug(`Found a total of ${allConnections.length} connection records to update.`) for (const connectionRecord of allConnections) { @@ -53,7 +53,7 @@ export async function migrateConnectionRecordToV0_2(agent: Agent) { // migrateToOobRecord will return the connection record if it has not been deleted. When using multiUseInvitation the connection record // will be removed after processing, in which case the update method will throw an error. if (_connectionRecord) { - await connectionRepository.update(connectionRecord) + await connectionRepository.update(agent.context, connectionRecord) } agent.config.logger.debug( @@ -161,7 +161,7 @@ export async function extractDidDocument(agent: Agent, connectionRecord: Connect const newDidDocument = convertToNewDidDocument(oldDidDoc) // Maybe we already have a record for this did because the migration failed previously - let didRecord = await didRepository.findById(newDidDocument.id) + let didRecord = await didRepository.findById(agent.context, newDidDocument.id) if (!didRecord) { agent.config.logger.debug(`Creating did record for did ${newDidDocument.id}`) @@ -180,7 +180,7 @@ export async function extractDidDocument(agent: Agent, connectionRecord: Connect didDocumentString: JsonEncoder.toString(oldDidDocJson), }) - await didRepository.save(didRecord) + await didRepository.save(agent.context, didRecord) agent.config.logger.debug(`Successfully saved did record for did ${newDidDocument.id}`) } else { @@ -207,7 +207,7 @@ export async function extractDidDocument(agent: Agent, connectionRecord: Connect const newTheirDidDocument = convertToNewDidDocument(oldTheirDidDoc) // Maybe we already have a record for this did because the migration failed previously - let didRecord = await didRepository.findById(newTheirDidDocument.id) + let didRecord = await didRepository.findById(agent.context, newTheirDidDocument.id) if (!didRecord) { agent.config.logger.debug(`Creating did record for theirDid ${newTheirDidDocument.id}`) @@ -227,7 +227,7 @@ export async function extractDidDocument(agent: Agent, connectionRecord: Connect didDocumentString: JsonEncoder.toString(oldTheirDidDocJson), }) - await didRepository.save(didRecord) + await didRepository.save(agent.context, didRecord) agent.config.logger.debug(`Successfully saved did record for theirDid ${newTheirDidDocument.id}`) } else { @@ -310,7 +310,7 @@ export async function migrateToOobRecord( const outOfBandInvitation = convertToNewInvitation(oldInvitation) // If both the recipientKeys and the @id match we assume the connection was created using the same invitation. - const oobRecords = await oobRepository.findByQuery({ + const oobRecords = await oobRepository.findByQuery(agent.context, { invitationId: oldInvitation.id, recipientKeyFingerprints: outOfBandInvitation.getRecipientKeys().map((key) => key.fingerprint), }) @@ -337,7 +337,7 @@ export async function migrateToOobRecord( createdAt: connectionRecord.createdAt, }) - await oobRepository.save(oobRecord) + await oobRepository.save(agent.context, oobRecord) agent.config.logger.debug(`Successfully saved out of band record for invitation @id ${oldInvitation.id}`) } else { agent.config.logger.debug( @@ -353,8 +353,8 @@ export async function migrateToOobRecord( oobRecord.mediatorId = connectionRecord.mediatorId oobRecord.autoAcceptConnection = connectionRecord.autoAcceptConnection - await oobRepository.update(oobRecord) - await connectionRepository.delete(connectionRecord) + await oobRepository.update(agent.context, oobRecord) + await connectionRepository.delete(agent.context, connectionRecord) agent.config.logger.debug( `Set reusable=true for out of band record with invitation @id ${oobRecord.outOfBandInvitation.id}.` ) diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts b/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts index 548f9a6b15..2f59d915ed 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts @@ -21,7 +21,7 @@ export async function migrateCredentialRecordToV0_2(agent: Agent) { const credentialRepository = agent.dependencyManager.resolve(CredentialRepository) agent.config.logger.debug(`Fetching all credential records from storage`) - const allCredentials = await credentialRepository.getAll() + const allCredentials = await credentialRepository.getAll(agent.context) agent.config.logger.debug(`Found a total of ${allCredentials.length} credential records to update.`) for (const credentialRecord of allCredentials) { @@ -31,7 +31,7 @@ export async function migrateCredentialRecordToV0_2(agent: Agent) { await migrateInternalCredentialRecordProperties(agent, credentialRecord) await moveDidCommMessages(agent, credentialRecord) - await credentialRepository.update(credentialRecord) + await credentialRepository.update(agent.context, credentialRecord) agent.config.logger.debug( `Successfully migrated credential record with id ${credentialRecord.id} to storage version 0.2` @@ -232,7 +232,7 @@ export async function moveDidCommMessages(agent: Agent, credentialRecord: Creden associatedRecordId: credentialRecord.id, message, }) - await didCommMessageRepository.save(didCommMessageRecord) + await didCommMessageRepository.save(agent.context, didCommMessageRecord) agent.config.logger.debug( `Successfully moved ${messageKey} from credential record with id ${credentialRecord.id} to DIDCommMessageRecord` diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts b/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts index 62f9da238d..e6d3447a1c 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts @@ -1,6 +1,6 @@ -import type { V0_1ToV0_2UpdateConfig } from '.' import type { Agent } from '../../../../agent/Agent' import type { MediationRecord } from '../../../../modules/routing' +import type { V0_1ToV0_2UpdateConfig } from './index' import { MediationRepository, MediationRole } from '../../../../modules/routing' @@ -17,7 +17,7 @@ export async function migrateMediationRecordToV0_2(agent: Agent, upgradeConfig: const mediationRepository = agent.dependencyManager.resolve(MediationRepository) agent.config.logger.debug(`Fetching all mediation records from storage`) - const allMediationRecords = await mediationRepository.getAll() + const allMediationRecords = await mediationRepository.getAll(agent.context) agent.config.logger.debug(`Found a total of ${allMediationRecords.length} mediation records to update.`) for (const mediationRecord of allMediationRecords) { @@ -25,7 +25,7 @@ export async function migrateMediationRecordToV0_2(agent: Agent, upgradeConfig: await updateMediationRole(agent, mediationRecord, upgradeConfig) - await mediationRepository.update(mediationRecord) + await mediationRepository.update(agent.context, mediationRecord) agent.config.logger.debug( `Successfully migrated mediation record with id ${mediationRecord.id} to storage version 0.2` diff --git a/packages/core/src/transport/HttpOutboundTransport.ts b/packages/core/src/transport/HttpOutboundTransport.ts index 58f23db96b..a9ff5c28d6 100644 --- a/packages/core/src/transport/HttpOutboundTransport.ts +++ b/packages/core/src/transport/HttpOutboundTransport.ts @@ -7,7 +7,6 @@ import type fetch from 'node-fetch' import { AbortController } from 'abort-controller' -import { AgentConfig } from '../agent/AgentConfig' import { AgentEventTypes } from '../agent/Events' import { AriesFrameworkError } from '../error/AriesFrameworkError' import { isValidJweStructure, JsonEncoder } from '../utils' @@ -15,16 +14,14 @@ import { isValidJweStructure, JsonEncoder } from '../utils' export class HttpOutboundTransport implements OutboundTransport { private agent!: Agent private logger!: Logger - private agentConfig!: AgentConfig private fetch!: typeof fetch public supportedSchemes = ['http', 'https'] public async start(agent: Agent): Promise { this.agent = agent - this.agentConfig = agent.dependencyManager.resolve(AgentConfig) - this.logger = this.agentConfig.logger - this.fetch = this.agentConfig.agentDependencies.fetch + this.logger = this.agent.config.logger + this.fetch = this.agent.config.agentDependencies.fetch this.logger.debug('Starting HTTP outbound transport') } @@ -55,7 +52,7 @@ export class HttpOutboundTransport implements OutboundTransport { response = await this.fetch(endpoint, { method: 'POST', body: JSON.stringify(payload), - headers: { 'Content-Type': this.agentConfig.didCommMimeType }, + headers: { 'Content-Type': this.agent.config.didCommMimeType }, signal: abortController.signal, }) clearTimeout(id) @@ -87,7 +84,7 @@ export class HttpOutboundTransport implements OutboundTransport { return } // Emit event with the received agent message. - this.agent.events.emit({ + this.agent.events.emit(this.agent.context, { type: AgentEventTypes.AgentMessageReceived, payload: { message: encryptedMessage, @@ -104,7 +101,7 @@ export class HttpOutboundTransport implements OutboundTransport { error, message: error.message, body: payload, - didCommMimeType: this.agentConfig.didCommMimeType, + didCommMimeType: this.agent.config.didCommMimeType, }) throw new AriesFrameworkError(`Error sending message to ${endpoint}: ${error.message}`, { cause: error }) } diff --git a/packages/core/src/transport/WsOutboundTransport.ts b/packages/core/src/transport/WsOutboundTransport.ts index 98f351493b..b0cc8e8d28 100644 --- a/packages/core/src/transport/WsOutboundTransport.ts +++ b/packages/core/src/transport/WsOutboundTransport.ts @@ -6,8 +6,6 @@ import type { OutboundTransport } from './OutboundTransport' import type { OutboundWebSocketClosedEvent } from './TransportEventTypes' import type WebSocket from 'ws' -import { AgentConfig } from '../agent/AgentConfig' -import { EventEmitter } from '../agent/EventEmitter' import { AgentEventTypes } from '../agent/Events' import { AriesFrameworkError } from '../error/AriesFrameworkError' import { isValidJweStructure, JsonEncoder } from '../utils' @@ -19,18 +17,16 @@ export class WsOutboundTransport implements OutboundTransport { private transportTable: Map = new Map() private agent!: Agent private logger!: Logger - private eventEmitter!: EventEmitter private WebSocketClass!: typeof WebSocket public supportedSchemes = ['ws', 'wss'] public async start(agent: Agent): Promise { this.agent = agent - const agentConfig = agent.dependencyManager.resolve(AgentConfig) - this.logger = agentConfig.logger - this.eventEmitter = agent.dependencyManager.resolve(EventEmitter) + this.logger = agent.config.logger + this.logger.debug('Starting WS outbound transport') - this.WebSocketClass = agentConfig.agentDependencies.WebSocketClass + this.WebSocketClass = agent.config.agentDependencies.WebSocketClass } public async stop() { @@ -111,7 +107,8 @@ export class WsOutboundTransport implements OutboundTransport { ) } this.logger.debug('Payload received from mediator:', payload) - this.eventEmitter.emit({ + + this.agent.events.emit(this.agent.context, { type: AgentEventTypes.AgentMessageReceived, payload: { message: payload, @@ -153,7 +150,7 @@ export class WsOutboundTransport implements OutboundTransport { socket.removeEventListener('message', this.handleMessageEvent) this.transportTable.delete(socketId) - this.eventEmitter.emit({ + this.agent.events.emit(this.agent.context, { type: TransportEventTypes.OutboundWebSocketClosedEvent, payload: { socketId, diff --git a/packages/core/src/wallet/IndyWallet.test.ts b/packages/core/src/wallet/IndyWallet.test.ts index a1147a6260..a59cd82f60 100644 --- a/packages/core/src/wallet/IndyWallet.test.ts +++ b/packages/core/src/wallet/IndyWallet.test.ts @@ -1,33 +1,41 @@ +import type { WalletConfig } from '../types' + import { BBS_SIGNATURE_LENGTH } from '@mattrglobal/bbs-signatures' import { SIGNATURE_LENGTH as ED25519_SIGNATURE_LENGTH } from '@stablelib/ed25519' -import { getBaseConfig } from '../../tests/helpers' -import { Agent } from '../agent/Agent' +import { agentDependencies } from '../../tests/helpers' +import testLogger from '../../tests/logger' import { KeyType } from '../crypto' +import { KeyDerivationMethod } from '../types' import { TypedArrayEncoder } from '../utils' import { IndyWallet } from './IndyWallet' import { WalletError } from './error' +// use raw key derivation method to speed up wallet creating / opening / closing between tests +const walletConfig: WalletConfig = { + id: 'Wallet: IndyWalletTest', + // generated using indy.generateWalletKey + key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', + keyDerivationMethod: KeyDerivationMethod.Raw, +} + describe('IndyWallet', () => { let indyWallet: IndyWallet - let agent: Agent const seed = 'sample-seed' const message = TypedArrayEncoder.fromString('sample-message') beforeEach(async () => { - const { config, agentDependencies } = getBaseConfig('IndyWallettest') - agent = new Agent(config, agentDependencies) - indyWallet = agent.injectionContainer.resolve(IndyWallet) - await agent.initialize() + indyWallet = new IndyWallet(agentDependencies, testLogger) + await indyWallet.createAndOpen(walletConfig) }) afterEach(async () => { - await agent.shutdown() - await agent.wallet.delete() + await indyWallet.delete() }) - test('Get the public DID', () => { + test('Get the public DID', async () => { + await indyWallet.initPublicDid({ seed: '000000000000000000000000Trustee9' }) expect(indyWallet.publicDid).toMatchObject({ did: expect.any(String), verkey: expect.any(String), @@ -35,7 +43,7 @@ describe('IndyWallet', () => { }) test('Get the Master Secret', () => { - expect(indyWallet.masterSecretId).toEqual('Wallet: IndyWallettest') + expect(indyWallet.masterSecretId).toEqual('Wallet: IndyWalletTest') }) test('Get the wallet handle', () => { diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts index c354d5ef5b..e99143dc7b 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/core/src/wallet/IndyWallet.ts @@ -1,5 +1,4 @@ import type { BlsKeyPair } from '../crypto/BbsService' -import type { Logger } from '../logger' import type { EncryptedMessage, KeyDerivationMethod, @@ -19,12 +18,14 @@ import type { } from './Wallet' import type { default as Indy, WalletStorageConfig } from 'indy-sdk' -import { AgentConfig } from '../agent/AgentConfig' +import { AgentDependencies } from '../agent/AgentDependencies' +import { InjectionSymbols } from '../constants' import { BbsService } from '../crypto/BbsService' import { Key } from '../crypto/Key' import { KeyType } from '../crypto/KeyType' import { AriesFrameworkError, IndySdkError, RecordDuplicateError, RecordNotFoundError } from '../error' -import { injectable } from '../plugins' +import { Logger } from '../logger' +import { inject, injectable } from '../plugins' import { JsonEncoder, TypedArrayEncoder } from '../utils' import { isError } from '../utils/error' import { isIndyError } from '../utils/indyError' @@ -41,9 +42,12 @@ export class IndyWallet implements Wallet { private publicDidInfo: DidInfo | undefined private indy: typeof Indy - public constructor(agentConfig: AgentConfig) { - this.logger = agentConfig.logger - this.indy = agentConfig.agentDependencies.indy + public constructor( + @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, + @inject(InjectionSymbols.Logger) logger: Logger + ) { + this.logger = logger + this.indy = agentDependencies.indy } public get isProvisioned() { diff --git a/packages/core/src/wallet/WalletModule.ts b/packages/core/src/wallet/WalletModule.ts index 89c301b26a..7d4bd0f739 100644 --- a/packages/core/src/wallet/WalletModule.ts +++ b/packages/core/src/wallet/WalletModule.ts @@ -1,33 +1,35 @@ -import type { Logger } from '../logger' import type { DependencyManager } from '../plugins' import type { WalletConfig, WalletConfigRekey, WalletExportImportConfig } from '../types' +import type { Wallet } from './Wallet' -import { AgentConfig } from '../agent/AgentConfig' +import { AgentContext } from '../agent' import { InjectionSymbols } from '../constants' +import { Logger } from '../logger' import { inject, injectable, module } from '../plugins' import { StorageUpdateService } from '../storage' import { CURRENT_FRAMEWORK_STORAGE_VERSION } from '../storage/migration/updates' -import { Wallet } from './Wallet' import { WalletError } from './error/WalletError' import { WalletNotFoundError } from './error/WalletNotFoundError' @module() @injectable() export class WalletModule { + private agentContext: AgentContext private wallet: Wallet private storageUpdateService: StorageUpdateService private logger: Logger private _walletConfig?: WalletConfig public constructor( - @inject(InjectionSymbols.Wallet) wallet: Wallet, storageUpdateService: StorageUpdateService, - agentConfig: AgentConfig + agentContext: AgentContext, + @inject(InjectionSymbols.Logger) logger: Logger ) { - this.wallet = wallet this.storageUpdateService = storageUpdateService - this.logger = agentConfig.logger + this.logger = logger + this.wallet = agentContext.wallet + this.agentContext = agentContext } public get isInitialized() { @@ -73,7 +75,7 @@ export class WalletModule { this._walletConfig = walletConfig // Store the storage version in the wallet - await this.storageUpdateService.setCurrentStorageVersion(CURRENT_FRAMEWORK_STORAGE_VERSION) + await this.storageUpdateService.setCurrentStorageVersion(this.agentContext, CURRENT_FRAMEWORK_STORAGE_VERSION) } public async create(walletConfig: WalletConfig): Promise { diff --git a/packages/core/src/wallet/util/assertIndyWallet.ts b/packages/core/src/wallet/util/assertIndyWallet.ts new file mode 100644 index 0000000000..6c6ac4a4eb --- /dev/null +++ b/packages/core/src/wallet/util/assertIndyWallet.ts @@ -0,0 +1,10 @@ +import type { Wallet } from '../Wallet' + +import { AriesFrameworkError } from '../../error' +import { IndyWallet } from '../IndyWallet' + +export function assertIndyWallet(wallet: Wallet): asserts wallet is IndyWallet { + if (!(wallet instanceof IndyWallet)) { + throw new AriesFrameworkError(`Expected wallet to be instance of IndyWallet, found ${wallet}`) + } +} diff --git a/packages/core/tests/connectionless-proofs.test.ts b/packages/core/tests/connectionless-proofs.test.ts index b38a30c36b..ab49b8c838 100644 --- a/packages/core/tests/connectionless-proofs.test.ts +++ b/packages/core/tests/connectionless-proofs.test.ts @@ -5,6 +5,7 @@ import { Subject, ReplaySubject } from 'rxjs' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { InjectionSymbols } from '../src' import { Agent } from '../src/agent/Agent' import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment' import { HandshakeProtocol } from '../src/modules/connections' @@ -345,8 +346,10 @@ describe('Present Proof', () => { // We want to stop the mediator polling before the agent is shutdown. // FIXME: add a way to stop mediator polling from the public api, and make sure this is // being handled in the agent shutdown so we don't get any errors with wallets being closed. - faberAgent.config.stop$.next(true) - aliceAgent.config.stop$.next(true) + const faberStop$ = faberAgent.injectionContainer.resolve>(InjectionSymbols.Stop$) + const aliceStop$ = aliceAgent.injectionContainer.resolve>(InjectionSymbols.Stop$) + faberStop$.next(true) + aliceStop$.next(true) await sleep(2000) }) }) diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 6dad0458c1..f031137044 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -12,6 +12,7 @@ import type { ProofPredicateInfo, ProofStateChangedEvent, SchemaTemplate, + Wallet, } from '../src' import type { AcceptOfferOptions } from '../src/modules/credentials' import type { IndyOfferCredentialFormat } from '../src/modules/credentials/formats/indy/IndyCredentialFormat' @@ -28,14 +29,17 @@ import { agentDependencies, WalletScheme } from '../../node/src' import { Agent, AgentConfig, + AgentContext, AriesFrameworkError, BasicMessageEventTypes, ConnectionRecord, CredentialEventTypes, CredentialState, + DependencyManager, DidExchangeRole, DidExchangeState, HandshakeProtocol, + InjectionSymbols, LogLevel, PredicateType, PresentationPreview, @@ -134,6 +138,20 @@ export function getAgentConfig(name: string, extraConfig: Partial = return new AgentConfig(config, agentDependencies) } +export function getAgentContext({ + dependencyManager = new DependencyManager(), + wallet, + agentConfig, +}: { + dependencyManager?: DependencyManager + wallet?: Wallet + agentConfig?: AgentConfig +} = {}) { + if (wallet) dependencyManager.registerInstance(InjectionSymbols.Wallet, wallet) + if (agentConfig) dependencyManager.registerInstance(AgentConfig, agentConfig) + return new AgentContext({ dependencyManager }) +} + export async function waitForProofRecord( agent: Agent, options: { diff --git a/packages/core/tests/ledger.test.ts b/packages/core/tests/ledger.test.ts index 992feeab30..ce0802353d 100644 --- a/packages/core/tests/ledger.test.ts +++ b/packages/core/tests/ledger.test.ts @@ -4,7 +4,6 @@ import * as indy from 'indy-sdk' import { Agent } from '../src/agent/Agent' import { DID_IDENTIFIER_REGEX, isAbbreviatedVerkey, isFullVerkey, VERKEY_REGEX } from '../src/utils/did' import { sleep } from '../src/utils/sleep' -import { IndyWallet } from '../src/wallet/IndyWallet' import { genesisPath, getBaseConfig } from './helpers' import testLogger from './logger' @@ -65,7 +64,7 @@ describe('ledger', () => { throw new Error('Agent does not have public did.') } - const faberWallet = faberAgent.dependencyManager.resolve(IndyWallet) + const faberWallet = faberAgent.context.wallet const didInfo = await faberWallet.createDid() const result = await faberAgent.ledger.registerPublicDid(didInfo.did, didInfo.verkey, 'alias', 'TRUST_ANCHOR') diff --git a/packages/core/tests/mocks/MockWallet.ts b/packages/core/tests/mocks/MockWallet.ts new file mode 100644 index 0000000000..83132e1303 --- /dev/null +++ b/packages/core/tests/mocks/MockWallet.ts @@ -0,0 +1,74 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import type { Wallet } from '../../src' +import type { Key } from '../../src/crypto' +import type { EncryptedMessage, WalletConfig, WalletExportImportConfig, WalletConfigRekey } from '../../src/types' +import type { Buffer } from '../../src/utils/buffer' +import type { + DidInfo, + UnpackedMessageContext, + DidConfig, + CreateKeyOptions, + SignOptions, + VerifyOptions, +} from '../../src/wallet' + +export class MockWallet implements Wallet { + public publicDid = undefined + public isInitialized = true + public isProvisioned = true + + public create(walletConfig: WalletConfig): Promise { + throw new Error('Method not implemented.') + } + public createAndOpen(walletConfig: WalletConfig): Promise { + throw new Error('Method not implemented.') + } + public open(walletConfig: WalletConfig): Promise { + throw new Error('Method not implemented.') + } + public rotateKey(walletConfig: WalletConfigRekey): Promise { + throw new Error('Method not implemented.') + } + public close(): Promise { + throw new Error('Method not implemented.') + } + public delete(): Promise { + throw new Error('Method not implemented.') + } + public export(exportConfig: WalletExportImportConfig): Promise { + throw new Error('Method not implemented.') + } + public import(walletConfig: WalletConfig, importConfig: WalletExportImportConfig): Promise { + throw new Error('Method not implemented.') + } + public initPublicDid(didConfig: DidConfig): Promise { + throw new Error('Method not implemented.') + } + public createDid(didConfig?: DidConfig): Promise { + throw new Error('Method not implemented.') + } + public pack( + payload: Record, + recipientKeys: string[], + senderVerkey?: string + ): Promise { + throw new Error('Method not implemented.') + } + public unpack(encryptedMessage: EncryptedMessage): Promise { + throw new Error('Method not implemented.') + } + public sign(options: SignOptions): Promise { + throw new Error('Method not implemented.') + } + public verify(options: VerifyOptions): Promise { + throw new Error('Method not implemented.') + } + + public createKey(options: CreateKeyOptions): Promise { + throw new Error('Method not implemented.') + } + + public generateNonce(): Promise { + throw new Error('Method not implemented.') + } +} diff --git a/packages/core/tests/mocks/index.ts b/packages/core/tests/mocks/index.ts new file mode 100644 index 0000000000..3dbf2226a2 --- /dev/null +++ b/packages/core/tests/mocks/index.ts @@ -0,0 +1 @@ +export * from './MockWallet' diff --git a/packages/core/tests/multi-protocol-version.test.ts b/packages/core/tests/multi-protocol-version.test.ts index 413ec53db7..0a2f86aa93 100644 --- a/packages/core/tests/multi-protocol-version.test.ts +++ b/packages/core/tests/multi-protocol-version.test.ts @@ -84,7 +84,7 @@ describe('multi version protocols', () => { ) ) - await bobMessageSender.sendMessage(createOutboundMessage(bobConnection, new TestMessageV11())) + await bobMessageSender.sendMessage(bobAgent.context, createOutboundMessage(bobConnection, new TestMessageV11())) // Wait for the agent message processed event to be called await agentMessageV11ProcessedPromise @@ -99,7 +99,7 @@ describe('multi version protocols', () => { ) ) - await bobMessageSender.sendMessage(createOutboundMessage(bobConnection, new TestMessageV15())) + await bobMessageSender.sendMessage(bobAgent.context, createOutboundMessage(bobConnection, new TestMessageV15())) await agentMessageV15ProcessedPromise expect(mockHandle).toHaveBeenCalledTimes(2) diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index bde3e6d1c3..dc56d8ad59 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -735,7 +735,7 @@ describe('out of band', () => { message, }) - expect(saveOrUpdateSpy).toHaveBeenCalledWith({ + expect(saveOrUpdateSpy).toHaveBeenCalledWith(expect.anything(), { agentMessage: message, associatedRecordId: credentialRecord.id, role: DidCommMessageRole.Sender, diff --git a/packages/core/tests/wallet.test.ts b/packages/core/tests/wallet.test.ts index 9d06608a4a..2d6d718d0c 100644 --- a/packages/core/tests/wallet.test.ts +++ b/packages/core/tests/wallet.test.ts @@ -124,7 +124,7 @@ describe('wallet', () => { }) // Save in wallet - await bobBasicMessageRepository.save(basicMessageRecord) + await bobBasicMessageRepository.save(bobAgent.context, basicMessageRecord) if (!bobAgent.config.walletConfig) { throw new Error('No wallet config on bobAgent') @@ -142,7 +142,7 @@ describe('wallet', () => { // This should create a new wallet // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await bobAgent.wallet.initialize(bobConfig.config.walletConfig!) - expect(await bobBasicMessageRepository.findById(basicMessageRecord.id)).toBeNull() + expect(await bobBasicMessageRepository.findById(bobAgent.context, basicMessageRecord.id)).toBeNull() await bobAgent.wallet.delete() // Import backup with different wallet id and initialize @@ -150,7 +150,9 @@ describe('wallet', () => { await bobAgent.wallet.initialize({ id: backupWalletName, key: backupWalletName }) // Expect same basic message record to exist in new wallet - expect(await bobBasicMessageRepository.getById(basicMessageRecord.id)).toMatchObject(basicMessageRecord) + expect(await bobBasicMessageRepository.getById(bobAgent.context, basicMessageRecord.id)).toMatchObject( + basicMessageRecord + ) }) test('changing wallet key', async () => { diff --git a/packages/node/src/transport/HttpInboundTransport.ts b/packages/node/src/transport/HttpInboundTransport.ts index 2835ce2a84..59144ee392 100644 --- a/packages/node/src/transport/HttpInboundTransport.ts +++ b/packages/node/src/transport/HttpInboundTransport.ts @@ -2,7 +2,7 @@ import type { InboundTransport, Agent, TransportSession, EncryptedMessage } from import type { Express, Request, Response } from 'express' import type { Server } from 'http' -import { DidCommMimeType, AriesFrameworkError, AgentConfig, TransportService, utils } from '@aries-framework/core' +import { DidCommMimeType, AriesFrameworkError, TransportService, utils } from '@aries-framework/core' import express, { text } from 'express' export class HttpInboundTransport implements InboundTransport { @@ -30,9 +30,8 @@ export class HttpInboundTransport implements InboundTransport { public async start(agent: Agent) { const transportService = agent.dependencyManager.resolve(TransportService) - const config = agent.dependencyManager.resolve(AgentConfig) - config.logger.debug(`Starting HTTP inbound transport`, { + agent.config.logger.debug(`Starting HTTP inbound transport`, { port: this.port, }) @@ -48,7 +47,7 @@ export class HttpInboundTransport implements InboundTransport { res.status(200).end() } } catch (error) { - config.logger.error(`Error processing inbound message: ${error.message}`, error) + agent.config.logger.error(`Error processing inbound message: ${error.message}`, error) if (!res.headersSent) { res.status(500).send('Error processing message') diff --git a/packages/node/src/transport/WsInboundTransport.ts b/packages/node/src/transport/WsInboundTransport.ts index c8f32817ab..25528c44ca 100644 --- a/packages/node/src/transport/WsInboundTransport.ts +++ b/packages/node/src/transport/WsInboundTransport.ts @@ -1,6 +1,6 @@ import type { Agent, InboundTransport, Logger, TransportSession, EncryptedMessage } from '@aries-framework/core' -import { AriesFrameworkError, AgentConfig, TransportService, utils } from '@aries-framework/core' +import { AriesFrameworkError, TransportService, utils } from '@aries-framework/core' import WebSocket, { Server } from 'ws' export class WsInboundTransport implements InboundTransport { @@ -16,11 +16,10 @@ export class WsInboundTransport implements InboundTransport { public async start(agent: Agent) { const transportService = agent.dependencyManager.resolve(TransportService) - const config = agent.dependencyManager.resolve(AgentConfig) - this.logger = config.logger + this.logger = agent.config.logger - const wsEndpoint = config.endpoints.find((e) => e.startsWith('ws')) + const wsEndpoint = agent.config.endpoints.find((e) => e.startsWith('ws')) this.logger.debug(`Starting WS inbound transport`, { endpoint: wsEndpoint, }) diff --git a/samples/extension-module/dummy/DummyApi.ts b/samples/extension-module/dummy/DummyApi.ts index b15735148a..82ac700f7b 100644 --- a/samples/extension-module/dummy/DummyApi.ts +++ b/samples/extension-module/dummy/DummyApi.ts @@ -1,6 +1,6 @@ import type { DummyRecord } from './repository/DummyRecord' -import { injectable, ConnectionService, Dispatcher, MessageSender } from '@aries-framework/core' +import { AgentContext, ConnectionService, Dispatcher, injectable, MessageSender } from '@aries-framework/core' import { DummyRequestHandler, DummyResponseHandler } from './handlers' import { DummyState } from './repository' @@ -11,16 +11,20 @@ export class DummyApi { private messageSender: MessageSender private dummyService: DummyService private connectionService: ConnectionService + private agentContext: AgentContext public constructor( dispatcher: Dispatcher, messageSender: MessageSender, dummyService: DummyService, - connectionService: ConnectionService + connectionService: ConnectionService, + agentContext: AgentContext ) { this.messageSender = messageSender this.dummyService = dummyService this.connectionService = connectionService + this.agentContext = agentContext + this.registerHandlers(dispatcher) } @@ -31,12 +35,12 @@ export class DummyApi { * @returns created Dummy Record */ public async request(connectionId: string) { - const connection = await this.connectionService.getById(connectionId) - const { record, message: payload } = await this.dummyService.createRequest(connection) + const connection = await this.connectionService.getById(this.agentContext, connectionId) + const { record, message: payload } = await this.dummyService.createRequest(this.agentContext, connection) - await this.messageSender.sendMessage({ connection, payload }) + await this.messageSender.sendMessage(this.agentContext, { connection, payload }) - await this.dummyService.updateState(record, DummyState.RequestSent) + await this.dummyService.updateState(this.agentContext, record, DummyState.RequestSent) return record } @@ -48,14 +52,14 @@ export class DummyApi { * @returns Updated dummy record */ public async respond(dummyId: string) { - const record = await this.dummyService.getById(dummyId) - const connection = await this.connectionService.getById(record.connectionId) + const record = await this.dummyService.getById(this.agentContext, dummyId) + const connection = await this.connectionService.getById(this.agentContext, record.connectionId) - const payload = await this.dummyService.createResponse(record) + const payload = await this.dummyService.createResponse(this.agentContext, record) - await this.messageSender.sendMessage({ connection, payload }) + await this.messageSender.sendMessage(this.agentContext, { connection, payload }) - await this.dummyService.updateState(record, DummyState.ResponseSent) + await this.dummyService.updateState(this.agentContext, record, DummyState.ResponseSent) return record } @@ -66,7 +70,7 @@ export class DummyApi { * @returns List containing all records */ public getAll(): Promise { - return this.dummyService.getAll() + return this.dummyService.getAll(this.agentContext) } private registerHandlers(dispatcher: Dispatcher) { diff --git a/samples/extension-module/dummy/services/DummyService.ts b/samples/extension-module/dummy/services/DummyService.ts index 3cc73eba9f..2defd9d393 100644 --- a/samples/extension-module/dummy/services/DummyService.ts +++ b/samples/extension-module/dummy/services/DummyService.ts @@ -1,5 +1,5 @@ import type { DummyStateChangedEvent } from './DummyEvents' -import type { ConnectionRecord, InboundMessageContext } from '@aries-framework/core' +import type { AgentContext, ConnectionRecord, InboundMessageContext } from '@aries-framework/core' import { injectable, JsonTransformer, EventEmitter } from '@aries-framework/core' @@ -27,7 +27,7 @@ export class DummyService { * @returns Object containing dummy request message and associated dummy record * */ - public async createRequest(connectionRecord: ConnectionRecord) { + public async createRequest(agentContext: AgentContext, connectionRecord: ConnectionRecord) { // Create message const message = new DummyRequestMessage({}) @@ -38,9 +38,9 @@ export class DummyService { state: DummyState.Init, }) - await this.dummyRepository.save(record) + await this.dummyRepository.save(agentContext, record) - this.emitStateChangedEvent(record, null) + this.emitStateChangedEvent(agentContext, record, null) return { record, message } } @@ -51,7 +51,7 @@ export class DummyService { * @param record the dummy record for which to create a dummy response * @returns outbound message containing dummy response */ - public async createResponse(record: DummyRecord) { + public async createResponse(agentContext: AgentContext, record: DummyRecord) { const responseMessage = new DummyResponseMessage({ threadId: record.threadId, }) @@ -76,9 +76,9 @@ export class DummyService { state: DummyState.RequestReceived, }) - await this.dummyRepository.save(record) + await this.dummyRepository.save(messageContext.agentContext, record) - this.emitStateChangedEvent(record, null) + this.emitStateChangedEvent(messageContext.agentContext, record, null) return record } @@ -96,13 +96,13 @@ export class DummyService { const connection = messageContext.assertReadyConnection() // Dummy record already exists - const record = await this.findByThreadAndConnectionId(message.threadId, connection.id) + const record = await this.findByThreadAndConnectionId(messageContext.agentContext, message.threadId, connection.id) if (record) { // Check current state record.assertState(DummyState.RequestSent) - await this.updateState(record, DummyState.ResponseReceived) + await this.updateState(messageContext.agentContext, record, DummyState.ResponseReceived) } else { throw new Error(`Dummy record not found with threadId ${message.threadId}`) } @@ -115,8 +115,8 @@ export class DummyService { * * @returns List containing all dummy records */ - public getAll(): Promise { - return this.dummyRepository.getAll() + public getAll(agentContext: AgentContext): Promise { + return this.dummyRepository.getAll(agentContext) } /** @@ -127,8 +127,8 @@ export class DummyService { * @return The dummy record * */ - public getById(dummyRecordId: string): Promise { - return this.dummyRepository.getById(dummyRecordId) + public getById(agentContext: AgentContext, dummyRecordId: string): Promise { + return this.dummyRepository.getById(agentContext, dummyRecordId) } /** @@ -140,8 +140,12 @@ export class DummyService { * @throws {RecordDuplicateError} If multiple records are found * @returns The dummy record */ - public async findByThreadAndConnectionId(threadId: string, connectionId?: string): Promise { - return this.dummyRepository.findSingleByQuery({ threadId, connectionId }) + public async findByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise { + return this.dummyRepository.findSingleByQuery(agentContext, { threadId, connectionId }) } /** @@ -152,19 +156,23 @@ export class DummyService { * @param newState The state to update to * */ - public async updateState(dummyRecord: DummyRecord, newState: DummyState) { + public async updateState(agentContext: AgentContext, dummyRecord: DummyRecord, newState: DummyState) { const previousState = dummyRecord.state dummyRecord.state = newState - await this.dummyRepository.update(dummyRecord) + await this.dummyRepository.update(agentContext, dummyRecord) - this.emitStateChangedEvent(dummyRecord, previousState) + this.emitStateChangedEvent(agentContext, dummyRecord, previousState) } - private emitStateChangedEvent(dummyRecord: DummyRecord, previousState: DummyState | null) { + private emitStateChangedEvent( + agentContext: AgentContext, + dummyRecord: DummyRecord, + previousState: DummyState | null + ) { // we need to clone the dummy record to avoid mutating records after they're emitted in an event const clonedDummyRecord = JsonTransformer.clone(dummyRecord) - this.eventEmitter.emit({ + this.eventEmitter.emit(agentContext, { type: DummyEventTypes.StateChanged, payload: { dummyRecord: clonedDummyRecord, previousState: previousState }, }) diff --git a/samples/mediator.ts b/samples/mediator.ts index ec57dc253b..da4dd15293 100644 --- a/samples/mediator.ts +++ b/samples/mediator.ts @@ -25,7 +25,6 @@ import { Agent, ConnectionInvitationMessage, LogLevel, - AgentConfig, WsOutboundTransport, } from '@aries-framework/core' import { HttpInboundTransport, agentDependencies, WsInboundTransport } from '@aries-framework/node' @@ -55,7 +54,7 @@ const agentConfig: InitConfig = { // Set up agent const agent = new Agent(agentConfig, agentDependencies) -const config = agent.dependencyManager.resolve(AgentConfig) +const config = agent.config // Create all transports const httpInboundTransport = new HttpInboundTransport({ app, port }) diff --git a/tests/InMemoryStorageService.ts b/tests/InMemoryStorageService.ts index 4feb45fc58..cd4415a2e5 100644 --- a/tests/InMemoryStorageService.ts +++ b/tests/InMemoryStorageService.ts @@ -1,3 +1,4 @@ +import type { AgentContext } from '../packages/core/src/agent' import type { BaseRecord, TagsBase } from '../packages/core/src/storage/BaseRecord' import type { StorageService, BaseRecordConstructor, Query } from '../packages/core/src/storage/StorageService' @@ -33,7 +34,7 @@ export class InMemoryStorageService implement } /** @inheritDoc */ - public async save(record: T) { + public async save(agentContext: AgentContext, record: T) { const value = JsonTransformer.toJSON(record) if (this.records[record.id]) { @@ -49,7 +50,7 @@ export class InMemoryStorageService implement } /** @inheritDoc */ - public async update(record: T): Promise { + public async update(agentContext: AgentContext, record: T): Promise { const value = JsonTransformer.toJSON(record) delete value._tags @@ -68,7 +69,7 @@ export class InMemoryStorageService implement } /** @inheritDoc */ - public async delete(record: T) { + public async delete(agentContext: AgentContext, record: T) { if (!this.records[record.id]) { throw new RecordNotFoundError(`record with id ${record.id} not found.`, { recordType: record.type, @@ -79,7 +80,11 @@ export class InMemoryStorageService implement } /** @inheritDoc */ - public async deleteById(recordClass: BaseRecordConstructor, id: string): Promise { + public async deleteById( + agentContext: AgentContext, + recordClass: BaseRecordConstructor, + id: string + ): Promise { if (!this.records[id]) { throw new RecordNotFoundError(`record with id ${id} not found.`, { recordType: recordClass.type, @@ -90,7 +95,7 @@ export class InMemoryStorageService implement } /** @inheritDoc */ - public async getById(recordClass: BaseRecordConstructor, id: string): Promise { + public async getById(agentContext: AgentContext, recordClass: BaseRecordConstructor, id: string): Promise { const record = this.records[id] if (!record) { @@ -103,7 +108,7 @@ export class InMemoryStorageService implement } /** @inheritDoc */ - public async getAll(recordClass: BaseRecordConstructor): Promise { + public async getAll(agentContext: AgentContext, recordClass: BaseRecordConstructor): Promise { const records = Object.values(this.records) .filter((record) => record.type === recordClass.type) .map((record) => this.recordToInstance(record, recordClass)) @@ -112,7 +117,11 @@ export class InMemoryStorageService implement } /** @inheritDoc */ - public async findByQuery(recordClass: BaseRecordConstructor, query: Query): Promise { + public async findByQuery( + agentContext: AgentContext, + recordClass: BaseRecordConstructor, + query: Query + ): Promise { if (query.$and || query.$or || query.$not) { throw new AriesFrameworkError( 'Advanced wallet query features $and, $or or $not not supported in in memory storage' diff --git a/tests/e2e-test.ts b/tests/e2e-test.ts index d074e0aa6c..7c42b6a13d 100644 --- a/tests/e2e-test.ts +++ b/tests/e2e-test.ts @@ -1,9 +1,11 @@ import type { Agent } from '@aries-framework/core' +import type { Subject } from 'rxjs' import { sleep } from '../packages/core/src/utils/sleep' import { issueCredential, makeConnection, prepareForIssuance, presentProof } from '../packages/core/tests/helpers' import { + InjectionSymbols, V1CredentialPreview, AttributeFilter, CredentialState, @@ -95,6 +97,7 @@ export async function e2eTest({ // We want to stop the mediator polling before the agent is shutdown. // FIXME: add a way to stop mediator polling from the public api, and make sure this is // being handled in the agent shutdown so we don't get any errors with wallets being closed. - recipientAgent.config.stop$.next(true) + const recipientStop$ = recipientAgent.injectionContainer.resolve>(InjectionSymbols.Stop$) + recipientStop$.next(true) await sleep(2000) } diff --git a/tests/transport/SubjectInboundTransport.ts b/tests/transport/SubjectInboundTransport.ts index 6611d616ae..cd713f7d3f 100644 --- a/tests/transport/SubjectInboundTransport.ts +++ b/tests/transport/SubjectInboundTransport.ts @@ -3,7 +3,6 @@ import type { TransportSession } from '../../packages/core/src/agent/TransportSe import type { EncryptedMessage } from '../../packages/core/src/types' import type { Subject, Subscription } from 'rxjs' -import { AgentConfig } from '../../packages/core/src/agent/AgentConfig' import { TransportService } from '../../packages/core/src/agent/TransportService' import { uuid } from '../../packages/core/src/utils/uuid' @@ -26,7 +25,7 @@ export class SubjectInboundTransport implements InboundTransport { } private subscribe(agent: Agent) { - const logger = agent.dependencyManager.resolve(AgentConfig).logger + const logger = agent.config.logger const transportService = agent.dependencyManager.resolve(TransportService) this.subscription = this.ourSubject.subscribe({ diff --git a/tests/transport/SubjectOutboundTransport.ts b/tests/transport/SubjectOutboundTransport.ts index 7adc82b10d..1754dbe067 100644 --- a/tests/transport/SubjectOutboundTransport.ts +++ b/tests/transport/SubjectOutboundTransport.ts @@ -9,6 +9,7 @@ export class SubjectOutboundTransport implements OutboundTransport { private logger!: Logger private subjectMap: { [key: string]: Subject | undefined } private agent!: Agent + private stop$!: Subject public supportedSchemes = ['rxjs'] @@ -20,6 +21,7 @@ export class SubjectOutboundTransport implements OutboundTransport { this.agent = agent this.logger = agent.dependencyManager.resolve(InjectionSymbols.Logger) + this.stop$ = agent.dependencyManager.resolve(InjectionSymbols.Stop$) } public async stop(): Promise { @@ -45,9 +47,9 @@ export class SubjectOutboundTransport implements OutboundTransport { // Create a replySubject just for this session. Both ends will be able to close it, // mimicking a transport like http or websocket. Close session automatically when agent stops const replySubject = new Subject() - this.agent.config.stop$.pipe(take(1)).subscribe(() => !replySubject.closed && replySubject.complete()) + this.stop$.pipe(take(1)).subscribe(() => !replySubject.closed && replySubject.complete()) - replySubject.pipe(takeUntil(this.agent.config.stop$)).subscribe({ + replySubject.pipe(takeUntil(this.stop$)).subscribe({ next: async ({ message }: SubjectMessage) => { this.logger.test('Received message') From a1b1e5a22fd4ab9ef593b5cd7b3c710afcab3142 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 7 Jul 2022 11:19:33 +0200 Subject: [PATCH 010/125] feat: add agent context provider (#921) Signed-off-by: Timo Glastra --- packages/core/src/agent/Agent.ts | 29 +++++++++-- packages/core/src/agent/AgentContext.ts | 32 ------------ packages/core/src/agent/EnvelopeService.ts | 2 +- packages/core/src/agent/EventEmitter.ts | 2 +- packages/core/src/agent/Events.ts | 1 + packages/core/src/agent/MessageReceiver.ts | 25 +++++++--- packages/core/src/agent/MessageSender.ts | 2 +- .../core/src/agent/context/AgentContext.ts | 49 +++++++++++++++++++ .../src/agent/context/AgentContextProvider.ts | 17 +++++++ .../context/DefaultAgentContextProvider.ts | 24 +++++++++ .../DefaultAgentContextProvider.test.ts | 18 +++++++ packages/core/src/agent/context/index.ts | 3 ++ packages/core/src/agent/index.ts | 2 +- .../src/agent/models/InboundMessageContext.ts | 2 +- packages/core/src/constants.ts | 1 + packages/core/src/index.ts | 3 +- .../core/src/modules/oob/OutOfBandModule.ts | 2 + .../proofs/ProofResponseCoordinator.ts | 2 +- .../pickup/v1/handlers/BatchHandler.ts | 1 + .../services/MediationRecipientService.ts | 1 + .../MediationRecipientService.test.ts | 2 + packages/core/tests/helpers.ts | 4 +- .../src/transport/HttpInboundTransport.ts | 7 ++- .../node/src/transport/WsInboundTransport.ts | 6 ++- tests/transport/SubjectInboundTransport.ts | 4 +- tests/transport/SubjectOutboundTransport.ts | 5 +- 26 files changed, 189 insertions(+), 57 deletions(-) delete mode 100644 packages/core/src/agent/AgentContext.ts create mode 100644 packages/core/src/agent/context/AgentContext.ts create mode 100644 packages/core/src/agent/context/AgentContextProvider.ts create mode 100644 packages/core/src/agent/context/DefaultAgentContextProvider.ts create mode 100644 packages/core/src/agent/context/__tests__/DefaultAgentContextProvider.test.ts create mode 100644 packages/core/src/agent/context/index.ts diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 49c9e37050..ce9a324b19 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -42,7 +42,6 @@ import { WalletModule } from '../wallet/WalletModule' import { WalletError } from '../wallet/error' import { AgentConfig } from './AgentConfig' -import { AgentContext } from './AgentContext' import { Dispatcher } from './Dispatcher' import { EnvelopeService } from './EnvelopeService' import { EventEmitter } from './EventEmitter' @@ -50,6 +49,7 @@ import { AgentEventTypes } from './Events' import { MessageReceiver } from './MessageReceiver' import { MessageSender } from './MessageSender' import { TransportService } from './TransportService' +import { AgentContext, DefaultAgentContextProvider } from './context' export class Agent { protected agentConfig: AgentConfig @@ -138,8 +138,9 @@ export class Agent { .pipe( takeUntil(this.stop$), concatMap((e) => - this.messageReceiver.receiveMessage(this.agentContext, e.payload.message, { + this.messageReceiver.receiveMessage(e.payload.message, { connection: e.payload.connection, + contextCorrelationId: e.payload.contextCorrelationId, }) ) ) @@ -269,8 +270,18 @@ export class Agent { return this.agentContext.wallet.publicDid } + /** + * Receive a message. This should mainly be used for receiving connection-less messages. + * + * If you want to receive messages that originated from e.g. a transport make sure to use the {@link MessageReceiver} + * for this. The `receiveMessage` method on the `Agent` class will associate the current context to the message, which + * may not be what should happen (e.g. in case of multi tenancy). + */ public async receiveMessage(inboundMessage: unknown, session?: TransportSession) { - return await this.messageReceiver.receiveMessage(this.agentContext, inboundMessage, { session }) + return await this.messageReceiver.receiveMessage(inboundMessage, { + session, + contextCorrelationId: this.agentContext.contextCorrelationId, + }) } public get injectionContainer() { @@ -368,6 +379,16 @@ export class Agent { W3cVcModule ) - dependencyManager.registerInstance(AgentContext, new AgentContext({ dependencyManager })) + // TODO: contextCorrelationId for base wallet + // Bind the default agent context to the container for use in modules etc. + dependencyManager.registerInstance( + AgentContext, + new AgentContext({ dependencyManager, contextCorrelationId: 'default' }) + ) + + // If no agent context provider has been registered we use the default agent context provider. + if (!this.dependencyManager.isRegistered(InjectionSymbols.AgentContextProvider)) { + this.dependencyManager.registerSingleton(InjectionSymbols.AgentContextProvider, DefaultAgentContextProvider) + } } } diff --git a/packages/core/src/agent/AgentContext.ts b/packages/core/src/agent/AgentContext.ts deleted file mode 100644 index a8e176d67f..0000000000 --- a/packages/core/src/agent/AgentContext.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { DependencyManager } from '../plugins' -import type { Wallet } from '../wallet' - -import { InjectionSymbols } from '../constants' - -import { AgentConfig } from './AgentConfig' - -export class AgentContext { - /** - * Dependency manager holds all dependencies for the current context. Possibly a child of a parent dependency manager, - * in which case all singleton dependencies from the parent context are also available to this context. - */ - public readonly dependencyManager: DependencyManager - - public constructor({ dependencyManager }: { dependencyManager: DependencyManager }) { - this.dependencyManager = dependencyManager - } - - /** - * Convenience method to access the agent config for the current context. - */ - public get config() { - return this.dependencyManager.resolve(AgentConfig) - } - - /** - * Convenience method to access the wallet for the current context. - */ - public get wallet() { - return this.dependencyManager.resolve(InjectionSymbols.Wallet) - } -} diff --git a/packages/core/src/agent/EnvelopeService.ts b/packages/core/src/agent/EnvelopeService.ts index d2ca8e4e51..cd50b22fc0 100644 --- a/packages/core/src/agent/EnvelopeService.ts +++ b/packages/core/src/agent/EnvelopeService.ts @@ -1,6 +1,6 @@ import type { EncryptedMessage, PlaintextMessage } from '../types' -import type { AgentContext } from './AgentContext' import type { AgentMessage } from './AgentMessage' +import type { AgentContext } from './context' import { InjectionSymbols } from '../constants' import { Key, KeyType } from '../crypto' diff --git a/packages/core/src/agent/EventEmitter.ts b/packages/core/src/agent/EventEmitter.ts index 284dcc1709..3dfe6205b3 100644 --- a/packages/core/src/agent/EventEmitter.ts +++ b/packages/core/src/agent/EventEmitter.ts @@ -1,5 +1,5 @@ -import type { AgentContext } from './AgentContext' import type { BaseEvent } from './Events' +import type { AgentContext } from './context' import type { EventEmitter as NativeEventEmitter } from 'events' import { fromEventPattern, Subject } from 'rxjs' diff --git a/packages/core/src/agent/Events.ts b/packages/core/src/agent/Events.ts index f6bc64a7bb..9c34620ca4 100644 --- a/packages/core/src/agent/Events.ts +++ b/packages/core/src/agent/Events.ts @@ -16,6 +16,7 @@ export interface AgentMessageReceivedEvent extends BaseEvent { payload: { message: unknown connection?: ConnectionRecord + contextCorrelationId?: string } } diff --git a/packages/core/src/agent/MessageReceiver.ts b/packages/core/src/agent/MessageReceiver.ts index 31282e0252..5fbef72a0f 100644 --- a/packages/core/src/agent/MessageReceiver.ts +++ b/packages/core/src/agent/MessageReceiver.ts @@ -1,17 +1,17 @@ import type { ConnectionRecord } from '../modules/connections' import type { InboundTransport } from '../transport' import type { EncryptedMessage, PlaintextMessage } from '../types' -import type { AgentContext } from './AgentContext' import type { AgentMessage } from './AgentMessage' import type { DecryptedMessageContext } from './EnvelopeService' import type { TransportSession } from './TransportService' +import type { AgentContext } from './context' import { InjectionSymbols } from '../constants' import { AriesFrameworkError } from '../error' import { Logger } from '../logger' import { ConnectionService } from '../modules/connections' import { ProblemReportError, ProblemReportMessage, ProblemReportReason } from '../modules/problem-reports' -import { injectable, inject } from '../plugins' +import { inject, injectable } from '../plugins' import { isValidJweStructure } from '../utils/JWE' import { JsonTransformer } from '../utils/JsonTransformer' import { canHandleMessageType, parseMessageType, replaceLegacyDidSovPrefixOnMessage } from '../utils/messageType' @@ -20,6 +20,7 @@ import { Dispatcher } from './Dispatcher' import { EnvelopeService } from './EnvelopeService' import { MessageSender } from './MessageSender' import { TransportService } from './TransportService' +import { AgentContextProvider } from './context' import { createOutboundMessage } from './helpers' import { InboundMessageContext } from './models/InboundMessageContext' @@ -31,6 +32,7 @@ export class MessageReceiver { private dispatcher: Dispatcher private logger: Logger private connectionService: ConnectionService + private agentContextProvider: AgentContextProvider public readonly inboundTransports: InboundTransport[] = [] public constructor( @@ -39,6 +41,7 @@ export class MessageReceiver { messageSender: MessageSender, connectionService: ConnectionService, dispatcher: Dispatcher, + @inject(InjectionSymbols.AgentContextProvider) agentContextProvider: AgentContextProvider, @inject(InjectionSymbols.Logger) logger: Logger ) { this.envelopeService = envelopeService @@ -46,6 +49,7 @@ export class MessageReceiver { this.messageSender = messageSender this.connectionService = connectionService this.dispatcher = dispatcher + this.agentContextProvider = agentContextProvider this.logger = logger } @@ -54,17 +58,26 @@ export class MessageReceiver { } /** - * Receive and handle an inbound DIDComm message. It will decrypt the message, transform it + * Receive and handle an inbound DIDComm message. It will determine the agent context, decrypt the message, transform it * to it's corresponding message class and finally dispatch it to the dispatcher. * * @param inboundMessage the message to receive and handle */ public async receiveMessage( - agentContext: AgentContext, inboundMessage: unknown, - { session, connection }: { session?: TransportSession; connection?: ConnectionRecord } + { + session, + connection, + contextCorrelationId, + }: { session?: TransportSession; connection?: ConnectionRecord; contextCorrelationId?: string } = {} ) { - this.logger.debug(`Agent ${agentContext.config.label} received message`) + this.logger.debug(`Agent received message`) + + // Find agent context for the inbound message + const agentContext = await this.agentContextProvider.getContextForInboundMessage(inboundMessage, { + contextCorrelationId, + }) + if (this.isEncryptedMessage(inboundMessage)) { await this.receiveEncryptedMessage(agentContext, inboundMessage as EncryptedMessage, session) } else if (this.isPlaintextMessage(inboundMessage)) { diff --git a/packages/core/src/agent/MessageSender.ts b/packages/core/src/agent/MessageSender.ts index a817124e31..5269bf72be 100644 --- a/packages/core/src/agent/MessageSender.ts +++ b/packages/core/src/agent/MessageSender.ts @@ -4,10 +4,10 @@ import type { DidDocument } from '../modules/dids' import type { OutOfBandRecord } from '../modules/oob/repository' import type { OutboundTransport } from '../transport/OutboundTransport' import type { OutboundMessage, OutboundPackage, EncryptedMessage } from '../types' -import type { AgentContext } from './AgentContext' import type { AgentMessage } from './AgentMessage' import type { EnvelopeKeys } from './EnvelopeService' import type { TransportSession } from './TransportService' +import type { AgentContext } from './context' import { DID_COMM_TRANSPORT_QUEUE, InjectionSymbols } from '../constants' import { ReturnRouteTypes } from '../decorators/transport/TransportDecorator' diff --git a/packages/core/src/agent/context/AgentContext.ts b/packages/core/src/agent/context/AgentContext.ts new file mode 100644 index 0000000000..ef425377b2 --- /dev/null +++ b/packages/core/src/agent/context/AgentContext.ts @@ -0,0 +1,49 @@ +import type { DependencyManager } from '../../plugins' +import type { Wallet } from '../../wallet' + +import { InjectionSymbols } from '../../constants' +import { AgentConfig } from '../AgentConfig' + +export class AgentContext { + /** + * Dependency manager holds all dependencies for the current context. Possibly a child of a parent dependency manager, + * in which case all singleton dependencies from the parent context are also available to this context. + */ + public readonly dependencyManager: DependencyManager + + /** + * An identifier that allows to correlate this context across sessions. This identifier is created by the `AgentContextProvider` + * and should only be meaningful to the `AgentContextProvider`. The `contextCorrelationId` MUST uniquely identity the context and + * should be enough to start a new session. + * + * An example of the `contextCorrelationId` is for example the id of the `TenantRecord` that is associated with this context when using the tenant module. + * The `TenantAgentContextProvider` will set the `contextCorrelationId` to the `TenantRecord` id when creating the context, and will be able to create a context + * for a specific tenant using the `contextCorrelationId`. + */ + public readonly contextCorrelationId: string + + public constructor({ + dependencyManager, + contextCorrelationId, + }: { + dependencyManager: DependencyManager + contextCorrelationId: string + }) { + this.dependencyManager = dependencyManager + this.contextCorrelationId = contextCorrelationId + } + + /** + * Convenience method to access the agent config for the current context. + */ + public get config() { + return this.dependencyManager.resolve(AgentConfig) + } + + /** + * Convenience method to access the wallet for the current context. + */ + public get wallet() { + return this.dependencyManager.resolve(InjectionSymbols.Wallet) + } +} diff --git a/packages/core/src/agent/context/AgentContextProvider.ts b/packages/core/src/agent/context/AgentContextProvider.ts new file mode 100644 index 0000000000..09047d38b6 --- /dev/null +++ b/packages/core/src/agent/context/AgentContextProvider.ts @@ -0,0 +1,17 @@ +import type { AgentContext } from './AgentContext' + +export interface AgentContextProvider { + /** + * Find the agent context based for an inbound message. It's possible to provide a contextCorrelationId to make it + * easier for the context provider implementation to correlate inbound messages to the correct context. This can be useful if + * a plaintext message is passed and the context provider can't determine the context based on the recipient public keys + * of the inbound message. + * + * The implementation of this method could range from a very simple one that always returns the same context to + * a complex one that manages the context for a multi-tenant agent. + */ + getContextForInboundMessage( + inboundMessage: unknown, + options?: { contextCorrelationId?: string } + ): Promise +} diff --git a/packages/core/src/agent/context/DefaultAgentContextProvider.ts b/packages/core/src/agent/context/DefaultAgentContextProvider.ts new file mode 100644 index 0000000000..3227dadc55 --- /dev/null +++ b/packages/core/src/agent/context/DefaultAgentContextProvider.ts @@ -0,0 +1,24 @@ +import type { AgentContextProvider } from './AgentContextProvider' + +import { injectable } from '../../plugins' + +import { AgentContext } from './AgentContext' + +/** + * Default implementation of AgentContextProvider. + * + * Holds a single `AgentContext` instance that will be used for all messages, i.e. a + * a single tenant agent. + */ +@injectable() +export class DefaultAgentContextProvider implements AgentContextProvider { + private agentContext: AgentContext + + public constructor(agentContext: AgentContext) { + this.agentContext = agentContext + } + + public async getContextForInboundMessage(): Promise { + return this.agentContext + } +} diff --git a/packages/core/src/agent/context/__tests__/DefaultAgentContextProvider.test.ts b/packages/core/src/agent/context/__tests__/DefaultAgentContextProvider.test.ts new file mode 100644 index 0000000000..f7faa58c78 --- /dev/null +++ b/packages/core/src/agent/context/__tests__/DefaultAgentContextProvider.test.ts @@ -0,0 +1,18 @@ +import type { AgentContextProvider } from '../AgentContextProvider' + +import { getAgentContext } from '../../../../tests/helpers' +import { DefaultAgentContextProvider } from '../DefaultAgentContextProvider' + +const agentContext = getAgentContext() + +describe('DefaultAgentContextProvider', () => { + describe('getContextForInboundMessage()', () => { + test('returns the agent context provided in the constructor', async () => { + const agentContextProvider: AgentContextProvider = new DefaultAgentContextProvider(agentContext) + + const message = {} + + await expect(agentContextProvider.getContextForInboundMessage(message)).resolves.toBe(agentContext) + }) + }) +}) diff --git a/packages/core/src/agent/context/index.ts b/packages/core/src/agent/context/index.ts new file mode 100644 index 0000000000..6f46b27942 --- /dev/null +++ b/packages/core/src/agent/context/index.ts @@ -0,0 +1,3 @@ +export * from './AgentContext' +export * from './AgentContextProvider' +export * from './DefaultAgentContextProvider' diff --git a/packages/core/src/agent/index.ts b/packages/core/src/agent/index.ts index 615455eb43..630b4d7e78 100644 --- a/packages/core/src/agent/index.ts +++ b/packages/core/src/agent/index.ts @@ -1 +1 @@ -export * from './AgentContext' +export * from './context' diff --git a/packages/core/src/agent/models/InboundMessageContext.ts b/packages/core/src/agent/models/InboundMessageContext.ts index a31d7a8614..c3f3628e09 100644 --- a/packages/core/src/agent/models/InboundMessageContext.ts +++ b/packages/core/src/agent/models/InboundMessageContext.ts @@ -1,7 +1,7 @@ import type { Key } from '../../crypto' import type { ConnectionRecord } from '../../modules/connections' -import type { AgentContext } from '../AgentContext' import type { AgentMessage } from '../AgentMessage' +import type { AgentContext } from '../context' import { AriesFrameworkError } from '../../error' diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index 9d7fdcbc61..0c67d367f6 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -2,6 +2,7 @@ export const InjectionSymbols = { MessageRepository: Symbol('MessageRepository'), StorageService: Symbol('StorageService'), Logger: Symbol('Logger'), + AgentContextProvider: Symbol('AgentContextProvider'), AgentDependencies: Symbol('AgentDependencies'), Stop$: Symbol('Stop$'), FileSystem: Symbol('FileSystem'), diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c2de657677..ac2359d908 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,7 +1,8 @@ // reflect-metadata used for class-transformer + class-validator import 'reflect-metadata' -export { AgentContext } from './agent/AgentContext' +export { AgentContext } from './agent' +export { MessageReceiver } from './agent/MessageReceiver' export { Agent } from './agent/Agent' export { EventEmitter } from './agent/EventEmitter' export { Handler, HandlerInboundMessage } from './agent/Handler' diff --git a/packages/core/src/modules/oob/OutOfBandModule.ts b/packages/core/src/modules/oob/OutOfBandModule.ts index 0a5ac5b55b..8d6cce08f1 100644 --- a/packages/core/src/modules/oob/OutOfBandModule.ts +++ b/packages/core/src/modules/oob/OutOfBandModule.ts @@ -625,6 +625,7 @@ export class OutOfBandModule { payload: { message: plaintextMessage, connection: connectionRecord, + contextCorrelationId: this.agentContext.contextCorrelationId, }, }) } @@ -666,6 +667,7 @@ export class OutOfBandModule { type: AgentEventTypes.AgentMessageReceived, payload: { message: plaintextMessage, + contextCorrelationId: this.agentContext.contextCorrelationId, }, }) } diff --git a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts index 7e95e73682..26f1d6b795 100644 --- a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts +++ b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts @@ -1,4 +1,4 @@ -import type { AgentContext } from '../../agent/AgentContext' +import type { AgentContext } from '../../agent/context' import type { ProofRecord } from './repository' import { injectable } from '../../plugins' diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts index b1449f6af5..b67bf8b1bb 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts @@ -24,6 +24,7 @@ export class BatchHandler implements Handler { type: AgentEventTypes.AgentMessageReceived, payload: { message: message.message, + contextCorrelationId: messageContext.agentContext.contextCorrelationId, }, }) }) diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index f9a8b50be9..a345231d9e 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -300,6 +300,7 @@ export class MediationRecipientService { type: AgentEventTypes.AgentMessageReceived, payload: { message: attachment.getDataAsJson(), + contextCorrelationId: messageContext.agentContext.contextCorrelationId, }, }) } diff --git a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts index 7e4263512b..92f1cd2141 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts @@ -244,12 +244,14 @@ describe('MediationRecipientService', () => { type: AgentEventTypes.AgentMessageReceived, payload: { message: { first: 'value' }, + contextCorrelationId: agentContext.contextCorrelationId, }, }) expect(eventEmitter.emit).toHaveBeenNthCalledWith(2, agentContext, { type: AgentEventTypes.AgentMessageReceived, payload: { message: { second: 'value' }, + contextCorrelationId: agentContext.contextCorrelationId, }, }) }) diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index f031137044..5c5be4adaf 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -142,14 +142,16 @@ export function getAgentContext({ dependencyManager = new DependencyManager(), wallet, agentConfig, + contextCorrelationId = 'mock', }: { dependencyManager?: DependencyManager wallet?: Wallet agentConfig?: AgentConfig + contextCorrelationId?: string } = {}) { if (wallet) dependencyManager.registerInstance(InjectionSymbols.Wallet, wallet) if (agentConfig) dependencyManager.registerInstance(AgentConfig, agentConfig) - return new AgentContext({ dependencyManager }) + return new AgentContext({ dependencyManager, contextCorrelationId }) } export async function waitForProofRecord( diff --git a/packages/node/src/transport/HttpInboundTransport.ts b/packages/node/src/transport/HttpInboundTransport.ts index 59144ee392..4c7fea9fdf 100644 --- a/packages/node/src/transport/HttpInboundTransport.ts +++ b/packages/node/src/transport/HttpInboundTransport.ts @@ -2,7 +2,7 @@ import type { InboundTransport, Agent, TransportSession, EncryptedMessage } from import type { Express, Request, Response } from 'express' import type { Server } from 'http' -import { DidCommMimeType, AriesFrameworkError, TransportService, utils } from '@aries-framework/core' +import { DidCommMimeType, AriesFrameworkError, TransportService, utils, MessageReceiver } from '@aries-framework/core' import express, { text } from 'express' export class HttpInboundTransport implements InboundTransport { @@ -30,6 +30,7 @@ export class HttpInboundTransport implements InboundTransport { public async start(agent: Agent) { const transportService = agent.dependencyManager.resolve(TransportService) + const messageReceiver = agent.dependencyManager.resolve(MessageReceiver) agent.config.logger.debug(`Starting HTTP inbound transport`, { port: this.port, @@ -40,7 +41,9 @@ export class HttpInboundTransport implements InboundTransport { try { const message = req.body const encryptedMessage = JSON.parse(message) - await agent.receiveMessage(encryptedMessage, session) + await messageReceiver.receiveMessage(encryptedMessage, { + session, + }) // If agent did not use session when processing message we need to send response here. if (!res.headersSent) { diff --git a/packages/node/src/transport/WsInboundTransport.ts b/packages/node/src/transport/WsInboundTransport.ts index 25528c44ca..2fa23c6168 100644 --- a/packages/node/src/transport/WsInboundTransport.ts +++ b/packages/node/src/transport/WsInboundTransport.ts @@ -1,6 +1,6 @@ import type { Agent, InboundTransport, Logger, TransportSession, EncryptedMessage } from '@aries-framework/core' -import { AriesFrameworkError, TransportService, utils } from '@aries-framework/core' +import { AriesFrameworkError, TransportService, utils, MessageReceiver } from '@aries-framework/core' import WebSocket, { Server } from 'ws' export class WsInboundTransport implements InboundTransport { @@ -58,11 +58,13 @@ export class WsInboundTransport implements InboundTransport { } private listenOnWebSocketMessages(agent: Agent, socket: WebSocket, session: TransportSession) { + const messageReceiver = agent.injectionContainer.resolve(MessageReceiver) + // eslint-disable-next-line @typescript-eslint/no-explicit-any socket.addEventListener('message', async (event: any) => { this.logger.debug('WebSocket message event received.', { url: event.target.url }) try { - await agent.receiveMessage(JSON.parse(event.data), session) + await messageReceiver.receiveMessage(JSON.parse(event.data), { session }) } catch (error) { this.logger.error('Error processing message') } diff --git a/tests/transport/SubjectInboundTransport.ts b/tests/transport/SubjectInboundTransport.ts index cd713f7d3f..572784fb17 100644 --- a/tests/transport/SubjectInboundTransport.ts +++ b/tests/transport/SubjectInboundTransport.ts @@ -3,6 +3,7 @@ import type { TransportSession } from '../../packages/core/src/agent/TransportSe import type { EncryptedMessage } from '../../packages/core/src/types' import type { Subject, Subscription } from 'rxjs' +import { MessageReceiver } from '../../packages/core/src' import { TransportService } from '../../packages/core/src/agent/TransportService' import { uuid } from '../../packages/core/src/utils/uuid' @@ -27,6 +28,7 @@ export class SubjectInboundTransport implements InboundTransport { private subscribe(agent: Agent) { const logger = agent.config.logger const transportService = agent.dependencyManager.resolve(TransportService) + const messageReceiver = agent.dependencyManager.resolve(MessageReceiver) this.subscription = this.ourSubject.subscribe({ next: async ({ message, replySubject }: SubjectMessage) => { @@ -44,7 +46,7 @@ export class SubjectInboundTransport implements InboundTransport { }) } - await agent.receiveMessage(message, session) + await messageReceiver.receiveMessage(message, { session }) }, }) } diff --git a/tests/transport/SubjectOutboundTransport.ts b/tests/transport/SubjectOutboundTransport.ts index 1754dbe067..7a7adfaa8e 100644 --- a/tests/transport/SubjectOutboundTransport.ts +++ b/tests/transport/SubjectOutboundTransport.ts @@ -3,7 +3,7 @@ import type { OutboundPackage, OutboundTransport, Agent, Logger } from '@aries-f import { takeUntil, Subject, take } from 'rxjs' -import { InjectionSymbols, AriesFrameworkError } from '@aries-framework/core' +import { MessageReceiver, InjectionSymbols, AriesFrameworkError } from '@aries-framework/core' export class SubjectOutboundTransport implements OutboundTransport { private logger!: Logger @@ -29,6 +29,7 @@ export class SubjectOutboundTransport implements OutboundTransport { } public async sendMessage(outboundPackage: OutboundPackage) { + const messageReceiver = this.agent.injectionContainer.resolve(MessageReceiver) this.logger.debug(`Sending outbound message to endpoint ${outboundPackage.endpoint}`, { endpoint: outboundPackage.endpoint, }) @@ -53,7 +54,7 @@ export class SubjectOutboundTransport implements OutboundTransport { next: async ({ message }: SubjectMessage) => { this.logger.test('Received message') - await this.agent.receiveMessage(message) + await messageReceiver.receiveMessage(message) }, }) From 113a5756ed1b630b3c05929d79f6afcceae4fa6a Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 8 Jul 2022 12:18:00 +0200 Subject: [PATCH 011/125] feat: add base agent class (#922) Signed-off-by: Timo Glastra --- packages/core/src/agent/Agent.ts | 243 ++++-------------- packages/core/src/agent/BaseAgent.ts | 194 ++++++++++++++ .../src/modules/vc/W3cCredentialService.ts | 2 +- .../src/storage/migration/UpdateAssistant.ts | 15 +- .../core/src/storage/migration/updates.ts | 4 +- .../migration/updates/0.1-0.2/connection.ts | 13 +- .../migration/updates/0.1-0.2/credential.ts | 16 +- .../migration/updates/0.1-0.2/index.ts | 4 +- .../migration/updates/0.1-0.2/mediation.ts | 9 +- 9 files changed, 277 insertions(+), 223 deletions(-) create mode 100644 packages/core/src/agent/BaseAgent.ts diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index ce9a324b19..2d10d61b05 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -1,10 +1,9 @@ -import type { Logger } from '../logger' +import type { DependencyManager } from '../plugins' import type { InboundTransport } from '../transport/InboundTransport' import type { OutboundTransport } from '../transport/OutboundTransport' import type { InitConfig } from '../types' import type { AgentDependencies } from './AgentDependencies' import type { AgentMessageReceivedEvent } from './Events' -import type { TransportSession } from './TransportService' import type { Subscription } from 'rxjs' import type { DependencyContainer } from 'tsyringe' @@ -29,19 +28,15 @@ import { ProofsModule } from '../modules/proofs/ProofsModule' import { QuestionAnswerModule } from '../modules/question-answer/QuestionAnswerModule' import { MediatorModule } from '../modules/routing/MediatorModule' import { RecipientModule } from '../modules/routing/RecipientModule' -import { RoutingService } from '../modules/routing/services/RoutingService' import { W3cVcModule } from '../modules/vc/module' -import { DependencyManager } from '../plugins' -import { StorageUpdateService, DidCommMessageRepository, StorageVersionRepository } from '../storage' +import { DidCommMessageRepository, StorageUpdateService, StorageVersionRepository } from '../storage' import { InMemoryMessageRepository } from '../storage/InMemoryMessageRepository' import { IndyStorageService } from '../storage/IndyStorageService' -import { UpdateAssistant } from '../storage/migration/UpdateAssistant' -import { DEFAULT_UPDATE_CONFIG } from '../storage/migration/updates' import { IndyWallet } from '../wallet/IndyWallet' import { WalletModule } from '../wallet/WalletModule' -import { WalletError } from '../wallet/error' import { AgentConfig } from './AgentConfig' +import { BaseAgent } from './BaseAgent' import { Dispatcher } from './Dispatcher' import { EnvelopeService } from './EnvelopeService' import { EventEmitter } from './EventEmitter' @@ -51,92 +46,25 @@ import { MessageSender } from './MessageSender' import { TransportService } from './TransportService' import { AgentContext, DefaultAgentContextProvider } from './context' -export class Agent { - protected agentConfig: AgentConfig - protected logger: Logger - public readonly dependencyManager: DependencyManager - protected eventEmitter: EventEmitter - protected messageReceiver: MessageReceiver - protected transportService: TransportService - protected messageSender: MessageSender - private _isInitialized = false +export class Agent extends BaseAgent { public messageSubscription: Subscription - private routingService: RoutingService - private agentContext: AgentContext - private stop$ = new Subject() - - public readonly connections: ConnectionsModule - public readonly proofs: ProofsModule - public readonly basicMessages: BasicMessagesModule - public readonly genericRecords: GenericRecordsModule - public readonly ledger: LedgerModule - public readonly questionAnswer!: QuestionAnswerModule - public readonly credentials: CredentialsModule - public readonly mediationRecipient: RecipientModule - public readonly mediator: MediatorModule - public readonly discovery: DiscoverFeaturesModule - public readonly dids: DidsModule - public readonly wallet: WalletModule - public readonly oob!: OutOfBandModule public constructor( initialConfig: InitConfig, dependencies: AgentDependencies, injectionContainer?: DependencyContainer ) { - // Take input container or child container so we don't interfere with anything outside of this agent - const container = injectionContainer ?? baseContainer.createChildContainer() - - this.dependencyManager = new DependencyManager(container) - - this.agentConfig = new AgentConfig(initialConfig, dependencies) - this.logger = this.agentConfig.logger - - this.logger.info('Creating agent with config', { - ...initialConfig, - // Prevent large object being logged. - // Will display true/false to indicate if value is present in config - logger: initialConfig.logger != undefined, - }) - - if (!this.agentConfig.walletConfig) { - this.logger.warn( - 'Wallet config has not been set on the agent config. ' + - 'Make sure to initialize the wallet yourself before initializing the agent, ' + - 'or provide the required wallet configuration in the agent constructor' - ) - } + // NOTE: we can't create variables before calling super as TS will complain that the super call must be the + // the first statement in the constructor. + super(new AgentConfig(initialConfig, dependencies), injectionContainer ?? baseContainer.createChildContainer()) - this.registerDependencies(this.dependencyManager) - - // Resolve instances after everything is registered - this.eventEmitter = this.dependencyManager.resolve(EventEmitter) - this.messageSender = this.dependencyManager.resolve(MessageSender) - this.messageReceiver = this.dependencyManager.resolve(MessageReceiver) - this.transportService = this.dependencyManager.resolve(TransportService) - this.routingService = this.dependencyManager.resolve(RoutingService) - this.agentContext = this.dependencyManager.resolve(AgentContext) - - // We set the modules in the constructor because that allows to set them as read-only - this.connections = this.dependencyManager.resolve(ConnectionsModule) - this.credentials = this.dependencyManager.resolve(CredentialsModule) as CredentialsModule - this.proofs = this.dependencyManager.resolve(ProofsModule) - this.mediator = this.dependencyManager.resolve(MediatorModule) - this.mediationRecipient = this.dependencyManager.resolve(RecipientModule) - this.basicMessages = this.dependencyManager.resolve(BasicMessagesModule) - this.questionAnswer = this.dependencyManager.resolve(QuestionAnswerModule) - this.genericRecords = this.dependencyManager.resolve(GenericRecordsModule) - this.ledger = this.dependencyManager.resolve(LedgerModule) - this.discovery = this.dependencyManager.resolve(DiscoverFeaturesModule) - this.dids = this.dependencyManager.resolve(DidsModule) - this.wallet = this.dependencyManager.resolve(WalletModule) - this.oob = this.dependencyManager.resolve(OutOfBandModule) + const stop$ = this.dependencyManager.resolve>(InjectionSymbols.Stop$) // Listen for new messages (either from transports or somewhere else in the framework / extensions) this.messageSubscription = this.eventEmitter .observable(AgentEventTypes.AgentMessageReceived) .pipe( - takeUntil(this.stop$), + takeUntil(stop$), concatMap((e) => this.messageReceiver.receiveMessage(e.payload.message, { connection: e.payload.connection, @@ -172,51 +100,9 @@ export class Agent { } public async initialize() { - const { connectToIndyLedgersOnStartup, publicDidSeed, walletConfig, mediatorConnectionsInvite } = this.agentConfig - - if (this._isInitialized) { - throw new AriesFrameworkError( - 'Agent already initialized. Currently it is not supported to re-initialize an already initialized agent.' - ) - } - - if (!this.wallet.isInitialized && walletConfig) { - await this.wallet.initialize(walletConfig) - } else if (!this.wallet.isInitialized) { - throw new WalletError( - 'Wallet config has not been set on the agent config. ' + - 'Make sure to initialize the wallet yourself before initializing the agent, ' + - 'or provide the required wallet configuration in the agent constructor' - ) - } - - // Make sure the storage is up to date - const storageUpdateService = this.dependencyManager.resolve(StorageUpdateService) - const isStorageUpToDate = await storageUpdateService.isUpToDate(this.agentContext) - this.logger.info(`Agent storage is ${isStorageUpToDate ? '' : 'not '}up to date.`) - - if (!isStorageUpToDate && this.agentConfig.autoUpdateStorageOnStartup) { - const updateAssistant = new UpdateAssistant(this, DEFAULT_UPDATE_CONFIG) - - await updateAssistant.initialize() - await updateAssistant.update() - } else if (!isStorageUpToDate) { - const currentVersion = await storageUpdateService.getCurrentStorageVersion(this.agentContext) - // Close wallet to prevent un-initialized agent with initialized wallet - await this.wallet.close() - throw new AriesFrameworkError( - // TODO: add link to where documentation on how to update can be found. - `Current agent storage is not up to date. ` + - `To prevent the framework state from getting corrupted the agent initialization is aborted. ` + - `Make sure to update the agent storage (currently at ${currentVersion}) to the latest version (${UpdateAssistant.frameworkStorageVersion}). ` + - `You can also downgrade your version of Aries Framework JavaScript.` - ) - } + const { connectToIndyLedgersOnStartup, mediatorConnectionsInvite } = this.agentConfig - if (publicDidSeed) { - // If an agent has publicDid it will be used as routing key. - await this.agentContext.wallet.initPublicDid({ seed: publicDidSeed }) - } + await super.initialize() // set the pools on the ledger. this.ledger.setPools(this.agentContext.config.indyLedgers) @@ -250,84 +136,20 @@ export class Agent { } public async shutdown() { + const stop$ = this.dependencyManager.resolve>(InjectionSymbols.Stop$) // All observables use takeUntil with the stop$ observable // this means all observables will stop running if a value is emitted on this observable - this.stop$.next(true) + stop$.next(true) // Stop transports const allTransports = [...this.inboundTransports, ...this.outboundTransports] const transportPromises = allTransports.map((transport) => transport.stop()) await Promise.all(transportPromises) - // close wallet if still initialized - if (this.wallet.isInitialized) { - await this.wallet.close() - } - this._isInitialized = false - } - - public get publicDid() { - return this.agentContext.wallet.publicDid - } - - /** - * Receive a message. This should mainly be used for receiving connection-less messages. - * - * If you want to receive messages that originated from e.g. a transport make sure to use the {@link MessageReceiver} - * for this. The `receiveMessage` method on the `Agent` class will associate the current context to the message, which - * may not be what should happen (e.g. in case of multi tenancy). - */ - public async receiveMessage(inboundMessage: unknown, session?: TransportSession) { - return await this.messageReceiver.receiveMessage(inboundMessage, { - session, - contextCorrelationId: this.agentContext.contextCorrelationId, - }) - } - - public get injectionContainer() { - return this.dependencyManager.container - } - - public get config() { - return this.agentConfig - } - - public get context() { - return this.agentContext - } - - private async getMediationConnection(mediatorInvitationUrl: string) { - const outOfBandInvitation = this.oob.parseInvitation(mediatorInvitationUrl) - const outOfBandRecord = await this.oob.findByInvitationId(outOfBandInvitation.id) - const [connection] = outOfBandRecord ? await this.connections.findAllByOutOfBandId(outOfBandRecord.id) : [] - - if (!connection) { - this.logger.debug('Mediation connection does not exist, creating connection') - // We don't want to use the current default mediator when connecting to another mediator - const routing = await this.routingService.getRouting(this.agentContext, { useDefaultMediator: false }) - - this.logger.debug('Routing created', routing) - const { connectionRecord: newConnection } = await this.oob.receiveInvitation(outOfBandInvitation, { - routing, - }) - this.logger.debug(`Mediation invitation processed`, { outOfBandInvitation }) - - if (!newConnection) { - throw new AriesFrameworkError('No connection record to provision mediation.') - } - - return this.connections.returnWhenIsConnected(newConnection.id) - } - - if (!connection.isReady) { - return this.connections.returnWhenIsConnected(connection.id) - } - return connection + await super.shutdown() } - private registerDependencies(dependencyManager: DependencyManager) { - const dependencies = this.agentConfig.agentDependencies - + protected registerDependencies(dependencyManager: DependencyManager) { // Register internal dependencies dependencyManager.registerSingleton(EventEmitter) dependencyManager.registerSingleton(MessageSender) @@ -342,9 +164,9 @@ export class Agent { dependencyManager.registerSingleton(StorageUpdateService) dependencyManager.registerInstance(AgentConfig, this.agentConfig) - dependencyManager.registerInstance(InjectionSymbols.AgentDependencies, dependencies) - dependencyManager.registerInstance(InjectionSymbols.FileSystem, new dependencies.FileSystem()) - dependencyManager.registerInstance(InjectionSymbols.Stop$, this.stop$) + dependencyManager.registerInstance(InjectionSymbols.AgentDependencies, this.agentConfig.agentDependencies) + dependencyManager.registerInstance(InjectionSymbols.Stop$, new Subject()) + dependencyManager.registerInstance(InjectionSymbols.FileSystem, new this.agentConfig.agentDependencies.FileSystem()) // Register possibly already defined services if (!dependencyManager.isRegistered(InjectionSymbols.Wallet)) { @@ -391,4 +213,33 @@ export class Agent { this.dependencyManager.registerSingleton(InjectionSymbols.AgentContextProvider, DefaultAgentContextProvider) } } + + protected async getMediationConnection(mediatorInvitationUrl: string) { + const outOfBandInvitation = this.oob.parseInvitation(mediatorInvitationUrl) + const outOfBandRecord = await this.oob.findByInvitationId(outOfBandInvitation.id) + const [connection] = outOfBandRecord ? await this.connections.findAllByOutOfBandId(outOfBandRecord.id) : [] + + if (!connection) { + this.logger.debug('Mediation connection does not exist, creating connection') + // We don't want to use the current default mediator when connecting to another mediator + const routing = await this.mediationRecipient.getRouting({ useDefaultMediator: false }) + + this.logger.debug('Routing created', routing) + const { connectionRecord: newConnection } = await this.oob.receiveInvitation(outOfBandInvitation, { + routing, + }) + this.logger.debug(`Mediation invitation processed`, { outOfBandInvitation }) + + if (!newConnection) { + throw new AriesFrameworkError('No connection record to provision mediation.') + } + + return this.connections.returnWhenIsConnected(newConnection.id) + } + + if (!connection.isReady) { + return this.connections.returnWhenIsConnected(connection.id) + } + return connection + } } diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts new file mode 100644 index 0000000000..eea807e245 --- /dev/null +++ b/packages/core/src/agent/BaseAgent.ts @@ -0,0 +1,194 @@ +import type { Logger } from '../logger' +import type { AgentConfig } from './AgentConfig' +import type { TransportSession } from './TransportService' +import type { DependencyContainer } from 'tsyringe' + +import { AriesFrameworkError } from '../error' +import { BasicMessagesModule } from '../modules/basic-messages/BasicMessagesModule' +import { ConnectionsModule } from '../modules/connections/ConnectionsModule' +import { CredentialsModule } from '../modules/credentials/CredentialsModule' +import { DidsModule } from '../modules/dids/DidsModule' +import { DiscoverFeaturesModule } from '../modules/discover-features' +import { GenericRecordsModule } from '../modules/generic-records/GenericRecordsModule' +import { LedgerModule } from '../modules/ledger/LedgerModule' +import { OutOfBandModule } from '../modules/oob/OutOfBandModule' +import { ProofsModule } from '../modules/proofs/ProofsModule' +import { QuestionAnswerModule } from '../modules/question-answer/QuestionAnswerModule' +import { MediatorModule } from '../modules/routing/MediatorModule' +import { RecipientModule } from '../modules/routing/RecipientModule' +import { DependencyManager } from '../plugins' +import { StorageUpdateService } from '../storage' +import { UpdateAssistant } from '../storage/migration/UpdateAssistant' +import { DEFAULT_UPDATE_CONFIG } from '../storage/migration/updates' +import { WalletModule } from '../wallet/WalletModule' +import { WalletError } from '../wallet/error' + +import { EventEmitter } from './EventEmitter' +import { MessageReceiver } from './MessageReceiver' +import { MessageSender } from './MessageSender' +import { TransportService } from './TransportService' +import { AgentContext } from './context' + +export abstract class BaseAgent { + protected agentConfig: AgentConfig + protected logger: Logger + public readonly dependencyManager: DependencyManager + protected eventEmitter: EventEmitter + protected messageReceiver: MessageReceiver + protected transportService: TransportService + protected messageSender: MessageSender + protected _isInitialized = false + protected agentContext: AgentContext + + public readonly connections: ConnectionsModule + public readonly proofs: ProofsModule + public readonly basicMessages: BasicMessagesModule + public readonly genericRecords: GenericRecordsModule + public readonly ledger: LedgerModule + public readonly questionAnswer!: QuestionAnswerModule + public readonly credentials: CredentialsModule + public readonly mediationRecipient: RecipientModule + public readonly mediator: MediatorModule + public readonly discovery: DiscoverFeaturesModule + public readonly dids: DidsModule + public readonly wallet: WalletModule + public readonly oob: OutOfBandModule + + public constructor(agentConfig: AgentConfig, container: DependencyContainer) { + this.dependencyManager = new DependencyManager(container) + + this.agentConfig = agentConfig + this.logger = this.agentConfig.logger + + this.logger.info('Creating agent with config', { + ...agentConfig, + // Prevent large object being logged. + // Will display true/false to indicate if value is present in config + logger: agentConfig.logger != undefined, + }) + + if (!this.agentConfig.walletConfig) { + this.logger.warn( + 'Wallet config has not been set on the agent config. ' + + 'Make sure to initialize the wallet yourself before initializing the agent, ' + + 'or provide the required wallet configuration in the agent constructor' + ) + } + + this.registerDependencies(this.dependencyManager) + + // Resolve instances after everything is registered + this.eventEmitter = this.dependencyManager.resolve(EventEmitter) + this.messageSender = this.dependencyManager.resolve(MessageSender) + this.messageReceiver = this.dependencyManager.resolve(MessageReceiver) + this.transportService = this.dependencyManager.resolve(TransportService) + this.agentContext = this.dependencyManager.resolve(AgentContext) + + // We set the modules in the constructor because that allows to set them as read-only + this.connections = this.dependencyManager.resolve(ConnectionsModule) + this.credentials = this.dependencyManager.resolve(CredentialsModule) as CredentialsModule + this.proofs = this.dependencyManager.resolve(ProofsModule) + this.mediator = this.dependencyManager.resolve(MediatorModule) + this.mediationRecipient = this.dependencyManager.resolve(RecipientModule) + this.basicMessages = this.dependencyManager.resolve(BasicMessagesModule) + this.questionAnswer = this.dependencyManager.resolve(QuestionAnswerModule) + this.genericRecords = this.dependencyManager.resolve(GenericRecordsModule) + this.ledger = this.dependencyManager.resolve(LedgerModule) + this.discovery = this.dependencyManager.resolve(DiscoverFeaturesModule) + this.dids = this.dependencyManager.resolve(DidsModule) + this.wallet = this.dependencyManager.resolve(WalletModule) + this.oob = this.dependencyManager.resolve(OutOfBandModule) + } + + public get isInitialized() { + return this._isInitialized && this.wallet.isInitialized + } + + public async initialize() { + const { publicDidSeed, walletConfig } = this.agentConfig + + if (this._isInitialized) { + throw new AriesFrameworkError( + 'Agent already initialized. Currently it is not supported to re-initialize an already initialized agent.' + ) + } + + if (!this.wallet.isInitialized && walletConfig) { + await this.wallet.initialize(walletConfig) + } else if (!this.wallet.isInitialized) { + throw new WalletError( + 'Wallet config has not been set on the agent config. ' + + 'Make sure to initialize the wallet yourself before initializing the agent, ' + + 'or provide the required wallet configuration in the agent constructor' + ) + } + + // Make sure the storage is up to date + const storageUpdateService = this.dependencyManager.resolve(StorageUpdateService) + const isStorageUpToDate = await storageUpdateService.isUpToDate(this.agentContext) + this.logger.info(`Agent storage is ${isStorageUpToDate ? '' : 'not '}up to date.`) + + if (!isStorageUpToDate && this.agentConfig.autoUpdateStorageOnStartup) { + const updateAssistant = new UpdateAssistant(this, DEFAULT_UPDATE_CONFIG) + + await updateAssistant.initialize() + await updateAssistant.update() + } else if (!isStorageUpToDate) { + const currentVersion = await storageUpdateService.getCurrentStorageVersion(this.agentContext) + // Close wallet to prevent un-initialized agent with initialized wallet + await this.wallet.close() + throw new AriesFrameworkError( + // TODO: add link to where documentation on how to update can be found. + `Current agent storage is not up to date. ` + + `To prevent the framework state from getting corrupted the agent initialization is aborted. ` + + `Make sure to update the agent storage (currently at ${currentVersion}) to the latest version (${UpdateAssistant.frameworkStorageVersion}). ` + + `You can also downgrade your version of Aries Framework JavaScript.` + ) + } + + if (publicDidSeed) { + // If an agent has publicDid it will be used as routing key. + await this.agentContext.wallet.initPublicDid({ seed: publicDidSeed }) + } + } + + public async shutdown() { + // close wallet if still initialized + if (this.wallet.isInitialized) { + await this.wallet.close() + } + this._isInitialized = false + } + + public get publicDid() { + return this.agentContext.wallet.publicDid + } + + /** + * Receive a message. This should mainly be used for receiving connection-less messages. + * + * If you want to receive messages that originated from e.g. a transport make sure to use the {@link MessageReceiver} + * for this. The `receiveMessage` method on the `Agent` class will associate the current context to the message, which + * may not be what should happen (e.g. in case of multi tenancy). + */ + public async receiveMessage(inboundMessage: unknown, session?: TransportSession) { + return await this.messageReceiver.receiveMessage(inboundMessage, { + session, + contextCorrelationId: this.agentContext.contextCorrelationId, + }) + } + + public get injectionContainer() { + return this.dependencyManager.container + } + + public get config() { + return this.agentConfig + } + + public get context() { + return this.agentContext + } + + protected abstract registerDependencies(dependencyManager: DependencyManager): void +} diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 5c724388a3..e46260224f 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -1,4 +1,4 @@ -import type { AgentContext } from '../..' +import type { AgentContext } from '../../agent/context' import type { Key } from '../../crypto/Key' import type { DocumentLoader } from './jsonldUtil' import type { W3cVerifyCredentialResult } from './models' diff --git a/packages/core/src/storage/migration/UpdateAssistant.ts b/packages/core/src/storage/migration/UpdateAssistant.ts index cb07798529..3cca35ab59 100644 --- a/packages/core/src/storage/migration/UpdateAssistant.ts +++ b/packages/core/src/storage/migration/UpdateAssistant.ts @@ -1,8 +1,7 @@ -import type { Agent } from '../../agent/Agent' +import type { BaseAgent } from '../../agent/BaseAgent' import type { FileSystem } from '../FileSystem' import type { UpdateConfig } from './updates' -import { AgentContext } from '../../agent' import { InjectionSymbols } from '../../constants' import { AriesFrameworkError } from '../../error' import { isFirstVersionHigherThanSecond, parseVersionString } from '../../utils/version' @@ -12,11 +11,10 @@ import { StorageUpdateService } from './StorageUpdateService' import { StorageUpdateError } from './error/StorageUpdateError' import { CURRENT_FRAMEWORK_STORAGE_VERSION, supportedUpdates } from './updates' -export class UpdateAssistant { +export class UpdateAssistant { private agent: Agent private storageUpdateService: StorageUpdateService private updateConfig: UpdateConfig - private agentContext: AgentContext private fileSystem: FileSystem public constructor(agent: Agent, updateConfig: UpdateConfig) { @@ -24,7 +22,6 @@ export class UpdateAssistant { this.updateConfig = updateConfig this.storageUpdateService = this.agent.dependencyManager.resolve(StorageUpdateService) - this.agentContext = this.agent.dependencyManager.resolve(AgentContext) this.fileSystem = this.agent.dependencyManager.resolve(InjectionSymbols.FileSystem) } @@ -46,11 +43,11 @@ export class UpdateAssistant { } public async isUpToDate() { - return this.storageUpdateService.isUpToDate(this.agentContext) + return this.storageUpdateService.isUpToDate(this.agent.context) } public async getCurrentAgentStorageVersion() { - return this.storageUpdateService.getCurrentStorageVersion(this.agentContext) + return this.storageUpdateService.getCurrentStorageVersion(this.agent.context) } public static get frameworkStorageVersion() { @@ -59,7 +56,7 @@ export class UpdateAssistant { public async getNeededUpdates() { const currentStorageVersion = parseVersionString( - await this.storageUpdateService.getCurrentStorageVersion(this.agentContext) + await this.storageUpdateService.getCurrentStorageVersion(this.agent.context) ) // Filter updates. We don't want older updates we already applied @@ -113,7 +110,7 @@ export class UpdateAssistant { await update.doUpdate(this.agent, this.updateConfig) // Update the framework version in storage - await this.storageUpdateService.setCurrentStorageVersion(this.agentContext, update.toVersion) + await this.storageUpdateService.setCurrentStorageVersion(this.agent.context, update.toVersion) this.agent.config.logger.info( `Successfully updated agent storage from version ${update.fromVersion} to version ${update.toVersion}` ) diff --git a/packages/core/src/storage/migration/updates.ts b/packages/core/src/storage/migration/updates.ts index c2f7fabb03..86d9ef5b71 100644 --- a/packages/core/src/storage/migration/updates.ts +++ b/packages/core/src/storage/migration/updates.ts @@ -1,4 +1,4 @@ -import type { Agent } from '../../agent/Agent' +import type { BaseAgent } from '../../agent/BaseAgent' import type { VersionString } from '../../utils/version' import type { V0_1ToV0_2UpdateConfig } from './updates/0.1-0.2' @@ -9,7 +9,7 @@ export const INITIAL_STORAGE_VERSION = '0.1' export interface Update { fromVersion: VersionString toVersion: VersionString - doUpdate: (agent: Agent, updateConfig: UpdateConfig) => Promise + doUpdate: (agent: Agent, updateConfig: UpdateConfig) => Promise } export interface UpdateConfig { diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts b/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts index 0c66521d5c..8166818ef3 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts @@ -1,4 +1,4 @@ -import type { Agent } from '../../../../agent/Agent' +import type { BaseAgent } from '../../../../agent/BaseAgent' import type { ConnectionRecord } from '../../../../modules/connections' import type { JsonObject } from '../../../../types' @@ -31,7 +31,7 @@ import { JsonEncoder, JsonTransformer } from '../../../../utils' * - {@link extractDidDocument} * - {@link migrateToOobRecord} */ -export async function migrateConnectionRecordToV0_2(agent: Agent) { +export async function migrateConnectionRecordToV0_2(agent: Agent) { agent.config.logger.info('Migrating connection records to storage version 0.2') const connectionRepository = agent.dependencyManager.resolve(ConnectionRepository) @@ -87,7 +87,10 @@ export async function migrateConnectionRecordToV0_2(agent: Agent) { * } * ``` */ -export async function updateConnectionRoleAndState(agent: Agent, connectionRecord: ConnectionRecord) { +export async function updateConnectionRoleAndState( + agent: Agent, + connectionRecord: ConnectionRecord +) { agent.config.logger.debug( `Extracting 'didDoc' and 'theirDidDoc' from connection record into separate DidRecord and updating unqualified dids to did:peer dids` ) @@ -140,7 +143,7 @@ export async function updateConnectionRoleAndState(agent: Agent, connectionRecor * } * ``` */ -export async function extractDidDocument(agent: Agent, connectionRecord: ConnectionRecord) { +export async function extractDidDocument(agent: Agent, connectionRecord: ConnectionRecord) { agent.config.logger.debug( `Extracting 'didDoc' and 'theirDidDoc' from connection record into separate DidRecord and updating unqualified dids to did:peer dids` ) @@ -286,7 +289,7 @@ export async function extractDidDocument(agent: Agent, connectionRecord: Connect * } * ``` */ -export async function migrateToOobRecord( +export async function migrateToOobRecord( agent: Agent, connectionRecord: ConnectionRecord ): Promise { diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts b/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts index 2f59d915ed..647f8e8a40 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts @@ -1,4 +1,4 @@ -import type { Agent } from '../../../../agent/Agent' +import type { BaseAgent } from '../../../../agent/BaseAgent' import type { CredentialMetadata, CredentialExchangeRecord } from '../../../../modules/credentials' import type { JsonObject } from '../../../../types' @@ -16,7 +16,7 @@ import { DidCommMessageRepository, DidCommMessageRecord, DidCommMessageRole } fr * The following transformations are applied: * - {@link updateIndyMetadata} */ -export async function migrateCredentialRecordToV0_2(agent: Agent) { +export async function migrateCredentialRecordToV0_2(agent: Agent) { agent.config.logger.info('Migrating credential records to storage version 0.2') const credentialRepository = agent.dependencyManager.resolve(CredentialRepository) @@ -115,7 +115,10 @@ export function getCredentialRole(credentialRecord: CredentialExchangeRecord) { * } * ``` */ -export async function updateIndyMetadata(agent: Agent, credentialRecord: CredentialExchangeRecord) { +export async function updateIndyMetadata( + agent: Agent, + credentialRecord: CredentialExchangeRecord +) { agent.config.logger.debug(`Updating indy metadata to use the generic metadata api available to records.`) const { requestMetadata, schemaId, credentialDefinitionId, ...rest } = credentialRecord.metadata.data @@ -173,7 +176,7 @@ export async function updateIndyMetadata(agent: Agent, credentialRecord: Credent * } * ``` */ -export async function migrateInternalCredentialRecordProperties( +export async function migrateInternalCredentialRecordProperties( agent: Agent, credentialRecord: CredentialExchangeRecord ) { @@ -210,7 +213,10 @@ export async function migrateInternalCredentialRecordProperties( * This migration scripts extracts all message (proposalMessage, offerMessage, requestMessage, credentialMessage) and moves * them into the DidCommMessageRepository. */ -export async function moveDidCommMessages(agent: Agent, credentialRecord: CredentialExchangeRecord) { +export async function moveDidCommMessages( + agent: Agent, + credentialRecord: CredentialExchangeRecord +) { agent.config.logger.debug( `Moving didcomm messages from credential record with id ${credentialRecord.id} to DidCommMessageRecord` ) diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/index.ts b/packages/core/src/storage/migration/updates/0.1-0.2/index.ts index 200a5f6376..17065705f3 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/index.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/index.ts @@ -1,4 +1,4 @@ -import type { Agent } from '../../../../agent/Agent' +import type { BaseAgent } from '../../../../agent/BaseAgent' import type { UpdateConfig } from '../../updates' import { migrateConnectionRecordToV0_2 } from './connection' @@ -9,7 +9,7 @@ export interface V0_1ToV0_2UpdateConfig { mediationRoleUpdateStrategy: 'allMediator' | 'allRecipient' | 'recipientIfEndpoint' | 'doNotChange' } -export async function updateV0_1ToV0_2(agent: Agent, config: UpdateConfig): Promise { +export async function updateV0_1ToV0_2(agent: Agent, config: UpdateConfig): Promise { await migrateCredentialRecordToV0_2(agent) await migrateMediationRecordToV0_2(agent, config.v0_1ToV0_2) await migrateConnectionRecordToV0_2(agent) diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts b/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts index e6d3447a1c..7d41c3366d 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts @@ -1,4 +1,4 @@ -import type { Agent } from '../../../../agent/Agent' +import type { BaseAgent } from '../../../../agent/BaseAgent' import type { MediationRecord } from '../../../../modules/routing' import type { V0_1ToV0_2UpdateConfig } from './index' @@ -12,7 +12,10 @@ import { MediationRepository, MediationRole } from '../../../../modules/routing' * The following transformations are applied: * - {@link updateMediationRole} */ -export async function migrateMediationRecordToV0_2(agent: Agent, upgradeConfig: V0_1ToV0_2UpdateConfig) { +export async function migrateMediationRecordToV0_2( + agent: Agent, + upgradeConfig: V0_1ToV0_2UpdateConfig +) { agent.config.logger.info('Migrating mediation records to storage version 0.2') const mediationRepository = agent.dependencyManager.resolve(MediationRepository) @@ -50,7 +53,7 @@ export async function migrateMediationRecordToV0_2(agent: Agent, upgradeConfig: * Most agents only act as either the role of mediator or recipient, in which case the `allMediator` or `allRecipient` configuration is the most appropriate. If your agent acts as both a recipient and mediator, the `recipientIfEndpoint` configuration is the most appropriate. The `doNotChange` options is not recommended and can lead to errors if the role is not set correctly. * */ -export async function updateMediationRole( +export async function updateMediationRole( agent: Agent, mediationRecord: MediationRecord, { mediationRoleUpdateStrategy }: V0_1ToV0_2UpdateConfig From 7cbd08c9bb4b14ab2db92b0546d6fcb520f5fec9 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Mon, 11 Jul 2022 11:28:51 +0200 Subject: [PATCH 012/125] feat(tenants): initial tenants module (#932) Signed-off-by: Timo Glastra --- .eslintrc.js | 11 +- packages/core/src/agent/Agent.ts | 18 +- packages/core/src/agent/AgentConfig.ts | 6 +- packages/core/src/agent/BaseAgent.ts | 18 +- packages/core/src/agent/EventEmitter.ts | 11 +- packages/core/src/agent/Events.ts | 5 + .../src/agent/__tests__/EventEmitter.test.ts | 59 +++++ .../core/src/agent/context/AgentContext.ts | 6 + .../src/agent/context/AgentContextProvider.ts | 8 +- .../context/DefaultAgentContextProvider.ts | 22 +- .../DefaultAgentContextProvider.test.ts | 28 +++ .../src/agent/models/InboundMessageContext.ts | 10 + packages/core/src/index.ts | 7 +- .../handlers/DidExchangeRequestHandler.ts | 2 +- .../RevocationNotificationService.test.ts | 6 + .../__tests__/V1CredentialServiceCred.test.ts | 9 + .../V1CredentialServiceProposeOffer.test.ts | 9 + .../__tests__/V2CredentialServiceCred.test.ts | 9 + .../V2CredentialServiceOffer.test.ts | 6 + .../proofs/__tests__/ProofService.test.ts | 3 + .../__tests__/QuestionAnswerService.test.ts | 6 + .../services/__tests__/RoutingService.test.ts | 3 + .../src/storage/__tests__/Repository.test.ts | 9 + .../storage/migration/__tests__/0.1.test.ts | 29 ++- .../__tests__/UpdateAssistant.test.ts | 11 +- packages/core/src/utils/JWE.ts | 10 +- packages/core/src/utils/__tests__/JWE.test.ts | 4 +- packages/core/src/wallet/IndyWallet.ts | 9 + packages/core/src/wallet/Wallet.ts | 1 + packages/core/tests/mocks/MockWallet.ts | 4 + packages/core/tests/oob.test.ts | 9 + ...{postgres.test.ts => postgres.e2e.test.ts} | 0 packages/module-tenants/README.md | 31 +++ packages/module-tenants/jest.config.ts | 14 ++ packages/module-tenants/package.json | 36 ++++ packages/module-tenants/src/TenantAgent.ts | 23 ++ packages/module-tenants/src/TenantsApi.ts | 56 +++++ .../module-tenants/src/TenantsApiOptions.ts | 9 + packages/module-tenants/src/TenantsModule.ts | 31 +++ .../src/__tests__/TenantAgent.test.ts | 30 +++ .../src/__tests__/TenantsApi.test.ts | 127 +++++++++++ .../src/__tests__/TenantsModule.test.ts | 31 +++ .../src/context/TenantAgentContextProvider.ts | 144 +++++++++++++ .../src/context/TenantSessionCoordinator.ts | 117 ++++++++++ .../TenantAgentContextProvider.test.ts | 151 +++++++++++++ .../TenantSessionCoordinator.test.ts | 126 +++++++++++ packages/module-tenants/src/context/types.ts | 0 packages/module-tenants/src/index.ts | 4 + .../module-tenants/src/models/TenantConfig.ts | 5 + .../src/repository/TenantRecord.ts | 37 ++++ .../src/repository/TenantRepository.ts | 13 ++ .../src/repository/TenantRoutingRecord.ts | 47 ++++ .../src/repository/TenantRoutingRepository.ts | 21 ++ .../repository/__tests__/TenantRecord.test.ts | 87 ++++++++ .../__tests__/TenantRoutingRecord.test.ts | 77 +++++++ .../__tests__/TenantRoutingRepository.test.ts | 38 ++++ .../module-tenants/src/repository/index.ts | 4 + .../src/services/TenantService.ts | 83 ++++++++ .../services/__tests__/TenantService.test.ts | 151 +++++++++++++ packages/module-tenants/src/services/index.ts | 1 + packages/module-tenants/tests/setup.ts | 1 + .../module-tenants/tests/tenants.e2e.test.ts | 201 ++++++++++++++++++ packages/module-tenants/tsconfig.build.json | 9 + packages/module-tenants/tsconfig.json | 6 + tests/transport/SubjectInboundTransport.ts | 8 +- yarn.lock | 9 +- 66 files changed, 2012 insertions(+), 64 deletions(-) create mode 100644 packages/core/src/agent/__tests__/EventEmitter.test.ts rename packages/core/tests/{postgres.test.ts => postgres.e2e.test.ts} (100%) create mode 100644 packages/module-tenants/README.md create mode 100644 packages/module-tenants/jest.config.ts create mode 100644 packages/module-tenants/package.json create mode 100644 packages/module-tenants/src/TenantAgent.ts create mode 100644 packages/module-tenants/src/TenantsApi.ts create mode 100644 packages/module-tenants/src/TenantsApiOptions.ts create mode 100644 packages/module-tenants/src/TenantsModule.ts create mode 100644 packages/module-tenants/src/__tests__/TenantAgent.test.ts create mode 100644 packages/module-tenants/src/__tests__/TenantsApi.test.ts create mode 100644 packages/module-tenants/src/__tests__/TenantsModule.test.ts create mode 100644 packages/module-tenants/src/context/TenantAgentContextProvider.ts create mode 100644 packages/module-tenants/src/context/TenantSessionCoordinator.ts create mode 100644 packages/module-tenants/src/context/__tests__/TenantAgentContextProvider.test.ts create mode 100644 packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts create mode 100644 packages/module-tenants/src/context/types.ts create mode 100644 packages/module-tenants/src/index.ts create mode 100644 packages/module-tenants/src/models/TenantConfig.ts create mode 100644 packages/module-tenants/src/repository/TenantRecord.ts create mode 100644 packages/module-tenants/src/repository/TenantRepository.ts create mode 100644 packages/module-tenants/src/repository/TenantRoutingRecord.ts create mode 100644 packages/module-tenants/src/repository/TenantRoutingRepository.ts create mode 100644 packages/module-tenants/src/repository/__tests__/TenantRecord.test.ts create mode 100644 packages/module-tenants/src/repository/__tests__/TenantRoutingRecord.test.ts create mode 100644 packages/module-tenants/src/repository/__tests__/TenantRoutingRepository.test.ts create mode 100644 packages/module-tenants/src/repository/index.ts create mode 100644 packages/module-tenants/src/services/TenantService.ts create mode 100644 packages/module-tenants/src/services/__tests__/TenantService.test.ts create mode 100644 packages/module-tenants/src/services/index.ts create mode 100644 packages/module-tenants/tests/setup.ts create mode 100644 packages/module-tenants/tests/tenants.e2e.test.ts create mode 100644 packages/module-tenants/tsconfig.build.json create mode 100644 packages/module-tenants/tsconfig.json diff --git a/.eslintrc.js b/.eslintrc.js index 75c94ae5c6..e45d4d2cad 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -99,7 +99,16 @@ module.exports = { }, }, { - files: ['*.test.ts', '**/__tests__/**', '**/tests/**', 'jest.*.ts', 'samples/**', 'demo/**', 'scripts/**'], + files: [ + '*.test.ts', + '**/__tests__/**', + '**/tests/**', + 'jest.*.ts', + 'samples/**', + 'demo/**', + 'scripts/**', + '**/tests/**', + ], env: { jest: true, node: false, diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 2d10d61b05..b3d4a5b05e 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -1,15 +1,12 @@ -import type { DependencyManager } from '../plugins' import type { InboundTransport } from '../transport/InboundTransport' import type { OutboundTransport } from '../transport/OutboundTransport' import type { InitConfig } from '../types' import type { AgentDependencies } from './AgentDependencies' import type { AgentMessageReceivedEvent } from './Events' import type { Subscription } from 'rxjs' -import type { DependencyContainer } from 'tsyringe' import { Subject } from 'rxjs' import { concatMap, takeUntil } from 'rxjs/operators' -import { container as baseContainer } from 'tsyringe' import { CacheRepository } from '../cache' import { InjectionSymbols } from '../constants' @@ -29,6 +26,7 @@ import { QuestionAnswerModule } from '../modules/question-answer/QuestionAnswerM import { MediatorModule } from '../modules/routing/MediatorModule' import { RecipientModule } from '../modules/routing/RecipientModule' import { W3cVcModule } from '../modules/vc/module' +import { DependencyManager } from '../plugins' import { DidCommMessageRepository, StorageUpdateService, StorageVersionRepository } from '../storage' import { InMemoryMessageRepository } from '../storage/InMemoryMessageRepository' import { IndyStorageService } from '../storage/IndyStorageService' @@ -52,11 +50,11 @@ export class Agent extends BaseAgent { public constructor( initialConfig: InitConfig, dependencies: AgentDependencies, - injectionContainer?: DependencyContainer + dependencyManager?: DependencyManager ) { // NOTE: we can't create variables before calling super as TS will complain that the super call must be the // the first statement in the constructor. - super(new AgentConfig(initialConfig, dependencies), injectionContainer ?? baseContainer.createChildContainer()) + super(new AgentConfig(initialConfig, dependencies), dependencyManager ?? new DependencyManager()) const stop$ = this.dependencyManager.resolve>(InjectionSymbols.Stop$) @@ -95,10 +93,6 @@ export class Agent extends BaseAgent { return this.eventEmitter } - public get isInitialized() { - return this._isInitialized && this.wallet.isInitialized - } - public async initialize() { const { connectToIndyLedgersOnStartup, mediatorConnectionsInvite } = this.agentConfig @@ -146,7 +140,13 @@ export class Agent extends BaseAgent { const transportPromises = allTransports.map((transport) => transport.stop()) await Promise.all(transportPromises) + // close wallet if still initialized + if (this.wallet.isInitialized) { + await this.wallet.close() + } + await super.shutdown() + this._isInitialized = false } protected registerDependencies(dependencyManager: DependencyManager) { diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index e43b17c183..df12417754 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -120,14 +120,12 @@ export class AgentConfig { ) } - public toString() { - const config = { + public toJSON() { + return { ...this.initConfig, logger: this.logger !== undefined, agentDependencies: this.agentDependencies != undefined, label: this.label, } - - return JSON.stringify(config, null, 2) } } diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index eea807e245..67c32965b5 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -1,7 +1,7 @@ import type { Logger } from '../logger' +import type { DependencyManager } from '../plugins' import type { AgentConfig } from './AgentConfig' import type { TransportSession } from './TransportService' -import type { DependencyContainer } from 'tsyringe' import { AriesFrameworkError } from '../error' import { BasicMessagesModule } from '../modules/basic-messages/BasicMessagesModule' @@ -16,7 +16,6 @@ import { ProofsModule } from '../modules/proofs/ProofsModule' import { QuestionAnswerModule } from '../modules/question-answer/QuestionAnswerModule' import { MediatorModule } from '../modules/routing/MediatorModule' import { RecipientModule } from '../modules/routing/RecipientModule' -import { DependencyManager } from '../plugins' import { StorageUpdateService } from '../storage' import { UpdateAssistant } from '../storage/migration/UpdateAssistant' import { DEFAULT_UPDATE_CONFIG } from '../storage/migration/updates' @@ -54,17 +53,14 @@ export abstract class BaseAgent { public readonly wallet: WalletModule public readonly oob: OutOfBandModule - public constructor(agentConfig: AgentConfig, container: DependencyContainer) { - this.dependencyManager = new DependencyManager(container) + public constructor(agentConfig: AgentConfig, dependencyManager: DependencyManager) { + this.dependencyManager = dependencyManager this.agentConfig = agentConfig this.logger = this.agentConfig.logger this.logger.info('Creating agent with config', { - ...agentConfig, - // Prevent large object being logged. - // Will display true/false to indicate if value is present in config - logger: agentConfig.logger != undefined, + agentConfig: agentConfig.toJSON(), }) if (!this.agentConfig.walletConfig) { @@ -153,11 +149,7 @@ export abstract class BaseAgent { } public async shutdown() { - // close wallet if still initialized - if (this.wallet.isInitialized) { - await this.wallet.close() - } - this._isInitialized = false + // No logic required at the moment } public get publicDid() { diff --git a/packages/core/src/agent/EventEmitter.ts b/packages/core/src/agent/EventEmitter.ts index 3dfe6205b3..3d30e5fa32 100644 --- a/packages/core/src/agent/EventEmitter.ts +++ b/packages/core/src/agent/EventEmitter.ts @@ -10,6 +10,8 @@ import { injectable, inject } from '../plugins' import { AgentDependencies } from './AgentDependencies' +type EmitEvent = Omit + @injectable() export class EventEmitter { private eventEmitter: NativeEventEmitter @@ -24,8 +26,13 @@ export class EventEmitter { } // agentContext is currently not used, but already making required as it will be used soon - public emit(agentContext: AgentContext, data: T) { - this.eventEmitter.emit(data.type, data) + public emit(agentContext: AgentContext, data: EmitEvent) { + this.eventEmitter.emit(data.type, { + ...data, + metadata: { + contextCorrelationId: agentContext.contextCorrelationId, + }, + }) } public on(event: T['type'], listener: (data: T) => void | Promise) { diff --git a/packages/core/src/agent/Events.ts b/packages/core/src/agent/Events.ts index 9c34620ca4..ad4faf8ed2 100644 --- a/packages/core/src/agent/Events.ts +++ b/packages/core/src/agent/Events.ts @@ -6,9 +6,14 @@ export enum AgentEventTypes { AgentMessageProcessed = 'AgentMessageProcessed', } +export interface EventMetadata { + contextCorrelationId: string +} + export interface BaseEvent { type: string payload: Record + metadata: EventMetadata } export interface AgentMessageReceivedEvent extends BaseEvent { diff --git a/packages/core/src/agent/__tests__/EventEmitter.test.ts b/packages/core/src/agent/__tests__/EventEmitter.test.ts new file mode 100644 index 0000000000..480ccbedbb --- /dev/null +++ b/packages/core/src/agent/__tests__/EventEmitter.test.ts @@ -0,0 +1,59 @@ +import type { EventEmitter as NativeEventEmitter } from 'events' + +import { Subject } from 'rxjs' + +import { agentDependencies, getAgentContext } from '../../../tests/helpers' +import { EventEmitter } from '../EventEmitter' + +const mockEmit = jest.fn() +const mockOn = jest.fn() +const mockOff = jest.fn() +const mock = jest.fn().mockImplementation(() => { + return { emit: mockEmit, on: mockOn, off: mockOff } +}) as jest.Mock + +const eventEmitter = new EventEmitter( + { ...agentDependencies, EventEmitterClass: mock as unknown as typeof NativeEventEmitter }, + new Subject() +) +const agentContext = getAgentContext({}) + +describe('EventEmitter', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + describe('emit', () => { + test("calls 'emit' on native event emitter instance", () => { + eventEmitter.emit(agentContext, { + payload: { some: 'payload' }, + type: 'some-event', + }) + + expect(mockEmit).toHaveBeenCalledWith('some-event', { + payload: { some: 'payload' }, + type: 'some-event', + metadata: { + contextCorrelationId: agentContext.contextCorrelationId, + }, + }) + }) + }) + + describe('on', () => { + test("calls 'on' on native event emitter instance", () => { + const listener = jest.fn() + eventEmitter.on('some-event', listener) + + expect(mockOn).toHaveBeenCalledWith('some-event', listener) + }) + }) + describe('off', () => { + test("calls 'off' on native event emitter instance", () => { + const listener = jest.fn() + eventEmitter.off('some-event', listener) + + expect(mockOff).toHaveBeenCalledWith('some-event', listener) + }) + }) +}) diff --git a/packages/core/src/agent/context/AgentContext.ts b/packages/core/src/agent/context/AgentContext.ts index ef425377b2..9c3e000c1b 100644 --- a/packages/core/src/agent/context/AgentContext.ts +++ b/packages/core/src/agent/context/AgentContext.ts @@ -46,4 +46,10 @@ export class AgentContext { public get wallet() { return this.dependencyManager.resolve(InjectionSymbols.Wallet) } + + public toJSON() { + return { + contextCorrelationId: this.contextCorrelationId, + } + } } diff --git a/packages/core/src/agent/context/AgentContextProvider.ts b/packages/core/src/agent/context/AgentContextProvider.ts index 09047d38b6..c9f1c81296 100644 --- a/packages/core/src/agent/context/AgentContextProvider.ts +++ b/packages/core/src/agent/context/AgentContextProvider.ts @@ -2,7 +2,7 @@ import type { AgentContext } from './AgentContext' export interface AgentContextProvider { /** - * Find the agent context based for an inbound message. It's possible to provide a contextCorrelationId to make it + * Get the agent context for an inbound message. It's possible to provide a contextCorrelationId to make it * easier for the context provider implementation to correlate inbound messages to the correct context. This can be useful if * a plaintext message is passed and the context provider can't determine the context based on the recipient public keys * of the inbound message. @@ -14,4 +14,10 @@ export interface AgentContextProvider { inboundMessage: unknown, options?: { contextCorrelationId?: string } ): Promise + + /** + * Get the agent context for a context correlation id. Will throw an error if no AgentContext could be retrieved + * for the specified contextCorrelationId. + */ + getAgentContextForContextCorrelationId(contextCorrelationId: string): Promise } diff --git a/packages/core/src/agent/context/DefaultAgentContextProvider.ts b/packages/core/src/agent/context/DefaultAgentContextProvider.ts index 3227dadc55..87df3fb03d 100644 --- a/packages/core/src/agent/context/DefaultAgentContextProvider.ts +++ b/packages/core/src/agent/context/DefaultAgentContextProvider.ts @@ -1,5 +1,6 @@ import type { AgentContextProvider } from './AgentContextProvider' +import { AriesFrameworkError } from '../../error' import { injectable } from '../../plugins' import { AgentContext } from './AgentContext' @@ -18,7 +19,26 @@ export class DefaultAgentContextProvider implements AgentContextProvider { this.agentContext = agentContext } - public async getContextForInboundMessage(): Promise { + public async getAgentContextForContextCorrelationId(contextCorrelationId: string): Promise { + if (contextCorrelationId !== this.agentContext.contextCorrelationId) { + throw new AriesFrameworkError( + `Could not get agent context for contextCorrelationId '${contextCorrelationId}'. Only contextCorrelationId '${this.agentContext.contextCorrelationId}' is supported.` + ) + } + + return this.agentContext + } + + public async getContextForInboundMessage( + // We don't need to look at the message as we always use the same context in the default agent context provider + _: unknown, + options?: { contextCorrelationId?: string } + ): Promise { + // This will throw an error if the contextCorrelationId does not match with the contextCorrelationId of the agent context property of this class. + if (options?.contextCorrelationId) { + return this.getAgentContextForContextCorrelationId(options.contextCorrelationId) + } + return this.agentContext } } diff --git a/packages/core/src/agent/context/__tests__/DefaultAgentContextProvider.test.ts b/packages/core/src/agent/context/__tests__/DefaultAgentContextProvider.test.ts index f7faa58c78..510848d00f 100644 --- a/packages/core/src/agent/context/__tests__/DefaultAgentContextProvider.test.ts +++ b/packages/core/src/agent/context/__tests__/DefaultAgentContextProvider.test.ts @@ -14,5 +14,33 @@ describe('DefaultAgentContextProvider', () => { await expect(agentContextProvider.getContextForInboundMessage(message)).resolves.toBe(agentContext) }) + + test('throws an error if the provided contextCorrelationId does not match with the contextCorrelationId from the constructor agent context', async () => { + const agentContextProvider: AgentContextProvider = new DefaultAgentContextProvider(agentContext) + + const message = {} + + await expect( + agentContextProvider.getContextForInboundMessage(message, { contextCorrelationId: 'wrong' }) + ).rejects.toThrowError( + `Could not get agent context for contextCorrelationId 'wrong'. Only contextCorrelationId 'mock' is supported.` + ) + }) + }) + + describe('getAgentContextForContextCorrelationId()', () => { + test('returns the agent context provided in the constructor if contextCorrelationId matches', async () => { + const agentContextProvider: AgentContextProvider = new DefaultAgentContextProvider(agentContext) + + await expect(agentContextProvider.getAgentContextForContextCorrelationId('mock')).resolves.toBe(agentContext) + }) + + test('throws an error if the contextCorrelationId does not match with the contextCorrelationId from the constructor agent context', async () => { + const agentContextProvider: AgentContextProvider = new DefaultAgentContextProvider(agentContext) + + await expect(agentContextProvider.getAgentContextForContextCorrelationId('wrong')).rejects.toThrowError( + `Could not get agent context for contextCorrelationId 'wrong'. Only contextCorrelationId 'mock' is supported.` + ) + }) }) }) diff --git a/packages/core/src/agent/models/InboundMessageContext.ts b/packages/core/src/agent/models/InboundMessageContext.ts index c3f3628e09..8a6e800160 100644 --- a/packages/core/src/agent/models/InboundMessageContext.ts +++ b/packages/core/src/agent/models/InboundMessageContext.ts @@ -45,4 +45,14 @@ export class InboundMessageContext { return this.connection } + + public toJSON() { + return { + message: this.message, + recipientKey: this.recipientKey?.fingerprint, + senderKey: this.senderKey?.fingerprint, + sessionId: this.sessionId, + agentContext: this.agentContext.toJSON(), + } + } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ac2359d908..234fc8b9da 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,9 +1,10 @@ // reflect-metadata used for class-transformer + class-validator import 'reflect-metadata' -export { AgentContext } from './agent' export { MessageReceiver } from './agent/MessageReceiver' export { Agent } from './agent/Agent' +export { BaseAgent } from './agent/BaseAgent' +export * from './agent' export { EventEmitter } from './agent/EventEmitter' export { Handler, HandlerInboundMessage } from './agent/Handler' export { InboundMessageContext } from './agent/models/InboundMessageContext' @@ -41,11 +42,13 @@ export * from './modules/ledger' export * from './modules/routing' export * from './modules/question-answer' export * from './modules/oob' +export * from './wallet/WalletModule' export * from './modules/dids' -export * from './utils/JsonTransformer' +export { JsonEncoder, JsonTransformer, isJsonObject, isValidJweStructure } from './utils' export * from './logger' export * from './error' export * from './wallet/error' +export { Key, KeyType } from './crypto' export { parseMessageType, IsValidMessageType } from './utils/messageType' export * from './agent/Events' diff --git a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts index 3c18a8dc84..20fa4437ec 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts @@ -68,7 +68,7 @@ export class DidExchangeRequestHandler implements Handler { const connectionRecord = await this.didExchangeProtocol.processRequest(messageContext, outOfBandRecord) - if (connectionRecord?.autoAcceptConnection ?? messageContext.agentContext.config.autoAcceptConnections) { + if (connectionRecord.autoAcceptConnection ?? messageContext.agentContext.config.autoAcceptConnections) { // TODO We should add an option to not pass routing and therefore do not rotate keys and use the keys from the invitation // TODO: Allow rotation of keys used in the invitation for new ones not only when out-of-band is reusable const routing = outOfBandRecord.reusable diff --git a/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts b/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts index 9222b3fdcf..c0b3e57904 100644 --- a/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts +++ b/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts @@ -103,6 +103,9 @@ describe('RevocationNotificationService', () => { expect(eventListenerMock).toHaveBeenCalledWith({ type: 'RevocationNotificationReceived', + metadata: { + contextCorrelationId: 'mock', + }, payload: { credentialRecord: expect.any(CredentialExchangeRecord), }, @@ -208,6 +211,9 @@ describe('RevocationNotificationService', () => { expect(eventListenerMock).toHaveBeenCalledWith({ type: 'RevocationNotificationReceived', + metadata: { + contextCorrelationId: 'mock', + }, payload: { credentialRecord: expect.any(CredentialExchangeRecord), }, diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts index 7f2760a501..343751b4d5 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts @@ -482,6 +482,9 @@ describe('V1CredentialService', () => { // then expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: CredentialState.RequestReceived, credentialRecord: expect.objectContaining({ @@ -608,6 +611,9 @@ describe('V1CredentialService', () => { // then expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: CredentialState.CredentialReceived, credentialRecord: expect.objectContaining({ @@ -934,6 +940,9 @@ describe('V1CredentialService', () => { const [[event]] = eventListenerMock.mock.calls expect(event).toMatchObject({ type: 'CredentialStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: CredentialState.OfferReceived, credentialRecord: expect.objectContaining({ diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts index 7ebe9467aa..3fd459ece5 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts @@ -182,6 +182,9 @@ describe('V1CredentialServiceProposeOffer', () => { expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: null, credentialRecord: expect.objectContaining({ @@ -289,6 +292,9 @@ describe('V1CredentialServiceProposeOffer', () => { expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: null, credentialRecord: expect.objectContaining({ @@ -385,6 +391,9 @@ describe('V1CredentialServiceProposeOffer', () => { // then expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: null, credentialRecord: expect.objectContaining({ diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts index 2168342054..619658db7d 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts @@ -484,6 +484,9 @@ describe('CredentialService', () => { // then expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: CredentialState.RequestReceived, credentialRecord: expect.objectContaining({ @@ -590,6 +593,9 @@ describe('CredentialService', () => { // then expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: CredentialState.CredentialReceived, credentialRecord: expect.objectContaining({ @@ -887,6 +893,9 @@ describe('CredentialService', () => { const [[event]] = eventListenerMock.mock.calls expect(event).toMatchObject({ type: 'CredentialStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: CredentialState.OfferReceived, credentialRecord: expect.objectContaining({ diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts index 89670711fe..4bb5ba769d 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts @@ -161,6 +161,9 @@ describe('V2CredentialServiceOffer', () => { expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: null, credentialRecord: expect.objectContaining({ @@ -248,6 +251,9 @@ describe('V2CredentialServiceOffer', () => { // then expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: null, credentialRecord: expect.objectContaining({ diff --git a/packages/core/src/modules/proofs/__tests__/ProofService.test.ts b/packages/core/src/modules/proofs/__tests__/ProofService.test.ts index 554856172e..b64ded9b1c 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofService.test.ts +++ b/packages/core/src/modules/proofs/__tests__/ProofService.test.ts @@ -175,6 +175,9 @@ describe('ProofService', () => { // then expect(eventListenerMock).toHaveBeenCalledWith({ type: 'ProofStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: null, proofRecord: expect.objectContaining({ diff --git a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts index c940c1e30c..1820debc06 100644 --- a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts +++ b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts @@ -99,6 +99,9 @@ describe('QuestionAnswerService', () => { expect(eventListenerMock).toHaveBeenCalledWith({ type: 'QuestionAnswerStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: null, questionAnswerRecord: expect.objectContaining({ @@ -146,6 +149,9 @@ describe('QuestionAnswerService', () => { expect(eventListenerMock).toHaveBeenCalledWith({ type: 'QuestionAnswerStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, payload: { previousState: QuestionAnswerState.QuestionReceived, questionAnswerRecord: expect.objectContaining({ diff --git a/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts b/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts index c1360ed1df..504da2f0b2 100644 --- a/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts @@ -66,6 +66,9 @@ describe('RoutingService', () => { expect(routing).toEqual(routing) expect(routingListener).toHaveBeenCalledWith({ type: RoutingEventTypes.RoutingCreatedEvent, + metadata: { + contextCorrelationId: 'mock', + }, payload: { routing, }, diff --git a/packages/core/src/storage/__tests__/Repository.test.ts b/packages/core/src/storage/__tests__/Repository.test.ts index 27faf1346a..11cb3dd9cc 100644 --- a/packages/core/src/storage/__tests__/Repository.test.ts +++ b/packages/core/src/storage/__tests__/Repository.test.ts @@ -61,6 +61,9 @@ describe('Repository', () => { // then expect(eventListenerMock).toHaveBeenCalledWith({ type: 'RecordSaved', + metadata: { + contextCorrelationId: 'mock', + }, payload: { record: expect.objectContaining({ id: 'test-id', @@ -91,6 +94,9 @@ describe('Repository', () => { // then expect(eventListenerMock).toHaveBeenCalledWith({ type: 'RecordUpdated', + metadata: { + contextCorrelationId: 'mock', + }, payload: { record: expect.objectContaining({ id: 'test-id', @@ -121,6 +127,9 @@ describe('Repository', () => { // then expect(eventListenerMock).toHaveBeenCalledWith({ type: 'RecordDeleted', + metadata: { + contextCorrelationId: 'mock', + }, payload: { record: expect.objectContaining({ id: 'test-id', diff --git a/packages/core/src/storage/migration/__tests__/0.1.test.ts b/packages/core/src/storage/migration/__tests__/0.1.test.ts index dffe2b5cf6..ecfca2ed69 100644 --- a/packages/core/src/storage/migration/__tests__/0.1.test.ts +++ b/packages/core/src/storage/migration/__tests__/0.1.test.ts @@ -4,12 +4,12 @@ import type { V0_1ToV0_2UpdateConfig } from '../updates/0.1-0.2' import { unlinkSync, readFileSync } from 'fs' import path from 'path' -import { container as baseContainer } from 'tsyringe' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' import { Agent } from '../../../../src' import { agentDependencies } from '../../../../tests/helpers' import { InjectionSymbols } from '../../../constants' +import { DependencyManager } from '../../../plugins' import * as uuid from '../../../utils/uuid' import { IndyWallet } from '../../../wallet/IndyWallet' import { UpdateAssistant } from '../UpdateAssistant' @@ -51,9 +51,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { ) for (const mediationRoleUpdateStrategy of mediationRoleUpdateStrategies) { - const container = baseContainer.createChildContainer() + const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() - container.registerInstance(InjectionSymbols.StorageService, storageService) + dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) const agent = new Agent( { @@ -61,7 +61,7 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { walletConfig, }, agentDependencies, - container + dependencyManager ) const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) @@ -111,10 +111,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { 'utf8' ) - const container = baseContainer.createChildContainer() + const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() - - container.registerInstance(InjectionSymbols.StorageService, storageService) + dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) const agent = new Agent( { @@ -122,7 +121,7 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { walletConfig, }, agentDependencies, - container + dependencyManager ) const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) @@ -174,10 +173,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { 'utf8' ) - const container = baseContainer.createChildContainer() + const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() - - container.registerInstance(InjectionSymbols.StorageService, storageService) + dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) const agent = new Agent( { @@ -186,7 +184,7 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { autoUpdateStorageOnStartup: true, }, agentDependencies, - container + dependencyManager ) const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) @@ -225,10 +223,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { 'utf8' ) - const container = baseContainer.createChildContainer() + const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() - - container.registerInstance(InjectionSymbols.StorageService, storageService) + dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) const agent = new Agent( { @@ -237,7 +234,7 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { autoUpdateStorageOnStartup: true, }, agentDependencies, - container + dependencyManager ) const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) diff --git a/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts b/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts index 316d78d648..4cc59315e2 100644 --- a/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts +++ b/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts @@ -1,12 +1,10 @@ import type { BaseRecord } from '../../BaseRecord' -import type { DependencyContainer } from 'tsyringe' - -import { container as baseContainer } from 'tsyringe' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' import { getBaseConfig } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { InjectionSymbols } from '../../../constants' +import { DependencyManager } from '../../../plugins' import { UpdateAssistant } from '../UpdateAssistant' const { agentDependencies, config } = getBaseConfig('UpdateAssistant') @@ -14,15 +12,14 @@ const { agentDependencies, config } = getBaseConfig('UpdateAssistant') describe('UpdateAssistant', () => { let updateAssistant: UpdateAssistant let agent: Agent - let container: DependencyContainer let storageService: InMemoryStorageService beforeEach(async () => { - container = baseContainer.createChildContainer() + const dependencyManager = new DependencyManager() storageService = new InMemoryStorageService() - container.registerInstance(InjectionSymbols.StorageService, storageService) + dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) - agent = new Agent(config, agentDependencies, container) + agent = new Agent(config, agentDependencies, dependencyManager) updateAssistant = new UpdateAssistant(agent, { v0_1ToV0_2: { diff --git a/packages/core/src/utils/JWE.ts b/packages/core/src/utils/JWE.ts index d8d7909b65..f0c7c6049f 100644 --- a/packages/core/src/utils/JWE.ts +++ b/packages/core/src/utils/JWE.ts @@ -2,5 +2,13 @@ import type { EncryptedMessage } from '../types' // eslint-disable-next-line @typescript-eslint/no-explicit-any export function isValidJweStructure(message: any): message is EncryptedMessage { - return message && typeof message === 'object' && message.protected && message.iv && message.ciphertext && message.tag + return Boolean( + message && + typeof message === 'object' && + message !== null && + typeof message.protected === 'string' && + message.iv && + message.ciphertext && + message.tag + ) } diff --git a/packages/core/src/utils/__tests__/JWE.test.ts b/packages/core/src/utils/__tests__/JWE.test.ts index 80d1af2ae8..02ed3b5a55 100644 --- a/packages/core/src/utils/__tests__/JWE.test.ts +++ b/packages/core/src/utils/__tests__/JWE.test.ts @@ -3,7 +3,7 @@ import { isValidJweStructure } from '../JWE' describe('ValidJWEStructure', () => { test('throws error when the response message has an invalid JWE structure', async () => { const responseMessage = 'invalid JWE structure' - await expect(isValidJweStructure(responseMessage)).toBeFalsy() + expect(isValidJweStructure(responseMessage)).toBe(false) }) test('valid JWE structure', async () => { @@ -14,6 +14,6 @@ describe('ValidJWEStructure', () => { ciphertext: 'mwRMpVg9wkF4rIZcBeWLcc0fWhs=', tag: '0yW0Lx8-vWevj3if91R06g==', } - await expect(isValidJweStructure(responseMessage)).toBeTruthy() + expect(isValidJweStructure(responseMessage)).toBe(true) }) }) diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts index e99143dc7b..92870f7d70 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/core/src/wallet/IndyWallet.ts @@ -344,6 +344,7 @@ export class IndyWallet implements Wallet { * @throws {WalletError} if the wallet is already closed or another error occurs */ public async close(): Promise { + this.logger.debug(`Closing wallet ${this.walletConfig?.id}`) if (!this.walletHandle) { throw new WalletError('Wallet is in invalid state, you are trying to close wallet that has no `walletHandle`.') } @@ -634,4 +635,12 @@ export class IndyWallet implements Wallet { throw isIndyError(error) ? new IndySdkError(error) : error } } + + public async generateWalletKey() { + try { + return await this.indy.generateWalletKey() + } catch (error) { + throw new WalletError('Error generating wallet key', { cause: error }) + } + } } diff --git a/packages/core/src/wallet/Wallet.ts b/packages/core/src/wallet/Wallet.ts index 06a8899c4c..102c25e213 100644 --- a/packages/core/src/wallet/Wallet.ts +++ b/packages/core/src/wallet/Wallet.ts @@ -31,6 +31,7 @@ export interface Wallet { pack(payload: Record, recipientKeys: string[], senderVerkey?: string): Promise unpack(encryptedMessage: EncryptedMessage): Promise generateNonce(): Promise + generateWalletKey(): Promise } export interface DidInfo { diff --git a/packages/core/tests/mocks/MockWallet.ts b/packages/core/tests/mocks/MockWallet.ts index 83132e1303..864454edb6 100644 --- a/packages/core/tests/mocks/MockWallet.ts +++ b/packages/core/tests/mocks/MockWallet.ts @@ -71,4 +71,8 @@ export class MockWallet implements Wallet { public generateNonce(): Promise { throw new Error('Method not implemented.') } + + public generateWalletKey(): Promise { + throw new Error('Method not implemented.') + } } diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index dc56d8ad59..416522376a 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -244,6 +244,9 @@ describe('out of band', () => { expect(eventListener).toHaveBeenCalledWith({ type: OutOfBandEventTypes.OutOfBandStateChanged, + metadata: { + contextCorrelationId: 'default', + }, payload: { outOfBandRecord, previousState: null, @@ -607,6 +610,9 @@ describe('out of band', () => { // Receiving the invitation expect(eventListener).toHaveBeenNthCalledWith(1, { type: OutOfBandEventTypes.OutOfBandStateChanged, + metadata: { + contextCorrelationId: 'default', + }, payload: { outOfBandRecord: expect.objectContaining({ state: OutOfBandState.Initial }), previousState: null, @@ -616,6 +622,9 @@ describe('out of band', () => { // Accepting the invitation expect(eventListener).toHaveBeenNthCalledWith(2, { type: OutOfBandEventTypes.OutOfBandStateChanged, + metadata: { + contextCorrelationId: 'default', + }, payload: { outOfBandRecord, previousState: OutOfBandState.Initial, diff --git a/packages/core/tests/postgres.test.ts b/packages/core/tests/postgres.e2e.test.ts similarity index 100% rename from packages/core/tests/postgres.test.ts rename to packages/core/tests/postgres.e2e.test.ts diff --git a/packages/module-tenants/README.md b/packages/module-tenants/README.md new file mode 100644 index 0000000000..e0ec4b3ad4 --- /dev/null +++ b/packages/module-tenants/README.md @@ -0,0 +1,31 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript - Tenant Module

+

+ License + typescript + @aries-framework/module-tenants version + +

+
+ +Aries Framework JavaScript Tenant Module provides an optional addon to Aries Framework JavaScript to use an agent with multiple tenants. diff --git a/packages/module-tenants/jest.config.ts b/packages/module-tenants/jest.config.ts new file mode 100644 index 0000000000..55c67d70a6 --- /dev/null +++ b/packages/module-tenants/jest.config.ts @@ -0,0 +1,14 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + name: packageJson.name, + displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/packages/module-tenants/package.json b/packages/module-tenants/package.json new file mode 100644 index 0000000000..dae63f0858 --- /dev/null +++ b/packages/module-tenants/package.json @@ -0,0 +1,36 @@ +{ + "name": "@aries-framework/module-tenants", + "main": "build/index", + "types": "build/index", + "version": "0.2.0", + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/module-tenants", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/module-tenants" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf -rf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@aries-framework/core": "0.2.0", + "async-mutex": "^0.3.2" + }, + "devDependencies": { + "@aries-framework/node": "0.2.0", + "reflect-metadata": "^0.1.13", + "rimraf": "~3.0.2", + "typescript": "~4.3.0" + } +} diff --git a/packages/module-tenants/src/TenantAgent.ts b/packages/module-tenants/src/TenantAgent.ts new file mode 100644 index 0000000000..fbeb5f9723 --- /dev/null +++ b/packages/module-tenants/src/TenantAgent.ts @@ -0,0 +1,23 @@ +import type { AgentContext } from '@aries-framework/core' + +import { BaseAgent } from '@aries-framework/core' + +export class TenantAgent extends BaseAgent { + public constructor(agentContext: AgentContext) { + super(agentContext.config, agentContext.dependencyManager) + } + + public async initialize() { + await super.initialize() + this._isInitialized = true + } + + public async shutdown() { + await super.shutdown() + this._isInitialized = false + } + + protected registerDependencies() { + // Nothing to do here + } +} diff --git a/packages/module-tenants/src/TenantsApi.ts b/packages/module-tenants/src/TenantsApi.ts new file mode 100644 index 0000000000..526f7c66d9 --- /dev/null +++ b/packages/module-tenants/src/TenantsApi.ts @@ -0,0 +1,56 @@ +import type { CreateTenantOptions, GetTenantAgentOptions } from './TenantsApiOptions' + +import { AgentContext, inject, InjectionSymbols, AgentContextProvider, injectable } from '@aries-framework/core' + +import { TenantAgent } from './TenantAgent' +import { TenantService } from './services' + +@injectable() +export class TenantsApi { + private agentContext: AgentContext + private tenantService: TenantService + private agentContextProvider: AgentContextProvider + + public constructor( + tenantService: TenantService, + agentContext: AgentContext, + @inject(InjectionSymbols.AgentContextProvider) agentContextProvider: AgentContextProvider + ) { + this.tenantService = tenantService + this.agentContext = agentContext + this.agentContextProvider = agentContextProvider + } + + public async getTenantAgent({ tenantId }: GetTenantAgentOptions): Promise { + const tenantContext = await this.agentContextProvider.getAgentContextForContextCorrelationId(tenantId) + + const tenantAgent = new TenantAgent(tenantContext) + await tenantAgent.initialize() + + return tenantAgent + } + + public async createTenant(options: CreateTenantOptions) { + const tenantRecord = await this.tenantService.createTenant(this.agentContext, options.config) + + // This initializes the tenant agent, creates the wallet etc... + const tenantAgent = await this.getTenantAgent({ tenantId: tenantRecord.id }) + await tenantAgent.shutdown() + + return tenantRecord + } + + public async getTenantById(tenantId: string) { + return this.tenantService.getTenantById(this.agentContext, tenantId) + } + + public async deleteTenantById(tenantId: string) { + // TODO: force remove context from the context provider (or session manager) + const tenantAgent = await this.getTenantAgent({ tenantId }) + + await tenantAgent.wallet.delete() + await tenantAgent.shutdown() + + return this.tenantService.deleteTenantById(this.agentContext, tenantId) + } +} diff --git a/packages/module-tenants/src/TenantsApiOptions.ts b/packages/module-tenants/src/TenantsApiOptions.ts new file mode 100644 index 0000000000..df9fa10324 --- /dev/null +++ b/packages/module-tenants/src/TenantsApiOptions.ts @@ -0,0 +1,9 @@ +import type { TenantConfig } from './models/TenantConfig' + +export interface GetTenantAgentOptions { + tenantId: string +} + +export interface CreateTenantOptions { + config: Omit +} diff --git a/packages/module-tenants/src/TenantsModule.ts b/packages/module-tenants/src/TenantsModule.ts new file mode 100644 index 0000000000..72545a8382 --- /dev/null +++ b/packages/module-tenants/src/TenantsModule.ts @@ -0,0 +1,31 @@ +import type { DependencyManager } from '@aries-framework/core' + +import { InjectionSymbols, module } from '@aries-framework/core' + +import { TenantsApi } from './TenantsApi' +import { TenantAgentContextProvider } from './context/TenantAgentContextProvider' +import { TenantSessionCoordinator } from './context/TenantSessionCoordinator' +import { TenantRepository, TenantRoutingRepository } from './repository' +import { TenantService } from './services' + +@module() +export class TenantsModule { + /** + * Registers the dependencies of the tenants module on the dependency manager. + */ + public static register(dependencyManager: DependencyManager) { + // Api + // NOTE: this is a singleton because tenants can't have their own tenants. This makes sure the tenants api is always used in the root agent context. + dependencyManager.registerSingleton(TenantsApi) + + // Services + dependencyManager.registerSingleton(TenantService) + + // Repositories + dependencyManager.registerSingleton(TenantRepository) + dependencyManager.registerSingleton(TenantRoutingRepository) + + dependencyManager.registerSingleton(InjectionSymbols.AgentContextProvider, TenantAgentContextProvider) + dependencyManager.registerSingleton(TenantSessionCoordinator) + } +} diff --git a/packages/module-tenants/src/__tests__/TenantAgent.test.ts b/packages/module-tenants/src/__tests__/TenantAgent.test.ts new file mode 100644 index 0000000000..ee97bd4b90 --- /dev/null +++ b/packages/module-tenants/src/__tests__/TenantAgent.test.ts @@ -0,0 +1,30 @@ +import { Agent } from '@aries-framework/core' + +import { agentDependencies, getAgentConfig, getAgentContext } from '../../../core/tests/helpers' +import { TenantAgent } from '../TenantAgent' + +describe('TenantAgent', () => { + test('possible to construct a TenantAgent instance', () => { + const agent = new Agent( + { + label: 'test', + walletConfig: { + id: 'Wallet: TenantAgentRoot', + key: 'Wallet: TenantAgentRoot', + }, + }, + agentDependencies + ) + + const tenantDependencyManager = agent.dependencyManager.createChild() + + const agentContext = getAgentContext({ + agentConfig: getAgentConfig('TenantAgent'), + dependencyManager: tenantDependencyManager, + }) + + const tenantAgent = new TenantAgent(agentContext) + + expect(tenantAgent).toBeInstanceOf(TenantAgent) + }) +}) diff --git a/packages/module-tenants/src/__tests__/TenantsApi.test.ts b/packages/module-tenants/src/__tests__/TenantsApi.test.ts new file mode 100644 index 0000000000..8fe48593d7 --- /dev/null +++ b/packages/module-tenants/src/__tests__/TenantsApi.test.ts @@ -0,0 +1,127 @@ +import { Agent, AgentContext } from '@aries-framework/core' + +import { agentDependencies, getAgentConfig, getAgentContext, mockFunction } from '../../../core/tests/helpers' +import { TenantAgent } from '../TenantAgent' +import { TenantsApi } from '../TenantsApi' +import { TenantAgentContextProvider } from '../context/TenantAgentContextProvider' +import { TenantRecord } from '../repository' +import { TenantService } from '../services/TenantService' + +jest.mock('../services/TenantService') +const TenantServiceMock = TenantService as jest.Mock + +jest.mock('../context/TenantAgentContextProvider') +const AgentContextProviderMock = TenantAgentContextProvider as jest.Mock + +const tenantService = new TenantServiceMock() +const agentContextProvider = new AgentContextProviderMock() +const agentConfig = getAgentConfig('TenantsApi') +const rootAgent = new Agent(agentConfig, agentDependencies) + +const tenantsApi = new TenantsApi(tenantService, rootAgent.context, agentContextProvider) + +describe('TenantsApi', () => { + describe('getTenantAgent', () => { + test('gets context from agent context provider and initializes tenant agent instance', async () => { + const tenantDependencyManager = rootAgent.dependencyManager.createChild() + const tenantAgentContext = getAgentContext({ + contextCorrelationId: 'tenant-id', + dependencyManager: tenantDependencyManager, + agentConfig: agentConfig.extend({ + label: 'tenant-agent', + walletConfig: { + id: 'Wallet: TenantsApi: tenant-id', + key: 'Wallet: TenantsApi: tenant-id', + }, + }), + }) + tenantDependencyManager.registerInstance(AgentContext, tenantAgentContext) + + mockFunction(agentContextProvider.getAgentContextForContextCorrelationId).mockResolvedValue(tenantAgentContext) + + const tenantAgent = await tenantsApi.getTenantAgent({ tenantId: 'tenant-id' }) + + expect(tenantAgent.isInitialized).toBe(true) + expect(tenantAgent.wallet.walletConfig).toEqual({ + id: 'Wallet: TenantsApi: tenant-id', + key: 'Wallet: TenantsApi: tenant-id', + }) + + expect(agentContextProvider.getAgentContextForContextCorrelationId).toBeCalledWith('tenant-id') + expect(tenantAgent).toBeInstanceOf(TenantAgent) + expect(tenantAgent.context).toBe(tenantAgentContext) + + await tenantAgent.wallet.delete() + await tenantAgent.shutdown() + }) + }) + + describe('createTenant', () => { + test('create tenant in the service and get the tenant agent to initialize', async () => { + const tenantRecord = new TenantRecord({ + id: 'tenant-id', + config: { + label: 'test', + walletConfig: { + id: 'Wallet: TenantsApi: tenant-id', + key: 'Wallet: TenantsApi: tenant-id', + }, + }, + }) + + const tenantAgentMock = { + wallet: { + delete: jest.fn(), + }, + shutdown: jest.fn(), + } as unknown as TenantAgent + + mockFunction(tenantService.createTenant).mockResolvedValue(tenantRecord) + const getTenantAgentSpy = jest.spyOn(tenantsApi, 'getTenantAgent').mockResolvedValue(tenantAgentMock) + + const createdTenantRecord = await tenantsApi.createTenant({ + config: { + label: 'test', + }, + }) + + expect(getTenantAgentSpy).toHaveBeenCalledWith({ tenantId: 'tenant-id' }) + expect(createdTenantRecord).toBe(tenantRecord) + expect(tenantAgentMock.shutdown).toHaveBeenCalled() + expect(tenantService.createTenant).toHaveBeenCalledWith(rootAgent.context, { + label: 'test', + }) + }) + }) + + describe('getTenantById', () => { + test('calls get tenant by id on tenant service', async () => { + const tenantRecord = jest.fn() as unknown as TenantRecord + mockFunction(tenantService.getTenantById).mockResolvedValue(tenantRecord) + + const actualTenantRecord = await tenantsApi.getTenantById('tenant-id') + + expect(tenantService.getTenantById).toHaveBeenCalledWith(rootAgent.context, 'tenant-id') + expect(actualTenantRecord).toBe(tenantRecord) + }) + }) + + describe('deleteTenantById', () => { + test('deletes the tenant and removes the wallet', async () => { + const tenantAgentMock = { + wallet: { + delete: jest.fn(), + }, + shutdown: jest.fn(), + } as unknown as TenantAgent + const getTenantAgentSpy = jest.spyOn(tenantsApi, 'getTenantAgent').mockResolvedValue(tenantAgentMock) + + await tenantsApi.deleteTenantById('tenant-id') + + expect(getTenantAgentSpy).toHaveBeenCalledWith({ tenantId: 'tenant-id' }) + expect(tenantAgentMock.wallet.delete).toHaveBeenCalled() + expect(tenantAgentMock.shutdown).toHaveBeenCalled() + expect(tenantService.deleteTenantById).toHaveBeenCalledWith(rootAgent.context, 'tenant-id') + }) + }) +}) diff --git a/packages/module-tenants/src/__tests__/TenantsModule.test.ts b/packages/module-tenants/src/__tests__/TenantsModule.test.ts new file mode 100644 index 0000000000..0e815072a3 --- /dev/null +++ b/packages/module-tenants/src/__tests__/TenantsModule.test.ts @@ -0,0 +1,31 @@ +import { InjectionSymbols } from '@aries-framework/core' + +import { DependencyManager } from '../../../core/src/plugins/DependencyManager' +import { TenantsApi } from '../TenantsApi' +import { TenantsModule } from '../TenantsModule' +import { TenantAgentContextProvider } from '../context/TenantAgentContextProvider' +import { TenantSessionCoordinator } from '../context/TenantSessionCoordinator' +import { TenantRepository, TenantRoutingRepository } from '../repository' +import { TenantService } from '../services' + +jest.mock('../../../core/src/plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('TenantsModule', () => { + test('registers dependencies on the dependency manager', () => { + TenantsModule.register(dependencyManager) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(6) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(TenantsApi) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(TenantService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(TenantRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(TenantRoutingRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith( + InjectionSymbols.AgentContextProvider, + TenantAgentContextProvider + ) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(TenantSessionCoordinator) + }) +}) diff --git a/packages/module-tenants/src/context/TenantAgentContextProvider.ts b/packages/module-tenants/src/context/TenantAgentContextProvider.ts new file mode 100644 index 0000000000..8b32e1142d --- /dev/null +++ b/packages/module-tenants/src/context/TenantAgentContextProvider.ts @@ -0,0 +1,144 @@ +import type { AgentContextProvider, RoutingCreatedEvent, EncryptedMessage } from '@aries-framework/core' + +import { + AriesFrameworkError, + injectable, + AgentContext, + EventEmitter, + inject, + Logger, + RoutingEventTypes, + InjectionSymbols, + KeyType, + Key, + isValidJweStructure, + JsonEncoder, + isJsonObject, +} from '@aries-framework/core' + +import { TenantService } from '../services' + +import { TenantSessionCoordinator } from './TenantSessionCoordinator' + +@injectable() +export class TenantAgentContextProvider implements AgentContextProvider { + private tenantService: TenantService + private rootAgentContext: AgentContext + private eventEmitter: EventEmitter + private logger: Logger + private tenantSessionCoordinator: TenantSessionCoordinator + + public constructor( + tenantService: TenantService, + rootAgentContext: AgentContext, + eventEmitter: EventEmitter, + tenantSessionCoordinator: TenantSessionCoordinator, + @inject(InjectionSymbols.Logger) logger: Logger + ) { + this.tenantService = tenantService + this.rootAgentContext = rootAgentContext + this.eventEmitter = eventEmitter + this.tenantSessionCoordinator = tenantSessionCoordinator + this.logger = logger + + // Start listener for newly created routing keys, so we can register a mapping for each new key for the tenant + this.listenForRoutingKeyCreatedEvents() + } + + public async getAgentContextForContextCorrelationId(tenantId: string) { + // TODO: maybe we can look at not having to retrieve the tenant record if there's already a context available. + const tenantRecord = await this.tenantService.getTenantById(this.rootAgentContext, tenantId) + const agentContext = this.tenantSessionCoordinator.getContextForSession(tenantRecord) + + this.logger.debug(`Created tenant agent context for tenant '${tenantId}'`) + + return agentContext + } + + public async getContextForInboundMessage(inboundMessage: unknown, options?: { contextCorrelationId?: string }) { + this.logger.debug('Getting context for inbound message in tenant agent context provider', { + contextCorrelationId: options?.contextCorrelationId, + }) + + let tenantId = options?.contextCorrelationId + let recipientKeys: Key[] = [] + + if (!tenantId && isValidJweStructure(inboundMessage)) { + this.logger.trace("Inbound message is a JWE, extracting tenant id from JWE's protected header") + recipientKeys = this.getRecipientKeysFromEncryptedMessage(inboundMessage) + + this.logger.trace(`Found ${recipientKeys.length} recipient keys in JWE's protected header`) + + // FIXME: what if there are multiple recipients in the same agent? If we receive the messages twice we will process it for + // the first found recipient multiple times. This is however a case I've never seen before and will add quite some complexity + // to resolve. I think we're fine to ignore this case for now. + for (const recipientKey of recipientKeys) { + const tenantRoutingRecord = await this.tenantService.findTenantRoutingRecordByRecipientKey( + this.rootAgentContext, + recipientKey + ) + + if (tenantRoutingRecord) { + this.logger.debug(`Found tenant routing record for recipient key ${recipientKeys[0].fingerprint}`, { + tenantId: tenantRoutingRecord.tenantId, + }) + tenantId = tenantRoutingRecord.tenantId + break + } + } + } + + if (!tenantId) { + this.logger.error("Couldn't determine tenant id for inbound message. Unable to create context", { + inboundMessage, + recipientKeys: recipientKeys.map((key) => key.fingerprint), + }) + throw new AriesFrameworkError("Couldn't determine tenant id for inbound message. Unable to create context") + } + + const agentContext = await this.getAgentContextForContextCorrelationId(tenantId) + + return agentContext + } + + private getRecipientKeysFromEncryptedMessage(jwe: EncryptedMessage): Key[] { + const jweProtected = JsonEncoder.fromBase64(jwe.protected) + if (!Array.isArray(jweProtected.recipients)) return [] + + const recipientKeys: Key[] = [] + + for (const recipient of jweProtected.recipients) { + // Check if recipient.header.kid is a string + if (isJsonObject(recipient) && isJsonObject(recipient.header) && typeof recipient.header.kid === 'string') { + // This won't work with other key types, we should detect what the encoding is of kid, and based on that + // determine how we extract the key from the message + const key = Key.fromPublicKeyBase58(recipient.header.kid, KeyType.Ed25519) + recipientKeys.push(key) + } + } + + return recipientKeys + } + + private async registerRecipientKeyForTenant(tenantId: string, recipientKey: Key) { + this.logger.debug(`Registering recipient key ${recipientKey.fingerprint} for tenant ${tenantId}`) + const tenantRecord = await this.tenantService.getTenantById(this.rootAgentContext, tenantId) + await this.tenantService.addTenantRoutingRecord(this.rootAgentContext, tenantRecord.id, recipientKey) + } + + private listenForRoutingKeyCreatedEvents() { + this.logger.debug('Listening for routing key created events in tenant agent context provider') + this.eventEmitter.on(RoutingEventTypes.RoutingCreatedEvent, async (event) => { + const contextCorrelationId = event.metadata.contextCorrelationId + const recipientKey = event.payload.routing.recipientKey + + // We don't want to register the key if it's for the root agent context + if (contextCorrelationId === this.rootAgentContext.contextCorrelationId) return + + this.logger.debug( + `Received routing key created event for tenant ${contextCorrelationId}, registering recipient key ${recipientKey.fingerprint} in base wallet` + ) + await this.registerRecipientKeyForTenant(contextCorrelationId, recipientKey) + }) + } +} diff --git a/packages/module-tenants/src/context/TenantSessionCoordinator.ts b/packages/module-tenants/src/context/TenantSessionCoordinator.ts new file mode 100644 index 0000000000..2fe8097f86 --- /dev/null +++ b/packages/module-tenants/src/context/TenantSessionCoordinator.ts @@ -0,0 +1,117 @@ +import type { TenantRecord } from '../repository' + +import { AgentConfig, AgentContext, AriesFrameworkError, injectable, WalletModule } from '@aries-framework/core' +import { Mutex } from 'async-mutex' + +/** + * Coordinates all agent context instance for tenant sessions. + * + * NOTE: the implementation in temporary and doesn't correctly handle the lifecycle of sessions, it's just an implementation to make + * multi-tenancy work. It will keep opening wallets over time, taking up more and more resources. The implementation will be improved in the near future. + * It does however handle race conditions on initialization of wallets (so two requests for the same tenant being processed in parallel) + */ +@injectable() +export class TenantSessionCoordinator { + private rootAgentContext: AgentContext + private tenantAgentContextMapping: TenantAgentContextMapping = {} + + public constructor(rootAgentContext: AgentContext) { + this.rootAgentContext = rootAgentContext + } + + // FIXME: add timeouts to the lock acquire (to prevent deadlocks) + public async getContextForSession(tenantRecord: TenantRecord): Promise { + let tenantContextMapping = this.tenantAgentContextMapping[tenantRecord.id] + + // TODO: we should probably create a new context (but with the same dependency manager / wallet) for each session. + // This way we can add a `.dispose()` on the agent context, which means that agent context isn't usable anymore. However + // the wallet won't be closed. + + // If we already have a context with sessions in place return the context and increment + // the session count. + if (isTenantContextSessions(tenantContextMapping)) { + tenantContextMapping.sessionCount++ + return tenantContextMapping.agentContext + } + + // TODO: look at semaphores to manage the total number of wallets + // If the context is currently being initialized, wait for it to complete. + else if (isTenantAgentContextInitializing(tenantContextMapping)) { + // Wait for the wallet to finish initializing, then try to + return await tenantContextMapping.mutex.runExclusive(() => { + tenantContextMapping = this.tenantAgentContextMapping[tenantRecord.id] + + // There should always be a context now, if this isn't the case we must error out + // TODO: handle the case where the previous initialization failed (the value is undefined) + // We can just open a new session in that case, but for now we'll ignore this flow + if (!isTenantContextSessions(tenantContextMapping)) { + throw new AriesFrameworkError('Tenant context is not ready yet') + } + + tenantContextMapping.sessionCount++ + return tenantContextMapping.agentContext + }) + } + // No value for this tenant exists yet, initialize a new session. + else { + // Set a mutex on the agent context mapping so other requests can wait for it to be initialized. + const mutex = new Mutex() + this.tenantAgentContextMapping[tenantRecord.id] = { + mutex, + } + + return await mutex.runExclusive(async () => { + const tenantDependencyManager = this.rootAgentContext.dependencyManager.createChild() + const tenantConfig = this.rootAgentContext.config.extend(tenantRecord.config) + + const agentContext = new AgentContext({ + contextCorrelationId: tenantRecord.id, + dependencyManager: tenantDependencyManager, + }) + + tenantDependencyManager.registerInstance(AgentContext, agentContext) + tenantDependencyManager.registerInstance(AgentConfig, tenantConfig) + + tenantContextMapping = { + agentContext, + sessionCount: 1, + } + + // NOTE: we're using the wallet module here because that correctly handle creating if it doesn't exist yet + // and will also write the storage version to the storage, which is needed by the update assistant. We either + // need to move this out of the module, or just keep using the module here. + const walletModule = agentContext.dependencyManager.resolve(WalletModule) + await walletModule.initialize(tenantRecord.config.walletConfig) + + this.tenantAgentContextMapping[tenantRecord.id] = tenantContextMapping + + return agentContext + }) + } + } +} + +interface TenantContextSessions { + sessionCount: number + agentContext: AgentContext +} + +interface TenantContextInitializing { + mutex: Mutex +} + +export interface TenantAgentContextMapping { + [tenantId: string]: TenantContextSessions | TenantContextInitializing | undefined +} + +function isTenantAgentContextInitializing( + contextMapping: TenantContextSessions | TenantContextInitializing | undefined +): contextMapping is TenantContextInitializing { + return contextMapping !== undefined && (contextMapping as TenantContextInitializing).mutex !== undefined +} + +function isTenantContextSessions( + contextMapping: TenantContextSessions | TenantContextInitializing | undefined +): contextMapping is TenantContextSessions { + return contextMapping !== undefined && (contextMapping as TenantContextSessions).sessionCount !== undefined +} diff --git a/packages/module-tenants/src/context/__tests__/TenantAgentContextProvider.test.ts b/packages/module-tenants/src/context/__tests__/TenantAgentContextProvider.test.ts new file mode 100644 index 0000000000..8b57626900 --- /dev/null +++ b/packages/module-tenants/src/context/__tests__/TenantAgentContextProvider.test.ts @@ -0,0 +1,151 @@ +import type { AgentContext } from '@aries-framework/core' + +import { Key } from '@aries-framework/core' + +import { EventEmitter } from '../../../../core/src/agent/EventEmitter' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../core/tests/helpers' +import { TenantRecord, TenantRoutingRecord } from '../../repository' +import { TenantService } from '../../services/TenantService' +import { TenantAgentContextProvider } from '../TenantAgentContextProvider' +import { TenantSessionCoordinator } from '../TenantSessionCoordinator' + +jest.mock('../../../../core/src/agent/EventEmitter') +jest.mock('../../services/TenantService') +jest.mock('../TenantSessionCoordinator') + +const EventEmitterMock = EventEmitter as jest.Mock +const TenantServiceMock = TenantService as jest.Mock +const TenantSessionCoordinatorMock = TenantSessionCoordinator as jest.Mock + +const tenantService = new TenantServiceMock() +const tenantSessionCoordinator = new TenantSessionCoordinatorMock() + +const rootAgentContext = getAgentContext() +const agentConfig = getAgentConfig('TenantAgentContextProvider') +const eventEmitter = new EventEmitterMock() + +const tenantAgentContextProvider = new TenantAgentContextProvider( + tenantService, + rootAgentContext, + eventEmitter, + tenantSessionCoordinator, + agentConfig.logger +) + +const inboundMessage = { + protected: + 'eyJlbmMiOiJ4Y2hhY2hhMjBwb2x5MTMwNV9pZXRmIiwidHlwIjoiSldNLzEuMCIsImFsZyI6IkF1dGhjcnlwdCIsInJlY2lwaWVudHMiOlt7ImVuY3J5cHRlZF9rZXkiOiIta3AzRlREbzdNTnlqSVlvWkhFdFhzMzRLTlpEODBIc2tEVTcxSVg5ejJpdVphUy1PVHhNc21KUWNCeDN1Y1lVIiwiaGVhZGVyIjp7ImtpZCI6IjdQd0ZrMXB2V2JOTkUyUDRDTFlnWW5jUXJMc0VSRUx2cDZmQVRaTktVZnpXIiwiaXYiOiJBSElTQk94MWhrWk5obkVwWndJNFlWZ09HNnQ3RDhlQiIsInNlbmRlciI6IjRMTnFHWGJ3SGlPU01uQThsV1M3bEpocER6aGY5UUIyYjNPUVZyWkkyeEctWWJkT1BUamR6WWRGamdpbFo4MF95bXhKSHpoWmp1bHhPeEVvek81VUhDQzJ3bnV3MHo3OVRRWjE5MFgzUFI2WWlrSUVIcms2N3A4V09WTT0ifX1dfQ==', + iv: 'CfsDEiS63uOJRZa-', + ciphertext: + 'V6V23nNdKSn2a0jqrjQoU8cj6Ks9w9_4eqE0_856hjd_gxYxqT4W0M9sZ5ov1zlOptrBz6wGDK-BoEbOzvgNqHmiUS5h_VvVuEIevpp9xYrCLlNigrXJEtoDGWkVVpYq3i14l5XGMYCu2zTL7QJxHqDrzRAG-5Iqht0FY45u4L471CMvj31XuZps6I_wl-TJWfeZbAS1Knp_dEnElqtbkcctOKjnvaosk2WYaIrQXRiJxk-4URGnmMAQxjSSt5KuzE3LQ_fa_u5PQLC0EaOsg5M9fYBSIB1_090fQ0QTPXLB69pyiFzLmb016vHGIG5nAbqNKS7fmkhxo7iMkOBlR5d5FpCAguXln4Fg4Q4tZgEaPXVkqmayTvLyeJqXa22dbNDhaPGrvjlNimn8moP8qSC0Avoozn4BDdLrSQxs5daYcH0JYhqz7VII2Mrb2gp2LMsQsoy-UrChTZSBeyjWdapqOzMc8yOhKYXwA_du0RfSaPFe8VJyMo4X73LC6-i1QU5xg3fZKiKJWRrTUazLvdNEXm79DV76LxylodY7OeCGH6E2amF1k10VC2eYwNCI3CfXS8uvjDEIQGgsVETCqwclWKxD-AhQEwZFRlNhZjlfbUyOKH8WAoikloN75T2AZiTivzlE5ZvnPUU_z4LJI02z-vpIMEjkHKcgjx0jDFi3VkfLPiOG4P6btZpxjISfZvWcCiebAhs5jAGX2zNjYiPErJx33zOclHYS8KEZB3fdAdpAZKdYlPyAOFpN8r21kn6HPYjm-3NmTqrHAjDIMgt0mJ6AI58XOnoqRWN7Hl1HWhy9qkt0AqRVJZIIoyFefvKRJvotsv9aj1ZGPqnrkR2Hpj7u-K8VOKreIg4kWYyVbAF8Oift9CrqJ4olOOSUOQZ_NL36qGJc1RCR_wRnTWikoRR_o4h4fDZtxTQG9nUgbAoaAumJAbp5mxrYBW6KVZ-Jm9rhdNnRRnvvd1e_uW-X66_9B5g0GM3BmsXK-ARpJP6ZYmpQYiVFjrDxOSrvq1gD3aPi0SCP6mYoNvemGoXFhGTPMTGQvy1RAwY9t_BosZNEMZMfYTzHxWhN55yXd0861uv5nFe_aLKQcdin8QySW-FS0jcExnRostY922fqT5JYPBINqAr59u8gpzX-N9DgczL1WjuKkwyezLrcCR1IaG9gZrEIJxLDRGHvBno6ZkqmLiuAx3LZxgrT5yN2fI7WjO5HHQMVLn7rVF-THmpLNTZmmsoJ2ZU9ZGeAMKBpcfIYIHgKHF1vumr_h2uCbvxlwqigm5A-dSmto0Fv9xewfDhZ5TvE-TKwHpwmb0OG4kZqC3CnMmzh_oSOK0Cc6ovldiVOUvXdVZJiSD9KEFxn1YmDNbsdMDP9GAAWAknFmdBp5x7DCCt6sMjCVuw1hbELAGXusipfdvfb4htSN5FR4k72efenEr0glFtDk7s5EvWTWsBZyv92P5et-70MjTKGtMJaC4uCBL3li3ty397yKKcJly2Fog5N0phqPHPHg_-CGZ8YpkcM_q3Ijcc8db701K2TShiG97AjOdCZCSgK8OGv_UFXxXXxiwrdQOM0Jfg0TCz_ESxQLAlepK4JQplE_kR8k3jDf5nH4SMueobioPfkLQ92lCFXBOCX3ugoJJnnb49CbQfi-49PAHsGaTopLXxZoEdf6kgJ8phFakBoMmbLE1zIV43oVR8T-zZYsr377q6c6LY46PyYusP7CB5wgXbG4nyJZ_zGZHvY_hnbcE2-EuysmzQV4-6rJdLdT8FSyX_Xo-K2ZmX-riFUcKamoFWmO3CDtexn-ZgtAIJpdjAApWHFxZWLI6xx67OgHl8GT2HIs_BdoetFvmj4tJ_Aw8_Mmb9W37B4Esom1Tg3XxxfLqj24s7UlgUwYFblkYtB1L9-9DkNlZZWkYJz-A28WW6OSqIYNw0ASyNDEp3Mwy0SHDUYh10NUmQ4C476QRNmr32Jv_6AiTGj1thibFg_Ewd_kdvvo0E7VL6gktZNh9kIT-EPgFAobR5IpG0_V1dJ7pEQPKN-n7nc6gWgry7kxNIfS4LcbPwVDsUzJiJ4Qlw=', + tag: 'goWiDaoxy4mHHRnkPiux4Q==', +} + +describe('TenantAgentContextProvider', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + describe('getAgentContextForContextCorrelationId', () => { + test('retrieves the tenant and calls tenant session coordinator', async () => { + const tenantRecord = new TenantRecord({ + id: 'tenant1', + config: { + label: 'Test Tenant', + walletConfig: { + id: 'test-wallet', + key: 'test-wallet-key', + }, + }, + }) + + const tenantAgentContext = jest.fn() as unknown as AgentContext + + mockFunction(tenantService.getTenantById).mockResolvedValue(tenantRecord) + mockFunction(tenantSessionCoordinator.getContextForSession).mockResolvedValue(tenantAgentContext) + + const returnedAgentContext = await tenantAgentContextProvider.getAgentContextForContextCorrelationId('tenant1') + + expect(tenantService.getTenantById).toHaveBeenCalledWith(rootAgentContext, 'tenant1') + expect(tenantSessionCoordinator.getContextForSession).toHaveBeenCalledWith(tenantRecord) + expect(returnedAgentContext).toBe(tenantAgentContext) + }) + }) + + describe('getContextForInboundMessage', () => { + test('directly calls get agent context if tenant id has been provided in the contextCorrelationId', async () => { + const tenantRecord = new TenantRecord({ + id: 'tenant1', + config: { + label: 'Test Tenant', + walletConfig: { + id: 'test-wallet', + key: 'test-wallet-key', + }, + }, + }) + + const tenantAgentContext = jest.fn() as unknown as AgentContext + + mockFunction(tenantService.getTenantById).mockResolvedValue(tenantRecord) + mockFunction(tenantSessionCoordinator.getContextForSession).mockResolvedValue(tenantAgentContext) + + const returnedAgentContext = await tenantAgentContextProvider.getContextForInboundMessage( + {}, + { contextCorrelationId: 'tenant1' } + ) + + expect(tenantService.getTenantById).toHaveBeenCalledWith(rootAgentContext, 'tenant1') + expect(tenantSessionCoordinator.getContextForSession).toHaveBeenCalledWith(tenantRecord) + expect(returnedAgentContext).toBe(tenantAgentContext) + expect(tenantService.findTenantRoutingRecordByRecipientKey).not.toHaveBeenCalled() + }) + + test('throws an error if not contextCorrelationId is provided and no tenant id could be extracted from the inbound message', async () => { + // no routing records found + mockFunction(tenantService.findTenantRoutingRecordByRecipientKey).mockResolvedValue(null) + + await expect(tenantAgentContextProvider.getContextForInboundMessage(inboundMessage)).rejects.toThrowError( + "Couldn't determine tenant id for inbound message. Unable to create context" + ) + }) + + test('finds the tenant id based on the inbound message recipient keys and calls get agent context', async () => { + const tenantRoutingRecord = new TenantRoutingRecord({ + recipientKeyFingerprint: 'z6MkkrCJLG5Mr8rqLXDksuWXPtAQfv95q7bHW7a6HqLLPtmt', + tenantId: 'tenant1', + }) + + const tenantRecord = new TenantRecord({ + id: 'tenant1', + config: { + label: 'Test Tenant', + walletConfig: { + id: 'test-wallet', + key: 'test-wallet-key', + }, + }, + }) + + const tenantAgentContext = jest.fn() as unknown as AgentContext + mockFunction(tenantService.findTenantRoutingRecordByRecipientKey).mockResolvedValue(tenantRoutingRecord) + + mockFunction(tenantService.getTenantById).mockResolvedValue(tenantRecord) + mockFunction(tenantSessionCoordinator.getContextForSession).mockResolvedValue(tenantAgentContext) + + const returnedAgentContext = await tenantAgentContextProvider.getContextForInboundMessage(inboundMessage) + + expect(tenantService.getTenantById).toHaveBeenCalledWith(rootAgentContext, 'tenant1') + expect(tenantSessionCoordinator.getContextForSession).toHaveBeenCalledWith(tenantRecord) + expect(returnedAgentContext).toBe(tenantAgentContext) + expect(tenantService.findTenantRoutingRecordByRecipientKey).toHaveBeenCalledWith( + rootAgentContext, + expect.any(Key) + ) + + const actualKey = mockFunction(tenantService.findTenantRoutingRecordByRecipientKey).mock.calls[0][1] + // Based on the recipient key from the inboundMessage protected header above + expect(actualKey.fingerprint).toBe('z6MkkrCJLG5Mr8rqLXDksuWXPtAQfv95q7bHW7a6HqLLPtmt') + }) + }) +}) diff --git a/packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts b/packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts new file mode 100644 index 0000000000..deaf159d1c --- /dev/null +++ b/packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts @@ -0,0 +1,126 @@ +import type { TenantAgentContextMapping } from '../TenantSessionCoordinator' +import type { AgentContext } from '@aries-framework/core' + +import { WalletModule } from '@aries-framework/core' +import { Mutex } from 'async-mutex' + +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../core/tests/helpers' +import { TenantRecord } from '../../repository' +import { TenantSessionCoordinator } from '../TenantSessionCoordinator' + +// tenantAgentContextMapping is private, but we need to access it to properly test this class. Adding type override to +// make sure we don't get a lot of type errors. +type PublicTenantAgentContextMapping = Omit & { + tenantAgentContextMapping: TenantAgentContextMapping +} + +const wallet = { + initialize: jest.fn(), +} as unknown as WalletModule + +const agentContext = getAgentContext({ + agentConfig: getAgentConfig('TenantSessionCoordinator'), +}) + +agentContext.dependencyManager.registerInstance(WalletModule, wallet) +const tenantSessionCoordinator = new TenantSessionCoordinator( + agentContext +) as unknown as PublicTenantAgentContextMapping + +describe('TenantSessionCoordinator', () => { + afterEach(() => { + tenantSessionCoordinator.tenantAgentContextMapping = {} + jest.clearAllMocks() + }) + + describe('getContextForSession', () => { + test('returns the context from the tenantAgentContextMapping and increases the session count if already available', async () => { + const tenant1AgentContext = jest.fn() as unknown as AgentContext + + const tenant1 = { + agentContext: tenant1AgentContext, + sessionCount: 1, + } + tenantSessionCoordinator.tenantAgentContextMapping = { + tenant1, + } + + const tenantRecord = new TenantRecord({ + id: 'tenant1', + config: { + label: 'Test Tenant', + walletConfig: { + id: 'test-wallet', + key: 'test-wallet-key', + }, + }, + }) + + const tenantAgentContext = await tenantSessionCoordinator.getContextForSession(tenantRecord) + expect(tenantAgentContext).toBe(tenant1AgentContext) + expect(tenant1.sessionCount).toBe(2) + }) + + test('creates a new agent context, initializes the wallet and stores it in the tenant agent context mapping', async () => { + const tenantRecord = new TenantRecord({ + id: 'tenant1', + config: { + label: 'Test Tenant', + walletConfig: { + id: 'test-wallet', + key: 'test-wallet-key', + }, + }, + }) + + const tenantAgentContext = await tenantSessionCoordinator.getContextForSession(tenantRecord) + + expect(wallet.initialize).toHaveBeenCalledWith(tenantRecord.config.walletConfig) + expect(tenantSessionCoordinator.tenantAgentContextMapping.tenant1).toEqual({ + agentContext: tenantAgentContext, + sessionCount: 1, + }) + + expect(tenantAgentContext.contextCorrelationId).toBe('tenant1') + }) + + test('locks and waits for lock to release when initialization is already in progress', async () => { + const tenantRecord = new TenantRecord({ + id: 'tenant1', + config: { + label: 'Test Tenant', + walletConfig: { + id: 'test-wallet', + key: 'test-wallet-key', + }, + }, + }) + + // Add timeout to mock the initialization and we can test that the mutex is used. + mockFunction(wallet.initialize).mockReturnValueOnce(new Promise((resolve) => setTimeout(resolve, 100))) + + // Start two context session creations (but don't await). It should set the mutex property on the tenant agent context mapping. + const tenantAgentContext1Promise = tenantSessionCoordinator.getContextForSession(tenantRecord) + const tenantAgentContext2Promise = tenantSessionCoordinator.getContextForSession(tenantRecord) + expect(tenantSessionCoordinator.tenantAgentContextMapping.tenant1).toEqual({ + mutex: expect.any(Mutex), + }) + + // Await both context value promises + const tenantAgentContext1 = await tenantAgentContext1Promise + const tenantAgentContext2 = await tenantAgentContext2Promise + + // There should be two sessions active now + expect(tenantSessionCoordinator.tenantAgentContextMapping.tenant1).toEqual({ + agentContext: tenantAgentContext1, + sessionCount: 2, + }) + + // Initialize should only be called once + expect(wallet.initialize).toHaveBeenCalledTimes(1) + expect(wallet.initialize).toHaveBeenCalledWith(tenantRecord.config.walletConfig) + + expect(tenantAgentContext1).toBe(tenantAgentContext2) + }) + }) +}) diff --git a/packages/module-tenants/src/context/types.ts b/packages/module-tenants/src/context/types.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/module-tenants/src/index.ts b/packages/module-tenants/src/index.ts new file mode 100644 index 0000000000..e4f3378296 --- /dev/null +++ b/packages/module-tenants/src/index.ts @@ -0,0 +1,4 @@ +export { TenantRecord, TenantRecordProps } from './repository/TenantRecord' +export * from './TenantsModule' +export * from './TenantsApi' +export * from './TenantsApiOptions' diff --git a/packages/module-tenants/src/models/TenantConfig.ts b/packages/module-tenants/src/models/TenantConfig.ts new file mode 100644 index 0000000000..d8849e73fe --- /dev/null +++ b/packages/module-tenants/src/models/TenantConfig.ts @@ -0,0 +1,5 @@ +import type { InitConfig, WalletConfig } from '@aries-framework/core' + +export type TenantConfig = Pick & { + walletConfig: Pick +} diff --git a/packages/module-tenants/src/repository/TenantRecord.ts b/packages/module-tenants/src/repository/TenantRecord.ts new file mode 100644 index 0000000000..6c49689d9e --- /dev/null +++ b/packages/module-tenants/src/repository/TenantRecord.ts @@ -0,0 +1,37 @@ +import type { TenantConfig } from '../models/TenantConfig' +import type { RecordTags, TagsBase } from '@aries-framework/core' + +import { BaseRecord, utils } from '@aries-framework/core' + +export type TenantRecordTags = RecordTags + +export interface TenantRecordProps { + id?: string + createdAt?: Date + config: TenantConfig + tags?: TagsBase +} + +export class TenantRecord extends BaseRecord { + public static readonly type = 'TenantRecord' + public readonly type = TenantRecord.type + + public config!: TenantConfig + + public constructor(props: TenantRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.createdAt = props.createdAt ?? new Date() + this._tags = props.tags ?? {} + this.config = props.config + } + } + + public getTags() { + return { + ...this._tags, + } + } +} diff --git a/packages/module-tenants/src/repository/TenantRepository.ts b/packages/module-tenants/src/repository/TenantRepository.ts new file mode 100644 index 0000000000..abfd0da287 --- /dev/null +++ b/packages/module-tenants/src/repository/TenantRepository.ts @@ -0,0 +1,13 @@ +import { Repository, StorageService, InjectionSymbols, EventEmitter, inject, injectable } from '@aries-framework/core' + +import { TenantRecord } from './TenantRecord' + +@injectable() +export class TenantRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(TenantRecord, storageService, eventEmitter) + } +} diff --git a/packages/module-tenants/src/repository/TenantRoutingRecord.ts b/packages/module-tenants/src/repository/TenantRoutingRecord.ts new file mode 100644 index 0000000000..62987290b0 --- /dev/null +++ b/packages/module-tenants/src/repository/TenantRoutingRecord.ts @@ -0,0 +1,47 @@ +import type { RecordTags, TagsBase } from '@aries-framework/core' + +import { BaseRecord, utils } from '@aries-framework/core' + +export type TenantRoutingRecordTags = RecordTags + +type DefaultTenantRoutingRecordTags = { + tenantId: string + recipientKeyFingerprint: string +} + +export interface TenantRoutingRecordProps { + id?: string + createdAt?: Date + tags?: TagsBase + + tenantId: string + recipientKeyFingerprint: string +} + +export class TenantRoutingRecord extends BaseRecord { + public static readonly type = 'TenantRoutingRecord' + public readonly type = TenantRoutingRecord.type + + public tenantId!: string + public recipientKeyFingerprint!: string + + public constructor(props: TenantRoutingRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.createdAt = props.createdAt ?? new Date() + this._tags = props.tags ?? {} + this.tenantId = props.tenantId + this.recipientKeyFingerprint = props.recipientKeyFingerprint + } + } + + public getTags() { + return { + ...this._tags, + tenantId: this.tenantId, + recipientKeyFingerprint: this.recipientKeyFingerprint, + } + } +} diff --git a/packages/module-tenants/src/repository/TenantRoutingRepository.ts b/packages/module-tenants/src/repository/TenantRoutingRepository.ts new file mode 100644 index 0000000000..1696aeb9fb --- /dev/null +++ b/packages/module-tenants/src/repository/TenantRoutingRepository.ts @@ -0,0 +1,21 @@ +import type { AgentContext, Key } from '@aries-framework/core' + +import { Repository, StorageService, InjectionSymbols, EventEmitter, inject, injectable } from '@aries-framework/core' + +import { TenantRoutingRecord } from './TenantRoutingRecord' + +@injectable() +export class TenantRoutingRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(TenantRoutingRecord, storageService, eventEmitter) + } + + public findByRecipientKey(agentContext: AgentContext, key: Key) { + return this.findSingleByQuery(agentContext, { + recipientKeyFingerprint: key.fingerprint, + }) + } +} diff --git a/packages/module-tenants/src/repository/__tests__/TenantRecord.test.ts b/packages/module-tenants/src/repository/__tests__/TenantRecord.test.ts new file mode 100644 index 0000000000..7c6e311704 --- /dev/null +++ b/packages/module-tenants/src/repository/__tests__/TenantRecord.test.ts @@ -0,0 +1,87 @@ +import { JsonTransformer } from '../../../../core/src' +import { TenantRecord } from '../TenantRecord' + +describe('TenantRecord', () => { + test('sets the values passed in the constructor on the record', () => { + const createdAt = new Date() + const tenantRecord = new TenantRecord({ + id: 'tenant-id', + createdAt, + tags: { + some: 'tag', + }, + config: { + label: 'test', + walletConfig: { + id: 'test', + key: 'test', + }, + }, + }) + + expect(tenantRecord.type).toBe('TenantRecord') + expect(tenantRecord.id).toBe('tenant-id') + expect(tenantRecord.createdAt).toBe(createdAt) + expect(tenantRecord.config).toMatchObject({ + label: 'test', + walletConfig: { + id: 'test', + key: 'test', + }, + }) + expect(tenantRecord.getTags()).toMatchObject({ + some: 'tag', + }) + }) + + test('serializes and deserializes', () => { + const createdAt = new Date('2022-02-02') + const tenantRecord = new TenantRecord({ + id: 'tenant-id', + createdAt, + tags: { + some: 'tag', + }, + config: { + label: 'test', + walletConfig: { + id: 'test', + key: 'test', + }, + }, + }) + + const json = tenantRecord.toJSON() + expect(json).toEqual({ + id: 'tenant-id', + createdAt: '2022-02-02T00:00:00.000Z', + metadata: {}, + _tags: { + some: 'tag', + }, + config: { + label: 'test', + walletConfig: { + id: 'test', + key: 'test', + }, + }, + }) + + const instance = JsonTransformer.fromJSON(json, TenantRecord) + + expect(instance.type).toBe('TenantRecord') + expect(instance.id).toBe('tenant-id') + expect(instance.createdAt.getTime()).toBe(createdAt.getTime()) + expect(instance.config).toMatchObject({ + label: 'test', + walletConfig: { + id: 'test', + key: 'test', + }, + }) + expect(instance.getTags()).toMatchObject({ + some: 'tag', + }) + }) +}) diff --git a/packages/module-tenants/src/repository/__tests__/TenantRoutingRecord.test.ts b/packages/module-tenants/src/repository/__tests__/TenantRoutingRecord.test.ts new file mode 100644 index 0000000000..1d47b2bd18 --- /dev/null +++ b/packages/module-tenants/src/repository/__tests__/TenantRoutingRecord.test.ts @@ -0,0 +1,77 @@ +import { JsonTransformer } from '../../../../core/src' +import { TenantRoutingRecord } from '../TenantRoutingRecord' + +describe('TenantRoutingRecord', () => { + test('sets the values passed in the constructor on the record', () => { + const createdAt = new Date() + const tenantRoutingRecord = new TenantRoutingRecord({ + id: 'record-id', + createdAt, + tags: { + some: 'tag', + }, + tenantId: 'tenant-id', + recipientKeyFingerprint: 'z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + }) + + expect(tenantRoutingRecord.type).toBe('TenantRoutingRecord') + expect(tenantRoutingRecord.id).toBe('record-id') + expect(tenantRoutingRecord.tenantId).toBe('tenant-id') + expect(tenantRoutingRecord.createdAt).toBe(createdAt) + expect(tenantRoutingRecord.recipientKeyFingerprint).toBe('z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') + expect(tenantRoutingRecord.getTags()).toMatchObject({ + some: 'tag', + }) + }) + + test('returns the default tags', () => { + const tenantRoutingRecord = new TenantRoutingRecord({ + tenantId: 'tenant-id', + recipientKeyFingerprint: 'z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + }) + + expect(tenantRoutingRecord.getTags()).toMatchObject({ + tenantId: 'tenant-id', + recipientKeyFingerprint: 'z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + }) + }) + + test('serializes and deserializes', () => { + const createdAt = new Date('2022-02-02') + const tenantRoutingRecord = new TenantRoutingRecord({ + id: 'record-id', + createdAt, + tags: { + some: 'tag', + }, + tenantId: 'tenant-id', + recipientKeyFingerprint: 'z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + }) + + const json = tenantRoutingRecord.toJSON() + expect(json).toEqual({ + id: 'record-id', + createdAt: '2022-02-02T00:00:00.000Z', + recipientKeyFingerprint: 'z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + tenantId: 'tenant-id', + metadata: {}, + _tags: { + some: 'tag', + }, + }) + + const instance = JsonTransformer.fromJSON(json, TenantRoutingRecord) + + expect(instance.type).toBe('TenantRoutingRecord') + expect(instance.id).toBe('record-id') + expect(instance.createdAt.getTime()).toBe(createdAt.getTime()) + expect(instance.recipientKeyFingerprint).toBe('z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') + expect(instance.tenantId).toBe('tenant-id') + + expect(instance.getTags()).toMatchObject({ + some: 'tag', + recipientKeyFingerprint: 'z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + tenantId: 'tenant-id', + }) + }) +}) diff --git a/packages/module-tenants/src/repository/__tests__/TenantRoutingRepository.test.ts b/packages/module-tenants/src/repository/__tests__/TenantRoutingRepository.test.ts new file mode 100644 index 0000000000..ed22ee31ab --- /dev/null +++ b/packages/module-tenants/src/repository/__tests__/TenantRoutingRepository.test.ts @@ -0,0 +1,38 @@ +import type { StorageService, EventEmitter } from '../../../../core/src' + +import { Key } from '../../../../core/src' +import { getAgentContext, mockFunction } from '../../../../core/tests/helpers' +import { TenantRoutingRecord } from '../TenantRoutingRecord' +import { TenantRoutingRepository } from '../TenantRoutingRepository' + +const storageServiceMock = { + findByQuery: jest.fn(), +} as unknown as StorageService +const eventEmitter = jest.fn() as unknown as EventEmitter +const agentContext = getAgentContext() + +const tenantRoutingRepository = new TenantRoutingRepository(storageServiceMock, eventEmitter) + +describe('TenantRoutingRepository', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + describe('findByRecipientKey', () => { + test('it should correctly transform the key to a fingerprint and return the routing record', async () => { + const key = Key.fromFingerprint('z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') + const tenantRoutingRecord = new TenantRoutingRecord({ + recipientKeyFingerprint: key.fingerprint, + tenantId: 'tenant-id', + }) + + mockFunction(storageServiceMock.findByQuery).mockResolvedValue([tenantRoutingRecord]) + const returnedRecord = await tenantRoutingRepository.findByRecipientKey(agentContext, key) + + expect(storageServiceMock.findByQuery).toHaveBeenCalledWith(agentContext, TenantRoutingRecord, { + recipientKeyFingerprint: 'z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + }) + expect(returnedRecord).toBe(tenantRoutingRecord) + }) + }) +}) diff --git a/packages/module-tenants/src/repository/index.ts b/packages/module-tenants/src/repository/index.ts new file mode 100644 index 0000000000..99ac579c10 --- /dev/null +++ b/packages/module-tenants/src/repository/index.ts @@ -0,0 +1,4 @@ +export * from './TenantRecord' +export * from './TenantRepository' +export * from './TenantRoutingRecord' +export * from './TenantRoutingRepository' diff --git a/packages/module-tenants/src/services/TenantService.ts b/packages/module-tenants/src/services/TenantService.ts new file mode 100644 index 0000000000..be6858d164 --- /dev/null +++ b/packages/module-tenants/src/services/TenantService.ts @@ -0,0 +1,83 @@ +import type { TenantConfig } from '../models/TenantConfig' +import type { AgentContext, Key } from '@aries-framework/core' + +import { injectable, utils } from '@aries-framework/core' + +import { TenantRepository, TenantRecord, TenantRoutingRepository, TenantRoutingRecord } from '../repository' + +@injectable() +export class TenantService { + private tenantRepository: TenantRepository + private tenantRoutingRepository: TenantRoutingRepository + + public constructor(tenantRepository: TenantRepository, tenantRoutingRepository: TenantRoutingRepository) { + this.tenantRepository = tenantRepository + this.tenantRoutingRepository = tenantRoutingRepository + } + + public async createTenant(agentContext: AgentContext, config: Omit) { + const tenantId = utils.uuid() + + const walletId = `tenant-${tenantId}` + const walletKey = await agentContext.wallet.generateWalletKey() + + const tenantRecord = new TenantRecord({ + id: tenantId, + config: { + ...config, + walletConfig: { + id: walletId, + key: walletKey, + }, + }, + }) + + await this.tenantRepository.save(agentContext, tenantRecord) + + return tenantRecord + } + + public async getTenantById(agentContext: AgentContext, tenantId: string) { + return this.tenantRepository.getById(agentContext, tenantId) + } + + public async deleteTenantById(agentContext: AgentContext, tenantId: string) { + const tenantRecord = await this.getTenantById(agentContext, tenantId) + + const tenantRoutingRecords = await this.tenantRoutingRepository.findByQuery(agentContext, { + tenantId: tenantRecord.id, + }) + + // Delete all tenant routing records + await Promise.all( + tenantRoutingRecords.map((tenantRoutingRecord) => + this.tenantRoutingRepository.delete(agentContext, tenantRoutingRecord) + ) + ) + + // Delete tenant record + await this.tenantRepository.delete(agentContext, tenantRecord) + } + + public async findTenantRoutingRecordByRecipientKey( + agentContext: AgentContext, + recipientKey: Key + ): Promise { + return this.tenantRoutingRepository.findByRecipientKey(agentContext, recipientKey) + } + + public async addTenantRoutingRecord( + agentContext: AgentContext, + tenantId: string, + recipientKey: Key + ): Promise { + const tenantRoutingRecord = new TenantRoutingRecord({ + tenantId, + recipientKeyFingerprint: recipientKey.fingerprint, + }) + + await this.tenantRoutingRepository.save(agentContext, tenantRoutingRecord) + + return tenantRoutingRecord + } +} diff --git a/packages/module-tenants/src/services/__tests__/TenantService.test.ts b/packages/module-tenants/src/services/__tests__/TenantService.test.ts new file mode 100644 index 0000000000..edbb44f9cd --- /dev/null +++ b/packages/module-tenants/src/services/__tests__/TenantService.test.ts @@ -0,0 +1,151 @@ +import type { Wallet } from '@aries-framework/core' + +import { Key } from '@aries-framework/core' + +import { getAgentContext, mockFunction } from '../../../../core/tests/helpers' +import { TenantRecord, TenantRoutingRecord } from '../../repository' +import { TenantRepository } from '../../repository/TenantRepository' +import { TenantRoutingRepository } from '../../repository/TenantRoutingRepository' +import { TenantService } from '../TenantService' + +jest.mock('../../repository/TenantRepository') +const TenantRepositoryMock = TenantRepository as jest.Mock +jest.mock('../../repository/TenantRoutingRepository') +const TenantRoutingRepositoryMock = TenantRoutingRepository as jest.Mock + +const wallet = { + generateWalletKey: jest.fn(() => Promise.resolve('walletKey')), +} as unknown as Wallet + +const tenantRepository = new TenantRepositoryMock() +const tenantRoutingRepository = new TenantRoutingRepositoryMock() +const agentContext = getAgentContext({ wallet }) + +const tenantService = new TenantService(tenantRepository, tenantRoutingRepository) + +describe('TenantService', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + describe('createTenant', () => { + test('creates a tenant record and stores it in the tenant repository', async () => { + const tenantRecord = await tenantService.createTenant(agentContext, { + label: 'Test Tenant', + connectionImageUrl: 'https://example.com/connection.png', + }) + + expect(tenantRecord).toMatchObject({ + id: expect.any(String), + config: { + label: 'Test Tenant', + connectionImageUrl: 'https://example.com/connection.png', + walletConfig: { + id: expect.any(String), + key: 'walletKey', + }, + }, + }) + + expect(agentContext.wallet.generateWalletKey).toHaveBeenCalled() + expect(tenantRepository.save).toHaveBeenCalledWith(agentContext, tenantRecord) + }) + }) + + describe('getTenantById', () => { + test('returns value from tenant repository get by id', async () => { + const tenantRecord = jest.fn() as unknown as TenantRecord + mockFunction(tenantRepository.getById).mockResolvedValue(tenantRecord) + const returnedTenantRecord = await tenantService.getTenantById(agentContext, 'tenantId') + + expect(returnedTenantRecord).toBe(tenantRecord) + }) + }) + + describe('deleteTenantById', () => { + test('retrieves the tenant record and calls delete on the tenant repository', async () => { + const tenantRecord = new TenantRecord({ + id: 'tenant-id', + config: { + label: 'Test Tenant', + walletConfig: { + id: 'tenant-wallet-id', + key: 'tenant-wallet-key', + }, + }, + }) + mockFunction(tenantRepository.getById).mockResolvedValue(tenantRecord) + mockFunction(tenantRoutingRepository.findByQuery).mockResolvedValue([]) + + await tenantService.deleteTenantById(agentContext, 'tenant-id') + + expect(tenantRepository.delete).toHaveBeenCalledWith(agentContext, tenantRecord) + }) + + test('deletes associated tenant routing records', async () => { + const tenantRecord = new TenantRecord({ + id: 'tenant-id', + config: { + label: 'Test Tenant', + walletConfig: { + id: 'tenant-wallet-id', + key: 'tenant-wallet-key', + }, + }, + }) + const tenantRoutingRecords = [ + new TenantRoutingRecord({ + recipientKeyFingerprint: '1', + tenantId: 'tenant-id', + }), + new TenantRoutingRecord({ + recipientKeyFingerprint: '2', + tenantId: 'tenant-id', + }), + ] + + mockFunction(tenantRepository.getById).mockResolvedValue(tenantRecord) + mockFunction(tenantRoutingRepository.findByQuery).mockResolvedValue(tenantRoutingRecords) + + await tenantService.deleteTenantById(agentContext, 'tenant-id') + + expect(tenantRoutingRepository.findByQuery).toHaveBeenCalledWith(agentContext, { + tenantId: 'tenant-id', + }) + + expect(tenantRoutingRepository.delete).toHaveBeenCalledTimes(2) + expect(tenantRoutingRepository.delete).toHaveBeenNthCalledWith(1, agentContext, tenantRoutingRecords[0]) + expect(tenantRoutingRepository.delete).toHaveBeenNthCalledWith(2, agentContext, tenantRoutingRecords[1]) + }) + }) + + describe('findTenantRoutingRecordByRecipientKey', () => { + test('returns value from tenant routing repository findByRecipientKey', async () => { + const tenantRoutingRecord = jest.fn() as unknown as TenantRoutingRecord + mockFunction(tenantRoutingRepository.findByRecipientKey).mockResolvedValue(tenantRoutingRecord) + + const recipientKey = Key.fromFingerprint('z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') + const returnedTenantRoutingRecord = await tenantService.findTenantRoutingRecordByRecipientKey( + agentContext, + recipientKey + ) + + expect(tenantRoutingRepository.findByRecipientKey).toHaveBeenCalledWith(agentContext, recipientKey) + expect(returnedTenantRoutingRecord).toBe(tenantRoutingRecord) + }) + }) + + describe('addTenantRoutingRecord', () => { + test('creates a tenant routing record and stores it in the tenant routing repository', async () => { + const recipientKey = Key.fromFingerprint('z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') + const tenantRoutingRecord = await tenantService.addTenantRoutingRecord(agentContext, 'tenant-id', recipientKey) + + expect(tenantRoutingRepository.save).toHaveBeenCalledWith(agentContext, tenantRoutingRecord) + expect(tenantRoutingRecord).toMatchObject({ + id: expect.any(String), + tenantId: 'tenant-id', + recipientKeyFingerprint: 'z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', + }) + }) + }) +}) diff --git a/packages/module-tenants/src/services/index.ts b/packages/module-tenants/src/services/index.ts new file mode 100644 index 0000000000..8f1c72138f --- /dev/null +++ b/packages/module-tenants/src/services/index.ts @@ -0,0 +1 @@ +export * from './TenantService' diff --git a/packages/module-tenants/tests/setup.ts b/packages/module-tenants/tests/setup.ts new file mode 100644 index 0000000000..3f425aaf8f --- /dev/null +++ b/packages/module-tenants/tests/setup.ts @@ -0,0 +1 @@ +import 'reflect-metadata' diff --git a/packages/module-tenants/tests/tenants.e2e.test.ts b/packages/module-tenants/tests/tenants.e2e.test.ts new file mode 100644 index 0000000000..c95c4c815d --- /dev/null +++ b/packages/module-tenants/tests/tenants.e2e.test.ts @@ -0,0 +1,201 @@ +import type { InitConfig } from '@aries-framework/core' + +import { Agent, DependencyManager } from '@aries-framework/core' +import { agentDependencies } from '@aries-framework/node' + +import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import testLogger from '../../core/tests/logger' +import { TenantsApi, TenantsModule } from '../src' + +jest.setTimeout(2000000) + +const agent1Config: InitConfig = { + label: 'Tenant Agent 1', + walletConfig: { + id: 'Wallet: tenants e2e agent 1', + key: 'Wallet: tenants e2e agent 1', + }, + logger: testLogger, + endpoints: ['rxjs:tenant-agent1'], + autoAcceptConnections: true, +} + +const agent2Config: InitConfig = { + label: 'Tenant Agent 2', + walletConfig: { + id: 'Wallet: tenants e2e agent 2', + key: 'Wallet: tenants e2e agent 2', + }, + logger: testLogger, + endpoints: ['rxjs:tenant-agent2'], + autoAcceptConnections: true, +} + +// Register tenant module. For now we need to create a custom dependency manager +// and register all plugins before initializing the agent. Later, we can add the module registration +// to the agent constructor. +const agent1DependencyManager = new DependencyManager() +agent1DependencyManager.registerModules(TenantsModule) + +const agent2DependencyManager = new DependencyManager() +agent2DependencyManager.registerModules(TenantsModule) + +// Create multi-tenant agents +const agent1 = new Agent(agent1Config, agentDependencies, agent1DependencyManager) +const agent2 = new Agent(agent2Config, agentDependencies, agent2DependencyManager) + +// Register inbound and outbound transports (so we can communicate with ourselves) +const agent1InboundTransport = new SubjectInboundTransport() +const agent2InboundTransport = new SubjectInboundTransport() + +agent1.registerInboundTransport(agent1InboundTransport) +agent2.registerInboundTransport(agent2InboundTransport) + +agent1.registerOutboundTransport( + new SubjectOutboundTransport({ + 'rxjs:tenant-agent1': agent1InboundTransport.ourSubject, + 'rxjs:tenant-agent2': agent2InboundTransport.ourSubject, + }) +) +agent2.registerOutboundTransport( + new SubjectOutboundTransport({ + 'rxjs:tenant-agent1': agent1InboundTransport.ourSubject, + 'rxjs:tenant-agent2': agent2InboundTransport.ourSubject, + }) +) + +const agent1TenantsApi = agent1.dependencyManager.resolve(TenantsApi) +const agent2TenantsApi = agent2.dependencyManager.resolve(TenantsApi) + +describe('Tenants E2E', () => { + beforeAll(async () => { + await agent1.initialize() + await agent2.initialize() + }) + + afterAll(async () => { + await agent1.wallet.delete() + await agent1.shutdown() + await agent2.wallet.delete() + await agent2.shutdown() + }) + + test('create get and delete a tenant', async () => { + // Create tenant + let tenantRecord1 = await agent1TenantsApi.createTenant({ + config: { + label: 'Tenant 1', + }, + }) + + // Retrieve tenant record from storage + tenantRecord1 = await agent1TenantsApi.getTenantById(tenantRecord1.id) + + // Get tenant agent + const tenantAgent = await agent1TenantsApi.getTenantAgent({ + tenantId: tenantRecord1.id, + }) + await tenantAgent.shutdown() + + // Delete tenant agent + await agent1TenantsApi.deleteTenantById(tenantRecord1.id) + + // Can not get tenant agent again + await expect(agent1TenantsApi.getTenantAgent({ tenantId: tenantRecord1.id })).rejects.toThrow( + `TenantRecord: record with id ${tenantRecord1.id} not found.` + ) + }) + + test('create a connection between two tenants within the same agent', async () => { + // Create tenants + const tenantRecord1 = await agent1TenantsApi.createTenant({ + config: { + label: 'Tenant 1', + }, + }) + const tenantRecord2 = await agent1TenantsApi.createTenant({ + config: { + label: 'Tenant 2', + }, + }) + + const tenantAgent1 = await agent1TenantsApi.getTenantAgent({ + tenantId: tenantRecord1.id, + }) + const tenantAgent2 = await agent1TenantsApi.getTenantAgent({ + tenantId: tenantRecord2.id, + }) + + // Create and receive oob invitation in scope of tenants + const outOfBandRecord = await tenantAgent1.oob.createInvitation() + const { connectionRecord: tenant2ConnectionRecord } = await tenantAgent2.oob.receiveInvitation( + outOfBandRecord.outOfBandInvitation + ) + + // Retrieve all oob records for the base and tenant agent, only the + // tenant agent should have a record. + const baseAgentOutOfBandRecords = await agent1.oob.getAll() + const tenantAgent1OutOfBandRecords = await tenantAgent1.oob.getAll() + const tenantAgent2OutOfBandRecords = await tenantAgent2.oob.getAll() + + expect(baseAgentOutOfBandRecords.length).toBe(0) + expect(tenantAgent1OutOfBandRecords.length).toBe(1) + expect(tenantAgent2OutOfBandRecords.length).toBe(1) + + if (!tenant2ConnectionRecord) throw new Error('Receive invitation did not return connection record') + await tenantAgent2.connections.returnWhenIsConnected(tenant2ConnectionRecord.id) + + // Find the connection record for the created oob invitation + const [connectionRecord] = await tenantAgent1.connections.findAllByOutOfBandId(outOfBandRecord.id) + await tenantAgent1.connections.returnWhenIsConnected(connectionRecord.id) + + await tenantAgent1.shutdown() + await tenantAgent1.shutdown() + + // Delete tenants (will also delete wallets) + await agent1TenantsApi.deleteTenantById(tenantAgent1.context.contextCorrelationId) + await agent1TenantsApi.deleteTenantById(tenantAgent2.context.contextCorrelationId) + }) + + test('create a connection between two tenants within different agents', async () => { + // Create tenants + const tenantRecord1 = await agent1TenantsApi.createTenant({ + config: { + label: 'Agent 1 Tenant 1', + }, + }) + const tenantAgent1 = await agent1TenantsApi.getTenantAgent({ + tenantId: tenantRecord1.id, + }) + + const tenantRecord2 = await agent2TenantsApi.createTenant({ + config: { + label: 'Agent 2 Tenant 1', + }, + }) + const tenantAgent2 = await agent2TenantsApi.getTenantAgent({ + tenantId: tenantRecord2.id, + }) + + // Create and receive oob invitation in scope of tenants + const outOfBandRecord = await tenantAgent1.oob.createInvitation() + const { connectionRecord: tenant2ConnectionRecord } = await tenantAgent2.oob.receiveInvitation( + outOfBandRecord.outOfBandInvitation + ) + + if (!tenant2ConnectionRecord) throw new Error('Receive invitation did not return connection record') + await tenantAgent2.connections.returnWhenIsConnected(tenant2ConnectionRecord.id) + + // Find the connection record for the created oob invitation + const [connectionRecord] = await tenantAgent1.connections.findAllByOutOfBandId(outOfBandRecord.id) + await tenantAgent1.connections.returnWhenIsConnected(connectionRecord.id) + + await tenantAgent1.shutdown() + await tenantAgent1.shutdown() + + // Delete tenants (will also delete wallets) + await agent1TenantsApi.deleteTenantById(tenantAgent1.context.contextCorrelationId) + await agent2TenantsApi.deleteTenantById(tenantAgent2.context.contextCorrelationId) + }) +}) diff --git a/packages/module-tenants/tsconfig.build.json b/packages/module-tenants/tsconfig.build.json new file mode 100644 index 0000000000..9c30e30bd2 --- /dev/null +++ b/packages/module-tenants/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.build.json", + + "compilerOptions": { + "outDir": "./build" + }, + + "include": ["src/**/*"] +} diff --git a/packages/module-tenants/tsconfig.json b/packages/module-tenants/tsconfig.json new file mode 100644 index 0000000000..46efe6f721 --- /dev/null +++ b/packages/module-tenants/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + } +} diff --git a/tests/transport/SubjectInboundTransport.ts b/tests/transport/SubjectInboundTransport.ts index 572784fb17..a9736a1bfb 100644 --- a/tests/transport/SubjectInboundTransport.ts +++ b/tests/transport/SubjectInboundTransport.ts @@ -1,7 +1,9 @@ import type { InboundTransport, Agent } from '../../packages/core/src' import type { TransportSession } from '../../packages/core/src/agent/TransportService' import type { EncryptedMessage } from '../../packages/core/src/types' -import type { Subject, Subscription } from 'rxjs' +import type { Subscription } from 'rxjs' + +import { Subject } from 'rxjs' import { MessageReceiver } from '../../packages/core/src' import { TransportService } from '../../packages/core/src/agent/TransportService' @@ -10,10 +12,10 @@ import { uuid } from '../../packages/core/src/utils/uuid' export type SubjectMessage = { message: EncryptedMessage; replySubject?: Subject } export class SubjectInboundTransport implements InboundTransport { - private ourSubject: Subject + public readonly ourSubject: Subject private subscription?: Subscription - public constructor(ourSubject: Subject) { + public constructor(ourSubject = new Subject()) { this.ourSubject = ourSubject } diff --git a/yarn.lock b/yarn.lock index 3d866296a0..371acdad4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3104,6 +3104,13 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async-mutex@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.3.2.tgz#1485eda5bda1b0ec7c8df1ac2e815757ad1831df" + integrity sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA== + dependencies: + tslib "^2.3.1" + async@^2.4.0: version "2.6.4" resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" @@ -10547,7 +10554,7 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0: +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== From adfa65b13152a980ba24b03082446e91d8ec5b37 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 13 Jul 2022 22:45:59 +0200 Subject: [PATCH 013/125] feat(tenants): tenant lifecycle (#942) * fix: make event listeners tenant aware * chore(deps): update tsyringe * feat: add agent context disposal * feat(tenants): with tenant agent method * test(tenants): add tests for session mutex * feat(tenants): use RAW key derivation * test(tenants): add e2e session tests * feat(tenants): destroy and end session Signed-off-by: Timo Glastra --- package.json | 2 +- packages/core/package.json | 2 +- packages/core/src/agent/Agent.ts | 7 +- packages/core/src/agent/BaseAgent.ts | 4 - packages/core/src/agent/Events.ts | 9 + packages/core/src/agent/MessageReceiver.ts | 17 +- .../core/src/agent/context/AgentContext.ts | 12 + .../src/agent/context/AgentContextProvider.ts | 7 + .../context/DefaultAgentContextProvider.ts | 11 + .../DefaultAgentContextProvider.test.ts | 19 ++ packages/core/src/index.ts | 2 +- .../connections/services/ConnectionService.ts | 2 + .../DiscoverFeaturesModule.ts | 3 +- .../core/src/modules/oob/OutOfBandModule.ts | 3 +- .../src/modules/routing/RecipientModule.ts | 7 + .../services/MediationRecipientService.ts | 3 +- .../core/src/plugins/DependencyManager.ts | 9 + packages/core/src/plugins/index.ts | 2 +- packages/core/src/wallet/IndyWallet.ts | 9 + packages/core/src/wallet/Wallet.ts | 3 +- packages/core/tests/mocks/MockWallet.ts | 4 + packages/module-tenants/package.json | 6 +- packages/module-tenants/src/TenantAgent.ts | 16 +- packages/module-tenants/src/TenantsApi.ts | 56 +++- .../module-tenants/src/TenantsApiOptions.ts | 3 + packages/module-tenants/src/TenantsModule.ts | 4 +- .../src/__tests__/TenantAgent.test.ts | 3 +- .../src/__tests__/TenantsApi.test.ts | 116 +++++++-- .../src/__tests__/TenantsModule.test.ts | 4 +- .../src/context/TenantAgentContextProvider.ts | 20 +- .../src/context/TenantSessionCoordinator.ts | 244 ++++++++++++------ .../src/context/TenantSessionMutex.ts | 104 ++++++++ .../TenantAgentContextProvider.test.ts | 42 +-- .../TenantSessionCoordinator.test.ts | 158 +++++++++++- .../__tests__/TenantSessionMutex.test.ts | 61 +++++ .../module-tenants/src/models/TenantConfig.ts | 2 +- ...enantService.ts => TenantRecordService.ts} | 5 +- .../services/__tests__/TenantService.test.ts | 22 +- packages/module-tenants/src/services/index.ts | 2 +- .../tests/tenant-sessions.e2e.test.ts | 90 +++++++ .../module-tenants/tests/tenants.e2e.test.ts | 34 ++- yarn.lock | 8 +- 42 files changed, 941 insertions(+), 196 deletions(-) create mode 100644 packages/module-tenants/src/context/TenantSessionMutex.ts create mode 100644 packages/module-tenants/src/context/__tests__/TenantSessionMutex.test.ts rename packages/module-tenants/src/services/{TenantService.ts => TenantRecordService.ts} (93%) create mode 100644 packages/module-tenants/tests/tenant-sessions.e2e.test.ts diff --git a/package.json b/package.json index 10fb8935e7..139ef64ae7 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "ts-jest": "^27.0.3", "ts-node": "^10.0.0", "tsconfig-paths": "^3.9.0", - "tsyringe": "^4.6.0", + "tsyringe": "^4.7.0", "typescript": "~4.3.0", "ws": "^7.4.6" }, diff --git a/packages/core/package.json b/packages/core/package.json index 12dc0a99c4..f38ce37605 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -49,7 +49,7 @@ "query-string": "^7.0.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", - "tsyringe": "^4.5.0", + "tsyringe": "^4.7.0", "uuid": "^8.3.2", "varint": "^6.0.0", "web-did-resolver": "^2.0.8" diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index b3d4a5b05e..a6c9601e7d 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -140,12 +140,10 @@ export class Agent extends BaseAgent { const transportPromises = allTransports.map((transport) => transport.stop()) await Promise.all(transportPromises) - // close wallet if still initialized if (this.wallet.isInitialized) { await this.wallet.close() } - await super.shutdown() this._isInitialized = false } @@ -205,7 +203,10 @@ export class Agent extends BaseAgent { // Bind the default agent context to the container for use in modules etc. dependencyManager.registerInstance( AgentContext, - new AgentContext({ dependencyManager, contextCorrelationId: 'default' }) + new AgentContext({ + dependencyManager, + contextCorrelationId: 'default', + }) ) // If no agent context provider has been registered we use the default agent context provider. diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index 67c32965b5..34115dc2a5 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -148,10 +148,6 @@ export abstract class BaseAgent { } } - public async shutdown() { - // No logic required at the moment - } - public get publicDid() { return this.agentContext.wallet.publicDid } diff --git a/packages/core/src/agent/Events.ts b/packages/core/src/agent/Events.ts index ad4faf8ed2..3688479795 100644 --- a/packages/core/src/agent/Events.ts +++ b/packages/core/src/agent/Events.ts @@ -1,5 +1,14 @@ import type { ConnectionRecord } from '../modules/connections' import type { AgentMessage } from './AgentMessage' +import type { Observable } from 'rxjs' + +import { filter } from 'rxjs' + +export function filterContextCorrelationId(contextCorrelationId: string) { + return (source: Observable) => { + return source.pipe(filter((event) => event.metadata.contextCorrelationId === contextCorrelationId)) + } +} export enum AgentEventTypes { AgentMessageReceived = 'AgentMessageReceived', diff --git a/packages/core/src/agent/MessageReceiver.ts b/packages/core/src/agent/MessageReceiver.ts index 5fbef72a0f..88b052cc42 100644 --- a/packages/core/src/agent/MessageReceiver.ts +++ b/packages/core/src/agent/MessageReceiver.ts @@ -78,12 +78,17 @@ export class MessageReceiver { contextCorrelationId, }) - if (this.isEncryptedMessage(inboundMessage)) { - await this.receiveEncryptedMessage(agentContext, inboundMessage as EncryptedMessage, session) - } else if (this.isPlaintextMessage(inboundMessage)) { - await this.receivePlaintextMessage(agentContext, inboundMessage, connection) - } else { - throw new AriesFrameworkError('Unable to parse incoming message: unrecognized format') + try { + if (this.isEncryptedMessage(inboundMessage)) { + await this.receiveEncryptedMessage(agentContext, inboundMessage as EncryptedMessage, session) + } else if (this.isPlaintextMessage(inboundMessage)) { + await this.receivePlaintextMessage(agentContext, inboundMessage, connection) + } else { + throw new AriesFrameworkError('Unable to parse incoming message: unrecognized format') + } + } finally { + // Always end the session for the agent context after handling the message. + await agentContext.endSession() } } diff --git a/packages/core/src/agent/context/AgentContext.ts b/packages/core/src/agent/context/AgentContext.ts index 9c3e000c1b..714a5933a5 100644 --- a/packages/core/src/agent/context/AgentContext.ts +++ b/packages/core/src/agent/context/AgentContext.ts @@ -1,5 +1,6 @@ import type { DependencyManager } from '../../plugins' import type { Wallet } from '../../wallet' +import type { AgentContextProvider } from './AgentContextProvider' import { InjectionSymbols } from '../../constants' import { AgentConfig } from '../AgentConfig' @@ -47,6 +48,17 @@ export class AgentContext { return this.dependencyManager.resolve(InjectionSymbols.Wallet) } + /** + * End session the current agent context + */ + public async endSession() { + const agentContextProvider = this.dependencyManager.resolve( + InjectionSymbols.AgentContextProvider + ) + + await agentContextProvider.endSessionForAgentContext(this) + } + public toJSON() { return { contextCorrelationId: this.contextCorrelationId, diff --git a/packages/core/src/agent/context/AgentContextProvider.ts b/packages/core/src/agent/context/AgentContextProvider.ts index c9f1c81296..14ba9984c5 100644 --- a/packages/core/src/agent/context/AgentContextProvider.ts +++ b/packages/core/src/agent/context/AgentContextProvider.ts @@ -20,4 +20,11 @@ export interface AgentContextProvider { * for the specified contextCorrelationId. */ getAgentContextForContextCorrelationId(contextCorrelationId: string): Promise + + /** + * End sessions for the provided agent context. This does not necessarily mean the wallet will be closed or the dependency manager will + * be disposed, it is to inform the agent context provider this session for the agent context is no longer in use. This should only be + * called once for every session and the agent context MUST not be used after this method is called. + */ + endSessionForAgentContext(agentContext: AgentContext): Promise } diff --git a/packages/core/src/agent/context/DefaultAgentContextProvider.ts b/packages/core/src/agent/context/DefaultAgentContextProvider.ts index 87df3fb03d..c4b6d9e18d 100644 --- a/packages/core/src/agent/context/DefaultAgentContextProvider.ts +++ b/packages/core/src/agent/context/DefaultAgentContextProvider.ts @@ -41,4 +41,15 @@ export class DefaultAgentContextProvider implements AgentContextProvider { return this.agentContext } + + public async endSessionForAgentContext(agentContext: AgentContext) { + // Throw an error if the context correlation id does not match to prevent misuse. + if (agentContext.contextCorrelationId !== this.agentContext.contextCorrelationId) { + throw new AriesFrameworkError( + `Could not end session for agent context with contextCorrelationId '${agentContext.contextCorrelationId}'. Only contextCorrelationId '${this.agentContext.contextCorrelationId}' is provided by this provider.` + ) + } + + // We won't dispose the agent context as we don't keep track of the total number of sessions for the root agent context.65 + } } diff --git a/packages/core/src/agent/context/__tests__/DefaultAgentContextProvider.test.ts b/packages/core/src/agent/context/__tests__/DefaultAgentContextProvider.test.ts index 510848d00f..05019a0e76 100644 --- a/packages/core/src/agent/context/__tests__/DefaultAgentContextProvider.test.ts +++ b/packages/core/src/agent/context/__tests__/DefaultAgentContextProvider.test.ts @@ -43,4 +43,23 @@ describe('DefaultAgentContextProvider', () => { ) }) }) + + describe('endSessionForAgentContext()', () => { + test('resolves when the correct agent context is passed', async () => { + const agentContextProvider: AgentContextProvider = new DefaultAgentContextProvider(agentContext) + + await expect(agentContextProvider.endSessionForAgentContext(agentContext)).resolves.toBeUndefined() + }) + + test('throws an error if the contextCorrelationId does not match with the contextCorrelationId from the constructor agent context', async () => { + const agentContextProvider: AgentContextProvider = new DefaultAgentContextProvider(agentContext) + const agentContext2 = getAgentContext({ + contextCorrelationId: 'mock2', + }) + + await expect(agentContextProvider.endSessionForAgentContext(agentContext2)).rejects.toThrowError( + `Could not end session for agent context with contextCorrelationId 'mock2'. Only contextCorrelationId 'mock' is provided by this provider.` + ) + }) + }) }) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 234fc8b9da..e1967d1ef3 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -14,7 +14,7 @@ export { Dispatcher } from './agent/Dispatcher' export { MessageSender } from './agent/MessageSender' export type { AgentDependencies } from './agent/AgentDependencies' export type { InitConfig, OutboundPackage, EncryptedMessage, WalletConfig } from './types' -export { KeyDerivationMethod, DidCommMimeType } from './types' +export { DidCommMimeType, KeyDerivationMethod } from './types' export type { FileSystem } from './storage/FileSystem' export * from './storage/BaseRecord' export { InMemoryMessageRepository } from './storage/InMemoryMessageRepository' diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index 8af388010c..6b26c59a15 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -12,6 +12,7 @@ import { firstValueFrom, ReplaySubject } from 'rxjs' import { first, map, timeout } from 'rxjs/operators' import { EventEmitter } from '../../../agent/EventEmitter' +import { filterContextCorrelationId } from '../../../agent/Events' import { InjectionSymbols } from '../../../constants' import { Key } from '../../../crypto' import { signData, unpackAndVerifySignatureDecorator } from '../../../decorators/signature/SignatureDecoratorUtils' @@ -749,6 +750,7 @@ export class ConnectionService { observable .pipe( + filterContextCorrelationId(agentContext.contextCorrelationId), map((e) => e.payload.connectionRecord), first(isConnected), // Do not wait for longer than specified timeout timeout(timeoutMs) diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts index f557d186dd..76b288e846 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts @@ -8,7 +8,7 @@ import { catchError, filter, map, takeUntil, timeout } from 'rxjs/operators' import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { EventEmitter } from '../../agent/EventEmitter' -import { AgentEventTypes } from '../../agent/Events' +import { filterContextCorrelationId, AgentEventTypes } from '../../agent/Events' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' import { InjectionSymbols } from '../../constants' @@ -58,6 +58,7 @@ export class DiscoverFeaturesModule { .pipe( // Stop when the agent shuts down takeUntil(this.stop$), + filterContextCorrelationId(this.agentContext.contextCorrelationId), // filter by connection id and query disclose message type filter( (e) => diff --git a/packages/core/src/modules/oob/OutOfBandModule.ts b/packages/core/src/modules/oob/OutOfBandModule.ts index 8d6cce08f1..c2f365ae07 100644 --- a/packages/core/src/modules/oob/OutOfBandModule.ts +++ b/packages/core/src/modules/oob/OutOfBandModule.ts @@ -12,7 +12,7 @@ import { catchError, EmptyError, first, firstValueFrom, map, of, timeout } from import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { EventEmitter } from '../../agent/EventEmitter' -import { AgentEventTypes } from '../../agent/Events' +import { filterContextCorrelationId, AgentEventTypes } from '../../agent/Events' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' import { InjectionSymbols } from '../../constants' @@ -681,6 +681,7 @@ export class OutOfBandModule { const reuseAcceptedEventPromise = firstValueFrom( this.eventEmitter.observable(OutOfBandEventTypes.HandshakeReused).pipe( + filterContextCorrelationId(this.agentContext.contextCorrelationId), // Find the first reuse event where the handshake reuse accepted matches the reuse message thread // TODO: Should we store the reuse state? Maybe we can keep it in memory for now first( diff --git a/packages/core/src/modules/routing/RecipientModule.ts b/packages/core/src/modules/routing/RecipientModule.ts index c1a60bd472..76115a165c 100644 --- a/packages/core/src/modules/routing/RecipientModule.ts +++ b/packages/core/src/modules/routing/RecipientModule.ts @@ -12,6 +12,7 @@ import { delayWhen, filter, first, takeUntil, tap, throttleTime, timeout } from import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { EventEmitter } from '../../agent/EventEmitter' +import { filterContextCorrelationId } from '../../agent/Events' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' import { InjectionSymbols } from '../../constants' @@ -145,6 +146,11 @@ export class RecipientModule { private async openWebSocketAndPickUp(mediator: MediationRecord, pickupStrategy: MediatorPickupStrategy) { let interval = 50 + // FIXME: this won't work for tenant agents created by the tenants module as the agent context session + // could be closed. I'm not sure we want to support this as you probably don't want different tenants opening + // various websocket connections to mediators. However we should look at throwing an error or making sure + // it is not possible to use the mediation module with tenant agents. + // Listens to Outbound websocket closed events and will reopen the websocket connection // in a recursive back off strategy if it matches the following criteria: // - Agent is not shutdown @@ -335,6 +341,7 @@ export class RecipientModule { // Apply required filters to observable stream subscribe to replay subject observable .pipe( + filterContextCorrelationId(this.agentContext.contextCorrelationId), // Only take event for current mediation record filter((event) => event.payload.mediationRecord.id === mediationRecord.id), // Only take event for previous state requested, current state granted diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index a345231d9e..2258afe845 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -14,7 +14,7 @@ import { firstValueFrom, ReplaySubject } from 'rxjs' import { filter, first, timeout } from 'rxjs/operators' import { EventEmitter } from '../../../agent/EventEmitter' -import { AgentEventTypes } from '../../../agent/Events' +import { filterContextCorrelationId, AgentEventTypes } from '../../../agent/Events' import { MessageSender } from '../../../agent/MessageSender' import { createOutboundMessage } from '../../../agent/helpers' import { Key, KeyType } from '../../../crypto' @@ -157,6 +157,7 @@ export class MediationRecipientService { // Apply required filters to observable stream and create promise to subscribe to observable observable .pipe( + filterContextCorrelationId(agentContext.contextCorrelationId), // Only take event for current mediation record filter((event) => mediationRecord.id === event.payload.mediationRecord.id), // Only wait for first event that matches the criteria diff --git a/packages/core/src/plugins/DependencyManager.ts b/packages/core/src/plugins/DependencyManager.ts index 2854ed0af0..a785ccf1e1 100644 --- a/packages/core/src/plugins/DependencyManager.ts +++ b/packages/core/src/plugins/DependencyManager.ts @@ -47,6 +47,15 @@ export class DependencyManager { else this.container.register(token, token, { lifecycle: Lifecycle.ContainerScoped }) } + /** + * Dispose the dependency manager. Calls `.dispose()` on all instances that implement the `Disposable` interface and have + * been constructed by the `DependencyManager`. This means all instances registered using `registerInstance` won't have the + * dispose method called. + */ + public async dispose() { + await this.container.dispose() + } + public createChild() { return new DependencyManager(this.container.createChildContainer()) } diff --git a/packages/core/src/plugins/index.ts b/packages/core/src/plugins/index.ts index 4e9cac645c..27b41e4d10 100644 --- a/packages/core/src/plugins/index.ts +++ b/packages/core/src/plugins/index.ts @@ -1,3 +1,3 @@ export * from './DependencyManager' export * from './Module' -export { inject, injectable } from 'tsyringe' +export { inject, injectable, Disposable } from 'tsyringe' diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts index 92870f7d70..493cfd4707 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/core/src/wallet/IndyWallet.ts @@ -82,6 +82,15 @@ export class IndyWallet implements Wallet { return this.walletConfig.id } + /** + * Dispose method is called when an agent context is disposed. + */ + public async dispose() { + if (this.isInitialized) { + await this.close() + } + } + private walletStorageConfig(walletConfig: WalletConfig): Indy.WalletConfig { const walletStorageConfig: Indy.WalletConfig = { id: walletConfig.id, diff --git a/packages/core/src/wallet/Wallet.ts b/packages/core/src/wallet/Wallet.ts index 102c25e213..57f128830d 100644 --- a/packages/core/src/wallet/Wallet.ts +++ b/packages/core/src/wallet/Wallet.ts @@ -1,4 +1,5 @@ import type { Key, KeyType } from '../crypto' +import type { Disposable } from '../plugins' import type { EncryptedMessage, WalletConfig, @@ -8,7 +9,7 @@ import type { } from '../types' import type { Buffer } from '../utils/buffer' -export interface Wallet { +export interface Wallet extends Disposable { publicDid: DidInfo | undefined isInitialized: boolean isProvisioned: boolean diff --git a/packages/core/tests/mocks/MockWallet.ts b/packages/core/tests/mocks/MockWallet.ts index 864454edb6..0063fdef9d 100644 --- a/packages/core/tests/mocks/MockWallet.ts +++ b/packages/core/tests/mocks/MockWallet.ts @@ -75,4 +75,8 @@ export class MockWallet implements Wallet { public generateWalletKey(): Promise { throw new Error('Method not implemented.') } + + public dispose() { + // Nothing to do here + } } diff --git a/packages/module-tenants/package.json b/packages/module-tenants/package.json index dae63f0858..0a26b432f7 100644 --- a/packages/module-tenants/package.json +++ b/packages/module-tenants/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/module-tenants", "main": "build/index", "types": "build/index", - "version": "0.2.0", + "version": "0.2.2", "files": [ "build" ], @@ -24,11 +24,11 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.0", + "@aries-framework/core": "0.2.2", "async-mutex": "^0.3.2" }, "devDependencies": { - "@aries-framework/node": "0.2.0", + "@aries-framework/node": "0.2.2", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/module-tenants/src/TenantAgent.ts b/packages/module-tenants/src/TenantAgent.ts index fbeb5f9723..20d0e61eb7 100644 --- a/packages/module-tenants/src/TenantAgent.ts +++ b/packages/module-tenants/src/TenantAgent.ts @@ -1,20 +1,30 @@ import type { AgentContext } from '@aries-framework/core' -import { BaseAgent } from '@aries-framework/core' +import { AriesFrameworkError, BaseAgent } from '@aries-framework/core' export class TenantAgent extends BaseAgent { + private sessionHasEnded = false + public constructor(agentContext: AgentContext) { super(agentContext.config, agentContext.dependencyManager) } public async initialize() { + if (this.sessionHasEnded) { + throw new AriesFrameworkError("Can't initialize agent after tenant sessions has been ended.") + } + await super.initialize() this._isInitialized = true } - public async shutdown() { - await super.shutdown() + public async endSession() { + this.logger.trace( + `Ending session for agent context with contextCorrelationId '${this.agentContext.contextCorrelationId}'` + ) + await this.agentContext.endSession() this._isInitialized = false + this.sessionHasEnded = true } protected registerDependencies() { diff --git a/packages/module-tenants/src/TenantsApi.ts b/packages/module-tenants/src/TenantsApi.ts index 526f7c66d9..901c21e35f 100644 --- a/packages/module-tenants/src/TenantsApi.ts +++ b/packages/module-tenants/src/TenantsApi.ts @@ -1,56 +1,88 @@ -import type { CreateTenantOptions, GetTenantAgentOptions } from './TenantsApiOptions' +import type { CreateTenantOptions, GetTenantAgentOptions, WithTenantAgentCallback } from './TenantsApiOptions' -import { AgentContext, inject, InjectionSymbols, AgentContextProvider, injectable } from '@aries-framework/core' +import { AgentContext, inject, InjectionSymbols, AgentContextProvider, injectable, Logger } from '@aries-framework/core' import { TenantAgent } from './TenantAgent' -import { TenantService } from './services' +import { TenantRecordService } from './services' @injectable() export class TenantsApi { private agentContext: AgentContext - private tenantService: TenantService + private tenantRecordService: TenantRecordService private agentContextProvider: AgentContextProvider + private logger: Logger public constructor( - tenantService: TenantService, + tenantRecordService: TenantRecordService, agentContext: AgentContext, - @inject(InjectionSymbols.AgentContextProvider) agentContextProvider: AgentContextProvider + @inject(InjectionSymbols.AgentContextProvider) agentContextProvider: AgentContextProvider, + @inject(InjectionSymbols.Logger) logger: Logger ) { - this.tenantService = tenantService + this.tenantRecordService = tenantRecordService this.agentContext = agentContext this.agentContextProvider = agentContextProvider + this.logger = logger } public async getTenantAgent({ tenantId }: GetTenantAgentOptions): Promise { + this.logger.debug(`Getting tenant agent for tenant '${tenantId}'`) const tenantContext = await this.agentContextProvider.getAgentContextForContextCorrelationId(tenantId) + this.logger.trace(`Got tenant context for tenant '${tenantId}'`) const tenantAgent = new TenantAgent(tenantContext) await tenantAgent.initialize() + this.logger.trace(`Initializing tenant agent for tenant '${tenantId}'`) return tenantAgent } + public async withTenantAgent( + options: GetTenantAgentOptions, + withTenantAgentCallback: WithTenantAgentCallback + ): Promise { + this.logger.debug(`Getting tenant agent for tenant '${options.tenantId}' in with tenant agent callback`) + const tenantAgent = await this.getTenantAgent(options) + + try { + this.logger.debug(`Calling tenant agent callback for tenant '${options.tenantId}'`) + await withTenantAgentCallback(tenantAgent) + } catch (error) { + this.logger.error(`Error in tenant agent callback for tenant '${options.tenantId}'`, { error }) + throw error + } finally { + this.logger.debug(`Ending tenant agent session for tenant '${options.tenantId}'`) + await tenantAgent.endSession() + } + } + public async createTenant(options: CreateTenantOptions) { - const tenantRecord = await this.tenantService.createTenant(this.agentContext, options.config) + this.logger.debug(`Creating tenant with label ${options.config.label}`) + const tenantRecord = await this.tenantRecordService.createTenant(this.agentContext, options.config) // This initializes the tenant agent, creates the wallet etc... const tenantAgent = await this.getTenantAgent({ tenantId: tenantRecord.id }) - await tenantAgent.shutdown() + await tenantAgent.endSession() + + this.logger.info(`Successfully created tenant '${tenantRecord.id}'`) return tenantRecord } public async getTenantById(tenantId: string) { - return this.tenantService.getTenantById(this.agentContext, tenantId) + this.logger.debug(`Getting tenant by id '${tenantId}'`) + return this.tenantRecordService.getTenantById(this.agentContext, tenantId) } public async deleteTenantById(tenantId: string) { + this.logger.debug(`Deleting tenant by id '${tenantId}'`) // TODO: force remove context from the context provider (or session manager) const tenantAgent = await this.getTenantAgent({ tenantId }) + this.logger.trace(`Deleting wallet for tenant '${tenantId}'`) await tenantAgent.wallet.delete() - await tenantAgent.shutdown() + this.logger.trace(`Shutting down agent for tenant '${tenantId}'`) + await tenantAgent.endSession() - return this.tenantService.deleteTenantById(this.agentContext, tenantId) + return this.tenantRecordService.deleteTenantById(this.agentContext, tenantId) } } diff --git a/packages/module-tenants/src/TenantsApiOptions.ts b/packages/module-tenants/src/TenantsApiOptions.ts index df9fa10324..12b01a16b8 100644 --- a/packages/module-tenants/src/TenantsApiOptions.ts +++ b/packages/module-tenants/src/TenantsApiOptions.ts @@ -1,9 +1,12 @@ +import type { TenantAgent } from './TenantAgent' import type { TenantConfig } from './models/TenantConfig' export interface GetTenantAgentOptions { tenantId: string } +export type WithTenantAgentCallback = (tenantAgent: TenantAgent) => Promise + export interface CreateTenantOptions { config: Omit } diff --git a/packages/module-tenants/src/TenantsModule.ts b/packages/module-tenants/src/TenantsModule.ts index 72545a8382..5dac760487 100644 --- a/packages/module-tenants/src/TenantsModule.ts +++ b/packages/module-tenants/src/TenantsModule.ts @@ -6,7 +6,7 @@ import { TenantsApi } from './TenantsApi' import { TenantAgentContextProvider } from './context/TenantAgentContextProvider' import { TenantSessionCoordinator } from './context/TenantSessionCoordinator' import { TenantRepository, TenantRoutingRepository } from './repository' -import { TenantService } from './services' +import { TenantRecordService } from './services' @module() export class TenantsModule { @@ -19,7 +19,7 @@ export class TenantsModule { dependencyManager.registerSingleton(TenantsApi) // Services - dependencyManager.registerSingleton(TenantService) + dependencyManager.registerSingleton(TenantRecordService) // Repositories dependencyManager.registerSingleton(TenantRepository) diff --git a/packages/module-tenants/src/__tests__/TenantAgent.test.ts b/packages/module-tenants/src/__tests__/TenantAgent.test.ts index ee97bd4b90..901afd77a0 100644 --- a/packages/module-tenants/src/__tests__/TenantAgent.test.ts +++ b/packages/module-tenants/src/__tests__/TenantAgent.test.ts @@ -1,4 +1,4 @@ -import { Agent } from '@aries-framework/core' +import { Agent, AgentContext } from '@aries-framework/core' import { agentDependencies, getAgentConfig, getAgentContext } from '../../../core/tests/helpers' import { TenantAgent } from '../TenantAgent' @@ -22,6 +22,7 @@ describe('TenantAgent', () => { agentConfig: getAgentConfig('TenantAgent'), dependencyManager: tenantDependencyManager, }) + tenantDependencyManager.registerInstance(AgentContext, agentContext) const tenantAgent = new TenantAgent(agentContext) diff --git a/packages/module-tenants/src/__tests__/TenantsApi.test.ts b/packages/module-tenants/src/__tests__/TenantsApi.test.ts index 8fe48593d7..3650c64509 100644 --- a/packages/module-tenants/src/__tests__/TenantsApi.test.ts +++ b/packages/module-tenants/src/__tests__/TenantsApi.test.ts @@ -1,24 +1,25 @@ -import { Agent, AgentContext } from '@aries-framework/core' +import { Agent, AgentContext, InjectionSymbols } from '@aries-framework/core' import { agentDependencies, getAgentConfig, getAgentContext, mockFunction } from '../../../core/tests/helpers' import { TenantAgent } from '../TenantAgent' import { TenantsApi } from '../TenantsApi' import { TenantAgentContextProvider } from '../context/TenantAgentContextProvider' import { TenantRecord } from '../repository' -import { TenantService } from '../services/TenantService' +import { TenantRecordService } from '../services/TenantRecordService' -jest.mock('../services/TenantService') -const TenantServiceMock = TenantService as jest.Mock +jest.mock('../services/TenantRecordService') +const TenantRecordServiceMock = TenantRecordService as jest.Mock jest.mock('../context/TenantAgentContextProvider') const AgentContextProviderMock = TenantAgentContextProvider as jest.Mock -const tenantService = new TenantServiceMock() +const tenantRecordService = new TenantRecordServiceMock() const agentContextProvider = new AgentContextProviderMock() const agentConfig = getAgentConfig('TenantsApi') const rootAgent = new Agent(agentConfig, agentDependencies) +rootAgent.dependencyManager.registerInstance(InjectionSymbols.AgentContextProvider, agentContextProvider) -const tenantsApi = new TenantsApi(tenantService, rootAgent.context, agentContextProvider) +const tenantsApi = new TenantsApi(tenantRecordService, rootAgent.context, agentContextProvider, agentConfig.logger) describe('TenantsApi', () => { describe('getTenantAgent', () => { @@ -52,7 +53,90 @@ describe('TenantsApi', () => { expect(tenantAgent.context).toBe(tenantAgentContext) await tenantAgent.wallet.delete() - await tenantAgent.shutdown() + await tenantAgent.endSession() + }) + }) + + describe('withTenantAgent', () => { + test('gets context from agent context provider and initializes tenant agent instance', async () => { + expect.assertions(6) + + const tenantDependencyManager = rootAgent.dependencyManager.createChild() + const tenantAgentContext = getAgentContext({ + contextCorrelationId: 'tenant-id', + dependencyManager: tenantDependencyManager, + agentConfig: agentConfig.extend({ + label: 'tenant-agent', + walletConfig: { + id: 'Wallet: TenantsApi: tenant-id', + key: 'Wallet: TenantsApi: tenant-id', + }, + }), + }) + tenantDependencyManager.registerInstance(AgentContext, tenantAgentContext) + + mockFunction(agentContextProvider.getAgentContextForContextCorrelationId).mockResolvedValue(tenantAgentContext) + + let endSessionSpy: jest.SpyInstance | undefined = undefined + await tenantsApi.withTenantAgent({ tenantId: 'tenant-id' }, async (tenantAgent) => { + endSessionSpy = jest.spyOn(tenantAgent, 'endSession') + expect(tenantAgent.isInitialized).toBe(true) + expect(tenantAgent.wallet.walletConfig).toEqual({ + id: 'Wallet: TenantsApi: tenant-id', + key: 'Wallet: TenantsApi: tenant-id', + }) + + expect(agentContextProvider.getAgentContextForContextCorrelationId).toBeCalledWith('tenant-id') + expect(tenantAgent).toBeInstanceOf(TenantAgent) + expect(tenantAgent.context).toBe(tenantAgentContext) + + await tenantAgent.wallet.delete() + }) + + expect(endSessionSpy).toHaveBeenCalled() + }) + + test('endSession is called even if the tenant agent callback throws an error', async () => { + expect.assertions(7) + + const tenantDependencyManager = rootAgent.dependencyManager.createChild() + const tenantAgentContext = getAgentContext({ + contextCorrelationId: 'tenant-id', + dependencyManager: tenantDependencyManager, + agentConfig: agentConfig.extend({ + label: 'tenant-agent', + walletConfig: { + id: 'Wallet: TenantsApi: tenant-id', + key: 'Wallet: TenantsApi: tenant-id', + }, + }), + }) + tenantDependencyManager.registerInstance(AgentContext, tenantAgentContext) + + mockFunction(agentContextProvider.getAgentContextForContextCorrelationId).mockResolvedValue(tenantAgentContext) + + let endSessionSpy: jest.SpyInstance | undefined = undefined + await expect( + tenantsApi.withTenantAgent({ tenantId: 'tenant-id' }, async (tenantAgent) => { + endSessionSpy = jest.spyOn(tenantAgent, 'endSession') + expect(tenantAgent.isInitialized).toBe(true) + expect(tenantAgent.wallet.walletConfig).toEqual({ + id: 'Wallet: TenantsApi: tenant-id', + key: 'Wallet: TenantsApi: tenant-id', + }) + + expect(agentContextProvider.getAgentContextForContextCorrelationId).toBeCalledWith('tenant-id') + expect(tenantAgent).toBeInstanceOf(TenantAgent) + expect(tenantAgent.context).toBe(tenantAgentContext) + + await tenantAgent.wallet.delete() + + throw new Error('Uh oh something went wrong') + }) + ).rejects.toThrow('Uh oh something went wrong') + + // endSession should have been called + expect(endSessionSpy).toHaveBeenCalled() }) }) @@ -73,10 +157,10 @@ describe('TenantsApi', () => { wallet: { delete: jest.fn(), }, - shutdown: jest.fn(), + endSession: jest.fn(), } as unknown as TenantAgent - mockFunction(tenantService.createTenant).mockResolvedValue(tenantRecord) + mockFunction(tenantRecordService.createTenant).mockResolvedValue(tenantRecord) const getTenantAgentSpy = jest.spyOn(tenantsApi, 'getTenantAgent').mockResolvedValue(tenantAgentMock) const createdTenantRecord = await tenantsApi.createTenant({ @@ -87,8 +171,8 @@ describe('TenantsApi', () => { expect(getTenantAgentSpy).toHaveBeenCalledWith({ tenantId: 'tenant-id' }) expect(createdTenantRecord).toBe(tenantRecord) - expect(tenantAgentMock.shutdown).toHaveBeenCalled() - expect(tenantService.createTenant).toHaveBeenCalledWith(rootAgent.context, { + expect(tenantAgentMock.endSession).toHaveBeenCalled() + expect(tenantRecordService.createTenant).toHaveBeenCalledWith(rootAgent.context, { label: 'test', }) }) @@ -97,11 +181,11 @@ describe('TenantsApi', () => { describe('getTenantById', () => { test('calls get tenant by id on tenant service', async () => { const tenantRecord = jest.fn() as unknown as TenantRecord - mockFunction(tenantService.getTenantById).mockResolvedValue(tenantRecord) + mockFunction(tenantRecordService.getTenantById).mockResolvedValue(tenantRecord) const actualTenantRecord = await tenantsApi.getTenantById('tenant-id') - expect(tenantService.getTenantById).toHaveBeenCalledWith(rootAgent.context, 'tenant-id') + expect(tenantRecordService.getTenantById).toHaveBeenCalledWith(rootAgent.context, 'tenant-id') expect(actualTenantRecord).toBe(tenantRecord) }) }) @@ -112,7 +196,7 @@ describe('TenantsApi', () => { wallet: { delete: jest.fn(), }, - shutdown: jest.fn(), + endSession: jest.fn(), } as unknown as TenantAgent const getTenantAgentSpy = jest.spyOn(tenantsApi, 'getTenantAgent').mockResolvedValue(tenantAgentMock) @@ -120,8 +204,8 @@ describe('TenantsApi', () => { expect(getTenantAgentSpy).toHaveBeenCalledWith({ tenantId: 'tenant-id' }) expect(tenantAgentMock.wallet.delete).toHaveBeenCalled() - expect(tenantAgentMock.shutdown).toHaveBeenCalled() - expect(tenantService.deleteTenantById).toHaveBeenCalledWith(rootAgent.context, 'tenant-id') + expect(tenantAgentMock.endSession).toHaveBeenCalled() + expect(tenantRecordService.deleteTenantById).toHaveBeenCalledWith(rootAgent.context, 'tenant-id') }) }) }) diff --git a/packages/module-tenants/src/__tests__/TenantsModule.test.ts b/packages/module-tenants/src/__tests__/TenantsModule.test.ts index 0e815072a3..23529cb7f3 100644 --- a/packages/module-tenants/src/__tests__/TenantsModule.test.ts +++ b/packages/module-tenants/src/__tests__/TenantsModule.test.ts @@ -6,7 +6,7 @@ import { TenantsModule } from '../TenantsModule' import { TenantAgentContextProvider } from '../context/TenantAgentContextProvider' import { TenantSessionCoordinator } from '../context/TenantSessionCoordinator' import { TenantRepository, TenantRoutingRepository } from '../repository' -import { TenantService } from '../services' +import { TenantRecordService } from '../services' jest.mock('../../../core/src/plugins/DependencyManager') const DependencyManagerMock = DependencyManager as jest.Mock @@ -19,7 +19,7 @@ describe('TenantsModule', () => { expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(6) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(TenantsApi) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(TenantService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(TenantRecordService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(TenantRepository) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(TenantRoutingRepository) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith( diff --git a/packages/module-tenants/src/context/TenantAgentContextProvider.ts b/packages/module-tenants/src/context/TenantAgentContextProvider.ts index 8b32e1142d..9831ac3a23 100644 --- a/packages/module-tenants/src/context/TenantAgentContextProvider.ts +++ b/packages/module-tenants/src/context/TenantAgentContextProvider.ts @@ -16,26 +16,26 @@ import { isJsonObject, } from '@aries-framework/core' -import { TenantService } from '../services' +import { TenantRecordService } from '../services' import { TenantSessionCoordinator } from './TenantSessionCoordinator' @injectable() export class TenantAgentContextProvider implements AgentContextProvider { - private tenantService: TenantService + private tenantRecordService: TenantRecordService private rootAgentContext: AgentContext private eventEmitter: EventEmitter private logger: Logger private tenantSessionCoordinator: TenantSessionCoordinator public constructor( - tenantService: TenantService, + tenantRecordService: TenantRecordService, rootAgentContext: AgentContext, eventEmitter: EventEmitter, tenantSessionCoordinator: TenantSessionCoordinator, @inject(InjectionSymbols.Logger) logger: Logger ) { - this.tenantService = tenantService + this.tenantRecordService = tenantRecordService this.rootAgentContext = rootAgentContext this.eventEmitter = eventEmitter this.tenantSessionCoordinator = tenantSessionCoordinator @@ -47,7 +47,7 @@ export class TenantAgentContextProvider implements AgentContextProvider { public async getAgentContextForContextCorrelationId(tenantId: string) { // TODO: maybe we can look at not having to retrieve the tenant record if there's already a context available. - const tenantRecord = await this.tenantService.getTenantById(this.rootAgentContext, tenantId) + const tenantRecord = await this.tenantRecordService.getTenantById(this.rootAgentContext, tenantId) const agentContext = this.tenantSessionCoordinator.getContextForSession(tenantRecord) this.logger.debug(`Created tenant agent context for tenant '${tenantId}'`) @@ -73,7 +73,7 @@ export class TenantAgentContextProvider implements AgentContextProvider { // the first found recipient multiple times. This is however a case I've never seen before and will add quite some complexity // to resolve. I think we're fine to ignore this case for now. for (const recipientKey of recipientKeys) { - const tenantRoutingRecord = await this.tenantService.findTenantRoutingRecordByRecipientKey( + const tenantRoutingRecord = await this.tenantRecordService.findTenantRoutingRecordByRecipientKey( this.rootAgentContext, recipientKey ) @@ -101,6 +101,10 @@ export class TenantAgentContextProvider implements AgentContextProvider { return agentContext } + public async endSessionForAgentContext(agentContext: AgentContext) { + await this.tenantSessionCoordinator.endAgentContextSession(agentContext) + } + private getRecipientKeysFromEncryptedMessage(jwe: EncryptedMessage): Key[] { const jweProtected = JsonEncoder.fromBase64(jwe.protected) if (!Array.isArray(jweProtected.recipients)) return [] @@ -122,8 +126,8 @@ export class TenantAgentContextProvider implements AgentContextProvider { private async registerRecipientKeyForTenant(tenantId: string, recipientKey: Key) { this.logger.debug(`Registering recipient key ${recipientKey.fingerprint} for tenant ${tenantId}`) - const tenantRecord = await this.tenantService.getTenantById(this.rootAgentContext, tenantId) - await this.tenantService.addTenantRoutingRecord(this.rootAgentContext, tenantRecord.id, recipientKey) + const tenantRecord = await this.tenantRecordService.getTenantById(this.rootAgentContext, tenantId) + await this.tenantRecordService.addTenantRoutingRecord(this.rootAgentContext, tenantRecord.id, recipientKey) } private listenForRoutingKeyCreatedEvents() { diff --git a/packages/module-tenants/src/context/TenantSessionCoordinator.ts b/packages/module-tenants/src/context/TenantSessionCoordinator.ts index 2fe8097f86..fc4816afb0 100644 --- a/packages/module-tenants/src/context/TenantSessionCoordinator.ts +++ b/packages/module-tenants/src/context/TenantSessionCoordinator.ts @@ -1,117 +1,205 @@ import type { TenantRecord } from '../repository' - -import { AgentConfig, AgentContext, AriesFrameworkError, injectable, WalletModule } from '@aries-framework/core' -import { Mutex } from 'async-mutex' +import type { MutexInterface } from 'async-mutex' + +import { + AgentConfig, + AgentContext, + AriesFrameworkError, + inject, + injectable, + InjectionSymbols, + Logger, + WalletModule, +} from '@aries-framework/core' +import { Mutex, withTimeout } from 'async-mutex' + +import { TenantSessionMutex } from './TenantSessionMutex' /** * Coordinates all agent context instance for tenant sessions. * - * NOTE: the implementation in temporary and doesn't correctly handle the lifecycle of sessions, it's just an implementation to make - * multi-tenancy work. It will keep opening wallets over time, taking up more and more resources. The implementation will be improved in the near future. - * It does however handle race conditions on initialization of wallets (so two requests for the same tenant being processed in parallel) + * This class keeps a mapping of tenant ids (context correlation ids) to agent context sessions mapping. Each mapping contains the agent context, + * the current session count and a mutex for making operations against the session mapping (opening / closing an agent context). The mutex ensures + * we're not susceptible to race conditions where multiple calls to open/close an agent context are made at the same time. Even though JavaScript is + * single threaded, promises can introduce race conditions as one process can stop and another process can be picked up. + * + * NOTE: the implementation doesn't yet cache agent context objects after they aren't being used for any sessions anymore. This means if a wallet is being used + * often in a short time it will be opened/closed very often. This is an improvement to be made in the near future. */ @injectable() export class TenantSessionCoordinator { private rootAgentContext: AgentContext + private logger: Logger private tenantAgentContextMapping: TenantAgentContextMapping = {} + private sessionMutex: TenantSessionMutex - public constructor(rootAgentContext: AgentContext) { + public constructor(rootAgentContext: AgentContext, @inject(InjectionSymbols.Logger) logger: Logger) { this.rootAgentContext = rootAgentContext + this.logger = logger + + // TODO: we should make the timeout and the session limit configurable, but until we have the modularization in place with + // module specific config, it's not easy to do so. Keeping it hardcoded for now + this.sessionMutex = new TenantSessionMutex(this.logger, 10000, 1000) } - // FIXME: add timeouts to the lock acquire (to prevent deadlocks) + /** + * Get agent context to use for a session. If an agent context for this tenant does not exist yet + * it will create it and store it for later use. If the agent context does already exist it will + * be returned. + */ public async getContextForSession(tenantRecord: TenantRecord): Promise { - let tenantContextMapping = this.tenantAgentContextMapping[tenantRecord.id] + this.logger.debug(`Getting context for session with tenant '${tenantRecord.id}'`) - // TODO: we should probably create a new context (but with the same dependency manager / wallet) for each session. - // This way we can add a `.dispose()` on the agent context, which means that agent context isn't usable anymore. However - // the wallet won't be closed. + // Wait for a session to be available + await this.sessionMutex.acquireSession() - // If we already have a context with sessions in place return the context and increment - // the session count. - if (isTenantContextSessions(tenantContextMapping)) { - tenantContextMapping.sessionCount++ - return tenantContextMapping.agentContext - } + try { + return await this.mutexForTenant(tenantRecord.id).runExclusive(async () => { + this.logger.debug(`Acquired lock for tenant '${tenantRecord.id}' to get context`) + const tenantSessions = this.getTenantSessionsMapping(tenantRecord.id) - // TODO: look at semaphores to manage the total number of wallets - // If the context is currently being initialized, wait for it to complete. - else if (isTenantAgentContextInitializing(tenantContextMapping)) { - // Wait for the wallet to finish initializing, then try to - return await tenantContextMapping.mutex.runExclusive(() => { - tenantContextMapping = this.tenantAgentContextMapping[tenantRecord.id] - - // There should always be a context now, if this isn't the case we must error out - // TODO: handle the case where the previous initialization failed (the value is undefined) - // We can just open a new session in that case, but for now we'll ignore this flow - if (!isTenantContextSessions(tenantContextMapping)) { - throw new AriesFrameworkError('Tenant context is not ready yet') + // If we don't have an agent context already, create one and initialize it + if (!tenantSessions.agentContext) { + this.logger.debug(`No agent context has been initialized for tenant '${tenantRecord.id}', creating one`) + tenantSessions.agentContext = await this.createAgentContext(tenantRecord) } - tenantContextMapping.sessionCount++ - return tenantContextMapping.agentContext + // If we already have a context with sessions in place return the context and increment + // the session count. + tenantSessions.sessionCount++ + this.logger.debug( + `Increased agent context session count for tenant '${tenantRecord.id}' to ${tenantSessions.sessionCount}` + ) + return tenantSessions.agentContext }) + } catch (error) { + this.logger.debug( + `Releasing session because an error occurred while getting the context for tenant ${tenantRecord.id}`, + { + errorMessage: error.message, + } + ) + // If there was an error acquiring the session, we MUST release it, otherwise this will lead to deadlocks over time. + this.sessionMutex.releaseSession() + + // Re-throw error + throw error } - // No value for this tenant exists yet, initialize a new session. - else { - // Set a mutex on the agent context mapping so other requests can wait for it to be initialized. - const mutex = new Mutex() - this.tenantAgentContextMapping[tenantRecord.id] = { - mutex, - } + } - return await mutex.runExclusive(async () => { - const tenantDependencyManager = this.rootAgentContext.dependencyManager.createChild() - const tenantConfig = this.rootAgentContext.config.extend(tenantRecord.config) + /** + * End a session for the provided agent context. It will decrease the session count for the agent context. + * If the number of sessions is zero after the context for this session has been ended, the agent context will be closed. + */ + public async endAgentContextSession(agentContext: AgentContext): Promise { + this.logger.debug( + `Ending session for agent context with contextCorrelationId ${agentContext.contextCorrelationId}'` + ) + const hasTenantSessionMapping = this.hasTenantSessionMapping(agentContext.contextCorrelationId) + + // Custom handling for the root agent context. We don't keep track of the total number of sessions for the root + // agent context, and we always keep the dependency manager intact. + if (!hasTenantSessionMapping && agentContext.contextCorrelationId === this.rootAgentContext.contextCorrelationId) { + this.logger.debug('Ending session for root agent context. Not disposing dependency manager') + return + } - const agentContext = new AgentContext({ - contextCorrelationId: tenantRecord.id, - dependencyManager: tenantDependencyManager, - }) + // This should not happen + if (!hasTenantSessionMapping) { + this.logger.error( + `Unknown agent context with contextCorrelationId '${agentContext.contextCorrelationId}'. Cannot end session` + ) + throw new AriesFrameworkError( + `Unknown agent context with contextCorrelationId '${agentContext.contextCorrelationId}'. Cannot end session` + ) + } - tenantDependencyManager.registerInstance(AgentContext, agentContext) - tenantDependencyManager.registerInstance(AgentConfig, tenantConfig) + await this.mutexForTenant(agentContext.contextCorrelationId).runExclusive(async () => { + this.logger.debug(`Acquired lock for tenant '${agentContext.contextCorrelationId}' to end session context`) + const tenantSessions = this.getTenantSessionsMapping(agentContext.contextCorrelationId) - tenantContextMapping = { - agentContext, - sessionCount: 1, - } + // TODO: check if session count is already 0 + tenantSessions.sessionCount-- + this.logger.debug( + `Decreased agent context session count for tenant '${agentContext.contextCorrelationId}' to ${tenantSessions.sessionCount}` + ) + + if (tenantSessions.sessionCount <= 0 && tenantSessions.agentContext) { + await this.closeAgentContext(tenantSessions.agentContext) + delete this.tenantAgentContextMapping[agentContext.contextCorrelationId] + } + }) - // NOTE: we're using the wallet module here because that correctly handle creating if it doesn't exist yet - // and will also write the storage version to the storage, which is needed by the update assistant. We either - // need to move this out of the module, or just keep using the module here. - const walletModule = agentContext.dependencyManager.resolve(WalletModule) - await walletModule.initialize(tenantRecord.config.walletConfig) + // Release a session so new sessions can be acquired + this.sessionMutex.releaseSession() + } - this.tenantAgentContextMapping[tenantRecord.id] = tenantContextMapping + private hasTenantSessionMapping(tenantId: T): boolean { + return this.tenantAgentContextMapping[tenantId] !== undefined + } - return agentContext - }) + private getTenantSessionsMapping(tenantId: string): TenantContextSessions { + let tenantSessionMapping = this.tenantAgentContextMapping[tenantId] + if (tenantSessionMapping) return tenantSessionMapping + + tenantSessionMapping = { + sessionCount: 0, + mutex: withTimeout( + new Mutex(), + // TODO: we should make the timeout configurable. + // NOTE: It can take a while to create an indy wallet. We're using RAW key derivation which should + // be fast enough to not cause a problem. This wil also only be problem when the wallet is being created + // for the first time or being acquired while wallet initialization is in progress. + 1000, + new AriesFrameworkError( + `Error acquiring lock for tenant ${tenantId}. Wallet initialization or shutdown took too long.` + ) + ), } + this.tenantAgentContextMapping[tenantId] = tenantSessionMapping + + return tenantSessionMapping } -} -interface TenantContextSessions { - sessionCount: number - agentContext: AgentContext -} + private mutexForTenant(tenantId: string) { + const tenantSessions = this.getTenantSessionsMapping(tenantId) -interface TenantContextInitializing { - mutex: Mutex -} + return tenantSessions.mutex + } -export interface TenantAgentContextMapping { - [tenantId: string]: TenantContextSessions | TenantContextInitializing | undefined + private async createAgentContext(tenantRecord: TenantRecord) { + const tenantDependencyManager = this.rootAgentContext.dependencyManager.createChild() + const tenantConfig = this.rootAgentContext.config.extend(tenantRecord.config) + + const agentContext = new AgentContext({ + contextCorrelationId: tenantRecord.id, + dependencyManager: tenantDependencyManager, + }) + + tenantDependencyManager.registerInstance(AgentContext, agentContext) + tenantDependencyManager.registerInstance(AgentConfig, tenantConfig) + + // NOTE: we're using the wallet module here because that correctly handle creating if it doesn't exist yet + // and will also write the storage version to the storage, which is needed by the update assistant. We either + // need to move this out of the module, or just keep using the module here. + const walletModule = agentContext.dependencyManager.resolve(WalletModule) + await walletModule.initialize(tenantRecord.config.walletConfig) + + return agentContext + } + + private async closeAgentContext(agentContext: AgentContext) { + this.logger.debug(`Closing agent context for tenant '${agentContext.contextCorrelationId}'`) + await agentContext.dependencyManager.dispose() + } } -function isTenantAgentContextInitializing( - contextMapping: TenantContextSessions | TenantContextInitializing | undefined -): contextMapping is TenantContextInitializing { - return contextMapping !== undefined && (contextMapping as TenantContextInitializing).mutex !== undefined +interface TenantContextSessions { + sessionCount: number + agentContext?: AgentContext + mutex: MutexInterface } -function isTenantContextSessions( - contextMapping: TenantContextSessions | TenantContextInitializing | undefined -): contextMapping is TenantContextSessions { - return contextMapping !== undefined && (contextMapping as TenantContextSessions).sessionCount !== undefined +export interface TenantAgentContextMapping { + [tenantId: string]: TenantContextSessions | undefined } diff --git a/packages/module-tenants/src/context/TenantSessionMutex.ts b/packages/module-tenants/src/context/TenantSessionMutex.ts new file mode 100644 index 0000000000..7bfb8386ba --- /dev/null +++ b/packages/module-tenants/src/context/TenantSessionMutex.ts @@ -0,0 +1,104 @@ +import type { Logger } from '@aries-framework/core' +import type { MutexInterface } from 'async-mutex' + +import { AriesFrameworkError } from '@aries-framework/core' +import { withTimeout, Mutex } from 'async-mutex' + +/** + * Keep track of the total number of tenant sessions currently active. This doesn't actually manage the tenant sessions itself, or have anything to do with + * the agent context. It merely counts the current number of sessions, and provides a mutex to lock new sessions from being created once the maximum number + * of sessions has been created. Session that can't be required withing the specified sessionsAcquireTimeout will throw an error. + */ +export class TenantSessionMutex { + private _currentSessions = 0 + public readonly maxSessions = Infinity + private sessionMutex: MutexInterface + private logger: Logger + + public constructor(logger: Logger, maxSessions = Infinity, sessionAcquireTimeout: number) { + this.logger = logger + + this.maxSessions = maxSessions + // Session mutex, it can take at most sessionAcquireTimeout to acquire a session, otherwise it will fail with the error below + this.sessionMutex = withTimeout( + new Mutex(), + sessionAcquireTimeout, + new AriesFrameworkError(`Failed to acquire an agent context session within ${sessionAcquireTimeout}ms`) + ) + } + + /** + * Getter to retrieve the total number of current sessions. + */ + public get currentSessions() { + return this._currentSessions + } + + private set currentSessions(value: number) { + this._currentSessions = value + } + + /** + * Wait to acquire a session. Will use the session semaphore to keep total number of sessions limited. + * For each session that is acquired using this method, the sessions MUST be closed by calling `releaseSession`. + * Failing to do so can lead to deadlocks over time. + */ + public async acquireSession() { + // TODO: We should update this to be weighted + // This will allow to weight sessions for contexts that already exist lower than sessions + // for contexts that need to be created (new injection container, wallet session etc..) + // E.g. opening a context could weigh 5, adding sessions to it would be 1 for each + this.logger.debug('Acquiring tenant session') + + // If we're out of sessions, wait for one to be released. + if (this.sessionMutex.isLocked()) { + this.logger.debug('Session mutex is locked, waiting for it to unlock') + // FIXME: waitForUnlock doesn't work with withTimeout but provides a better API (would rather not acquire and lock) + // await this.sessionMutex.waitForUnlock() + // Workaround https://github.com/MatrixAI/js-async-locks/pull/3/files#diff-4ee6a7d91cb8428765713bc3045e1dda5d43214030657a9c04804e96d68778bfR46-R61 + await this.sessionMutex.acquire() + if (this.currentSessions < this.maxSessions) { + this.sessionMutex.release() + } + } + + this.logger.debug(`Increasing current session count to ${this.currentSessions + 1} (max: ${this.maxSessions})`) + // We have waited for the session to unlock, + this.currentSessions++ + + // If we reached the limit we should lock the session mutex + if (this.currentSessions >= this.maxSessions) { + this.logger.debug(`Reached max number of sessions ${this.maxSessions}, locking mutex`) + await this.sessionMutex.acquire() + } + + this.logger.debug(`Acquired tenant session (${this.currentSessions} / ${this.maxSessions})`) + } + + /** + * Release a session from the session mutex. If the total number of current sessions drops below + * the max number of sessions, the session mutex will be released so new sessions can be started. + */ + public releaseSession() { + this.logger.debug('Releasing tenant session') + + if (this.currentSessions > 0) { + this.logger.debug(`Decreasing current sessions to ${this.currentSessions - 1} (max: ${this.maxSessions})`) + this.currentSessions-- + } else { + this.logger.warn( + 'Total sessions is already at 0, and releasing a session should not happen in this case. Not decrementing current session count.' + ) + } + + // If the number of current sessions is lower than the max number of sessions we can release the mutex + if (this.sessionMutex.isLocked() && this.currentSessions < this.maxSessions) { + this.logger.debug( + `Releasing session mutex as number of current sessions ${this.currentSessions} is below max number of sessions ${this.maxSessions}` + ) + // Even though marked as deprecated, it is not actually deprecated and will be kept + // https://github.com/DirtyHairy/async-mutex/issues/50#issuecomment-1007785141 + this.sessionMutex.release() + } + } +} diff --git a/packages/module-tenants/src/context/__tests__/TenantAgentContextProvider.test.ts b/packages/module-tenants/src/context/__tests__/TenantAgentContextProvider.test.ts index 8b57626900..aa6f80cd3b 100644 --- a/packages/module-tenants/src/context/__tests__/TenantAgentContextProvider.test.ts +++ b/packages/module-tenants/src/context/__tests__/TenantAgentContextProvider.test.ts @@ -5,19 +5,19 @@ import { Key } from '@aries-framework/core' import { EventEmitter } from '../../../../core/src/agent/EventEmitter' import { getAgentConfig, getAgentContext, mockFunction } from '../../../../core/tests/helpers' import { TenantRecord, TenantRoutingRecord } from '../../repository' -import { TenantService } from '../../services/TenantService' +import { TenantRecordService } from '../../services/TenantRecordService' import { TenantAgentContextProvider } from '../TenantAgentContextProvider' import { TenantSessionCoordinator } from '../TenantSessionCoordinator' jest.mock('../../../../core/src/agent/EventEmitter') -jest.mock('../../services/TenantService') +jest.mock('../../services/TenantRecordService') jest.mock('../TenantSessionCoordinator') const EventEmitterMock = EventEmitter as jest.Mock -const TenantServiceMock = TenantService as jest.Mock +const TenantRecordServiceMock = TenantRecordService as jest.Mock const TenantSessionCoordinatorMock = TenantSessionCoordinator as jest.Mock -const tenantService = new TenantServiceMock() +const tenantRecordService = new TenantRecordServiceMock() const tenantSessionCoordinator = new TenantSessionCoordinatorMock() const rootAgentContext = getAgentContext() @@ -25,7 +25,7 @@ const agentConfig = getAgentConfig('TenantAgentContextProvider') const eventEmitter = new EventEmitterMock() const tenantAgentContextProvider = new TenantAgentContextProvider( - tenantService, + tenantRecordService, rootAgentContext, eventEmitter, tenantSessionCoordinator, @@ -61,12 +61,12 @@ describe('TenantAgentContextProvider', () => { const tenantAgentContext = jest.fn() as unknown as AgentContext - mockFunction(tenantService.getTenantById).mockResolvedValue(tenantRecord) + mockFunction(tenantRecordService.getTenantById).mockResolvedValue(tenantRecord) mockFunction(tenantSessionCoordinator.getContextForSession).mockResolvedValue(tenantAgentContext) const returnedAgentContext = await tenantAgentContextProvider.getAgentContextForContextCorrelationId('tenant1') - expect(tenantService.getTenantById).toHaveBeenCalledWith(rootAgentContext, 'tenant1') + expect(tenantRecordService.getTenantById).toHaveBeenCalledWith(rootAgentContext, 'tenant1') expect(tenantSessionCoordinator.getContextForSession).toHaveBeenCalledWith(tenantRecord) expect(returnedAgentContext).toBe(tenantAgentContext) }) @@ -87,7 +87,7 @@ describe('TenantAgentContextProvider', () => { const tenantAgentContext = jest.fn() as unknown as AgentContext - mockFunction(tenantService.getTenantById).mockResolvedValue(tenantRecord) + mockFunction(tenantRecordService.getTenantById).mockResolvedValue(tenantRecord) mockFunction(tenantSessionCoordinator.getContextForSession).mockResolvedValue(tenantAgentContext) const returnedAgentContext = await tenantAgentContextProvider.getContextForInboundMessage( @@ -95,15 +95,15 @@ describe('TenantAgentContextProvider', () => { { contextCorrelationId: 'tenant1' } ) - expect(tenantService.getTenantById).toHaveBeenCalledWith(rootAgentContext, 'tenant1') + expect(tenantRecordService.getTenantById).toHaveBeenCalledWith(rootAgentContext, 'tenant1') expect(tenantSessionCoordinator.getContextForSession).toHaveBeenCalledWith(tenantRecord) expect(returnedAgentContext).toBe(tenantAgentContext) - expect(tenantService.findTenantRoutingRecordByRecipientKey).not.toHaveBeenCalled() + expect(tenantRecordService.findTenantRoutingRecordByRecipientKey).not.toHaveBeenCalled() }) test('throws an error if not contextCorrelationId is provided and no tenant id could be extracted from the inbound message', async () => { // no routing records found - mockFunction(tenantService.findTenantRoutingRecordByRecipientKey).mockResolvedValue(null) + mockFunction(tenantRecordService.findTenantRoutingRecordByRecipientKey).mockResolvedValue(null) await expect(tenantAgentContextProvider.getContextForInboundMessage(inboundMessage)).rejects.toThrowError( "Couldn't determine tenant id for inbound message. Unable to create context" @@ -128,24 +128,34 @@ describe('TenantAgentContextProvider', () => { }) const tenantAgentContext = jest.fn() as unknown as AgentContext - mockFunction(tenantService.findTenantRoutingRecordByRecipientKey).mockResolvedValue(tenantRoutingRecord) + mockFunction(tenantRecordService.findTenantRoutingRecordByRecipientKey).mockResolvedValue(tenantRoutingRecord) - mockFunction(tenantService.getTenantById).mockResolvedValue(tenantRecord) + mockFunction(tenantRecordService.getTenantById).mockResolvedValue(tenantRecord) mockFunction(tenantSessionCoordinator.getContextForSession).mockResolvedValue(tenantAgentContext) const returnedAgentContext = await tenantAgentContextProvider.getContextForInboundMessage(inboundMessage) - expect(tenantService.getTenantById).toHaveBeenCalledWith(rootAgentContext, 'tenant1') + expect(tenantRecordService.getTenantById).toHaveBeenCalledWith(rootAgentContext, 'tenant1') expect(tenantSessionCoordinator.getContextForSession).toHaveBeenCalledWith(tenantRecord) expect(returnedAgentContext).toBe(tenantAgentContext) - expect(tenantService.findTenantRoutingRecordByRecipientKey).toHaveBeenCalledWith( + expect(tenantRecordService.findTenantRoutingRecordByRecipientKey).toHaveBeenCalledWith( rootAgentContext, expect.any(Key) ) - const actualKey = mockFunction(tenantService.findTenantRoutingRecordByRecipientKey).mock.calls[0][1] + const actualKey = mockFunction(tenantRecordService.findTenantRoutingRecordByRecipientKey).mock.calls[0][1] // Based on the recipient key from the inboundMessage protected header above expect(actualKey.fingerprint).toBe('z6MkkrCJLG5Mr8rqLXDksuWXPtAQfv95q7bHW7a6HqLLPtmt') }) }) + + describe('disposeAgentContext', () => { + test('calls disposeAgentContextSession on tenant session coordinator', async () => { + const tenantAgentContext = jest.fn() as unknown as AgentContext + + await tenantAgentContextProvider.endSessionForAgentContext(tenantAgentContext) + + expect(tenantSessionCoordinator.endAgentContextSession).toHaveBeenCalledWith(tenantAgentContext) + }) + }) }) diff --git a/packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts b/packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts index deaf159d1c..f3766cfcc7 100644 --- a/packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts +++ b/packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts @@ -1,12 +1,17 @@ import type { TenantAgentContextMapping } from '../TenantSessionCoordinator' -import type { AgentContext } from '@aries-framework/core' +import type { DependencyManager } from '@aries-framework/core' -import { WalletModule } from '@aries-framework/core' -import { Mutex } from 'async-mutex' +import { WalletModule, AgentContext, AgentConfig } from '@aries-framework/core' +import { Mutex, withTimeout } from 'async-mutex' import { getAgentConfig, getAgentContext, mockFunction } from '../../../../core/tests/helpers' +import testLogger from '../../../../core/tests/logger' import { TenantRecord } from '../../repository' import { TenantSessionCoordinator } from '../TenantSessionCoordinator' +import { TenantSessionMutex } from '../TenantSessionMutex' + +jest.mock('../TenantSessionMutex') +const TenantSessionMutexMock = TenantSessionMutex as jest.Mock // tenantAgentContextMapping is private, but we need to access it to properly test this class. Adding type override to // make sure we don't get a lot of type errors. @@ -24,9 +29,12 @@ const agentContext = getAgentContext({ agentContext.dependencyManager.registerInstance(WalletModule, wallet) const tenantSessionCoordinator = new TenantSessionCoordinator( - agentContext + agentContext, + testLogger ) as unknown as PublicTenantAgentContextMapping +const tenantSessionMutexMock = TenantSessionMutexMock.mock.instances[0] + describe('TenantSessionCoordinator', () => { afterEach(() => { tenantSessionCoordinator.tenantAgentContextMapping = {} @@ -39,6 +47,7 @@ describe('TenantSessionCoordinator', () => { const tenant1 = { agentContext: tenant1AgentContext, + mutex: new Mutex(), sessionCount: 1, } tenantSessionCoordinator.tenantAgentContextMapping = { @@ -57,6 +66,7 @@ describe('TenantSessionCoordinator', () => { }) const tenantAgentContext = await tenantSessionCoordinator.getContextForSession(tenantRecord) + expect(tenantSessionMutexMock.acquireSession).toHaveBeenCalledTimes(1) expect(tenantAgentContext).toBe(tenant1AgentContext) expect(tenant1.sessionCount).toBe(2) }) @@ -72,18 +82,65 @@ describe('TenantSessionCoordinator', () => { }, }, }) + const createChildSpy = jest.spyOn(agentContext.dependencyManager, 'createChild') + const extendSpy = jest.spyOn(agentContext.config, 'extend') + + const tenantDependencyManager = { + registerInstance: jest.fn(), + resolve: jest.fn(() => wallet), + } as unknown as DependencyManager + const mockConfig = jest.fn() as unknown as AgentConfig + + createChildSpy.mockReturnValue(tenantDependencyManager) + extendSpy.mockReturnValue(mockConfig) const tenantAgentContext = await tenantSessionCoordinator.getContextForSession(tenantRecord) expect(wallet.initialize).toHaveBeenCalledWith(tenantRecord.config.walletConfig) + expect(tenantSessionMutexMock.acquireSession).toHaveBeenCalledTimes(1) + expect(extendSpy).toHaveBeenCalledWith(tenantRecord.config) + expect(createChildSpy).toHaveBeenCalledWith() + expect(tenantDependencyManager.registerInstance).toHaveBeenCalledWith(AgentContext, expect.any(AgentContext)) + expect(tenantDependencyManager.registerInstance).toHaveBeenCalledWith(AgentConfig, mockConfig) + expect(tenantSessionCoordinator.tenantAgentContextMapping.tenant1).toEqual({ agentContext: tenantAgentContext, + mutex: expect.objectContaining({ + acquire: expect.any(Function), + cancel: expect.any(Function), + isLocked: expect.any(Function), + release: expect.any(Function), + runExclusive: expect.any(Function), + waitForUnlock: expect.any(Function), + }), sessionCount: 1, }) expect(tenantAgentContext.contextCorrelationId).toBe('tenant1') }) + test('rethrows error and releases session if error is throw while getting agent context', async () => { + const tenantRecord = new TenantRecord({ + id: 'tenant1', + config: { + label: 'Test Tenant', + walletConfig: { + id: 'test-wallet', + key: 'test-wallet-key', + }, + }, + }) + + // Throw error during wallet initialization + mockFunction(wallet.initialize).mockRejectedValue(new Error('Test error')) + + await expect(tenantSessionCoordinator.getContextForSession(tenantRecord)).rejects.toThrowError('Test error') + + expect(wallet.initialize).toHaveBeenCalledWith(tenantRecord.config.walletConfig) + expect(tenantSessionMutexMock.acquireSession).toHaveBeenCalledTimes(1) + expect(tenantSessionMutexMock.releaseSession).toHaveBeenCalledTimes(1) + }) + test('locks and waits for lock to release when initialization is already in progress', async () => { const tenantRecord = new TenantRecord({ id: 'tenant1', @@ -102,18 +159,36 @@ describe('TenantSessionCoordinator', () => { // Start two context session creations (but don't await). It should set the mutex property on the tenant agent context mapping. const tenantAgentContext1Promise = tenantSessionCoordinator.getContextForSession(tenantRecord) const tenantAgentContext2Promise = tenantSessionCoordinator.getContextForSession(tenantRecord) - expect(tenantSessionCoordinator.tenantAgentContextMapping.tenant1).toEqual({ - mutex: expect.any(Mutex), - }) + expect(tenantSessionCoordinator.tenantAgentContextMapping.tenant1).toBeUndefined() - // Await both context value promises + // Await first session promise, should have 1 session const tenantAgentContext1 = await tenantAgentContext1Promise - const tenantAgentContext2 = await tenantAgentContext2Promise + expect(tenantSessionCoordinator.tenantAgentContextMapping.tenant1).toEqual({ + agentContext: tenantAgentContext1, + sessionCount: 1, + mutex: expect.objectContaining({ + acquire: expect.any(Function), + cancel: expect.any(Function), + isLocked: expect.any(Function), + release: expect.any(Function), + runExclusive: expect.any(Function), + waitForUnlock: expect.any(Function), + }), + }) // There should be two sessions active now + const tenantAgentContext2 = await tenantAgentContext2Promise expect(tenantSessionCoordinator.tenantAgentContextMapping.tenant1).toEqual({ agentContext: tenantAgentContext1, sessionCount: 2, + mutex: expect.objectContaining({ + acquire: expect.any(Function), + cancel: expect.any(Function), + isLocked: expect.any(Function), + release: expect.any(Function), + runExclusive: expect.any(Function), + waitForUnlock: expect.any(Function), + }), }) // Initialize should only be called once @@ -123,4 +198,69 @@ describe('TenantSessionCoordinator', () => { expect(tenantAgentContext1).toBe(tenantAgentContext2) }) }) + + describe('endAgentContextSessions', () => { + test('Returns early and does not release a session if the agent context correlation id matches the root agent context', async () => { + const rootAgentContextMock = { + contextCorrelationId: 'mock', + dependencyManager: { dispose: jest.fn() }, + } as unknown as AgentContext + await tenantSessionCoordinator.endAgentContextSession(rootAgentContextMock) + + expect(tenantSessionMutexMock.releaseSession).not.toHaveBeenCalled() + }) + + test('throws an error if not agent context session exists for the tenant', async () => { + const tenantAgentContextMock = { contextCorrelationId: 'does-not-exist' } as unknown as AgentContext + expect(tenantSessionCoordinator.endAgentContextSession(tenantAgentContextMock)).rejects.toThrowError( + `Unknown agent context with contextCorrelationId 'does-not-exist'. Cannot end session` + ) + }) + + test('decreases the tenant session count and calls release session', async () => { + const tenant1AgentContext = { contextCorrelationId: 'tenant1' } as unknown as AgentContext + + const tenant1 = { + agentContext: tenant1AgentContext, + mutex: withTimeout(new Mutex(), 0), + sessionCount: 2, + } + tenantSessionCoordinator.tenantAgentContextMapping = { + tenant1, + } + + await tenantSessionCoordinator.endAgentContextSession(tenant1AgentContext) + + // Should have reduced session count by one + expect(tenantSessionCoordinator.tenantAgentContextMapping.tenant1).toEqual({ + agentContext: tenant1AgentContext, + mutex: tenant1.mutex, + sessionCount: 1, + }) + expect(tenantSessionMutexMock.releaseSession).toHaveBeenCalledTimes(1) + }) + + test('closes the agent context and removes the agent context mapping if the number of sessions reaches 0', async () => { + const tenant1AgentContext = { + dependencyManager: { dispose: jest.fn() }, + contextCorrelationId: 'tenant1', + } as unknown as AgentContext + + const tenant1 = { + agentContext: tenant1AgentContext, + mutex: withTimeout(new Mutex(), 0), + sessionCount: 1, + } + tenantSessionCoordinator.tenantAgentContextMapping = { + tenant1, + } + + await tenantSessionCoordinator.endAgentContextSession(tenant1AgentContext) + + // Should have removed tenant1 + expect(tenantSessionCoordinator.tenantAgentContextMapping.tenant1).toBeUndefined() + expect(tenant1AgentContext.dependencyManager.dispose).toHaveBeenCalledTimes(1) + expect(tenantSessionMutexMock.releaseSession).toHaveBeenCalledTimes(1) + }) + }) }) diff --git a/packages/module-tenants/src/context/__tests__/TenantSessionMutex.test.ts b/packages/module-tenants/src/context/__tests__/TenantSessionMutex.test.ts new file mode 100644 index 0000000000..6430e9b831 --- /dev/null +++ b/packages/module-tenants/src/context/__tests__/TenantSessionMutex.test.ts @@ -0,0 +1,61 @@ +import testLogger from '../../../../core/tests/logger' +import { TenantSessionMutex } from '../TenantSessionMutex' + +describe('TenantSessionMutex', () => { + test('correctly sets values', () => { + const tenantSessionMutex = new TenantSessionMutex(testLogger, 12, 50) + + expect(tenantSessionMutex.maxSessions).toBe(12) + expect(tenantSessionMutex.currentSessions).toBe(0) + }) + + describe('acquireSession', () => { + test('should immediately acquire the session if maxSessions has not been reached', async () => { + const tenantSessionMutex = new TenantSessionMutex(testLogger, 1, 0) + + expect(tenantSessionMutex.currentSessions).toBe(0) + await expect(tenantSessionMutex.acquireSession()).resolves.toBeUndefined() + expect(tenantSessionMutex.currentSessions).toBe(1) + }) + + test('should throw an error if a session could not be acquired within sessionAcquireTimeout', async () => { + const tenantSessionMutex = new TenantSessionMutex(testLogger, 1, 0) + + expect(tenantSessionMutex.currentSessions).toBe(0) + await tenantSessionMutex.acquireSession() + expect(tenantSessionMutex.currentSessions).toBe(1) + await expect(tenantSessionMutex.acquireSession()).rejects.toThrowError( + 'Failed to acquire an agent context session within 0ms' + ) + expect(tenantSessionMutex.currentSessions).toBe(1) + }) + }) + + describe('releaseSession', () => { + test('should release the session', async () => { + const tenantSessionMutex = new TenantSessionMutex(testLogger, 1, 0) + expect(tenantSessionMutex.currentSessions).toBe(0) + + await tenantSessionMutex.acquireSession() + expect(tenantSessionMutex.currentSessions).toBe(1) + + expect(tenantSessionMutex.releaseSession()).toBeUndefined() + expect(tenantSessionMutex.currentSessions).toBe(0) + }) + + test('resolves an acquire sessions if another sessions is being released', async () => { + const tenantSessionMutex = new TenantSessionMutex(testLogger, 1, 100) + expect(tenantSessionMutex.currentSessions).toBe(0) + + await tenantSessionMutex.acquireSession() + expect(tenantSessionMutex.currentSessions).toBe(1) + + const acquirePromise = tenantSessionMutex.acquireSession() + tenantSessionMutex.releaseSession() + expect(tenantSessionMutex.currentSessions).toBe(0) + + await acquirePromise + expect(tenantSessionMutex.currentSessions).toBe(1) + }) + }) +}) diff --git a/packages/module-tenants/src/models/TenantConfig.ts b/packages/module-tenants/src/models/TenantConfig.ts index d8849e73fe..a5391e2f7d 100644 --- a/packages/module-tenants/src/models/TenantConfig.ts +++ b/packages/module-tenants/src/models/TenantConfig.ts @@ -1,5 +1,5 @@ import type { InitConfig, WalletConfig } from '@aries-framework/core' export type TenantConfig = Pick & { - walletConfig: Pick + walletConfig: Pick } diff --git a/packages/module-tenants/src/services/TenantService.ts b/packages/module-tenants/src/services/TenantRecordService.ts similarity index 93% rename from packages/module-tenants/src/services/TenantService.ts rename to packages/module-tenants/src/services/TenantRecordService.ts index be6858d164..3b690d7c3c 100644 --- a/packages/module-tenants/src/services/TenantService.ts +++ b/packages/module-tenants/src/services/TenantRecordService.ts @@ -1,12 +1,12 @@ import type { TenantConfig } from '../models/TenantConfig' import type { AgentContext, Key } from '@aries-framework/core' -import { injectable, utils } from '@aries-framework/core' +import { injectable, utils, KeyDerivationMethod } from '@aries-framework/core' import { TenantRepository, TenantRecord, TenantRoutingRepository, TenantRoutingRecord } from '../repository' @injectable() -export class TenantService { +export class TenantRecordService { private tenantRepository: TenantRepository private tenantRoutingRepository: TenantRoutingRepository @@ -28,6 +28,7 @@ export class TenantService { walletConfig: { id: walletId, key: walletKey, + keyDerivationMethod: KeyDerivationMethod.Raw, }, }, }) diff --git a/packages/module-tenants/src/services/__tests__/TenantService.test.ts b/packages/module-tenants/src/services/__tests__/TenantService.test.ts index edbb44f9cd..228eb597a4 100644 --- a/packages/module-tenants/src/services/__tests__/TenantService.test.ts +++ b/packages/module-tenants/src/services/__tests__/TenantService.test.ts @@ -6,7 +6,7 @@ import { getAgentContext, mockFunction } from '../../../../core/tests/helpers' import { TenantRecord, TenantRoutingRecord } from '../../repository' import { TenantRepository } from '../../repository/TenantRepository' import { TenantRoutingRepository } from '../../repository/TenantRoutingRepository' -import { TenantService } from '../TenantService' +import { TenantRecordService } from '../TenantRecordService' jest.mock('../../repository/TenantRepository') const TenantRepositoryMock = TenantRepository as jest.Mock @@ -21,16 +21,16 @@ const tenantRepository = new TenantRepositoryMock() const tenantRoutingRepository = new TenantRoutingRepositoryMock() const agentContext = getAgentContext({ wallet }) -const tenantService = new TenantService(tenantRepository, tenantRoutingRepository) +const tenantRecordService = new TenantRecordService(tenantRepository, tenantRoutingRepository) -describe('TenantService', () => { +describe('TenantRecordService', () => { afterEach(() => { jest.clearAllMocks() }) describe('createTenant', () => { test('creates a tenant record and stores it in the tenant repository', async () => { - const tenantRecord = await tenantService.createTenant(agentContext, { + const tenantRecord = await tenantRecordService.createTenant(agentContext, { label: 'Test Tenant', connectionImageUrl: 'https://example.com/connection.png', }) @@ -56,7 +56,7 @@ describe('TenantService', () => { test('returns value from tenant repository get by id', async () => { const tenantRecord = jest.fn() as unknown as TenantRecord mockFunction(tenantRepository.getById).mockResolvedValue(tenantRecord) - const returnedTenantRecord = await tenantService.getTenantById(agentContext, 'tenantId') + const returnedTenantRecord = await tenantRecordService.getTenantById(agentContext, 'tenantId') expect(returnedTenantRecord).toBe(tenantRecord) }) @@ -77,7 +77,7 @@ describe('TenantService', () => { mockFunction(tenantRepository.getById).mockResolvedValue(tenantRecord) mockFunction(tenantRoutingRepository.findByQuery).mockResolvedValue([]) - await tenantService.deleteTenantById(agentContext, 'tenant-id') + await tenantRecordService.deleteTenantById(agentContext, 'tenant-id') expect(tenantRepository.delete).toHaveBeenCalledWith(agentContext, tenantRecord) }) @@ -107,7 +107,7 @@ describe('TenantService', () => { mockFunction(tenantRepository.getById).mockResolvedValue(tenantRecord) mockFunction(tenantRoutingRepository.findByQuery).mockResolvedValue(tenantRoutingRecords) - await tenantService.deleteTenantById(agentContext, 'tenant-id') + await tenantRecordService.deleteTenantById(agentContext, 'tenant-id') expect(tenantRoutingRepository.findByQuery).toHaveBeenCalledWith(agentContext, { tenantId: 'tenant-id', @@ -125,7 +125,7 @@ describe('TenantService', () => { mockFunction(tenantRoutingRepository.findByRecipientKey).mockResolvedValue(tenantRoutingRecord) const recipientKey = Key.fromFingerprint('z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') - const returnedTenantRoutingRecord = await tenantService.findTenantRoutingRecordByRecipientKey( + const returnedTenantRoutingRecord = await tenantRecordService.findTenantRoutingRecordByRecipientKey( agentContext, recipientKey ) @@ -138,7 +138,11 @@ describe('TenantService', () => { describe('addTenantRoutingRecord', () => { test('creates a tenant routing record and stores it in the tenant routing repository', async () => { const recipientKey = Key.fromFingerprint('z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') - const tenantRoutingRecord = await tenantService.addTenantRoutingRecord(agentContext, 'tenant-id', recipientKey) + const tenantRoutingRecord = await tenantRecordService.addTenantRoutingRecord( + agentContext, + 'tenant-id', + recipientKey + ) expect(tenantRoutingRepository.save).toHaveBeenCalledWith(agentContext, tenantRoutingRecord) expect(tenantRoutingRecord).toMatchObject({ diff --git a/packages/module-tenants/src/services/index.ts b/packages/module-tenants/src/services/index.ts index 8f1c72138f..9dde8c8dfc 100644 --- a/packages/module-tenants/src/services/index.ts +++ b/packages/module-tenants/src/services/index.ts @@ -1 +1 @@ -export * from './TenantService' +export * from './TenantRecordService' diff --git a/packages/module-tenants/tests/tenant-sessions.e2e.test.ts b/packages/module-tenants/tests/tenant-sessions.e2e.test.ts new file mode 100644 index 0000000000..c55070034d --- /dev/null +++ b/packages/module-tenants/tests/tenant-sessions.e2e.test.ts @@ -0,0 +1,90 @@ +import type { InitConfig } from '@aries-framework/core' + +import { Agent, DependencyManager } from '@aries-framework/core' +import { agentDependencies } from '@aries-framework/node' + +import testLogger from '../../core/tests/logger' +import { TenantsApi, TenantsModule } from '../src' + +jest.setTimeout(2000000) + +const agentConfig: InitConfig = { + label: 'Tenant Agent 1', + walletConfig: { + id: 'Wallet: tenant sessions e2e agent 1', + key: 'Wallet: tenant sessions e2e agent 1', + }, + logger: testLogger, + endpoints: ['rxjs:tenant-agent1'], + autoAcceptConnections: true, +} + +// Register tenant module. For now we need to create a custom dependency manager +// and register all plugins before initializing the agent. Later, we can add the module registration +// to the agent constructor. +const dependencyManager = new DependencyManager() +dependencyManager.registerModules(TenantsModule) + +// Create multi-tenant agent +const agent = new Agent(agentConfig, agentDependencies, dependencyManager) +const agentTenantsApi = agent.dependencyManager.resolve(TenantsApi) + +describe('Tenants Sessions E2E', () => { + beforeAll(async () => { + await agent.initialize() + }) + + afterAll(async () => { + await agent.wallet.delete() + await agent.shutdown() + }) + + test('create 100 sessions in parallel for the same tenant and close them', async () => { + const numberOfSessions = 100 + + const tenantRecord = await agentTenantsApi.createTenant({ + config: { + label: 'Agent 1 Tenant 1', + }, + }) + + const tenantAgentPromises = [] + + for (let session = 0; session < numberOfSessions; session++) { + tenantAgentPromises.push(agentTenantsApi.getTenantAgent({ tenantId: tenantRecord.id })) + } + + const tenantAgents = await Promise.all(tenantAgentPromises) + + await Promise.all(tenantAgents.map((tenantAgent) => tenantAgent.endSession())) + }) + + test('create 5 sessions each for 20 tenants in parallel and close them', async () => { + const numberOfTenants = 20 + const numberOfSessions = 5 + + const tenantRecordPromises = [] + for (let tenantNo = 0; tenantNo <= numberOfTenants; tenantNo++) { + const tenantRecord = agentTenantsApi.createTenant({ + config: { + label: 'Agent 1 Tenant 1', + }, + }) + + tenantRecordPromises.push(tenantRecord) + } + + const tenantRecords = await Promise.all(tenantRecordPromises) + + const tenantAgentPromises = [] + for (const tenantRecord of tenantRecords) { + for (let session = 0; session < numberOfSessions; session++) { + tenantAgentPromises.push(agentTenantsApi.getTenantAgent({ tenantId: tenantRecord.id })) + } + } + + const tenantAgents = await Promise.all(tenantAgentPromises) + + await Promise.all(tenantAgents.map((tenantAgent) => tenantAgent.endSession())) + }) +}) diff --git a/packages/module-tenants/tests/tenants.e2e.test.ts b/packages/module-tenants/tests/tenants.e2e.test.ts index c95c4c815d..c0ca618892 100644 --- a/packages/module-tenants/tests/tenants.e2e.test.ts +++ b/packages/module-tenants/tests/tenants.e2e.test.ts @@ -1,6 +1,6 @@ import type { InitConfig } from '@aries-framework/core' -import { Agent, DependencyManager } from '@aries-framework/core' +import { OutOfBandRecord, Agent, DependencyManager } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/node' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' @@ -96,7 +96,7 @@ describe('Tenants E2E', () => { const tenantAgent = await agent1TenantsApi.getTenantAgent({ tenantId: tenantRecord1.id, }) - await tenantAgent.shutdown() + await tenantAgent.endSession() // Delete tenant agent await agent1TenantsApi.deleteTenantById(tenantRecord1.id) @@ -150,8 +150,8 @@ describe('Tenants E2E', () => { const [connectionRecord] = await tenantAgent1.connections.findAllByOutOfBandId(outOfBandRecord.id) await tenantAgent1.connections.returnWhenIsConnected(connectionRecord.id) - await tenantAgent1.shutdown() - await tenantAgent1.shutdown() + await tenantAgent1.endSession() + await tenantAgent2.endSession() // Delete tenants (will also delete wallets) await agent1TenantsApi.deleteTenantById(tenantAgent1.context.contextCorrelationId) @@ -191,11 +191,29 @@ describe('Tenants E2E', () => { const [connectionRecord] = await tenantAgent1.connections.findAllByOutOfBandId(outOfBandRecord.id) await tenantAgent1.connections.returnWhenIsConnected(connectionRecord.id) - await tenantAgent1.shutdown() - await tenantAgent1.shutdown() + await tenantAgent1.endSession() + await tenantAgent2.endSession() // Delete tenants (will also delete wallets) - await agent1TenantsApi.deleteTenantById(tenantAgent1.context.contextCorrelationId) - await agent2TenantsApi.deleteTenantById(tenantAgent2.context.contextCorrelationId) + await agent1TenantsApi.deleteTenantById(tenantRecord1.id) + await agent2TenantsApi.deleteTenantById(tenantRecord2.id) + }) + + test('perform actions within the callback of withTenantAgent', async () => { + const tenantRecord = await agent1TenantsApi.createTenant({ + config: { + label: 'Agent 1 Tenant 1', + }, + }) + + await agent1TenantsApi.withTenantAgent({ tenantId: tenantRecord.id }, async (tenantAgent) => { + const outOfBandRecord = await tenantAgent.oob.createInvitation() + + expect(outOfBandRecord).toBeInstanceOf(OutOfBandRecord) + expect(tenantAgent.context.contextCorrelationId).toBe(tenantRecord.id) + expect(tenantAgent.config.label).toBe('Agent 1 Tenant 1') + }) + + await agent1TenantsApi.deleteTenantById(tenantRecord.id) }) }) diff --git a/yarn.lock b/yarn.lock index 371acdad4f..a36ad3ec64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10573,10 +10573,10 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" -tsyringe@^4.5.0, tsyringe@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/tsyringe/-/tsyringe-4.6.0.tgz#14915d3d7f0db35e1cf7269bdbf7c440713c8d07" - integrity sha512-BMQAZamSfEmIQzH8WJeRu1yZGQbPSDuI9g+yEiKZFIcO46GPZuMOC2d0b52cVBdw1d++06JnDSIIZvEnogMdAw== +tsyringe@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/tsyringe/-/tsyringe-4.7.0.tgz#aea0a9d565385deebb6def60cda342b15016f283" + integrity sha512-ncFDM1jTLsok4ejMvSW5jN1VGPQD48y2tfAR0pdptWRKYX4bkbqPt92k7KJ5RFJ1KV36JEs/+TMh7I6OUgj74g== dependencies: tslib "^1.9.3" From 1e708e9aeeb63977a7305999a5027d9743a56f91 Mon Sep 17 00:00:00 2001 From: Mo <10432473+morrieinmaas@users.noreply.github.com> Date: Thu, 14 Jul 2022 12:01:43 +0200 Subject: [PATCH 014/125] feat(ledger): smart schema and credential definition registration (#900) Signed-off-by: Moriarty --- .../AnonCredsCredentialDefinitionRecord.ts | 39 ++ ...AnonCredsCredentialDefinitionRepository.ts | 27 ++ .../indy/repository/AnonCredsSchemaRecord.ts | 39 ++ .../repository/AnonCredsSchemaRepository.ts | 27 ++ .../core/src/modules/ledger/LedgerModule.ts | 84 +++- .../ledger/__tests__/LedgerModule.test.ts | 372 ++++++++++++++++++ .../ledger/__tests__/ledgerUtils.test.ts | 61 +++ .../core/src/modules/ledger/ledgerUtil.ts | 8 + 8 files changed, 648 insertions(+), 9 deletions(-) create mode 100644 packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts create mode 100644 packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts create mode 100644 packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts create mode 100644 packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts create mode 100644 packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts create mode 100644 packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts diff --git a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts new file mode 100644 index 0000000000..730262447f --- /dev/null +++ b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts @@ -0,0 +1,39 @@ +import type { CredDef } from 'indy-sdk' + +import { BaseRecord } from '../../../storage/BaseRecord' +import { didFromCredentialDefinitionId } from '../../../utils/did' + +export interface AnonCredsCredentialDefinitionRecordProps { + credentialDefinition: CredDef +} + +export type DefaultAnonCredsCredentialDefinitionTags = { + credentialDefinitionId: string + issuerDid: string + schemaId: string + tag: string +} + +export class AnonCredsCredentialDefinitionRecord extends BaseRecord { + public static readonly type = 'AnonCredsCredentialDefinitionRecord' + public readonly type = AnonCredsCredentialDefinitionRecord.type + public readonly credentialDefinition!: CredDef + + public constructor(props: AnonCredsCredentialDefinitionRecordProps) { + super() + + if (props) { + this.credentialDefinition = props.credentialDefinition + } + } + + public getTags() { + return { + ...this._tags, + credentialDefinitionId: this.credentialDefinition.id, + issuerDid: didFromCredentialDefinitionId(this.credentialDefinition.id), + schemaId: this.credentialDefinition.schemaId, + tag: this.credentialDefinition.tag, + } + } +} diff --git a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts new file mode 100644 index 0000000000..706c7e2cac --- /dev/null +++ b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts @@ -0,0 +1,27 @@ +import type { AgentContext } from '../../../agent/context/AgentContext' + +import { EventEmitter } from '../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../constants' +import { injectable, inject } from '../../../plugins' +import { Repository } from '../../../storage/Repository' +import { StorageService } from '../../../storage/StorageService' + +import { AnonCredsCredentialDefinitionRecord } from './AnonCredsCredentialDefinitionRecord' + +@injectable() +export class AnonCredsCredentialDefinitionRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(AnonCredsCredentialDefinitionRecord, storageService, eventEmitter) + } + + public async getByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) { + return this.getSingleByQuery(agentContext, { credentialDefinitionId }) + } + + public async findByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) { + return this.findSingleByQuery(agentContext, { credentialDefinitionId }) + } +} diff --git a/packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts b/packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts new file mode 100644 index 0000000000..1c369626cf --- /dev/null +++ b/packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts @@ -0,0 +1,39 @@ +import type { Schema } from 'indy-sdk' + +import { BaseRecord } from '../../../storage/BaseRecord' +import { didFromSchemaId } from '../../../utils/did' + +export interface AnonCredsSchemaRecordProps { + schema: Schema +} + +export type DefaultAnonCredsSchemaTags = { + schemaId: string + schemaIssuerDid: string + schemaName: string + schemaVersion: string +} + +export class AnonCredsSchemaRecord extends BaseRecord { + public static readonly type = 'AnonCredsSchemaRecord' + public readonly type = AnonCredsSchemaRecord.type + public readonly schema!: Schema + + public constructor(props: AnonCredsSchemaRecordProps) { + super() + + if (props) { + this.schema = props.schema + } + } + + public getTags() { + return { + ...this._tags, + schemaId: this.schema.id, + schemaIssuerDid: didFromSchemaId(this.schema.id), + schemaName: this.schema.name, + schemaVersion: this.schema.version, + } + } +} diff --git a/packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts b/packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts new file mode 100644 index 0000000000..311931f1f6 --- /dev/null +++ b/packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts @@ -0,0 +1,27 @@ +import type { AgentContext } from '../../../agent/context/AgentContext' + +import { EventEmitter } from '../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../constants' +import { injectable, inject } from '../../../plugins' +import { Repository } from '../../../storage/Repository' +import { StorageService } from '../../../storage/StorageService' + +import { AnonCredsSchemaRecord } from './AnonCredsSchemaRecord' + +@injectable() +export class AnonCredsSchemaRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(AnonCredsSchemaRecord, storageService, eventEmitter) + } + + public async getBySchemaId(agentContext: AgentContext, schemaId: string) { + return this.getSingleByQuery(agentContext, { schemaId: schemaId }) + } + + public async findBySchemaId(agentContext: AgentContext, schemaId: string) { + return await this.findSingleByQuery(agentContext, { schemaId: schemaId }) + } +} diff --git a/packages/core/src/modules/ledger/LedgerModule.ts b/packages/core/src/modules/ledger/LedgerModule.ts index 9262511c8c..b6fb73e8ca 100644 --- a/packages/core/src/modules/ledger/LedgerModule.ts +++ b/packages/core/src/modules/ledger/LedgerModule.ts @@ -1,23 +1,37 @@ import type { DependencyManager } from '../../plugins' import type { IndyPoolConfig } from './IndyPool' -import type { CredentialDefinitionTemplate, SchemaTemplate } from './services' -import type { NymRole } from 'indy-sdk' +import type { SchemaTemplate, CredentialDefinitionTemplate } from './services' +import type { CredDef, NymRole, Schema } from 'indy-sdk' import { AgentContext } from '../../agent' import { AriesFrameworkError } from '../../error' +import { IndySdkError } from '../../error/IndySdkError' import { injectable, module } from '../../plugins' +import { isIndyError } from '../../utils/indyError' +import { AnonCredsCredentialDefinitionRepository } from '../indy/repository/AnonCredsCredentialDefinitionRepository' +import { AnonCredsSchemaRepository } from '../indy/repository/AnonCredsSchemaRepository' -import { IndyLedgerService, IndyPoolService } from './services' +import { generateCredentialDefinitionId, generateSchemaId } from './ledgerUtil' +import { IndyPoolService, IndyLedgerService } from './services' @module() @injectable() export class LedgerModule { private ledgerService: IndyLedgerService private agentContext: AgentContext - - public constructor(ledgerService: IndyLedgerService, agentContext: AgentContext) { + private anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository + private anonCredsSchemaRepository: AnonCredsSchemaRepository + + public constructor( + ledgerService: IndyLedgerService, + agentContext: AgentContext, + anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository, + anonCredsSchemaRepository: AnonCredsSchemaRepository + ) { this.ledgerService = ledgerService this.agentContext = agentContext + this.anonCredsCredentialDefinitionRepository = anonCredsCredentialDefinitionRepository + this.anonCredsSchemaRepository = anonCredsSchemaRepository } public setPools(poolConfigs: IndyPoolConfig[]) { @@ -45,18 +59,47 @@ export class LedgerModule { return this.ledgerService.getPublicDid(this.agentContext, did) } - public async registerSchema(schema: SchemaTemplate) { + public async getSchema(id: string) { + return this.ledgerService.getSchema(this.agentContext, id) + } + + public async registerSchema(schema: SchemaTemplate): Promise { const did = this.agentContext.wallet.publicDid?.did if (!did) { throw new AriesFrameworkError('Agent has no public DID.') } + const schemaId = generateSchemaId(did, schema.name, schema.version) + + // Try find the schema in the wallet + const schemaRecord = await this.anonCredsSchemaRepository.findBySchemaId(this.agentContext, schemaId) + // Schema in wallet + if (schemaRecord) return schemaRecord.schema + + const schemaFromLedger = await this.findBySchemaIdOnLedger(schemaId) + if (schemaFromLedger) return schemaFromLedger return this.ledgerService.registerSchema(this.agentContext, did, schema) } - public async getSchema(id: string) { - return this.ledgerService.getSchema(this.agentContext, id) + private async findBySchemaIdOnLedger(schemaId: string) { + try { + return await this.ledgerService.getSchema(this.agentContext, schemaId) + } catch (e) { + if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null + + throw e + } + } + + private async findByCredentialDefinitionIdOnLedger(credentialDefinitionId: string): Promise { + try { + return await this.ledgerService.getCredentialDefinition(this.agentContext, credentialDefinitionId) + } catch (e) { + if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null + + throw e + } } public async registerCredentialDefinition( @@ -68,7 +111,30 @@ export class LedgerModule { throw new AriesFrameworkError('Agent has no public DID.') } - return this.ledgerService.registerCredentialDefinition(this.agentContext, did, { + // Construct credential definition ID + const credentialDefinitionId = generateCredentialDefinitionId( + did, + credentialDefinitionTemplate.schema.seqNo, + credentialDefinitionTemplate.tag + ) + + // Check if the credential exists in wallet. If so, return it + const credentialDefinitionRecord = await this.anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId( + this.agentContext, + credentialDefinitionId + ) + if (credentialDefinitionRecord) return credentialDefinitionRecord.credentialDefinition + + // Check for the credential on the ledger. + const credentialDefinitionOnLedger = await this.findByCredentialDefinitionIdOnLedger(credentialDefinitionId) + if (credentialDefinitionOnLedger) { + throw new AriesFrameworkError( + `No credential definition record found and credential definition ${credentialDefinitionId} already exists on the ledger.` + ) + } + + // Register the credential + return await this.ledgerService.registerCredentialDefinition(this.agentContext, did, { ...credentialDefinitionTemplate, signatureType: 'CL', }) diff --git a/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts b/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts new file mode 100644 index 0000000000..e33c6e3c41 --- /dev/null +++ b/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts @@ -0,0 +1,372 @@ +import type { AgentContext } from '../../../agent/context/AgentContext' +import type { IndyPoolConfig } from '../IndyPool' +import type { CredentialDefinitionTemplate } from '../services/IndyLedgerService' +import type * as Indy from 'indy-sdk' + +import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../tests/helpers' +import { AriesFrameworkError } from '../../../error/AriesFrameworkError' +import { IndyWallet } from '../../../wallet/IndyWallet' +import { AnonCredsCredentialDefinitionRecord } from '../../indy/repository/AnonCredsCredentialDefinitionRecord' +import { AnonCredsCredentialDefinitionRepository } from '../../indy/repository/AnonCredsCredentialDefinitionRepository' +import { AnonCredsSchemaRecord } from '../../indy/repository/AnonCredsSchemaRecord' +import { AnonCredsSchemaRepository } from '../../indy/repository/AnonCredsSchemaRepository' +import { LedgerModule } from '../LedgerModule' +import { generateCredentialDefinitionId, generateSchemaId } from '../ledgerUtil' +import { IndyLedgerService } from '../services/IndyLedgerService' + +jest.mock('../services/IndyLedgerService') +const IndyLedgerServiceMock = IndyLedgerService as jest.Mock + +jest.mock('../../indy/repository/AnonCredsCredentialDefinitionRepository') +const AnonCredsCredentialDefinitionRepositoryMock = + AnonCredsCredentialDefinitionRepository as jest.Mock +jest.mock('../../indy/repository/AnonCredsSchemaRepository') +const AnonCredsSchemaRepositoryMock = AnonCredsSchemaRepository as jest.Mock + +const did = 'Y5bj4SjCiTM9PgeheKAiXx' + +const schemaId = 'abcd' + +const schema: Indy.Schema = { + id: schemaId, + attrNames: ['hello', 'world'], + name: 'awesomeSchema', + version: '1', + ver: '1', + seqNo: 99, +} + +const credentialDefinition = { + schema: 'abcde', + tag: 'someTag', + signatureType: 'CL', + supportRevocation: true, +} + +const credDef: Indy.CredDef = { + id: 'abcde', + schemaId: schema.id, + type: 'CL', + tag: 'someTag', + value: { + primary: credentialDefinition as Record, + revocation: true, + }, + ver: '1', +} + +const credentialDefinitionTemplate: Omit = { + schema: schema, + tag: 'someTag', + supportRevocation: true, +} + +const revocRegDef: Indy.RevocRegDef = { + id: 'abcde', + revocDefType: 'CL_ACCUM', + tag: 'someTag', + credDefId: 'abcde', + value: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + maxCredNum: 3, + tailsHash: 'abcde', + tailsLocation: 'xyz', + publicKeys: ['abcde', 'fghijk'], + }, + ver: 'abcde', +} + +const schemaIdGenerated = generateSchemaId(did, schema.name, schema.version) + +const credentialDefinitionId = generateCredentialDefinitionId( + did, + credentialDefinitionTemplate.schema.seqNo, + credentialDefinitionTemplate.tag +) + +const pools: IndyPoolConfig[] = [ + { + id: 'sovrinMain', + isProduction: true, + genesisTransactions: 'xxx', + transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + }, +] + +describe('LedgerModule', () => { + let wallet: IndyWallet + let ledgerService: IndyLedgerService + let anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository + let anonCredsSchemaRepository: AnonCredsSchemaRepository + let ledgerModule: LedgerModule + let agentContext: AgentContext + + const contextCorrelationId = 'mock' + const agentConfig = getAgentConfig('LedgerModuleTest', { + indyLedgers: pools, + }) + + beforeEach(async () => { + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await wallet.createAndOpen(agentConfig.walletConfig!) + }) + + afterEach(async () => { + await wallet.delete() + }) + + beforeEach(async () => { + ledgerService = new IndyLedgerServiceMock() + + agentContext = getAgentContext({ + wallet, + agentConfig, + contextCorrelationId, + }) + + anonCredsCredentialDefinitionRepository = new AnonCredsCredentialDefinitionRepositoryMock() + anonCredsSchemaRepository = new AnonCredsSchemaRepositoryMock() + + ledgerModule = new LedgerModule( + ledgerService, + agentContext, + anonCredsCredentialDefinitionRepository, + anonCredsSchemaRepository + ) + }) + + describe('LedgerModule', () => { + // Connect to pools + describe('connectToPools', () => { + it('should connect to all pools', async () => { + mockFunction(ledgerService.connectToPools).mockResolvedValue([1, 2, 4]) + await expect(ledgerModule.connectToPools()).resolves + expect(ledgerService.connectToPools).toHaveBeenCalled() + }) + }) + + // Register public did + describe('registerPublicDid', () => { + it('should register a public DID', async () => { + mockFunction(ledgerService.registerPublicDid).mockResolvedValueOnce(did) + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + await expect(ledgerModule.registerPublicDid(did, 'abcde', 'someAlias')).resolves.toEqual(did) + expect(ledgerService.registerPublicDid).toHaveBeenCalledWith( + agentContext, + did, + did, + 'abcde', + 'someAlias', + undefined + ) + }) + + it('should throw an error if the DID cannot be registered because there is no public did', async () => { + const did = 'Y5bj4SjCiTM9PgeheKAiXx' + mockProperty(wallet, 'publicDid', undefined) + await expect(ledgerModule.registerPublicDid(did, 'abcde', 'someAlias')).rejects.toThrowError( + AriesFrameworkError + ) + }) + }) + + // Get public DID + describe('getPublicDid', () => { + it('should return the public DID if there is one', async () => { + const nymResponse: Indy.GetNymResponse = { did: 'Y5bj4SjCiTM9PgeheKAiXx', verkey: 'abcde', role: 'STEWARD' } + mockProperty(wallet, 'publicDid', { did: nymResponse.did, verkey: nymResponse.verkey }) + mockFunction(ledgerService.getPublicDid).mockResolvedValueOnce(nymResponse) + await expect(ledgerModule.getPublicDid(nymResponse.did)).resolves.toEqual(nymResponse) + expect(ledgerService.getPublicDid).toHaveBeenCalledWith(agentContext, nymResponse.did) + }) + }) + + // Get schema + describe('getSchema', () => { + it('should return the schema by id if there is one', async () => { + mockFunction(ledgerService.getSchema).mockResolvedValueOnce(schema) + await expect(ledgerModule.getSchema(schemaId)).resolves.toEqual(schema) + expect(ledgerService.getSchema).toHaveBeenCalledWith(agentContext, schemaId) + }) + + it('should throw an error if no schema for the id exists', async () => { + mockFunction(ledgerService.getSchema).mockRejectedValueOnce( + new AriesFrameworkError('Error retrieving schema abcd from ledger 1') + ) + await expect(ledgerModule.getSchema(schemaId)).rejects.toThrowError(AriesFrameworkError) + expect(ledgerService.getSchema).toHaveBeenCalledWith(agentContext, schemaId) + }) + }) + + describe('registerSchema', () => { + it('should throw an error if there is no public DID', async () => { + mockProperty(wallet, 'publicDid', undefined) + await expect(ledgerModule.registerSchema({ ...schema, attributes: ['hello', 'world'] })).rejects.toThrowError( + AriesFrameworkError + ) + }) + + it('should return the schema from anonCreds when it already exists', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(anonCredsSchemaRepository.findBySchemaId).mockResolvedValueOnce( + new AnonCredsSchemaRecord({ schema: schema }) + ) + await expect(ledgerModule.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual( + schema + ) + expect(anonCredsSchemaRepository.findBySchemaId).toHaveBeenCalledWith(agentContext, schemaIdGenerated) + }) + + it('should return the schema from the ledger when it already exists', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(LedgerModule.prototype as any, 'findBySchemaIdOnLedger') + .mockResolvedValueOnce(new AnonCredsSchemaRecord({ schema: schema })) + await expect( + ledgerModule.registerSchema({ ...schema, attributes: ['hello', 'world'] }) + ).resolves.toHaveProperty('schema', schema) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(jest.spyOn(LedgerModule.prototype as any, 'findBySchemaIdOnLedger')).toHaveBeenCalledWith( + schemaIdGenerated + ) + }) + + it('should return the schema after registering it', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(ledgerService.registerSchema).mockResolvedValueOnce(schema) + await expect(ledgerModule.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual( + schema + ) + expect(ledgerService.registerSchema).toHaveBeenCalledWith(agentContext, did, { + ...schema, + attributes: ['hello', 'world'], + }) + }) + }) + + describe('registerCredentialDefinition', () => { + it('should throw an error if there si no public DID', async () => { + mockProperty(wallet, 'publicDid', undefined) + await expect(ledgerModule.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( + AriesFrameworkError + ) + }) + + it('should return the credential definition from the wallet if it already exists', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + const anonCredsCredentialDefinitionRecord: AnonCredsCredentialDefinitionRecord = + new AnonCredsCredentialDefinitionRecord({ + credentialDefinition: credDef, + }) + mockFunction(anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId).mockResolvedValueOnce( + anonCredsCredentialDefinitionRecord + ) + await expect(ledgerModule.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toHaveProperty( + 'value.primary', + credentialDefinition + ) + expect(anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId).toHaveBeenCalledWith( + agentContext, + credentialDefinitionId + ) + }) + + it('should throw an exception if the definition already exists on the ledger', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(LedgerModule.prototype as any, 'findByCredentialDefinitionIdOnLedger') + .mockResolvedValueOnce({ credentialDefinition: credentialDefinition }) + await expect(ledgerModule.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( + AriesFrameworkError + ) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(jest.spyOn(LedgerModule.prototype as any, 'findByCredentialDefinitionIdOnLedger')).toHaveBeenCalledWith( + credentialDefinitionId + ) + }) + + it('should register the credential successfully if it is neither in the wallet and neither on the ledger', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(ledgerService.registerCredentialDefinition).mockResolvedValueOnce(credDef) + await expect(ledgerModule.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toEqual(credDef) + expect(ledgerService.registerCredentialDefinition).toHaveBeenCalledWith(agentContext, did, { + ...credentialDefinitionTemplate, + signatureType: 'CL', + }) + }) + }) + + describe('getCredentialDefinition', () => { + it('should return the credential definition given the id', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(ledgerService.getCredentialDefinition).mockResolvedValue(credDef) + await expect(ledgerModule.getCredentialDefinition(credDef.id)).resolves.toEqual(credDef) + expect(ledgerService.getCredentialDefinition).toHaveBeenCalledWith(agentContext, credDef.id) + }) + + it('should throw an error if there is no credential definition for the given id', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(ledgerService.getCredentialDefinition).mockRejectedValueOnce(new AriesFrameworkError('')) + await expect(ledgerModule.getCredentialDefinition(credDef.id)).rejects.toThrowError(AriesFrameworkError) + expect(ledgerService.getCredentialDefinition).toHaveBeenCalledWith(agentContext, credDef.id) + }) + }) + + describe('getRevocationRegistryDefinition', () => { + it('should return the ParseRevocationRegistryDefinitionTemplate for a valid revocationRegistryDefinitionId', async () => { + const parseRevocationRegistryDefinitionTemplate = { + revocationRegistryDefinition: revocRegDef, + revocationRegistryDefinitionTxnTime: 12345678, + } + mockFunction(ledgerService.getRevocationRegistryDefinition).mockResolvedValue( + parseRevocationRegistryDefinitionTemplate + ) + await expect(ledgerModule.getRevocationRegistryDefinition(revocRegDef.id)).resolves.toBe( + parseRevocationRegistryDefinitionTemplate + ) + expect(ledgerService.getRevocationRegistryDefinition).toHaveBeenLastCalledWith(agentContext, revocRegDef.id) + }) + + it('should throw an error if the ParseRevocationRegistryDefinitionTemplate does not exists', async () => { + mockFunction(ledgerService.getRevocationRegistryDefinition).mockRejectedValueOnce(new AriesFrameworkError('')) + await expect(ledgerModule.getRevocationRegistryDefinition('abcde')).rejects.toThrowError(AriesFrameworkError) + expect(ledgerService.getRevocationRegistryDefinition).toHaveBeenCalledWith(agentContext, revocRegDef.id) + }) + }) + + describe('getRevocationRegistryDelta', () => { + it('should return the ParseRevocationRegistryDeltaTemplate', async () => { + const revocRegDelta = { + value: { + prevAccum: 'prev', + accum: 'accum', + issued: [1, 2, 3], + revoked: [4, 5, 6], + }, + ver: 'ver', + } + const parseRevocationRegistryDeltaTemplate = { + revocationRegistryDelta: revocRegDelta, + deltaTimestamp: 12345678, + } + + mockFunction(ledgerService.getRevocationRegistryDelta).mockResolvedValueOnce( + parseRevocationRegistryDeltaTemplate + ) + await expect(ledgerModule.getRevocationRegistryDelta('12345')).resolves.toEqual( + parseRevocationRegistryDeltaTemplate + ) + expect(ledgerService.getRevocationRegistryDelta).toHaveBeenCalledTimes(1) + }) + + it('should throw an error if the delta cannot be obtained', async () => { + mockFunction(ledgerService.getRevocationRegistryDelta).mockRejectedValueOnce(new AriesFrameworkError('')) + await expect(ledgerModule.getRevocationRegistryDelta('abcde1234')).rejects.toThrowError(AriesFrameworkError) + expect(ledgerService.getRevocationRegistryDelta).toHaveBeenCalledTimes(1) + }) + }) + }) +}) diff --git a/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts b/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts new file mode 100644 index 0000000000..a27e788ed2 --- /dev/null +++ b/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts @@ -0,0 +1,61 @@ +import type { LedgerRejectResponse, LedgerReqnackResponse } from 'indy-sdk' + +import * as LedgerUtil from '../ledgerUtil' + +describe('LedgerUtils', () => { + // IsLedgerRejectResponse + it('Should return true if the response op is: REJECT', () => { + const ledgerResponse: LedgerRejectResponse = { + op: 'REJECT', + reqId: 1, + reason: 'Why not', + identifier: '123456', + } + expect(LedgerUtil.isLedgerRejectResponse(ledgerResponse)).toEqual(true) + }) + it('Should return false if the response op is not: REJECT', () => { + const ledgerResponse: LedgerReqnackResponse = { + op: 'REQNACK', + reqId: 1, + reason: 'Why not', + identifier: '123456', + } + expect(LedgerUtil.isLedgerRejectResponse(ledgerResponse)).toEqual(false) + }) + + // isLedgerReqnackResponse + it('Should return true if the response op is: REQNACK', () => { + const ledgerResponse: LedgerReqnackResponse = { + op: 'REQNACK', + reqId: 1, + reason: 'Why not', + identifier: '123456', + } + expect(LedgerUtil.isLedgerReqnackResponse(ledgerResponse)).toEqual(true) + }) + it('Should return false if the response op is NOT: REQNACK', () => { + const ledgerResponse: LedgerRejectResponse = { + op: 'REJECT', + reqId: 1, + reason: 'Why not', + identifier: '123456', + } + expect(LedgerUtil.isLedgerReqnackResponse(ledgerResponse)).toEqual(false) + }) + + // generateSchemaId + it('Should return a valid schema ID given did name and version', () => { + const did = '12345', + name = 'backbench', + version = '420' + expect(LedgerUtil.generateSchemaId(did, name, version)).toEqual('12345:2:backbench:420') + }) + + // generateCredentialDefinitionId + it('Should return a valid schema ID given did name and version', () => { + const did = '12345', + seqNo = 420, + tag = 'someTag' + expect(LedgerUtil.generateCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') + }) +}) diff --git a/packages/core/src/modules/ledger/ledgerUtil.ts b/packages/core/src/modules/ledger/ledgerUtil.ts index 62e75f1e72..6c9cfb1cf8 100644 --- a/packages/core/src/modules/ledger/ledgerUtil.ts +++ b/packages/core/src/modules/ledger/ledgerUtil.ts @@ -7,3 +7,11 @@ export function isLedgerRejectResponse(response: Indy.LedgerResponse): response export function isLedgerReqnackResponse(response: Indy.LedgerResponse): response is Indy.LedgerReqnackResponse { return response.op === 'REQNACK' } + +export function generateSchemaId(did: string, name: string, version: string) { + return `${did}:2:${name}:${version}` +} + +export function generateCredentialDefinitionId(did: string, seqNo: number, tag: string) { + return `${did}:3:CL:${seqNo}:${tag}` +} From ab8b8ef1357c7a8dc338eaea16b20d93a0c92d4f Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 19 Jul 2022 10:30:48 +0200 Subject: [PATCH 015/125] feat: add dynamic suite and signing provider (#949) Signed-off-by: Timo Glastra --- packages/core/src/crypto/BbsService.ts | 151 ------------------ .../src/crypto/__tests__/JwsService.test.ts | 3 +- .../Bls12381g2SigningProvider.ts | 111 +++++++++++++ .../signing-provider/SigningProvider.ts | 32 ++++ .../signing-provider/SigningProviderError.ts | 3 + .../SigningProviderRegistry.ts | 32 ++++ .../__tests__/SigningProviderRegistry.test.ts | 46 ++++++ .../core/src/crypto/signing-provider/index.ts | 4 + .../signature/SignatureDecoratorUtils.test.ts | 3 +- .../__tests__/ConnectionService.test.ts | 3 +- .../modules/dids/__tests__/peer-did.test.ts | 3 +- .../__tests__/IndyLedgerService.test.ts | 3 +- .../ledger/__tests__/IndyPoolService.test.ts | 3 +- .../ledger/__tests__/LedgerModule.test.ts | 3 +- .../oob/__tests__/OutOfBandService.test.ts | 3 +- .../__tests__/QuestionAnswerService.test.ts | 3 +- .../MediationRecipientService.test.ts | 3 +- .../src/modules/vc/SignatureSuiteRegistry.ts | 33 ++-- .../src/modules/vc/W3cCredentialService.ts | 8 +- .../vc/__tests__/W3cCredentialService.test.ts | 31 +++- packages/core/src/modules/vc/module.ts | 28 ++++ packages/core/src/plugins/index.ts | 2 +- .../__tests__/IndyStorageService.test.ts | 3 +- packages/core/src/wallet/IndyWallet.test.ts | 24 +-- packages/core/src/wallet/IndyWallet.ts | 69 +++++--- packages/core/src/wallet/WalletModule.ts | 4 + 26 files changed, 381 insertions(+), 230 deletions(-) delete mode 100644 packages/core/src/crypto/BbsService.ts create mode 100644 packages/core/src/crypto/signing-provider/Bls12381g2SigningProvider.ts create mode 100644 packages/core/src/crypto/signing-provider/SigningProvider.ts create mode 100644 packages/core/src/crypto/signing-provider/SigningProviderError.ts create mode 100644 packages/core/src/crypto/signing-provider/SigningProviderRegistry.ts create mode 100644 packages/core/src/crypto/signing-provider/__tests__/SigningProviderRegistry.test.ts create mode 100644 packages/core/src/crypto/signing-provider/index.ts diff --git a/packages/core/src/crypto/BbsService.ts b/packages/core/src/crypto/BbsService.ts deleted file mode 100644 index c00e814249..0000000000 --- a/packages/core/src/crypto/BbsService.ts +++ /dev/null @@ -1,151 +0,0 @@ -import type { CreateKeyOptions } from '../wallet' -import type { BlsKeyPair as _BlsKeyPair } from '@mattrglobal/bbs-signatures' - -import { - bls12381toBbs, - generateBls12381G2KeyPair, - generateBls12381G1KeyPair, - sign, - verify, -} from '@mattrglobal/bbs-signatures' - -import { TypedArrayEncoder } from '../utils/TypedArrayEncoder' -import { Buffer } from '../utils/buffer' -import { WalletError } from '../wallet/error' - -import { KeyType } from './KeyType' - -export interface BlsKeyPair { - publicKeyBase58: string - privateKeyBase58: string - keyType: Extract -} - -interface BbsCreateKeyOptions extends CreateKeyOptions { - keyType: Extract -} - -interface BbsSignOptions { - messages: Buffer | Buffer[] - publicKey: Buffer - privateKey: Buffer -} - -interface BbsVerifyOptions { - publicKey: Buffer - signature: Buffer - messages: Buffer | Buffer[] -} - -export class BbsService { - /** - * Create an instance of a Key class for the following key types: - * - Bls12381g1 - * - Bls12381g2 - * - * @param keyType KeyType The type of key to be created (see above for the accepted types) - * - * @returns A Key class with the public key and key type - * - * @throws {WalletError} When a key could not be created - * @throws {WalletError} When the method is called with an invalid keytype - */ - public static async createKey({ keyType, seed }: BbsCreateKeyOptions): Promise { - // Generate bytes from the seed as required by the bbs-signatures libraries - const seedBytes = seed ? TypedArrayEncoder.fromString(seed) : undefined - - // Temporary keypair holder - let blsKeyPair: Required<_BlsKeyPair> - - switch (keyType) { - case KeyType.Bls12381g1: - // Generate a bls12-381G1 keypair - blsKeyPair = await generateBls12381G1KeyPair(seedBytes) - break - case KeyType.Bls12381g2: - // Generate a bls12-381G2 keypair - blsKeyPair = await generateBls12381G2KeyPair(seedBytes) - break - default: - // additional check. Should never be hit as this function will only be called from a place where - // a key type check already happened. - throw new WalletError(`Cannot create key with the BbsService for key type: ${keyType}`) - } - - return { - keyType, - publicKeyBase58: TypedArrayEncoder.toBase58(blsKeyPair.publicKey), - privateKeyBase58: TypedArrayEncoder.toBase58(blsKeyPair.secretKey), - } - } - - /** - * Sign an arbitrary amount of messages, in byte form, with a keypair - * - * @param messages Buffer[] List of messages in Buffer form - * @param publicKey Buffer Publickey required for the signing process - * @param privateKey Buffer PrivateKey required for the signing process - * - * @returns A Buffer containing the signature of the messages - * - * @throws {WalletError} When there are no supplied messages - */ - public static async sign({ messages, publicKey, privateKey }: BbsSignOptions): Promise { - if (messages.length === 0) throw new WalletError('Unable to create a signature without any messages') - // Check if it is a single message or list and if it is a single message convert it to a list - const normalizedMessages = (TypedArrayEncoder.isTypedArray(messages) ? [messages as Buffer] : messages) as Buffer[] - - // Get the Uint8Array variant of all the messages - const messageBuffers = normalizedMessages.map((m) => Uint8Array.from(m)) - - const bbsKeyPair = await bls12381toBbs({ - keyPair: { publicKey: Uint8Array.from(publicKey), secretKey: Uint8Array.from(privateKey) }, - messageCount: normalizedMessages.length, - }) - - // Sign the messages via the keyPair - const signature = await sign({ - keyPair: bbsKeyPair, - messages: messageBuffers, - }) - - // Convert the Uint8Array signature to a Buffer type - return Buffer.from(signature) - } - - /** - * Verify an arbitrary amount of messages with their signature created with their key pair - * - * @param publicKey Buffer The public key used to sign the messages - * @param messages Buffer[] The messages that have to be verified if they are signed - * @param signature Buffer The signature that has to be verified if it was created with the messages and public key - * - * @returns A boolean whether the signature is create with the public key over the messages - * - * @throws {WalletError} When the message list is empty - * @throws {WalletError} When the verification process failed - */ - public static async verify({ signature, messages, publicKey }: BbsVerifyOptions): Promise { - if (messages.length === 0) throw new WalletError('Unable to create a signature without any messages') - // Check if it is a single message or list and if it is a single message convert it to a list - const normalizedMessages = (TypedArrayEncoder.isTypedArray(messages) ? [messages as Buffer] : messages) as Buffer[] - - // Get the Uint8Array variant of all the messages - const messageBuffers = normalizedMessages.map((m) => Uint8Array.from(m)) - - const bbsKeyPair = await bls12381toBbs({ - keyPair: { publicKey: Uint8Array.from(publicKey) }, - messageCount: normalizedMessages.length, - }) - - // Verify the signature against the messages with their public key - const { verified, error } = await verify({ signature, messages: messageBuffers, publicKey: bbsKeyPair.publicKey }) - - // If the messages could not be verified and an error occured - if (!verified && error) { - throw new WalletError(`Could not verify the signature against the messages: ${error}`) - } - - return verified - } -} diff --git a/packages/core/src/crypto/__tests__/JwsService.test.ts b/packages/core/src/crypto/__tests__/JwsService.test.ts index d3371200ff..41b777e3cd 100644 --- a/packages/core/src/crypto/__tests__/JwsService.test.ts +++ b/packages/core/src/crypto/__tests__/JwsService.test.ts @@ -8,6 +8,7 @@ import { IndyWallet } from '../../wallet/IndyWallet' import { JwsService } from '../JwsService' import { Key } from '../Key' import { KeyType } from '../KeyType' +import { SigningProviderRegistry } from '../signing-provider' import * as didJwsz6Mkf from './__fixtures__/didJwsz6Mkf' import * as didJwsz6Mkv from './__fixtures__/didJwsz6Mkv' @@ -19,7 +20,7 @@ describe('JwsService', () => { beforeAll(async () => { const config = getAgentConfig('JwsService') - wallet = new IndyWallet(config.agentDependencies, config.logger) + wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([])) agentContext = getAgentContext({ wallet, }) diff --git a/packages/core/src/crypto/signing-provider/Bls12381g2SigningProvider.ts b/packages/core/src/crypto/signing-provider/Bls12381g2SigningProvider.ts new file mode 100644 index 0000000000..02998c007f --- /dev/null +++ b/packages/core/src/crypto/signing-provider/Bls12381g2SigningProvider.ts @@ -0,0 +1,111 @@ +import type { SigningProvider, CreateKeyPairOptions, SignOptions, VerifyOptions, KeyPair } from './SigningProvider' + +import { bls12381toBbs, verify, sign, generateBls12381G2KeyPair } from '@mattrglobal/bbs-signatures' + +import { injectable } from '../../plugins' +import { TypedArrayEncoder } from '../../utils' +import { Buffer } from '../../utils/buffer' +import { KeyType } from '../KeyType' + +import { SigningProviderError } from './SigningProviderError' + +/** + * This will be extracted to the bbs package. + */ +@injectable() +export class Bls12381g2SigningProvider implements SigningProvider { + public readonly keyType = KeyType.Bls12381g2 + + /** + * Create a KeyPair with type Bls12381g2 + * + * @throws {SigningProviderError} When a key could not be created + */ + public async createKeyPair({ seed }: CreateKeyPairOptions): Promise { + // Generate bytes from the seed as required by the bbs-signatures libraries + const seedBytes = seed ? TypedArrayEncoder.fromString(seed) : undefined + + const blsKeyPair = await generateBls12381G2KeyPair(seedBytes) + + return { + keyType: KeyType.Bls12381g2, + publicKeyBase58: TypedArrayEncoder.toBase58(blsKeyPair.publicKey), + privateKeyBase58: TypedArrayEncoder.toBase58(blsKeyPair.secretKey), + } + } + + /** + * Sign an arbitrary amount of messages, in byte form, with a keypair + * + * @param messages Buffer[] List of messages in Buffer form + * @param publicKey Buffer Publickey required for the signing process + * @param privateKey Buffer PrivateKey required for the signing process + * + * @returns A Buffer containing the signature of the messages + * + * @throws {SigningProviderError} When there are no supplied messages + */ + public async sign({ data, publicKeyBase58, privateKeyBase58 }: SignOptions): Promise { + if (data.length === 0) throw new SigningProviderError('Unable to create a signature without any messages') + // Check if it is a single message or list and if it is a single message convert it to a list + const normalizedMessages = (TypedArrayEncoder.isTypedArray(data) ? [data as Buffer] : data) as Buffer[] + + // Get the Uint8Array variant of all the messages + const messageBuffers = normalizedMessages.map((m) => Uint8Array.from(m)) + + const publicKey = TypedArrayEncoder.fromBase58(publicKeyBase58) + const privateKey = TypedArrayEncoder.fromBase58(privateKeyBase58) + + const bbsKeyPair = await bls12381toBbs({ + keyPair: { publicKey: Uint8Array.from(publicKey), secretKey: Uint8Array.from(privateKey) }, + messageCount: normalizedMessages.length, + }) + + // Sign the messages via the keyPair + const signature = await sign({ + keyPair: bbsKeyPair, + messages: messageBuffers, + }) + + // Convert the Uint8Array signature to a Buffer type + return Buffer.from(signature) + } + + /** + * Verify an arbitrary amount of messages with their signature created with their key pair + * + * @param publicKey Buffer The public key used to sign the messages + * @param messages Buffer[] The messages that have to be verified if they are signed + * @param signature Buffer The signature that has to be verified if it was created with the messages and public key + * + * @returns A boolean whether the signature is create with the public key over the messages + * + * @throws {SigningProviderError} When the message list is empty + * @throws {SigningProviderError} When the verification process failed + */ + public async verify({ data, publicKeyBase58, signature }: VerifyOptions): Promise { + if (data.length === 0) throw new SigningProviderError('Unable to create a signature without any messages') + // Check if it is a single message or list and if it is a single message convert it to a list + const normalizedMessages = (TypedArrayEncoder.isTypedArray(data) ? [data as Buffer] : data) as Buffer[] + + const publicKey = TypedArrayEncoder.fromBase58(publicKeyBase58) + + // Get the Uint8Array variant of all the messages + const messageBuffers = normalizedMessages.map((m) => Uint8Array.from(m)) + + const bbsKeyPair = await bls12381toBbs({ + keyPair: { publicKey: Uint8Array.from(publicKey) }, + messageCount: normalizedMessages.length, + }) + + // Verify the signature against the messages with their public key + const { verified, error } = await verify({ signature, messages: messageBuffers, publicKey: bbsKeyPair.publicKey }) + + // If the messages could not be verified and an error occurred + if (!verified && error) { + throw new SigningProviderError(`Could not verify the signature against the messages: ${error}`) + } + + return verified + } +} diff --git a/packages/core/src/crypto/signing-provider/SigningProvider.ts b/packages/core/src/crypto/signing-provider/SigningProvider.ts new file mode 100644 index 0000000000..2f2c31e701 --- /dev/null +++ b/packages/core/src/crypto/signing-provider/SigningProvider.ts @@ -0,0 +1,32 @@ +import type { Buffer } from '../../utils/buffer' +import type { KeyType } from '../KeyType' + +export interface KeyPair { + publicKeyBase58: string + privateKeyBase58: string + keyType: KeyType +} + +export interface SignOptions { + data: Buffer | Buffer[] + publicKeyBase58: string + privateKeyBase58: string +} + +export interface VerifyOptions { + data: Buffer | Buffer[] + publicKeyBase58: string + signature: Buffer +} + +export interface CreateKeyPairOptions { + seed?: string +} + +export interface SigningProvider { + readonly keyType: KeyType + + createKeyPair(options: CreateKeyPairOptions): Promise + sign(options: SignOptions): Promise + verify(options: VerifyOptions): Promise +} diff --git a/packages/core/src/crypto/signing-provider/SigningProviderError.ts b/packages/core/src/crypto/signing-provider/SigningProviderError.ts new file mode 100644 index 0000000000..21bcf0650b --- /dev/null +++ b/packages/core/src/crypto/signing-provider/SigningProviderError.ts @@ -0,0 +1,3 @@ +import { AriesFrameworkError } from '../../error' + +export class SigningProviderError extends AriesFrameworkError {} diff --git a/packages/core/src/crypto/signing-provider/SigningProviderRegistry.ts b/packages/core/src/crypto/signing-provider/SigningProviderRegistry.ts new file mode 100644 index 0000000000..993ae09547 --- /dev/null +++ b/packages/core/src/crypto/signing-provider/SigningProviderRegistry.ts @@ -0,0 +1,32 @@ +import type { KeyType } from '..' +import type { SigningProvider } from './SigningProvider' + +import { AriesFrameworkError } from '../../error' +import { injectable, injectAll } from '../../plugins' + +export const SigningProviderToken = Symbol('SigningProviderToken') + +@injectable() +export class SigningProviderRegistry { + private signingKeyProviders: SigningProvider[] + + public constructor(@injectAll(SigningProviderToken) signingKeyProviders: SigningProvider[]) { + this.signingKeyProviders = signingKeyProviders + } + + public hasProviderForKeyType(keyType: KeyType): boolean { + const signingKeyProvider = this.signingKeyProviders.find((x) => x.keyType === keyType) + + return signingKeyProvider !== undefined + } + + public getProviderForKeyType(keyType: KeyType): SigningProvider { + const signingKeyProvider = this.signingKeyProviders.find((x) => x.keyType === keyType) + + if (!signingKeyProvider) { + throw new AriesFrameworkError(`No signing key provider for key type: ${keyType}`) + } + + return signingKeyProvider + } +} diff --git a/packages/core/src/crypto/signing-provider/__tests__/SigningProviderRegistry.test.ts b/packages/core/src/crypto/signing-provider/__tests__/SigningProviderRegistry.test.ts new file mode 100644 index 0000000000..315b76f80a --- /dev/null +++ b/packages/core/src/crypto/signing-provider/__tests__/SigningProviderRegistry.test.ts @@ -0,0 +1,46 @@ +import type { Buffer } from '../../../utils/buffer' +import type { SigningProvider, KeyPair } from '../SigningProvider' + +import { KeyType } from '../../KeyType' +import { SigningProviderRegistry } from '../SigningProviderRegistry' + +class SigningProviderMock implements SigningProvider { + public readonly keyType = KeyType.Bls12381g2 + + public async createKeyPair(): Promise { + throw new Error('Method not implemented.') + } + public async sign(): Promise { + throw new Error('Method not implemented.') + } + public async verify(): Promise { + throw new Error('Method not implemented.') + } +} + +const signingProvider = new SigningProviderMock() +const signingProviderRegistry = new SigningProviderRegistry([signingProvider]) + +describe('SigningProviderRegistry', () => { + describe('hasProviderForKeyType', () => { + test('returns true if the key type is registered', () => { + expect(signingProviderRegistry.hasProviderForKeyType(KeyType.Bls12381g2)).toBe(true) + }) + + test('returns false if the key type is not registered', () => { + expect(signingProviderRegistry.hasProviderForKeyType(KeyType.Ed25519)).toBe(false) + }) + }) + + describe('getProviderForKeyType', () => { + test('returns the correct provider true if the key type is registered', () => { + expect(signingProviderRegistry.getProviderForKeyType(KeyType.Bls12381g2)).toBe(signingProvider) + }) + + test('throws error if the key type is not registered', () => { + expect(() => signingProviderRegistry.getProviderForKeyType(KeyType.Ed25519)).toThrowError( + 'No signing key provider for key type: ed25519' + ) + }) + }) +}) diff --git a/packages/core/src/crypto/signing-provider/index.ts b/packages/core/src/crypto/signing-provider/index.ts new file mode 100644 index 0000000000..165ca2ba92 --- /dev/null +++ b/packages/core/src/crypto/signing-provider/index.ts @@ -0,0 +1,4 @@ +export * from './Bls12381g2SigningProvider' +export * from './SigningProvider' +export * from './SigningProviderRegistry' +export * from './SigningProviderError' diff --git a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts index 0f216a372b..ec6c906818 100644 --- a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts +++ b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts @@ -1,4 +1,5 @@ import { getAgentConfig } from '../../../tests/helpers' +import { SigningProviderRegistry } from '../../crypto/signing-provider' import { IndyWallet } from '../../wallet/IndyWallet' import { SignatureDecorator } from './SignatureDecorator' @@ -41,7 +42,7 @@ describe('Decorators | Signature | SignatureDecoratorUtils', () => { beforeAll(async () => { const config = getAgentConfig('SignatureDecoratorUtilsTest') - wallet = new IndyWallet(config.agentDependencies, config.logger) + wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([])) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) }) diff --git a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index 9f16191403..25ade9e32d 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -15,6 +15,7 @@ import { AgentMessage } from '../../../agent/AgentMessage' import { EventEmitter } from '../../../agent/EventEmitter' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import { Key, KeyType } from '../../../crypto' +import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { signData, unpackAndVerifySignatureDecorator } from '../../../decorators/signature/SignatureDecoratorUtils' import { JsonTransformer } from '../../../utils/JsonTransformer' import { uuid } from '../../../utils/uuid' @@ -63,7 +64,7 @@ describe('ConnectionService', () => { let agentContext: AgentContext beforeAll(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger) + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) agentContext = getAgentContext({ wallet, agentConfig }) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) diff --git a/packages/core/src/modules/dids/__tests__/peer-did.test.ts b/packages/core/src/modules/dids/__tests__/peer-did.test.ts index 98a772fb07..38f5747b17 100644 --- a/packages/core/src/modules/dids/__tests__/peer-did.test.ts +++ b/packages/core/src/modules/dids/__tests__/peer-did.test.ts @@ -6,6 +6,7 @@ import { Subject } from 'rxjs' import { getAgentConfig, getAgentContext } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' import { Key, KeyType } from '../../../crypto' +import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { IndyStorageService } from '../../../storage/IndyStorageService' import { JsonTransformer } from '../../../utils' import { IndyWallet } from '../../../wallet/IndyWallet' @@ -31,7 +32,7 @@ describe('peer dids', () => { let eventEmitter: EventEmitter beforeEach(async () => { - wallet = new IndyWallet(config.agentDependencies, config.logger) + wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([])) agentContext = getAgentContext({ wallet }) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) diff --git a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts b/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts index 85d29438a3..74636089b3 100644 --- a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts +++ b/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts @@ -7,6 +7,7 @@ import { Subject } from 'rxjs' import { NodeFileSystem } from '../../../../../node/src/NodeFileSystem' import { getAgentConfig, getAgentContext, mockFunction } from '../../../../tests/helpers' import { CacheRepository } from '../../../cache/CacheRepository' +import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { IndyWallet } from '../../../wallet/IndyWallet' import { IndyIssuerService } from '../../indy/services/IndyIssuerService' import { IndyPool } from '../IndyPool' @@ -41,7 +42,7 @@ describe('IndyLedgerService', () => { let ledgerService: IndyLedgerService beforeAll(async () => { - wallet = new IndyWallet(config.agentDependencies, config.logger) + wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([])) agentContext = getAgentContext() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) diff --git a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts b/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts index eebbe4332c..3e6a65b96b 100644 --- a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts +++ b/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts @@ -8,6 +8,7 @@ import { NodeFileSystem } from '../../../../../node/src/NodeFileSystem' import { agentDependencies, getAgentConfig, getAgentContext, mockFunction } from '../../../../tests/helpers' import { CacheRecord } from '../../../cache' import { CacheRepository } from '../../../cache/CacheRepository' +import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' import { IndyWallet } from '../../../wallet/IndyWallet' import { LedgerError } from '../error/LedgerError' @@ -63,7 +64,7 @@ describe('IndyPoolService', () => { let cacheRepository: CacheRepository beforeAll(async () => { - wallet = new IndyWallet(config.agentDependencies, config.logger) + wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([])) agentContext = getAgentContext() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) diff --git a/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts b/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts index e33c6e3c41..9be7b6167a 100644 --- a/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts +++ b/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts @@ -4,6 +4,7 @@ import type { CredentialDefinitionTemplate } from '../services/IndyLedgerService import type * as Indy from 'indy-sdk' import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../tests/helpers' +import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' import { IndyWallet } from '../../../wallet/IndyWallet' import { AnonCredsCredentialDefinitionRecord } from '../../indy/repository/AnonCredsCredentialDefinitionRecord' @@ -107,7 +108,7 @@ describe('LedgerModule', () => { }) beforeEach(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger) + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) }) diff --git a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts index d5c1e358ad..553ed826b9 100644 --- a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts +++ b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts @@ -14,6 +14,7 @@ import { import { EventEmitter } from '../../../agent/EventEmitter' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import { KeyType, Key } from '../../../crypto' +import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { AriesFrameworkError } from '../../../error' import { IndyWallet } from '../../../wallet/IndyWallet' import { DidExchangeState } from '../../connections/models' @@ -39,7 +40,7 @@ describe('OutOfBandService', () => { let agentContext: AgentContext beforeAll(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger) + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) agentContext = getAgentContext() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) diff --git a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts index 1820debc06..fa5f57f86e 100644 --- a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts +++ b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts @@ -14,6 +14,7 @@ import { mockFunction, } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' +import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { IndyWallet } from '../../../wallet/IndyWallet' import { QuestionAnswerEventTypes } from '../QuestionAnswerEvents' import { QuestionAnswerRole } from '../QuestionAnswerRole' @@ -62,7 +63,7 @@ describe('QuestionAnswerService', () => { beforeAll(async () => { agentConfig = getAgentConfig('QuestionAnswerServiceTest') - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger) + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) agentContext = getAgentContext() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) diff --git a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts index 92f1cd2141..76c51345f1 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts @@ -8,6 +8,7 @@ import { AgentEventTypes } from '../../../../agent/Events' import { MessageSender } from '../../../../agent/MessageSender' import { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' import { Key } from '../../../../crypto' +import { SigningProviderRegistry } from '../../../../crypto/signing-provider' import { Attachment } from '../../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../../error' import { uuid } from '../../../../utils/uuid' @@ -62,7 +63,7 @@ describe('MediationRecipientService', () => { let agentContext: AgentContext beforeAll(async () => { - wallet = new IndyWallet(config.agentDependencies, config.logger) + wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([])) agentContext = getAgentContext({ agentConfig: config, }) diff --git a/packages/core/src/modules/vc/SignatureSuiteRegistry.ts b/packages/core/src/modules/vc/SignatureSuiteRegistry.ts index 0d5404aa44..925fa3f334 100644 --- a/packages/core/src/modules/vc/SignatureSuiteRegistry.ts +++ b/packages/core/src/modules/vc/SignatureSuiteRegistry.ts @@ -1,12 +1,13 @@ -import { KeyType } from '../../crypto' +import type { KeyType } from '../../crypto' + import { AriesFrameworkError } from '../../error' +import { injectable, injectAll } from '../../plugins' import { suites } from './libraries/jsonld-signatures' -import { Ed25519Signature2018 } from './signature-suites' -import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from './signature-suites/bbs' const LinkedDataSignature = suites.LinkedDataSignature +export const SignatureSuiteToken = Symbol('SignatureSuiteToken') export interface SuiteInfo { suiteClass: typeof LinkedDataSignature proofType: string @@ -14,27 +15,13 @@ export interface SuiteInfo { keyType: string } +@injectable() export class SignatureSuiteRegistry { - private suiteMapping: SuiteInfo[] = [ - { - suiteClass: Ed25519Signature2018, - proofType: 'Ed25519Signature2018', - requiredKeyType: 'Ed25519VerificationKey2018', - keyType: KeyType.Ed25519, - }, - { - suiteClass: BbsBlsSignature2020, - proofType: 'BbsBlsSignature2020', - requiredKeyType: 'BbsBlsSignatureProof2020', - keyType: KeyType.Bls12381g2, - }, - { - suiteClass: BbsBlsSignatureProof2020, - proofType: 'BbsBlsSignatureProof2020', - requiredKeyType: 'BbsBlsSignatureProof2020', - keyType: KeyType.Bls12381g2, - }, - ] + private suiteMapping: SuiteInfo[] + + public constructor(@injectAll(SignatureSuiteToken) suites: SuiteInfo[]) { + this.suiteMapping = suites + } public get supportedProofTypes(): string[] { return this.suiteMapping.map((x) => x.proofType) diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index e46260224f..a8ba4e704d 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -37,10 +37,14 @@ export class W3cCredentialService { private didResolver: DidResolverService private suiteRegistry: SignatureSuiteRegistry - public constructor(w3cCredentialRepository: W3cCredentialRepository, didResolver: DidResolverService) { + public constructor( + w3cCredentialRepository: W3cCredentialRepository, + didResolver: DidResolverService, + suiteRegistry: SignatureSuiteRegistry + ) { this.w3cCredentialRepository = w3cCredentialRepository this.didResolver = didResolver - this.suiteRegistry = new SignatureSuiteRegistry() + this.suiteRegistry = suiteRegistry } /** diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index b9a8e9d2a8..0d133a18f5 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -3,12 +3,14 @@ import type { AgentContext } from '../../../agent' import { getAgentConfig, getAgentContext } from '../../../../tests/helpers' import { KeyType } from '../../../crypto' import { Key } from '../../../crypto/Key' +import { Bls12381g2SigningProvider, SigningProviderRegistry } from '../../../crypto/signing-provider' import { JsonTransformer } from '../../../utils/JsonTransformer' import { IndyWallet } from '../../../wallet/IndyWallet' import { WalletError } from '../../../wallet/error' import { DidKey, DidResolverService } from '../../dids' import { DidRepository } from '../../dids/repository' import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' +import { SignatureSuiteRegistry } from '../SignatureSuiteRegistry' import { W3cCredentialService } from '../W3cCredentialService' import { orArrayToArray } from '../jsonldUtil' import { purposes } from '../libraries/jsonld-signatures' @@ -18,10 +20,35 @@ import { W3cPresentation } from '../models/presentation/W3Presentation' import { W3cVerifiablePresentation } from '../models/presentation/W3cVerifiablePresentation' import { CredentialIssuancePurpose } from '../proof-purposes/CredentialIssuancePurpose' import { W3cCredentialRepository } from '../repository/W3cCredentialRepository' +import { Ed25519Signature2018 } from '../signature-suites' +import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from '../signature-suites/bbs' import { customDocumentLoader } from './documentLoader' import { BbsBlsSignature2020Fixtures, Ed25519Signature2018Fixtures } from './fixtures' +const signatureSuiteRegistry = new SignatureSuiteRegistry([ + { + suiteClass: Ed25519Signature2018, + proofType: 'Ed25519Signature2018', + requiredKeyType: 'Ed25519VerificationKey2018', + keyType: KeyType.Ed25519, + }, + { + suiteClass: BbsBlsSignature2020, + proofType: 'BbsBlsSignature2020', + requiredKeyType: 'BbsBlsSignatureProof2020', + keyType: KeyType.Bls12381g2, + }, + { + suiteClass: BbsBlsSignatureProof2020, + proofType: 'BbsBlsSignatureProof2020', + requiredKeyType: 'BbsBlsSignatureProof2020', + keyType: KeyType.Bls12381g2, + }, +]) + +const signingProviderRegistry = new SigningProviderRegistry([new Bls12381g2SigningProvider()]) + jest.mock('../../ledger/services/IndyLedgerService') const IndyLedgerServiceMock = IndyLedgerService as jest.Mock @@ -41,7 +68,7 @@ describe('W3cCredentialService', () => { const seed = 'testseed000000000000000000000001' beforeAll(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger) + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, signingProviderRegistry) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) agentContext = getAgentContext({ @@ -54,7 +81,7 @@ describe('W3cCredentialService', () => { agentConfig.logger ) w3cCredentialRepository = new W3cCredentialRepositoryMock() - w3cCredentialService = new W3cCredentialService(w3cCredentialRepository, didResolverService) + w3cCredentialService = new W3cCredentialService(w3cCredentialRepository, didResolverService, signatureSuiteRegistry) w3cCredentialService.documentLoaderWithContext = () => customDocumentLoader }) diff --git a/packages/core/src/modules/vc/module.ts b/packages/core/src/modules/vc/module.ts index 11e4983e05..f9102217e3 100644 --- a/packages/core/src/modules/vc/module.ts +++ b/packages/core/src/modules/vc/module.ts @@ -1,14 +1,42 @@ import type { DependencyManager } from '../../plugins' +import { KeyType } from '../../crypto' import { module } from '../../plugins' +import { SignatureSuiteRegistry, SignatureSuiteToken } from './SignatureSuiteRegistry' import { W3cCredentialService } from './W3cCredentialService' import { W3cCredentialRepository } from './repository/W3cCredentialRepository' +import { Ed25519Signature2018 } from './signature-suites' +import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from './signature-suites/bbs' @module() export class W3cVcModule { public static register(dependencyManager: DependencyManager) { dependencyManager.registerSingleton(W3cCredentialService) dependencyManager.registerSingleton(W3cCredentialRepository) + + dependencyManager.registerSingleton(SignatureSuiteRegistry) + + // Always register ed25519 signature suite + dependencyManager.registerInstance(SignatureSuiteToken, { + suiteClass: Ed25519Signature2018, + proofType: 'Ed25519Signature2018', + requiredKeyType: 'Ed25519VerificationKey2018', + keyType: KeyType.Ed25519, + }) + + // This will be moved out of core into the bbs module + dependencyManager.registerInstance(SignatureSuiteToken, { + suiteClass: BbsBlsSignature2020, + proofType: 'BbsBlsSignature2020', + requiredKeyType: 'BbsBlsSignatureProof2020', + keyType: KeyType.Bls12381g2, + }) + dependencyManager.registerInstance(SignatureSuiteToken, { + suiteClass: BbsBlsSignatureProof2020, + proofType: 'BbsBlsSignatureProof2020', + requiredKeyType: 'BbsBlsSignatureProof2020', + keyType: KeyType.Bls12381g2, + }) } } diff --git a/packages/core/src/plugins/index.ts b/packages/core/src/plugins/index.ts index 27b41e4d10..faee88c0f1 100644 --- a/packages/core/src/plugins/index.ts +++ b/packages/core/src/plugins/index.ts @@ -1,3 +1,3 @@ export * from './DependencyManager' export * from './Module' -export { inject, injectable, Disposable } from 'tsyringe' +export { inject, injectable, Disposable, injectAll } from 'tsyringe' diff --git a/packages/core/src/storage/__tests__/IndyStorageService.test.ts b/packages/core/src/storage/__tests__/IndyStorageService.test.ts index 08f2df3fa7..bd61553b08 100644 --- a/packages/core/src/storage/__tests__/IndyStorageService.test.ts +++ b/packages/core/src/storage/__tests__/IndyStorageService.test.ts @@ -3,6 +3,7 @@ import type { TagsBase } from '../BaseRecord' import type * as Indy from 'indy-sdk' import { agentDependencies, getAgentConfig, getAgentContext } from '../../../tests/helpers' +import { SigningProviderRegistry } from '../../crypto/signing-provider' import { RecordDuplicateError, RecordNotFoundError } from '../../error' import { IndyWallet } from '../../wallet/IndyWallet' import { IndyStorageService } from '../IndyStorageService' @@ -18,7 +19,7 @@ describe('IndyStorageService', () => { beforeEach(async () => { const agentConfig = getAgentConfig('IndyStorageServiceTest') indy = agentConfig.agentDependencies.indy - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger) + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) agentContext = getAgentContext({ wallet, agentConfig, diff --git a/packages/core/src/wallet/IndyWallet.test.ts b/packages/core/src/wallet/IndyWallet.test.ts index a59cd82f60..c2ab276035 100644 --- a/packages/core/src/wallet/IndyWallet.test.ts +++ b/packages/core/src/wallet/IndyWallet.test.ts @@ -6,6 +6,7 @@ import { SIGNATURE_LENGTH as ED25519_SIGNATURE_LENGTH } from '@stablelib/ed25519 import { agentDependencies } from '../../tests/helpers' import testLogger from '../../tests/logger' import { KeyType } from '../crypto' +import { Bls12381g2SigningProvider, SigningProviderRegistry } from '../crypto/signing-provider' import { KeyDerivationMethod } from '../types' import { TypedArrayEncoder } from '../utils' @@ -26,7 +27,11 @@ describe('IndyWallet', () => { const message = TypedArrayEncoder.fromString('sample-message') beforeEach(async () => { - indyWallet = new IndyWallet(agentDependencies, testLogger) + indyWallet = new IndyWallet( + agentDependencies, + testLogger, + new SigningProviderRegistry([new Bls12381g2SigningProvider()]) + ) await indyWallet.createAndOpen(walletConfig) }) @@ -79,13 +84,6 @@ describe('IndyWallet', () => { }) }) - test('Create blsg12381g1 keypair', async () => { - await expect(indyWallet.createKey({ seed, keyType: KeyType.Bls12381g1 })).resolves.toMatchObject({ - publicKeyBase58: '6RhvX1RK5rA9uXdTtV6WvHWNQqcCW86BQxz1aBPr6ebBcppCYMD3LLy7QLg4cGcWaq', - keyType: KeyType.Bls12381g1, - }) - }) - test('Create bls12381g2 keypair', async () => { await expect(indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 })).resolves.toMatchObject({ publicKeyBase58: @@ -120,16 +118,6 @@ describe('IndyWallet', () => { expect(signature.length).toStrictEqual(BBS_SIGNATURE_LENGTH) }) - test('Fail to create a signature with a bls12381g1 keypair', async () => { - const bls12381g1Key = await indyWallet.createKey({ seed, keyType: KeyType.Bls12381g1 }) - await expect( - indyWallet.sign({ - data: message, - key: bls12381g1Key, - }) - ).rejects.toThrowError(WalletError) - }) - test('Verify a signed message with a ed25519 publicKey', async () => { const ed25519Key = await indyWallet.createKey({ keyType: KeyType.Ed25519 }) const signature = await indyWallet.sign({ diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts index 493cfd4707..0e1462cb3e 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/core/src/wallet/IndyWallet.ts @@ -1,4 +1,4 @@ -import type { BlsKeyPair } from '../crypto/BbsService' +import type { KeyPair } from '../crypto/signing-provider/SigningProvider' import type { EncryptedMessage, KeyDerivationMethod, @@ -20,9 +20,9 @@ import type { default as Indy, WalletStorageConfig } from 'indy-sdk' import { AgentDependencies } from '../agent/AgentDependencies' import { InjectionSymbols } from '../constants' -import { BbsService } from '../crypto/BbsService' import { Key } from '../crypto/Key' import { KeyType } from '../crypto/KeyType' +import { SigningProviderRegistry } from '../crypto/signing-provider/SigningProviderRegistry' import { AriesFrameworkError, IndySdkError, RecordDuplicateError, RecordNotFoundError } from '../error' import { Logger } from '../logger' import { inject, injectable } from '../plugins' @@ -39,14 +39,17 @@ export class IndyWallet implements Wallet { private walletHandle?: number private logger: Logger + private signingKeyProviderRegistry: SigningProviderRegistry private publicDidInfo: DidInfo | undefined private indy: typeof Indy public constructor( @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, - @inject(InjectionSymbols.Logger) logger: Logger + @inject(InjectionSymbols.Logger) logger: Logger, + signingKeyProviderRegistry: SigningProviderRegistry ) { this.logger = logger + this.signingKeyProviderRegistry = signingKeyProviderRegistry this.indy = agentDependencies.indy } @@ -467,6 +470,7 @@ export class IndyWallet implements Wallet { */ public async createKey({ seed, keyType }: CreateKeyOptions): Promise { try { + // Ed25519 is supported natively in Indy wallet if (keyType === KeyType.Ed25519) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore @@ -474,10 +478,13 @@ export class IndyWallet implements Wallet { return Key.fromPublicKeyBase58(verkey, keyType) } - if (keyType === KeyType.Bls12381g1 || keyType === KeyType.Bls12381g2) { - const blsKeyPair = await BbsService.createKey({ keyType, seed }) - await this.storeKeyPair(blsKeyPair) - return Key.fromPublicKeyBase58(blsKeyPair.publicKeyBase58, keyType) + // Check if there is a signing key provider for the specified key type. + if (this.signingKeyProviderRegistry.hasProviderForKeyType(keyType)) { + const signingKeyProvider = this.signingKeyProviderRegistry.getProviderForKeyType(keyType) + + const keyPair = await signingKeyProvider.createKeyPair({ seed }) + await this.storeKeyPair(keyPair) + return Key.fromPublicKeyBase58(keyPair.publicKeyBase58, keyType) } } catch (error) { if (!isError(error)) { @@ -501,6 +508,7 @@ export class IndyWallet implements Wallet { */ public async sign({ data, key }: SignOptions): Promise { try { + // Ed25519 is supported natively in Indy wallet if (key.keyType === KeyType.Ed25519) { // Checks to see if it is an not an Array of messages, but just a single one if (!TypedArrayEncoder.isTypedArray(data)) { @@ -509,13 +517,18 @@ export class IndyWallet implements Wallet { return await this.indy.cryptoSign(this.handle, key.publicKeyBase58, data as Buffer) } - if (key.keyType === KeyType.Bls12381g2) { - const blsKeyPair = await this.retrieveKeyPair(key.publicKeyBase58) - return BbsService.sign({ - messages: data, - publicKey: key.publicKey, - privateKey: TypedArrayEncoder.fromBase58(blsKeyPair.privateKeyBase58), + // Check if there is a signing key provider for the specified key type. + if (this.signingKeyProviderRegistry.hasProviderForKeyType(key.keyType)) { + const signingKeyProvider = this.signingKeyProviderRegistry.getProviderForKeyType(key.keyType) + + const keyPair = await this.retrieveKeyPair(key.publicKeyBase58) + const signed = await signingKeyProvider.sign({ + data, + privateKeyBase58: keyPair.privateKeyBase58, + publicKeyBase58: key.publicKeyBase58, }) + + return signed } } catch (error) { if (!isError(error)) { @@ -542,6 +555,7 @@ export class IndyWallet implements Wallet { */ public async verify({ data, key, signature }: VerifyOptions): Promise { try { + // Ed25519 is supported natively in Indy wallet if (key.keyType === KeyType.Ed25519) { // Checks to see if it is an not an Array of messages, but just a single one if (!TypedArrayEncoder.isTypedArray(data)) { @@ -550,8 +564,17 @@ export class IndyWallet implements Wallet { return await this.indy.cryptoVerify(key.publicKeyBase58, data as Buffer, signature) } - if (key.keyType === KeyType.Bls12381g2) { - return await BbsService.verify({ signature, publicKey: key.publicKey, messages: data }) + // Check if there is a signing key provider for the specified key type. + if (this.signingKeyProviderRegistry.hasProviderForKeyType(key.keyType)) { + const signingKeyProvider = this.signingKeyProviderRegistry.getProviderForKeyType(key.keyType) + + const signed = await signingKeyProvider.verify({ + data, + signature, + publicKeyBase58: key.publicKeyBase58, + }) + + return signed } } catch (error) { if (!isError(error)) { @@ -609,11 +632,11 @@ export class IndyWallet implements Wallet { } } - private async retrieveKeyPair(publicKeyBase58: string): Promise { + private async retrieveKeyPair(publicKeyBase58: string): Promise { try { - const { value } = await this.indy.getWalletRecord(this.handle, 'KeyPairRecord', `keypair-${publicKeyBase58}`, {}) + const { value } = await this.indy.getWalletRecord(this.handle, 'KeyPairRecord', `key-${publicKeyBase58}`, {}) if (value) { - return JsonEncoder.fromString(value) as BlsKeyPair + return JsonEncoder.fromString(value) as KeyPair } else { throw new WalletError(`No content found for record with public key: ${publicKeyBase58}`) } @@ -628,14 +651,16 @@ export class IndyWallet implements Wallet { } } - private async storeKeyPair(blsKeyPair: BlsKeyPair): Promise { + private async storeKeyPair(keyPair: KeyPair): Promise { try { await this.indy.addWalletRecord( this.handle, 'KeyPairRecord', - `keypair-${blsKeyPair.publicKeyBase58}`, - JSON.stringify(blsKeyPair), - {} + `key-${keyPair.publicKeyBase58}`, + JSON.stringify(keyPair), + { + keyType: keyPair.keyType, + } ) } catch (error) { if (isIndyError(error, 'WalletItemAlreadyExists')) { diff --git a/packages/core/src/wallet/WalletModule.ts b/packages/core/src/wallet/WalletModule.ts index 7d4bd0f739..2c318a23c8 100644 --- a/packages/core/src/wallet/WalletModule.ts +++ b/packages/core/src/wallet/WalletModule.ts @@ -4,6 +4,7 @@ import type { Wallet } from './Wallet' import { AgentContext } from '../agent' import { InjectionSymbols } from '../constants' +import { Bls12381g2SigningProvider, SigningProviderToken } from '../crypto/signing-provider' import { Logger } from '../logger' import { inject, injectable, module } from '../plugins' import { StorageUpdateService } from '../storage' @@ -114,5 +115,8 @@ export class WalletModule { public static register(dependencyManager: DependencyManager) { // Api dependencyManager.registerContextScoped(WalletModule) + + // Signing providers. + dependencyManager.registerSingleton(SigningProviderToken, Bls12381g2SigningProvider) } } From 7cbccb1ce9dae2cb1e4887220898f2f74cca8dbe Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 19 Jul 2022 11:49:24 +0200 Subject: [PATCH 016/125] refactor!: module to api and module config (#943) Signed-off-by: Timo Glastra BREAKING CHANGE: All module api classes have been renamed from `XXXModule` to `XXXApi`. A module now represents a module plugin, and is separate from the API of a module. If you previously imported e.g. the `CredentialsModule` class, you should now import the `CredentialsApi` class --- packages/core/src/agent/Agent.ts | 89 ++- packages/core/src/agent/AgentConfig.ts | 45 ++ packages/core/src/agent/BaseAgent.ts | 78 +- .../core/src/agent/__tests__/Agent.test.ts | 42 +- packages/core/src/index.ts | 2 +- .../basic-messages/BasicMessagesApi.ts | 49 ++ .../basic-messages/BasicMessagesModule.ts | 55 +- .../__tests__/BasicMessagesModule.test.ts | 23 + .../core/src/modules/basic-messages/index.ts | 3 +- .../src/modules/connections/ConnectionsApi.ts | 312 ++++++++ .../modules/connections/ConnectionsModule.ts | 313 +------- .../connections/ConnectionsModuleConfig.ts | 26 + .../__tests__/ConnectionsModule.test.ts | 31 + .../__tests__/ConnectionsModuleConfig.test.ts | 17 + .../handlers/ConnectionRequestHandler.ts | 8 +- .../handlers/ConnectionResponseHandler.ts | 8 +- .../handlers/DidExchangeRequestHandler.ts | 8 +- .../handlers/DidExchangeResponseHandler.ts | 8 +- .../core/src/modules/connections/index.ts | 4 +- .../src/modules/credentials/CredentialsApi.ts | 634 ++++++++++++++++ ...uleOptions.ts => CredentialsApiOptions.ts} | 20 +- .../modules/credentials/CredentialsModule.ts | 642 +--------------- .../credentials/CredentialsModuleConfig.ts | 27 + .../__tests__/CredentialsModule.test.ts | 33 + .../__tests__/CredentialsModuleConfig.test.ts | 18 + .../formats/indy/IndyCredentialFormat.ts | 10 +- .../core/src/modules/credentials/index.ts | 7 +- .../models/CredentialAutoAcceptType.ts | 6 +- .../protocol/v1/V1CredentialService.ts | 16 +- .../__tests__/V1CredentialServiceCred.test.ts | 4 +- .../V1CredentialServiceProposeOffer.test.ts | 4 +- .../v1-connectionless-credentials.e2e.test.ts | 2 +- .../v1-credentials-auto-accept.e2e.test.ts | 2 +- .../v1/handlers/V1IssueCredentialHandler.ts | 4 +- .../v1/handlers/V1OfferCredentialHandler.ts | 4 +- .../v1/handlers/V1ProposeCredentialHandler.ts | 4 +- .../v1/handlers/V1RequestCredentialHandler.ts | 4 +- .../protocol/v2/V2CredentialService.ts | 14 +- .../__tests__/V2CredentialServiceCred.test.ts | 4 +- .../V2CredentialServiceOffer.test.ts | 4 +- .../v2-connectionless-credentials.e2e.test.ts | 2 +- .../v2-credentials-auto-accept.e2e.test.ts | 2 +- .../v2/handlers/V2IssueCredentialHandler.ts | 4 +- .../v2/handlers/V2OfferCredentialHandler.ts | 4 +- .../v2/handlers/V2ProposeCredentialHandler.ts | 4 +- .../v2/handlers/V2RequestCredentialHandler.ts | 4 +- packages/core/src/modules/dids/DidsApi.ts | 37 + packages/core/src/modules/dids/DidsModule.ts | 44 +- .../modules/dids/__tests__/DidsModule.test.ts | 23 + packages/core/src/modules/dids/index.ts | 3 +- .../discover-features/DiscoverFeaturesApi.ts | 101 +++ .../DiscoverFeaturesModule.ts | 107 +-- .../__tests__/DiscoverFeaturesModule.test.ts | 21 + .../src/modules/discover-features/index.ts | 3 +- .../generic-records/GenericRecordsApi.ts | 89 +++ .../generic-records/GenericRecordsModule.ts | 97 +-- .../__tests__/GenericRecordsModule.test.ts | 23 + .../core/src/modules/generic-records/index.ts | 2 + .../GenericRecordService.ts | 0 .../modules/indy/{module.ts => IndyModule.ts} | 9 +- .../modules/indy/__tests__/IndyModule.test.ts | 27 + packages/core/src/modules/indy/index.ts | 1 + packages/core/src/modules/ledger/LedgerApi.ts | 166 ++++ .../core/src/modules/ledger/LedgerModule.ts | 175 +---- .../src/modules/ledger/LedgerModuleConfig.ts | 43 ++ .../ledger/__tests__/LedgerApi.test.ts | 368 +++++++++ .../ledger/__tests__/LedgerModule.test.ts | 377 +-------- packages/core/src/modules/ledger/index.ts | 3 +- packages/core/src/modules/oob/OutOfBandApi.ts | 709 +++++++++++++++++ .../core/src/modules/oob/OutOfBandModule.ts | 714 +----------------- .../oob/__tests__/OutOfBandModule.test.ts | 23 + packages/core/src/modules/oob/index.ts | 3 +- .../proofs/ProofResponseCoordinator.ts | 12 +- packages/core/src/modules/proofs/ProofsApi.ts | 541 +++++++++++++ .../core/src/modules/proofs/ProofsModule.ts | 552 +------------- .../src/modules/proofs/ProofsModuleConfig.ts | 27 + .../proofs/__tests__/ProofsModule.test.ts | 23 + .../proofs/handlers/PresentationHandler.ts | 4 +- .../handlers/ProposePresentationHandler.ts | 4 +- .../handlers/RequestPresentationHandler.ts | 4 +- packages/core/src/modules/proofs/index.ts | 3 +- .../question-answer/QuestionAnswerApi.ts | 105 +++ .../question-answer/QuestionAnswerModule.ts | 111 +-- .../__tests__/QuestionAnswerModule.test.ts | 23 + .../core/src/modules/question-answer/index.ts | 3 +- .../core/src/modules/routing/MediatorApi.ts | 91 +++ .../src/modules/routing/MediatorModule.ts | 101 +-- .../modules/routing/MediatorModuleConfig.ts | 25 + .../core/src/modules/routing/RecipientApi.ts | 405 ++++++++++ .../src/modules/routing/RecipientModule.ts | 412 +--------- .../modules/routing/RecipientModuleConfig.ts | 74 ++ .../routing/__tests__/MediatorModule.test.ts | 27 + .../routing/__tests__/RecipientModule.test.ts | 24 + .../handlers/MediationRequestHandler.ts | 7 +- packages/core/src/modules/routing/index.ts | 4 +- .../services/MediationRecipientService.ts | 8 +- .../MediationRecipientService.test.ts | 4 +- .../modules/vc/{module.ts => W3cVcModule.ts} | 8 +- .../modules/vc/__tests__/W3cVcModule.test.ts | 46 ++ packages/core/src/modules/vc/index.ts | 1 + packages/core/src/plugins/Module.ts | 5 +- .../__tests__/DependencyManager.test.ts | 26 +- .../src/storage/migration/UpdateAssistant.ts | 13 + packages/core/src/wallet/WalletApi.ts | 108 +++ packages/core/src/wallet/WalletModule.ts | 117 +-- .../src/wallet/__tests__/WalletModule.test.ts | 17 + packages/core/src/wallet/index.ts | 2 + packages/module-tenants/src/TenantsModule.ts | 20 +- .../module-tenants/src/TenantsModuleConfig.ts | 42 ++ .../src/__tests__/TenantsModule.test.ts | 7 +- .../src/__tests__/TenantsModuleConfig.test.ts | 20 + .../src/context/TenantSessionCoordinator.ts | 27 +- .../TenantSessionCoordinator.test.ts | 10 +- packages/module-tenants/src/index.ts | 1 + .../tests/tenant-sessions.e2e.test.ts | 4 +- .../module-tenants/tests/tenants.e2e.test.ts | 4 +- samples/extension-module/README.md | 2 +- .../dummy/{module.ts => DummyModule.ts} | 9 +- samples/extension-module/dummy/index.ts | 2 +- samples/extension-module/requester.ts | 2 +- samples/extension-module/responder.ts | 2 +- 121 files changed, 4941 insertions(+), 3920 deletions(-) create mode 100644 packages/core/src/modules/basic-messages/BasicMessagesApi.ts create mode 100644 packages/core/src/modules/basic-messages/__tests__/BasicMessagesModule.test.ts create mode 100644 packages/core/src/modules/connections/ConnectionsApi.ts create mode 100644 packages/core/src/modules/connections/ConnectionsModuleConfig.ts create mode 100644 packages/core/src/modules/connections/__tests__/ConnectionsModule.test.ts create mode 100644 packages/core/src/modules/connections/__tests__/ConnectionsModuleConfig.test.ts create mode 100644 packages/core/src/modules/credentials/CredentialsApi.ts rename packages/core/src/modules/credentials/{CredentialsModuleOptions.ts => CredentialsApiOptions.ts} (84%) create mode 100644 packages/core/src/modules/credentials/CredentialsModuleConfig.ts create mode 100644 packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts create mode 100644 packages/core/src/modules/credentials/__tests__/CredentialsModuleConfig.test.ts create mode 100644 packages/core/src/modules/dids/DidsApi.ts create mode 100644 packages/core/src/modules/dids/__tests__/DidsModule.test.ts create mode 100644 packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts create mode 100644 packages/core/src/modules/discover-features/__tests__/DiscoverFeaturesModule.test.ts create mode 100644 packages/core/src/modules/generic-records/GenericRecordsApi.ts create mode 100644 packages/core/src/modules/generic-records/__tests__/GenericRecordsModule.test.ts create mode 100644 packages/core/src/modules/generic-records/index.ts rename packages/core/src/modules/generic-records/{service => services}/GenericRecordService.ts (100%) rename packages/core/src/modules/indy/{module.ts => IndyModule.ts} (74%) create mode 100644 packages/core/src/modules/indy/__tests__/IndyModule.test.ts create mode 100644 packages/core/src/modules/ledger/LedgerApi.ts create mode 100644 packages/core/src/modules/ledger/LedgerModuleConfig.ts create mode 100644 packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts create mode 100644 packages/core/src/modules/oob/OutOfBandApi.ts create mode 100644 packages/core/src/modules/oob/__tests__/OutOfBandModule.test.ts create mode 100644 packages/core/src/modules/proofs/ProofsApi.ts create mode 100644 packages/core/src/modules/proofs/ProofsModuleConfig.ts create mode 100644 packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts create mode 100644 packages/core/src/modules/question-answer/QuestionAnswerApi.ts create mode 100644 packages/core/src/modules/question-answer/__tests__/QuestionAnswerModule.test.ts create mode 100644 packages/core/src/modules/routing/MediatorApi.ts create mode 100644 packages/core/src/modules/routing/MediatorModuleConfig.ts create mode 100644 packages/core/src/modules/routing/RecipientApi.ts create mode 100644 packages/core/src/modules/routing/RecipientModuleConfig.ts create mode 100644 packages/core/src/modules/routing/__tests__/MediatorModule.test.ts create mode 100644 packages/core/src/modules/routing/__tests__/RecipientModule.test.ts rename packages/core/src/modules/vc/{module.ts => W3cVcModule.ts} (87%) create mode 100644 packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts create mode 100644 packages/core/src/wallet/WalletApi.ts create mode 100644 packages/core/src/wallet/__tests__/WalletModule.test.ts create mode 100644 packages/module-tenants/src/TenantsModuleConfig.ts create mode 100644 packages/module-tenants/src/__tests__/TenantsModuleConfig.test.ts rename samples/extension-module/dummy/{module.ts => DummyModule.ts} (56%) diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index a6c9601e7d..f0944078ff 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -12,26 +12,25 @@ import { CacheRepository } from '../cache' import { InjectionSymbols } from '../constants' import { JwsService } from '../crypto/JwsService' import { AriesFrameworkError } from '../error' -import { BasicMessagesModule } from '../modules/basic-messages/BasicMessagesModule' -import { ConnectionsModule } from '../modules/connections/ConnectionsModule' -import { CredentialsModule } from '../modules/credentials/CredentialsModule' -import { DidsModule } from '../modules/dids/DidsModule' +import { BasicMessagesModule } from '../modules/basic-messages' +import { ConnectionsModule } from '../modules/connections' +import { CredentialsModule } from '../modules/credentials' +import { DidsModule } from '../modules/dids' import { DiscoverFeaturesModule } from '../modules/discover-features' -import { GenericRecordsModule } from '../modules/generic-records/GenericRecordsModule' -import { IndyModule } from '../modules/indy/module' -import { LedgerModule } from '../modules/ledger/LedgerModule' -import { OutOfBandModule } from '../modules/oob/OutOfBandModule' -import { ProofsModule } from '../modules/proofs/ProofsModule' -import { QuestionAnswerModule } from '../modules/question-answer/QuestionAnswerModule' -import { MediatorModule } from '../modules/routing/MediatorModule' -import { RecipientModule } from '../modules/routing/RecipientModule' -import { W3cVcModule } from '../modules/vc/module' +import { GenericRecordsModule } from '../modules/generic-records' +import { IndyModule } from '../modules/indy' +import { LedgerModule } from '../modules/ledger' +import { OutOfBandModule } from '../modules/oob' +import { ProofsModule } from '../modules/proofs' +import { QuestionAnswerModule } from '../modules/question-answer' +import { MediatorModule, RecipientModule } from '../modules/routing' +import { W3cVcModule } from '../modules/vc' import { DependencyManager } from '../plugins' import { DidCommMessageRepository, StorageUpdateService, StorageVersionRepository } from '../storage' import { InMemoryMessageRepository } from '../storage/InMemoryMessageRepository' import { IndyStorageService } from '../storage/IndyStorageService' +import { WalletModule } from '../wallet' import { IndyWallet } from '../wallet/IndyWallet' -import { WalletModule } from '../wallet/WalletModule' import { AgentConfig } from './AgentConfig' import { BaseAgent } from './BaseAgent' @@ -94,14 +93,12 @@ export class Agent extends BaseAgent { } public async initialize() { - const { connectToIndyLedgersOnStartup, mediatorConnectionsInvite } = this.agentConfig - await super.initialize() // set the pools on the ledger. - this.ledger.setPools(this.agentContext.config.indyLedgers) + this.ledger.setPools(this.ledger.config.indyLedgers) // As long as value isn't false we will async connect to all genesis pools on startup - if (connectToIndyLedgersOnStartup) { + if (this.ledger.config.connectToIndyLedgersOnStartup) { this.ledger.connectToPools().catch((error) => { this.logger.warn('Error connecting to ledger, will try to reconnect when needed.', { error }) }) @@ -118,9 +115,13 @@ export class Agent extends BaseAgent { // Connect to mediator through provided invitation if provided in config // Also requests mediation ans sets as default mediator // Because this requires the connections module, we do this in the agent constructor - if (mediatorConnectionsInvite) { - this.logger.debug('Provision mediation with invitation', { mediatorConnectionsInvite }) - const mediationConnection = await this.getMediationConnection(mediatorConnectionsInvite) + if (this.mediationRecipient.config.mediatorInvitationUrl) { + this.logger.debug('Provision mediation with invitation', { + mediatorInvitationUrl: this.mediationRecipient.config.mediatorInvitationUrl, + }) + const mediationConnection = await this.getMediationConnection( + this.mediationRecipient.config.mediatorInvitationUrl + ) await this.mediationRecipient.provision(mediationConnection) } await this.mediator.initialize() @@ -182,21 +183,37 @@ export class Agent extends BaseAgent { // Register all modules dependencyManager.registerModules( - ConnectionsModule, - CredentialsModule, - ProofsModule, - MediatorModule, - RecipientModule, - BasicMessagesModule, - QuestionAnswerModule, - GenericRecordsModule, - LedgerModule, - DiscoverFeaturesModule, - DidsModule, - WalletModule, - OutOfBandModule, - IndyModule, - W3cVcModule + new ConnectionsModule({ + autoAcceptConnections: this.agentConfig.autoAcceptConnections, + }), + new CredentialsModule({ + autoAcceptCredentials: this.agentConfig.autoAcceptCredentials, + }), + new ProofsModule({ + autoAcceptProofs: this.agentConfig.autoAcceptProofs, + }), + new MediatorModule({ + autoAcceptMediationRequests: this.agentConfig.autoAcceptMediationRequests, + }), + new RecipientModule({ + maximumMessagePickup: this.agentConfig.maximumMessagePickup, + mediatorInvitationUrl: this.agentConfig.mediatorConnectionsInvite, + mediatorPickupStrategy: this.agentConfig.mediatorPickupStrategy, + mediatorPollingInterval: this.agentConfig.mediatorPollingInterval, + }), + new BasicMessagesModule(), + new QuestionAnswerModule(), + new GenericRecordsModule(), + new LedgerModule({ + connectToIndyLedgersOnStartup: this.agentConfig.connectToIndyLedgersOnStartup, + indyLedgers: this.agentConfig.indyLedgers, + }), + new DiscoverFeaturesModule(), + new DidsModule(), + new WalletModule(), + new OutOfBandModule(), + new IndyModule(), + new W3cVcModule() ) // TODO: contextCorrelationId for base wallet diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index df12417754..1f391d481d 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -31,30 +31,51 @@ export class AgentConfig { } } + /** + * @deprecated use connectToIndyLedgersOnStartup from the `LedgerModuleConfig` class + */ public get connectToIndyLedgersOnStartup() { return this.initConfig.connectToIndyLedgersOnStartup ?? true } + /** + * @todo remove once did registrar module is available + */ public get publicDidSeed() { return this.initConfig.publicDidSeed } + /** + * @deprecated use indyLedgers from the `LedgerModuleConfig` class + */ public get indyLedgers() { return this.initConfig.indyLedgers ?? [] } + /** + * @todo move to context configuration + */ public get walletConfig() { return this.initConfig.walletConfig } + /** + * @deprecated use autoAcceptConnections from the `ConnectionsModuleConfig` class + */ public get autoAcceptConnections() { return this.initConfig.autoAcceptConnections ?? false } + /** + * @deprecated use autoAcceptProofs from the `ProofsModuleConfig` class + */ public get autoAcceptProofs() { return this.initConfig.autoAcceptProofs ?? AutoAcceptProof.Never } + /** + * @deprecated use autoAcceptCredentials from the `CredentialsModuleConfig` class + */ public get autoAcceptCredentials() { return this.initConfig.autoAcceptCredentials ?? AutoAcceptCredential.Never } @@ -63,14 +84,23 @@ export class AgentConfig { return this.initConfig.didCommMimeType ?? DidCommMimeType.V0 } + /** + * @deprecated use mediatorPollingInterval from the `RecipientModuleConfig` class + */ public get mediatorPollingInterval() { return this.initConfig.mediatorPollingInterval ?? 5000 } + /** + * @deprecated use mediatorPickupStrategy from the `RecipientModuleConfig` class + */ public get mediatorPickupStrategy() { return this.initConfig.mediatorPickupStrategy } + /** + * @deprecated use maximumMessagePickup from the `RecipientModuleConfig` class + */ public get maximumMessagePickup() { return this.initConfig.maximumMessagePickup ?? 10 } @@ -85,18 +115,30 @@ export class AgentConfig { return this.initConfig.endpoints as [string, ...string[]] } + /** + * @deprecated use mediatorInvitationUrl from the `RecipientModuleConfig` class + */ public get mediatorConnectionsInvite() { return this.initConfig.mediatorConnectionsInvite } + /** + * @deprecated use autoAcceptMediationRequests from the `MediatorModuleConfig` class + */ public get autoAcceptMediationRequests() { return this.initConfig.autoAcceptMediationRequests ?? false } + /** + * @deprecated you can use `RecipientApi.setDefaultMediator` to set the default mediator. + */ public get defaultMediatorId() { return this.initConfig.defaultMediatorId } + /** + * @deprecated you can set the `default` tag to `false` (or remove it completely) to clear the default mediator. + */ public get clearDefaultMediator() { return this.initConfig.clearDefaultMediator ?? false } @@ -105,6 +147,9 @@ export class AgentConfig { return this.initConfig.useLegacyDidSovPrefix ?? false } + /** + * @todo move to context configuration + */ public get connectionImageUrl() { return this.initConfig.connectionImageUrl } diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index 34115dc2a5..2a895f777b 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -4,22 +4,22 @@ import type { AgentConfig } from './AgentConfig' import type { TransportSession } from './TransportService' import { AriesFrameworkError } from '../error' -import { BasicMessagesModule } from '../modules/basic-messages/BasicMessagesModule' -import { ConnectionsModule } from '../modules/connections/ConnectionsModule' -import { CredentialsModule } from '../modules/credentials/CredentialsModule' -import { DidsModule } from '../modules/dids/DidsModule' -import { DiscoverFeaturesModule } from '../modules/discover-features' -import { GenericRecordsModule } from '../modules/generic-records/GenericRecordsModule' -import { LedgerModule } from '../modules/ledger/LedgerModule' -import { OutOfBandModule } from '../modules/oob/OutOfBandModule' -import { ProofsModule } from '../modules/proofs/ProofsModule' -import { QuestionAnswerModule } from '../modules/question-answer/QuestionAnswerModule' -import { MediatorModule } from '../modules/routing/MediatorModule' -import { RecipientModule } from '../modules/routing/RecipientModule' +import { BasicMessagesApi } from '../modules/basic-messages/BasicMessagesApi' +import { ConnectionsApi } from '../modules/connections/ConnectionsApi' +import { CredentialsApi } from '../modules/credentials/CredentialsApi' +import { DidsApi } from '../modules/dids/DidsApi' +import { DiscoverFeaturesApi } from '../modules/discover-features' +import { GenericRecordsApi } from '../modules/generic-records/GenericRecordsApi' +import { LedgerApi } from '../modules/ledger/LedgerApi' +import { OutOfBandApi } from '../modules/oob/OutOfBandApi' +import { ProofsApi } from '../modules/proofs/ProofsApi' +import { QuestionAnswerApi } from '../modules/question-answer/QuestionAnswerApi' +import { MediatorApi } from '../modules/routing/MediatorApi' +import { RecipientApi } from '../modules/routing/RecipientApi' import { StorageUpdateService } from '../storage' import { UpdateAssistant } from '../storage/migration/UpdateAssistant' import { DEFAULT_UPDATE_CONFIG } from '../storage/migration/updates' -import { WalletModule } from '../wallet/WalletModule' +import { WalletApi } from '../wallet/WalletApi' import { WalletError } from '../wallet/error' import { EventEmitter } from './EventEmitter' @@ -39,19 +39,19 @@ export abstract class BaseAgent { protected _isInitialized = false protected agentContext: AgentContext - public readonly connections: ConnectionsModule - public readonly proofs: ProofsModule - public readonly basicMessages: BasicMessagesModule - public readonly genericRecords: GenericRecordsModule - public readonly ledger: LedgerModule - public readonly questionAnswer!: QuestionAnswerModule - public readonly credentials: CredentialsModule - public readonly mediationRecipient: RecipientModule - public readonly mediator: MediatorModule - public readonly discovery: DiscoverFeaturesModule - public readonly dids: DidsModule - public readonly wallet: WalletModule - public readonly oob: OutOfBandModule + public readonly connections: ConnectionsApi + public readonly proofs: ProofsApi + public readonly basicMessages: BasicMessagesApi + public readonly genericRecords: GenericRecordsApi + public readonly ledger: LedgerApi + public readonly questionAnswer!: QuestionAnswerApi + public readonly credentials: CredentialsApi + public readonly mediationRecipient: RecipientApi + public readonly mediator: MediatorApi + public readonly discovery: DiscoverFeaturesApi + public readonly dids: DidsApi + public readonly wallet: WalletApi + public readonly oob: OutOfBandApi public constructor(agentConfig: AgentConfig, dependencyManager: DependencyManager) { this.dependencyManager = dependencyManager @@ -81,19 +81,19 @@ export abstract class BaseAgent { this.agentContext = this.dependencyManager.resolve(AgentContext) // We set the modules in the constructor because that allows to set them as read-only - this.connections = this.dependencyManager.resolve(ConnectionsModule) - this.credentials = this.dependencyManager.resolve(CredentialsModule) as CredentialsModule - this.proofs = this.dependencyManager.resolve(ProofsModule) - this.mediator = this.dependencyManager.resolve(MediatorModule) - this.mediationRecipient = this.dependencyManager.resolve(RecipientModule) - this.basicMessages = this.dependencyManager.resolve(BasicMessagesModule) - this.questionAnswer = this.dependencyManager.resolve(QuestionAnswerModule) - this.genericRecords = this.dependencyManager.resolve(GenericRecordsModule) - this.ledger = this.dependencyManager.resolve(LedgerModule) - this.discovery = this.dependencyManager.resolve(DiscoverFeaturesModule) - this.dids = this.dependencyManager.resolve(DidsModule) - this.wallet = this.dependencyManager.resolve(WalletModule) - this.oob = this.dependencyManager.resolve(OutOfBandModule) + this.connections = this.dependencyManager.resolve(ConnectionsApi) + this.credentials = this.dependencyManager.resolve(CredentialsApi) as CredentialsApi + this.proofs = this.dependencyManager.resolve(ProofsApi) + this.mediator = this.dependencyManager.resolve(MediatorApi) + this.mediationRecipient = this.dependencyManager.resolve(RecipientApi) + this.basicMessages = this.dependencyManager.resolve(BasicMessagesApi) + this.questionAnswer = this.dependencyManager.resolve(QuestionAnswerApi) + this.genericRecords = this.dependencyManager.resolve(GenericRecordsApi) + this.ledger = this.dependencyManager.resolve(LedgerApi) + this.discovery = this.dependencyManager.resolve(DiscoverFeaturesApi) + this.dids = this.dependencyManager.resolve(DidsApi) + this.wallet = this.dependencyManager.resolve(WalletApi) + this.oob = this.dependencyManager.resolve(OutOfBandApi) } public get isInitialized() { diff --git a/packages/core/src/agent/__tests__/Agent.test.ts b/packages/core/src/agent/__tests__/Agent.test.ts index 558f267e14..7c92573fa4 100644 --- a/packages/core/src/agent/__tests__/Agent.test.ts +++ b/packages/core/src/agent/__tests__/Agent.test.ts @@ -1,20 +1,20 @@ import { getBaseConfig } from '../../../tests/helpers' import { InjectionSymbols } from '../../constants' import { BasicMessageRepository, BasicMessageService } from '../../modules/basic-messages' -import { BasicMessagesModule } from '../../modules/basic-messages/BasicMessagesModule' -import { ConnectionsModule } from '../../modules/connections/ConnectionsModule' +import { BasicMessagesApi } from '../../modules/basic-messages/BasicMessagesApi' +import { ConnectionsApi } from '../../modules/connections/ConnectionsApi' import { ConnectionRepository } from '../../modules/connections/repository/ConnectionRepository' import { ConnectionService } from '../../modules/connections/services/ConnectionService' import { TrustPingService } from '../../modules/connections/services/TrustPingService' import { CredentialRepository } from '../../modules/credentials' -import { CredentialsModule } from '../../modules/credentials/CredentialsModule' +import { CredentialsApi } from '../../modules/credentials/CredentialsApi' import { IndyLedgerService } from '../../modules/ledger' -import { LedgerModule } from '../../modules/ledger/LedgerModule' +import { LedgerApi } from '../../modules/ledger/LedgerApi' import { ProofRepository, ProofService } from '../../modules/proofs' -import { ProofsModule } from '../../modules/proofs/ProofsModule' +import { ProofsApi } from '../../modules/proofs/ProofsApi' import { - MediatorModule, - RecipientModule, + MediatorApi, + RecipientApi, MediationRepository, MediatorService, MediationRecipientService, @@ -110,29 +110,29 @@ describe('Agent', () => { const container = agent.dependencyManager // Modules - expect(container.resolve(ConnectionsModule)).toBeInstanceOf(ConnectionsModule) + expect(container.resolve(ConnectionsApi)).toBeInstanceOf(ConnectionsApi) expect(container.resolve(ConnectionService)).toBeInstanceOf(ConnectionService) expect(container.resolve(ConnectionRepository)).toBeInstanceOf(ConnectionRepository) expect(container.resolve(TrustPingService)).toBeInstanceOf(TrustPingService) - expect(container.resolve(ProofsModule)).toBeInstanceOf(ProofsModule) + expect(container.resolve(ProofsApi)).toBeInstanceOf(ProofsApi) expect(container.resolve(ProofService)).toBeInstanceOf(ProofService) expect(container.resolve(ProofRepository)).toBeInstanceOf(ProofRepository) - expect(container.resolve(CredentialsModule)).toBeInstanceOf(CredentialsModule) + expect(container.resolve(CredentialsApi)).toBeInstanceOf(CredentialsApi) expect(container.resolve(CredentialRepository)).toBeInstanceOf(CredentialRepository) - expect(container.resolve(BasicMessagesModule)).toBeInstanceOf(BasicMessagesModule) + expect(container.resolve(BasicMessagesApi)).toBeInstanceOf(BasicMessagesApi) expect(container.resolve(BasicMessageService)).toBeInstanceOf(BasicMessageService) expect(container.resolve(BasicMessageRepository)).toBeInstanceOf(BasicMessageRepository) - expect(container.resolve(MediatorModule)).toBeInstanceOf(MediatorModule) - expect(container.resolve(RecipientModule)).toBeInstanceOf(RecipientModule) + expect(container.resolve(MediatorApi)).toBeInstanceOf(MediatorApi) + expect(container.resolve(RecipientApi)).toBeInstanceOf(RecipientApi) expect(container.resolve(MediationRepository)).toBeInstanceOf(MediationRepository) expect(container.resolve(MediatorService)).toBeInstanceOf(MediatorService) expect(container.resolve(MediationRecipientService)).toBeInstanceOf(MediationRecipientService) - expect(container.resolve(LedgerModule)).toBeInstanceOf(LedgerModule) + expect(container.resolve(LedgerApi)).toBeInstanceOf(LedgerApi) expect(container.resolve(IndyLedgerService)).toBeInstanceOf(IndyLedgerService) // Symbols, interface based @@ -152,29 +152,29 @@ describe('Agent', () => { const container = agent.dependencyManager // Modules - expect(container.resolve(ConnectionsModule)).toBe(container.resolve(ConnectionsModule)) + expect(container.resolve(ConnectionsApi)).toBe(container.resolve(ConnectionsApi)) expect(container.resolve(ConnectionService)).toBe(container.resolve(ConnectionService)) expect(container.resolve(ConnectionRepository)).toBe(container.resolve(ConnectionRepository)) expect(container.resolve(TrustPingService)).toBe(container.resolve(TrustPingService)) - expect(container.resolve(ProofsModule)).toBe(container.resolve(ProofsModule)) + expect(container.resolve(ProofsApi)).toBe(container.resolve(ProofsApi)) expect(container.resolve(ProofService)).toBe(container.resolve(ProofService)) expect(container.resolve(ProofRepository)).toBe(container.resolve(ProofRepository)) - expect(container.resolve(CredentialsModule)).toBe(container.resolve(CredentialsModule)) + expect(container.resolve(CredentialsApi)).toBe(container.resolve(CredentialsApi)) expect(container.resolve(CredentialRepository)).toBe(container.resolve(CredentialRepository)) - expect(container.resolve(BasicMessagesModule)).toBe(container.resolve(BasicMessagesModule)) + expect(container.resolve(BasicMessagesApi)).toBe(container.resolve(BasicMessagesApi)) expect(container.resolve(BasicMessageService)).toBe(container.resolve(BasicMessageService)) expect(container.resolve(BasicMessageRepository)).toBe(container.resolve(BasicMessageRepository)) - expect(container.resolve(MediatorModule)).toBe(container.resolve(MediatorModule)) - expect(container.resolve(RecipientModule)).toBe(container.resolve(RecipientModule)) + expect(container.resolve(MediatorApi)).toBe(container.resolve(MediatorApi)) + expect(container.resolve(RecipientApi)).toBe(container.resolve(RecipientApi)) expect(container.resolve(MediationRepository)).toBe(container.resolve(MediationRepository)) expect(container.resolve(MediatorService)).toBe(container.resolve(MediatorService)) expect(container.resolve(MediationRecipientService)).toBe(container.resolve(MediationRecipientService)) - expect(container.resolve(LedgerModule)).toBe(container.resolve(LedgerModule)) + expect(container.resolve(LedgerApi)).toBe(container.resolve(LedgerApi)) expect(container.resolve(IndyLedgerService)).toBe(container.resolve(IndyLedgerService)) // Symbols, interface based diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e1967d1ef3..afadf847a1 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -42,7 +42,7 @@ export * from './modules/ledger' export * from './modules/routing' export * from './modules/question-answer' export * from './modules/oob' -export * from './wallet/WalletModule' +export * from './wallet/WalletApi' export * from './modules/dids' export { JsonEncoder, JsonTransformer, isJsonObject, isValidJweStructure } from './utils' export * from './logger' diff --git a/packages/core/src/modules/basic-messages/BasicMessagesApi.ts b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts new file mode 100644 index 0000000000..9de00e5e2b --- /dev/null +++ b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts @@ -0,0 +1,49 @@ +import type { BasicMessageTags } from './repository/BasicMessageRecord' + +import { AgentContext } from '../../agent' +import { Dispatcher } from '../../agent/Dispatcher' +import { MessageSender } from '../../agent/MessageSender' +import { createOutboundMessage } from '../../agent/helpers' +import { injectable } from '../../plugins' +import { ConnectionService } from '../connections' + +import { BasicMessageHandler } from './handlers' +import { BasicMessageService } from './services' + +@injectable() +export class BasicMessagesApi { + private basicMessageService: BasicMessageService + private messageSender: MessageSender + private connectionService: ConnectionService + private agentContext: AgentContext + + public constructor( + dispatcher: Dispatcher, + basicMessageService: BasicMessageService, + messageSender: MessageSender, + connectionService: ConnectionService, + agentContext: AgentContext + ) { + this.basicMessageService = basicMessageService + this.messageSender = messageSender + this.connectionService = connectionService + this.agentContext = agentContext + this.registerHandlers(dispatcher) + } + + public async sendMessage(connectionId: string, message: string) { + const connection = await this.connectionService.getById(this.agentContext, connectionId) + + const basicMessage = await this.basicMessageService.createMessage(this.agentContext, message, connection) + const outboundMessage = createOutboundMessage(connection, basicMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + } + + public async findAllByQuery(query: Partial) { + return this.basicMessageService.findAllByQuery(this.agentContext, query) + } + + private registerHandlers(dispatcher: Dispatcher) { + dispatcher.registerHandler(new BasicMessageHandler(this.basicMessageService)) + } +} diff --git a/packages/core/src/modules/basic-messages/BasicMessagesModule.ts b/packages/core/src/modules/basic-messages/BasicMessagesModule.ts index 796ffa2334..03da109ff4 100644 --- a/packages/core/src/modules/basic-messages/BasicMessagesModule.ts +++ b/packages/core/src/modules/basic-messages/BasicMessagesModule.ts @@ -1,61 +1,16 @@ -import type { DependencyManager } from '../../plugins' -import type { BasicMessageTags } from './repository/BasicMessageRecord' +import type { DependencyManager, Module } from '../../plugins' -import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' -import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' -import { injectable, module } from '../../plugins' -import { ConnectionService } from '../connections' - -import { BasicMessageHandler } from './handlers' +import { BasicMessagesApi } from './BasicMessagesApi' import { BasicMessageRepository } from './repository' import { BasicMessageService } from './services' -@module() -@injectable() -export class BasicMessagesModule { - private basicMessageService: BasicMessageService - private messageSender: MessageSender - private connectionService: ConnectionService - private agentContext: AgentContext - - public constructor( - dispatcher: Dispatcher, - basicMessageService: BasicMessageService, - messageSender: MessageSender, - connectionService: ConnectionService, - agentContext: AgentContext - ) { - this.basicMessageService = basicMessageService - this.messageSender = messageSender - this.connectionService = connectionService - this.agentContext = agentContext - this.registerHandlers(dispatcher) - } - - public async sendMessage(connectionId: string, message: string) { - const connection = await this.connectionService.getById(this.agentContext, connectionId) - - const basicMessage = await this.basicMessageService.createMessage(this.agentContext, message, connection) - const outboundMessage = createOutboundMessage(connection, basicMessage) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - } - - public async findAllByQuery(query: Partial) { - return this.basicMessageService.findAllByQuery(this.agentContext, query) - } - - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new BasicMessageHandler(this.basicMessageService)) - } - +export class BasicMessagesModule implements Module { /** * Registers the dependencies of the basic message module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(BasicMessagesModule) + dependencyManager.registerContextScoped(BasicMessagesApi) // Services dependencyManager.registerSingleton(BasicMessageService) diff --git a/packages/core/src/modules/basic-messages/__tests__/BasicMessagesModule.test.ts b/packages/core/src/modules/basic-messages/__tests__/BasicMessagesModule.test.ts new file mode 100644 index 0000000000..caaaedae00 --- /dev/null +++ b/packages/core/src/modules/basic-messages/__tests__/BasicMessagesModule.test.ts @@ -0,0 +1,23 @@ +import { DependencyManager } from '../../../plugins/DependencyManager' +import { BasicMessagesApi } from '../BasicMessagesApi' +import { BasicMessagesModule } from '../BasicMessagesModule' +import { BasicMessageRepository } from '../repository' +import { BasicMessageService } from '../services' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('BasicMessagesModule', () => { + test('registers dependencies on the dependency manager', () => { + new BasicMessagesModule().register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(BasicMessagesApi) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(2) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(BasicMessageService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(BasicMessageRepository) + }) +}) diff --git a/packages/core/src/modules/basic-messages/index.ts b/packages/core/src/modules/basic-messages/index.ts index eed8753ff2..e0ca5207d1 100644 --- a/packages/core/src/modules/basic-messages/index.ts +++ b/packages/core/src/modules/basic-messages/index.ts @@ -2,5 +2,6 @@ export * from './messages' export * from './services' export * from './repository' export * from './BasicMessageEvents' -export * from './BasicMessagesModule' +export * from './BasicMessagesApi' export * from './BasicMessageRole' +export * from './BasicMessagesModule' diff --git a/packages/core/src/modules/connections/ConnectionsApi.ts b/packages/core/src/modules/connections/ConnectionsApi.ts new file mode 100644 index 0000000000..cc4154eb08 --- /dev/null +++ b/packages/core/src/modules/connections/ConnectionsApi.ts @@ -0,0 +1,312 @@ +import type { OutOfBandRecord } from '../oob/repository' +import type { ConnectionRecord } from './repository/ConnectionRecord' +import type { Routing } from './services' + +import { AgentContext } from '../../agent' +import { Dispatcher } from '../../agent/Dispatcher' +import { MessageSender } from '../../agent/MessageSender' +import { createOutboundMessage } from '../../agent/helpers' +import { ReturnRouteTypes } from '../../decorators/transport/TransportDecorator' +import { AriesFrameworkError } from '../../error' +import { injectable } from '../../plugins' +import { DidResolverService } from '../dids' +import { DidRepository } from '../dids/repository' +import { OutOfBandService } from '../oob/OutOfBandService' +import { RoutingService } from '../routing/services/RoutingService' + +import { ConnectionsModuleConfig } from './ConnectionsModuleConfig' +import { DidExchangeProtocol } from './DidExchangeProtocol' +import { + AckMessageHandler, + ConnectionRequestHandler, + ConnectionResponseHandler, + DidExchangeCompleteHandler, + DidExchangeRequestHandler, + DidExchangeResponseHandler, + TrustPingMessageHandler, + TrustPingResponseMessageHandler, +} from './handlers' +import { HandshakeProtocol } from './models' +import { ConnectionService } from './services/ConnectionService' +import { TrustPingService } from './services/TrustPingService' + +@injectable() +export class ConnectionsApi { + /** + * Configuration for the connections module + */ + public readonly config: ConnectionsModuleConfig + + private didExchangeProtocol: DidExchangeProtocol + private connectionService: ConnectionService + private outOfBandService: OutOfBandService + private messageSender: MessageSender + private trustPingService: TrustPingService + private routingService: RoutingService + private didRepository: DidRepository + private didResolverService: DidResolverService + private agentContext: AgentContext + + public constructor( + dispatcher: Dispatcher, + didExchangeProtocol: DidExchangeProtocol, + connectionService: ConnectionService, + outOfBandService: OutOfBandService, + trustPingService: TrustPingService, + routingService: RoutingService, + didRepository: DidRepository, + didResolverService: DidResolverService, + messageSender: MessageSender, + agentContext: AgentContext, + connectionsModuleConfig: ConnectionsModuleConfig + ) { + this.didExchangeProtocol = didExchangeProtocol + this.connectionService = connectionService + this.outOfBandService = outOfBandService + this.trustPingService = trustPingService + this.routingService = routingService + this.didRepository = didRepository + this.messageSender = messageSender + this.didResolverService = didResolverService + this.agentContext = agentContext + this.config = connectionsModuleConfig + + this.registerHandlers(dispatcher) + } + + public async acceptOutOfBandInvitation( + outOfBandRecord: OutOfBandRecord, + config: { + autoAcceptConnection?: boolean + label?: string + alias?: string + imageUrl?: string + protocol: HandshakeProtocol + routing?: Routing + } + ) { + const { protocol, label, alias, imageUrl, autoAcceptConnection } = config + + const routing = + config.routing || + (await this.routingService.getRouting(this.agentContext, { mediatorId: outOfBandRecord.mediatorId })) + + let result + if (protocol === HandshakeProtocol.DidExchange) { + result = await this.didExchangeProtocol.createRequest(this.agentContext, outOfBandRecord, { + label, + alias, + routing, + autoAcceptConnection, + }) + } else if (protocol === HandshakeProtocol.Connections) { + result = await this.connectionService.createRequest(this.agentContext, outOfBandRecord, { + label, + alias, + imageUrl, + routing, + autoAcceptConnection, + }) + } else { + throw new AriesFrameworkError(`Unsupported handshake protocol ${protocol}.`) + } + + const { message, connectionRecord } = result + const outboundMessage = createOutboundMessage(connectionRecord, message, outOfBandRecord) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + return connectionRecord + } + + /** + * Accept a connection request as inviter (by sending a connection response message) for the connection with the specified connection id. + * This is not needed when auto accepting of connection is enabled. + * + * @param connectionId the id of the connection for which to accept the request + * @returns connection record + */ + public async acceptRequest(connectionId: string): Promise { + const connectionRecord = await this.connectionService.findById(this.agentContext, connectionId) + if (!connectionRecord) { + throw new AriesFrameworkError(`Connection record ${connectionId} not found.`) + } + if (!connectionRecord.outOfBandId) { + throw new AriesFrameworkError(`Connection record ${connectionId} does not have out-of-band record.`) + } + + const outOfBandRecord = await this.outOfBandService.findById(this.agentContext, connectionRecord.outOfBandId) + if (!outOfBandRecord) { + throw new AriesFrameworkError(`Out-of-band record ${connectionRecord.outOfBandId} not found.`) + } + + let outboundMessage + if (connectionRecord.protocol === HandshakeProtocol.DidExchange) { + const message = await this.didExchangeProtocol.createResponse( + this.agentContext, + connectionRecord, + outOfBandRecord + ) + outboundMessage = createOutboundMessage(connectionRecord, message) + } else { + const { message } = await this.connectionService.createResponse( + this.agentContext, + connectionRecord, + outOfBandRecord + ) + outboundMessage = createOutboundMessage(connectionRecord, message) + } + + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + return connectionRecord + } + + /** + * Accept a connection response as invitee (by sending a trust ping message) for the connection with the specified connection id. + * This is not needed when auto accepting of connection is enabled. + * + * @param connectionId the id of the connection for which to accept the response + * @returns connection record + */ + public async acceptResponse(connectionId: string): Promise { + const connectionRecord = await this.connectionService.getById(this.agentContext, connectionId) + + let outboundMessage + if (connectionRecord.protocol === HandshakeProtocol.DidExchange) { + if (!connectionRecord.outOfBandId) { + throw new AriesFrameworkError(`Connection ${connectionRecord.id} does not have outOfBandId!`) + } + const outOfBandRecord = await this.outOfBandService.findById(this.agentContext, connectionRecord.outOfBandId) + if (!outOfBandRecord) { + throw new AriesFrameworkError( + `OutOfBand record for connection ${connectionRecord.id} with outOfBandId ${connectionRecord.outOfBandId} not found!` + ) + } + const message = await this.didExchangeProtocol.createComplete( + this.agentContext, + connectionRecord, + outOfBandRecord + ) + // 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 + message.setReturnRouting(ReturnRouteTypes.none) + outboundMessage = createOutboundMessage(connectionRecord, message) + } else { + const { message } = await this.connectionService.createTrustPing(this.agentContext, connectionRecord, { + responseRequested: false, + }) + // 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 + message.setReturnRouting(ReturnRouteTypes.none) + outboundMessage = createOutboundMessage(connectionRecord, message) + } + + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + return connectionRecord + } + + public async returnWhenIsConnected(connectionId: string, options?: { timeoutMs: number }): Promise { + return this.connectionService.returnWhenIsConnected(this.agentContext, connectionId, options?.timeoutMs) + } + + /** + * Retrieve all connections records + * + * @returns List containing all connection records + */ + public getAll() { + return this.connectionService.getAll(this.agentContext) + } + + /** + * Retrieve a connection record by id + * + * @param connectionId The connection record id + * @throws {RecordNotFoundError} If no record is found + * @return The connection record + * + */ + public getById(connectionId: string): Promise { + return this.connectionService.getById(this.agentContext, connectionId) + } + + /** + * Find a connection record by id + * + * @param connectionId the connection record id + * @returns The connection record or null if not found + */ + public findById(connectionId: string): Promise { + return this.connectionService.findById(this.agentContext, connectionId) + } + + /** + * Delete a connection record by id + * + * @param connectionId the connection record id + */ + public async deleteById(connectionId: string) { + return this.connectionService.deleteById(this.agentContext, connectionId) + } + + public async findAllByOutOfBandId(outOfBandId: string) { + return this.connectionService.findAllByOutOfBandId(this.agentContext, outOfBandId) + } + + /** + * Retrieve a connection record by thread id + * + * @param threadId The thread id + * @throws {RecordNotFoundError} If no record is found + * @throws {RecordDuplicateError} If multiple records are found + * @returns The connection record + */ + public getByThreadId(threadId: string): Promise { + return this.connectionService.getByThreadId(this.agentContext, threadId) + } + + public async findByDid(did: string): Promise { + return this.connectionService.findByTheirDid(this.agentContext, did) + } + + public async findByInvitationDid(invitationDid: string): Promise { + return this.connectionService.findByInvitationDid(this.agentContext, invitationDid) + } + + private registerHandlers(dispatcher: Dispatcher) { + dispatcher.registerHandler( + new ConnectionRequestHandler( + this.connectionService, + this.outOfBandService, + this.routingService, + this.didRepository, + this.config + ) + ) + dispatcher.registerHandler( + new ConnectionResponseHandler(this.connectionService, this.outOfBandService, this.didResolverService, this.config) + ) + dispatcher.registerHandler(new AckMessageHandler(this.connectionService)) + dispatcher.registerHandler(new TrustPingMessageHandler(this.trustPingService, this.connectionService)) + dispatcher.registerHandler(new TrustPingResponseMessageHandler(this.trustPingService)) + + dispatcher.registerHandler( + new DidExchangeRequestHandler( + this.didExchangeProtocol, + this.outOfBandService, + this.routingService, + this.didRepository, + this.config + ) + ) + + dispatcher.registerHandler( + new DidExchangeResponseHandler( + this.didExchangeProtocol, + this.outOfBandService, + this.connectionService, + this.didResolverService, + this.config + ) + ) + dispatcher.registerHandler(new DidExchangeCompleteHandler(this.didExchangeProtocol, this.outOfBandService)) + } +} diff --git a/packages/core/src/modules/connections/ConnectionsModule.ts b/packages/core/src/modules/connections/ConnectionsModule.ts index d8a4094c6e..5e6c98ee0a 100644 --- a/packages/core/src/modules/connections/ConnectionsModule.ts +++ b/packages/core/src/modules/connections/ConnectionsModule.ts @@ -1,313 +1,28 @@ -import type { DependencyManager } from '../../plugins' -import type { OutOfBandRecord } from '../oob/repository' -import type { ConnectionRecord } from './repository/ConnectionRecord' -import type { Routing } from './services' - -import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' -import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' -import { ReturnRouteTypes } from '../../decorators/transport/TransportDecorator' -import { AriesFrameworkError } from '../../error' -import { injectable, module } from '../../plugins' -import { DidResolverService } from '../dids' -import { DidRepository } from '../dids/repository' -import { OutOfBandService } from '../oob/OutOfBandService' -import { RoutingService } from '../routing/services/RoutingService' +import type { DependencyManager, Module } from '../../plugins' +import type { ConnectionsModuleConfigOptions } from './ConnectionsModuleConfig' +import { ConnectionsApi } from './ConnectionsApi' +import { ConnectionsModuleConfig } from './ConnectionsModuleConfig' import { DidExchangeProtocol } from './DidExchangeProtocol' -import { - AckMessageHandler, - ConnectionRequestHandler, - ConnectionResponseHandler, - DidExchangeCompleteHandler, - DidExchangeRequestHandler, - DidExchangeResponseHandler, - TrustPingMessageHandler, - TrustPingResponseMessageHandler, -} from './handlers' -import { HandshakeProtocol } from './models' import { ConnectionRepository } from './repository' -import { ConnectionService } from './services/ConnectionService' -import { TrustPingService } from './services/TrustPingService' - -@module() -@injectable() -export class ConnectionsModule { - private didExchangeProtocol: DidExchangeProtocol - private connectionService: ConnectionService - private outOfBandService: OutOfBandService - private messageSender: MessageSender - private trustPingService: TrustPingService - private routingService: RoutingService - private didRepository: DidRepository - private didResolverService: DidResolverService - private agentContext: AgentContext - - public constructor( - dispatcher: Dispatcher, - didExchangeProtocol: DidExchangeProtocol, - connectionService: ConnectionService, - outOfBandService: OutOfBandService, - trustPingService: TrustPingService, - routingService: RoutingService, - didRepository: DidRepository, - didResolverService: DidResolverService, - messageSender: MessageSender, - agentContext: AgentContext - ) { - this.didExchangeProtocol = didExchangeProtocol - this.connectionService = connectionService - this.outOfBandService = outOfBandService - this.trustPingService = trustPingService - this.routingService = routingService - this.didRepository = didRepository - this.messageSender = messageSender - this.didResolverService = didResolverService - this.agentContext = agentContext - - this.registerHandlers(dispatcher) - } - - public async acceptOutOfBandInvitation( - outOfBandRecord: OutOfBandRecord, - config: { - autoAcceptConnection?: boolean - label?: string - alias?: string - imageUrl?: string - protocol: HandshakeProtocol - routing?: Routing - } - ) { - const { protocol, label, alias, imageUrl, autoAcceptConnection } = config - - const routing = - config.routing || - (await this.routingService.getRouting(this.agentContext, { mediatorId: outOfBandRecord.mediatorId })) - - let result - if (protocol === HandshakeProtocol.DidExchange) { - result = await this.didExchangeProtocol.createRequest(this.agentContext, outOfBandRecord, { - label, - alias, - routing, - autoAcceptConnection, - }) - } else if (protocol === HandshakeProtocol.Connections) { - result = await this.connectionService.createRequest(this.agentContext, outOfBandRecord, { - label, - alias, - imageUrl, - routing, - autoAcceptConnection, - }) - } else { - throw new AriesFrameworkError(`Unsupported handshake protocol ${protocol}.`) - } - - const { message, connectionRecord } = result - const outboundMessage = createOutboundMessage(connectionRecord, message, outOfBandRecord) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - return connectionRecord - } - - /** - * Accept a connection request as inviter (by sending a connection response message) for the connection with the specified connection id. - * This is not needed when auto accepting of connection is enabled. - * - * @param connectionId the id of the connection for which to accept the request - * @returns connection record - */ - public async acceptRequest(connectionId: string): Promise { - const connectionRecord = await this.connectionService.findById(this.agentContext, connectionId) - if (!connectionRecord) { - throw new AriesFrameworkError(`Connection record ${connectionId} not found.`) - } - if (!connectionRecord.outOfBandId) { - throw new AriesFrameworkError(`Connection record ${connectionId} does not have out-of-band record.`) - } - - const outOfBandRecord = await this.outOfBandService.findById(this.agentContext, connectionRecord.outOfBandId) - if (!outOfBandRecord) { - throw new AriesFrameworkError(`Out-of-band record ${connectionRecord.outOfBandId} not found.`) - } +import { ConnectionService, TrustPingService } from './services' - let outboundMessage - if (connectionRecord.protocol === HandshakeProtocol.DidExchange) { - const message = await this.didExchangeProtocol.createResponse( - this.agentContext, - connectionRecord, - outOfBandRecord - ) - outboundMessage = createOutboundMessage(connectionRecord, message) - } else { - const { message } = await this.connectionService.createResponse( - this.agentContext, - connectionRecord, - outOfBandRecord - ) - outboundMessage = createOutboundMessage(connectionRecord, message) - } +export class ConnectionsModule implements Module { + public readonly config: ConnectionsModuleConfig - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - return connectionRecord - } - - /** - * Accept a connection response as invitee (by sending a trust ping message) for the connection with the specified connection id. - * This is not needed when auto accepting of connection is enabled. - * - * @param connectionId the id of the connection for which to accept the response - * @returns connection record - */ - public async acceptResponse(connectionId: string): Promise { - const connectionRecord = await this.connectionService.getById(this.agentContext, connectionId) - - let outboundMessage - if (connectionRecord.protocol === HandshakeProtocol.DidExchange) { - if (!connectionRecord.outOfBandId) { - throw new AriesFrameworkError(`Connection ${connectionRecord.id} does not have outOfBandId!`) - } - const outOfBandRecord = await this.outOfBandService.findById(this.agentContext, connectionRecord.outOfBandId) - if (!outOfBandRecord) { - throw new AriesFrameworkError( - `OutOfBand record for connection ${connectionRecord.id} with outOfBandId ${connectionRecord.outOfBandId} not found!` - ) - } - const message = await this.didExchangeProtocol.createComplete( - this.agentContext, - connectionRecord, - outOfBandRecord - ) - // 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 - message.setReturnRouting(ReturnRouteTypes.none) - outboundMessage = createOutboundMessage(connectionRecord, message) - } else { - const { message } = await this.connectionService.createTrustPing(this.agentContext, connectionRecord, { - responseRequested: false, - }) - // 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 - message.setReturnRouting(ReturnRouteTypes.none) - outboundMessage = createOutboundMessage(connectionRecord, message) - } - - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - return connectionRecord - } - - public async returnWhenIsConnected(connectionId: string, options?: { timeoutMs: number }): Promise { - return this.connectionService.returnWhenIsConnected(this.agentContext, connectionId, options?.timeoutMs) - } - - /** - * Retrieve all connections records - * - * @returns List containing all connection records - */ - public getAll() { - return this.connectionService.getAll(this.agentContext) - } - - /** - * Retrieve a connection record by id - * - * @param connectionId The connection record id - * @throws {RecordNotFoundError} If no record is found - * @return The connection record - * - */ - public getById(connectionId: string): Promise { - return this.connectionService.getById(this.agentContext, connectionId) - } - - /** - * Find a connection record by id - * - * @param connectionId the connection record id - * @returns The connection record or null if not found - */ - public findById(connectionId: string): Promise { - return this.connectionService.findById(this.agentContext, connectionId) - } - - /** - * Delete a connection record by id - * - * @param connectionId the connection record id - */ - public async deleteById(connectionId: string) { - return this.connectionService.deleteById(this.agentContext, connectionId) - } - - public async findAllByOutOfBandId(outOfBandId: string) { - return this.connectionService.findAllByOutOfBandId(this.agentContext, outOfBandId) - } - - /** - * Retrieve a connection record by thread id - * - * @param threadId The thread id - * @throws {RecordNotFoundError} If no record is found - * @throws {RecordDuplicateError} If multiple records are found - * @returns The connection record - */ - public getByThreadId(threadId: string): Promise { - return this.connectionService.getByThreadId(this.agentContext, threadId) - } - - public async findByDid(did: string): Promise { - return this.connectionService.findByTheirDid(this.agentContext, did) - } - - public async findByInvitationDid(invitationDid: string): Promise { - return this.connectionService.findByInvitationDid(this.agentContext, invitationDid) - } - - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler( - new ConnectionRequestHandler( - this.connectionService, - this.outOfBandService, - this.routingService, - this.didRepository - ) - ) - dispatcher.registerHandler( - new ConnectionResponseHandler(this.connectionService, this.outOfBandService, this.didResolverService) - ) - dispatcher.registerHandler(new AckMessageHandler(this.connectionService)) - dispatcher.registerHandler(new TrustPingMessageHandler(this.trustPingService, this.connectionService)) - dispatcher.registerHandler(new TrustPingResponseMessageHandler(this.trustPingService)) - - dispatcher.registerHandler( - new DidExchangeRequestHandler( - this.didExchangeProtocol, - this.outOfBandService, - this.routingService, - this.didRepository - ) - ) - - dispatcher.registerHandler( - new DidExchangeResponseHandler( - this.didExchangeProtocol, - this.outOfBandService, - this.connectionService, - this.didResolverService - ) - ) - dispatcher.registerHandler(new DidExchangeCompleteHandler(this.didExchangeProtocol, this.outOfBandService)) + public constructor(config?: ConnectionsModuleConfigOptions) { + this.config = new ConnectionsModuleConfig(config) } /** * Registers the dependencies of the connections module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(ConnectionsModule) + dependencyManager.registerContextScoped(ConnectionsApi) + + // Config + dependencyManager.registerInstance(ConnectionsModuleConfig, this.config) // Services dependencyManager.registerSingleton(ConnectionService) diff --git a/packages/core/src/modules/connections/ConnectionsModuleConfig.ts b/packages/core/src/modules/connections/ConnectionsModuleConfig.ts new file mode 100644 index 0000000000..b4b69edacf --- /dev/null +++ b/packages/core/src/modules/connections/ConnectionsModuleConfig.ts @@ -0,0 +1,26 @@ +/** + * ConnectionsModuleConfigOptions defines the interface for the options of the ConnectionsModuleConfig class. + * This can contain optional parameters that have default values in the config class itself. + */ +export interface ConnectionsModuleConfigOptions { + /** + * Whether to automatically accept connection messages. Applies to both the connection protocol (RFC 0160) + * and the DID exchange protocol (RFC 0023). + * + * @default false + */ + autoAcceptConnections?: boolean +} + +export class ConnectionsModuleConfig { + private options: ConnectionsModuleConfigOptions + + public constructor(options?: ConnectionsModuleConfigOptions) { + this.options = options ?? {} + } + + /** See {@link ConnectionsModuleConfigOptions.autoAcceptConnections} */ + public get autoAcceptConnections() { + return this.options.autoAcceptConnections ?? false + } +} diff --git a/packages/core/src/modules/connections/__tests__/ConnectionsModule.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionsModule.test.ts new file mode 100644 index 0000000000..2231f69b07 --- /dev/null +++ b/packages/core/src/modules/connections/__tests__/ConnectionsModule.test.ts @@ -0,0 +1,31 @@ +import { DependencyManager } from '../../../plugins/DependencyManager' +import { ConnectionsApi } from '../ConnectionsApi' +import { ConnectionsModule } from '../ConnectionsModule' +import { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' +import { DidExchangeProtocol } from '../DidExchangeProtocol' +import { ConnectionRepository } from '../repository' +import { ConnectionService, TrustPingService } from '../services' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('ConnectionsModule', () => { + test('registers dependencies on the dependency manager', () => { + const connectionsModule = new ConnectionsModule() + connectionsModule.register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(ConnectionsApi) + + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(ConnectionsModuleConfig, connectionsModule.config) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(4) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(ConnectionService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidExchangeProtocol) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(TrustPingService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(ConnectionRepository) + }) +}) diff --git a/packages/core/src/modules/connections/__tests__/ConnectionsModuleConfig.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionsModuleConfig.test.ts new file mode 100644 index 0000000000..bc4c0b29bb --- /dev/null +++ b/packages/core/src/modules/connections/__tests__/ConnectionsModuleConfig.test.ts @@ -0,0 +1,17 @@ +import { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' + +describe('ConnectionsModuleConfig', () => { + test('sets default values', () => { + const config = new ConnectionsModuleConfig() + + expect(config.autoAcceptConnections).toBe(false) + }) + + test('sets values', () => { + const config = new ConnectionsModuleConfig({ + autoAcceptConnections: true, + }) + + expect(config.autoAcceptConnections).toBe(true) + }) +}) diff --git a/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts index 1f55bea49a..1291546616 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts @@ -2,6 +2,7 @@ import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { DidRepository } from '../../dids/repository' import type { OutOfBandService } from '../../oob/OutOfBandService' import type { RoutingService } from '../../routing/services/RoutingService' +import type { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' import type { ConnectionService } from '../services/ConnectionService' import { createOutboundMessage } from '../../../agent/helpers' @@ -13,18 +14,21 @@ export class ConnectionRequestHandler implements Handler { private outOfBandService: OutOfBandService private routingService: RoutingService private didRepository: DidRepository + private connectionsModuleConfig: ConnectionsModuleConfig public supportedMessages = [ConnectionRequestMessage] public constructor( connectionService: ConnectionService, outOfBandService: OutOfBandService, routingService: RoutingService, - didRepository: DidRepository + didRepository: DidRepository, + connectionsModuleConfig: ConnectionsModuleConfig ) { this.connectionService = connectionService this.outOfBandService = outOfBandService this.routingService = routingService this.didRepository = didRepository + this.connectionsModuleConfig = connectionsModuleConfig } public async handle(messageContext: HandlerInboundMessage) { @@ -53,7 +57,7 @@ export class ConnectionRequestHandler implements Handler { const connectionRecord = await this.connectionService.processRequest(messageContext, outOfBandRecord) - if (connectionRecord?.autoAcceptConnection ?? messageContext.agentContext.config.autoAcceptConnections) { + if (connectionRecord?.autoAcceptConnection ?? this.connectionsModuleConfig.autoAcceptConnections) { // TODO: Allow rotation of keys used in the invitation for new ones not only when out-of-band is reusable const routing = outOfBandRecord.reusable ? await this.routingService.getRouting(messageContext.agentContext) diff --git a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts index 3d36029345..4b53e220d9 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts @@ -1,6 +1,7 @@ import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { DidResolverService } from '../../dids' import type { OutOfBandService } from '../../oob/OutOfBandService' +import type { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' import type { ConnectionService } from '../services/ConnectionService' import { createOutboundMessage } from '../../../agent/helpers' @@ -12,17 +13,20 @@ export class ConnectionResponseHandler implements Handler { private connectionService: ConnectionService private outOfBandService: OutOfBandService private didResolverService: DidResolverService + private connectionsModuleConfig: ConnectionsModuleConfig public supportedMessages = [ConnectionResponseMessage] public constructor( connectionService: ConnectionService, outOfBandService: OutOfBandService, - didResolverService: DidResolverService + didResolverService: DidResolverService, + connectionsModuleConfig: ConnectionsModuleConfig ) { this.connectionService = connectionService this.outOfBandService = outOfBandService this.didResolverService = didResolverService + this.connectionsModuleConfig = connectionsModuleConfig } public async handle(messageContext: HandlerInboundMessage) { @@ -72,7 +76,7 @@ export class ConnectionResponseHandler implements Handler { // TODO: should we only send ping message in case of autoAcceptConnection or always? // In AATH we have a separate step to send the ping. So for now we'll only do it // if auto accept is enable - if (connection.autoAcceptConnection ?? messageContext.agentContext.config.autoAcceptConnections) { + if (connection.autoAcceptConnection ?? this.connectionsModuleConfig.autoAcceptConnections) { const { message } = await this.connectionService.createTrustPing(messageContext.agentContext, connection, { responseRequested: false, }) diff --git a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts index 20fa4437ec..f9d46cec7b 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts @@ -2,6 +2,7 @@ import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { DidRepository } from '../../dids/repository' import type { OutOfBandService } from '../../oob/OutOfBandService' import type { RoutingService } from '../../routing/services/RoutingService' +import type { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' import type { DidExchangeProtocol } from '../DidExchangeProtocol' import { createOutboundMessage } from '../../../agent/helpers' @@ -14,18 +15,21 @@ export class DidExchangeRequestHandler implements Handler { private outOfBandService: OutOfBandService private routingService: RoutingService private didRepository: DidRepository + private connectionsModuleConfig: ConnectionsModuleConfig public supportedMessages = [DidExchangeRequestMessage] public constructor( didExchangeProtocol: DidExchangeProtocol, outOfBandService: OutOfBandService, routingService: RoutingService, - didRepository: DidRepository + didRepository: DidRepository, + connectionsModuleConfig: ConnectionsModuleConfig ) { this.didExchangeProtocol = didExchangeProtocol this.outOfBandService = outOfBandService this.routingService = routingService this.didRepository = didRepository + this.connectionsModuleConfig = connectionsModuleConfig } public async handle(messageContext: HandlerInboundMessage) { @@ -68,7 +72,7 @@ export class DidExchangeRequestHandler implements Handler { const connectionRecord = await this.didExchangeProtocol.processRequest(messageContext, outOfBandRecord) - if (connectionRecord.autoAcceptConnection ?? messageContext.agentContext.config.autoAcceptConnections) { + if (connectionRecord.autoAcceptConnection ?? this.connectionsModuleConfig.autoAcceptConnections) { // TODO We should add an option to not pass routing and therefore do not rotate keys and use the keys from the invitation // TODO: Allow rotation of keys used in the invitation for new ones not only when out-of-band is reusable const routing = outOfBandRecord.reusable diff --git a/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts index c43ca94a79..f2b40697ea 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts @@ -1,6 +1,7 @@ import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { DidResolverService } from '../../dids' import type { OutOfBandService } from '../../oob/OutOfBandService' +import type { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' import type { DidExchangeProtocol } from '../DidExchangeProtocol' import type { ConnectionService } from '../services' @@ -16,18 +17,21 @@ export class DidExchangeResponseHandler implements Handler { private outOfBandService: OutOfBandService private connectionService: ConnectionService private didResolverService: DidResolverService + private connectionsModuleConfig: ConnectionsModuleConfig public supportedMessages = [DidExchangeResponseMessage] public constructor( didExchangeProtocol: DidExchangeProtocol, outOfBandService: OutOfBandService, connectionService: ConnectionService, - didResolverService: DidResolverService + didResolverService: DidResolverService, + connectionsModuleConfig: ConnectionsModuleConfig ) { this.didExchangeProtocol = didExchangeProtocol this.outOfBandService = outOfBandService this.connectionService = connectionService this.didResolverService = didResolverService + this.connectionsModuleConfig = connectionsModuleConfig } public async handle(messageContext: HandlerInboundMessage) { @@ -98,7 +102,7 @@ export class DidExchangeResponseHandler implements Handler { // TODO: should we only send complete message in case of autoAcceptConnection or always? // In AATH we have a separate step to send the complete. So for now we'll only do it // if auto accept is enabled - if (connection.autoAcceptConnection ?? messageContext.agentContext.config.autoAcceptConnections) { + if (connection.autoAcceptConnection ?? this.connectionsModuleConfig.autoAcceptConnections) { const message = await this.didExchangeProtocol.createComplete( messageContext.agentContext, connection, diff --git a/packages/core/src/modules/connections/index.ts b/packages/core/src/modules/connections/index.ts index f384af2d2a..52fe834617 100644 --- a/packages/core/src/modules/connections/index.ts +++ b/packages/core/src/modules/connections/index.ts @@ -3,5 +3,7 @@ export * from './models' export * from './repository' export * from './services' export * from './ConnectionEvents' -export * from './ConnectionsModule' +export * from './ConnectionsApi' export * from './DidExchangeProtocol' +export * from './ConnectionsModuleConfig' +export * from './ConnectionsModule' diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts new file mode 100644 index 0000000000..658f723ba8 --- /dev/null +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -0,0 +1,634 @@ +import type { AgentMessage } from '../../agent/AgentMessage' +import type { DeleteCredentialOptions } from './CredentialServiceOptions' +import type { + AcceptCredentialOptions, + AcceptOfferOptions, + AcceptProposalOptions, + AcceptRequestOptions, + CreateOfferOptions, + FindCredentialMessageReturn, + FindOfferMessageReturn, + FindProposalMessageReturn, + FindRequestMessageReturn, + GetFormatDataReturn, + NegotiateOfferOptions, + NegotiateProposalOptions, + OfferCredentialOptions, + ProposeCredentialOptions, + SendProblemReportOptions, + ServiceMap, +} from './CredentialsApiOptions' +import type { CredentialFormat } from './formats' +import type { IndyCredentialFormat } from './formats/indy/IndyCredentialFormat' +import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' +import type { CredentialService } from './services/CredentialService' + +import { AgentContext } from '../../agent' +import { MessageSender } from '../../agent/MessageSender' +import { createOutboundMessage } from '../../agent/helpers' +import { InjectionSymbols } from '../../constants' +import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' +import { AriesFrameworkError } from '../../error' +import { Logger } from '../../logger' +import { inject, injectable } from '../../plugins' +import { DidCommMessageRole } from '../../storage' +import { DidCommMessageRepository } from '../../storage/didcomm/DidCommMessageRepository' +import { ConnectionService } from '../connections/services' +import { RoutingService } from '../routing/services/RoutingService' + +import { CredentialsModuleConfig } from './CredentialsModuleConfig' +import { CredentialState } from './models/CredentialState' +import { RevocationNotificationService } from './protocol/revocation-notification/services' +import { V1CredentialService } from './protocol/v1/V1CredentialService' +import { V2CredentialService } from './protocol/v2/V2CredentialService' +import { CredentialRepository } from './repository/CredentialRepository' + +export interface CredentialsApi[]> { + // Proposal methods + proposeCredential(options: ProposeCredentialOptions): Promise + acceptProposal(options: AcceptProposalOptions): Promise + negotiateProposal(options: NegotiateProposalOptions): Promise + + // Offer methods + offerCredential(options: OfferCredentialOptions): Promise + acceptOffer(options: AcceptOfferOptions): Promise + declineOffer(credentialRecordId: string): Promise + negotiateOffer(options: NegotiateOfferOptions): Promise + // out of band + createOffer(options: CreateOfferOptions): Promise<{ + message: AgentMessage + credentialRecord: CredentialExchangeRecord + }> + // Request + // This is for beginning the exchange with a request (no proposal or offer). Only possible + // (currently) with W3C. We will not implement this in phase I + // requestCredential(credentialOptions: RequestCredentialOptions): Promise + + // when the issuer accepts the request he issues the credential to the holder + acceptRequest(options: AcceptRequestOptions): Promise + + // Credential + acceptCredential(options: AcceptCredentialOptions): Promise + sendProblemReport(options: SendProblemReportOptions): Promise + + // Record Methods + getAll(): Promise + getById(credentialRecordId: string): Promise + findById(credentialRecordId: string): Promise + deleteById(credentialRecordId: string, options?: DeleteCredentialOptions): Promise + getFormatData(credentialRecordId: string): Promise> + + // DidComm Message Records + findProposalMessage(credentialExchangeId: string): Promise> + findOfferMessage(credentialExchangeId: string): Promise> + findRequestMessage(credentialExchangeId: string): Promise> + findCredentialMessage(credentialExchangeId: string): Promise> +} + +@injectable() +export class CredentialsApi< + CFs extends CredentialFormat[] = [IndyCredentialFormat], + CSs extends CredentialService[] = [V1CredentialService, V2CredentialService] +> implements CredentialsApi +{ + /** + * Configuration for the connections module + */ + public readonly config: CredentialsModuleConfig + + private connectionService: ConnectionService + private messageSender: MessageSender + private credentialRepository: CredentialRepository + private agentContext: AgentContext + private didCommMessageRepository: DidCommMessageRepository + private routingService: RoutingService + private logger: Logger + private serviceMap: ServiceMap + + public constructor( + messageSender: MessageSender, + connectionService: ConnectionService, + agentContext: AgentContext, + @inject(InjectionSymbols.Logger) logger: Logger, + credentialRepository: CredentialRepository, + mediationRecipientService: RoutingService, + didCommMessageRepository: DidCommMessageRepository, + v1Service: V1CredentialService, + v2Service: V2CredentialService, + // only injected so the handlers will be registered + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _revocationNotificationService: RevocationNotificationService, + config: CredentialsModuleConfig + ) { + this.messageSender = messageSender + this.connectionService = connectionService + this.credentialRepository = credentialRepository + this.routingService = mediationRecipientService + this.agentContext = agentContext + this.didCommMessageRepository = didCommMessageRepository + this.logger = logger + this.config = config + + // Dynamically build service map. This will be extracted once services are registered dynamically + this.serviceMap = [v1Service, v2Service].reduce( + (serviceMap, service) => ({ + ...serviceMap, + [service.version]: service, + }), + {} + ) as ServiceMap + + this.logger.debug(`Initializing Credentials Module for agent ${this.agentContext.config.label}`) + } + + public getService(protocolVersion: PVT): CredentialService { + if (!this.serviceMap[protocolVersion]) { + throw new AriesFrameworkError(`No credential service registered for protocol version ${protocolVersion}`) + } + + return this.serviceMap[protocolVersion] + } + + /** + * Initiate a new credential exchange as holder by sending a credential proposal message + * to the connection with the specified credential options + * + * @param options configuration to use for the proposal + * @returns Credential exchange record associated with the sent proposal message + */ + + public async proposeCredential(options: ProposeCredentialOptions): Promise { + const service = this.getService(options.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) + + const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + + // will get back a credential record -> map to Credential Exchange Record + const { credentialRecord, message } = await service.createProposal(this.agentContext, { + connection, + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + this.logger.debug('We have a message (sending outbound): ', message) + + // send the message here + const outbound = createOutboundMessage(connection, message) + + this.logger.debug('In proposeCredential: Send Proposal to Issuer') + await this.messageSender.sendMessage(this.agentContext, outbound) + return credentialRecord + } + + /** + * Accept a credential proposal as issuer (by sending a credential offer message) to the connection + * associated with the credential record. + * + * @param options config object for accepting the proposal + * @returns Credential exchange record associated with the credential offer + * + */ + public async acceptProposal(options: AcceptProposalOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) + + if (!credentialRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for credential record '${credentialRecord.id}'. Connection-less issuance does not support credential proposal or negotiation.` + ) + } + + // with version we can get the Service + const service = this.getService(credentialRecord.protocolVersion) + + // will get back a credential record -> map to Credential Exchange Record + const { message } = await service.acceptProposal(this.agentContext, { + credentialRecord, + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + // send the message + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const outbound = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outbound) + + return credentialRecord + } + + /** + * Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection + * associated with the credential record. + * + * @param options configuration for the offer see {@link NegotiateProposalOptions} + * @returns Credential exchange record associated with the credential offer + * + */ + public async negotiateProposal(options: NegotiateProposalOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) + + if (!credentialRecord.connectionId) { + throw new AriesFrameworkError( + `No connection id for credential record ${credentialRecord.id} not found. Connection-less issuance does not support negotiation` + ) + } + + // with version we can get the Service + const service = this.getService(credentialRecord.protocolVersion) + + const { message } = await service.negotiateProposal(this.agentContext, { + credentialRecord, + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + + /** + * Initiate a new credential exchange as issuer by sending a credential offer message + * to the connection with the specified connection id. + * + * @param options config options for the credential offer + * @returns Credential exchange record associated with the sent credential offer message + */ + public async offerCredential(options: OfferCredentialOptions): Promise { + const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + const service = this.getService(options.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) + + const { message, credentialRecord } = await service.createOffer(this.agentContext, { + credentialFormats: options.credentialFormats, + autoAcceptCredential: options.autoAcceptCredential, + comment: options.comment, + connection, + }) + + this.logger.debug('Offer Message successfully created; message= ', message) + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + + /** + * Accept a credential offer as holder (by sending a credential request message) to the connection + * associated with the credential record. + * + * @param options The object containing config options of the offer to be accepted + * @returns Object containing offer associated credential record + */ + public async acceptOffer(options: AcceptOfferOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) + + const service = this.getService(credentialRecord.protocolVersion) + + this.logger.debug(`Got a CredentialService object for this version; version = ${service.version}`) + const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) + + // Use connection if present + if (credentialRecord.connectionId) { + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + + const { message } = await service.acceptOffer(this.agentContext, { + credentialRecord, + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + // Use ~service decorator otherwise + else if (offerMessage?.service) { + // Create ~service decorator + const routing = await this.routingService.getRouting(this.agentContext) + const ourService = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.recipientKey.publicKeyBase58], + routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), + }) + const recipientService = offerMessage.service + + const { message } = await service.acceptOffer(this.agentContext, { + credentialRecord, + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + // Set and save ~service decorator to record (to remember our verkey) + message.service = ourService + await this.didCommMessageRepository.saveOrUpdateAgentMessage(this.agentContext, { + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + + await this.messageSender.sendMessageToService(this.agentContext, { + message, + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }) + + return credentialRecord + } + // Cannot send message without connectionId or ~service decorator + else { + throw new AriesFrameworkError( + `Cannot accept offer for credential record without connectionId or ~service decorator on credential offer.` + ) + } + } + + public async declineOffer(credentialRecordId: string): Promise { + const credentialRecord = await this.getById(credentialRecordId) + credentialRecord.assertState(CredentialState.OfferReceived) + + // with version we can get the Service + const service = this.getService(credentialRecord.protocolVersion) + await service.updateState(this.agentContext, credentialRecord, CredentialState.Declined) + + return credentialRecord + } + + public async negotiateOffer(options: NegotiateOfferOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) + + const service = this.getService(credentialRecord.protocolVersion) + const { message } = await service.negotiateOffer(this.agentContext, { + credentialFormats: options.credentialFormats, + credentialRecord, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + if (!credentialRecord.connectionId) { + throw new AriesFrameworkError( + `No connection id for credential record ${credentialRecord.id} not found. Connection-less issuance does not support negotiation` + ) + } + + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + + /** + * Initiate a new credential exchange as issuer by creating a credential offer + * not bound to any connection. The offer must be delivered out-of-band to the holder + * @param options The credential options to use for the offer + * @returns The credential record and credential offer message + */ + public async createOffer(options: CreateOfferOptions): Promise<{ + message: AgentMessage + credentialRecord: CredentialExchangeRecord + }> { + const service = this.getService(options.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) + const { message, credentialRecord } = await service.createOffer(this.agentContext, { + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + + this.logger.debug('Offer Message successfully created; message= ', message) + + return { message, credentialRecord } + } + + /** + * Accept a credential request as holder (by sending a credential request message) to the connection + * associated with the credential record. + * + * @param options The object containing config options of the request + * @returns CredentialExchangeRecord updated with information pertaining to this request + */ + public async acceptRequest(options: AcceptRequestOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) + + // with version we can get the Service + const service = this.getService(credentialRecord.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${credentialRecord.protocolVersion}`) + + const { message } = await service.acceptRequest(this.agentContext, { + credentialRecord, + credentialFormats: options.credentialFormats, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + this.logger.debug('We have a credential message (sending outbound): ', message) + + const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) + const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) + + // Use connection if present + if (credentialRecord.connectionId) { + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + // Use ~service decorator otherwise + else if (requestMessage?.service && offerMessage?.service) { + const recipientService = requestMessage.service + const ourService = offerMessage.service + + message.service = ourService + await this.didCommMessageRepository.saveOrUpdateAgentMessage(this.agentContext, { + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + + await this.messageSender.sendMessageToService(this.agentContext, { + message, + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }) + + return credentialRecord + } + // Cannot send message without connectionId or ~service decorator + else { + throw new AriesFrameworkError( + `Cannot accept request for credential record without connectionId or ~service decorator on credential offer / request.` + ) + } + } + + /** + * Accept a credential as holder (by sending a credential acknowledgement message) to the connection + * associated with the credential record. + * + * @param credentialRecordId The id of the credential record for which to accept the credential + * @returns credential exchange record associated with the sent credential acknowledgement message + * + */ + public async acceptCredential(options: AcceptCredentialOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) + + // with version we can get the Service + const service = this.getService(credentialRecord.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${credentialRecord.protocolVersion}`) + + const { message } = await service.acceptCredential(this.agentContext, { + credentialRecord, + }) + + const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) + const credentialMessage = await service.findCredentialMessage(this.agentContext, credentialRecord.id) + + if (credentialRecord.connectionId) { + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const outboundMessage = createOutboundMessage(connection, message) + + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + // Use ~service decorator otherwise + else if (credentialMessage?.service && requestMessage?.service) { + const recipientService = credentialMessage.service + const ourService = requestMessage.service + + await this.messageSender.sendMessageToService(this.agentContext, { + message, + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }) + + return credentialRecord + } + // Cannot send message without connectionId or ~service decorator + else { + throw new AriesFrameworkError( + `Cannot accept credential without connectionId or ~service decorator on credential message.` + ) + } + } + + /** + * Send problem report message for a credential record + * @param credentialRecordId The id of the credential record for which to send problem report + * @param message message to send + * @returns credential record associated with the credential problem report message + */ + public async sendProblemReport(options: SendProblemReportOptions) { + const credentialRecord = await this.getById(options.credentialRecordId) + if (!credentialRecord.connectionId) { + throw new AriesFrameworkError(`No connectionId found for credential record '${credentialRecord.id}'.`) + } + const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + + const service = this.getService(credentialRecord.protocolVersion) + const problemReportMessage = service.createProblemReport(this.agentContext, { message: options.message }) + problemReportMessage.setThread({ + threadId: credentialRecord.threadId, + }) + const outboundMessage = createOutboundMessage(connection, problemReportMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return credentialRecord + } + + public async getFormatData(credentialRecordId: string): Promise> { + const credentialRecord = await this.getById(credentialRecordId) + const service = this.getService(credentialRecord.protocolVersion) + + return service.getFormatData(this.agentContext, credentialRecordId) + } + + /** + * Retrieve a credential record by id + * + * @param credentialRecordId The credential record id + * @throws {RecordNotFoundError} If no record is found + * @return The credential record + * + */ + public getById(credentialRecordId: string): Promise { + return this.credentialRepository.getById(this.agentContext, credentialRecordId) + } + + /** + * Retrieve all credential records + * + * @returns List containing all credential records + */ + public getAll(): Promise { + return this.credentialRepository.getAll(this.agentContext) + } + + /** + * Find a credential record by id + * + * @param credentialRecordId the credential record id + * @returns The credential record or null if not found + */ + public findById(credentialRecordId: string): Promise { + return this.credentialRepository.findById(this.agentContext, credentialRecordId) + } + + /** + * Delete a credential record by id, also calls service to delete from wallet + * + * @param credentialId the credential record id + * @param options the delete credential options for the delete operation + */ + public async deleteById(credentialId: string, options?: DeleteCredentialOptions) { + const credentialRecord = await this.getById(credentialId) + const service = this.getService(credentialRecord.protocolVersion) + return service.delete(this.agentContext, credentialRecord, options) + } + + public async findProposalMessage(credentialExchangeId: string): Promise> { + const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + + return service.findProposalMessage(this.agentContext, credentialExchangeId) + } + + public async findOfferMessage(credentialExchangeId: string): Promise> { + const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + + return service.findOfferMessage(this.agentContext, credentialExchangeId) + } + + public async findRequestMessage(credentialExchangeId: string): Promise> { + const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + + return service.findRequestMessage(this.agentContext, credentialExchangeId) + } + + public async findCredentialMessage(credentialExchangeId: string): Promise> { + const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + + return service.findCredentialMessage(this.agentContext, credentialExchangeId) + } + + private async getServiceForCredentialExchangeId(credentialExchangeId: string) { + const credentialExchangeRecord = await this.getById(credentialExchangeId) + + return this.getService(credentialExchangeRecord.protocolVersion) + } +} diff --git a/packages/core/src/modules/credentials/CredentialsModuleOptions.ts b/packages/core/src/modules/credentials/CredentialsApiOptions.ts similarity index 84% rename from packages/core/src/modules/credentials/CredentialsModuleOptions.ts rename to packages/core/src/modules/credentials/CredentialsApiOptions.ts index 3224f6d1ca..8dc345bcae 100644 --- a/packages/core/src/modules/credentials/CredentialsModuleOptions.ts +++ b/packages/core/src/modules/credentials/CredentialsApiOptions.ts @@ -41,7 +41,7 @@ interface BaseOptions { } /** - * Interface for CredentialsModule.proposeCredential. Will send a proposal. + * Interface for CredentialsApi.proposeCredential. Will send a proposal. */ export interface ProposeCredentialOptions< CFs extends CredentialFormat[] = CredentialFormat[], @@ -53,7 +53,7 @@ export interface ProposeCredentialOptions< } /** - * Interface for CredentialsModule.acceptProposal. Will send an offer + * Interface for CredentialsApi.acceptProposal. Will send an offer * * credentialFormats is optional because this is an accept method */ @@ -63,7 +63,7 @@ export interface AcceptProposalOptions extends BaseOptions { credentialRecordId: string @@ -71,7 +71,7 @@ export interface NegotiateProposalOptions extends BaseOptions { credentialRecordId: string @@ -111,7 +111,7 @@ export interface NegotiateOfferOptions[]> { - // Proposal methods - proposeCredential(options: ProposeCredentialOptions): Promise - acceptProposal(options: AcceptProposalOptions): Promise - negotiateProposal(options: NegotiateProposalOptions): Promise - - // Offer methods - offerCredential(options: OfferCredentialOptions): Promise - acceptOffer(options: AcceptOfferOptions): Promise - declineOffer(credentialRecordId: string): Promise - negotiateOffer(options: NegotiateOfferOptions): Promise - // out of band - createOffer(options: CreateOfferOptions): Promise<{ - message: AgentMessage - credentialRecord: CredentialExchangeRecord - }> - // Request - // This is for beginning the exchange with a request (no proposal or offer). Only possible - // (currently) with W3C. We will not implement this in phase I - // requestCredential(credentialOptions: RequestCredentialOptions): Promise - - // when the issuer accepts the request he issues the credential to the holder - acceptRequest(options: AcceptRequestOptions): Promise - - // Credential - acceptCredential(options: AcceptCredentialOptions): Promise - sendProblemReport(options: SendProblemReportOptions): Promise - - // Record Methods - getAll(): Promise - getById(credentialRecordId: string): Promise - findById(credentialRecordId: string): Promise - deleteById(credentialRecordId: string, options?: DeleteCredentialOptions): Promise - getFormatData(credentialRecordId: string): Promise> - - // DidComm Message Records - findProposalMessage(credentialExchangeId: string): Promise> - findOfferMessage(credentialExchangeId: string): Promise> - findRequestMessage(credentialExchangeId: string): Promise> - findCredentialMessage(credentialExchangeId: string): Promise> -} - -@module() -@injectable() -export class CredentialsModule< - CFs extends CredentialFormat[] = [IndyCredentialFormat], - CSs extends CredentialService[] = [V1CredentialService, V2CredentialService] -> implements CredentialsModule -{ - private connectionService: ConnectionService - private messageSender: MessageSender - private credentialRepository: CredentialRepository - private agentContext: AgentContext - private didCommMessageRepo: DidCommMessageRepository - private routingService: RoutingService - private logger: Logger - private serviceMap: ServiceMap - - public constructor( - messageSender: MessageSender, - connectionService: ConnectionService, - agentContext: AgentContext, - @inject(InjectionSymbols.Logger) logger: Logger, - credentialRepository: CredentialRepository, - mediationRecipientService: RoutingService, - didCommMessageRepository: DidCommMessageRepository, - v1Service: V1CredentialService, - v2Service: V2CredentialService, - // only injected so the handlers will be registered - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _revocationNotificationService: RevocationNotificationService - ) { - this.messageSender = messageSender - this.connectionService = connectionService - this.credentialRepository = credentialRepository - this.routingService = mediationRecipientService - this.agentContext = agentContext - this.didCommMessageRepo = didCommMessageRepository - this.logger = logger - - // Dynamically build service map. This will be extracted once services are registered dynamically - this.serviceMap = [v1Service, v2Service].reduce( - (serviceMap, service) => ({ - ...serviceMap, - [service.version]: service, - }), - {} - ) as ServiceMap - - this.logger.debug(`Initializing Credentials Module for agent ${this.agentContext.config.label}`) - } - - public getService(protocolVersion: PVT): CredentialService { - if (!this.serviceMap[protocolVersion]) { - throw new AriesFrameworkError(`No credential service registered for protocol version ${protocolVersion}`) - } - - return this.serviceMap[protocolVersion] - } - - /** - * Initiate a new credential exchange as holder by sending a credential proposal message - * to the connection with the specified credential options - * - * @param options configuration to use for the proposal - * @returns Credential exchange record associated with the sent proposal message - */ - - public async proposeCredential(options: ProposeCredentialOptions): Promise { - const service = this.getService(options.protocolVersion) - - this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) - - const connection = await this.connectionService.getById(this.agentContext, options.connectionId) - - // will get back a credential record -> map to Credential Exchange Record - const { credentialRecord, message } = await service.createProposal(this.agentContext, { - connection, - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - this.logger.debug('We have a message (sending outbound): ', message) - - // send the message here - const outbound = createOutboundMessage(connection, message) - - this.logger.debug('In proposeCredential: Send Proposal to Issuer') - await this.messageSender.sendMessage(this.agentContext, outbound) - return credentialRecord - } - - /** - * Accept a credential proposal as issuer (by sending a credential offer message) to the connection - * associated with the credential record. - * - * @param options config object for accepting the proposal - * @returns Credential exchange record associated with the credential offer - * - */ - public async acceptProposal(options: AcceptProposalOptions): Promise { - const credentialRecord = await this.getById(options.credentialRecordId) - - if (!credentialRecord.connectionId) { - throw new AriesFrameworkError( - `No connectionId found for credential record '${credentialRecord.id}'. Connection-less issuance does not support credential proposal or negotiation.` - ) - } - - // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) - - // will get back a credential record -> map to Credential Exchange Record - const { message } = await service.acceptProposal(this.agentContext, { - credentialRecord, - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - // send the message - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outbound = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outbound) - - return credentialRecord - } - - /** - * Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection - * associated with the credential record. - * - * @param options configuration for the offer see {@link NegotiateProposalOptions} - * @returns Credential exchange record associated with the credential offer - * - */ - public async negotiateProposal(options: NegotiateProposalOptions): Promise { - const credentialRecord = await this.getById(options.credentialRecordId) - - if (!credentialRecord.connectionId) { - throw new AriesFrameworkError( - `No connection id for credential record ${credentialRecord.id} not found. Connection-less issuance does not support negotiation` - ) - } - - // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) - - const { message } = await service.negotiateProposal(this.agentContext, { - credentialRecord, - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return credentialRecord - } - - /** - * Initiate a new credential exchange as issuer by sending a credential offer message - * to the connection with the specified connection id. - * - * @param options config options for the credential offer - * @returns Credential exchange record associated with the sent credential offer message - */ - public async offerCredential(options: OfferCredentialOptions): Promise { - const connection = await this.connectionService.getById(this.agentContext, options.connectionId) - const service = this.getService(options.protocolVersion) - - this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) - - const { message, credentialRecord } = await service.createOffer(this.agentContext, { - credentialFormats: options.credentialFormats, - autoAcceptCredential: options.autoAcceptCredential, - comment: options.comment, - connection, - }) - - this.logger.debug('Offer Message successfully created; message= ', message) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return credentialRecord - } - - /** - * Accept a credential offer as holder (by sending a credential request message) to the connection - * associated with the credential record. - * - * @param options The object containing config options of the offer to be accepted - * @returns Object containing offer associated credential record - */ - public async acceptOffer(options: AcceptOfferOptions): Promise { - const credentialRecord = await this.getById(options.credentialRecordId) - - const service = this.getService(credentialRecord.protocolVersion) - - this.logger.debug(`Got a CredentialService object for this version; version = ${service.version}`) - const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) - - // Use connection if present - if (credentialRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - - const { message } = await service.acceptOffer(this.agentContext, { - credentialRecord, - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return credentialRecord - } - // Use ~service decorator otherwise - else if (offerMessage?.service) { - // Create ~service decorator - const routing = await this.routingService.getRouting(this.agentContext) - const ourService = new ServiceDecorator({ - serviceEndpoint: routing.endpoints[0], - recipientKeys: [routing.recipientKey.publicKeyBase58], - routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), - }) - const recipientService = offerMessage.service - - const { message } = await service.acceptOffer(this.agentContext, { - credentialRecord, - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - // Set and save ~service decorator to record (to remember our verkey) - message.service = ourService - await this.didCommMessageRepo.saveOrUpdateAgentMessage(this.agentContext, { - agentMessage: message, - role: DidCommMessageRole.Sender, - associatedRecordId: credentialRecord.id, - }) - - await this.messageSender.sendMessageToService(this.agentContext, { - message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, - }) - - return credentialRecord - } - // Cannot send message without connectionId or ~service decorator - else { - throw new AriesFrameworkError( - `Cannot accept offer for credential record without connectionId or ~service decorator on credential offer.` - ) - } - } - - public async declineOffer(credentialRecordId: string): Promise { - const credentialRecord = await this.getById(credentialRecordId) - credentialRecord.assertState(CredentialState.OfferReceived) - - // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) - await service.updateState(this.agentContext, credentialRecord, CredentialState.Declined) - - return credentialRecord - } - - public async negotiateOffer(options: NegotiateOfferOptions): Promise { - const credentialRecord = await this.getById(options.credentialRecordId) - - const service = this.getService(credentialRecord.protocolVersion) - const { message } = await service.negotiateOffer(this.agentContext, { - credentialFormats: options.credentialFormats, - credentialRecord, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - if (!credentialRecord.connectionId) { - throw new AriesFrameworkError( - `No connection id for credential record ${credentialRecord.id} not found. Connection-less issuance does not support negotiation` - ) - } +import { V1CredentialService } from './protocol/v1' +import { V2CredentialService } from './protocol/v2' +import { CredentialRepository } from './repository' - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) +export class CredentialsModule implements Module { + public readonly config: CredentialsModuleConfig - return credentialRecord - } - - /** - * Initiate a new credential exchange as issuer by creating a credential offer - * not bound to any connection. The offer must be delivered out-of-band to the holder - * @param options The credential options to use for the offer - * @returns The credential record and credential offer message - */ - public async createOffer(options: CreateOfferOptions): Promise<{ - message: AgentMessage - credentialRecord: CredentialExchangeRecord - }> { - const service = this.getService(options.protocolVersion) - - this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) - const { message, credentialRecord } = await service.createOffer(this.agentContext, { - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - - this.logger.debug('Offer Message successfully created; message= ', message) - - return { message, credentialRecord } - } - - /** - * Accept a credential request as holder (by sending a credential request message) to the connection - * associated with the credential record. - * - * @param options The object containing config options of the request - * @returns CredentialExchangeRecord updated with information pertaining to this request - */ - public async acceptRequest(options: AcceptRequestOptions): Promise { - const credentialRecord = await this.getById(options.credentialRecordId) - - // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) - - this.logger.debug(`Got a CredentialService object for version ${credentialRecord.protocolVersion}`) - - const { message } = await service.acceptRequest(this.agentContext, { - credentialRecord, - credentialFormats: options.credentialFormats, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - this.logger.debug('We have a credential message (sending outbound): ', message) - - const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) - const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) - - // Use connection if present - if (credentialRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return credentialRecord - } - // Use ~service decorator otherwise - else if (requestMessage?.service && offerMessage?.service) { - const recipientService = requestMessage.service - const ourService = offerMessage.service - - message.service = ourService - await this.didCommMessageRepo.saveOrUpdateAgentMessage(this.agentContext, { - agentMessage: message, - role: DidCommMessageRole.Sender, - associatedRecordId: credentialRecord.id, - }) - - await this.messageSender.sendMessageToService(this.agentContext, { - message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, - }) - - return credentialRecord - } - // Cannot send message without connectionId or ~service decorator - else { - throw new AriesFrameworkError( - `Cannot accept request for credential record without connectionId or ~service decorator on credential offer / request.` - ) - } - } - - /** - * Accept a credential as holder (by sending a credential acknowledgement message) to the connection - * associated with the credential record. - * - * @param credentialRecordId The id of the credential record for which to accept the credential - * @returns credential exchange record associated with the sent credential acknowledgement message - * - */ - public async acceptCredential(options: AcceptCredentialOptions): Promise { - const credentialRecord = await this.getById(options.credentialRecordId) - - // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) - - this.logger.debug(`Got a CredentialService object for version ${credentialRecord.protocolVersion}`) - - const { message } = await service.acceptCredential(this.agentContext, { - credentialRecord, - }) - - const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) - const credentialMessage = await service.findCredentialMessage(this.agentContext, credentialRecord.id) - - if (credentialRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, message) - - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return credentialRecord - } - // Use ~service decorator otherwise - else if (credentialMessage?.service && requestMessage?.service) { - const recipientService = credentialMessage.service - const ourService = requestMessage.service - - await this.messageSender.sendMessageToService(this.agentContext, { - message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, - }) - - return credentialRecord - } - // Cannot send message without connectionId or ~service decorator - else { - throw new AriesFrameworkError( - `Cannot accept credential without connectionId or ~service decorator on credential message.` - ) - } - } - - /** - * Send problem report message for a credential record - * @param credentialRecordId The id of the credential record for which to send problem report - * @param message message to send - * @returns credential record associated with the credential problem report message - */ - public async sendProblemReport(options: SendProblemReportOptions) { - const credentialRecord = await this.getById(options.credentialRecordId) - if (!credentialRecord.connectionId) { - throw new AriesFrameworkError(`No connectionId found for credential record '${credentialRecord.id}'.`) - } - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - - const service = this.getService(credentialRecord.protocolVersion) - const problemReportMessage = service.createProblemReport(this.agentContext, { message: options.message }) - problemReportMessage.setThread({ - threadId: credentialRecord.threadId, - }) - const outboundMessage = createOutboundMessage(connection, problemReportMessage) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return credentialRecord - } - - public async getFormatData(credentialRecordId: string): Promise> { - const credentialRecord = await this.getById(credentialRecordId) - const service = this.getService(credentialRecord.protocolVersion) - - return service.getFormatData(this.agentContext, credentialRecordId) - } - - /** - * Retrieve a credential record by id - * - * @param credentialRecordId The credential record id - * @throws {RecordNotFoundError} If no record is found - * @return The credential record - * - */ - public getById(credentialRecordId: string): Promise { - return this.credentialRepository.getById(this.agentContext, credentialRecordId) - } - - /** - * Retrieve all credential records - * - * @returns List containing all credential records - */ - public getAll(): Promise { - return this.credentialRepository.getAll(this.agentContext) - } - - /** - * Find a credential record by id - * - * @param credentialRecordId the credential record id - * @returns The credential record or null if not found - */ - public findById(credentialRecordId: string): Promise { - return this.credentialRepository.findById(this.agentContext, credentialRecordId) - } - - /** - * Delete a credential record by id, also calls service to delete from wallet - * - * @param credentialId the credential record id - * @param options the delete credential options for the delete operation - */ - public async deleteById(credentialId: string, options?: DeleteCredentialOptions) { - const credentialRecord = await this.getById(credentialId) - const service = this.getService(credentialRecord.protocolVersion) - return service.delete(this.agentContext, credentialRecord, options) - } - - public async findProposalMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - - return service.findProposalMessage(this.agentContext, credentialExchangeId) - } - - public async findOfferMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - - return service.findOfferMessage(this.agentContext, credentialExchangeId) - } - - public async findRequestMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - - return service.findRequestMessage(this.agentContext, credentialExchangeId) - } - - public async findCredentialMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - - return service.findCredentialMessage(this.agentContext, credentialExchangeId) - } - - private async getServiceForCredentialExchangeId(credentialExchangeId: string) { - const credentialExchangeRecord = await this.getById(credentialExchangeId) - - return this.getService(credentialExchangeRecord.protocolVersion) + public constructor(config?: CredentialsModuleConfigOptions) { + this.config = new CredentialsModuleConfig(config) } /** * Registers the dependencies of the credentials module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(CredentialsModule) + dependencyManager.registerContextScoped(CredentialsApi) + + // Config + dependencyManager.registerInstance(CredentialsModuleConfig, this.config) // Services dependencyManager.registerSingleton(V1CredentialService) diff --git a/packages/core/src/modules/credentials/CredentialsModuleConfig.ts b/packages/core/src/modules/credentials/CredentialsModuleConfig.ts new file mode 100644 index 0000000000..7bce4095f4 --- /dev/null +++ b/packages/core/src/modules/credentials/CredentialsModuleConfig.ts @@ -0,0 +1,27 @@ +import { AutoAcceptCredential } from './models' + +/** + * CredentialsModuleConfigOptions defines the interface for the options of the CredentialsModuleConfig class. + * This can contain optional parameters that have default values in the config class itself. + */ +export interface CredentialsModuleConfigOptions { + /** + * Whether to automatically accept credential messages. Applies to all issue credential protocol versions. + * + * @default {@link AutoAcceptCredential.Never} + */ + autoAcceptCredentials?: AutoAcceptCredential +} + +export class CredentialsModuleConfig { + private options: CredentialsModuleConfigOptions + + public constructor(options?: CredentialsModuleConfigOptions) { + this.options = options ?? {} + } + + /** See {@link CredentialsModuleConfigOptions.autoAcceptCredentials} */ + public get autoAcceptCredentials() { + return this.options.autoAcceptCredentials ?? AutoAcceptCredential.Never + } +} diff --git a/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts new file mode 100644 index 0000000000..b430d90d9c --- /dev/null +++ b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts @@ -0,0 +1,33 @@ +import { DependencyManager } from '../../../plugins/DependencyManager' +import { CredentialsApi } from '../CredentialsApi' +import { CredentialsModule } from '../CredentialsModule' +import { CredentialsModuleConfig } from '../CredentialsModuleConfig' +import { IndyCredentialFormatService } from '../formats' +import { V1CredentialService, V2CredentialService } from '../protocol' +import { RevocationNotificationService } from '../protocol/revocation-notification/services' +import { CredentialRepository } from '../repository' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('CredentialsModule', () => { + test('registers dependencies on the dependency manager', () => { + const credentialsModule = new CredentialsModule() + credentialsModule.register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(CredentialsApi) + + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(CredentialsModuleConfig, credentialsModule.config) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(5) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V1CredentialService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V2CredentialService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(RevocationNotificationService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(CredentialRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyCredentialFormatService) + }) +}) diff --git a/packages/core/src/modules/credentials/__tests__/CredentialsModuleConfig.test.ts b/packages/core/src/modules/credentials/__tests__/CredentialsModuleConfig.test.ts new file mode 100644 index 0000000000..5725cd7378 --- /dev/null +++ b/packages/core/src/modules/credentials/__tests__/CredentialsModuleConfig.test.ts @@ -0,0 +1,18 @@ +import { CredentialsModuleConfig } from '../CredentialsModuleConfig' +import { AutoAcceptCredential } from '../models' + +describe('CredentialsModuleConfig', () => { + test('sets default values', () => { + const config = new CredentialsModuleConfig() + + expect(config.autoAcceptCredentials).toBe(AutoAcceptCredential.Never) + }) + + test('sets values', () => { + const config = new CredentialsModuleConfig({ + autoAcceptCredentials: AutoAcceptCredential.Always, + }) + + expect(config.autoAcceptCredentials).toBe(AutoAcceptCredential.Always) + }) +}) diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts index 527e37518e..9f15c1152f 100644 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts @@ -5,8 +5,8 @@ import type { IndyCredProposeOptions } from './models/IndyCredPropose' import type { Cred, CredOffer, CredReq } from 'indy-sdk' /** - * This defines the module payload for calling CredentialsModule.createProposal - * or CredentialsModule.negotiateOffer + * This defines the module payload for calling CredentialsApi.createProposal + * or CredentialsApi.negotiateOffer */ export interface IndyProposeCredentialFormat extends IndyCredProposeOptions { attributes?: CredentialPreviewAttributeOptions[] @@ -14,7 +14,7 @@ export interface IndyProposeCredentialFormat extends IndyCredProposeOptions { } /** - * This defines the module payload for calling CredentialsModule.acceptProposal + * This defines the module payload for calling CredentialsApi.acceptProposal */ export interface IndyAcceptProposalFormat { credentialDefinitionId?: string @@ -27,8 +27,8 @@ export interface IndyAcceptOfferFormat { } /** - * This defines the module payload for calling CredentialsModule.offerCredential - * or CredentialsModule.negotiateProposal + * This defines the module payload for calling CredentialsApi.offerCredential + * or CredentialsApi.negotiateProposal */ export interface IndyOfferCredentialFormat { credentialDefinitionId: string diff --git a/packages/core/src/modules/credentials/index.ts b/packages/core/src/modules/credentials/index.ts index 8b24da2371..d34680afe1 100644 --- a/packages/core/src/modules/credentials/index.ts +++ b/packages/core/src/modules/credentials/index.ts @@ -1,8 +1,9 @@ -export * from './CredentialsModule' +export * from './CredentialsApi' +export * from './CredentialsApiOptions' export * from './repository' - export * from './CredentialEvents' -export * from './CredentialsModuleOptions' export * from './models' export * from './formats' export * from './protocol' +export * from './CredentialsModule' +export * from './CredentialsModuleConfig' diff --git a/packages/core/src/modules/credentials/models/CredentialAutoAcceptType.ts b/packages/core/src/modules/credentials/models/CredentialAutoAcceptType.ts index 79d11568e7..397e1ff70a 100644 --- a/packages/core/src/modules/credentials/models/CredentialAutoAcceptType.ts +++ b/packages/core/src/modules/credentials/models/CredentialAutoAcceptType.ts @@ -2,12 +2,12 @@ * Typing of the state for auto acceptance */ export enum AutoAcceptCredential { - // Always auto accepts the credential no matter if it changed in subsequent steps + /** Always auto accepts the credential no matter if it changed in subsequent steps */ Always = 'always', - // Needs one acceptation and the rest will be automated if nothing changes + /** Needs one acceptation and the rest will be automated if nothing changes */ ContentApproved = 'contentApproved', - // Never auto accept a credential + /** Never auto accept a credential */ Never = 'never', } diff --git a/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts b/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts index 938dbf7dd6..c9bba83a18 100644 --- a/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts +++ b/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts @@ -14,7 +14,7 @@ import type { NegotiateOfferOptions, NegotiateProposalOptions, } from '../../CredentialServiceOptions' -import type { GetFormatDataReturn } from '../../CredentialsModuleOptions' +import type { GetFormatDataReturn } from '../../CredentialsApiOptions' import type { CredentialFormat } from '../../formats' import type { IndyCredentialFormat } from '../../formats/indy/IndyCredentialFormat' @@ -32,6 +32,7 @@ import { uuid } from '../../../../utils/uuid' import { AckStatus } from '../../../common' import { ConnectionService } from '../../../connections/services' import { RoutingService } from '../../../routing/services/RoutingService' +import { CredentialsModuleConfig } from '../../CredentialsModuleConfig' import { CredentialProblemReportReason } from '../../errors' import { IndyCredentialFormatService } from '../../formats/indy/IndyCredentialFormatService' import { IndyCredPropose } from '../../formats/indy/models' @@ -68,6 +69,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat private connectionService: ConnectionService private formatService: IndyCredentialFormatService private routingService: RoutingService + private credentialsModuleConfig: CredentialsModuleConfig public constructor( connectionService: ConnectionService, @@ -77,12 +79,14 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat dispatcher: Dispatcher, eventEmitter: EventEmitter, credentialRepository: CredentialRepository, - formatService: IndyCredentialFormatService + formatService: IndyCredentialFormatService, + credentialsModuleConfig: CredentialsModuleConfig ) { super(credentialRepository, didCommMessageRepository, eventEmitter, dispatcher, logger) this.connectionService = connectionService this.formatService = formatService this.routingService = routingService + this.credentialsModuleConfig = credentialsModuleConfig this.registerHandlers() } @@ -963,7 +967,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat const { credentialRecord, proposalMessage } = options const autoAccept = composeAutoAccept( credentialRecord.autoAcceptCredential, - agentContext.config.autoAcceptCredentials + this.credentialsModuleConfig.autoAcceptCredentials ) // Handle always / never cases @@ -999,7 +1003,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat const { credentialRecord, offerMessage } = options const autoAccept = composeAutoAccept( credentialRecord.autoAcceptCredential, - agentContext.config.autoAcceptCredentials + this.credentialsModuleConfig.autoAcceptCredentials ) // Handle always / never cases @@ -1035,7 +1039,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat const { credentialRecord, requestMessage } = options const autoAccept = composeAutoAccept( credentialRecord.autoAcceptCredential, - agentContext.config.autoAcceptCredentials + this.credentialsModuleConfig.autoAcceptCredentials ) // Handle always / never cases @@ -1067,7 +1071,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat const { credentialRecord, credentialMessage } = options const autoAccept = composeAutoAccept( credentialRecord.autoAcceptCredential, - agentContext.config.autoAcceptCredentials + this.credentialsModuleConfig.autoAcceptCredentials ) // Handle always / never cases diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts index 343751b4d5..3e68856131 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts @@ -24,6 +24,7 @@ import { DidExchangeState } from '../../../../connections' import { ConnectionService } from '../../../../connections/services/ConnectionService' import { RoutingService } from '../../../../routing/services/RoutingService' import { CredentialEventTypes } from '../../../CredentialEvents' +import { CredentialsModuleConfig } from '../../../CredentialsModuleConfig' import { credDef, credReq } from '../../../__tests__/fixtures' import { CredentialProblemReportReason } from '../../../errors/CredentialProblemReportReason' import { IndyCredentialFormatService } from '../../../formats/indy/IndyCredentialFormatService' @@ -248,7 +249,8 @@ describe('V1CredentialService', () => { dispatcher, eventEmitter, credentialRepository, - indyCredentialFormatService + indyCredentialFormatService, + new CredentialsModuleConfig() ) }) diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts index 3fd459ece5..3d22169e93 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts @@ -16,6 +16,7 @@ import { ConnectionService } from '../../../../connections/services/ConnectionSe import { IndyLedgerService } from '../../../../ledger/services' import { RoutingService } from '../../../../routing/services/RoutingService' import { CredentialEventTypes } from '../../../CredentialEvents' +import { CredentialsModuleConfig } from '../../../CredentialsModuleConfig' import { schema, credDef } from '../../../__tests__/fixtures' import { IndyCredentialFormatService } from '../../../formats' import { CredentialFormatSpec } from '../../../models' @@ -114,7 +115,8 @@ describe('V1CredentialServiceProposeOffer', () => { dispatcher, eventEmitter, credentialRepository, - indyCredentialFormatService + indyCredentialFormatService, + new CredentialsModuleConfig() ) }) diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts index a8cf883879..3f8c508400 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts @@ -1,6 +1,6 @@ import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { AcceptOfferOptions, AcceptRequestOptions, CreateOfferOptions } from '../../../CredentialsModuleOptions' +import type { AcceptOfferOptions, AcceptRequestOptions, CreateOfferOptions } from '../../../CredentialsApiOptions' import { ReplaySubject, Subject } from 'rxjs' diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts index e0f8ef7fdc..75cbdcae29 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts @@ -1,6 +1,6 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections' -import type { AcceptOfferOptions, AcceptProposalOptions } from '../../../CredentialsModuleOptions' +import type { AcceptOfferOptions, AcceptProposalOptions } from '../../../CredentialsApiOptions' import type { Schema } from 'indy-sdk' import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts index bf12db449a..c8b986d97e 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts @@ -40,9 +40,7 @@ export class V1IssueCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: HandlerInboundMessage ) { - this.logger.info( - `Automatically sending acknowledgement with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` - ) + this.logger.info(`Automatically sending acknowledgement with autoAccept`) const { message } = await this.credentialService.acceptCredential(messageContext.agentContext, { credentialRecord, }) diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts index 207cbff379..510c5de434 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts @@ -46,9 +46,7 @@ export class V1OfferCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: HandlerInboundMessage ) { - this.logger.info( - `Automatically sending request with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` - ) + this.logger.info(`Automatically sending request with autoAccept`) if (messageContext.connection) { const { message } = await this.credentialService.acceptOffer(messageContext.agentContext, { credentialRecord }) diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts index 38c32018d7..05dc7371af 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts @@ -36,9 +36,7 @@ export class V1ProposeCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: HandlerInboundMessage ) { - this.logger.info( - `Automatically sending offer with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` - ) + this.logger.info(`Automatically sending offer with autoAccept`) if (!messageContext.connection) { this.logger.error('No connection on the messageContext, aborting auto accept') diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts index a5eb94ad41..f155001022 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts @@ -41,9 +41,7 @@ export class V1RequestCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: HandlerInboundMessage ) { - this.logger.info( - `Automatically sending credential with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` - ) + this.logger.info(`Automatically sending credential with autoAccept`) const offerMessage = await this.credentialService.findOfferMessage(messageContext.agentContext, credentialRecord.id) diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts index 75023e3ddc..177af825f4 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts @@ -37,6 +37,7 @@ import { uuid } from '../../../../utils/uuid' import { AckStatus } from '../../../common' import { ConnectionService } from '../../../connections' import { RoutingService } from '../../../routing/services/RoutingService' +import { CredentialsModuleConfig } from '../../CredentialsModuleConfig' import { CredentialProblemReportReason } from '../../errors' import { IndyCredentialFormatService } from '../../formats/indy/IndyCredentialFormatService' import { AutoAcceptCredential, CredentialState } from '../../models' @@ -68,6 +69,7 @@ export class V2CredentialService private routingService: RoutingService + private credentialsModuleConfig: CredentialsModuleConfig private formatServiceMap: { [key: string]: CredentialFormatService } public constructor( @@ -78,12 +80,14 @@ export class V2CredentialService { eventEmitter, credentialRepository, indyCredentialFormatService, - agentConfig.logger + agentConfig.logger, + new CredentialsModuleConfig() ) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts index 4bb5ba769d..45c987a818 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts @@ -16,6 +16,7 @@ import { ConnectionService } from '../../../../connections/services/ConnectionSe import { IndyLedgerService } from '../../../../ledger/services' import { RoutingService } from '../../../../routing/services/RoutingService' import { CredentialEventTypes } from '../../../CredentialEvents' +import { CredentialsModuleConfig } from '../../../CredentialsModuleConfig' import { credDef, schema } from '../../../__tests__/fixtures' import { IndyCredentialFormatService } from '../../../formats/indy/IndyCredentialFormatService' import { CredentialFormatSpec } from '../../../models' @@ -103,7 +104,8 @@ describe('V2CredentialServiceOffer', () => { eventEmitter, credentialRepository, indyCredentialFormatService, - agentConfig.logger + agentConfig.logger, + new CredentialsModuleConfig() ) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts index 157948df9e..ced9f4f195 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts @@ -1,6 +1,6 @@ import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { AcceptOfferOptions, AcceptRequestOptions, CreateOfferOptions } from '../../../CredentialsModuleOptions' +import type { AcceptOfferOptions, AcceptRequestOptions, CreateOfferOptions } from '../../../CredentialsApiOptions' import { ReplaySubject, Subject } from 'rxjs' diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts index 5036232d4a..6486f445d4 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts @@ -1,6 +1,6 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections' -import type { AcceptOfferOptions, AcceptProposalOptions } from '../../../CredentialsModuleOptions' +import type { AcceptOfferOptions, AcceptProposalOptions } from '../../../CredentialsApiOptions' import type { Schema } from 'indy-sdk' import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts index 9329fa298a..d2279dd013 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts @@ -43,9 +43,7 @@ export class V2IssueCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: HandlerInboundMessage ) { - this.logger.info( - `Automatically sending acknowledgement with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` - ) + this.logger.info(`Automatically sending acknowledgement with autoAccept`) const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts index 7d3c3b6419..f656ae8351 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts @@ -49,9 +49,7 @@ export class V2OfferCredentialHandler implements Handler { messageContext: HandlerInboundMessage, offerMessage?: V2OfferCredentialMessage ) { - this.logger.info( - `Automatically sending request with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` - ) + this.logger.info(`Automatically sending request with autoAccept`) if (messageContext.connection) { const { message } = await this.credentialService.acceptOffer(messageContext.agentContext, { diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts index 9c63943302..c005480617 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts @@ -35,9 +35,7 @@ export class V2ProposeCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: HandlerInboundMessage ) { - this.logger.info( - `Automatically sending offer with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` - ) + this.logger.info(`Automatically sending offer with autoAccept`) if (!messageContext.connection) { this.logger.error('No connection on the messageContext, aborting auto accept') diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts index 6f5145dedd..b8076c9a7b 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts @@ -44,9 +44,7 @@ export class V2RequestCredentialHandler implements Handler { credentialRecord: CredentialExchangeRecord, messageContext: InboundMessageContext ) { - this.logger.info( - `Automatically sending credential with autoAccept on ${messageContext.agentContext.config.autoAcceptCredentials}` - ) + this.logger.info(`Automatically sending credential with autoAccept`) const offerMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, diff --git a/packages/core/src/modules/dids/DidsApi.ts b/packages/core/src/modules/dids/DidsApi.ts new file mode 100644 index 0000000000..7599be95cb --- /dev/null +++ b/packages/core/src/modules/dids/DidsApi.ts @@ -0,0 +1,37 @@ +import type { Key } from '../../crypto' +import type { DidResolutionOptions } from './types' + +import { AgentContext } from '../../agent' +import { injectable } from '../../plugins' + +import { DidRepository } from './repository' +import { DidResolverService } from './services/DidResolverService' + +@injectable() +export class DidsApi { + private resolverService: DidResolverService + private didRepository: DidRepository + private agentContext: AgentContext + + public constructor(resolverService: DidResolverService, didRepository: DidRepository, agentContext: AgentContext) { + this.resolverService = resolverService + this.didRepository = didRepository + this.agentContext = agentContext + } + + public resolve(didUrl: string, options?: DidResolutionOptions) { + return this.resolverService.resolve(this.agentContext, didUrl, options) + } + + public resolveDidDocument(didUrl: string) { + return this.resolverService.resolveDidDocument(this.agentContext, didUrl) + } + + public findByRecipientKey(recipientKey: Key) { + return this.didRepository.findByRecipientKey(this.agentContext, recipientKey) + } + + public findAllByRecipientKey(recipientKey: Key) { + return this.didRepository.findAllByRecipientKey(this.agentContext, recipientKey) + } +} diff --git a/packages/core/src/modules/dids/DidsModule.ts b/packages/core/src/modules/dids/DidsModule.ts index d10ff463f1..5cc570ec48 100644 --- a/packages/core/src/modules/dids/DidsModule.ts +++ b/packages/core/src/modules/dids/DidsModule.ts @@ -1,48 +1,16 @@ -import type { Key } from '../../crypto' -import type { DependencyManager } from '../../plugins' -import type { DidResolutionOptions } from './types' - -import { AgentContext } from '../../agent' -import { injectable, module } from '../../plugins' +import type { DependencyManager, Module } from '../../plugins' +import { DidsApi } from './DidsApi' import { DidRepository } from './repository' -import { DidResolverService } from './services/DidResolverService' - -@module() -@injectable() -export class DidsModule { - private resolverService: DidResolverService - private didRepository: DidRepository - private agentContext: AgentContext - - public constructor(resolverService: DidResolverService, didRepository: DidRepository, agentContext: AgentContext) { - this.resolverService = resolverService - this.didRepository = didRepository - this.agentContext = agentContext - } - - public resolve(didUrl: string, options?: DidResolutionOptions) { - return this.resolverService.resolve(this.agentContext, didUrl, options) - } - - public resolveDidDocument(didUrl: string) { - return this.resolverService.resolveDidDocument(this.agentContext, didUrl) - } - - public findByRecipientKey(recipientKey: Key) { - return this.didRepository.findByRecipientKey(this.agentContext, recipientKey) - } - - public findAllByRecipientKey(recipientKey: Key) { - return this.didRepository.findAllByRecipientKey(this.agentContext, recipientKey) - } +import { DidResolverService } from './services' +export class DidsModule implements Module { /** * Registers the dependencies of the dids module module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(DidsModule) + dependencyManager.registerContextScoped(DidsApi) // Services dependencyManager.registerSingleton(DidResolverService) diff --git a/packages/core/src/modules/dids/__tests__/DidsModule.test.ts b/packages/core/src/modules/dids/__tests__/DidsModule.test.ts new file mode 100644 index 0000000000..00926a9ace --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/DidsModule.test.ts @@ -0,0 +1,23 @@ +import { DependencyManager } from '../../../plugins/DependencyManager' +import { DidsApi } from '../DidsApi' +import { DidsModule } from '../DidsModule' +import { DidRepository } from '../repository' +import { DidResolverService } from '../services' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('DidsModule', () => { + test('registers dependencies on the dependency manager', () => { + new DidsModule().register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(DidsApi) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(2) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRepository) + }) +}) diff --git a/packages/core/src/modules/dids/index.ts b/packages/core/src/modules/dids/index.ts index b34160b8de..d9473ea73f 100644 --- a/packages/core/src/modules/dids/index.ts +++ b/packages/core/src/modules/dids/index.ts @@ -1,6 +1,7 @@ export * from './types' export * from './domain' -export * from './DidsModule' +export * from './DidsApi' export * from './repository' export * from './services' +export * from './DidsModule' export { DidKey } from './methods/key/DidKey' diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts new file mode 100644 index 0000000000..5ab8193324 --- /dev/null +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts @@ -0,0 +1,101 @@ +import type { AgentMessageProcessedEvent } from '../../agent/Events' +import type { ParsedMessageType } from '../../utils/messageType' + +import { firstValueFrom, of, ReplaySubject, Subject } from 'rxjs' +import { catchError, filter, map, takeUntil, timeout } from 'rxjs/operators' + +import { AgentContext } from '../../agent' +import { Dispatcher } from '../../agent/Dispatcher' +import { EventEmitter } from '../../agent/EventEmitter' +import { filterContextCorrelationId, AgentEventTypes } from '../../agent/Events' +import { MessageSender } from '../../agent/MessageSender' +import { createOutboundMessage } from '../../agent/helpers' +import { InjectionSymbols } from '../../constants' +import { inject, injectable } from '../../plugins' +import { canHandleMessageType, parseMessageType } from '../../utils/messageType' +import { ConnectionService } from '../connections/services' + +import { DiscloseMessageHandler, QueryMessageHandler } from './handlers' +import { DiscloseMessage } from './messages' +import { DiscoverFeaturesService } from './services' + +@injectable() +export class DiscoverFeaturesApi { + private connectionService: ConnectionService + private messageSender: MessageSender + private discoverFeaturesService: DiscoverFeaturesService + private eventEmitter: EventEmitter + private stop$: Subject + private agentContext: AgentContext + + public constructor( + dispatcher: Dispatcher, + connectionService: ConnectionService, + messageSender: MessageSender, + discoverFeaturesService: DiscoverFeaturesService, + eventEmitter: EventEmitter, + @inject(InjectionSymbols.Stop$) stop$: Subject, + agentContext: AgentContext + ) { + this.connectionService = connectionService + this.messageSender = messageSender + this.discoverFeaturesService = discoverFeaturesService + this.registerHandlers(dispatcher) + this.eventEmitter = eventEmitter + this.stop$ = stop$ + this.agentContext = agentContext + } + + public async isProtocolSupported(connectionId: string, message: { type: ParsedMessageType }) { + const { protocolUri } = message.type + + // Listen for response to our feature query + const replaySubject = new ReplaySubject(1) + this.eventEmitter + .observable(AgentEventTypes.AgentMessageProcessed) + .pipe( + // Stop when the agent shuts down + takeUntil(this.stop$), + filterContextCorrelationId(this.agentContext.contextCorrelationId), + // filter by connection id and query disclose message type + filter( + (e) => + e.payload.connection?.id === connectionId && + canHandleMessageType(DiscloseMessage, parseMessageType(e.payload.message.type)) + ), + // Return whether the protocol is supported + map((e) => { + const message = e.payload.message as DiscloseMessage + return message.protocols.map((p) => p.protocolId).includes(protocolUri) + }), + // TODO: make configurable + // If we don't have an answer in 7 seconds (no response, not supported, etc...) error + timeout(7000), + // We want to return false if an error occurred + catchError(() => of(false)) + ) + .subscribe(replaySubject) + + await this.queryFeatures(connectionId, { + query: protocolUri, + comment: 'Detect if protocol is supported', + }) + + const isProtocolSupported = await firstValueFrom(replaySubject) + return isProtocolSupported + } + + public async queryFeatures(connectionId: string, options: { query: string; comment?: string }) { + const connection = await this.connectionService.getById(this.agentContext, connectionId) + + const queryMessage = await this.discoverFeaturesService.createQuery(options) + + const outbound = createOutboundMessage(connection, queryMessage) + await this.messageSender.sendMessage(this.agentContext, outbound) + } + + private registerHandlers(dispatcher: Dispatcher) { + dispatcher.registerHandler(new DiscloseMessageHandler()) + dispatcher.registerHandler(new QueryMessageHandler(this.discoverFeaturesService)) + } +} diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts index 76b288e846..8490fc88b7 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts @@ -1,112 +1,15 @@ -import type { AgentMessageProcessedEvent } from '../../agent/Events' -import type { DependencyManager } from '../../plugins' -import type { ParsedMessageType } from '../../utils/messageType' +import type { DependencyManager, Module } from '../../plugins' -import { firstValueFrom, of, ReplaySubject, Subject } from 'rxjs' -import { catchError, filter, map, takeUntil, timeout } from 'rxjs/operators' - -import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' -import { EventEmitter } from '../../agent/EventEmitter' -import { filterContextCorrelationId, AgentEventTypes } from '../../agent/Events' -import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' -import { InjectionSymbols } from '../../constants' -import { inject, injectable, module } from '../../plugins' -import { canHandleMessageType, parseMessageType } from '../../utils/messageType' -import { ConnectionService } from '../connections/services' - -import { DiscloseMessageHandler, QueryMessageHandler } from './handlers' -import { DiscloseMessage } from './messages' +import { DiscoverFeaturesApi } from './DiscoverFeaturesApi' import { DiscoverFeaturesService } from './services' -@module() -@injectable() -export class DiscoverFeaturesModule { - private connectionService: ConnectionService - private messageSender: MessageSender - private discoverFeaturesService: DiscoverFeaturesService - private eventEmitter: EventEmitter - private stop$: Subject - private agentContext: AgentContext - - public constructor( - dispatcher: Dispatcher, - connectionService: ConnectionService, - messageSender: MessageSender, - discoverFeaturesService: DiscoverFeaturesService, - eventEmitter: EventEmitter, - @inject(InjectionSymbols.Stop$) stop$: Subject, - agentContext: AgentContext - ) { - this.connectionService = connectionService - this.messageSender = messageSender - this.discoverFeaturesService = discoverFeaturesService - this.registerHandlers(dispatcher) - this.eventEmitter = eventEmitter - this.stop$ = stop$ - this.agentContext = agentContext - } - - public async isProtocolSupported(connectionId: string, message: { type: ParsedMessageType }) { - const { protocolUri } = message.type - - // Listen for response to our feature query - const replaySubject = new ReplaySubject(1) - this.eventEmitter - .observable(AgentEventTypes.AgentMessageProcessed) - .pipe( - // Stop when the agent shuts down - takeUntil(this.stop$), - filterContextCorrelationId(this.agentContext.contextCorrelationId), - // filter by connection id and query disclose message type - filter( - (e) => - e.payload.connection?.id === connectionId && - canHandleMessageType(DiscloseMessage, parseMessageType(e.payload.message.type)) - ), - // Return whether the protocol is supported - map((e) => { - const message = e.payload.message as DiscloseMessage - return message.protocols.map((p) => p.protocolId).includes(protocolUri) - }), - // TODO: make configurable - // If we don't have an answer in 7 seconds (no response, not supported, etc...) error - timeout(7000), - // We want to return false if an error occurred - catchError(() => of(false)) - ) - .subscribe(replaySubject) - - await this.queryFeatures(connectionId, { - query: protocolUri, - comment: 'Detect if protocol is supported', - }) - - const isProtocolSupported = await firstValueFrom(replaySubject) - return isProtocolSupported - } - - public async queryFeatures(connectionId: string, options: { query: string; comment?: string }) { - const connection = await this.connectionService.getById(this.agentContext, connectionId) - - const queryMessage = await this.discoverFeaturesService.createQuery(options) - - const outbound = createOutboundMessage(connection, queryMessage) - await this.messageSender.sendMessage(this.agentContext, outbound) - } - - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new DiscloseMessageHandler()) - dispatcher.registerHandler(new QueryMessageHandler(this.discoverFeaturesService)) - } - +export class DiscoverFeaturesModule implements Module { /** * Registers the dependencies of the discover features module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(DiscoverFeaturesModule) + dependencyManager.registerContextScoped(DiscoverFeaturesApi) // Services dependencyManager.registerSingleton(DiscoverFeaturesService) diff --git a/packages/core/src/modules/discover-features/__tests__/DiscoverFeaturesModule.test.ts b/packages/core/src/modules/discover-features/__tests__/DiscoverFeaturesModule.test.ts new file mode 100644 index 0000000000..c47b85bb36 --- /dev/null +++ b/packages/core/src/modules/discover-features/__tests__/DiscoverFeaturesModule.test.ts @@ -0,0 +1,21 @@ +import { DependencyManager } from '../../../plugins/DependencyManager' +import { DiscoverFeaturesApi } from '../DiscoverFeaturesApi' +import { DiscoverFeaturesModule } from '../DiscoverFeaturesModule' +import { DiscoverFeaturesService } from '../services' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('DiscoverFeaturesModule', () => { + test('registers dependencies on the dependency manager', () => { + new DiscoverFeaturesModule().register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(DiscoverFeaturesApi) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DiscoverFeaturesService) + }) +}) diff --git a/packages/core/src/modules/discover-features/index.ts b/packages/core/src/modules/discover-features/index.ts index b4bbee6b99..f2ab347e4d 100644 --- a/packages/core/src/modules/discover-features/index.ts +++ b/packages/core/src/modules/discover-features/index.ts @@ -1,4 +1,5 @@ -export * from './DiscoverFeaturesModule' +export * from './DiscoverFeaturesApi' export * from './handlers' export * from './messages' export * from './services' +export * from './DiscoverFeaturesModule' diff --git a/packages/core/src/modules/generic-records/GenericRecordsApi.ts b/packages/core/src/modules/generic-records/GenericRecordsApi.ts new file mode 100644 index 0000000000..feae3a9758 --- /dev/null +++ b/packages/core/src/modules/generic-records/GenericRecordsApi.ts @@ -0,0 +1,89 @@ +import type { GenericRecord, GenericRecordTags, SaveGenericRecordOption } from './repository/GenericRecord' + +import { AgentContext } from '../../agent' +import { InjectionSymbols } from '../../constants' +import { Logger } from '../../logger' +import { inject, injectable } from '../../plugins' + +import { GenericRecordService } from './services/GenericRecordService' + +export type ContentType = { + content: string +} + +@injectable() +export class GenericRecordsApi { + private genericRecordsService: GenericRecordService + private logger: Logger + private agentContext: AgentContext + + public constructor( + genericRecordsService: GenericRecordService, + @inject(InjectionSymbols.Logger) logger: Logger, + agentContext: AgentContext + ) { + this.genericRecordsService = genericRecordsService + this.logger = logger + this.agentContext = agentContext + } + + public async save({ content, tags, id }: SaveGenericRecordOption) { + try { + const record = await this.genericRecordsService.save(this.agentContext, { + id, + content: content, + tags: tags, + }) + return record + } catch (error) { + this.logger.error('Error while saving generic-record', { + error, + content, + errorMessage: error instanceof Error ? error.message : error, + }) + throw error + } + } + + public async delete(record: GenericRecord): Promise { + try { + await this.genericRecordsService.delete(this.agentContext, record) + } catch (error) { + this.logger.error('Error while saving generic-record', { + error, + content: record.content, + errorMessage: error instanceof Error ? error.message : error, + }) + throw error + } + } + + public async deleteById(id: string): Promise { + await this.genericRecordsService.deleteById(this.agentContext, id) + } + + public async update(record: GenericRecord): Promise { + try { + await this.genericRecordsService.update(this.agentContext, record) + } catch (error) { + this.logger.error('Error while update generic-record', { + error, + content: record.content, + errorMessage: error instanceof Error ? error.message : error, + }) + throw error + } + } + + public async findById(id: string) { + return this.genericRecordsService.findById(this.agentContext, id) + } + + public async findAllByQuery(query: Partial): Promise { + return this.genericRecordsService.findAllByQuery(this.agentContext, query) + } + + public async getAll(): Promise { + return this.genericRecordsService.getAll(this.agentContext) + } +} diff --git a/packages/core/src/modules/generic-records/GenericRecordsModule.ts b/packages/core/src/modules/generic-records/GenericRecordsModule.ts index 9ce523adde..0171374197 100644 --- a/packages/core/src/modules/generic-records/GenericRecordsModule.ts +++ b/packages/core/src/modules/generic-records/GenericRecordsModule.ts @@ -1,101 +1,16 @@ -import type { DependencyManager } from '../../plugins' -import type { GenericRecord, GenericRecordTags, SaveGenericRecordOption } from './repository/GenericRecord' - -import { AgentContext } from '../../agent' -import { InjectionSymbols } from '../../constants' -import { Logger } from '../../logger' -import { inject, injectable, module } from '../../plugins' +import type { DependencyManager, Module } from '../../plugins' +import { GenericRecordsApi } from './GenericRecordsApi' import { GenericRecordsRepository } from './repository/GenericRecordsRepository' -import { GenericRecordService } from './service/GenericRecordService' - -export type ContentType = { - content: string -} - -@module() -@injectable() -export class GenericRecordsModule { - private genericRecordsService: GenericRecordService - private logger: Logger - private agentContext: AgentContext - - public constructor( - genericRecordsService: GenericRecordService, - @inject(InjectionSymbols.Logger) logger: Logger, - agentContext: AgentContext - ) { - this.genericRecordsService = genericRecordsService - this.logger = logger - this.agentContext = agentContext - } - - public async save({ content, tags, id }: SaveGenericRecordOption) { - try { - const record = await this.genericRecordsService.save(this.agentContext, { - id, - content, - tags, - }) - return record - } catch (error) { - this.logger.error('Error while saving generic-record', { - error, - content, - errorMessage: error instanceof Error ? error.message : error, - }) - throw error - } - } - - public async delete(record: GenericRecord): Promise { - try { - await this.genericRecordsService.delete(this.agentContext, record) - } catch (error) { - this.logger.error('Error while saving generic-record', { - error, - content: record.content, - errorMessage: error instanceof Error ? error.message : error, - }) - throw error - } - } - - public async deleteById(id: string): Promise { - await this.genericRecordsService.deleteById(this.agentContext, id) - } - - public async update(record: GenericRecord): Promise { - try { - await this.genericRecordsService.update(this.agentContext, record) - } catch (error) { - this.logger.error('Error while update generic-record', { - error, - content: record.content, - errorMessage: error instanceof Error ? error.message : error, - }) - throw error - } - } - - public async findById(id: string) { - return this.genericRecordsService.findById(this.agentContext, id) - } - - public async findAllByQuery(query: Partial): Promise { - return this.genericRecordsService.findAllByQuery(this.agentContext, query) - } - - public async getAll(): Promise { - return this.genericRecordsService.getAll(this.agentContext) - } +import { GenericRecordService } from './services/GenericRecordService' +export class GenericRecordsModule implements Module { /** * Registers the dependencies of the generic records module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(GenericRecordsModule) + dependencyManager.registerContextScoped(GenericRecordsApi) // Services dependencyManager.registerSingleton(GenericRecordService) diff --git a/packages/core/src/modules/generic-records/__tests__/GenericRecordsModule.test.ts b/packages/core/src/modules/generic-records/__tests__/GenericRecordsModule.test.ts new file mode 100644 index 0000000000..498c7f6fc2 --- /dev/null +++ b/packages/core/src/modules/generic-records/__tests__/GenericRecordsModule.test.ts @@ -0,0 +1,23 @@ +import { DependencyManager } from '../../../plugins/DependencyManager' +import { GenericRecordsApi } from '../GenericRecordsApi' +import { GenericRecordsModule } from '../GenericRecordsModule' +import { GenericRecordsRepository } from '../repository/GenericRecordsRepository' +import { GenericRecordService } from '../services/GenericRecordService' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('GenericRecordsModule', () => { + test('registers dependencies on the dependency manager', () => { + new GenericRecordsModule().register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(GenericRecordsApi) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(2) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(GenericRecordService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(GenericRecordsRepository) + }) +}) diff --git a/packages/core/src/modules/generic-records/index.ts b/packages/core/src/modules/generic-records/index.ts new file mode 100644 index 0000000000..48d68d003f --- /dev/null +++ b/packages/core/src/modules/generic-records/index.ts @@ -0,0 +1,2 @@ +export * from './GenericRecordsApi' +export * from './GenericRecordsModule' diff --git a/packages/core/src/modules/generic-records/service/GenericRecordService.ts b/packages/core/src/modules/generic-records/services/GenericRecordService.ts similarity index 100% rename from packages/core/src/modules/generic-records/service/GenericRecordService.ts rename to packages/core/src/modules/generic-records/services/GenericRecordService.ts diff --git a/packages/core/src/modules/indy/module.ts b/packages/core/src/modules/indy/IndyModule.ts similarity index 74% rename from packages/core/src/modules/indy/module.ts rename to packages/core/src/modules/indy/IndyModule.ts index f18441cac9..563a853874 100644 --- a/packages/core/src/modules/indy/module.ts +++ b/packages/core/src/modules/indy/IndyModule.ts @@ -1,15 +1,12 @@ -import type { DependencyManager } from '../../plugins' - -import { module } from '../../plugins' +import type { DependencyManager, Module } from '../../plugins' import { IndyRevocationService, IndyUtilitiesService } from './services' import { IndyHolderService } from './services/IndyHolderService' import { IndyIssuerService } from './services/IndyIssuerService' import { IndyVerifierService } from './services/IndyVerifierService' -@module() -export class IndyModule { - public static register(dependencyManager: DependencyManager) { +export class IndyModule implements Module { + public register(dependencyManager: DependencyManager) { dependencyManager.registerSingleton(IndyIssuerService) dependencyManager.registerSingleton(IndyHolderService) dependencyManager.registerSingleton(IndyVerifierService) diff --git a/packages/core/src/modules/indy/__tests__/IndyModule.test.ts b/packages/core/src/modules/indy/__tests__/IndyModule.test.ts new file mode 100644 index 0000000000..edad08f2d6 --- /dev/null +++ b/packages/core/src/modules/indy/__tests__/IndyModule.test.ts @@ -0,0 +1,27 @@ +import { DependencyManager } from '../../../plugins/DependencyManager' +import { IndyModule } from '../IndyModule' +import { + IndyHolderService, + IndyIssuerService, + IndyVerifierService, + IndyRevocationService, + IndyUtilitiesService, +} from '../services' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('IndyModule', () => { + test('registers dependencies on the dependency manager', () => { + new IndyModule().register(dependencyManager) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(5) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyHolderService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyIssuerService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyRevocationService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyVerifierService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyUtilitiesService) + }) +}) diff --git a/packages/core/src/modules/indy/index.ts b/packages/core/src/modules/indy/index.ts index ef147a3621..5b289c3de8 100644 --- a/packages/core/src/modules/indy/index.ts +++ b/packages/core/src/modules/indy/index.ts @@ -1 +1,2 @@ export * from './services' +export * from './IndyModule' diff --git a/packages/core/src/modules/ledger/LedgerApi.ts b/packages/core/src/modules/ledger/LedgerApi.ts new file mode 100644 index 0000000000..07a535ff47 --- /dev/null +++ b/packages/core/src/modules/ledger/LedgerApi.ts @@ -0,0 +1,166 @@ +import type { IndyPoolConfig } from './IndyPool' +import type { SchemaTemplate, CredentialDefinitionTemplate } from './services' +import type { CredDef, NymRole, Schema } from 'indy-sdk' + +import { AgentContext } from '../../agent' +import { AriesFrameworkError } from '../../error' +import { IndySdkError } from '../../error/IndySdkError' +import { injectable } from '../../plugins' +import { isIndyError } from '../../utils/indyError' +import { AnonCredsCredentialDefinitionRepository } from '../indy/repository/AnonCredsCredentialDefinitionRepository' +import { AnonCredsSchemaRepository } from '../indy/repository/AnonCredsSchemaRepository' + +import { LedgerModuleConfig } from './LedgerModuleConfig' +import { generateCredentialDefinitionId, generateSchemaId } from './ledgerUtil' +import { IndyLedgerService } from './services' + +@injectable() +export class LedgerApi { + public config: LedgerModuleConfig + + private ledgerService: IndyLedgerService + private agentContext: AgentContext + private anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository + private anonCredsSchemaRepository: AnonCredsSchemaRepository + + public constructor( + ledgerService: IndyLedgerService, + agentContext: AgentContext, + anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository, + anonCredsSchemaRepository: AnonCredsSchemaRepository, + config: LedgerModuleConfig + ) { + this.ledgerService = ledgerService + this.agentContext = agentContext + this.anonCredsCredentialDefinitionRepository = anonCredsCredentialDefinitionRepository + this.anonCredsSchemaRepository = anonCredsSchemaRepository + this.config = config + } + + public setPools(poolConfigs: IndyPoolConfig[]) { + return this.ledgerService.setPools(poolConfigs) + } + + /** + * Connect to all the ledger pools + */ + public async connectToPools() { + await this.ledgerService.connectToPools() + } + + public async registerPublicDid(did: string, verkey: string, alias: string, role?: NymRole) { + const myPublicDid = this.agentContext.wallet.publicDid?.did + + if (!myPublicDid) { + throw new AriesFrameworkError('Agent has no public DID.') + } + + return this.ledgerService.registerPublicDid(this.agentContext, myPublicDid, did, verkey, alias, role) + } + + public async getPublicDid(did: string) { + return this.ledgerService.getPublicDid(this.agentContext, did) + } + + public async getSchema(id: string) { + return this.ledgerService.getSchema(this.agentContext, id) + } + + public async registerSchema(schema: SchemaTemplate): Promise { + const did = this.agentContext.wallet.publicDid?.did + + if (!did) { + throw new AriesFrameworkError('Agent has no public DID.') + } + + const schemaId = generateSchemaId(did, schema.name, schema.version) + + // Try find the schema in the wallet + const schemaRecord = await this.anonCredsSchemaRepository.findBySchemaId(this.agentContext, schemaId) + // Schema in wallet + if (schemaRecord) return schemaRecord.schema + + const schemaFromLedger = await this.findBySchemaIdOnLedger(schemaId) + if (schemaFromLedger) return schemaFromLedger + return this.ledgerService.registerSchema(this.agentContext, did, schema) + } + + private async findBySchemaIdOnLedger(schemaId: string) { + try { + return await this.ledgerService.getSchema(this.agentContext, schemaId) + } catch (e) { + if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null + + throw e + } + } + + private async findByCredentialDefinitionIdOnLedger(credentialDefinitionId: string): Promise { + try { + return await this.ledgerService.getCredentialDefinition(this.agentContext, credentialDefinitionId) + } catch (e) { + if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null + + throw e + } + } + + public async registerCredentialDefinition( + credentialDefinitionTemplate: Omit + ) { + const did = this.agentContext.wallet.publicDid?.did + + if (!did) { + throw new AriesFrameworkError('Agent has no public DID.') + } + + // Construct credential definition ID + const credentialDefinitionId = generateCredentialDefinitionId( + did, + credentialDefinitionTemplate.schema.seqNo, + credentialDefinitionTemplate.tag + ) + + // Check if the credential exists in wallet. If so, return it + const credentialDefinitionRecord = await this.anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId( + this.agentContext, + credentialDefinitionId + ) + if (credentialDefinitionRecord) return credentialDefinitionRecord.credentialDefinition + + // Check for the credential on the ledger. + const credentialDefinitionOnLedger = await this.findByCredentialDefinitionIdOnLedger(credentialDefinitionId) + if (credentialDefinitionOnLedger) { + throw new AriesFrameworkError( + `No credential definition record found and credential definition ${credentialDefinitionId} already exists on the ledger.` + ) + } + + // Register the credential + return await this.ledgerService.registerCredentialDefinition(this.agentContext, did, { + ...credentialDefinitionTemplate, + signatureType: 'CL', + }) + } + + public async getCredentialDefinition(id: string) { + return this.ledgerService.getCredentialDefinition(this.agentContext, id) + } + + public async getRevocationRegistryDefinition(revocationRegistryDefinitionId: string) { + return this.ledgerService.getRevocationRegistryDefinition(this.agentContext, revocationRegistryDefinitionId) + } + + public async getRevocationRegistryDelta( + revocationRegistryDefinitionId: string, + fromSeconds = 0, + toSeconds = new Date().getTime() + ) { + return this.ledgerService.getRevocationRegistryDelta( + this.agentContext, + revocationRegistryDefinitionId, + fromSeconds, + toSeconds + ) + } +} diff --git a/packages/core/src/modules/ledger/LedgerModule.ts b/packages/core/src/modules/ledger/LedgerModule.ts index b6fb73e8ca..eb501dac91 100644 --- a/packages/core/src/modules/ledger/LedgerModule.ts +++ b/packages/core/src/modules/ledger/LedgerModule.ts @@ -1,175 +1,36 @@ -import type { DependencyManager } from '../../plugins' -import type { IndyPoolConfig } from './IndyPool' -import type { SchemaTemplate, CredentialDefinitionTemplate } from './services' -import type { CredDef, NymRole, Schema } from 'indy-sdk' +import type { DependencyManager, Module } from '../../plugins' +import type { LedgerModuleConfigOptions } from './LedgerModuleConfig' -import { AgentContext } from '../../agent' -import { AriesFrameworkError } from '../../error' -import { IndySdkError } from '../../error/IndySdkError' -import { injectable, module } from '../../plugins' -import { isIndyError } from '../../utils/indyError' import { AnonCredsCredentialDefinitionRepository } from '../indy/repository/AnonCredsCredentialDefinitionRepository' import { AnonCredsSchemaRepository } from '../indy/repository/AnonCredsSchemaRepository' -import { generateCredentialDefinitionId, generateSchemaId } from './ledgerUtil' -import { IndyPoolService, IndyLedgerService } from './services' +import { LedgerApi } from './LedgerApi' +import { LedgerModuleConfig } from './LedgerModuleConfig' +import { IndyLedgerService, IndyPoolService } from './services' -@module() -@injectable() -export class LedgerModule { - private ledgerService: IndyLedgerService - private agentContext: AgentContext - private anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository - private anonCredsSchemaRepository: AnonCredsSchemaRepository +export class LedgerModule implements Module { + public readonly config: LedgerModuleConfig - public constructor( - ledgerService: IndyLedgerService, - agentContext: AgentContext, - anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository, - anonCredsSchemaRepository: AnonCredsSchemaRepository - ) { - this.ledgerService = ledgerService - this.agentContext = agentContext - this.anonCredsCredentialDefinitionRepository = anonCredsCredentialDefinitionRepository - this.anonCredsSchemaRepository = anonCredsSchemaRepository - } - - public setPools(poolConfigs: IndyPoolConfig[]) { - return this.ledgerService.setPools(poolConfigs) - } - - /** - * Connect to all the ledger pools - */ - public async connectToPools() { - await this.ledgerService.connectToPools() - } - - public async registerPublicDid(did: string, verkey: string, alias: string, role?: NymRole) { - const myPublicDid = this.agentContext.wallet.publicDid?.did - - if (!myPublicDid) { - throw new AriesFrameworkError('Agent has no public DID.') - } - - return this.ledgerService.registerPublicDid(this.agentContext, myPublicDid, did, verkey, alias, role) - } - - public async getPublicDid(did: string) { - return this.ledgerService.getPublicDid(this.agentContext, did) - } - - public async getSchema(id: string) { - return this.ledgerService.getSchema(this.agentContext, id) - } - - public async registerSchema(schema: SchemaTemplate): Promise { - const did = this.agentContext.wallet.publicDid?.did - - if (!did) { - throw new AriesFrameworkError('Agent has no public DID.') - } - - const schemaId = generateSchemaId(did, schema.name, schema.version) - - // Try find the schema in the wallet - const schemaRecord = await this.anonCredsSchemaRepository.findBySchemaId(this.agentContext, schemaId) - // Schema in wallet - if (schemaRecord) return schemaRecord.schema - - const schemaFromLedger = await this.findBySchemaIdOnLedger(schemaId) - if (schemaFromLedger) return schemaFromLedger - return this.ledgerService.registerSchema(this.agentContext, did, schema) - } - - private async findBySchemaIdOnLedger(schemaId: string) { - try { - return await this.ledgerService.getSchema(this.agentContext, schemaId) - } catch (e) { - if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null - - throw e - } - } - - private async findByCredentialDefinitionIdOnLedger(credentialDefinitionId: string): Promise { - try { - return await this.ledgerService.getCredentialDefinition(this.agentContext, credentialDefinitionId) - } catch (e) { - if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null - - throw e - } - } - - public async registerCredentialDefinition( - credentialDefinitionTemplate: Omit - ) { - const did = this.agentContext.wallet.publicDid?.did - - if (!did) { - throw new AriesFrameworkError('Agent has no public DID.') - } - - // Construct credential definition ID - const credentialDefinitionId = generateCredentialDefinitionId( - did, - credentialDefinitionTemplate.schema.seqNo, - credentialDefinitionTemplate.tag - ) - - // Check if the credential exists in wallet. If so, return it - const credentialDefinitionRecord = await this.anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId( - this.agentContext, - credentialDefinitionId - ) - if (credentialDefinitionRecord) return credentialDefinitionRecord.credentialDefinition - - // Check for the credential on the ledger. - const credentialDefinitionOnLedger = await this.findByCredentialDefinitionIdOnLedger(credentialDefinitionId) - if (credentialDefinitionOnLedger) { - throw new AriesFrameworkError( - `No credential definition record found and credential definition ${credentialDefinitionId} already exists on the ledger.` - ) - } - - // Register the credential - return await this.ledgerService.registerCredentialDefinition(this.agentContext, did, { - ...credentialDefinitionTemplate, - signatureType: 'CL', - }) - } - - public async getCredentialDefinition(id: string) { - return this.ledgerService.getCredentialDefinition(this.agentContext, id) - } - - public async getRevocationRegistryDefinition(revocationRegistryDefinitionId: string) { - return this.ledgerService.getRevocationRegistryDefinition(this.agentContext, revocationRegistryDefinitionId) - } - - public async getRevocationRegistryDelta( - revocationRegistryDefinitionId: string, - fromSeconds = 0, - toSeconds = new Date().getTime() - ) { - return this.ledgerService.getRevocationRegistryDelta( - this.agentContext, - revocationRegistryDefinitionId, - fromSeconds, - toSeconds - ) + public constructor(config?: LedgerModuleConfigOptions) { + this.config = new LedgerModuleConfig(config) } /** * Registers the dependencies of the ledger module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(LedgerModule) + dependencyManager.registerContextScoped(LedgerApi) + + // Config + dependencyManager.registerInstance(LedgerModuleConfig, this.config) // Services dependencyManager.registerSingleton(IndyLedgerService) dependencyManager.registerSingleton(IndyPoolService) + + // Repositories + dependencyManager.registerSingleton(AnonCredsCredentialDefinitionRepository) + dependencyManager.registerSingleton(AnonCredsSchemaRepository) } } diff --git a/packages/core/src/modules/ledger/LedgerModuleConfig.ts b/packages/core/src/modules/ledger/LedgerModuleConfig.ts new file mode 100644 index 0000000000..12c9d99fc0 --- /dev/null +++ b/packages/core/src/modules/ledger/LedgerModuleConfig.ts @@ -0,0 +1,43 @@ +import type { IndyPoolConfig } from './IndyPool' + +/** + * LedgerModuleConfigOptions defines the interface for the options of the RecipientModuleConfig class. + * This can contain optional parameters that have default values in the config class itself. + */ +export interface LedgerModuleConfigOptions { + /** + * Whether to automatically connect to all {@link LedgerModuleConfigOptions.indyLedgers} on startup. + * This will be done asynchronously, so the initialization of the agent won't be impacted. However, + * this does mean there may be unneeded connections to the ledger. + * + * @default true + */ + connectToIndyLedgersOnStartup?: boolean + + /** + * Array of configurations of indy ledgers to connect to. Each item in the list must include either the `genesisPath` or `genesisTransactions` property. + * + * The first ledger in the list will be used for writing transactions to the ledger. + * + * @default [] + */ + indyLedgers?: IndyPoolConfig[] +} + +export class LedgerModuleConfig { + private options: LedgerModuleConfigOptions + + public constructor(options?: LedgerModuleConfigOptions) { + this.options = options ?? {} + } + + /** See {@link LedgerModuleConfigOptions.connectToIndyLedgersOnStartup} */ + public get connectToIndyLedgersOnStartup() { + return this.options.connectToIndyLedgersOnStartup ?? true + } + + /** See {@link LedgerModuleConfigOptions.indyLedgers} */ + public get indyLedgers() { + return this.options.indyLedgers ?? [] + } +} diff --git a/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts b/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts new file mode 100644 index 0000000000..1df7a5c120 --- /dev/null +++ b/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts @@ -0,0 +1,368 @@ +import type { AgentContext } from '../../../agent/context/AgentContext' +import type { IndyPoolConfig } from '../IndyPool' +import type { CredentialDefinitionTemplate } from '../services/IndyLedgerService' +import type * as Indy from 'indy-sdk' + +import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../tests/helpers' +import { SigningProviderRegistry } from '../../../crypto/signing-provider' +import { AriesFrameworkError } from '../../../error/AriesFrameworkError' +import { IndyWallet } from '../../../wallet/IndyWallet' +import { AnonCredsCredentialDefinitionRecord } from '../../indy/repository/AnonCredsCredentialDefinitionRecord' +import { AnonCredsCredentialDefinitionRepository } from '../../indy/repository/AnonCredsCredentialDefinitionRepository' +import { AnonCredsSchemaRecord } from '../../indy/repository/AnonCredsSchemaRecord' +import { AnonCredsSchemaRepository } from '../../indy/repository/AnonCredsSchemaRepository' +import { LedgerApi } from '../LedgerApi' +import { LedgerModuleConfig } from '../LedgerModuleConfig' +import { generateCredentialDefinitionId, generateSchemaId } from '../ledgerUtil' +import { IndyLedgerService } from '../services/IndyLedgerService' + +jest.mock('../services/IndyLedgerService') +const IndyLedgerServiceMock = IndyLedgerService as jest.Mock + +jest.mock('../../indy/repository/AnonCredsCredentialDefinitionRepository') +const AnonCredsCredentialDefinitionRepositoryMock = + AnonCredsCredentialDefinitionRepository as jest.Mock +jest.mock('../../indy/repository/AnonCredsSchemaRepository') +const AnonCredsSchemaRepositoryMock = AnonCredsSchemaRepository as jest.Mock + +const did = 'Y5bj4SjCiTM9PgeheKAiXx' + +const schemaId = 'abcd' + +const schema: Indy.Schema = { + id: schemaId, + attrNames: ['hello', 'world'], + name: 'awesomeSchema', + version: '1', + ver: '1', + seqNo: 99, +} + +const credentialDefinition = { + schema: 'abcde', + tag: 'someTag', + signatureType: 'CL', + supportRevocation: true, +} + +const credDef: Indy.CredDef = { + id: 'abcde', + schemaId: schema.id, + type: 'CL', + tag: 'someTag', + value: { + primary: credentialDefinition as Record, + revocation: true, + }, + ver: '1', +} + +const credentialDefinitionTemplate: Omit = { + schema: schema, + tag: 'someTag', + supportRevocation: true, +} + +const revocRegDef: Indy.RevocRegDef = { + id: 'abcde', + revocDefType: 'CL_ACCUM', + tag: 'someTag', + credDefId: 'abcde', + value: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + maxCredNum: 3, + tailsHash: 'abcde', + tailsLocation: 'xyz', + publicKeys: ['abcde', 'fghijk'], + }, + ver: 'abcde', +} + +const schemaIdGenerated = generateSchemaId(did, schema.name, schema.version) + +const credentialDefinitionId = generateCredentialDefinitionId( + did, + credentialDefinitionTemplate.schema.seqNo, + credentialDefinitionTemplate.tag +) + +const pools: IndyPoolConfig[] = [ + { + id: 'sovrinMain', + isProduction: true, + genesisTransactions: 'xxx', + transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + }, +] + +describe('LedgerApi', () => { + let wallet: IndyWallet + let ledgerService: IndyLedgerService + let anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository + let anonCredsSchemaRepository: AnonCredsSchemaRepository + let ledgerApi: LedgerApi + let agentContext: AgentContext + + const contextCorrelationId = 'mock' + const agentConfig = getAgentConfig('LedgerApiTest', { + indyLedgers: pools, + }) + + beforeEach(async () => { + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await wallet.createAndOpen(agentConfig.walletConfig!) + }) + + afterEach(async () => { + await wallet.delete() + }) + + beforeEach(async () => { + ledgerService = new IndyLedgerServiceMock() + + agentContext = getAgentContext({ + wallet, + agentConfig, + contextCorrelationId, + }) + + anonCredsCredentialDefinitionRepository = new AnonCredsCredentialDefinitionRepositoryMock() + anonCredsSchemaRepository = new AnonCredsSchemaRepositoryMock() + + ledgerApi = new LedgerApi( + ledgerService, + agentContext, + anonCredsCredentialDefinitionRepository, + anonCredsSchemaRepository, + new LedgerModuleConfig() + ) + }) + + describe('LedgerApi', () => { + // Connect to pools + describe('connectToPools', () => { + it('should connect to all pools', async () => { + mockFunction(ledgerService.connectToPools).mockResolvedValue([1, 2, 4]) + await expect(ledgerApi.connectToPools()).resolves.toBeUndefined() + expect(ledgerService.connectToPools).toHaveBeenCalled() + }) + }) + + // Register public did + describe('registerPublicDid', () => { + it('should register a public DID', async () => { + mockFunction(ledgerService.registerPublicDid).mockResolvedValueOnce(did) + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + await expect(ledgerApi.registerPublicDid(did, 'abcde', 'someAlias')).resolves.toEqual(did) + expect(ledgerService.registerPublicDid).toHaveBeenCalledWith( + agentContext, + did, + did, + 'abcde', + 'someAlias', + undefined + ) + }) + + it('should throw an error if the DID cannot be registered because there is no public did', async () => { + const did = 'Y5bj4SjCiTM9PgeheKAiXx' + mockProperty(wallet, 'publicDid', undefined) + await expect(ledgerApi.registerPublicDid(did, 'abcde', 'someAlias')).rejects.toThrowError(AriesFrameworkError) + }) + }) + + // Get public DID + describe('getPublicDid', () => { + it('should return the public DID if there is one', async () => { + const nymResponse: Indy.GetNymResponse = { did: 'Y5bj4SjCiTM9PgeheKAiXx', verkey: 'abcde', role: 'STEWARD' } + mockProperty(wallet, 'publicDid', { did: nymResponse.did, verkey: nymResponse.verkey }) + mockFunction(ledgerService.getPublicDid).mockResolvedValueOnce(nymResponse) + await expect(ledgerApi.getPublicDid(nymResponse.did)).resolves.toEqual(nymResponse) + expect(ledgerService.getPublicDid).toHaveBeenCalledWith(agentContext, nymResponse.did) + }) + }) + + // Get schema + describe('getSchema', () => { + it('should return the schema by id if there is one', async () => { + mockFunction(ledgerService.getSchema).mockResolvedValueOnce(schema) + await expect(ledgerApi.getSchema(schemaId)).resolves.toEqual(schema) + expect(ledgerService.getSchema).toHaveBeenCalledWith(agentContext, schemaId) + }) + + it('should throw an error if no schema for the id exists', async () => { + mockFunction(ledgerService.getSchema).mockRejectedValueOnce( + new AriesFrameworkError('Error retrieving schema abcd from ledger 1') + ) + await expect(ledgerApi.getSchema(schemaId)).rejects.toThrowError(AriesFrameworkError) + expect(ledgerService.getSchema).toHaveBeenCalledWith(agentContext, schemaId) + }) + }) + + describe('registerSchema', () => { + it('should throw an error if there is no public DID', async () => { + mockProperty(wallet, 'publicDid', undefined) + await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).rejects.toThrowError( + AriesFrameworkError + ) + }) + + it('should return the schema from anonCreds when it already exists', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(anonCredsSchemaRepository.findBySchemaId).mockResolvedValueOnce( + new AnonCredsSchemaRecord({ schema: schema }) + ) + await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual(schema) + expect(anonCredsSchemaRepository.findBySchemaId).toHaveBeenCalledWith(agentContext, schemaIdGenerated) + }) + + it('should return the schema from the ledger when it already exists', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(LedgerApi.prototype as any, 'findBySchemaIdOnLedger') + .mockResolvedValueOnce(new AnonCredsSchemaRecord({ schema: schema })) + await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toHaveProperty( + 'schema', + schema + ) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(jest.spyOn(LedgerApi.prototype as any, 'findBySchemaIdOnLedger')).toHaveBeenCalledWith(schemaIdGenerated) + }) + + it('should return the schema after registering it', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(ledgerService.registerSchema).mockResolvedValueOnce(schema) + await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual(schema) + expect(ledgerService.registerSchema).toHaveBeenCalledWith(agentContext, did, { + ...schema, + attributes: ['hello', 'world'], + }) + }) + }) + + describe('registerCredentialDefinition', () => { + it('should throw an error if there si no public DID', async () => { + mockProperty(wallet, 'publicDid', undefined) + await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( + AriesFrameworkError + ) + }) + + it('should return the credential definition from the wallet if it already exists', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + const anonCredsCredentialDefinitionRecord: AnonCredsCredentialDefinitionRecord = + new AnonCredsCredentialDefinitionRecord({ + credentialDefinition: credDef, + }) + mockFunction(anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId).mockResolvedValueOnce( + anonCredsCredentialDefinitionRecord + ) + await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toHaveProperty( + 'value.primary', + credentialDefinition + ) + expect(anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId).toHaveBeenCalledWith( + agentContext, + credentialDefinitionId + ) + }) + + it('should throw an exception if the definition already exists on the ledger', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(LedgerApi.prototype as any, 'findByCredentialDefinitionIdOnLedger') + .mockResolvedValueOnce({ credentialDefinition: credentialDefinition }) + await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( + AriesFrameworkError + ) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(jest.spyOn(LedgerApi.prototype as any, 'findByCredentialDefinitionIdOnLedger')).toHaveBeenCalledWith( + credentialDefinitionId + ) + }) + + it('should register the credential successfully if it is neither in the wallet and neither on the ledger', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(ledgerService.registerCredentialDefinition).mockResolvedValueOnce(credDef) + await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toEqual(credDef) + expect(ledgerService.registerCredentialDefinition).toHaveBeenCalledWith(agentContext, did, { + ...credentialDefinitionTemplate, + signatureType: 'CL', + }) + }) + }) + + describe('getCredentialDefinition', () => { + it('should return the credential definition given the id', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(ledgerService.getCredentialDefinition).mockResolvedValue(credDef) + await expect(ledgerApi.getCredentialDefinition(credDef.id)).resolves.toEqual(credDef) + expect(ledgerService.getCredentialDefinition).toHaveBeenCalledWith(agentContext, credDef.id) + }) + + it('should throw an error if there is no credential definition for the given id', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(ledgerService.getCredentialDefinition).mockRejectedValueOnce(new AriesFrameworkError('')) + await expect(ledgerApi.getCredentialDefinition(credDef.id)).rejects.toThrowError(AriesFrameworkError) + expect(ledgerService.getCredentialDefinition).toHaveBeenCalledWith(agentContext, credDef.id) + }) + }) + + describe('getRevocationRegistryDefinition', () => { + it('should return the ParseRevocationRegistryDefinitionTemplate for a valid revocationRegistryDefinitionId', async () => { + const parseRevocationRegistryDefinitionTemplate = { + revocationRegistryDefinition: revocRegDef, + revocationRegistryDefinitionTxnTime: 12345678, + } + mockFunction(ledgerService.getRevocationRegistryDefinition).mockResolvedValue( + parseRevocationRegistryDefinitionTemplate + ) + await expect(ledgerApi.getRevocationRegistryDefinition(revocRegDef.id)).resolves.toBe( + parseRevocationRegistryDefinitionTemplate + ) + expect(ledgerService.getRevocationRegistryDefinition).toHaveBeenLastCalledWith(agentContext, revocRegDef.id) + }) + + it('should throw an error if the ParseRevocationRegistryDefinitionTemplate does not exists', async () => { + mockFunction(ledgerService.getRevocationRegistryDefinition).mockRejectedValueOnce(new AriesFrameworkError('')) + await expect(ledgerApi.getRevocationRegistryDefinition('abcde')).rejects.toThrowError(AriesFrameworkError) + expect(ledgerService.getRevocationRegistryDefinition).toHaveBeenCalledWith(agentContext, revocRegDef.id) + }) + }) + + describe('getRevocationRegistryDelta', () => { + it('should return the ParseRevocationRegistryDeltaTemplate', async () => { + const revocRegDelta = { + value: { + prevAccum: 'prev', + accum: 'accum', + issued: [1, 2, 3], + revoked: [4, 5, 6], + }, + ver: 'ver', + } + const parseRevocationRegistryDeltaTemplate = { + revocationRegistryDelta: revocRegDelta, + deltaTimestamp: 12345678, + } + + mockFunction(ledgerService.getRevocationRegistryDelta).mockResolvedValueOnce( + parseRevocationRegistryDeltaTemplate + ) + await expect(ledgerApi.getRevocationRegistryDelta('12345')).resolves.toEqual( + parseRevocationRegistryDeltaTemplate + ) + expect(ledgerService.getRevocationRegistryDelta).toHaveBeenCalledTimes(1) + }) + + it('should throw an error if the delta cannot be obtained', async () => { + mockFunction(ledgerService.getRevocationRegistryDelta).mockRejectedValueOnce(new AriesFrameworkError('')) + await expect(ledgerApi.getRevocationRegistryDelta('abcde1234')).rejects.toThrowError(AriesFrameworkError) + expect(ledgerService.getRevocationRegistryDelta).toHaveBeenCalledTimes(1) + }) + }) + }) +}) diff --git a/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts b/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts index 9be7b6167a..b258bd5416 100644 --- a/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts +++ b/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts @@ -1,373 +1,26 @@ -import type { AgentContext } from '../../../agent/context/AgentContext' -import type { IndyPoolConfig } from '../IndyPool' -import type { CredentialDefinitionTemplate } from '../services/IndyLedgerService' -import type * as Indy from 'indy-sdk' - -import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../tests/helpers' -import { SigningProviderRegistry } from '../../../crypto/signing-provider' -import { AriesFrameworkError } from '../../../error/AriesFrameworkError' -import { IndyWallet } from '../../../wallet/IndyWallet' -import { AnonCredsCredentialDefinitionRecord } from '../../indy/repository/AnonCredsCredentialDefinitionRecord' +import { DependencyManager } from '../../../plugins/DependencyManager' import { AnonCredsCredentialDefinitionRepository } from '../../indy/repository/AnonCredsCredentialDefinitionRepository' -import { AnonCredsSchemaRecord } from '../../indy/repository/AnonCredsSchemaRecord' import { AnonCredsSchemaRepository } from '../../indy/repository/AnonCredsSchemaRepository' +import { LedgerApi } from '../LedgerApi' import { LedgerModule } from '../LedgerModule' -import { generateCredentialDefinitionId, generateSchemaId } from '../ledgerUtil' -import { IndyLedgerService } from '../services/IndyLedgerService' - -jest.mock('../services/IndyLedgerService') -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock - -jest.mock('../../indy/repository/AnonCredsCredentialDefinitionRepository') -const AnonCredsCredentialDefinitionRepositoryMock = - AnonCredsCredentialDefinitionRepository as jest.Mock -jest.mock('../../indy/repository/AnonCredsSchemaRepository') -const AnonCredsSchemaRepositoryMock = AnonCredsSchemaRepository as jest.Mock - -const did = 'Y5bj4SjCiTM9PgeheKAiXx' - -const schemaId = 'abcd' - -const schema: Indy.Schema = { - id: schemaId, - attrNames: ['hello', 'world'], - name: 'awesomeSchema', - version: '1', - ver: '1', - seqNo: 99, -} - -const credentialDefinition = { - schema: 'abcde', - tag: 'someTag', - signatureType: 'CL', - supportRevocation: true, -} - -const credDef: Indy.CredDef = { - id: 'abcde', - schemaId: schema.id, - type: 'CL', - tag: 'someTag', - value: { - primary: credentialDefinition as Record, - revocation: true, - }, - ver: '1', -} - -const credentialDefinitionTemplate: Omit = { - schema: schema, - tag: 'someTag', - supportRevocation: true, -} - -const revocRegDef: Indy.RevocRegDef = { - id: 'abcde', - revocDefType: 'CL_ACCUM', - tag: 'someTag', - credDefId: 'abcde', - value: { - issuanceType: 'ISSUANCE_BY_DEFAULT', - maxCredNum: 3, - tailsHash: 'abcde', - tailsLocation: 'xyz', - publicKeys: ['abcde', 'fghijk'], - }, - ver: 'abcde', -} - -const schemaIdGenerated = generateSchemaId(did, schema.name, schema.version) +import { IndyLedgerService, IndyPoolService } from '../services' -const credentialDefinitionId = generateCredentialDefinitionId( - did, - credentialDefinitionTemplate.schema.seqNo, - credentialDefinitionTemplate.tag -) +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock -const pools: IndyPoolConfig[] = [ - { - id: 'sovrinMain', - isProduction: true, - genesisTransactions: 'xxx', - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, - }, -] +const dependencyManager = new DependencyManagerMock() describe('LedgerModule', () => { - let wallet: IndyWallet - let ledgerService: IndyLedgerService - let anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository - let anonCredsSchemaRepository: AnonCredsSchemaRepository - let ledgerModule: LedgerModule - let agentContext: AgentContext - - const contextCorrelationId = 'mock' - const agentConfig = getAgentConfig('LedgerModuleTest', { - indyLedgers: pools, - }) - - beforeEach(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(agentConfig.walletConfig!) - }) - - afterEach(async () => { - await wallet.delete() - }) - - beforeEach(async () => { - ledgerService = new IndyLedgerServiceMock() - - agentContext = getAgentContext({ - wallet, - agentConfig, - contextCorrelationId, - }) - - anonCredsCredentialDefinitionRepository = new AnonCredsCredentialDefinitionRepositoryMock() - anonCredsSchemaRepository = new AnonCredsSchemaRepositoryMock() - - ledgerModule = new LedgerModule( - ledgerService, - agentContext, - anonCredsCredentialDefinitionRepository, - anonCredsSchemaRepository - ) - }) - - describe('LedgerModule', () => { - // Connect to pools - describe('connectToPools', () => { - it('should connect to all pools', async () => { - mockFunction(ledgerService.connectToPools).mockResolvedValue([1, 2, 4]) - await expect(ledgerModule.connectToPools()).resolves - expect(ledgerService.connectToPools).toHaveBeenCalled() - }) - }) - - // Register public did - describe('registerPublicDid', () => { - it('should register a public DID', async () => { - mockFunction(ledgerService.registerPublicDid).mockResolvedValueOnce(did) - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - await expect(ledgerModule.registerPublicDid(did, 'abcde', 'someAlias')).resolves.toEqual(did) - expect(ledgerService.registerPublicDid).toHaveBeenCalledWith( - agentContext, - did, - did, - 'abcde', - 'someAlias', - undefined - ) - }) - - it('should throw an error if the DID cannot be registered because there is no public did', async () => { - const did = 'Y5bj4SjCiTM9PgeheKAiXx' - mockProperty(wallet, 'publicDid', undefined) - await expect(ledgerModule.registerPublicDid(did, 'abcde', 'someAlias')).rejects.toThrowError( - AriesFrameworkError - ) - }) - }) - - // Get public DID - describe('getPublicDid', () => { - it('should return the public DID if there is one', async () => { - const nymResponse: Indy.GetNymResponse = { did: 'Y5bj4SjCiTM9PgeheKAiXx', verkey: 'abcde', role: 'STEWARD' } - mockProperty(wallet, 'publicDid', { did: nymResponse.did, verkey: nymResponse.verkey }) - mockFunction(ledgerService.getPublicDid).mockResolvedValueOnce(nymResponse) - await expect(ledgerModule.getPublicDid(nymResponse.did)).resolves.toEqual(nymResponse) - expect(ledgerService.getPublicDid).toHaveBeenCalledWith(agentContext, nymResponse.did) - }) - }) - - // Get schema - describe('getSchema', () => { - it('should return the schema by id if there is one', async () => { - mockFunction(ledgerService.getSchema).mockResolvedValueOnce(schema) - await expect(ledgerModule.getSchema(schemaId)).resolves.toEqual(schema) - expect(ledgerService.getSchema).toHaveBeenCalledWith(agentContext, schemaId) - }) - - it('should throw an error if no schema for the id exists', async () => { - mockFunction(ledgerService.getSchema).mockRejectedValueOnce( - new AriesFrameworkError('Error retrieving schema abcd from ledger 1') - ) - await expect(ledgerModule.getSchema(schemaId)).rejects.toThrowError(AriesFrameworkError) - expect(ledgerService.getSchema).toHaveBeenCalledWith(agentContext, schemaId) - }) - }) - - describe('registerSchema', () => { - it('should throw an error if there is no public DID', async () => { - mockProperty(wallet, 'publicDid', undefined) - await expect(ledgerModule.registerSchema({ ...schema, attributes: ['hello', 'world'] })).rejects.toThrowError( - AriesFrameworkError - ) - }) - - it('should return the schema from anonCreds when it already exists', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(anonCredsSchemaRepository.findBySchemaId).mockResolvedValueOnce( - new AnonCredsSchemaRecord({ schema: schema }) - ) - await expect(ledgerModule.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual( - schema - ) - expect(anonCredsSchemaRepository.findBySchemaId).toHaveBeenCalledWith(agentContext, schemaIdGenerated) - }) - - it('should return the schema from the ledger when it already exists', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - jest - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .spyOn(LedgerModule.prototype as any, 'findBySchemaIdOnLedger') - .mockResolvedValueOnce(new AnonCredsSchemaRecord({ schema: schema })) - await expect( - ledgerModule.registerSchema({ ...schema, attributes: ['hello', 'world'] }) - ).resolves.toHaveProperty('schema', schema) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect(jest.spyOn(LedgerModule.prototype as any, 'findBySchemaIdOnLedger')).toHaveBeenCalledWith( - schemaIdGenerated - ) - }) - - it('should return the schema after registering it', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(ledgerService.registerSchema).mockResolvedValueOnce(schema) - await expect(ledgerModule.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual( - schema - ) - expect(ledgerService.registerSchema).toHaveBeenCalledWith(agentContext, did, { - ...schema, - attributes: ['hello', 'world'], - }) - }) - }) - - describe('registerCredentialDefinition', () => { - it('should throw an error if there si no public DID', async () => { - mockProperty(wallet, 'publicDid', undefined) - await expect(ledgerModule.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( - AriesFrameworkError - ) - }) - - it('should return the credential definition from the wallet if it already exists', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - const anonCredsCredentialDefinitionRecord: AnonCredsCredentialDefinitionRecord = - new AnonCredsCredentialDefinitionRecord({ - credentialDefinition: credDef, - }) - mockFunction(anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId).mockResolvedValueOnce( - anonCredsCredentialDefinitionRecord - ) - await expect(ledgerModule.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toHaveProperty( - 'value.primary', - credentialDefinition - ) - expect(anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId).toHaveBeenCalledWith( - agentContext, - credentialDefinitionId - ) - }) - - it('should throw an exception if the definition already exists on the ledger', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - jest - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .spyOn(LedgerModule.prototype as any, 'findByCredentialDefinitionIdOnLedger') - .mockResolvedValueOnce({ credentialDefinition: credentialDefinition }) - await expect(ledgerModule.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( - AriesFrameworkError - ) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect(jest.spyOn(LedgerModule.prototype as any, 'findByCredentialDefinitionIdOnLedger')).toHaveBeenCalledWith( - credentialDefinitionId - ) - }) - - it('should register the credential successfully if it is neither in the wallet and neither on the ledger', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(ledgerService.registerCredentialDefinition).mockResolvedValueOnce(credDef) - await expect(ledgerModule.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toEqual(credDef) - expect(ledgerService.registerCredentialDefinition).toHaveBeenCalledWith(agentContext, did, { - ...credentialDefinitionTemplate, - signatureType: 'CL', - }) - }) - }) - - describe('getCredentialDefinition', () => { - it('should return the credential definition given the id', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(ledgerService.getCredentialDefinition).mockResolvedValue(credDef) - await expect(ledgerModule.getCredentialDefinition(credDef.id)).resolves.toEqual(credDef) - expect(ledgerService.getCredentialDefinition).toHaveBeenCalledWith(agentContext, credDef.id) - }) - - it('should throw an error if there is no credential definition for the given id', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(ledgerService.getCredentialDefinition).mockRejectedValueOnce(new AriesFrameworkError('')) - await expect(ledgerModule.getCredentialDefinition(credDef.id)).rejects.toThrowError(AriesFrameworkError) - expect(ledgerService.getCredentialDefinition).toHaveBeenCalledWith(agentContext, credDef.id) - }) - }) - - describe('getRevocationRegistryDefinition', () => { - it('should return the ParseRevocationRegistryDefinitionTemplate for a valid revocationRegistryDefinitionId', async () => { - const parseRevocationRegistryDefinitionTemplate = { - revocationRegistryDefinition: revocRegDef, - revocationRegistryDefinitionTxnTime: 12345678, - } - mockFunction(ledgerService.getRevocationRegistryDefinition).mockResolvedValue( - parseRevocationRegistryDefinitionTemplate - ) - await expect(ledgerModule.getRevocationRegistryDefinition(revocRegDef.id)).resolves.toBe( - parseRevocationRegistryDefinitionTemplate - ) - expect(ledgerService.getRevocationRegistryDefinition).toHaveBeenLastCalledWith(agentContext, revocRegDef.id) - }) - - it('should throw an error if the ParseRevocationRegistryDefinitionTemplate does not exists', async () => { - mockFunction(ledgerService.getRevocationRegistryDefinition).mockRejectedValueOnce(new AriesFrameworkError('')) - await expect(ledgerModule.getRevocationRegistryDefinition('abcde')).rejects.toThrowError(AriesFrameworkError) - expect(ledgerService.getRevocationRegistryDefinition).toHaveBeenCalledWith(agentContext, revocRegDef.id) - }) - }) - - describe('getRevocationRegistryDelta', () => { - it('should return the ParseRevocationRegistryDeltaTemplate', async () => { - const revocRegDelta = { - value: { - prevAccum: 'prev', - accum: 'accum', - issued: [1, 2, 3], - revoked: [4, 5, 6], - }, - ver: 'ver', - } - const parseRevocationRegistryDeltaTemplate = { - revocationRegistryDelta: revocRegDelta, - deltaTimestamp: 12345678, - } + test('registers dependencies on the dependency manager', () => { + new LedgerModule().register(dependencyManager) - mockFunction(ledgerService.getRevocationRegistryDelta).mockResolvedValueOnce( - parseRevocationRegistryDeltaTemplate - ) - await expect(ledgerModule.getRevocationRegistryDelta('12345')).resolves.toEqual( - parseRevocationRegistryDeltaTemplate - ) - expect(ledgerService.getRevocationRegistryDelta).toHaveBeenCalledTimes(1) - }) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(LedgerApi) - it('should throw an error if the delta cannot be obtained', async () => { - mockFunction(ledgerService.getRevocationRegistryDelta).mockRejectedValueOnce(new AriesFrameworkError('')) - await expect(ledgerModule.getRevocationRegistryDelta('abcde1234')).rejects.toThrowError(AriesFrameworkError) - expect(ledgerService.getRevocationRegistryDelta).toHaveBeenCalledTimes(1) - }) - }) + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(4) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyLedgerService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyPoolService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsCredentialDefinitionRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsSchemaRepository) }) }) diff --git a/packages/core/src/modules/ledger/index.ts b/packages/core/src/modules/ledger/index.ts index 1168480d3c..fc65f390db 100644 --- a/packages/core/src/modules/ledger/index.ts +++ b/packages/core/src/modules/ledger/index.ts @@ -1,3 +1,4 @@ export * from './services' -export * from './LedgerModule' +export * from './LedgerApi' export * from './IndyPool' +export * from './LedgerModule' diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts new file mode 100644 index 0000000000..a5edd589c0 --- /dev/null +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -0,0 +1,709 @@ +import type { AgentMessage } from '../../agent/AgentMessage' +import type { AgentMessageReceivedEvent } from '../../agent/Events' +import type { Key } from '../../crypto' +import type { Attachment } from '../../decorators/attachment/Attachment' +import type { PlaintextMessage } from '../../types' +import type { ConnectionInvitationMessage, ConnectionRecord, Routing } from '../connections' +import type { HandshakeReusedEvent } from './domain/OutOfBandEvents' + +import { catchError, EmptyError, first, firstValueFrom, map, of, timeout } from 'rxjs' + +import { AgentContext } from '../../agent' +import { Dispatcher } from '../../agent/Dispatcher' +import { EventEmitter } from '../../agent/EventEmitter' +import { filterContextCorrelationId, AgentEventTypes } from '../../agent/Events' +import { MessageSender } from '../../agent/MessageSender' +import { createOutboundMessage } from '../../agent/helpers' +import { InjectionSymbols } from '../../constants' +import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' +import { AriesFrameworkError } from '../../error' +import { Logger } from '../../logger' +import { inject, injectable } from '../../plugins' +import { DidCommMessageRepository, DidCommMessageRole } from '../../storage' +import { JsonEncoder, JsonTransformer } from '../../utils' +import { parseMessageType, supportsIncomingMessageType } from '../../utils/messageType' +import { parseInvitationUrl, parseInvitationShortUrl } from '../../utils/parseInvitation' +import { ConnectionsApi, DidExchangeState, HandshakeProtocol } from '../connections' +import { DidKey } from '../dids' +import { didKeyToVerkey } from '../dids/helpers' +import { outOfBandServiceToNumAlgo2Did } from '../dids/methods/peer/peerDidNumAlgo2' +import { RoutingService } from '../routing/services/RoutingService' + +import { OutOfBandService } from './OutOfBandService' +import { OutOfBandDidCommService } from './domain/OutOfBandDidCommService' +import { OutOfBandEventTypes } from './domain/OutOfBandEvents' +import { OutOfBandRole } from './domain/OutOfBandRole' +import { OutOfBandState } from './domain/OutOfBandState' +import { HandshakeReuseHandler } from './handlers' +import { HandshakeReuseAcceptedHandler } from './handlers/HandshakeReuseAcceptedHandler' +import { convertToNewInvitation, convertToOldInvitation } from './helpers' +import { OutOfBandInvitation } from './messages' +import { OutOfBandRecord } from './repository/OutOfBandRecord' + +const didCommProfiles = ['didcomm/aip1', 'didcomm/aip2;env=rfc19'] + +export interface CreateOutOfBandInvitationConfig { + label?: string + alias?: string + imageUrl?: string + goalCode?: string + goal?: string + handshake?: boolean + handshakeProtocols?: HandshakeProtocol[] + messages?: AgentMessage[] + multiUseInvitation?: boolean + autoAcceptConnection?: boolean + routing?: Routing + appendedAttachments?: Attachment[] +} + +export interface CreateLegacyInvitationConfig { + label?: string + alias?: string + imageUrl?: string + multiUseInvitation?: boolean + autoAcceptConnection?: boolean + routing?: Routing +} + +export interface ReceiveOutOfBandInvitationConfig { + label?: string + alias?: string + imageUrl?: string + autoAcceptInvitation?: boolean + autoAcceptConnection?: boolean + reuseConnection?: boolean + routing?: Routing +} + +@injectable() +export class OutOfBandApi { + private outOfBandService: OutOfBandService + private routingService: RoutingService + private connectionsApi: ConnectionsApi + private didCommMessageRepository: DidCommMessageRepository + private dispatcher: Dispatcher + private messageSender: MessageSender + private eventEmitter: EventEmitter + private agentContext: AgentContext + private logger: Logger + + public constructor( + dispatcher: Dispatcher, + outOfBandService: OutOfBandService, + routingService: RoutingService, + connectionsApi: ConnectionsApi, + didCommMessageRepository: DidCommMessageRepository, + messageSender: MessageSender, + eventEmitter: EventEmitter, + @inject(InjectionSymbols.Logger) logger: Logger, + agentContext: AgentContext + ) { + this.dispatcher = dispatcher + this.agentContext = agentContext + this.logger = logger + this.outOfBandService = outOfBandService + this.routingService = routingService + this.connectionsApi = connectionsApi + this.didCommMessageRepository = didCommMessageRepository + this.messageSender = messageSender + this.eventEmitter = eventEmitter + this.registerHandlers(dispatcher) + } + + /** + * Creates an outbound out-of-band record containing out-of-band invitation message defined in + * Aries RFC 0434: Out-of-Band Protocol 1.1. + * + * It automatically adds all supported handshake protocols by agent to `handshake_protocols`. You + * can modify this by setting `handshakeProtocols` in `config` parameter. If you want to create + * invitation without handshake, you can set `handshake` to `false`. + * + * If `config` parameter contains `messages` it adds them to `requests~attach` attribute. + * + * Agent role: sender (inviter) + * + * @param config configuration of how out-of-band invitation should be created + * @returns out-of-band record + */ + public async createInvitation(config: CreateOutOfBandInvitationConfig = {}): Promise { + const multiUseInvitation = config.multiUseInvitation ?? false + const handshake = config.handshake ?? true + const customHandshakeProtocols = config.handshakeProtocols + const autoAcceptConnection = config.autoAcceptConnection ?? this.connectionsApi.config.autoAcceptConnections + // We don't want to treat an empty array as messages being provided + const messages = config.messages && config.messages.length > 0 ? config.messages : undefined + const label = config.label ?? this.agentContext.config.label + const imageUrl = config.imageUrl ?? this.agentContext.config.connectionImageUrl + const appendedAttachments = + config.appendedAttachments && config.appendedAttachments.length > 0 ? config.appendedAttachments : undefined + + if (!handshake && !messages) { + throw new AriesFrameworkError( + 'One or both of handshake_protocols and requests~attach MUST be included in the message.' + ) + } + + if (!handshake && customHandshakeProtocols) { + throw new AriesFrameworkError(`Attribute 'handshake' can not be 'false' when 'handshakeProtocols' is defined.`) + } + + // For now we disallow creating multi-use invitation with attachments. This would mean we need multi-use + // credential and presentation exchanges. + if (messages && multiUseInvitation) { + throw new AriesFrameworkError("Attribute 'multiUseInvitation' can not be 'true' when 'messages' is defined.") + } + + let handshakeProtocols + if (handshake) { + // Find supported handshake protocol preserving the order of handshake protocols defined + // by agent + if (customHandshakeProtocols) { + this.assertHandshakeProtocols(customHandshakeProtocols) + handshakeProtocols = customHandshakeProtocols + } else { + handshakeProtocols = this.getSupportedHandshakeProtocols() + } + } + + const routing = config.routing ?? (await this.routingService.getRouting(this.agentContext, {})) + + const services = routing.endpoints.map((endpoint, index) => { + return new OutOfBandDidCommService({ + id: `#inline-${index}`, + serviceEndpoint: endpoint, + recipientKeys: [routing.recipientKey].map((key) => new DidKey(key).did), + routingKeys: routing.routingKeys.map((key) => new DidKey(key).did), + }) + }) + + const options = { + label, + goal: config.goal, + goalCode: config.goalCode, + imageUrl, + accept: didCommProfiles, + services, + handshakeProtocols, + appendedAttachments, + } + const outOfBandInvitation = new OutOfBandInvitation(options) + + if (messages) { + messages.forEach((message) => { + if (message.service) { + // We can remove `~service` attribute from message. Newer OOB messages have `services` attribute instead. + message.service = undefined + } + outOfBandInvitation.addRequest(message) + }) + } + + const outOfBandRecord = new OutOfBandRecord({ + mediatorId: routing.mediatorId, + role: OutOfBandRole.Sender, + state: OutOfBandState.AwaitResponse, + outOfBandInvitation: outOfBandInvitation, + reusable: multiUseInvitation, + autoAcceptConnection, + }) + + await this.outOfBandService.save(this.agentContext, outOfBandRecord) + this.outOfBandService.emitStateChangedEvent(this.agentContext, outOfBandRecord, null) + + return outOfBandRecord + } + + /** + * Creates an outbound out-of-band record in the same way how `createInvitation` method does it, + * but it also converts out-of-band invitation message to an "legacy" invitation message defined + * in RFC 0160: Connection Protocol and returns it together with out-of-band record. + * + * Agent role: sender (inviter) + * + * @param config configuration of how a connection invitation should be created + * @returns out-of-band record and connection invitation + */ + public async createLegacyInvitation(config: CreateLegacyInvitationConfig = {}) { + const outOfBandRecord = await this.createInvitation({ + ...config, + handshakeProtocols: [HandshakeProtocol.Connections], + }) + return { outOfBandRecord, invitation: convertToOldInvitation(outOfBandRecord.outOfBandInvitation) } + } + + public async createLegacyConnectionlessInvitation(config: { + recordId: string + message: Message + domain: string + }): Promise<{ message: Message; invitationUrl: string }> { + // Create keys (and optionally register them at the mediator) + const routing = await this.routingService.getRouting(this.agentContext) + + // Set the service on the message + config.message.service = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.recipientKey].map((key) => key.publicKeyBase58), + routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), + }) + + // We need to update the message with the new service, so we can + // retrieve it from storage later on. + await this.didCommMessageRepository.saveOrUpdateAgentMessage(this.agentContext, { + agentMessage: config.message, + associatedRecordId: config.recordId, + role: DidCommMessageRole.Sender, + }) + + return { + message: config.message, + invitationUrl: `${config.domain}?d_m=${JsonEncoder.toBase64URL(JsonTransformer.toJSON(config.message))}`, + } + } + + /** + * Parses URL, decodes invitation and calls `receiveMessage` with parsed invitation message. + * + * Agent role: receiver (invitee) + * + * @param invitationUrl url containing a base64 encoded invitation to receive + * @param config configuration of how out-of-band invitation should be processed + * @returns out-of-band record and connection record if one has been created + */ + public async receiveInvitationFromUrl(invitationUrl: string, config: ReceiveOutOfBandInvitationConfig = {}) { + const message = await this.parseInvitationShortUrl(invitationUrl) + + return this.receiveInvitation(message, config) + } + + /** + * Parses URL containing encoded invitation and returns invitation message. + * + * @param invitationUrl URL containing encoded invitation + * + * @returns OutOfBandInvitation + */ + public parseInvitation(invitationUrl: string): OutOfBandInvitation { + return parseInvitationUrl(invitationUrl) + } + + /** + * Parses URL containing encoded invitation and returns invitation message. Compatible with + * parsing shortened URLs + * + * @param invitationUrl URL containing encoded invitation + * + * @returns OutOfBandInvitation + */ + public async parseInvitationShortUrl(invitation: string): Promise { + return await parseInvitationShortUrl(invitation, this.agentContext.config.agentDependencies) + } + + /** + * Creates inbound out-of-band record and assigns out-of-band invitation message to it if the + * message is valid. It automatically passes out-of-band invitation for further processing to + * `acceptInvitation` method. If you don't want to do that you can set `autoAcceptInvitation` + * attribute in `config` parameter to `false` and accept the message later by calling + * `acceptInvitation`. + * + * It supports both OOB (Aries RFC 0434: Out-of-Band Protocol 1.1) and Connection Invitation + * (0160: Connection Protocol). + * + * Agent role: receiver (invitee) + * + * @param invitation either OutOfBandInvitation or ConnectionInvitationMessage + * @param config config for handling of invitation + * + * @returns out-of-band record and connection record if one has been created. + */ + public async receiveInvitation( + invitation: OutOfBandInvitation | ConnectionInvitationMessage, + config: ReceiveOutOfBandInvitationConfig = {} + ): Promise<{ outOfBandRecord: OutOfBandRecord; connectionRecord?: ConnectionRecord }> { + // Convert to out of band invitation if needed + const outOfBandInvitation = + invitation instanceof OutOfBandInvitation ? invitation : convertToNewInvitation(invitation) + + const { handshakeProtocols } = outOfBandInvitation + const { routing } = config + + const autoAcceptInvitation = config.autoAcceptInvitation ?? true + const autoAcceptConnection = config.autoAcceptConnection ?? true + const reuseConnection = config.reuseConnection ?? false + const label = config.label ?? this.agentContext.config.label + const alias = config.alias + const imageUrl = config.imageUrl ?? this.agentContext.config.connectionImageUrl + + const messages = outOfBandInvitation.getRequests() + + if ((!handshakeProtocols || handshakeProtocols.length === 0) && (!messages || messages?.length === 0)) { + throw new AriesFrameworkError( + 'One or both of handshake_protocols and requests~attach MUST be included in the message.' + ) + } + + // Make sure we haven't processed this invitation before. + let outOfBandRecord = await this.findByInvitationId(outOfBandInvitation.id) + if (outOfBandRecord) { + throw new AriesFrameworkError( + `An out of band record with invitation ${outOfBandInvitation.id} already exists. Invitations should have a unique id.` + ) + } + + outOfBandRecord = new OutOfBandRecord({ + role: OutOfBandRole.Receiver, + state: OutOfBandState.Initial, + outOfBandInvitation: outOfBandInvitation, + autoAcceptConnection, + }) + await this.outOfBandService.save(this.agentContext, outOfBandRecord) + this.outOfBandService.emitStateChangedEvent(this.agentContext, outOfBandRecord, null) + + if (autoAcceptInvitation) { + return await this.acceptInvitation(outOfBandRecord.id, { + label, + alias, + imageUrl, + autoAcceptConnection, + reuseConnection, + routing, + }) + } + + return { outOfBandRecord } + } + + /** + * Creates a connection if the out-of-band invitation message contains `handshake_protocols` + * attribute, except for the case when connection already exists and `reuseConnection` is enabled. + * + * It passes first supported message from `requests~attach` attribute to the agent, except for the + * case reuse of connection is applied when it just sends `handshake-reuse` message to existing + * connection. + * + * Agent role: receiver (invitee) + * + * @param outOfBandId + * @param config + * @returns out-of-band record and connection record if one has been created. + */ + public async acceptInvitation( + outOfBandId: string, + config: { + autoAcceptConnection?: boolean + reuseConnection?: boolean + label?: string + alias?: string + imageUrl?: string + mediatorId?: string + routing?: Routing + } + ) { + const outOfBandRecord = await this.outOfBandService.getById(this.agentContext, outOfBandId) + + const { outOfBandInvitation } = outOfBandRecord + const { label, alias, imageUrl, autoAcceptConnection, reuseConnection, routing } = config + const { handshakeProtocols, services } = outOfBandInvitation + const messages = outOfBandInvitation.getRequests() + + const existingConnection = await this.findExistingConnection(services) + + await this.outOfBandService.updateState(this.agentContext, outOfBandRecord, OutOfBandState.PrepareResponse) + + if (handshakeProtocols) { + this.logger.debug('Out of band message contains handshake protocols.') + + let connectionRecord + if (existingConnection && reuseConnection) { + this.logger.debug( + `Connection already exists and reuse is enabled. Reusing an existing connection with ID ${existingConnection.id}.` + ) + + if (!messages) { + this.logger.debug('Out of band message does not contain any request messages.') + const isHandshakeReuseSuccessful = await this.handleHandshakeReuse(outOfBandRecord, existingConnection) + + // Handshake reuse was successful + if (isHandshakeReuseSuccessful) { + this.logger.debug(`Handshake reuse successful. Reusing existing connection ${existingConnection.id}.`) + connectionRecord = existingConnection + } else { + // Handshake reuse failed. Not setting connection record + this.logger.debug(`Handshake reuse failed. Not using existing connection ${existingConnection.id}.`) + } + } else { + // Handshake reuse because we found a connection and we can respond directly to the message + this.logger.debug(`Reusing existing connection ${existingConnection.id}.`) + connectionRecord = existingConnection + } + } + + // If no existing connection was found, reuseConnection is false, or we didn't receive a + // handshake-reuse-accepted message we create a new connection + if (!connectionRecord) { + this.logger.debug('Connection does not exist or reuse is disabled. Creating a new connection.') + // Find first supported handshake protocol preserving the order of handshake protocols + // defined by `handshake_protocols` attribute in the invitation message + const handshakeProtocol = this.getFirstSupportedProtocol(handshakeProtocols) + connectionRecord = await this.connectionsApi.acceptOutOfBandInvitation(outOfBandRecord, { + label, + alias, + imageUrl, + autoAcceptConnection, + protocol: handshakeProtocol, + routing, + }) + } + + if (messages) { + this.logger.debug('Out of band message contains request messages.') + if (connectionRecord.isReady) { + await this.emitWithConnection(connectionRecord, messages) + } else { + // Wait until the connection is ready and then pass the messages to the agent for further processing + this.connectionsApi + .returnWhenIsConnected(connectionRecord.id) + .then((connectionRecord) => this.emitWithConnection(connectionRecord, messages)) + .catch((error) => { + if (error instanceof EmptyError) { + this.logger.warn( + `Agent unsubscribed before connection got into ${DidExchangeState.Completed} state`, + error + ) + } else { + this.logger.error('Promise waiting for the connection to be complete failed.', error) + } + }) + } + } + return { outOfBandRecord, connectionRecord } + } else if (messages) { + this.logger.debug('Out of band message contains only request messages.') + if (existingConnection) { + this.logger.debug('Connection already exists.', { connectionId: existingConnection.id }) + await this.emitWithConnection(existingConnection, messages) + } else { + await this.emitWithServices(services, messages) + } + } + return { outOfBandRecord } + } + + public async findByRecipientKey(recipientKey: Key) { + return this.outOfBandService.findByRecipientKey(this.agentContext, recipientKey) + } + + public async findByInvitationId(invitationId: string) { + return this.outOfBandService.findByInvitationId(this.agentContext, invitationId) + } + + /** + * Retrieve all out of bands records + * + * @returns List containing all out of band records + */ + public getAll() { + return this.outOfBandService.getAll(this.agentContext) + } + + /** + * Retrieve a out of band record by id + * + * @param outOfBandId The out of band record id + * @throws {RecordNotFoundError} If no record is found + * @return The out of band record + * + */ + public getById(outOfBandId: string): Promise { + return this.outOfBandService.getById(this.agentContext, outOfBandId) + } + + /** + * Find an out of band record by id + * + * @param outOfBandId the out of band record id + * @returns The out of band record or null if not found + */ + public findById(outOfBandId: string): Promise { + return this.outOfBandService.findById(this.agentContext, outOfBandId) + } + + /** + * Delete an out of band record by id + * + * @param outOfBandId the out of band record id + */ + public async deleteById(outOfBandId: string) { + return this.outOfBandService.deleteById(this.agentContext, outOfBandId) + } + + private assertHandshakeProtocols(handshakeProtocols: HandshakeProtocol[]) { + if (!this.areHandshakeProtocolsSupported(handshakeProtocols)) { + const supportedProtocols = this.getSupportedHandshakeProtocols() + throw new AriesFrameworkError( + `Handshake protocols [${handshakeProtocols}] are not supported. Supported protocols are [${supportedProtocols}]` + ) + } + } + + private areHandshakeProtocolsSupported(handshakeProtocols: HandshakeProtocol[]) { + const supportedProtocols = this.getSupportedHandshakeProtocols() + return handshakeProtocols.every((p) => supportedProtocols.includes(p)) + } + + private getSupportedHandshakeProtocols(): HandshakeProtocol[] { + const handshakeMessageFamilies = ['https://didcomm.org/didexchange', 'https://didcomm.org/connections'] + const handshakeProtocols = this.dispatcher.filterSupportedProtocolsByMessageFamilies(handshakeMessageFamilies) + + if (handshakeProtocols.length === 0) { + throw new AriesFrameworkError('There is no handshake protocol supported. Agent can not create a connection.') + } + + // Order protocols according to `handshakeMessageFamilies` array + const orderedProtocols = handshakeMessageFamilies + .map((messageFamily) => handshakeProtocols.find((p) => p.startsWith(messageFamily))) + .filter((item): item is string => !!item) + + return orderedProtocols as HandshakeProtocol[] + } + + private getFirstSupportedProtocol(handshakeProtocols: HandshakeProtocol[]) { + const supportedProtocols = this.getSupportedHandshakeProtocols() + const handshakeProtocol = handshakeProtocols.find((p) => supportedProtocols.includes(p)) + if (!handshakeProtocol) { + throw new AriesFrameworkError( + `Handshake protocols [${handshakeProtocols}] are not supported. Supported protocols are [${supportedProtocols}]` + ) + } + return handshakeProtocol + } + + private async findExistingConnection(services: Array) { + this.logger.debug('Searching for an existing connection for out-of-band invitation services.', { services }) + + // TODO: for each did we should look for a connection with the invitation did OR a connection with theirDid that matches the service did + for (const didOrService of services) { + // We need to check if the service is an instance of string because of limitations from class-validator + if (typeof didOrService === 'string' || didOrService instanceof String) { + // TODO await this.connectionsApi.findByTheirDid() + throw new AriesFrameworkError('Dids are not currently supported in out-of-band invitation services attribute.') + } + + const did = outOfBandServiceToNumAlgo2Did(didOrService) + const connections = await this.connectionsApi.findByInvitationDid(did) + this.logger.debug(`Retrieved ${connections.length} connections for invitation did ${did}`) + + if (connections.length === 1) { + const [firstConnection] = connections + return firstConnection + } else if (connections.length > 1) { + this.logger.warn(`There is more than one connection created from invitationDid ${did}. Taking the first one.`) + const [firstConnection] = connections + return firstConnection + } + return null + } + } + + private async emitWithConnection(connectionRecord: ConnectionRecord, messages: PlaintextMessage[]) { + const supportedMessageTypes = this.dispatcher.supportedMessageTypes + const plaintextMessage = messages.find((message) => { + const parsedMessageType = parseMessageType(message['@type']) + return supportedMessageTypes.find((type) => supportsIncomingMessageType(parsedMessageType, type)) + }) + + if (!plaintextMessage) { + throw new AriesFrameworkError('There is no message in requests~attach supported by agent.') + } + + this.logger.debug(`Message with type ${plaintextMessage['@type']} can be processed.`) + + this.eventEmitter.emit(this.agentContext, { + type: AgentEventTypes.AgentMessageReceived, + payload: { + message: plaintextMessage, + connection: connectionRecord, + contextCorrelationId: this.agentContext.contextCorrelationId, + }, + }) + } + + private async emitWithServices(services: Array, messages: PlaintextMessage[]) { + if (!services || services.length === 0) { + throw new AriesFrameworkError(`There are no services. We can not emit messages`) + } + + const supportedMessageTypes = this.dispatcher.supportedMessageTypes + const plaintextMessage = messages.find((message) => { + const parsedMessageType = parseMessageType(message['@type']) + return supportedMessageTypes.find((type) => supportsIncomingMessageType(parsedMessageType, type)) + }) + + if (!plaintextMessage) { + throw new AriesFrameworkError('There is no message in requests~attach supported by agent.') + } + + this.logger.debug(`Message with type ${plaintextMessage['@type']} can be processed.`) + + // The framework currently supports only older OOB messages with `~service` decorator. + // TODO: support receiving messages with other services so we don't have to transform the service + // to ~service decorator + const [service] = services + + if (typeof service === 'string') { + throw new AriesFrameworkError('Dids are not currently supported in out-of-band invitation services attribute.') + } + + const serviceDecorator = new ServiceDecorator({ + recipientKeys: service.recipientKeys.map(didKeyToVerkey), + routingKeys: service.routingKeys?.map(didKeyToVerkey) || [], + serviceEndpoint: service.serviceEndpoint, + }) + + plaintextMessage['~service'] = JsonTransformer.toJSON(serviceDecorator) + this.eventEmitter.emit(this.agentContext, { + type: AgentEventTypes.AgentMessageReceived, + payload: { + message: plaintextMessage, + contextCorrelationId: this.agentContext.contextCorrelationId, + }, + }) + } + + private async handleHandshakeReuse(outOfBandRecord: OutOfBandRecord, connectionRecord: ConnectionRecord) { + const reuseMessage = await this.outOfBandService.createHandShakeReuse( + this.agentContext, + outOfBandRecord, + connectionRecord + ) + + const reuseAcceptedEventPromise = firstValueFrom( + this.eventEmitter.observable(OutOfBandEventTypes.HandshakeReused).pipe( + filterContextCorrelationId(this.agentContext.contextCorrelationId), + // Find the first reuse event where the handshake reuse accepted matches the reuse message thread + // TODO: Should we store the reuse state? Maybe we can keep it in memory for now + first( + (event) => + event.payload.reuseThreadId === reuseMessage.threadId && + event.payload.outOfBandRecord.id === outOfBandRecord.id && + event.payload.connectionRecord.id === connectionRecord.id + ), + // If the event is found, we return the value true + map(() => true), + timeout(15000), + // If timeout is reached, we return false + catchError(() => of(false)) + ) + ) + + const outbound = createOutboundMessage(connectionRecord, reuseMessage) + await this.messageSender.sendMessage(this.agentContext, outbound) + + return reuseAcceptedEventPromise + } + + private registerHandlers(dispatcher: Dispatcher) { + dispatcher.registerHandler(new HandshakeReuseHandler(this.outOfBandService)) + dispatcher.registerHandler(new HandshakeReuseAcceptedHandler(this.outOfBandService)) + } +} diff --git a/packages/core/src/modules/oob/OutOfBandModule.ts b/packages/core/src/modules/oob/OutOfBandModule.ts index c2f365ae07..3b31876a48 100644 --- a/packages/core/src/modules/oob/OutOfBandModule.ts +++ b/packages/core/src/modules/oob/OutOfBandModule.ts @@ -1,720 +1,16 @@ -import type { AgentMessage } from '../../agent/AgentMessage' -import type { AgentMessageReceivedEvent } from '../../agent/Events' -import type { Key } from '../../crypto' -import type { Attachment } from '../../decorators/attachment/Attachment' -import type { ConnectionInvitationMessage, ConnectionRecord, Routing } from '../../modules/connections' -import type { DependencyManager } from '../../plugins' -import type { PlaintextMessage } from '../../types' -import type { HandshakeReusedEvent } from './domain/OutOfBandEvents' - -import { catchError, EmptyError, first, firstValueFrom, map, of, timeout } from 'rxjs' - -import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' -import { EventEmitter } from '../../agent/EventEmitter' -import { filterContextCorrelationId, AgentEventTypes } from '../../agent/Events' -import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' -import { InjectionSymbols } from '../../constants' -import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' -import { AriesFrameworkError } from '../../error' -import { Logger } from '../../logger' -import { ConnectionsModule, DidExchangeState, HandshakeProtocol } from '../../modules/connections' -import { inject, injectable, module } from '../../plugins' -import { DidCommMessageRepository, DidCommMessageRole } from '../../storage' -import { JsonEncoder, JsonTransformer } from '../../utils' -import { parseMessageType, supportsIncomingMessageType } from '../../utils/messageType' -import { parseInvitationUrl, parseInvitationShortUrl } from '../../utils/parseInvitation' -import { DidKey } from '../dids' -import { didKeyToVerkey } from '../dids/helpers' -import { outOfBandServiceToNumAlgo2Did } from '../dids/methods/peer/peerDidNumAlgo2' -import { RoutingService } from '../routing/services/RoutingService' +import type { DependencyManager, Module } from '../../plugins' +import { OutOfBandApi } from './OutOfBandApi' import { OutOfBandService } from './OutOfBandService' -import { OutOfBandDidCommService } from './domain/OutOfBandDidCommService' -import { OutOfBandEventTypes } from './domain/OutOfBandEvents' -import { OutOfBandRole } from './domain/OutOfBandRole' -import { OutOfBandState } from './domain/OutOfBandState' -import { HandshakeReuseHandler } from './handlers' -import { HandshakeReuseAcceptedHandler } from './handlers/HandshakeReuseAcceptedHandler' -import { convertToNewInvitation, convertToOldInvitation } from './helpers' -import { OutOfBandInvitation } from './messages' import { OutOfBandRepository } from './repository' -import { OutOfBandRecord } from './repository/OutOfBandRecord' - -const didCommProfiles = ['didcomm/aip1', 'didcomm/aip2;env=rfc19'] - -export interface CreateOutOfBandInvitationConfig { - label?: string - alias?: string - imageUrl?: string - goalCode?: string - goal?: string - handshake?: boolean - handshakeProtocols?: HandshakeProtocol[] - messages?: AgentMessage[] - multiUseInvitation?: boolean - autoAcceptConnection?: boolean - routing?: Routing - appendedAttachments?: Attachment[] -} - -export interface CreateLegacyInvitationConfig { - label?: string - alias?: string - imageUrl?: string - multiUseInvitation?: boolean - autoAcceptConnection?: boolean - routing?: Routing -} - -export interface ReceiveOutOfBandInvitationConfig { - label?: string - alias?: string - imageUrl?: string - autoAcceptInvitation?: boolean - autoAcceptConnection?: boolean - reuseConnection?: boolean - routing?: Routing -} - -@module() -@injectable() -export class OutOfBandModule { - private outOfBandService: OutOfBandService - private routingService: RoutingService - private connectionsModule: ConnectionsModule - private didCommMessageRepository: DidCommMessageRepository - private dispatcher: Dispatcher - private messageSender: MessageSender - private eventEmitter: EventEmitter - private agentContext: AgentContext - private logger: Logger - - public constructor( - dispatcher: Dispatcher, - outOfBandService: OutOfBandService, - routingService: RoutingService, - connectionsModule: ConnectionsModule, - didCommMessageRepository: DidCommMessageRepository, - messageSender: MessageSender, - eventEmitter: EventEmitter, - @inject(InjectionSymbols.Logger) logger: Logger, - agentContext: AgentContext - ) { - this.dispatcher = dispatcher - this.agentContext = agentContext - this.logger = logger - this.outOfBandService = outOfBandService - this.routingService = routingService - this.connectionsModule = connectionsModule - this.didCommMessageRepository = didCommMessageRepository - this.messageSender = messageSender - this.eventEmitter = eventEmitter - this.registerHandlers(dispatcher) - } - - /** - * Creates an outbound out-of-band record containing out-of-band invitation message defined in - * Aries RFC 0434: Out-of-Band Protocol 1.1. - * - * It automatically adds all supported handshake protocols by agent to `handshake_protocols`. You - * can modify this by setting `handshakeProtocols` in `config` parameter. If you want to create - * invitation without handshake, you can set `handshake` to `false`. - * - * If `config` parameter contains `messages` it adds them to `requests~attach` attribute. - * - * Agent role: sender (inviter) - * - * @param config configuration of how out-of-band invitation should be created - * @returns out-of-band record - */ - public async createInvitation(config: CreateOutOfBandInvitationConfig = {}): Promise { - const multiUseInvitation = config.multiUseInvitation ?? false - const handshake = config.handshake ?? true - const customHandshakeProtocols = config.handshakeProtocols - const autoAcceptConnection = config.autoAcceptConnection ?? this.agentContext.config.autoAcceptConnections - // We don't want to treat an empty array as messages being provided - const messages = config.messages && config.messages.length > 0 ? config.messages : undefined - const label = config.label ?? this.agentContext.config.label - const imageUrl = config.imageUrl ?? this.agentContext.config.connectionImageUrl - const appendedAttachments = - config.appendedAttachments && config.appendedAttachments.length > 0 ? config.appendedAttachments : undefined - - if (!handshake && !messages) { - throw new AriesFrameworkError( - 'One or both of handshake_protocols and requests~attach MUST be included in the message.' - ) - } - - if (!handshake && customHandshakeProtocols) { - throw new AriesFrameworkError(`Attribute 'handshake' can not be 'false' when 'handshakeProtocols' is defined.`) - } - - // For now we disallow creating multi-use invitation with attachments. This would mean we need multi-use - // credential and presentation exchanges. - if (messages && multiUseInvitation) { - throw new AriesFrameworkError("Attribute 'multiUseInvitation' can not be 'true' when 'messages' is defined.") - } - - let handshakeProtocols - if (handshake) { - // Find supported handshake protocol preserving the order of handshake protocols defined - // by agent - if (customHandshakeProtocols) { - this.assertHandshakeProtocols(customHandshakeProtocols) - handshakeProtocols = customHandshakeProtocols - } else { - handshakeProtocols = this.getSupportedHandshakeProtocols() - } - } - - const routing = config.routing ?? (await this.routingService.getRouting(this.agentContext, {})) - - const services = routing.endpoints.map((endpoint, index) => { - return new OutOfBandDidCommService({ - id: `#inline-${index}`, - serviceEndpoint: endpoint, - recipientKeys: [routing.recipientKey].map((key) => new DidKey(key).did), - routingKeys: routing.routingKeys.map((key) => new DidKey(key).did), - }) - }) - - const options = { - label, - goal: config.goal, - goalCode: config.goalCode, - imageUrl, - accept: didCommProfiles, - services, - handshakeProtocols, - appendedAttachments, - } - const outOfBandInvitation = new OutOfBandInvitation(options) - - if (messages) { - messages.forEach((message) => { - if (message.service) { - // We can remove `~service` attribute from message. Newer OOB messages have `services` attribute instead. - message.service = undefined - } - outOfBandInvitation.addRequest(message) - }) - } - - const outOfBandRecord = new OutOfBandRecord({ - mediatorId: routing.mediatorId, - role: OutOfBandRole.Sender, - state: OutOfBandState.AwaitResponse, - outOfBandInvitation: outOfBandInvitation, - reusable: multiUseInvitation, - autoAcceptConnection, - }) - - await this.outOfBandService.save(this.agentContext, outOfBandRecord) - this.outOfBandService.emitStateChangedEvent(this.agentContext, outOfBandRecord, null) - - return outOfBandRecord - } - - /** - * Creates an outbound out-of-band record in the same way how `createInvitation` method does it, - * but it also converts out-of-band invitation message to an "legacy" invitation message defined - * in RFC 0160: Connection Protocol and returns it together with out-of-band record. - * - * Agent role: sender (inviter) - * - * @param config configuration of how a connection invitation should be created - * @returns out-of-band record and connection invitation - */ - public async createLegacyInvitation(config: CreateLegacyInvitationConfig = {}) { - const outOfBandRecord = await this.createInvitation({ - ...config, - handshakeProtocols: [HandshakeProtocol.Connections], - }) - return { outOfBandRecord, invitation: convertToOldInvitation(outOfBandRecord.outOfBandInvitation) } - } - - public async createLegacyConnectionlessInvitation(config: { - recordId: string - message: Message - domain: string - }): Promise<{ message: Message; invitationUrl: string }> { - // Create keys (and optionally register them at the mediator) - const routing = await this.routingService.getRouting(this.agentContext) - - // Set the service on the message - config.message.service = new ServiceDecorator({ - serviceEndpoint: routing.endpoints[0], - recipientKeys: [routing.recipientKey].map((key) => key.publicKeyBase58), - routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), - }) - - // We need to update the message with the new service, so we can - // retrieve it from storage later on. - await this.didCommMessageRepository.saveOrUpdateAgentMessage(this.agentContext, { - agentMessage: config.message, - associatedRecordId: config.recordId, - role: DidCommMessageRole.Sender, - }) - - return { - message: config.message, - invitationUrl: `${config.domain}?d_m=${JsonEncoder.toBase64URL(JsonTransformer.toJSON(config.message))}`, - } - } - - /** - * Parses URL, decodes invitation and calls `receiveMessage` with parsed invitation message. - * - * Agent role: receiver (invitee) - * - * @param invitationUrl url containing a base64 encoded invitation to receive - * @param config configuration of how out-of-band invitation should be processed - * @returns out-of-band record and connection record if one has been created - */ - public async receiveInvitationFromUrl(invitationUrl: string, config: ReceiveOutOfBandInvitationConfig = {}) { - const message = await this.parseInvitationShortUrl(invitationUrl) - return this.receiveInvitation(message, config) - } - - /** - * Parses URL containing encoded invitation and returns invitation message. - * - * @param invitationUrl URL containing encoded invitation - * - * @returns OutOfBandInvitation - */ - public parseInvitation(invitationUrl: string): OutOfBandInvitation { - return parseInvitationUrl(invitationUrl) - } - - /** - * Parses URL containing encoded invitation and returns invitation message. Compatible with - * parsing shortened URLs - * - * @param invitationUrl URL containing encoded invitation - * - * @returns OutOfBandInvitation - */ - public async parseInvitationShortUrl(invitation: string): Promise { - return await parseInvitationShortUrl(invitation, this.agentContext.config.agentDependencies) - } - - /** - * Creates inbound out-of-band record and assigns out-of-band invitation message to it if the - * message is valid. It automatically passes out-of-band invitation for further processing to - * `acceptInvitation` method. If you don't want to do that you can set `autoAcceptInvitation` - * attribute in `config` parameter to `false` and accept the message later by calling - * `acceptInvitation`. - * - * It supports both OOB (Aries RFC 0434: Out-of-Band Protocol 1.1) and Connection Invitation - * (0160: Connection Protocol). - * - * Agent role: receiver (invitee) - * - * @param invitation either OutOfBandInvitation or ConnectionInvitationMessage - * @param config config for handling of invitation - * - * @returns out-of-band record and connection record if one has been created. - */ - public async receiveInvitation( - invitation: OutOfBandInvitation | ConnectionInvitationMessage, - config: ReceiveOutOfBandInvitationConfig = {} - ): Promise<{ outOfBandRecord: OutOfBandRecord; connectionRecord?: ConnectionRecord }> { - // Convert to out of band invitation if needed - const outOfBandInvitation = - invitation instanceof OutOfBandInvitation ? invitation : convertToNewInvitation(invitation) - - const { handshakeProtocols } = outOfBandInvitation - const { routing } = config - - const autoAcceptInvitation = config.autoAcceptInvitation ?? true - const autoAcceptConnection = config.autoAcceptConnection ?? true - const reuseConnection = config.reuseConnection ?? false - const label = config.label ?? this.agentContext.config.label - const alias = config.alias - const imageUrl = config.imageUrl ?? this.agentContext.config.connectionImageUrl - - const messages = outOfBandInvitation.getRequests() - - if ((!handshakeProtocols || handshakeProtocols.length === 0) && (!messages || messages?.length === 0)) { - throw new AriesFrameworkError( - 'One or both of handshake_protocols and requests~attach MUST be included in the message.' - ) - } - - // Make sure we haven't processed this invitation before. - let outOfBandRecord = await this.findByInvitationId(outOfBandInvitation.id) - if (outOfBandRecord) { - throw new AriesFrameworkError( - `An out of band record with invitation ${outOfBandInvitation.id} already exists. Invitations should have a unique id.` - ) - } - - outOfBandRecord = new OutOfBandRecord({ - role: OutOfBandRole.Receiver, - state: OutOfBandState.Initial, - outOfBandInvitation: outOfBandInvitation, - autoAcceptConnection, - }) - await this.outOfBandService.save(this.agentContext, outOfBandRecord) - this.outOfBandService.emitStateChangedEvent(this.agentContext, outOfBandRecord, null) - - if (autoAcceptInvitation) { - return await this.acceptInvitation(outOfBandRecord.id, { - label, - alias, - imageUrl, - autoAcceptConnection, - reuseConnection, - routing, - }) - } - - return { outOfBandRecord } - } - - /** - * Creates a connection if the out-of-band invitation message contains `handshake_protocols` - * attribute, except for the case when connection already exists and `reuseConnection` is enabled. - * - * It passes first supported message from `requests~attach` attribute to the agent, except for the - * case reuse of connection is applied when it just sends `handshake-reuse` message to existing - * connection. - * - * Agent role: receiver (invitee) - * - * @param outOfBandId - * @param config - * @returns out-of-band record and connection record if one has been created. - */ - public async acceptInvitation( - outOfBandId: string, - config: { - autoAcceptConnection?: boolean - reuseConnection?: boolean - label?: string - alias?: string - imageUrl?: string - mediatorId?: string - routing?: Routing - } - ) { - const outOfBandRecord = await this.outOfBandService.getById(this.agentContext, outOfBandId) - - const { outOfBandInvitation } = outOfBandRecord - const { label, alias, imageUrl, autoAcceptConnection, reuseConnection, routing } = config - const { handshakeProtocols, services } = outOfBandInvitation - const messages = outOfBandInvitation.getRequests() - - const existingConnection = await this.findExistingConnection(services) - - await this.outOfBandService.updateState(this.agentContext, outOfBandRecord, OutOfBandState.PrepareResponse) - - if (handshakeProtocols) { - this.logger.debug('Out of band message contains handshake protocols.') - - let connectionRecord - if (existingConnection && reuseConnection) { - this.logger.debug( - `Connection already exists and reuse is enabled. Reusing an existing connection with ID ${existingConnection.id}.` - ) - - if (!messages) { - this.logger.debug('Out of band message does not contain any request messages.') - const isHandshakeReuseSuccessful = await this.handleHandshakeReuse(outOfBandRecord, existingConnection) - - // Handshake reuse was successful - if (isHandshakeReuseSuccessful) { - this.logger.debug(`Handshake reuse successful. Reusing existing connection ${existingConnection.id}.`) - connectionRecord = existingConnection - } else { - // Handshake reuse failed. Not setting connection record - this.logger.debug(`Handshake reuse failed. Not using existing connection ${existingConnection.id}.`) - } - } else { - // Handshake reuse because we found a connection and we can respond directly to the message - this.logger.debug(`Reusing existing connection ${existingConnection.id}.`) - connectionRecord = existingConnection - } - } - - // If no existing connection was found, reuseConnection is false, or we didn't receive a - // handshake-reuse-accepted message we create a new connection - if (!connectionRecord) { - this.logger.debug('Connection does not exist or reuse is disabled. Creating a new connection.') - // Find first supported handshake protocol preserving the order of handshake protocols - // defined by `handshake_protocols` attribute in the invitation message - const handshakeProtocol = this.getFirstSupportedProtocol(handshakeProtocols) - connectionRecord = await this.connectionsModule.acceptOutOfBandInvitation(outOfBandRecord, { - label, - alias, - imageUrl, - autoAcceptConnection, - protocol: handshakeProtocol, - routing, - }) - } - - if (messages) { - this.logger.debug('Out of band message contains request messages.') - if (connectionRecord.isReady) { - await this.emitWithConnection(connectionRecord, messages) - } else { - // Wait until the connection is ready and then pass the messages to the agent for further processing - this.connectionsModule - .returnWhenIsConnected(connectionRecord.id) - .then((connectionRecord) => this.emitWithConnection(connectionRecord, messages)) - .catch((error) => { - if (error instanceof EmptyError) { - this.logger.warn( - `Agent unsubscribed before connection got into ${DidExchangeState.Completed} state`, - error - ) - } else { - this.logger.error('Promise waiting for the connection to be complete failed.', error) - } - }) - } - } - return { outOfBandRecord, connectionRecord } - } else if (messages) { - this.logger.debug('Out of band message contains only request messages.') - if (existingConnection) { - this.logger.debug('Connection already exists.', { connectionId: existingConnection.id }) - await this.emitWithConnection(existingConnection, messages) - } else { - await this.emitWithServices(services, messages) - } - } - return { outOfBandRecord } - } - - public async findByRecipientKey(recipientKey: Key) { - return this.outOfBandService.findByRecipientKey(this.agentContext, recipientKey) - } - - public async findByInvitationId(invitationId: string) { - return this.outOfBandService.findByInvitationId(this.agentContext, invitationId) - } - - /** - * Retrieve all out of bands records - * - * @returns List containing all out of band records - */ - public getAll() { - return this.outOfBandService.getAll(this.agentContext) - } - - /** - * Retrieve a out of band record by id - * - * @param outOfBandId The out of band record id - * @throws {RecordNotFoundError} If no record is found - * @return The out of band record - * - */ - public getById(outOfBandId: string): Promise { - return this.outOfBandService.getById(this.agentContext, outOfBandId) - } - - /** - * Find an out of band record by id - * - * @param outOfBandId the out of band record id - * @returns The out of band record or null if not found - */ - public findById(outOfBandId: string): Promise { - return this.outOfBandService.findById(this.agentContext, outOfBandId) - } - - /** - * Delete an out of band record by id - * - * @param outOfBandId the out of band record id - */ - public async deleteById(outOfBandId: string) { - return this.outOfBandService.deleteById(this.agentContext, outOfBandId) - } - - private assertHandshakeProtocols(handshakeProtocols: HandshakeProtocol[]) { - if (!this.areHandshakeProtocolsSupported(handshakeProtocols)) { - const supportedProtocols = this.getSupportedHandshakeProtocols() - throw new AriesFrameworkError( - `Handshake protocols [${handshakeProtocols}] are not supported. Supported protocols are [${supportedProtocols}]` - ) - } - } - - private areHandshakeProtocolsSupported(handshakeProtocols: HandshakeProtocol[]) { - const supportedProtocols = this.getSupportedHandshakeProtocols() - return handshakeProtocols.every((p) => supportedProtocols.includes(p)) - } - - private getSupportedHandshakeProtocols(): HandshakeProtocol[] { - const handshakeMessageFamilies = ['https://didcomm.org/didexchange', 'https://didcomm.org/connections'] - const handshakeProtocols = this.dispatcher.filterSupportedProtocolsByMessageFamilies(handshakeMessageFamilies) - - if (handshakeProtocols.length === 0) { - throw new AriesFrameworkError('There is no handshake protocol supported. Agent can not create a connection.') - } - - // Order protocols according to `handshakeMessageFamilies` array - const orderedProtocols = handshakeMessageFamilies - .map((messageFamily) => handshakeProtocols.find((p) => p.startsWith(messageFamily))) - .filter((item): item is string => !!item) - - return orderedProtocols as HandshakeProtocol[] - } - - private getFirstSupportedProtocol(handshakeProtocols: HandshakeProtocol[]) { - const supportedProtocols = this.getSupportedHandshakeProtocols() - const handshakeProtocol = handshakeProtocols.find((p) => supportedProtocols.includes(p)) - if (!handshakeProtocol) { - throw new AriesFrameworkError( - `Handshake protocols [${handshakeProtocols}] are not supported. Supported protocols are [${supportedProtocols}]` - ) - } - return handshakeProtocol - } - - private async findExistingConnection(services: Array) { - this.logger.debug('Searching for an existing connection for out-of-band invitation services.', { services }) - - // TODO: for each did we should look for a connection with the invitation did OR a connection with theirDid that matches the service did - for (const didOrService of services) { - // We need to check if the service is an instance of string because of limitations from class-validator - if (typeof didOrService === 'string' || didOrService instanceof String) { - // TODO await this.connectionsModule.findByTheirDid() - throw new AriesFrameworkError('Dids are not currently supported in out-of-band invitation services attribute.') - } - - const did = outOfBandServiceToNumAlgo2Did(didOrService) - const connections = await this.connectionsModule.findByInvitationDid(did) - this.logger.debug(`Retrieved ${connections.length} connections for invitation did ${did}`) - - if (connections.length === 1) { - const [firstConnection] = connections - return firstConnection - } else if (connections.length > 1) { - this.logger.warn(`There is more than one connection created from invitationDid ${did}. Taking the first one.`) - const [firstConnection] = connections - return firstConnection - } - return null - } - } - - private async emitWithConnection(connectionRecord: ConnectionRecord, messages: PlaintextMessage[]) { - const supportedMessageTypes = this.dispatcher.supportedMessageTypes - const plaintextMessage = messages.find((message) => { - const parsedMessageType = parseMessageType(message['@type']) - return supportedMessageTypes.find((type) => supportsIncomingMessageType(parsedMessageType, type)) - }) - - if (!plaintextMessage) { - throw new AriesFrameworkError('There is no message in requests~attach supported by agent.') - } - - this.logger.debug(`Message with type ${plaintextMessage['@type']} can be processed.`) - - this.eventEmitter.emit(this.agentContext, { - type: AgentEventTypes.AgentMessageReceived, - payload: { - message: plaintextMessage, - connection: connectionRecord, - contextCorrelationId: this.agentContext.contextCorrelationId, - }, - }) - } - - private async emitWithServices(services: Array, messages: PlaintextMessage[]) { - if (!services || services.length === 0) { - throw new AriesFrameworkError(`There are no services. We can not emit messages`) - } - - const supportedMessageTypes = this.dispatcher.supportedMessageTypes - const plaintextMessage = messages.find((message) => { - const parsedMessageType = parseMessageType(message['@type']) - return supportedMessageTypes.find((type) => supportsIncomingMessageType(parsedMessageType, type)) - }) - - if (!plaintextMessage) { - throw new AriesFrameworkError('There is no message in requests~attach supported by agent.') - } - - this.logger.debug(`Message with type ${plaintextMessage['@type']} can be processed.`) - - // The framework currently supports only older OOB messages with `~service` decorator. - // TODO: support receiving messages with other services so we don't have to transform the service - // to ~service decorator - const [service] = services - - if (typeof service === 'string') { - throw new AriesFrameworkError('Dids are not currently supported in out-of-band invitation services attribute.') - } - - const serviceDecorator = new ServiceDecorator({ - recipientKeys: service.recipientKeys.map(didKeyToVerkey), - routingKeys: service.routingKeys?.map(didKeyToVerkey) || [], - serviceEndpoint: service.serviceEndpoint, - }) - - plaintextMessage['~service'] = JsonTransformer.toJSON(serviceDecorator) - this.eventEmitter.emit(this.agentContext, { - type: AgentEventTypes.AgentMessageReceived, - payload: { - message: plaintextMessage, - contextCorrelationId: this.agentContext.contextCorrelationId, - }, - }) - } - - private async handleHandshakeReuse(outOfBandRecord: OutOfBandRecord, connectionRecord: ConnectionRecord) { - const reuseMessage = await this.outOfBandService.createHandShakeReuse( - this.agentContext, - outOfBandRecord, - connectionRecord - ) - - const reuseAcceptedEventPromise = firstValueFrom( - this.eventEmitter.observable(OutOfBandEventTypes.HandshakeReused).pipe( - filterContextCorrelationId(this.agentContext.contextCorrelationId), - // Find the first reuse event where the handshake reuse accepted matches the reuse message thread - // TODO: Should we store the reuse state? Maybe we can keep it in memory for now - first( - (event) => - event.payload.reuseThreadId === reuseMessage.threadId && - event.payload.outOfBandRecord.id === outOfBandRecord.id && - event.payload.connectionRecord.id === connectionRecord.id - ), - // If the event is found, we return the value true - map(() => true), - timeout(15000), - // If timeout is reached, we return false - catchError(() => of(false)) - ) - ) - - const outbound = createOutboundMessage(connectionRecord, reuseMessage) - await this.messageSender.sendMessage(this.agentContext, outbound) - - return reuseAcceptedEventPromise - } - - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new HandshakeReuseHandler(this.outOfBandService)) - dispatcher.registerHandler(new HandshakeReuseAcceptedHandler(this.outOfBandService)) - } +export class OutOfBandModule implements Module { /** * Registers the dependencies of the ot of band module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(OutOfBandModule) + dependencyManager.registerContextScoped(OutOfBandApi) // Services dependencyManager.registerSingleton(OutOfBandService) diff --git a/packages/core/src/modules/oob/__tests__/OutOfBandModule.test.ts b/packages/core/src/modules/oob/__tests__/OutOfBandModule.test.ts new file mode 100644 index 0000000000..6613250092 --- /dev/null +++ b/packages/core/src/modules/oob/__tests__/OutOfBandModule.test.ts @@ -0,0 +1,23 @@ +import { DependencyManager } from '../../../plugins/DependencyManager' +import { OutOfBandApi } from '../OutOfBandApi' +import { OutOfBandModule } from '../OutOfBandModule' +import { OutOfBandService } from '../OutOfBandService' +import { OutOfBandRepository } from '../repository/OutOfBandRepository' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('OutOfBandModule', () => { + test('registers dependencies on the dependency manager', () => { + new OutOfBandModule().register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(OutOfBandApi) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(2) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(OutOfBandService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(OutOfBandRepository) + }) +}) diff --git a/packages/core/src/modules/oob/index.ts b/packages/core/src/modules/oob/index.ts index 2216f42770..b0c593951b 100644 --- a/packages/core/src/modules/oob/index.ts +++ b/packages/core/src/modules/oob/index.ts @@ -1,5 +1,6 @@ export * from './messages' export * from './repository' -export * from './OutOfBandModule' +export * from './OutOfBandApi' export * from './OutOfBandService' +export * from './OutOfBandModule' export * from './domain' diff --git a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts index 26f1d6b795..e101bd003d 100644 --- a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts +++ b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts @@ -4,6 +4,7 @@ import type { ProofRecord } from './repository' import { injectable } from '../../plugins' import { AutoAcceptProof } from './ProofAutoAcceptType' +import { ProofsModuleConfig } from './ProofsModuleConfig' /** * This class handles all the automation with all the messages in the present proof protocol @@ -11,6 +12,11 @@ import { AutoAcceptProof } from './ProofAutoAcceptType' */ @injectable() export class ProofResponseCoordinator { + private proofsModuleConfig: ProofsModuleConfig + + public constructor(proofsModuleConfig: ProofsModuleConfig) { + this.proofsModuleConfig = proofsModuleConfig + } /** * Returns the proof auto accept config based on priority: * - The record config takes first priority @@ -30,7 +36,7 @@ export class ProofResponseCoordinator { public shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, - agentContext.config.autoAcceptProofs + this.proofsModuleConfig.autoAcceptProofs ) if (autoAccept === AutoAcceptProof.Always) { @@ -45,7 +51,7 @@ export class ProofResponseCoordinator { public shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, - agentContext.config.autoAcceptProofs + this.proofsModuleConfig.autoAcceptProofs ) if ( @@ -64,7 +70,7 @@ export class ProofResponseCoordinator { public shouldAutoRespondToPresentation(agentContext: AgentContext, proofRecord: ProofRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, - agentContext.config.autoAcceptProofs + this.proofsModuleConfig.autoAcceptProofs ) if ( diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts new file mode 100644 index 0000000000..b1a7075957 --- /dev/null +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -0,0 +1,541 @@ +import type { AutoAcceptProof } from './ProofAutoAcceptType' +import type { PresentationPreview, RequestPresentationMessage } from './messages' +import type { RequestedCredentials, RetrievedCredentials } from './models' +import type { ProofRequestOptions } from './models/ProofRequest' +import type { ProofRecord } from './repository/ProofRecord' + +import { AgentContext } from '../../agent' +import { Dispatcher } from '../../agent/Dispatcher' +import { MessageSender } from '../../agent/MessageSender' +import { createOutboundMessage } from '../../agent/helpers' +import { InjectionSymbols } from '../../constants' +import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' +import { AriesFrameworkError } from '../../error' +import { Logger } from '../../logger' +import { inject, injectable } from '../../plugins' +import { ConnectionService } from '../connections/services/ConnectionService' +import { RoutingService } from '../routing/services/RoutingService' + +import { ProofResponseCoordinator } from './ProofResponseCoordinator' +import { PresentationProblemReportReason } from './errors' +import { + PresentationAckHandler, + PresentationHandler, + PresentationProblemReportHandler, + ProposePresentationHandler, + RequestPresentationHandler, +} from './handlers' +import { PresentationProblemReportMessage } from './messages/PresentationProblemReportMessage' +import { ProofRequest } from './models/ProofRequest' +import { ProofService } from './services' + +@injectable() +export class ProofsApi { + private proofService: ProofService + private connectionService: ConnectionService + private messageSender: MessageSender + private routingService: RoutingService + private agentContext: AgentContext + private proofResponseCoordinator: ProofResponseCoordinator + private logger: Logger + + public constructor( + dispatcher: Dispatcher, + proofService: ProofService, + connectionService: ConnectionService, + routingService: RoutingService, + agentContext: AgentContext, + messageSender: MessageSender, + proofResponseCoordinator: ProofResponseCoordinator, + @inject(InjectionSymbols.Logger) logger: Logger + ) { + this.proofService = proofService + this.connectionService = connectionService + this.messageSender = messageSender + this.routingService = routingService + this.agentContext = agentContext + this.proofResponseCoordinator = proofResponseCoordinator + this.logger = logger + this.registerHandlers(dispatcher) + } + + /** + * Initiate a new presentation exchange as prover by sending a presentation proposal message + * to the connection with the specified connection id. + * + * @param connectionId The connection to send the proof proposal to + * @param presentationProposal The presentation proposal to include in the message + * @param config Additional configuration to use for the proposal + * @returns Proof record associated with the sent proposal message + * + */ + public async proposeProof( + connectionId: string, + presentationProposal: PresentationPreview, + config?: { + comment?: string + autoAcceptProof?: AutoAcceptProof + parentThreadId?: string + } + ): Promise { + const connection = await this.connectionService.getById(this.agentContext, connectionId) + + const { message, proofRecord } = await this.proofService.createProposal( + this.agentContext, + connection, + presentationProposal, + config + ) + + const outbound = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outbound) + + return proofRecord + } + + /** + * Accept a presentation proposal as verifier (by sending a presentation request message) to the connection + * associated with the proof record. + * + * @param proofRecordId The id of the proof record for which to accept the proposal + * @param config Additional configuration to use for the request + * @returns Proof record associated with the presentation request + * + */ + public async acceptProposal( + proofRecordId: string, + config?: { + request?: { + name?: string + version?: string + nonce?: string + } + comment?: string + } + ): Promise { + const proofRecord = await this.proofService.getById(this.agentContext, proofRecordId) + + if (!proofRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for credential record '${proofRecord.id}'. Connection-less issuance does not support presentation proposal or negotiation.` + ) + } + + const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + + const presentationProposal = proofRecord.proposalMessage?.presentationProposal + if (!presentationProposal) { + throw new AriesFrameworkError(`Proof record with id ${proofRecordId} is missing required presentation proposal`) + } + + const proofRequest = await this.proofService.createProofRequestFromProposal( + this.agentContext, + presentationProposal, + { + name: config?.request?.name ?? 'proof-request', + version: config?.request?.version ?? '1.0', + nonce: config?.request?.nonce, + } + ) + + const { message } = await this.proofService.createRequestAsResponse(this.agentContext, proofRecord, proofRequest, { + comment: config?.comment, + }) + + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return proofRecord + } + + /** + * Initiate a new presentation exchange as verifier by sending a presentation request message + * to the connection with the specified connection id + * + * @param connectionId The connection to send the proof request to + * @param proofRequestOptions Options to build the proof request + * @returns Proof record associated with the sent request message + * + */ + public async requestProof( + connectionId: string, + proofRequestOptions: CreateProofRequestOptions, + config?: ProofRequestConfig + ): Promise { + const connection = await this.connectionService.getById(this.agentContext, connectionId) + + const nonce = proofRequestOptions.nonce ?? (await this.proofService.generateProofRequestNonce(this.agentContext)) + + const proofRequest = new ProofRequest({ + name: proofRequestOptions.name ?? 'proof-request', + version: proofRequestOptions.name ?? '1.0', + nonce, + requestedAttributes: proofRequestOptions.requestedAttributes, + requestedPredicates: proofRequestOptions.requestedPredicates, + }) + + const { message, proofRecord } = await this.proofService.createRequest( + this.agentContext, + proofRequest, + connection, + config + ) + + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return proofRecord + } + + /** + * Initiate a new presentation exchange as verifier by creating a presentation request + * not bound to any connection. The request must be delivered out-of-band to the holder + * + * @param proofRequestOptions Options to build the proof request + * @returns The proof record and proof request message + * + */ + public async createOutOfBandRequest( + proofRequestOptions: CreateProofRequestOptions, + config?: ProofRequestConfig + ): Promise<{ + requestMessage: RequestPresentationMessage + proofRecord: ProofRecord + }> { + const nonce = proofRequestOptions.nonce ?? (await this.proofService.generateProofRequestNonce(this.agentContext)) + + const proofRequest = new ProofRequest({ + name: proofRequestOptions.name ?? 'proof-request', + version: proofRequestOptions.name ?? '1.0', + nonce, + requestedAttributes: proofRequestOptions.requestedAttributes, + requestedPredicates: proofRequestOptions.requestedPredicates, + }) + + const { message, proofRecord } = await this.proofService.createRequest( + this.agentContext, + proofRequest, + undefined, + config + ) + + // Create and set ~service decorator + const routing = await this.routingService.getRouting(this.agentContext) + message.service = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.recipientKey.publicKeyBase58], + routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), + }) + + // Save ~service decorator to record (to remember our verkey) + proofRecord.requestMessage = message + await this.proofService.update(this.agentContext, proofRecord) + + return { proofRecord, requestMessage: message } + } + + /** + * Accept a presentation request as prover (by sending a presentation message) to the connection + * associated with the proof record. + * + * @param proofRecordId The id of the proof record for which to accept the request + * @param requestedCredentials The requested credentials object specifying which credentials to use for the proof + * @param config Additional configuration to use for the presentation + * @returns Proof record associated with the sent presentation message + * + */ + public async acceptRequest( + proofRecordId: string, + requestedCredentials: RequestedCredentials, + config?: { + comment?: string + } + ): Promise { + const record = await this.proofService.getById(this.agentContext, proofRecordId) + const { message, proofRecord } = await this.proofService.createPresentation( + this.agentContext, + record, + requestedCredentials, + config + ) + + // Use connection if present + if (proofRecord.connectionId) { + const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return proofRecord + } + // Use ~service decorator otherwise + else if (proofRecord.requestMessage?.service) { + // Create ~service decorator + const routing = await this.routingService.getRouting(this.agentContext) + const ourService = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.recipientKey.publicKeyBase58], + routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), + }) + + const recipientService = proofRecord.requestMessage.service + + // Set and save ~service decorator to record (to remember our verkey) + message.service = ourService + proofRecord.presentationMessage = message + await this.proofService.update(this.agentContext, proofRecord) + + await this.messageSender.sendMessageToService(this.agentContext, { + message, + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }) + + return proofRecord + } + // Cannot send message without connectionId or ~service decorator + else { + throw new AriesFrameworkError( + `Cannot accept presentation request without connectionId or ~service decorator on presentation request.` + ) + } + } + + /** + * Declines a proof request as holder + * @param proofRecordId the id of the proof request to be declined + * @returns proof record that was declined + */ + public async declineRequest(proofRecordId: string) { + const proofRecord = await this.proofService.getById(this.agentContext, proofRecordId) + await this.proofService.declineRequest(this.agentContext, proofRecord) + return proofRecord + } + + /** + * Accept a presentation as prover (by sending a presentation acknowledgement message) to the connection + * associated with the proof record. + * + * @param proofRecordId The id of the proof record for which to accept the presentation + * @returns Proof record associated with the sent presentation acknowledgement message + * + */ + public async acceptPresentation(proofRecordId: string): Promise { + const record = await this.proofService.getById(this.agentContext, proofRecordId) + const { message, proofRecord } = await this.proofService.createAck(this.agentContext, record) + + // Use connection if present + if (proofRecord.connectionId) { + const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + } + // Use ~service decorator otherwise + else if (proofRecord.requestMessage?.service && proofRecord.presentationMessage?.service) { + const recipientService = proofRecord.presentationMessage?.service + const ourService = proofRecord.requestMessage.service + + await this.messageSender.sendMessageToService(this.agentContext, { + message, + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }) + } + + // Cannot send message without credentialId or ~service decorator + else { + throw new AriesFrameworkError( + `Cannot accept presentation without connectionId or ~service decorator on presentation message.` + ) + } + + return proofRecord + } + + /** + * Create a {@link RetrievedCredentials} object. Given input proof request and presentation proposal, + * use credentials in the wallet to build indy requested credentials object for input to proof creation. + * If restrictions allow, self attested attributes will be used. + * + * + * @param proofRecordId the id of the proof request to get the matching credentials for + * @param config optional configuration for credential selection process. Use `filterByPresentationPreview` (default `true`) to only include + * credentials that match the presentation preview from the presentation proposal (if available). + + * @returns RetrievedCredentials object + */ + public async getRequestedCredentialsForProofRequest( + proofRecordId: string, + config?: GetRequestedCredentialsConfig + ): Promise { + const proofRecord = await this.proofService.getById(this.agentContext, proofRecordId) + + const indyProofRequest = proofRecord.requestMessage?.indyProofRequest + const presentationPreview = config?.filterByPresentationPreview + ? proofRecord.proposalMessage?.presentationProposal + : undefined + + if (!indyProofRequest) { + throw new AriesFrameworkError( + 'Unable to get requested credentials for proof request. No proof request message was found or the proof request message does not contain an indy proof request.' + ) + } + + return this.proofService.getRequestedCredentialsForProofRequest(this.agentContext, indyProofRequest, { + presentationProposal: presentationPreview, + filterByNonRevocationRequirements: config?.filterByNonRevocationRequirements ?? true, + }) + } + + /** + * Takes a RetrievedCredentials object and auto selects credentials in a RequestedCredentials object + * + * Use the return value of this method as input to {@link ProofService.createPresentation} to + * automatically accept a received presentation request. + * + * @param retrievedCredentials The retrieved credentials object to get credentials from + * + * @returns RequestedCredentials + */ + public autoSelectCredentialsForProofRequest(retrievedCredentials: RetrievedCredentials): RequestedCredentials { + return this.proofService.autoSelectCredentialsForProofRequest(retrievedCredentials) + } + + /** + * Send problem report message for a proof record + * @param proofRecordId The id of the proof record for which to send problem report + * @param message message to send + * @returns proof record associated with the proof problem report message + */ + public async sendProblemReport(proofRecordId: string, message: string) { + const record = await this.proofService.getById(this.agentContext, proofRecordId) + if (!record.connectionId) { + throw new AriesFrameworkError(`No connectionId found for proof record '${record.id}'.`) + } + const connection = await this.connectionService.getById(this.agentContext, record.connectionId) + const presentationProblemReportMessage = new PresentationProblemReportMessage({ + description: { + en: message, + code: PresentationProblemReportReason.Abandoned, + }, + }) + presentationProblemReportMessage.setThread({ + threadId: record.threadId, + parentThreadId: record.parentThreadId, + }) + const outboundMessage = createOutboundMessage(connection, presentationProblemReportMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return record + } + + /** + * Retrieve all proof records + * + * @returns List containing all proof records + */ + public getAll(): Promise { + return this.proofService.getAll(this.agentContext) + } + + /** + * Retrieve a proof record by id + * + * @param proofRecordId The proof record id + * @throws {RecordNotFoundError} If no record is found + * @throws {RecordDuplicateError} If multiple records are found + * @return The proof record + * + */ + public async getById(proofRecordId: string): Promise { + return this.proofService.getById(this.agentContext, proofRecordId) + } + + /** + * Retrieve a proof record by id + * + * @param proofRecordId The proof record id + * @return The proof record or null if not found + * + */ + public async findById(proofRecordId: string): Promise { + return this.proofService.findById(this.agentContext, proofRecordId) + } + + /** + * Delete a proof record by id + * + * @param proofId the proof record id + */ + public async deleteById(proofId: string) { + return this.proofService.deleteById(this.agentContext, proofId) + } + + /** + * Retrieve a proof record by connection id and thread id + * + * @param connectionId The connection id + * @param threadId The thread id + * @throws {RecordNotFoundError} If no record is found + * @throws {RecordDuplicateError} If multiple records are found + * @returns The proof record + */ + public async getByThreadAndConnectionId(threadId: string, connectionId?: string): Promise { + return this.proofService.getByThreadAndConnectionId(this.agentContext, threadId, connectionId) + } + + /** + * Retrieve proof records by connection id and parent thread id + * + * @param connectionId The connection id + * @param parentThreadId The parent thread id + * @returns List containing all proof records matching the given query + */ + public async getByParentThreadAndConnectionId(parentThreadId: string, connectionId?: string): Promise { + return this.proofService.getByParentThreadAndConnectionId(this.agentContext, parentThreadId, connectionId) + } + + private registerHandlers(dispatcher: Dispatcher) { + dispatcher.registerHandler( + new ProposePresentationHandler(this.proofService, this.proofResponseCoordinator, this.logger) + ) + dispatcher.registerHandler( + new RequestPresentationHandler(this.proofService, this.proofResponseCoordinator, this.routingService, this.logger) + ) + dispatcher.registerHandler(new PresentationHandler(this.proofService, this.proofResponseCoordinator, this.logger)) + dispatcher.registerHandler(new PresentationAckHandler(this.proofService)) + dispatcher.registerHandler(new PresentationProblemReportHandler(this.proofService)) + } +} + +export type CreateProofRequestOptions = Partial< + Pick +> + +export interface ProofRequestConfig { + comment?: string + autoAcceptProof?: AutoAcceptProof + parentThreadId?: string +} + +export interface GetRequestedCredentialsConfig { + /** + * Whether to filter the retrieved credentials using the presentation preview. + * This configuration will only have effect if a presentation proposal message is available + * containing a presentation preview. + * + * @default false + */ + filterByPresentationPreview?: boolean + + /** + * Whether to filter the retrieved credentials using the non-revocation request in the proof request. + * This configuration will only have effect if the proof request requires proof on non-revocation of any kind. + * Default to true + * + * @default true + */ + filterByNonRevocationRequirements?: boolean +} diff --git a/packages/core/src/modules/proofs/ProofsModule.ts b/packages/core/src/modules/proofs/ProofsModule.ts index 9594c44108..829db07281 100644 --- a/packages/core/src/modules/proofs/ProofsModule.ts +++ b/packages/core/src/modules/proofs/ProofsModule.ts @@ -1,523 +1,27 @@ -import type { DependencyManager } from '../../plugins' -import type { AutoAcceptProof } from './ProofAutoAcceptType' -import type { PresentationPreview, RequestPresentationMessage } from './messages' -import type { RequestedCredentials, RetrievedCredentials } from './models' -import type { ProofRequestOptions } from './models/ProofRequest' -import type { ProofRecord } from './repository/ProofRecord' +import type { DependencyManager, Module } from '../../plugins' +import type { ProofsModuleConfigOptions } from './ProofsModuleConfig' -import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' -import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' -import { InjectionSymbols } from '../../constants' -import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' -import { AriesFrameworkError } from '../../error' -import { Logger } from '../../logger' -import { inject, injectable, module } from '../../plugins' -import { ConnectionService } from '../connections/services/ConnectionService' -import { RoutingService } from '../routing/services/RoutingService' - -import { ProofResponseCoordinator } from './ProofResponseCoordinator' -import { PresentationProblemReportReason } from './errors' -import { - PresentationAckHandler, - PresentationHandler, - PresentationProblemReportHandler, - ProposePresentationHandler, - RequestPresentationHandler, -} from './handlers' -import { PresentationProblemReportMessage } from './messages/PresentationProblemReportMessage' -import { ProofRequest } from './models/ProofRequest' +import { ProofsApi } from './ProofsApi' +import { ProofsModuleConfig } from './ProofsModuleConfig' import { ProofRepository } from './repository' import { ProofService } from './services' -@module() -@injectable() -export class ProofsModule { - private proofService: ProofService - private connectionService: ConnectionService - private messageSender: MessageSender - private routingService: RoutingService - private agentContext: AgentContext - private proofResponseCoordinator: ProofResponseCoordinator - private logger: Logger - - public constructor( - dispatcher: Dispatcher, - proofService: ProofService, - connectionService: ConnectionService, - routingService: RoutingService, - agentContext: AgentContext, - messageSender: MessageSender, - proofResponseCoordinator: ProofResponseCoordinator, - @inject(InjectionSymbols.Logger) logger: Logger - ) { - this.proofService = proofService - this.connectionService = connectionService - this.messageSender = messageSender - this.routingService = routingService - this.agentContext = agentContext - this.proofResponseCoordinator = proofResponseCoordinator - this.logger = logger - this.registerHandlers(dispatcher) - } - - /** - * Initiate a new presentation exchange as prover by sending a presentation proposal message - * to the connection with the specified connection id. - * - * @param connectionId The connection to send the proof proposal to - * @param presentationProposal The presentation proposal to include in the message - * @param config Additional configuration to use for the proposal - * @returns Proof record associated with the sent proposal message - * - */ - public async proposeProof( - connectionId: string, - presentationProposal: PresentationPreview, - config?: { - comment?: string - autoAcceptProof?: AutoAcceptProof - parentThreadId?: string - } - ): Promise { - const connection = await this.connectionService.getById(this.agentContext, connectionId) - - const { message, proofRecord } = await this.proofService.createProposal( - this.agentContext, - connection, - presentationProposal, - config - ) - - const outbound = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outbound) - - return proofRecord - } - - /** - * Accept a presentation proposal as verifier (by sending a presentation request message) to the connection - * associated with the proof record. - * - * @param proofRecordId The id of the proof record for which to accept the proposal - * @param config Additional configuration to use for the request - * @returns Proof record associated with the presentation request - * - */ - public async acceptProposal( - proofRecordId: string, - config?: { - request?: { - name?: string - version?: string - nonce?: string - } - comment?: string - } - ): Promise { - const proofRecord = await this.proofService.getById(this.agentContext, proofRecordId) - - if (!proofRecord.connectionId) { - throw new AriesFrameworkError( - `No connectionId found for credential record '${proofRecord.id}'. Connection-less issuance does not support presentation proposal or negotiation.` - ) - } - - const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) - - const presentationProposal = proofRecord.proposalMessage?.presentationProposal - if (!presentationProposal) { - throw new AriesFrameworkError(`Proof record with id ${proofRecordId} is missing required presentation proposal`) - } - - const proofRequest = await this.proofService.createProofRequestFromProposal( - this.agentContext, - presentationProposal, - { - name: config?.request?.name ?? 'proof-request', - version: config?.request?.version ?? '1.0', - nonce: config?.request?.nonce, - } - ) - - const { message } = await this.proofService.createRequestAsResponse(this.agentContext, proofRecord, proofRequest, { - comment: config?.comment, - }) - - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return proofRecord - } - - /** - * Initiate a new presentation exchange as verifier by sending a presentation request message - * to the connection with the specified connection id - * - * @param connectionId The connection to send the proof request to - * @param proofRequestOptions Options to build the proof request - * @returns Proof record associated with the sent request message - * - */ - public async requestProof( - connectionId: string, - proofRequestOptions: CreateProofRequestOptions, - config?: ProofRequestConfig - ): Promise { - const connection = await this.connectionService.getById(this.agentContext, connectionId) - - const nonce = proofRequestOptions.nonce ?? (await this.proofService.generateProofRequestNonce(this.agentContext)) - - const proofRequest = new ProofRequest({ - name: proofRequestOptions.name ?? 'proof-request', - version: proofRequestOptions.name ?? '1.0', - nonce, - requestedAttributes: proofRequestOptions.requestedAttributes, - requestedPredicates: proofRequestOptions.requestedPredicates, - }) - - const { message, proofRecord } = await this.proofService.createRequest( - this.agentContext, - proofRequest, - connection, - config - ) - - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return proofRecord - } - - /** - * Initiate a new presentation exchange as verifier by creating a presentation request - * not bound to any connection. The request must be delivered out-of-band to the holder - * - * @param proofRequestOptions Options to build the proof request - * @returns The proof record and proof request message - * - */ - public async createOutOfBandRequest( - proofRequestOptions: CreateProofRequestOptions, - config?: ProofRequestConfig - ): Promise<{ - requestMessage: RequestPresentationMessage - proofRecord: ProofRecord - }> { - const nonce = proofRequestOptions.nonce ?? (await this.proofService.generateProofRequestNonce(this.agentContext)) - - const proofRequest = new ProofRequest({ - name: proofRequestOptions.name ?? 'proof-request', - version: proofRequestOptions.name ?? '1.0', - nonce, - requestedAttributes: proofRequestOptions.requestedAttributes, - requestedPredicates: proofRequestOptions.requestedPredicates, - }) - - const { message, proofRecord } = await this.proofService.createRequest( - this.agentContext, - proofRequest, - undefined, - config - ) - - // Create and set ~service decorator - const routing = await this.routingService.getRouting(this.agentContext) - message.service = new ServiceDecorator({ - serviceEndpoint: routing.endpoints[0], - recipientKeys: [routing.recipientKey.publicKeyBase58], - routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), - }) - - // Save ~service decorator to record (to remember our verkey) - proofRecord.requestMessage = message - await this.proofService.update(this.agentContext, proofRecord) - - return { proofRecord, requestMessage: message } - } - - /** - * Accept a presentation request as prover (by sending a presentation message) to the connection - * associated with the proof record. - * - * @param proofRecordId The id of the proof record for which to accept the request - * @param requestedCredentials The requested credentials object specifying which credentials to use for the proof - * @param config Additional configuration to use for the presentation - * @returns Proof record associated with the sent presentation message - * - */ - public async acceptRequest( - proofRecordId: string, - requestedCredentials: RequestedCredentials, - config?: { - comment?: string - } - ): Promise { - const record = await this.proofService.getById(this.agentContext, proofRecordId) - const { message, proofRecord } = await this.proofService.createPresentation( - this.agentContext, - record, - requestedCredentials, - config - ) - - // Use connection if present - if (proofRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) - - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return proofRecord - } - // Use ~service decorator otherwise - else if (proofRecord.requestMessage?.service) { - // Create ~service decorator - const routing = await this.routingService.getRouting(this.agentContext) - const ourService = new ServiceDecorator({ - serviceEndpoint: routing.endpoints[0], - recipientKeys: [routing.recipientKey.publicKeyBase58], - routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), - }) - - const recipientService = proofRecord.requestMessage.service - - // Set and save ~service decorator to record (to remember our verkey) - message.service = ourService - proofRecord.presentationMessage = message - await this.proofService.update(this.agentContext, proofRecord) - - await this.messageSender.sendMessageToService(this.agentContext, { - message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, - }) - - return proofRecord - } - // Cannot send message without connectionId or ~service decorator - else { - throw new AriesFrameworkError( - `Cannot accept presentation request without connectionId or ~service decorator on presentation request.` - ) - } - } - - /** - * Declines a proof request as holder - * @param proofRecordId the id of the proof request to be declined - * @returns proof record that was declined - */ - public async declineRequest(proofRecordId: string) { - const proofRecord = await this.proofService.getById(this.agentContext, proofRecordId) - await this.proofService.declineRequest(this.agentContext, proofRecord) - return proofRecord - } - - /** - * Accept a presentation as prover (by sending a presentation acknowledgement message) to the connection - * associated with the proof record. - * - * @param proofRecordId The id of the proof record for which to accept the presentation - * @returns Proof record associated with the sent presentation acknowledgement message - * - */ - public async acceptPresentation(proofRecordId: string): Promise { - const record = await this.proofService.getById(this.agentContext, proofRecordId) - const { message, proofRecord } = await this.proofService.createAck(this.agentContext, record) - - // Use connection if present - if (proofRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - } - // Use ~service decorator otherwise - else if (proofRecord.requestMessage?.service && proofRecord.presentationMessage?.service) { - const recipientService = proofRecord.presentationMessage?.service - const ourService = proofRecord.requestMessage.service - - await this.messageSender.sendMessageToService(this.agentContext, { - message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, - }) - } - - // Cannot send message without credentialId or ~service decorator - else { - throw new AriesFrameworkError( - `Cannot accept presentation without connectionId or ~service decorator on presentation message.` - ) - } - - return proofRecord - } - - /** - * Create a {@link RetrievedCredentials} object. Given input proof request and presentation proposal, - * use credentials in the wallet to build indy requested credentials object for input to proof creation. - * If restrictions allow, self attested attributes will be used. - * - * - * @param proofRecordId the id of the proof request to get the matching credentials for - * @param config optional configuration for credential selection process. Use `filterByPresentationPreview` (default `true`) to only include - * credentials that match the presentation preview from the presentation proposal (if available). +export class ProofsModule implements Module { + public readonly config: ProofsModuleConfig - * @returns RetrievedCredentials object - */ - public async getRequestedCredentialsForProofRequest( - proofRecordId: string, - config?: GetRequestedCredentialsConfig - ): Promise { - const proofRecord = await this.proofService.getById(this.agentContext, proofRecordId) - - const indyProofRequest = proofRecord.requestMessage?.indyProofRequest - const presentationPreview = config?.filterByPresentationPreview - ? proofRecord.proposalMessage?.presentationProposal - : undefined - - if (!indyProofRequest) { - throw new AriesFrameworkError( - 'Unable to get requested credentials for proof request. No proof request message was found or the proof request message does not contain an indy proof request.' - ) - } - - return this.proofService.getRequestedCredentialsForProofRequest(this.agentContext, indyProofRequest, { - presentationProposal: presentationPreview, - filterByNonRevocationRequirements: config?.filterByNonRevocationRequirements ?? true, - }) - } - - /** - * Takes a RetrievedCredentials object and auto selects credentials in a RequestedCredentials object - * - * Use the return value of this method as input to {@link ProofService.createPresentation} to - * automatically accept a received presentation request. - * - * @param retrievedCredentials The retrieved credentials object to get credentials from - * - * @returns RequestedCredentials - */ - public autoSelectCredentialsForProofRequest(retrievedCredentials: RetrievedCredentials): RequestedCredentials { - return this.proofService.autoSelectCredentialsForProofRequest(retrievedCredentials) - } - - /** - * Send problem report message for a proof record - * @param proofRecordId The id of the proof record for which to send problem report - * @param message message to send - * @returns proof record associated with the proof problem report message - */ - public async sendProblemReport(proofRecordId: string, message: string) { - const record = await this.proofService.getById(this.agentContext, proofRecordId) - if (!record.connectionId) { - throw new AriesFrameworkError(`No connectionId found for proof record '${record.id}'.`) - } - const connection = await this.connectionService.getById(this.agentContext, record.connectionId) - const presentationProblemReportMessage = new PresentationProblemReportMessage({ - description: { - en: message, - code: PresentationProblemReportReason.Abandoned, - }, - }) - presentationProblemReportMessage.setThread({ - threadId: record.threadId, - parentThreadId: record.parentThreadId, - }) - const outboundMessage = createOutboundMessage(connection, presentationProblemReportMessage) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return record - } - - /** - * Retrieve all proof records - * - * @returns List containing all proof records - */ - public getAll(): Promise { - return this.proofService.getAll(this.agentContext) - } - - /** - * Retrieve a proof record by id - * - * @param proofRecordId The proof record id - * @throws {RecordNotFoundError} If no record is found - * @throws {RecordDuplicateError} If multiple records are found - * @return The proof record - * - */ - public async getById(proofRecordId: string): Promise { - return this.proofService.getById(this.agentContext, proofRecordId) - } - - /** - * Retrieve a proof record by id - * - * @param proofRecordId The proof record id - * @return The proof record or null if not found - * - */ - public async findById(proofRecordId: string): Promise { - return this.proofService.findById(this.agentContext, proofRecordId) - } - - /** - * Delete a proof record by id - * - * @param proofId the proof record id - */ - public async deleteById(proofId: string) { - return this.proofService.deleteById(this.agentContext, proofId) - } - - /** - * Retrieve a proof record by connection id and thread id - * - * @param connectionId The connection id - * @param threadId The thread id - * @throws {RecordNotFoundError} If no record is found - * @throws {RecordDuplicateError} If multiple records are found - * @returns The proof record - */ - public async getByThreadAndConnectionId(threadId: string, connectionId?: string): Promise { - return this.proofService.getByThreadAndConnectionId(this.agentContext, threadId, connectionId) - } - - /** - * Retrieve proof records by connection id and parent thread id - * - * @param connectionId The connection id - * @param parentThreadId The parent thread id - * @returns List containing all proof records matching the given query - */ - public async getByParentThreadAndConnectionId(parentThreadId: string, connectionId?: string): Promise { - return this.proofService.getByParentThreadAndConnectionId(this.agentContext, parentThreadId, connectionId) - } - - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler( - new ProposePresentationHandler(this.proofService, this.proofResponseCoordinator, this.logger) - ) - dispatcher.registerHandler( - new RequestPresentationHandler(this.proofService, this.proofResponseCoordinator, this.routingService, this.logger) - ) - dispatcher.registerHandler(new PresentationHandler(this.proofService, this.proofResponseCoordinator, this.logger)) - dispatcher.registerHandler(new PresentationAckHandler(this.proofService)) - dispatcher.registerHandler(new PresentationProblemReportHandler(this.proofService)) + public constructor(config?: ProofsModuleConfigOptions) { + this.config = new ProofsModuleConfig(config) } /** * Registers the dependencies of the proofs module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(ProofsModule) + dependencyManager.registerContextScoped(ProofsApi) + + // Config + dependencyManager.registerInstance(ProofsModuleConfig, this.config) // Services dependencyManager.registerSingleton(ProofService) @@ -526,33 +30,3 @@ export class ProofsModule { dependencyManager.registerSingleton(ProofRepository) } } - -export type CreateProofRequestOptions = Partial< - Pick -> - -export interface ProofRequestConfig { - comment?: string - autoAcceptProof?: AutoAcceptProof - parentThreadId?: string -} - -export interface GetRequestedCredentialsConfig { - /** - * Whether to filter the retrieved credentials using the presentation preview. - * This configuration will only have effect if a presentation proposal message is available - * containing a presentation preview. - * - * @default false - */ - filterByPresentationPreview?: boolean - - /** - * Whether to filter the retrieved credentials using the non-revocation request in the proof request. - * This configuration will only have effect if the proof request requires proof on non-revocation of any kind. - * Default to true - * - * @default true - */ - filterByNonRevocationRequirements?: boolean -} diff --git a/packages/core/src/modules/proofs/ProofsModuleConfig.ts b/packages/core/src/modules/proofs/ProofsModuleConfig.ts new file mode 100644 index 0000000000..88fd470c0b --- /dev/null +++ b/packages/core/src/modules/proofs/ProofsModuleConfig.ts @@ -0,0 +1,27 @@ +import { AutoAcceptProof } from './ProofAutoAcceptType' + +/** + * ProofsModuleConfigOptions defines the interface for the options of the ProofsModuleConfig class. + * This can contain optional parameters that have default values in the config class itself. + */ +export interface ProofsModuleConfigOptions { + /** + * Whether to automatically accept proof messages. Applies to all present proof protocol versions. + * + * @default {@link AutoAcceptProof.Never} + */ + autoAcceptProofs?: AutoAcceptProof +} + +export class ProofsModuleConfig { + private options: ProofsModuleConfigOptions + + public constructor(options?: ProofsModuleConfigOptions) { + this.options = options ?? {} + } + + /** See {@link ProofsModuleConfigOptions.autoAcceptProofs} */ + public get autoAcceptProofs() { + return this.options.autoAcceptProofs ?? AutoAcceptProof.Never + } +} diff --git a/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts new file mode 100644 index 0000000000..98ab74e7bc --- /dev/null +++ b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts @@ -0,0 +1,23 @@ +import { DependencyManager } from '../../../plugins/DependencyManager' +import { ProofsApi } from '../ProofsApi' +import { ProofsModule } from '../ProofsModule' +import { ProofRepository } from '../repository' +import { ProofService } from '../services' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('ProofsModule', () => { + test('registers dependencies on the dependency manager', () => { + new ProofsModule().register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(ProofsApi) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(2) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(ProofService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(ProofRepository) + }) +}) diff --git a/packages/core/src/modules/proofs/handlers/PresentationHandler.ts b/packages/core/src/modules/proofs/handlers/PresentationHandler.ts index 991a3a550d..8f651a9562 100644 --- a/packages/core/src/modules/proofs/handlers/PresentationHandler.ts +++ b/packages/core/src/modules/proofs/handlers/PresentationHandler.ts @@ -28,9 +28,7 @@ export class PresentationHandler implements Handler { } private async createAck(record: ProofRecord, messageContext: HandlerInboundMessage) { - this.logger.info( - `Automatically sending acknowledgement with autoAccept on ${messageContext.agentContext.config.autoAcceptProofs}` - ) + this.logger.info(`Automatically sending acknowledgement with autoAccept`) const { message, proofRecord } = await this.proofService.createAck(messageContext.agentContext, record) diff --git a/packages/core/src/modules/proofs/handlers/ProposePresentationHandler.ts b/packages/core/src/modules/proofs/handlers/ProposePresentationHandler.ts index 6ab1879fdb..3e07ebfc60 100644 --- a/packages/core/src/modules/proofs/handlers/ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/handlers/ProposePresentationHandler.ts @@ -31,9 +31,7 @@ export class ProposePresentationHandler implements Handler { proofRecord: ProofRecord, messageContext: HandlerInboundMessage ) { - this.logger.info( - `Automatically sending request with autoAccept on ${messageContext.agentContext.config.autoAcceptProofs}` - ) + this.logger.info(`Automatically sending request with autoAccept`) if (!messageContext.connection) { this.logger.error('No connection on the messageContext') diff --git a/packages/core/src/modules/proofs/handlers/RequestPresentationHandler.ts b/packages/core/src/modules/proofs/handlers/RequestPresentationHandler.ts index 87b1445d94..e2839783c8 100644 --- a/packages/core/src/modules/proofs/handlers/RequestPresentationHandler.ts +++ b/packages/core/src/modules/proofs/handlers/RequestPresentationHandler.ts @@ -43,9 +43,7 @@ export class RequestPresentationHandler implements Handler { const indyProofRequest = record.requestMessage?.indyProofRequest const presentationProposal = record.proposalMessage?.presentationProposal - this.logger.info( - `Automatically sending presentation with autoAccept on ${messageContext.agentContext.config.autoAcceptProofs}` - ) + this.logger.info(`Automatically sending presentation with autoAccept`) if (!indyProofRequest) { this.logger.error('Proof request is undefined.') diff --git a/packages/core/src/modules/proofs/index.ts b/packages/core/src/modules/proofs/index.ts index a4e5d95714..44efac8eba 100644 --- a/packages/core/src/modules/proofs/index.ts +++ b/packages/core/src/modules/proofs/index.ts @@ -4,5 +4,6 @@ export * from './services' export * from './ProofState' export * from './repository' export * from './ProofEvents' -export * from './ProofsModule' +export * from './ProofsApi' export * from './ProofAutoAcceptType' +export * from './ProofsModule' diff --git a/packages/core/src/modules/question-answer/QuestionAnswerApi.ts b/packages/core/src/modules/question-answer/QuestionAnswerApi.ts new file mode 100644 index 0000000000..3b19628fb9 --- /dev/null +++ b/packages/core/src/modules/question-answer/QuestionAnswerApi.ts @@ -0,0 +1,105 @@ +import type { ValidResponse } from './models' + +import { AgentContext } from '../../agent' +import { Dispatcher } from '../../agent/Dispatcher' +import { MessageSender } from '../../agent/MessageSender' +import { createOutboundMessage } from '../../agent/helpers' +import { injectable } from '../../plugins' +import { ConnectionService } from '../connections' + +import { AnswerMessageHandler, QuestionMessageHandler } from './handlers' +import { QuestionAnswerService } from './services' + +@injectable() +export class QuestionAnswerApi { + private questionAnswerService: QuestionAnswerService + private messageSender: MessageSender + private connectionService: ConnectionService + private agentContext: AgentContext + + public constructor( + dispatcher: Dispatcher, + questionAnswerService: QuestionAnswerService, + messageSender: MessageSender, + connectionService: ConnectionService, + agentContext: AgentContext + ) { + this.questionAnswerService = questionAnswerService + this.messageSender = messageSender + this.connectionService = connectionService + this.agentContext = agentContext + this.registerHandlers(dispatcher) + } + + /** + * Create a question message with possible valid responses, then send message to the + * holder + * + * @param connectionId connection to send the question message to + * @param config config for creating question message + * @returns QuestionAnswer record + */ + public async sendQuestion( + connectionId: string, + config: { + question: string + validResponses: ValidResponse[] + detail?: string + } + ) { + const connection = await this.connectionService.getById(this.agentContext, connectionId) + connection.assertReady() + + const { questionMessage, questionAnswerRecord } = await this.questionAnswerService.createQuestion( + this.agentContext, + connectionId, + { + question: config.question, + validResponses: config.validResponses, + detail: config?.detail, + } + ) + const outboundMessage = createOutboundMessage(connection, questionMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return questionAnswerRecord + } + + /** + * Create an answer message as the holder and send it in response to a question message + * + * @param questionRecordId the id of the questionAnswer record + * @param response response included in the answer message + * @returns QuestionAnswer record + */ + public async sendAnswer(questionRecordId: string, response: string) { + const questionRecord = await this.questionAnswerService.getById(this.agentContext, questionRecordId) + + const { answerMessage, questionAnswerRecord } = await this.questionAnswerService.createAnswer( + this.agentContext, + questionRecord, + response + ) + + const connection = await this.connectionService.getById(this.agentContext, questionRecord.connectionId) + + const outboundMessage = createOutboundMessage(connection, answerMessage) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return questionAnswerRecord + } + + /** + * Get all QuestionAnswer records + * + * @returns list containing all QuestionAnswer records + */ + public getAll() { + return this.questionAnswerService.getAll(this.agentContext) + } + + private registerHandlers(dispatcher: Dispatcher) { + dispatcher.registerHandler(new QuestionMessageHandler(this.questionAnswerService)) + dispatcher.registerHandler(new AnswerMessageHandler(this.questionAnswerService)) + } +} diff --git a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts b/packages/core/src/modules/question-answer/QuestionAnswerModule.ts index bc17fe8ada..9fcea50803 100644 --- a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts +++ b/packages/core/src/modules/question-answer/QuestionAnswerModule.ts @@ -1,117 +1,16 @@ -import type { DependencyManager } from '../../plugins' -import type { ValidResponse } from './models' +import type { DependencyManager, Module } from '../../plugins' -import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' -import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' -import { injectable, module } from '../../plugins' -import { ConnectionService } from '../connections' - -import { AnswerMessageHandler, QuestionMessageHandler } from './handlers' +import { QuestionAnswerApi } from './QuestionAnswerApi' import { QuestionAnswerRepository } from './repository' import { QuestionAnswerService } from './services' -@module() -@injectable() -export class QuestionAnswerModule { - private questionAnswerService: QuestionAnswerService - private messageSender: MessageSender - private connectionService: ConnectionService - private agentContext: AgentContext - - public constructor( - dispatcher: Dispatcher, - questionAnswerService: QuestionAnswerService, - messageSender: MessageSender, - connectionService: ConnectionService, - agentContext: AgentContext - ) { - this.questionAnswerService = questionAnswerService - this.messageSender = messageSender - this.connectionService = connectionService - this.agentContext = agentContext - this.registerHandlers(dispatcher) - } - - /** - * Create a question message with possible valid responses, then send message to the - * holder - * - * @param connectionId connection to send the question message to - * @param config config for creating question message - * @returns QuestionAnswer record - */ - public async sendQuestion( - connectionId: string, - config: { - question: string - validResponses: ValidResponse[] - detail?: string - } - ) { - const connection = await this.connectionService.getById(this.agentContext, connectionId) - connection.assertReady() - - const { questionMessage, questionAnswerRecord } = await this.questionAnswerService.createQuestion( - this.agentContext, - connectionId, - { - question: config.question, - validResponses: config.validResponses, - detail: config?.detail, - } - ) - const outboundMessage = createOutboundMessage(connection, questionMessage) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return questionAnswerRecord - } - - /** - * Create an answer message as the holder and send it in response to a question message - * - * @param questionRecordId the id of the questionAnswer record - * @param response response included in the answer message - * @returns QuestionAnswer record - */ - public async sendAnswer(questionRecordId: string, response: string) { - const questionRecord = await this.questionAnswerService.getById(this.agentContext, questionRecordId) - - const { answerMessage, questionAnswerRecord } = await this.questionAnswerService.createAnswer( - this.agentContext, - questionRecord, - response - ) - - const connection = await this.connectionService.getById(this.agentContext, questionRecord.connectionId) - - const outboundMessage = createOutboundMessage(connection, answerMessage) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return questionAnswerRecord - } - - /** - * Get all QuestionAnswer records - * - * @returns list containing all QuestionAnswer records - */ - public getAll() { - return this.questionAnswerService.getAll(this.agentContext) - } - - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new QuestionMessageHandler(this.questionAnswerService)) - dispatcher.registerHandler(new AnswerMessageHandler(this.questionAnswerService)) - } - +export class QuestionAnswerModule implements Module { /** * Registers the dependencies of the question answer module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(QuestionAnswerModule) + dependencyManager.registerContextScoped(QuestionAnswerApi) // Services dependencyManager.registerSingleton(QuestionAnswerService) diff --git a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerModule.test.ts b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerModule.test.ts new file mode 100644 index 0000000000..a285e5898a --- /dev/null +++ b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerModule.test.ts @@ -0,0 +1,23 @@ +import { DependencyManager } from '../../../plugins/DependencyManager' +import { QuestionAnswerApi } from '../QuestionAnswerApi' +import { QuestionAnswerModule } from '../QuestionAnswerModule' +import { QuestionAnswerRepository } from '../repository/QuestionAnswerRepository' +import { QuestionAnswerService } from '../services' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('QuestionAnswerModule', () => { + test('registers dependencies on the dependency manager', () => { + new QuestionAnswerModule().register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(QuestionAnswerApi) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(2) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(QuestionAnswerService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(QuestionAnswerRepository) + }) +}) diff --git a/packages/core/src/modules/question-answer/index.ts b/packages/core/src/modules/question-answer/index.ts index 9c5a336fbb..e4b16be20f 100644 --- a/packages/core/src/modules/question-answer/index.ts +++ b/packages/core/src/modules/question-answer/index.ts @@ -3,5 +3,6 @@ export * from './models' export * from './services' export * from './repository' export * from './QuestionAnswerEvents' -export * from './QuestionAnswerModule' +export * from './QuestionAnswerApi' export * from './QuestionAnswerRole' +export * from './QuestionAnswerModule' diff --git a/packages/core/src/modules/routing/MediatorApi.ts b/packages/core/src/modules/routing/MediatorApi.ts new file mode 100644 index 0000000000..a75d0ec999 --- /dev/null +++ b/packages/core/src/modules/routing/MediatorApi.ts @@ -0,0 +1,91 @@ +import type { EncryptedMessage } from '../../types' +import type { MediationRecord } from './repository' + +import { AgentContext } from '../../agent' +import { Dispatcher } from '../../agent/Dispatcher' +import { EventEmitter } from '../../agent/EventEmitter' +import { MessageSender } from '../../agent/MessageSender' +import { createOutboundMessage } from '../../agent/helpers' +import { injectable } from '../../plugins' +import { ConnectionService } from '../connections/services' + +import { MediatorModuleConfig } from './MediatorModuleConfig' +import { ForwardHandler, KeylistUpdateHandler } from './handlers' +import { MediationRequestHandler } from './handlers/MediationRequestHandler' +import { MessagePickupService, V2MessagePickupService } from './protocol' +import { BatchHandler, BatchPickupHandler } from './protocol/pickup/v1/handlers' +import { MediatorService } from './services/MediatorService' + +@injectable() +export class MediatorApi { + public config: MediatorModuleConfig + + private mediatorService: MediatorService + private messagePickupService: MessagePickupService + private messageSender: MessageSender + private eventEmitter: EventEmitter + private agentContext: AgentContext + private connectionService: ConnectionService + + public constructor( + dispatcher: Dispatcher, + mediationService: MediatorService, + messagePickupService: MessagePickupService, + // Only imported so it is injected and handlers are registered + v2MessagePickupService: V2MessagePickupService, + messageSender: MessageSender, + eventEmitter: EventEmitter, + agentContext: AgentContext, + connectionService: ConnectionService, + config: MediatorModuleConfig + ) { + this.mediatorService = mediationService + this.messagePickupService = messagePickupService + this.messageSender = messageSender + this.eventEmitter = eventEmitter + this.connectionService = connectionService + this.agentContext = agentContext + this.config = config + this.registerHandlers(dispatcher) + } + + public async initialize() { + this.agentContext.config.logger.debug('Mediator routing record not loaded yet, retrieving from storage') + const routingRecord = await this.mediatorService.findMediatorRoutingRecord(this.agentContext) + + // If we don't have a routing record yet for this tenant, create it + if (!routingRecord) { + this.agentContext.config.logger.debug( + 'Mediator routing record does not exist yet, creating routing keys and record' + ) + await this.mediatorService.createMediatorRoutingRecord(this.agentContext) + } + } + + public async grantRequestedMediation(mediatorId: string): Promise { + const record = await this.mediatorService.getById(this.agentContext, mediatorId) + const connectionRecord = await this.connectionService.getById(this.agentContext, record.connectionId) + + const { message, mediationRecord } = await this.mediatorService.createGrantMediationMessage( + this.agentContext, + record + ) + const outboundMessage = createOutboundMessage(connectionRecord, message) + + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return mediationRecord + } + + public queueMessage(connectionId: string, message: EncryptedMessage) { + return this.messagePickupService.queueMessage(connectionId, message) + } + + private registerHandlers(dispatcher: Dispatcher) { + dispatcher.registerHandler(new KeylistUpdateHandler(this.mediatorService)) + dispatcher.registerHandler(new ForwardHandler(this.mediatorService, this.connectionService, this.messageSender)) + dispatcher.registerHandler(new BatchPickupHandler(this.messagePickupService)) + dispatcher.registerHandler(new BatchHandler(this.eventEmitter)) + dispatcher.registerHandler(new MediationRequestHandler(this.mediatorService, this.config)) + } +} diff --git a/packages/core/src/modules/routing/MediatorModule.ts b/packages/core/src/modules/routing/MediatorModule.ts index daf43e65d4..97aa521934 100644 --- a/packages/core/src/modules/routing/MediatorModule.ts +++ b/packages/core/src/modules/routing/MediatorModule.ts @@ -1,99 +1,36 @@ -import type { DependencyManager } from '../../plugins' -import type { EncryptedMessage } from '../../types' -import type { MediationRecord } from './repository' +import type { DependencyManager, Module } from '../../plugins' +import type { MediatorModuleConfigOptions } from './MediatorModuleConfig' -import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' -import { EventEmitter } from '../../agent/EventEmitter' -import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' -import { injectable, module } from '../../plugins' -import { ConnectionService } from '../connections/services' - -import { KeylistUpdateHandler, ForwardHandler } from './handlers' -import { MediationRequestHandler } from './handlers/MediationRequestHandler' +import { MediatorApi } from './MediatorApi' +import { MediatorModuleConfig } from './MediatorModuleConfig' import { MessagePickupService, V2MessagePickupService } from './protocol' -import { MediatorService } from './services/MediatorService' - -@module() -@injectable() -export class MediatorModule { - private mediatorService: MediatorService - private messagePickupService: MessagePickupService - private messageSender: MessageSender - public eventEmitter: EventEmitter - public agentContext: AgentContext - public connectionService: ConnectionService - - public constructor( - dispatcher: Dispatcher, - mediationService: MediatorService, - messagePickupService: MessagePickupService, - messageSender: MessageSender, - eventEmitter: EventEmitter, - agentContext: AgentContext, - connectionService: ConnectionService - ) { - this.mediatorService = mediationService - this.messagePickupService = messagePickupService - this.messageSender = messageSender - this.eventEmitter = eventEmitter - this.connectionService = connectionService - this.agentContext = agentContext - this.registerHandlers(dispatcher) - } - - public async initialize() { - this.agentContext.config.logger.debug('Mediator routing record not loaded yet, retrieving from storage') - const routingRecord = await this.mediatorService.findMediatorRoutingRecord(this.agentContext) - - // If we don't have a routing record yet for this tenant, create it - if (!routingRecord) { - this.agentContext.config.logger.debug( - 'Mediator routing record does not exist yet, creating routing keys and record' - ) - await this.mediatorService.createMediatorRoutingRecord(this.agentContext) - } - } +import { MediationRepository, MediatorRoutingRepository } from './repository' +import { MediatorService } from './services' - public async grantRequestedMediation(mediatorId: string): Promise { - const record = await this.mediatorService.getById(this.agentContext, mediatorId) - const connectionRecord = await this.connectionService.getById(this.agentContext, record.connectionId) +export class MediatorModule implements Module { + public readonly config: MediatorModuleConfig - const { message, mediationRecord } = await this.mediatorService.createGrantMediationMessage( - this.agentContext, - record - ) - const outboundMessage = createOutboundMessage(connectionRecord, message) - - await this.messageSender.sendMessage(this.agentContext, outboundMessage) - - return mediationRecord - } - - public queueMessage(connectionId: string, message: EncryptedMessage) { - return this.messagePickupService.queueMessage(connectionId, message) - } - - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new KeylistUpdateHandler(this.mediatorService)) - dispatcher.registerHandler(new ForwardHandler(this.mediatorService, this.connectionService, this.messageSender)) - dispatcher.registerHandler(new MediationRequestHandler(this.mediatorService)) + public constructor(config?: MediatorModuleConfigOptions) { + this.config = new MediatorModuleConfig(config) } /** - * Registers the dependencies of the mediator module on the dependency manager. + * Registers the dependencies of the question answer module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(MediatorModule) + dependencyManager.registerContextScoped(MediatorApi) + + // Config + dependencyManager.registerInstance(MediatorModuleConfig, this.config) // Services dependencyManager.registerSingleton(MediatorService) dependencyManager.registerSingleton(MessagePickupService) dependencyManager.registerSingleton(V2MessagePickupService) - // FIXME: Inject in constructor - dependencyManager.resolve(V2MessagePickupService) + // Repositories + dependencyManager.registerSingleton(MediationRepository) + dependencyManager.registerSingleton(MediatorRoutingRepository) } } diff --git a/packages/core/src/modules/routing/MediatorModuleConfig.ts b/packages/core/src/modules/routing/MediatorModuleConfig.ts new file mode 100644 index 0000000000..2e781fbc85 --- /dev/null +++ b/packages/core/src/modules/routing/MediatorModuleConfig.ts @@ -0,0 +1,25 @@ +/** + * MediatorModuleConfigOptions defines the interface for the options of the RecipientModuleConfig class. + * This can contain optional parameters that have default values in the config class itself. + */ +export interface MediatorModuleConfigOptions { + /** + * Whether to automatically accept and grant incoming mediation requests. + * + * @default false + */ + autoAcceptMediationRequests?: boolean +} + +export class MediatorModuleConfig { + private options: MediatorModuleConfigOptions + + public constructor(options?: MediatorModuleConfigOptions) { + this.options = options ?? {} + } + + /** See {@link RecipientModuleConfigOptions.autoAcceptMediationRequests} */ + public get autoAcceptMediationRequests() { + return this.options.autoAcceptMediationRequests ?? false + } +} diff --git a/packages/core/src/modules/routing/RecipientApi.ts b/packages/core/src/modules/routing/RecipientApi.ts new file mode 100644 index 0000000000..036ff2ed1d --- /dev/null +++ b/packages/core/src/modules/routing/RecipientApi.ts @@ -0,0 +1,405 @@ +import type { OutboundWebSocketClosedEvent } from '../../transport' +import type { OutboundMessage } from '../../types' +import type { ConnectionRecord } from '../connections' +import type { MediationStateChangedEvent } from './RoutingEvents' +import type { MediationRecord } from './repository' +import type { GetRoutingOptions } from './services/RoutingService' + +import { firstValueFrom, interval, ReplaySubject, Subject, timer } from 'rxjs' +import { delayWhen, filter, first, takeUntil, tap, throttleTime, timeout } from 'rxjs/operators' + +import { AgentContext } from '../../agent' +import { Dispatcher } from '../../agent/Dispatcher' +import { EventEmitter } from '../../agent/EventEmitter' +import { filterContextCorrelationId } from '../../agent/Events' +import { MessageSender } from '../../agent/MessageSender' +import { createOutboundMessage } from '../../agent/helpers' +import { InjectionSymbols } from '../../constants' +import { AriesFrameworkError } from '../../error' +import { Logger } from '../../logger' +import { inject, injectable } from '../../plugins' +import { TransportEventTypes } from '../../transport' +import { ConnectionService } from '../connections/services' +import { DidsApi } from '../dids' +import { DiscoverFeaturesApi } from '../discover-features' + +import { MediatorPickupStrategy } from './MediatorPickupStrategy' +import { RecipientModuleConfig } from './RecipientModuleConfig' +import { RoutingEventTypes } from './RoutingEvents' +import { KeylistUpdateResponseHandler } from './handlers/KeylistUpdateResponseHandler' +import { MediationDenyHandler } from './handlers/MediationDenyHandler' +import { MediationGrantHandler } from './handlers/MediationGrantHandler' +import { MediationState } from './models/MediationState' +import { StatusRequestMessage, BatchPickupMessage } from './protocol' +import { StatusHandler, MessageDeliveryHandler } from './protocol/pickup/v2/handlers' +import { MediationRepository } from './repository' +import { MediationRecipientService } from './services/MediationRecipientService' +import { RoutingService } from './services/RoutingService' + +@injectable() +export class RecipientApi { + public config: RecipientModuleConfig + + private mediationRecipientService: MediationRecipientService + private connectionService: ConnectionService + private dids: DidsApi + private messageSender: MessageSender + private eventEmitter: EventEmitter + private logger: Logger + private discoverFeaturesApi: DiscoverFeaturesApi + private mediationRepository: MediationRepository + private routingService: RoutingService + private agentContext: AgentContext + private stop$: Subject + + public constructor( + dispatcher: Dispatcher, + mediationRecipientService: MediationRecipientService, + connectionService: ConnectionService, + dids: DidsApi, + messageSender: MessageSender, + eventEmitter: EventEmitter, + discoverFeaturesApi: DiscoverFeaturesApi, + mediationRepository: MediationRepository, + routingService: RoutingService, + @inject(InjectionSymbols.Logger) logger: Logger, + agentContext: AgentContext, + @inject(InjectionSymbols.Stop$) stop$: Subject, + recipientModuleConfig: RecipientModuleConfig + ) { + this.connectionService = connectionService + this.dids = dids + this.mediationRecipientService = mediationRecipientService + this.messageSender = messageSender + this.eventEmitter = eventEmitter + this.logger = logger + this.discoverFeaturesApi = discoverFeaturesApi + this.mediationRepository = mediationRepository + this.routingService = routingService + this.agentContext = agentContext + this.stop$ = stop$ + this.config = recipientModuleConfig + this.registerHandlers(dispatcher) + } + + public async initialize() { + const { defaultMediatorId, clearDefaultMediator } = this.agentContext.config + + // Set default mediator by id + if (defaultMediatorId) { + const mediatorRecord = await this.mediationRecipientService.getById(this.agentContext, defaultMediatorId) + await this.mediationRecipientService.setDefaultMediator(this.agentContext, mediatorRecord) + } + // Clear the stored default mediator + else if (clearDefaultMediator) { + await this.mediationRecipientService.clearDefaultMediator(this.agentContext) + } + + // Poll for messages from mediator + const defaultMediator = await this.findDefaultMediator() + if (defaultMediator) { + await this.initiateMessagePickup(defaultMediator) + } + } + + private async sendMessage(outboundMessage: OutboundMessage, pickupStrategy?: MediatorPickupStrategy) { + const mediatorPickupStrategy = pickupStrategy ?? this.config.mediatorPickupStrategy + const transportPriority = + mediatorPickupStrategy === MediatorPickupStrategy.Implicit + ? { schemes: ['wss', 'ws'], restrictive: true } + : undefined + + await this.messageSender.sendMessage(this.agentContext, outboundMessage, { + transportPriority, + // TODO: add keepAlive: true to enforce through the public api + // we need to keep the socket alive. It already works this way, but would + // be good to make more explicit from the public facing API. + // This would also make it easier to change the internal API later on. + // keepAlive: true, + }) + } + + private async openMediationWebSocket(mediator: MediationRecord) { + const connection = await this.connectionService.getById(this.agentContext, mediator.connectionId) + const { message, connectionRecord } = await this.connectionService.createTrustPing(this.agentContext, connection, { + responseRequested: false, + }) + + const websocketSchemes = ['ws', 'wss'] + const didDocument = connectionRecord.theirDid && (await this.dids.resolveDidDocument(connectionRecord.theirDid)) + const services = didDocument && didDocument?.didCommServices + const hasWebSocketTransport = services && services.some((s) => websocketSchemes.includes(s.protocolScheme)) + + if (!hasWebSocketTransport) { + throw new AriesFrameworkError('Cannot open websocket to connection without websocket service endpoint') + } + + await this.messageSender.sendMessage(this.agentContext, createOutboundMessage(connectionRecord, message), { + transportPriority: { + schemes: websocketSchemes, + restrictive: true, + // TODO: add keepAlive: true to enforce through the public api + // we need to keep the socket alive. It already works this way, but would + // be good to make more explicit from the public facing API. + // This would also make it easier to change the internal API later on. + // keepAlive: true, + }, + }) + } + + private async openWebSocketAndPickUp(mediator: MediationRecord, pickupStrategy: MediatorPickupStrategy) { + let interval = 50 + + // FIXME: this won't work for tenant agents created by the tenants module as the agent context session + // could be closed. I'm not sure we want to support this as you probably don't want different tenants opening + // various websocket connections to mediators. However we should look at throwing an error or making sure + // it is not possible to use the mediation module with tenant agents. + + // Listens to Outbound websocket closed events and will reopen the websocket connection + // in a recursive back off strategy if it matches the following criteria: + // - Agent is not shutdown + // - Socket was for current mediator connection id + this.eventEmitter + .observable(TransportEventTypes.OutboundWebSocketClosedEvent) + .pipe( + // Stop when the agent shuts down + takeUntil(this.stop$), + filter((e) => e.payload.connectionId === mediator.connectionId), + // Make sure we're not reconnecting multiple times + throttleTime(interval), + // Increase the interval (recursive back-off) + tap(() => (interval *= 2)), + // Wait for interval time before reconnecting + delayWhen(() => timer(interval)) + ) + .subscribe(async () => { + this.logger.debug( + `Websocket connection to mediator with connectionId '${mediator.connectionId}' is closed, attempting to reconnect...` + ) + try { + if (pickupStrategy === MediatorPickupStrategy.PickUpV2) { + // Start Pickup v2 protocol to receive messages received while websocket offline + await this.sendStatusRequest({ mediatorId: mediator.id }) + } else { + await this.openMediationWebSocket(mediator) + } + } catch (error) { + this.logger.warn('Unable to re-open websocket connection to mediator', { error }) + } + }) + try { + if (pickupStrategy === MediatorPickupStrategy.Implicit) { + await this.openMediationWebSocket(mediator) + } + } catch (error) { + this.logger.warn('Unable to open websocket connection to mediator', { error }) + } + } + + public async initiateMessagePickup(mediator: MediationRecord) { + const { mediatorPollingInterval } = this.config + const mediatorPickupStrategy = await this.getPickupStrategyForMediator(mediator) + const mediatorConnection = await this.connectionService.getById(this.agentContext, mediator.connectionId) + + switch (mediatorPickupStrategy) { + case MediatorPickupStrategy.PickUpV2: + this.logger.info(`Starting pickup of messages from mediator '${mediator.id}'`) + await this.openWebSocketAndPickUp(mediator, mediatorPickupStrategy) + await this.sendStatusRequest({ mediatorId: mediator.id }) + break + case MediatorPickupStrategy.PickUpV1: { + // Explicit means polling every X seconds with batch message + this.logger.info(`Starting explicit (batch) pickup of messages from mediator '${mediator.id}'`) + const subscription = interval(mediatorPollingInterval) + .pipe(takeUntil(this.stop$)) + .subscribe(async () => { + await this.pickupMessages(mediatorConnection) + }) + return subscription + } + case MediatorPickupStrategy.Implicit: + // Implicit means sending ping once and keeping connection open. This requires a long-lived transport + // such as WebSockets to work + this.logger.info(`Starting implicit pickup of messages from mediator '${mediator.id}'`) + await this.openWebSocketAndPickUp(mediator, mediatorPickupStrategy) + break + default: + this.logger.info(`Skipping pickup of messages from mediator '${mediator.id}' due to pickup strategy none`) + } + } + + private async sendStatusRequest(config: { mediatorId: string; recipientKey?: string }) { + const mediationRecord = await this.mediationRecipientService.getById(this.agentContext, config.mediatorId) + + const statusRequestMessage = await this.mediationRecipientService.createStatusRequest(mediationRecord, { + recipientKey: config.recipientKey, + }) + + const mediatorConnection = await this.connectionService.getById(this.agentContext, mediationRecord.connectionId) + return this.messageSender.sendMessage( + this.agentContext, + createOutboundMessage(mediatorConnection, statusRequestMessage) + ) + } + + private async getPickupStrategyForMediator(mediator: MediationRecord) { + let mediatorPickupStrategy = mediator.pickupStrategy ?? this.config.mediatorPickupStrategy + + // If mediator pickup strategy is not configured we try to query if batch pickup + // is supported through the discover features protocol + if (!mediatorPickupStrategy) { + const isPickUpV2Supported = await this.discoverFeaturesApi.isProtocolSupported( + mediator.connectionId, + StatusRequestMessage + ) + if (isPickUpV2Supported) { + mediatorPickupStrategy = MediatorPickupStrategy.PickUpV2 + } else { + const isBatchPickupSupported = await this.discoverFeaturesApi.isProtocolSupported( + mediator.connectionId, + BatchPickupMessage + ) + + // Use explicit pickup strategy + mediatorPickupStrategy = isBatchPickupSupported + ? MediatorPickupStrategy.PickUpV1 + : MediatorPickupStrategy.Implicit + } + + // Store the result so it can be reused next time + mediator.pickupStrategy = mediatorPickupStrategy + await this.mediationRepository.update(this.agentContext, mediator) + } + + return mediatorPickupStrategy + } + + public async discoverMediation() { + return this.mediationRecipientService.discoverMediation(this.agentContext) + } + + public async pickupMessages(mediatorConnection: ConnectionRecord, pickupStrategy?: MediatorPickupStrategy) { + mediatorConnection.assertReady() + + const pickupMessage = + pickupStrategy === MediatorPickupStrategy.PickUpV2 + ? new StatusRequestMessage({}) + : new BatchPickupMessage({ batchSize: 10 }) + const outboundMessage = createOutboundMessage(mediatorConnection, pickupMessage) + await this.sendMessage(outboundMessage, pickupStrategy) + } + + public async setDefaultMediator(mediatorRecord: MediationRecord) { + return this.mediationRecipientService.setDefaultMediator(this.agentContext, mediatorRecord) + } + + public async requestMediation(connection: ConnectionRecord): Promise { + const { mediationRecord, message } = await this.mediationRecipientService.createRequest( + this.agentContext, + connection + ) + const outboundMessage = createOutboundMessage(connection, message) + + await this.sendMessage(outboundMessage) + return mediationRecord + } + + public async notifyKeylistUpdate(connection: ConnectionRecord, verkey: string) { + const message = this.mediationRecipientService.createKeylistUpdateMessage(verkey) + const outboundMessage = createOutboundMessage(connection, message) + await this.sendMessage(outboundMessage) + } + + public async findByConnectionId(connectionId: string) { + return await this.mediationRecipientService.findByConnectionId(this.agentContext, connectionId) + } + + public async getMediators() { + return await this.mediationRecipientService.getMediators(this.agentContext) + } + + public async findDefaultMediator(): Promise { + return this.mediationRecipientService.findDefaultMediator(this.agentContext) + } + + public async findDefaultMediatorConnection(): Promise { + const mediatorRecord = await this.findDefaultMediator() + + if (mediatorRecord) { + return this.connectionService.getById(this.agentContext, mediatorRecord.connectionId) + } + + return null + } + + public async requestAndAwaitGrant(connection: ConnectionRecord, timeoutMs = 10000): Promise { + const { mediationRecord, message } = await this.mediationRecipientService.createRequest( + this.agentContext, + connection + ) + + // Create observable for event + const observable = this.eventEmitter.observable(RoutingEventTypes.MediationStateChanged) + const subject = new ReplaySubject(1) + + // Apply required filters to observable stream subscribe to replay subject + observable + .pipe( + filterContextCorrelationId(this.agentContext.contextCorrelationId), + // Only take event for current mediation record + filter((event) => event.payload.mediationRecord.id === mediationRecord.id), + // Only take event for previous state requested, current state granted + filter((event) => event.payload.previousState === MediationState.Requested), + filter((event) => event.payload.mediationRecord.state === MediationState.Granted), + // Only wait for first event that matches the criteria + first(), + // Do not wait for longer than specified timeout + timeout(timeoutMs) + ) + .subscribe(subject) + + // Send mediation request message + const outboundMessage = createOutboundMessage(connection, message) + await this.sendMessage(outboundMessage) + + const event = await firstValueFrom(subject) + return event.payload.mediationRecord + } + + /** + * Requests mediation for a given connection and sets that as default mediator. + * + * @param connection connection record which will be used for mediation + * @returns mediation record + */ + public async provision(connection: ConnectionRecord) { + this.logger.debug('Connection completed, requesting mediation') + + let mediation = await this.findByConnectionId(connection.id) + if (!mediation) { + this.logger.info(`Requesting mediation for connection ${connection.id}`) + mediation = await this.requestAndAwaitGrant(connection, 60000) // TODO: put timeout as a config parameter + this.logger.debug('Mediation granted, setting as default mediator') + await this.setDefaultMediator(mediation) + this.logger.debug('Default mediator set') + } else { + this.logger.debug(`Mediator invitation has already been ${mediation.isReady ? 'granted' : 'requested'}`) + } + + return mediation + } + + public async getRouting(options: GetRoutingOptions) { + return this.routingService.getRouting(this.agentContext, options) + } + + // Register handlers for the several messages for the mediator. + private registerHandlers(dispatcher: Dispatcher) { + dispatcher.registerHandler(new KeylistUpdateResponseHandler(this.mediationRecipientService)) + dispatcher.registerHandler(new MediationGrantHandler(this.mediationRecipientService)) + dispatcher.registerHandler(new MediationDenyHandler(this.mediationRecipientService)) + dispatcher.registerHandler(new StatusHandler(this.mediationRecipientService)) + dispatcher.registerHandler(new MessageDeliveryHandler(this.mediationRecipientService)) + //dispatcher.registerHandler(new KeylistListHandler(this.mediationRecipientService)) // TODO: write this + } +} diff --git a/packages/core/src/modules/routing/RecipientModule.ts b/packages/core/src/modules/routing/RecipientModule.ts index 76115a165c..8233b2aacf 100644 --- a/packages/core/src/modules/routing/RecipientModule.ts +++ b/packages/core/src/modules/routing/RecipientModule.ts @@ -1,408 +1,27 @@ -import type { DependencyManager } from '../../plugins' -import type { OutboundWebSocketClosedEvent } from '../../transport' -import type { OutboundMessage } from '../../types' -import type { ConnectionRecord } from '../connections' -import type { MediationStateChangedEvent } from './RoutingEvents' -import type { MediationRecord } from './index' -import type { GetRoutingOptions } from './services/RoutingService' +import type { DependencyManager, Module } from '../../plugins' +import type { RecipientModuleConfigOptions } from './RecipientModuleConfig' -import { firstValueFrom, interval, ReplaySubject, Subject, timer } from 'rxjs' -import { delayWhen, filter, first, takeUntil, tap, throttleTime, timeout } from 'rxjs/operators' +import { RecipientApi } from './RecipientApi' +import { RecipientModuleConfig } from './RecipientModuleConfig' +import { MediationRepository } from './repository' +import { MediationRecipientService, RoutingService } from './services' -import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' -import { EventEmitter } from '../../agent/EventEmitter' -import { filterContextCorrelationId } from '../../agent/Events' -import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' -import { InjectionSymbols } from '../../constants' -import { AriesFrameworkError } from '../../error' -import { Logger } from '../../logger' -import { inject, injectable, module } from '../../plugins' -import { TransportEventTypes } from '../../transport' -import { ConnectionService } from '../connections/services' -import { DidsModule } from '../dids' -import { DiscoverFeaturesModule } from '../discover-features' +export class RecipientModule implements Module { + public readonly config: RecipientModuleConfig -import { MediatorPickupStrategy } from './MediatorPickupStrategy' -import { RoutingEventTypes } from './RoutingEvents' -import { KeylistUpdateResponseHandler } from './handlers/KeylistUpdateResponseHandler' -import { MediationDenyHandler } from './handlers/MediationDenyHandler' -import { MediationGrantHandler } from './handlers/MediationGrantHandler' -import { MediationState } from './models/MediationState' -import { BatchPickupMessage, StatusRequestMessage } from './protocol' -import { MediationRepository, MediatorRoutingRepository } from './repository' -import { MediationRecipientService } from './services/MediationRecipientService' -import { RoutingService } from './services/RoutingService' - -@module() -@injectable() -export class RecipientModule { - private mediationRecipientService: MediationRecipientService - private connectionService: ConnectionService - private dids: DidsModule - private messageSender: MessageSender - private eventEmitter: EventEmitter - private logger: Logger - private discoverFeaturesModule: DiscoverFeaturesModule - private mediationRepository: MediationRepository - private routingService: RoutingService - private agentContext: AgentContext - private stop$: Subject - - public constructor( - dispatcher: Dispatcher, - mediationRecipientService: MediationRecipientService, - connectionService: ConnectionService, - dids: DidsModule, - messageSender: MessageSender, - eventEmitter: EventEmitter, - discoverFeaturesModule: DiscoverFeaturesModule, - mediationRepository: MediationRepository, - routingService: RoutingService, - @inject(InjectionSymbols.Logger) logger: Logger, - agentContext: AgentContext, - @inject(InjectionSymbols.Stop$) stop$: Subject - ) { - this.connectionService = connectionService - this.dids = dids - this.mediationRecipientService = mediationRecipientService - this.messageSender = messageSender - this.eventEmitter = eventEmitter - this.logger = logger - this.discoverFeaturesModule = discoverFeaturesModule - this.mediationRepository = mediationRepository - this.routingService = routingService - this.agentContext = agentContext - this.stop$ = stop$ - this.registerHandlers(dispatcher) - } - - public async initialize() { - const { defaultMediatorId, clearDefaultMediator } = this.agentContext.config - - // Set default mediator by id - if (defaultMediatorId) { - const mediatorRecord = await this.mediationRecipientService.getById(this.agentContext, defaultMediatorId) - await this.mediationRecipientService.setDefaultMediator(this.agentContext, mediatorRecord) - } - // Clear the stored default mediator - else if (clearDefaultMediator) { - await this.mediationRecipientService.clearDefaultMediator(this.agentContext) - } - - // Poll for messages from mediator - const defaultMediator = await this.findDefaultMediator() - if (defaultMediator) { - await this.initiateMessagePickup(defaultMediator) - } - } - - private async sendMessage(outboundMessage: OutboundMessage, pickupStrategy?: MediatorPickupStrategy) { - const mediatorPickupStrategy = pickupStrategy ?? this.agentContext.config.mediatorPickupStrategy - const transportPriority = - mediatorPickupStrategy === MediatorPickupStrategy.Implicit - ? { schemes: ['wss', 'ws'], restrictive: true } - : undefined - - await this.messageSender.sendMessage(this.agentContext, outboundMessage, { - transportPriority, - // TODO: add keepAlive: true to enforce through the public api - // we need to keep the socket alive. It already works this way, but would - // be good to make more explicit from the public facing API. - // This would also make it easier to change the internal API later on. - // keepAlive: true, - }) - } - - private async openMediationWebSocket(mediator: MediationRecord) { - const connection = await this.connectionService.getById(this.agentContext, mediator.connectionId) - const { message, connectionRecord } = await this.connectionService.createTrustPing(this.agentContext, connection, { - responseRequested: false, - }) - - const websocketSchemes = ['ws', 'wss'] - const didDocument = connectionRecord.theirDid && (await this.dids.resolveDidDocument(connectionRecord.theirDid)) - const services = didDocument && didDocument?.didCommServices - const hasWebSocketTransport = services && services.some((s) => websocketSchemes.includes(s.protocolScheme)) - - if (!hasWebSocketTransport) { - throw new AriesFrameworkError('Cannot open websocket to connection without websocket service endpoint') - } - - await this.messageSender.sendMessage(this.agentContext, createOutboundMessage(connectionRecord, message), { - transportPriority: { - schemes: websocketSchemes, - restrictive: true, - // TODO: add keepAlive: true to enforce through the public api - // we need to keep the socket alive. It already works this way, but would - // be good to make more explicit from the public facing API. - // This would also make it easier to change the internal API later on. - // keepAlive: true, - }, - }) - } - - private async openWebSocketAndPickUp(mediator: MediationRecord, pickupStrategy: MediatorPickupStrategy) { - let interval = 50 - - // FIXME: this won't work for tenant agents created by the tenants module as the agent context session - // could be closed. I'm not sure we want to support this as you probably don't want different tenants opening - // various websocket connections to mediators. However we should look at throwing an error or making sure - // it is not possible to use the mediation module with tenant agents. - - // Listens to Outbound websocket closed events and will reopen the websocket connection - // in a recursive back off strategy if it matches the following criteria: - // - Agent is not shutdown - // - Socket was for current mediator connection id - this.eventEmitter - .observable(TransportEventTypes.OutboundWebSocketClosedEvent) - .pipe( - // Stop when the agent shuts down - takeUntil(this.stop$), - filter((e) => e.payload.connectionId === mediator.connectionId), - // Make sure we're not reconnecting multiple times - throttleTime(interval), - // Increase the interval (recursive back-off) - tap(() => (interval *= 2)), - // Wait for interval time before reconnecting - delayWhen(() => timer(interval)) - ) - .subscribe(async () => { - this.logger.debug( - `Websocket connection to mediator with connectionId '${mediator.connectionId}' is closed, attempting to reconnect...` - ) - try { - if (pickupStrategy === MediatorPickupStrategy.PickUpV2) { - // Start Pickup v2 protocol to receive messages received while websocket offline - await this.sendStatusRequest({ mediatorId: mediator.id }) - } else { - await this.openMediationWebSocket(mediator) - } - } catch (error) { - this.logger.warn('Unable to re-open websocket connection to mediator', { error }) - } - }) - try { - if (pickupStrategy === MediatorPickupStrategy.Implicit) { - await this.openMediationWebSocket(mediator) - } - } catch (error) { - this.logger.warn('Unable to open websocket connection to mediator', { error }) - } - } - - public async initiateMessagePickup(mediator: MediationRecord) { - const { mediatorPollingInterval } = this.agentContext.config - const mediatorPickupStrategy = await this.getPickupStrategyForMediator(mediator) - const mediatorConnection = await this.connectionService.getById(this.agentContext, mediator.connectionId) - - switch (mediatorPickupStrategy) { - case MediatorPickupStrategy.PickUpV2: - this.logger.info(`Starting pickup of messages from mediator '${mediator.id}'`) - await this.openWebSocketAndPickUp(mediator, mediatorPickupStrategy) - await this.sendStatusRequest({ mediatorId: mediator.id }) - break - case MediatorPickupStrategy.PickUpV1: { - // Explicit means polling every X seconds with batch message - this.logger.info(`Starting explicit (batch) pickup of messages from mediator '${mediator.id}'`) - const subscription = interval(mediatorPollingInterval) - .pipe(takeUntil(this.stop$)) - .subscribe(async () => { - await this.pickupMessages(mediatorConnection) - }) - return subscription - } - case MediatorPickupStrategy.Implicit: - // Implicit means sending ping once and keeping connection open. This requires a long-lived transport - // such as WebSockets to work - this.logger.info(`Starting implicit pickup of messages from mediator '${mediator.id}'`) - await this.openWebSocketAndPickUp(mediator, mediatorPickupStrategy) - break - default: - this.logger.info(`Skipping pickup of messages from mediator '${mediator.id}' due to pickup strategy none`) - } - } - - private async sendStatusRequest(config: { mediatorId: string; recipientKey?: string }) { - const mediationRecord = await this.mediationRecipientService.getById(this.agentContext, config.mediatorId) - - const statusRequestMessage = await this.mediationRecipientService.createStatusRequest(mediationRecord, { - recipientKey: config.recipientKey, - }) - - const mediatorConnection = await this.connectionService.getById(this.agentContext, mediationRecord.connectionId) - return this.messageSender.sendMessage( - this.agentContext, - createOutboundMessage(mediatorConnection, statusRequestMessage) - ) - } - - private async getPickupStrategyForMediator(mediator: MediationRecord) { - let mediatorPickupStrategy = mediator.pickupStrategy ?? this.agentContext.config.mediatorPickupStrategy - - // If mediator pickup strategy is not configured we try to query if batch pickup - // is supported through the discover features protocol - if (!mediatorPickupStrategy) { - const isPickUpV2Supported = await this.discoverFeaturesModule.isProtocolSupported( - mediator.connectionId, - StatusRequestMessage - ) - if (isPickUpV2Supported) { - mediatorPickupStrategy = MediatorPickupStrategy.PickUpV2 - } else { - const isBatchPickupSupported = await this.discoverFeaturesModule.isProtocolSupported( - mediator.connectionId, - BatchPickupMessage - ) - - // Use explicit pickup strategy - mediatorPickupStrategy = isBatchPickupSupported - ? MediatorPickupStrategy.PickUpV1 - : MediatorPickupStrategy.Implicit - } - - // Store the result so it can be reused next time - mediator.pickupStrategy = mediatorPickupStrategy - await this.mediationRepository.update(this.agentContext, mediator) - } - - return mediatorPickupStrategy - } - - public async discoverMediation() { - return this.mediationRecipientService.discoverMediation(this.agentContext) - } - - public async pickupMessages(mediatorConnection: ConnectionRecord, pickupStrategy?: MediatorPickupStrategy) { - mediatorConnection.assertReady() - - const pickupMessage = - pickupStrategy === MediatorPickupStrategy.PickUpV2 - ? new StatusRequestMessage({}) - : new BatchPickupMessage({ batchSize: 10 }) - const outboundMessage = createOutboundMessage(mediatorConnection, pickupMessage) - await this.sendMessage(outboundMessage, pickupStrategy) - } - - public async setDefaultMediator(mediatorRecord: MediationRecord) { - return this.mediationRecipientService.setDefaultMediator(this.agentContext, mediatorRecord) - } - - public async requestMediation(connection: ConnectionRecord): Promise { - const { mediationRecord, message } = await this.mediationRecipientService.createRequest( - this.agentContext, - connection - ) - const outboundMessage = createOutboundMessage(connection, message) - - await this.sendMessage(outboundMessage) - return mediationRecord - } - - public async notifyKeylistUpdate(connection: ConnectionRecord, verkey: string) { - const message = this.mediationRecipientService.createKeylistUpdateMessage(verkey) - const outboundMessage = createOutboundMessage(connection, message) - await this.sendMessage(outboundMessage) - } - - public async findByConnectionId(connectionId: string) { - return await this.mediationRecipientService.findByConnectionId(this.agentContext, connectionId) - } - - public async getMediators() { - return await this.mediationRecipientService.getMediators(this.agentContext) - } - - public async findDefaultMediator(): Promise { - return this.mediationRecipientService.findDefaultMediator(this.agentContext) - } - - public async findDefaultMediatorConnection(): Promise { - const mediatorRecord = await this.findDefaultMediator() - - if (mediatorRecord) { - return this.connectionService.getById(this.agentContext, mediatorRecord.connectionId) - } - - return null - } - - public async requestAndAwaitGrant(connection: ConnectionRecord, timeoutMs = 10000): Promise { - const { mediationRecord, message } = await this.mediationRecipientService.createRequest( - this.agentContext, - connection - ) - - // Create observable for event - const observable = this.eventEmitter.observable(RoutingEventTypes.MediationStateChanged) - const subject = new ReplaySubject(1) - - // Apply required filters to observable stream subscribe to replay subject - observable - .pipe( - filterContextCorrelationId(this.agentContext.contextCorrelationId), - // Only take event for current mediation record - filter((event) => event.payload.mediationRecord.id === mediationRecord.id), - // Only take event for previous state requested, current state granted - filter((event) => event.payload.previousState === MediationState.Requested), - filter((event) => event.payload.mediationRecord.state === MediationState.Granted), - // Only wait for first event that matches the criteria - first(), - // Do not wait for longer than specified timeout - timeout(timeoutMs) - ) - .subscribe(subject) - - // Send mediation request message - const outboundMessage = createOutboundMessage(connection, message) - await this.sendMessage(outboundMessage) - - const event = await firstValueFrom(subject) - return event.payload.mediationRecord - } - - /** - * Requests mediation for a given connection and sets that as default mediator. - * - * @param connection connection record which will be used for mediation - * @returns mediation record - */ - public async provision(connection: ConnectionRecord) { - this.logger.debug('Connection completed, requesting mediation') - - let mediation = await this.findByConnectionId(connection.id) - if (!mediation) { - this.logger.info(`Requesting mediation for connection ${connection.id}`) - mediation = await this.requestAndAwaitGrant(connection, 60000) // TODO: put timeout as a config parameter - this.logger.debug('Mediation granted, setting as default mediator') - await this.setDefaultMediator(mediation) - this.logger.debug('Default mediator set') - } else { - this.logger.debug(`Mediator invitation has already been ${mediation.isReady ? 'granted' : 'requested'}`) - } - - return mediation - } - - public async getRouting(options: GetRoutingOptions) { - return this.routingService.getRouting(this.agentContext, options) - } - - // Register handlers for the several messages for the mediator. - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new KeylistUpdateResponseHandler(this.mediationRecipientService)) - dispatcher.registerHandler(new MediationGrantHandler(this.mediationRecipientService)) - dispatcher.registerHandler(new MediationDenyHandler(this.mediationRecipientService)) - //dispatcher.registerHandler(new KeylistListHandler(this.mediationRecipientService)) // TODO: write this + public constructor(config?: RecipientModuleConfigOptions) { + this.config = new RecipientModuleConfig(config) } /** * Registers the dependencies of the mediator recipient module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(RecipientModule) + dependencyManager.registerContextScoped(RecipientApi) + + // Config + dependencyManager.registerInstance(RecipientModuleConfig, this.config) // Services dependencyManager.registerSingleton(MediationRecipientService) @@ -410,6 +29,5 @@ export class RecipientModule { // Repositories dependencyManager.registerSingleton(MediationRepository) - dependencyManager.registerSingleton(MediatorRoutingRepository) } } diff --git a/packages/core/src/modules/routing/RecipientModuleConfig.ts b/packages/core/src/modules/routing/RecipientModuleConfig.ts new file mode 100644 index 0000000000..d8679c4fad --- /dev/null +++ b/packages/core/src/modules/routing/RecipientModuleConfig.ts @@ -0,0 +1,74 @@ +import type { MediatorPickupStrategy } from './MediatorPickupStrategy' + +/** + * RecipientModuleConfigOptions defines the interface for the options of the RecipientModuleConfig class. + * This can contain optional parameters that have default values in the config class itself. + */ +export interface RecipientModuleConfigOptions { + /** + * Strategy to use for picking up messages from the mediator. If no strategy is provided, the agent will use the discover + * features protocol to determine the best strategy. + * + * + * - `MediatorPickupStrategy.PickUpV1` - explicitly pick up messages from the mediator according to [RFC 0212 Pickup Protocol](https://github.com/hyperledger/aries-rfcs/blob/main/features/0212-pickup/README.md) + * - `MediatorPickupStrategy.PickUpV2` - pick up messages from the mediator according to [RFC 0685 Pickup V2 Protocol](https://github.com/hyperledger/aries-rfcs/tree/main/features/0685-pickup-v2/README.md). + * - `MediatorPickupStrategy.Implicit` - Open a WebSocket with the mediator to implicitly receive messages. (currently used by Aries Cloud Agent Python) + * - `MediatorPickupStrategy.None` - Do not retrieve messages from the mediator. + * + * @default undefined + */ + mediatorPickupStrategy?: MediatorPickupStrategy + + /** + * Interval in milliseconds between picking up message from the mediator. This is only applicable when the pickup protocol v1 + * is used. + * + * @default 5000 + */ + mediatorPollingInterval?: number + + /** + * Maximum number of messages to retrieve from the mediator in a single batch. This is only applicable when the pickup protocol v2 + * is used. + * + * @todo integrate with pickup protocol v1 + * @default 10 + */ + maximumMessagePickup?: number + + /** + * Invitation url for connection to a mediator. If provided, a connection to the mediator will be made, and the mediator will be set as default. + * This is meant as the simplest form of connecting to a mediator, if more control is desired the api should be used. + * + * Supports both RFC 0434 Out Of Band v1 and RFC 0160 Connections v1 invitations. + */ + mediatorInvitationUrl?: string +} + +export class RecipientModuleConfig { + private options: RecipientModuleConfigOptions + + public constructor(options?: RecipientModuleConfigOptions) { + this.options = options ?? {} + } + + /** See {@link RecipientModuleConfigOptions.mediatorPollingInterval} */ + public get mediatorPollingInterval() { + return this.options.mediatorPollingInterval ?? 5000 + } + + /** See {@link RecipientModuleConfigOptions.mediatorPickupStrategy} */ + public get mediatorPickupStrategy() { + return this.options.mediatorPickupStrategy + } + + /** See {@link RecipientModuleConfigOptions.maximumMessagePickup} */ + public get maximumMessagePickup() { + return this.options.maximumMessagePickup ?? 10 + } + + /** See {@link RecipientModuleConfigOptions.mediatorInvitationUrl} */ + public get mediatorInvitationUrl() { + return this.options.mediatorInvitationUrl + } +} diff --git a/packages/core/src/modules/routing/__tests__/MediatorModule.test.ts b/packages/core/src/modules/routing/__tests__/MediatorModule.test.ts new file mode 100644 index 0000000000..096e83cfad --- /dev/null +++ b/packages/core/src/modules/routing/__tests__/MediatorModule.test.ts @@ -0,0 +1,27 @@ +import { DependencyManager } from '../../../plugins/DependencyManager' +import { MediatorApi } from '../MediatorApi' +import { MediatorModule } from '../MediatorModule' +import { MessagePickupService, V2MessagePickupService } from '../protocol' +import { MediationRepository, MediatorRoutingRepository } from '../repository' +import { MediatorService } from '../services' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('MediatorModule', () => { + test('registers dependencies on the dependency manager', () => { + new MediatorModule().register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(MediatorApi) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(5) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(MediatorService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(MessagePickupService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V2MessagePickupService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(MediationRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(MediatorRoutingRepository) + }) +}) diff --git a/packages/core/src/modules/routing/__tests__/RecipientModule.test.ts b/packages/core/src/modules/routing/__tests__/RecipientModule.test.ts new file mode 100644 index 0000000000..916840344d --- /dev/null +++ b/packages/core/src/modules/routing/__tests__/RecipientModule.test.ts @@ -0,0 +1,24 @@ +import { DependencyManager } from '../../../plugins/DependencyManager' +import { RecipientApi } from '../RecipientApi' +import { RecipientModule } from '../RecipientModule' +import { MediationRepository } from '../repository' +import { MediationRecipientService, RoutingService } from '../services' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('RecipientModule', () => { + test('registers dependencies on the dependency manager', () => { + new RecipientModule().register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(RecipientApi) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(3) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(MediationRecipientService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(RoutingService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(MediationRepository) + }) +}) diff --git a/packages/core/src/modules/routing/handlers/MediationRequestHandler.ts b/packages/core/src/modules/routing/handlers/MediationRequestHandler.ts index 2cc2944668..2c6f7be994 100644 --- a/packages/core/src/modules/routing/handlers/MediationRequestHandler.ts +++ b/packages/core/src/modules/routing/handlers/MediationRequestHandler.ts @@ -1,4 +1,5 @@ import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MediatorModuleConfig } from '../MediatorModuleConfig' import type { MediatorService } from '../services/MediatorService' import { createOutboundMessage } from '../../../agent/helpers' @@ -6,10 +7,12 @@ import { MediationRequestMessage } from '../messages/MediationRequestMessage' export class MediationRequestHandler implements Handler { private mediatorService: MediatorService + private mediatorModuleConfig: MediatorModuleConfig public supportedMessages = [MediationRequestMessage] - public constructor(mediatorService: MediatorService) { + public constructor(mediatorService: MediatorService, mediatorModuleConfig: MediatorModuleConfig) { this.mediatorService = mediatorService + this.mediatorModuleConfig = mediatorModuleConfig } public async handle(messageContext: HandlerInboundMessage) { @@ -17,7 +20,7 @@ export class MediationRequestHandler implements Handler { const mediationRecord = await this.mediatorService.processMediationRequest(messageContext) - if (messageContext.agentContext.config.autoAcceptMediationRequests) { + if (this.mediatorModuleConfig.autoAcceptMediationRequests) { const { message } = await this.mediatorService.createGrantMediationMessage( messageContext.agentContext, mediationRecord diff --git a/packages/core/src/modules/routing/index.ts b/packages/core/src/modules/routing/index.ts index f3bf782a20..6032d26ecd 100644 --- a/packages/core/src/modules/routing/index.ts +++ b/packages/core/src/modules/routing/index.ts @@ -4,6 +4,8 @@ export * from './protocol' export * from './repository' export * from './models' export * from './RoutingEvents' +export * from './MediatorApi' +export * from './RecipientApi' +export * from './MediatorPickupStrategy' export * from './MediatorModule' export * from './RecipientModule' -export * from './MediatorPickupStrategy' diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index 2258afe845..ab90eeba66 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -24,6 +24,7 @@ import { JsonTransformer } from '../../../utils' import { ConnectionService } from '../../connections/services/ConnectionService' import { didKeyToVerkey } from '../../dids/helpers' import { ProblemReportError } from '../../problem-reports' +import { RecipientModuleConfig } from '../RecipientModuleConfig' import { RoutingEventTypes } from '../RoutingEvents' import { RoutingProblemReportReason } from '../error' import { KeylistUpdateAction, MediationRequestMessage } from '../messages' @@ -39,17 +40,20 @@ export class MediationRecipientService { private eventEmitter: EventEmitter private connectionService: ConnectionService private messageSender: MessageSender + private recipientModuleConfig: RecipientModuleConfig public constructor( connectionService: ConnectionService, messageSender: MessageSender, mediatorRepository: MediationRepository, - eventEmitter: EventEmitter + eventEmitter: EventEmitter, + recipientModuleConfig: RecipientModuleConfig ) { this.mediationRepository = mediatorRepository this.eventEmitter = eventEmitter this.connectionService = connectionService this.messageSender = messageSender + this.recipientModuleConfig = recipientModuleConfig } public async createStatusRequest( @@ -272,7 +276,7 @@ export class MediationRecipientService { return null } - const { maximumMessagePickup } = messageContext.agentContext.config + const { maximumMessagePickup } = this.recipientModuleConfig const limit = messageCount < maximumMessagePickup ? messageCount : maximumMessagePickup const deliveryRequestMessage = new DeliveryRequestMessage({ diff --git a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts index 76c51345f1..210fac58d3 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts @@ -17,6 +17,7 @@ import { DidExchangeState } from '../../../connections' import { ConnectionRepository } from '../../../connections/repository/ConnectionRepository' import { ConnectionService } from '../../../connections/services/ConnectionService' import { DidRepository } from '../../../dids/repository/DidRepository' +import { RecipientModuleConfig } from '../../RecipientModuleConfig' import { MediationGrantMessage } from '../../messages' import { MediationRole, MediationState } from '../../models' import { DeliveryRequestMessage, MessageDeliveryMessage, MessagesReceivedMessage, StatusMessage } from '../../protocol' @@ -96,7 +97,8 @@ describe('MediationRecipientService', () => { connectionService, messageSender, mediationRepository, - eventEmitter + eventEmitter, + new RecipientModuleConfig() ) }) diff --git a/packages/core/src/modules/vc/module.ts b/packages/core/src/modules/vc/W3cVcModule.ts similarity index 87% rename from packages/core/src/modules/vc/module.ts rename to packages/core/src/modules/vc/W3cVcModule.ts index f9102217e3..30d5eb3be9 100644 --- a/packages/core/src/modules/vc/module.ts +++ b/packages/core/src/modules/vc/W3cVcModule.ts @@ -1,7 +1,6 @@ -import type { DependencyManager } from '../../plugins' +import type { DependencyManager, Module } from '../../plugins' import { KeyType } from '../../crypto' -import { module } from '../../plugins' import { SignatureSuiteRegistry, SignatureSuiteToken } from './SignatureSuiteRegistry' import { W3cCredentialService } from './W3cCredentialService' @@ -9,9 +8,8 @@ import { W3cCredentialRepository } from './repository/W3cCredentialRepository' import { Ed25519Signature2018 } from './signature-suites' import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from './signature-suites/bbs' -@module() -export class W3cVcModule { - public static register(dependencyManager: DependencyManager) { +export class W3cVcModule implements Module { + public register(dependencyManager: DependencyManager) { dependencyManager.registerSingleton(W3cCredentialService) dependencyManager.registerSingleton(W3cCredentialRepository) diff --git a/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts b/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts new file mode 100644 index 0000000000..e60807c1b6 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts @@ -0,0 +1,46 @@ +import { KeyType } from '../../../crypto' +import { DependencyManager } from '../../../plugins/DependencyManager' +import { SignatureSuiteRegistry, SignatureSuiteToken } from '../SignatureSuiteRegistry' +import { W3cCredentialService } from '../W3cCredentialService' +import { W3cVcModule } from '../W3cVcModule' +import { W3cCredentialRepository } from '../repository' +import { Ed25519Signature2018 } from '../signature-suites' +import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from '../signature-suites/bbs' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('W3cVcModule', () => { + test('registers dependencies on the dependency manager', () => { + new W3cVcModule().register(dependencyManager) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(3) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(W3cCredentialService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(W3cCredentialRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(SignatureSuiteRegistry) + + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(3) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(SignatureSuiteToken, { + suiteClass: Ed25519Signature2018, + proofType: 'Ed25519Signature2018', + requiredKeyType: 'Ed25519VerificationKey2018', + keyType: KeyType.Ed25519, + }) + + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(SignatureSuiteToken, { + suiteClass: BbsBlsSignature2020, + proofType: 'BbsBlsSignature2020', + requiredKeyType: 'BbsBlsSignatureProof2020', + keyType: KeyType.Bls12381g2, + }) + + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(SignatureSuiteToken, { + suiteClass: BbsBlsSignatureProof2020, + proofType: 'BbsBlsSignatureProof2020', + requiredKeyType: 'BbsBlsSignatureProof2020', + keyType: KeyType.Bls12381g2, + }) + }) +}) diff --git a/packages/core/src/modules/vc/index.ts b/packages/core/src/modules/vc/index.ts index 6aa4d6b1d6..8a4149599f 100644 --- a/packages/core/src/modules/vc/index.ts +++ b/packages/core/src/modules/vc/index.ts @@ -1,2 +1,3 @@ export * from './W3cCredentialService' export * from './repository/W3cCredentialRecord' +export * from './W3cVcModule' diff --git a/packages/core/src/plugins/Module.ts b/packages/core/src/plugins/Module.ts index c209e26021..5210e2d9c4 100644 --- a/packages/core/src/plugins/Module.ts +++ b/packages/core/src/plugins/Module.ts @@ -1,8 +1,7 @@ +import type { Constructor } from '../utils/mixins' import type { DependencyManager } from './DependencyManager' export interface Module { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - new (...args: any[]): any register(dependencyManager: DependencyManager): void } @@ -11,5 +10,5 @@ export interface Module { * on the class declaration. */ export function module() { - return (constructor: U) => constructor + return >(constructor: U) => constructor } diff --git a/packages/core/src/plugins/__tests__/DependencyManager.test.ts b/packages/core/src/plugins/__tests__/DependencyManager.test.ts index e2ffd2ca3c..0991324abe 100644 --- a/packages/core/src/plugins/__tests__/DependencyManager.test.ts +++ b/packages/core/src/plugins/__tests__/DependencyManager.test.ts @@ -1,7 +1,8 @@ +import type { Module } from '../Module' + import { container as rootContainer, injectable, Lifecycle } from 'tsyringe' import { DependencyManager } from '../DependencyManager' -import { module } from '../Module' class Instance { public random = Math.random() @@ -19,24 +20,25 @@ describe('DependencyManager', () => { describe('registerModules', () => { it('calls the register method for all module plugins', () => { - @module() @injectable() - class Module1 { - public static register = jest.fn() + class Module1 implements Module { + public register = jest.fn() } - @module() @injectable() - class Module2 { - public static register = jest.fn() + class Module2 implements Module { + public register = jest.fn() } - dependencyManager.registerModules(Module1, Module2) - expect(Module1.register).toHaveBeenCalledTimes(1) - expect(Module1.register).toHaveBeenLastCalledWith(dependencyManager) + const module1 = new Module1() + const module2 = new Module2() + + dependencyManager.registerModules(module1, module2) + expect(module1.register).toHaveBeenCalledTimes(1) + expect(module1.register).toHaveBeenLastCalledWith(dependencyManager) - expect(Module2.register).toHaveBeenCalledTimes(1) - expect(Module2.register).toHaveBeenLastCalledWith(dependencyManager) + expect(module2.register).toHaveBeenCalledTimes(1) + expect(module2.register).toHaveBeenLastCalledWith(dependencyManager) }) }) diff --git a/packages/core/src/storage/migration/UpdateAssistant.ts b/packages/core/src/storage/migration/UpdateAssistant.ts index 3cca35ab59..8cf30b461b 100644 --- a/packages/core/src/storage/migration/UpdateAssistant.ts +++ b/packages/core/src/storage/migration/UpdateAssistant.ts @@ -4,6 +4,7 @@ import type { UpdateConfig } from './updates' import { InjectionSymbols } from '../../constants' import { AriesFrameworkError } from '../../error' +import { isIndyError } from '../../utils/indyError' import { isFirstVersionHigherThanSecond, parseVersionString } from '../../utils/version' import { WalletError } from '../../wallet/error/WalletError' @@ -125,6 +126,18 @@ export class UpdateAssistant { throw error } } catch (error) { + // Backup already exists at path + if (error instanceof AriesFrameworkError && isIndyError(error.cause, 'CommonIOError')) { + const backupPath = this.getBackupPath(updateIdentifier) + const errorMessage = `Error updating storage with updateIdentifier ${updateIdentifier} because of an IO error. This is probably because the backup at path ${backupPath} already exists` + this.agent.config.logger.fatal(errorMessage, { + error, + updateIdentifier, + backupPath, + }) + throw new StorageUpdateError(errorMessage, { cause: error }) + } + this.agent.config.logger.error(`Error updating storage (updateIdentifier: ${updateIdentifier})`, { cause: error, }) diff --git a/packages/core/src/wallet/WalletApi.ts b/packages/core/src/wallet/WalletApi.ts new file mode 100644 index 0000000000..c59cd8fb54 --- /dev/null +++ b/packages/core/src/wallet/WalletApi.ts @@ -0,0 +1,108 @@ +import type { WalletConfig, WalletConfigRekey, WalletExportImportConfig } from '../types' +import type { Wallet } from './Wallet' + +import { AgentContext } from '../agent' +import { InjectionSymbols } from '../constants' +import { Logger } from '../logger' +import { inject, injectable } from '../plugins' +import { StorageUpdateService } from '../storage' +import { CURRENT_FRAMEWORK_STORAGE_VERSION } from '../storage/migration/updates' + +import { WalletError } from './error/WalletError' +import { WalletNotFoundError } from './error/WalletNotFoundError' + +@injectable() +export class WalletApi { + private agentContext: AgentContext + private wallet: Wallet + private storageUpdateService: StorageUpdateService + private logger: Logger + private _walletConfig?: WalletConfig + + public constructor( + storageUpdateService: StorageUpdateService, + agentContext: AgentContext, + @inject(InjectionSymbols.Logger) logger: Logger + ) { + this.storageUpdateService = storageUpdateService + this.logger = logger + this.wallet = agentContext.wallet + this.agentContext = agentContext + } + + public get isInitialized() { + return this.wallet.isInitialized + } + + public get isProvisioned() { + return this.wallet.isProvisioned + } + + public get walletConfig() { + return this._walletConfig + } + + public async initialize(walletConfig: WalletConfig): Promise { + this.logger.info(`Initializing wallet '${walletConfig.id}'`, walletConfig) + + if (this.isInitialized) { + throw new WalletError( + 'Wallet instance already initialized. Close the currently opened wallet before re-initializing the wallet' + ) + } + + // Open wallet, creating if it doesn't exist yet + try { + await this.open(walletConfig) + } catch (error) { + // If the wallet does not exist yet, create it and try to open again + if (error instanceof WalletNotFoundError) { + // Keep the wallet open after creating it, this saves an extra round trip of closing/opening + // the wallet, which can save quite some time. + await this.createAndOpen(walletConfig) + } else { + throw error + } + } + } + + public async createAndOpen(walletConfig: WalletConfig): Promise { + // Always keep the wallet open, as we still need to store the storage version in the wallet. + await this.wallet.createAndOpen(walletConfig) + + this._walletConfig = walletConfig + + // Store the storage version in the wallet + await this.storageUpdateService.setCurrentStorageVersion(this.agentContext, CURRENT_FRAMEWORK_STORAGE_VERSION) + } + + public async create(walletConfig: WalletConfig): Promise { + await this.createAndOpen(walletConfig) + await this.close() + } + + public async open(walletConfig: WalletConfig): Promise { + await this.wallet.open(walletConfig) + this._walletConfig = walletConfig + } + + public async close(): Promise { + await this.wallet.close() + } + + public async rotateKey(walletConfig: WalletConfigRekey): Promise { + await this.wallet.rotateKey(walletConfig) + } + + public async delete(): Promise { + await this.wallet.delete() + } + + public async export(exportConfig: WalletExportImportConfig): Promise { + await this.wallet.export(exportConfig) + } + + public async import(walletConfig: WalletConfig, importConfig: WalletExportImportConfig): Promise { + await this.wallet.import(walletConfig, importConfig) + } +} diff --git a/packages/core/src/wallet/WalletModule.ts b/packages/core/src/wallet/WalletModule.ts index 2c318a23c8..002dda6b2f 100644 --- a/packages/core/src/wallet/WalletModule.ts +++ b/packages/core/src/wallet/WalletModule.ts @@ -1,120 +1,17 @@ -import type { DependencyManager } from '../plugins' -import type { WalletConfig, WalletConfigRekey, WalletExportImportConfig } from '../types' -import type { Wallet } from './Wallet' +import type { DependencyManager, Module } from '../plugins' -import { AgentContext } from '../agent' -import { InjectionSymbols } from '../constants' -import { Bls12381g2SigningProvider, SigningProviderToken } from '../crypto/signing-provider' -import { Logger } from '../logger' -import { inject, injectable, module } from '../plugins' -import { StorageUpdateService } from '../storage' -import { CURRENT_FRAMEWORK_STORAGE_VERSION } from '../storage/migration/updates' +import { SigningProviderToken, Bls12381g2SigningProvider } from '../crypto/signing-provider' -import { WalletError } from './error/WalletError' -import { WalletNotFoundError } from './error/WalletNotFoundError' - -@module() -@injectable() -export class WalletModule { - private agentContext: AgentContext - private wallet: Wallet - private storageUpdateService: StorageUpdateService - private logger: Logger - private _walletConfig?: WalletConfig - - public constructor( - storageUpdateService: StorageUpdateService, - agentContext: AgentContext, - @inject(InjectionSymbols.Logger) logger: Logger - ) { - this.storageUpdateService = storageUpdateService - this.logger = logger - this.wallet = agentContext.wallet - this.agentContext = agentContext - } - - public get isInitialized() { - return this.wallet.isInitialized - } - - public get isProvisioned() { - return this.wallet.isProvisioned - } - - public get walletConfig() { - return this._walletConfig - } - - public async initialize(walletConfig: WalletConfig): Promise { - this.logger.info(`Initializing wallet '${walletConfig.id}'`, walletConfig) - - if (this.isInitialized) { - throw new WalletError( - 'Wallet instance already initialized. Close the currently opened wallet before re-initializing the wallet' - ) - } - - // Open wallet, creating if it doesn't exist yet - try { - await this.open(walletConfig) - } catch (error) { - // If the wallet does not exist yet, create it and try to open again - if (error instanceof WalletNotFoundError) { - // Keep the wallet open after creating it, this saves an extra round trip of closing/opening - // the wallet, which can save quite some time. - await this.createAndOpen(walletConfig) - } else { - throw error - } - } - } - - public async createAndOpen(walletConfig: WalletConfig): Promise { - // Always keep the wallet open, as we still need to store the storage version in the wallet. - await this.wallet.createAndOpen(walletConfig) - - this._walletConfig = walletConfig - - // Store the storage version in the wallet - await this.storageUpdateService.setCurrentStorageVersion(this.agentContext, CURRENT_FRAMEWORK_STORAGE_VERSION) - } - - public async create(walletConfig: WalletConfig): Promise { - await this.createAndOpen(walletConfig) - await this.close() - } - - public async open(walletConfig: WalletConfig): Promise { - await this.wallet.open(walletConfig) - this._walletConfig = walletConfig - } - - public async close(): Promise { - await this.wallet.close() - } - - public async rotateKey(walletConfig: WalletConfigRekey): Promise { - await this.wallet.rotateKey(walletConfig) - } - - public async delete(): Promise { - await this.wallet.delete() - } - - public async export(exportConfig: WalletExportImportConfig): Promise { - await this.wallet.export(exportConfig) - } - - public async import(walletConfig: WalletConfig, importConfig: WalletExportImportConfig): Promise { - await this.wallet.import(walletConfig, importConfig) - } +import { WalletApi } from './WalletApi' +// TODO: this should be moved into the modules directory +export class WalletModule implements Module { /** * Registers the dependencies of the wallet module on the injection dependencyManager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api - dependencyManager.registerContextScoped(WalletModule) + dependencyManager.registerContextScoped(WalletApi) // Signing providers. dependencyManager.registerSingleton(SigningProviderToken, Bls12381g2SigningProvider) diff --git a/packages/core/src/wallet/__tests__/WalletModule.test.ts b/packages/core/src/wallet/__tests__/WalletModule.test.ts new file mode 100644 index 0000000000..894c911d58 --- /dev/null +++ b/packages/core/src/wallet/__tests__/WalletModule.test.ts @@ -0,0 +1,17 @@ +import { DependencyManager } from '../../plugins/DependencyManager' +import { WalletApi } from '../WalletApi' +import { WalletModule } from '../WalletModule' + +jest.mock('../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('WalletModule', () => { + test('registers dependencies on the dependency manager', () => { + new WalletModule().register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(WalletApi) + }) +}) diff --git a/packages/core/src/wallet/index.ts b/packages/core/src/wallet/index.ts index c9f6729d0c..e60dcfdb68 100644 --- a/packages/core/src/wallet/index.ts +++ b/packages/core/src/wallet/index.ts @@ -1 +1,3 @@ export * from './Wallet' +export * from './WalletApi' +export * from './WalletModule' diff --git a/packages/module-tenants/src/TenantsModule.ts b/packages/module-tenants/src/TenantsModule.ts index 5dac760487..190f165c3e 100644 --- a/packages/module-tenants/src/TenantsModule.ts +++ b/packages/module-tenants/src/TenantsModule.ts @@ -1,23 +1,33 @@ -import type { DependencyManager } from '@aries-framework/core' +import type { TenantsModuleConfigOptions } from './TenantsModuleConfig' +import type { DependencyManager, Module } from '@aries-framework/core' -import { InjectionSymbols, module } from '@aries-framework/core' +import { InjectionSymbols } from '@aries-framework/core' import { TenantsApi } from './TenantsApi' +import { TenantsModuleConfig } from './TenantsModuleConfig' import { TenantAgentContextProvider } from './context/TenantAgentContextProvider' import { TenantSessionCoordinator } from './context/TenantSessionCoordinator' import { TenantRepository, TenantRoutingRepository } from './repository' import { TenantRecordService } from './services' -@module() -export class TenantsModule { +export class TenantsModule implements Module { + public readonly config: TenantsModuleConfig + + public constructor(config?: TenantsModuleConfigOptions) { + this.config = new TenantsModuleConfig(config) + } + /** * Registers the dependencies of the tenants module on the dependency manager. */ - public static register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager) { // Api // NOTE: this is a singleton because tenants can't have their own tenants. This makes sure the tenants api is always used in the root agent context. dependencyManager.registerSingleton(TenantsApi) + // Config + dependencyManager.registerInstance(TenantsModuleConfig, this.config) + // Services dependencyManager.registerSingleton(TenantRecordService) diff --git a/packages/module-tenants/src/TenantsModuleConfig.ts b/packages/module-tenants/src/TenantsModuleConfig.ts new file mode 100644 index 0000000000..7fc8b7c84c --- /dev/null +++ b/packages/module-tenants/src/TenantsModuleConfig.ts @@ -0,0 +1,42 @@ +/** + * TenantsModuleConfigOptions defines the interface for the options of the TenantsModuleConfig class. + * This can contain optional parameters that have default values in the config class itself. + */ +export interface TenantsModuleConfigOptions { + /** + * Maximum number of concurrent tenant sessions that can be active at the same time. Defaults to + * 100 concurrent sessions. The default is low on purpose, to make sure deployments determine their own + * session limit based on the hardware and usage of the tenants module. Use `Infinity` to allow unlimited + * concurrent sessions. + * + * @default 100 + */ + sessionLimit?: number + + /** + * Timeout in milliseconds for acquiring a tenant session. If the {@link TenantsModuleConfigOptions.maxNumberOfSessions} is reached and + * a tenant sessions couldn't be acquired within the specified timeout, an error will be thrown and the session creation will be aborted. + * Use `Infinity` to disable the timeout. + * + * @default 1000 + */ + sessionAcquireTimeout?: number +} + +export class TenantsModuleConfig { + private options: TenantsModuleConfigOptions + + public constructor(options?: TenantsModuleConfigOptions) { + this.options = options ?? {} + } + + /** See {@link TenantsModuleConfigOptions.sessionLimit} */ + public get sessionLimit(): number { + return this.options.sessionLimit ?? 100 + } + + /** See {@link TenantsModuleConfigOptions.sessionAcquireTimeout} */ + public get sessionAcquireTimeout(): number { + return this.options.sessionAcquireTimeout ?? 1000 + } +} diff --git a/packages/module-tenants/src/__tests__/TenantsModule.test.ts b/packages/module-tenants/src/__tests__/TenantsModule.test.ts index 23529cb7f3..fb0ab20231 100644 --- a/packages/module-tenants/src/__tests__/TenantsModule.test.ts +++ b/packages/module-tenants/src/__tests__/TenantsModule.test.ts @@ -3,6 +3,7 @@ import { InjectionSymbols } from '@aries-framework/core' import { DependencyManager } from '../../../core/src/plugins/DependencyManager' import { TenantsApi } from '../TenantsApi' import { TenantsModule } from '../TenantsModule' +import { TenantsModuleConfig } from '../TenantsModuleConfig' import { TenantAgentContextProvider } from '../context/TenantAgentContextProvider' import { TenantSessionCoordinator } from '../context/TenantSessionCoordinator' import { TenantRepository, TenantRoutingRepository } from '../repository' @@ -15,7 +16,8 @@ const dependencyManager = new DependencyManagerMock() describe('TenantsModule', () => { test('registers dependencies on the dependency manager', () => { - TenantsModule.register(dependencyManager) + const tenantsModule = new TenantsModule() + tenantsModule.register(dependencyManager) expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(6) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(TenantsApi) @@ -27,5 +29,8 @@ describe('TenantsModule', () => { TenantAgentContextProvider ) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(TenantSessionCoordinator) + + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(TenantsModuleConfig, tenantsModule.config) }) }) diff --git a/packages/module-tenants/src/__tests__/TenantsModuleConfig.test.ts b/packages/module-tenants/src/__tests__/TenantsModuleConfig.test.ts new file mode 100644 index 0000000000..b942434bad --- /dev/null +++ b/packages/module-tenants/src/__tests__/TenantsModuleConfig.test.ts @@ -0,0 +1,20 @@ +import { TenantsModuleConfig } from '../TenantsModuleConfig' + +describe('TenantsModuleConfig', () => { + test('sets default values', () => { + const config = new TenantsModuleConfig() + + expect(config.sessionLimit).toBe(100) + expect(config.sessionAcquireTimeout).toBe(1000) + }) + + test('sets values', () => { + const config = new TenantsModuleConfig({ + sessionAcquireTimeout: 12, + sessionLimit: 42, + }) + + expect(config.sessionAcquireTimeout).toBe(12) + expect(config.sessionLimit).toBe(42) + }) +}) diff --git a/packages/module-tenants/src/context/TenantSessionCoordinator.ts b/packages/module-tenants/src/context/TenantSessionCoordinator.ts index fc4816afb0..cdc7428a86 100644 --- a/packages/module-tenants/src/context/TenantSessionCoordinator.ts +++ b/packages/module-tenants/src/context/TenantSessionCoordinator.ts @@ -9,10 +9,12 @@ import { injectable, InjectionSymbols, Logger, - WalletModule, + WalletApi, } from '@aries-framework/core' import { Mutex, withTimeout } from 'async-mutex' +import { TenantsModuleConfig } from '../TenantsModuleConfig' + import { TenantSessionMutex } from './TenantSessionMutex' /** @@ -32,14 +34,24 @@ export class TenantSessionCoordinator { private logger: Logger private tenantAgentContextMapping: TenantAgentContextMapping = {} private sessionMutex: TenantSessionMutex + private tenantsModuleConfig: TenantsModuleConfig - public constructor(rootAgentContext: AgentContext, @inject(InjectionSymbols.Logger) logger: Logger) { + public constructor( + rootAgentContext: AgentContext, + @inject(InjectionSymbols.Logger) logger: Logger, + tenantsModuleConfig: TenantsModuleConfig + ) { this.rootAgentContext = rootAgentContext this.logger = logger + this.tenantsModuleConfig = tenantsModuleConfig // TODO: we should make the timeout and the session limit configurable, but until we have the modularization in place with // module specific config, it's not easy to do so. Keeping it hardcoded for now - this.sessionMutex = new TenantSessionMutex(this.logger, 10000, 1000) + this.sessionMutex = new TenantSessionMutex( + this.logger, + this.tenantsModuleConfig.sessionLimit, + this.tenantsModuleConfig.sessionAcquireTimeout + ) } /** @@ -146,11 +158,10 @@ export class TenantSessionCoordinator { sessionCount: 0, mutex: withTimeout( new Mutex(), - // TODO: we should make the timeout configurable. // NOTE: It can take a while to create an indy wallet. We're using RAW key derivation which should // be fast enough to not cause a problem. This wil also only be problem when the wallet is being created // for the first time or being acquired while wallet initialization is in progress. - 1000, + this.tenantsModuleConfig.sessionAcquireTimeout, new AriesFrameworkError( `Error acquiring lock for tenant ${tenantId}. Wallet initialization or shutdown took too long.` ) @@ -179,11 +190,11 @@ export class TenantSessionCoordinator { tenantDependencyManager.registerInstance(AgentContext, agentContext) tenantDependencyManager.registerInstance(AgentConfig, tenantConfig) - // NOTE: we're using the wallet module here because that correctly handle creating if it doesn't exist yet + // NOTE: we're using the wallet api here because that correctly handle creating if it doesn't exist yet // and will also write the storage version to the storage, which is needed by the update assistant. We either // need to move this out of the module, or just keep using the module here. - const walletModule = agentContext.dependencyManager.resolve(WalletModule) - await walletModule.initialize(tenantRecord.config.walletConfig) + const walletApi = agentContext.dependencyManager.resolve(WalletApi) + await walletApi.initialize(tenantRecord.config.walletConfig) return agentContext } diff --git a/packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts b/packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts index f3766cfcc7..dd659db44c 100644 --- a/packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts +++ b/packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts @@ -1,11 +1,12 @@ import type { TenantAgentContextMapping } from '../TenantSessionCoordinator' import type { DependencyManager } from '@aries-framework/core' -import { WalletModule, AgentContext, AgentConfig } from '@aries-framework/core' +import { AgentContext, AgentConfig, WalletApi } from '@aries-framework/core' import { Mutex, withTimeout } from 'async-mutex' import { getAgentConfig, getAgentContext, mockFunction } from '../../../../core/tests/helpers' import testLogger from '../../../../core/tests/logger' +import { TenantsModuleConfig } from '../../TenantsModuleConfig' import { TenantRecord } from '../../repository' import { TenantSessionCoordinator } from '../TenantSessionCoordinator' import { TenantSessionMutex } from '../TenantSessionMutex' @@ -21,16 +22,17 @@ type PublicTenantAgentContextMapping = Omit { const numberOfSessions = 5 const tenantRecordPromises = [] - for (let tenantNo = 0; tenantNo <= numberOfTenants; tenantNo++) { + for (let tenantNo = 0; tenantNo < numberOfTenants; tenantNo++) { const tenantRecord = agentTenantsApi.createTenant({ config: { label: 'Agent 1 Tenant 1', diff --git a/packages/module-tenants/tests/tenants.e2e.test.ts b/packages/module-tenants/tests/tenants.e2e.test.ts index c0ca618892..a8b24a88f1 100644 --- a/packages/module-tenants/tests/tenants.e2e.test.ts +++ b/packages/module-tenants/tests/tenants.e2e.test.ts @@ -36,10 +36,10 @@ const agent2Config: InitConfig = { // and register all plugins before initializing the agent. Later, we can add the module registration // to the agent constructor. const agent1DependencyManager = new DependencyManager() -agent1DependencyManager.registerModules(TenantsModule) +agent1DependencyManager.registerModules(new TenantsModule()) const agent2DependencyManager = new DependencyManager() -agent2DependencyManager.registerModules(TenantsModule) +agent2DependencyManager.registerModules(new TenantsModule()) // Create multi-tenant agents const agent1 = new Agent(agent1Config, agentDependencies, agent1DependencyManager) diff --git a/samples/extension-module/README.md b/samples/extension-module/README.md index 95af31e4fe..952ea962f5 100644 --- a/samples/extension-module/README.md +++ b/samples/extension-module/README.md @@ -28,7 +28,7 @@ import { DummyModule, DummyApi } from './dummy' const agent = new Agent(/** agent config... */) // Register the module with it's dependencies -agent.dependencyManager.registerModules(DummyModule) +agent.dependencyManager.registerModules(new DummyModule()) const dummyApi = agent.dependencyManager.resolve(DummyApi) diff --git a/samples/extension-module/dummy/module.ts b/samples/extension-module/dummy/DummyModule.ts similarity index 56% rename from samples/extension-module/dummy/module.ts rename to samples/extension-module/dummy/DummyModule.ts index 4f23539679..9f0f50f99a 100644 --- a/samples/extension-module/dummy/module.ts +++ b/samples/extension-module/dummy/DummyModule.ts @@ -1,13 +1,10 @@ -import type { DependencyManager } from '@aries-framework/core' - -import { module } from '@aries-framework/core' +import type { DependencyManager, Module } from '@aries-framework/core' import { DummyRepository } from './repository' import { DummyService } from './services' -@module() -export class DummyModule { - public static register(dependencyManager: DependencyManager) { +export class DummyModule implements Module { + public register(dependencyManager: DependencyManager) { // Api dependencyManager.registerContextScoped(DummyModule) diff --git a/samples/extension-module/dummy/index.ts b/samples/extension-module/dummy/index.ts index 281d382e3f..3849e17339 100644 --- a/samples/extension-module/dummy/index.ts +++ b/samples/extension-module/dummy/index.ts @@ -3,4 +3,4 @@ export * from './handlers' export * from './messages' export * from './services' export * from './repository' -export * from './module' +export * from './DummyModule' diff --git a/samples/extension-module/requester.ts b/samples/extension-module/requester.ts index 7128004130..4eaf2c9e7b 100644 --- a/samples/extension-module/requester.ts +++ b/samples/extension-module/requester.ts @@ -26,7 +26,7 @@ const run = async () => { ) // Register the DummyModule - agent.dependencyManager.registerModules(DummyModule) + agent.dependencyManager.registerModules(new DummyModule()) // Register transports agent.registerOutboundTransport(wsOutboundTransport) diff --git a/samples/extension-module/responder.ts b/samples/extension-module/responder.ts index 065dc49232..8d09540a3e 100644 --- a/samples/extension-module/responder.ts +++ b/samples/extension-module/responder.ts @@ -34,7 +34,7 @@ const run = async () => { ) // Register the DummyModule - agent.dependencyManager.registerModules(DummyModule) + agent.dependencyManager.registerModules(new DummyModule()) // Register transports agent.registerInboundTransport(httpInboundTransport) From be37011c139c5cc69fc591060319d8c373e9508b Mon Sep 17 00:00:00 2001 From: Karim Stekelenburg Date: Thu, 21 Jul 2022 14:05:44 +0200 Subject: [PATCH 017/125] feat(vc): delete w3c credential record (#886) Signed-off-by: Karim --- .../src/modules/vc/W3cCredentialService.ts | 27 ++-- .../vc/__tests__/W3cCredentialService.test.ts | 118 ++++++++++++++---- .../vc/models/W3cCredentialServiceOptions.ts | 2 +- 3 files changed, 109 insertions(+), 38 deletions(-) diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index a8ba4e704d..4b9ccef0aa 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -1,5 +1,6 @@ import type { AgentContext } from '../../agent/context' import type { Key } from '../../crypto/Key' +import type { Query } from '../../storage/StorageService' import type { DocumentLoader } from './jsonldUtil' import type { W3cVerifyCredentialResult } from './models' import type { @@ -340,7 +341,7 @@ export class W3cCredentialService { ): Promise { // Get the expanded types const expandedTypes = ( - await jsonld.expand(JsonTransformer.toJSON(options.record), { + await jsonld.expand(JsonTransformer.toJSON(options.credential), { documentLoader: this.documentLoaderWithContext(agentContext), }) )[0]['@type'] @@ -348,7 +349,7 @@ export class W3cCredentialService { // Create an instance of the w3cCredentialRecord const w3cCredentialRecord = new W3cCredentialRecord({ tags: { expandedTypes: orArrayToArray(expandedTypes) }, - credential: options.record, + credential: options.credential, }) // Store the w3c credential record @@ -357,26 +358,30 @@ export class W3cCredentialService { return w3cCredentialRecord } - public async getAllCredentials(agentContext: AgentContext): Promise { - const allRecords = await this.w3cCredentialRepository.getAll(agentContext) - return allRecords.map((record) => record.credential) + public async removeCredentialRecord(agentContext: AgentContext, id: string) { + const credential = await this.w3cCredentialRepository.getById(agentContext, id) + await this.w3cCredentialRepository.delete(agentContext, credential) } - public async getCredentialById(agentContext: AgentContext, id: string): Promise { - return (await this.w3cCredentialRepository.getById(agentContext, id)).credential + public async getAllCredentialRecords(agentContext: AgentContext): Promise { + return await this.w3cCredentialRepository.getAll(agentContext) } - public async findCredentialsByQuery( + public async getCredentialRecordById(agentContext: AgentContext, id: string): Promise { + return await this.w3cCredentialRepository.getById(agentContext, id) + } + + public async findCredentialRecordsByQuery( agentContext: AgentContext, - query: Parameters[1] + query: Query ): Promise { const result = await this.w3cCredentialRepository.findByQuery(agentContext, query) return result.map((record) => record.credential) } - public async findSingleCredentialByQuery( + public async findCredentialRecordByQuery( agentContext: AgentContext, - query: Parameters[1] + query: Query ): Promise { const result = await this.w3cCredentialRepository.findSingleByQuery(agentContext, query) return result?.credential diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index 0d133a18f5..f6edc17ce4 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -1,6 +1,6 @@ import type { AgentContext } from '../../../agent' -import { getAgentConfig, getAgentContext } from '../../../../tests/helpers' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../tests/helpers' import { KeyType } from '../../../crypto' import { Key } from '../../../crypto/Key' import { Bls12381g2SigningProvider, SigningProviderRegistry } from '../../../crypto/signing-provider' @@ -13,13 +13,14 @@ import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' import { SignatureSuiteRegistry } from '../SignatureSuiteRegistry' import { W3cCredentialService } from '../W3cCredentialService' import { orArrayToArray } from '../jsonldUtil' +import jsonld from '../libraries/jsonld' import { purposes } from '../libraries/jsonld-signatures' import { W3cCredential, W3cVerifiableCredential } from '../models' import { LinkedDataProof } from '../models/LinkedDataProof' import { W3cPresentation } from '../models/presentation/W3Presentation' import { W3cVerifiablePresentation } from '../models/presentation/W3cVerifiablePresentation' import { CredentialIssuancePurpose } from '../proof-purposes/CredentialIssuancePurpose' -import { W3cCredentialRepository } from '../repository/W3cCredentialRepository' +import { W3cCredentialRecord, W3cCredentialRepository } from '../repository' import { Ed25519Signature2018 } from '../signature-suites' import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from '../signature-suites/bbs' @@ -59,6 +60,19 @@ const W3cCredentialRepositoryMock = W3cCredentialRepository as jest.Mock { + const expandedTypes = ( + await jsonld.expand(JsonTransformer.toJSON(credential), { documentLoader: customDocumentLoader }) + )[0]['@type'] + + // Create an instance of the w3cCredentialRecord + return new W3cCredentialRecord({ + tags: { expandedTypes: orArrayToArray(expandedTypes) }, + credential: credential, + }) +} + describe('W3cCredentialService', () => { let wallet: IndyWallet let agentContext: AgentContext @@ -285,30 +299,6 @@ describe('W3cCredentialService', () => { expect(result.verified).toBe(true) }) }) - describe('storeCredential', () => { - it('should store a credential', async () => { - const credential = JsonTransformer.fromJSON( - Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, - W3cVerifiableCredential - ) - - const w3cCredentialRecord = await w3cCredentialService.storeCredential(agentContext, { record: credential }) - - expect(w3cCredentialRecord).toMatchObject({ - type: 'W3cCredentialRecord', - id: expect.any(String), - createdAt: expect.any(Date), - credential: expect.any(W3cVerifiableCredential), - }) - - expect(w3cCredentialRecord.getTags()).toMatchObject({ - expandedTypes: [ - 'https://www.w3.org/2018/credentials#VerifiableCredential', - 'https://example.org/examples#UniversityDegreeCredential', - ], - }) - }) - }) }) describe('BbsBlsSignature2020', () => { @@ -454,4 +444,80 @@ describe('W3cCredentialService', () => { }) }) }) + describe('Credential Storage', () => { + let w3cCredentialRecord: W3cCredentialRecord + let w3cCredentialRepositoryDeleteMock: jest.MockedFunction + + beforeEach(async () => { + const credential = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + W3cVerifiableCredential + ) + + w3cCredentialRecord = await credentialRecordFactory(credential) + + mockFunction(w3cCredentialRepository.getById).mockResolvedValue(w3cCredentialRecord) + mockFunction(w3cCredentialRepository.getAll).mockResolvedValue([w3cCredentialRecord]) + w3cCredentialRepositoryDeleteMock = mockFunction(w3cCredentialRepository.delete).mockResolvedValue() + }) + describe('storeCredential', () => { + it('should store a credential and expand the tags correctly', async () => { + const credential = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + W3cVerifiableCredential + ) + + w3cCredentialRecord = await w3cCredentialService.storeCredential(agentContext, { credential: credential }) + + expect(w3cCredentialRecord).toMatchObject({ + type: 'W3cCredentialRecord', + id: expect.any(String), + createdAt: expect.any(Date), + credential: expect.any(W3cVerifiableCredential), + }) + + expect(w3cCredentialRecord.getTags()).toMatchObject({ + expandedTypes: [ + 'https://www.w3.org/2018/credentials#VerifiableCredential', + 'https://example.org/examples#UniversityDegreeCredential', + ], + }) + }) + }) + + describe('removeCredentialRecord', () => { + it('should remove a credential', async () => { + const credential = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + W3cVerifiableCredential + ) + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await w3cCredentialService.removeCredentialRecord(agentContext, credential.id!) + + expect(w3cCredentialRepositoryDeleteMock).toBeCalledWith(agentContext, w3cCredentialRecord) + }) + }) + + describe('getAllCredentialRecords', () => { + it('should retrieve all W3cCredentialRecords', async () => { + const credential = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + W3cVerifiableCredential + ) + await w3cCredentialService.storeCredential(agentContext, { credential: credential }) + + const records = await w3cCredentialService.getAllCredentialRecords(agentContext) + + expect(records.length).toEqual(1) + }) + }) + describe('getCredentialRecordById', () => { + it('should retrieve a W3cCredentialRecord by id', async () => { + const credential = await w3cCredentialService.getCredentialRecordById(agentContext, w3cCredentialRecord.id) + + expect(credential.id).toEqual(w3cCredentialRecord.id) + }) + }) + }) }) diff --git a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts index b683677576..88206fa4bd 100644 --- a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts +++ b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts @@ -25,7 +25,7 @@ export interface VerifyCredentialOptions { } export interface StoreCredentialOptions { - record: W3cVerifiableCredential + credential: W3cVerifiableCredential } export interface CreatePresentationOptions { From 80c3740f625328125fe8121035f2d83ce1dee6a5 Mon Sep 17 00:00:00 2001 From: Karim Stekelenburg Date: Thu, 21 Jul 2022 14:23:00 +0200 Subject: [PATCH 018/125] fix(vc): change pubKey input from Buffer to Uint8Array (#935) Signed-off-by: Karim --- .../modules/vc/signature-suites/bbs/BbsBlsSignatureProof2020.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignatureProof2020.ts b/packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignatureProof2020.ts index 2eec683b1f..3dbd76f466 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignatureProof2020.ts +++ b/packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignatureProof2020.ts @@ -194,7 +194,7 @@ export class BbsBlsSignatureProof2020 extends suites.LinkedDataProof { // Compute the proof const outputProof = await blsCreateProof({ signature, - publicKey: key.publicKeyBuffer, + publicKey: Uint8Array.from(key.publicKeyBuffer), messages: allInputStatements, nonce, revealed: revealIndicies, From 93f3c93310f9dae032daa04a920b7df18e2f8a65 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 21 Jul 2022 17:05:51 +0200 Subject: [PATCH 019/125] feat(dids): add did registrar (#953) Signed-off-by: Timo Glastra --- packages/core/package.json | 2 +- .../connections/DidExchangeProtocol.ts | 55 +-- .../__tests__/ConnectionService.test.ts | 33 +- .../connections/services/ConnectionService.ts | 78 ++-- packages/core/src/modules/dids/DidsApi.ts | 91 ++++- packages/core/src/modules/dids/DidsModule.ts | 25 +- .../core/src/modules/dids/DidsModuleConfig.ts | 68 ++++ .../modules/dids/__tests__/DidsModule.test.ts | 47 ++- .../dids/__tests__/DidsModuleConfig.test.ts | 46 +++ .../dids/__tests__/dids-registrar.e2e.test.ts | 267 +++++++++++++ .../dids/__tests__/dids-resolver.e2e.test.ts} | 50 ++- .../modules/dids/__tests__/peer-did.test.ts | 4 +- .../src/modules/dids/domain/DidRegistrar.ts | 19 + .../src/modules/dids/domain/DidResolver.ts | 2 + .../dids/domain/__tests__/DidDocument.test.ts | 2 +- .../core/src/modules/dids/domain/index.ts | 3 + .../core/src/modules/dids/domain/parse.ts | 6 +- packages/core/src/modules/dids/index.ts | 3 +- .../core/src/modules/dids/methods/index.ts | 4 + .../dids/methods/key/KeyDidRegistrar.ts | 129 ++++++ .../dids/methods/key/KeyDidResolver.ts | 3 + .../key/__tests__/KeyDidRegistrar.test.ts | 153 ++++++++ .../__tests__/__fixtures__/didKeyz6MksLe.json | 36 ++ .../src/modules/dids/methods/key/index.ts | 2 + .../dids/methods/peer/PeerDidRegistrar.ts | 205 ++++++++++ .../dids/methods/peer/PeerDidResolver.ts | 4 +- .../{didPeer.test.ts => DidPeer.test.ts} | 0 .../peer/__tests__/PeerDidRegistrar.test.ts | 367 ++++++++++++++++++ .../__fixtures__/didPeer0z6MksLe.json | 36 ++ .../createPeerDidDocumentFromServices.ts} | 21 +- .../src/modules/dids/methods/peer/index.ts | 4 + .../dids/methods/sov/SovDidRegistrar.ts | 195 ++++++++++ .../dids/methods/sov/SovDidResolver.ts | 132 +------ .../sov/__tests__/SovDidRegistrar.test.ts | 345 ++++++++++++++++ .../src/modules/dids/methods/sov/index.ts | 2 + .../core/src/modules/dids/methods/sov/util.ts | 123 ++++++ .../dids/methods/web/WebDidResolver.ts | 2 + .../src/modules/dids/methods/web/index.ts | 1 + .../modules/dids/repository/DidRepository.ts | 8 + .../dids/services/DidRegistrarService.ts | 159 ++++++++ .../dids/services/DidResolverService.ts | 22 +- .../__tests__/DidRegistrarService.test.ts | 199 ++++++++++ .../__tests__/DidResolverService.test.ts | 39 +- .../core/src/modules/dids/services/index.ts | 1 + packages/core/src/modules/dids/types.ts | 71 ++++ .../ledger/services/IndyLedgerService.ts | 30 +- .../MediationRecipientService.test.ts | 14 +- .../vc/__tests__/W3cCredentialService.test.ts | 11 +- ...coder.test.ts => MultibaseEncoder.test.ts} | 0 ...coder.test.ts => MultihashEncoder.test.ts} | 0 packages/core/src/wallet/IndyWallet.ts | 8 +- packages/core/src/wallet/Wallet.ts | 2 +- .../core/src/wallet/util/assertIndyWallet.ts | 4 +- .../tests/__fixtures__/didKeyz6Mkqbe1.json | 38 ++ packages/react-native/package.json | 2 +- yarn.lock | 8 +- 56 files changed, 2892 insertions(+), 289 deletions(-) create mode 100644 packages/core/src/modules/dids/DidsModuleConfig.ts create mode 100644 packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts create mode 100644 packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts rename packages/core/{tests/dids.test.ts => src/modules/dids/__tests__/dids-resolver.e2e.test.ts} (77%) create mode 100644 packages/core/src/modules/dids/domain/DidRegistrar.ts create mode 100644 packages/core/src/modules/dids/methods/index.ts create mode 100644 packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts create mode 100644 packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts create mode 100644 packages/core/src/modules/dids/methods/key/__tests__/__fixtures__/didKeyz6MksLe.json create mode 100644 packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts rename packages/core/src/modules/dids/methods/peer/__tests__/{didPeer.test.ts => DidPeer.test.ts} (100%) create mode 100644 packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts create mode 100644 packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer0z6MksLe.json rename packages/core/src/modules/dids/{domain/createPeerDidFromServices.ts => methods/peer/createPeerDidDocumentFromServices.ts} (77%) create mode 100644 packages/core/src/modules/dids/methods/peer/index.ts create mode 100644 packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts create mode 100644 packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts create mode 100644 packages/core/src/modules/dids/methods/sov/index.ts create mode 100644 packages/core/src/modules/dids/methods/sov/util.ts create mode 100644 packages/core/src/modules/dids/methods/web/index.ts create mode 100644 packages/core/src/modules/dids/services/DidRegistrarService.ts create mode 100644 packages/core/src/modules/dids/services/__tests__/DidRegistrarService.test.ts rename packages/core/src/modules/dids/{ => services}/__tests__/DidResolverService.test.ts (53%) rename packages/core/src/utils/__tests__/{MultiBaseEncoder.test.ts => MultibaseEncoder.test.ts} (100%) rename packages/core/src/utils/__tests__/{MultiHashEncoder.test.ts => MultihashEncoder.test.ts} (100%) create mode 100644 packages/core/tests/__fixtures__/didKeyz6Mkqbe1.json diff --git a/packages/core/package.json b/packages/core/package.json index f38ce37605..360da5bd98 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -32,7 +32,7 @@ "@stablelib/ed25519": "^1.0.2", "@stablelib/random": "^1.0.1", "@stablelib/sha256": "^1.0.1", - "@types/indy-sdk": "^1.16.19", + "@types/indy-sdk": "^1.16.21", "@types/node-fetch": "^2.5.10", "@types/ws": "^7.4.6", "abort-controller": "^3.0.0", diff --git a/packages/core/src/modules/connections/DidExchangeProtocol.ts b/packages/core/src/modules/connections/DidExchangeProtocol.ts index a1a865ccd2..6b308adced 100644 --- a/packages/core/src/modules/connections/DidExchangeProtocol.ts +++ b/packages/core/src/modules/connections/DidExchangeProtocol.ts @@ -2,6 +2,7 @@ import type { AgentContext } from '../../agent' import type { ResolvedDidCommService } from '../../agent/MessageSender' import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' import type { ParsedMessageType } from '../../utils/messageType' +import type { PeerDidCreateOptions } from '../dids' import type { OutOfBandDidCommService } from '../oob/domain/OutOfBandDidCommService' import type { OutOfBandRecord } from '../oob/repository' import type { ConnectionRecord } from './repository' @@ -16,14 +17,17 @@ import { Logger } from '../../logger' import { inject, injectable } from '../../plugins' import { JsonEncoder } from '../../utils/JsonEncoder' import { JsonTransformer } from '../../utils/JsonTransformer' -import { DidDocument } from '../dids' -import { DidDocumentRole } from '../dids/domain/DidDocumentRole' -import { createDidDocumentFromServices } from '../dids/domain/createPeerDidFromServices' +import { + DidDocument, + DidRegistrarService, + DidDocumentRole, + createPeerDidDocumentFromServices, + DidKey, + getNumAlgoFromPeerDid, + PeerDidNumAlgo, +} from '../dids' import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type' import { didKeyToInstanceOfKey } from '../dids/helpers' -import { DidKey } from '../dids/methods/key/DidKey' -import { getNumAlgoFromPeerDid, PeerDidNumAlgo } from '../dids/methods/peer/didPeer' -import { didDocumentJsonToNumAlgo1Did } from '../dids/methods/peer/peerDidNumAlgo1' import { DidRecord, DidRepository } from '../dids/repository' import { OutOfBandRole } from '../oob/domain/OutOfBandRole' import { OutOfBandState } from '../oob/domain/OutOfBandState' @@ -48,17 +52,20 @@ interface DidExchangeRequestParams { @injectable() export class DidExchangeProtocol { private connectionService: ConnectionService + private didRegistrarService: DidRegistrarService private jwsService: JwsService private didRepository: DidRepository private logger: Logger public constructor( connectionService: ConnectionService, + didRegistrarService: DidRegistrarService, didRepository: DidRepository, jwsService: JwsService, @inject(InjectionSymbols.Logger) logger: Logger ) { this.connectionService = connectionService + this.didRegistrarService = didRegistrarService this.didRepository = didRepository this.jwsService = jwsService this.logger = logger @@ -165,6 +172,8 @@ export class DidExchangeProtocol { ) } + // TODO: Move this into the didcomm module, and add a method called store received did document. + // This can be called from both the did exchange and the connection protocol. const didDocument = await this.extractDidDocument(messageContext.agentContext, message) const didRecord = new DidRecord({ id: message.did, @@ -406,32 +415,28 @@ export class DidExchangeProtocol { } private async createPeerDidDoc(agentContext: AgentContext, services: ResolvedDidCommService[]) { - const didDocument = createDidDocumentFromServices(services) + // Create did document without the id property + const didDocument = createPeerDidDocumentFromServices(services) - const peerDid = didDocumentJsonToNumAlgo1Did(didDocument.toJSON()) - didDocument.id = peerDid - - const didRecord = new DidRecord({ - id: peerDid, - role: DidDocumentRole.Created, + // Register did:peer document. This will generate the id property and save it to a did record + const result = await this.didRegistrarService.create(agentContext, { + method: 'peer', didDocument, - tags: { - // We need to save the recipientKeys, so we can find the associated did - // of a key when we receive a message from another connection. - recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint), + options: { + numAlgo: PeerDidNumAlgo.GenesisDoc, }, }) - this.logger.debug('Saving DID record', { - id: didRecord.id, - role: didRecord.role, - tags: didRecord.getTags(), - didDocument: 'omitted...', + if (result.didState?.state !== 'finished') { + throw new AriesFrameworkError(`Did document creation failed: ${JSON.stringify(result.didState)}`) + } + + this.logger.debug(`Did document with did ${result.didState.did} created.`, { + did: result.didState.did, + didDocument: result.didState.didDocument, }) - await this.didRepository.save(agentContext, didRecord) - this.logger.debug('Did record created.', didRecord) - return didDocument + return result.didState.didDocument } private async createSignedAttachment(agentContext: AgentContext, didDoc: DidDocument, verkeys: string[]) { diff --git a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index 25ade9e32d..e4520b6528 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -1,5 +1,6 @@ import type { AgentContext } from '../../../agent' import type { Wallet } from '../../../wallet/Wallet' +import type { DidDocument } from '../../dids' import type { Routing } from '../services/ConnectionService' import { Subject } from 'rxjs' @@ -22,9 +23,11 @@ import { uuid } from '../../../utils/uuid' import { IndyWallet } from '../../../wallet/IndyWallet' import { AckMessage, AckStatus } from '../../common' import { DidKey, IndyAgentService } from '../../dids' +import { DidDocumentRole } from '../../dids/domain/DidDocumentRole' import { DidCommV1Service } from '../../dids/domain/service/DidCommV1Service' import { didDocumentJsonToNumAlgo1Did } from '../../dids/methods/peer/peerDidNumAlgo1' -import { DidRepository } from '../../dids/repository' +import { DidRecord, DidRepository } from '../../dids/repository' +import { DidRegistrarService } from '../../dids/services/DidRegistrarService' import { OutOfBandRole } from '../../oob/domain/OutOfBandRole' import { OutOfBandState } from '../../oob/domain/OutOfBandState' import { ConnectionRequestMessage, ConnectionResponseMessage, TrustPingMessage } from '../messages' @@ -43,9 +46,22 @@ import { ConnectionService } from '../services/ConnectionService' import { convertToNewDidDocument } from '../services/helpers' jest.mock('../repository/ConnectionRepository') +jest.mock('../../dids/services/DidRegistrarService') jest.mock('../../dids/repository/DidRepository') const ConnectionRepositoryMock = ConnectionRepository as jest.Mock const DidRepositoryMock = DidRepository as jest.Mock +const DidRegistrarServiceMock = DidRegistrarService as jest.Mock + +const didRegistrarService = new DidRegistrarServiceMock() +mockFunction(didRegistrarService.create).mockResolvedValue({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:peer:123', + didDocument: {} as DidDocument, + }, +}) const connectionImageUrl = 'https://example.com/image.png' @@ -78,13 +94,26 @@ describe('ConnectionService', () => { eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) connectionRepository = new ConnectionRepositoryMock() didRepository = new DidRepositoryMock() - connectionService = new ConnectionService(agentConfig.logger, connectionRepository, didRepository, eventEmitter) + connectionService = new ConnectionService( + agentConfig.logger, + connectionRepository, + didRepository, + didRegistrarService, + eventEmitter + ) myRouting = { recipientKey: Key.fromFingerprint('z6MkwFkSP4uv5PhhKJCGehtjuZedkotC7VF64xtMsxuM8R3W'), endpoints: agentConfig.endpoints ?? [], routingKeys: [], mediatorId: 'fakeMediatorId', } + + mockFunction(didRepository.getById).mockResolvedValue( + new DidRecord({ + id: 'did:peer:123', + role: DidDocumentRole.Created, + }) + ) }) describe('createRequest', () => { diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index 6b26c59a15..aa2960dbe0 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -2,6 +2,7 @@ import type { AgentContext } from '../../../agent' import type { AgentMessage } from '../../../agent/AgentMessage' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { AckMessage } from '../../common' +import type { PeerDidCreateOptions } from '../../dids/methods/peer/PeerDidRegistrar' import type { OutOfBandDidCommService } from '../../oob/domain/OutOfBandDidCommService' import type { OutOfBandRecord } from '../../oob/repository' import type { ConnectionStateChangedEvent } from '../ConnectionEvents' @@ -21,9 +22,10 @@ import { Logger } from '../../../logger' import { inject, injectable } from '../../../plugins' import { JsonTransformer } from '../../../utils/JsonTransformer' import { indyDidFromPublicKeyBase58 } from '../../../utils/did' -import { DidKey, IndyAgentService } from '../../dids' +import { DidKey, DidRegistrarService, IndyAgentService } from '../../dids' import { DidDocumentRole } from '../../dids/domain/DidDocumentRole' import { didKeyToVerkey } from '../../dids/helpers' +import { PeerDidNumAlgo } from '../../dids/methods/peer/didPeer' import { didDocumentJsonToNumAlgo1Did } from '../../dids/methods/peer/peerDidNumAlgo1' import { DidRecord, DidRepository } from '../../dids/repository' import { DidRecordMetadataKeys } from '../../dids/repository/didRecordMetadataTypes' @@ -59,6 +61,7 @@ export interface ConnectionRequestParams { export class ConnectionService { private connectionRepository: ConnectionRepository private didRepository: DidRepository + private didRegistrarService: DidRegistrarService private eventEmitter: EventEmitter private logger: Logger @@ -66,10 +69,12 @@ export class ConnectionService { @inject(InjectionSymbols.Logger) logger: Logger, connectionRepository: ConnectionRepository, didRepository: DidRepository, + didRegistrarService: DidRegistrarService, eventEmitter: EventEmitter ) { this.connectionRepository = connectionRepository this.didRepository = didRepository + this.didRegistrarService = didRegistrarService this.eventEmitter = eventEmitter this.logger = logger } @@ -101,10 +106,7 @@ export class ConnectionService { // We take just the first one for now. const [invitationDid] = outOfBandInvitation.invitationDids - const { did: peerDid } = await this.createDid(agentContext, { - role: DidDocumentRole.Created, - didDoc, - }) + const didDocument = await this.registerCreatedPeerDidDocument(agentContext, didDoc) const connectionRecord = await this.createConnection(agentContext, { protocol: HandshakeProtocol.Connections, @@ -112,7 +114,7 @@ export class ConnectionService { state: DidExchangeState.InvitationReceived, theirLabel: outOfBandInvitation.label, alias: config?.alias, - did: peerDid, + did: didDocument.id, mediatorId, autoAcceptConnection: config?.autoAcceptConnection, outOfBandId: outOfBandRecord.id, @@ -161,10 +163,7 @@ export class ConnectionService { }) } - const { did: peerDid } = await this.createDid(messageContext.agentContext, { - role: DidDocumentRole.Received, - didDoc: message.connection.didDoc, - }) + const didDocument = await this.storeReceivedPeerDidDocument(messageContext.agentContext, message.connection.didDoc) const connectionRecord = await this.createConnection(messageContext.agentContext, { protocol: HandshakeProtocol.Connections, @@ -173,7 +172,7 @@ export class ConnectionService { theirLabel: message.label, imageUrl: message.imageUrl, outOfBandId: outOfBandRecord.id, - theirDid: peerDid, + theirDid: didDocument.id, threadId: message.threadId, mediatorId: outOfBandRecord.mediatorId, autoAcceptConnection: outOfBandRecord.autoAcceptConnection, @@ -210,10 +209,7 @@ export class ConnectionService { ) ) - const { did: peerDid } = await this.createDid(agentContext, { - role: DidDocumentRole.Created, - didDoc, - }) + const didDocument = await this.registerCreatedPeerDidDocument(agentContext, didDoc) const connection = new Connection({ did: didDoc.id, @@ -233,7 +229,7 @@ export class ConnectionService { connectionSig: await signData(connectionJson, agentContext.wallet, signingKey), }) - connectionRecord.did = peerDid + connectionRecord.did = didDocument.id await this.updateState(agentContext, connectionRecord, DidExchangeState.ResponseSent) this.logger.debug(`Create message ${ConnectionResponseMessage.type.messageTypeUri} end`, { @@ -309,12 +305,9 @@ export class ConnectionService { throw new AriesFrameworkError('DID Document is missing.') } - const { did: peerDid } = await this.createDid(messageContext.agentContext, { - role: DidDocumentRole.Received, - didDoc: connection.didDoc, - }) + const didDocument = await this.storeReceivedPeerDidDocument(messageContext.agentContext, connection.didDoc) - connectionRecord.theirDid = peerDid + connectionRecord.theirDid = didDocument.id connectionRecord.threadId = message.threadId await this.updateState(messageContext.agentContext, connectionRecord, DidExchangeState.ResponseReceived) @@ -632,15 +625,52 @@ export class ConnectionService { return connectionRecord } - private async createDid(agentContext: AgentContext, { role, didDoc }: { role: DidDocumentRole; didDoc: DidDoc }) { + private async registerCreatedPeerDidDocument(agentContext: AgentContext, didDoc: DidDoc) { + // Convert the legacy did doc to a new did document + const didDocument = convertToNewDidDocument(didDoc) + + // Register did:peer document. This will generate the id property and save it to a did record + const result = await this.didRegistrarService.create(agentContext, { + method: 'peer', + didDocument, + options: { + numAlgo: PeerDidNumAlgo.GenesisDoc, + }, + }) + + if (result.didState?.state !== 'finished') { + throw new AriesFrameworkError(`Did document creation failed: ${JSON.stringify(result.didState)}`) + } + + this.logger.debug(`Did document with did ${result.didState.did} created.`, { + did: result.didState.did, + didDocument: result.didState.didDocument, + }) + + const didRecord = await this.didRepository.getById(agentContext, result.didState.did) + + // Store the unqualified did with the legacy did document in the metadata + // Can be removed at a later stage if we know for sure we don't need it anymore + didRecord.metadata.set(DidRecordMetadataKeys.LegacyDid, { + unqualifiedDid: didDoc.id, + didDocumentString: JsonTransformer.serialize(didDoc), + }) + + await this.didRepository.update(agentContext, didRecord) + return result.didState.didDocument + } + + private async storeReceivedPeerDidDocument(agentContext: AgentContext, didDoc: DidDoc) { // Convert the legacy did doc to a new did document const didDocument = convertToNewDidDocument(didDoc) + // TODO: Move this into the didcomm module, and add a method called store received did document. + // This can be called from both the did exchange and the connection protocol. const peerDid = didDocumentJsonToNumAlgo1Did(didDocument.toJSON()) didDocument.id = peerDid const didRecord = new DidRecord({ id: peerDid, - role, + role: DidDocumentRole.Received, didDocument, tags: { // We need to save the recipientKeys, so we can find the associated did @@ -665,7 +695,7 @@ export class ConnectionService { await this.didRepository.save(agentContext, didRecord) this.logger.debug('Did record created.', didRecord) - return { did: peerDid, didDocument } + return didDocument } private createDidDoc(routing: Routing) { diff --git a/packages/core/src/modules/dids/DidsApi.ts b/packages/core/src/modules/dids/DidsApi.ts index 7599be95cb..59134e5f6d 100644 --- a/packages/core/src/modules/dids/DidsApi.ts +++ b/packages/core/src/modules/dids/DidsApi.ts @@ -1,37 +1,102 @@ -import type { Key } from '../../crypto' -import type { DidResolutionOptions } from './types' +import type { + DidCreateOptions, + DidCreateResult, + DidDeactivateOptions, + DidDeactivateResult, + DidResolutionOptions, + DidUpdateOptions, + DidUpdateResult, +} from './types' import { AgentContext } from '../../agent' import { injectable } from '../../plugins' +import { DidsModuleConfig } from './DidsModuleConfig' import { DidRepository } from './repository' -import { DidResolverService } from './services/DidResolverService' +import { DidRegistrarService, DidResolverService } from './services' @injectable() export class DidsApi { - private resolverService: DidResolverService + public config: DidsModuleConfig + + private didResolverService: DidResolverService + private didRegistrarService: DidRegistrarService private didRepository: DidRepository private agentContext: AgentContext - public constructor(resolverService: DidResolverService, didRepository: DidRepository, agentContext: AgentContext) { - this.resolverService = resolverService + public constructor( + didResolverService: DidResolverService, + didRegistrarService: DidRegistrarService, + didRepository: DidRepository, + agentContext: AgentContext, + config: DidsModuleConfig + ) { + this.didResolverService = didResolverService + this.didRegistrarService = didRegistrarService this.didRepository = didRepository this.agentContext = agentContext + this.config = config } + /** + * Resolve a did to a did document. + * + * Follows the interface as defined in https://w3c-ccg.github.io/did-resolution/ + */ public resolve(didUrl: string, options?: DidResolutionOptions) { - return this.resolverService.resolve(this.agentContext, didUrl, options) + return this.didResolverService.resolve(this.agentContext, didUrl, options) } - public resolveDidDocument(didUrl: string) { - return this.resolverService.resolveDidDocument(this.agentContext, didUrl) + /** + * Create, register and store a did and did document. + * + * Follows the interface as defined in https://identity.foundation/did-registration + */ + public create( + options: CreateOptions + ): Promise { + return this.didRegistrarService.create(this.agentContext, options) + } + + /** + * Update an existing did document. + * + * Follows the interface as defined in https://identity.foundation/did-registration + */ + public update( + options: UpdateOptions + ): Promise { + return this.didRegistrarService.update(this.agentContext, options) } - public findByRecipientKey(recipientKey: Key) { - return this.didRepository.findByRecipientKey(this.agentContext, recipientKey) + /** + * Deactivate an existing did. + * + * Follows the interface as defined in https://identity.foundation/did-registration + */ + public deactivate( + options: DeactivateOptions + ): Promise { + return this.didRegistrarService.deactivate(this.agentContext, options) + } + + /** + * Resolve a did to a did document. This won't return the associated metadata as defined + * in the did resolution specification, and will throw an error if the did document could not + * be resolved. + */ + public resolveDidDocument(didUrl: string) { + return this.didResolverService.resolveDidDocument(this.agentContext, didUrl) } - public findAllByRecipientKey(recipientKey: Key) { - return this.didRepository.findAllByRecipientKey(this.agentContext, recipientKey) + /** + * Get a list of all dids created by the agent. This will return a list of {@link DidRecord} objects. + * Each document will have an id property with the value of the did. Optionally, it will contain a did document, + * but this is only for documents that can't be resolved from the did itself or remotely. + * + * You can call `${@link DidsModule.resolve} to resolve the did document based on the did itself. + */ + public getCreatedDids({ method }: { method?: string } = {}) { + return this.didRepository.getCreatedDids(this.agentContext, { method }) } } diff --git a/packages/core/src/modules/dids/DidsModule.ts b/packages/core/src/modules/dids/DidsModule.ts index 5cc570ec48..0a43f0a154 100644 --- a/packages/core/src/modules/dids/DidsModule.ts +++ b/packages/core/src/modules/dids/DidsModule.ts @@ -1,10 +1,19 @@ import type { DependencyManager, Module } from '../../plugins' +import type { DidsModuleConfigOptions } from './DidsModuleConfig' import { DidsApi } from './DidsApi' +import { DidsModuleConfig } from './DidsModuleConfig' +import { DidResolverToken, DidRegistrarToken } from './domain' import { DidRepository } from './repository' -import { DidResolverService } from './services' +import { DidResolverService, DidRegistrarService } from './services' export class DidsModule implements Module { + public readonly config: DidsModuleConfig + + public constructor(config?: DidsModuleConfigOptions) { + this.config = new DidsModuleConfig(config) + } + /** * Registers the dependencies of the dids module module on the dependency manager. */ @@ -12,8 +21,22 @@ export class DidsModule implements Module { // Api dependencyManager.registerContextScoped(DidsApi) + // Config + dependencyManager.registerInstance(DidsModuleConfig, this.config) + // Services dependencyManager.registerSingleton(DidResolverService) + dependencyManager.registerSingleton(DidRegistrarService) dependencyManager.registerSingleton(DidRepository) + + // Register all did resolvers + for (const Resolver of this.config.resolvers) { + dependencyManager.registerSingleton(DidResolverToken, Resolver) + } + + // Register all did registrars + for (const Registrar of this.config.registrars) { + dependencyManager.registerSingleton(DidRegistrarToken, Registrar) + } } } diff --git a/packages/core/src/modules/dids/DidsModuleConfig.ts b/packages/core/src/modules/dids/DidsModuleConfig.ts new file mode 100644 index 0000000000..e05bd0daca --- /dev/null +++ b/packages/core/src/modules/dids/DidsModuleConfig.ts @@ -0,0 +1,68 @@ +import type { Constructor } from '../../utils/mixins' +import type { DidRegistrar, DidResolver } from './domain' + +import { + KeyDidRegistrar, + SovDidRegistrar, + PeerDidRegistrar, + KeyDidResolver, + PeerDidResolver, + SovDidResolver, + WebDidResolver, +} from './methods' + +/** + * DidsModuleConfigOptions defines the interface for the options of the DidsModuleConfig class. + * This can contain optional parameters that have default values in the config class itself. + */ +export interface DidsModuleConfigOptions { + /** + * List of did registrars that should be registered on the dids module. The registrar must + * follow the {@link DidRegistrar} interface, and must be constructable. The registrar will be injected + * into the `DidRegistrarService` and should be decorated with the `@injectable` decorator. + * + * If no registrars are provided, the default registrars will be used. The `PeerDidRegistrar` will ALWAYS be + * registered, as it is needed for the connections and out of band module to function. Other did methods can be + * disabled. + * + * @default [KeyDidRegistrar, SovDidRegistrar, PeerDidRegistrar] + */ + registrars?: Constructor[] + + /** + * List of did resolvers that should be registered on the dids module. The resolver must + * follow the {@link DidResolver} interface, and must be constructable. The resolver will be injected + * into the `DidResolverService` and should be decorated with the `@injectable` decorator. + * + * If no resolvers are provided, the default resolvers will be used. The `PeerDidResolver` will ALWAYS be + * registered, as it is needed for the connections and out of band module to function. Other did methods can be + * disabled. + * + * @default [SovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver] + */ + resolvers?: Constructor[] +} + +export class DidsModuleConfig { + private options: DidsModuleConfigOptions + + public constructor(options?: DidsModuleConfigOptions) { + this.options = options ?? {} + } + + /** See {@link DidsModuleConfigOptions.registrars} */ + public get registrars() { + const registrars = this.options.registrars ?? [KeyDidRegistrar, SovDidRegistrar, PeerDidRegistrar] + + // If the peer did registrar is not included yet, add it + return registrars.includes(PeerDidRegistrar) ? registrars : [...registrars, PeerDidRegistrar] + } + + /** See {@link DidsModuleConfigOptions.resolvers} */ + public get resolvers() { + const resolvers = this.options.resolvers ?? [SovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver] + + // If the peer did resolver is not included yet, add it + return resolvers.includes(PeerDidResolver) ? resolvers : [...resolvers, PeerDidResolver] + } +} diff --git a/packages/core/src/modules/dids/__tests__/DidsModule.test.ts b/packages/core/src/modules/dids/__tests__/DidsModule.test.ts index 00926a9ace..3a372fea59 100644 --- a/packages/core/src/modules/dids/__tests__/DidsModule.test.ts +++ b/packages/core/src/modules/dids/__tests__/DidsModule.test.ts @@ -1,8 +1,22 @@ +import type { Constructor } from '../../../utils/mixins' +import type { DidRegistrar, DidResolver } from '../domain' + import { DependencyManager } from '../../../plugins/DependencyManager' import { DidsApi } from '../DidsApi' import { DidsModule } from '../DidsModule' +import { DidsModuleConfig } from '../DidsModuleConfig' +import { DidRegistrarToken, DidResolverToken } from '../domain' +import { + KeyDidRegistrar, + KeyDidResolver, + PeerDidRegistrar, + PeerDidResolver, + SovDidRegistrar, + SovDidResolver, + WebDidResolver, +} from '../methods' import { DidRepository } from '../repository' -import { DidResolverService } from '../services' +import { DidRegistrarService, DidResolverService } from '../services' jest.mock('../../../plugins/DependencyManager') const DependencyManagerMock = DependencyManager as jest.Mock @@ -11,13 +25,40 @@ const dependencyManager = new DependencyManagerMock() describe('DidsModule', () => { test('registers dependencies on the dependency manager', () => { - new DidsModule().register(dependencyManager) + const didsModule = new DidsModule() + didsModule.register(dependencyManager) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(DidsApi) - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(2) + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(DidsModuleConfig, didsModule.config) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(10) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRepository) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, SovDidResolver) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, WebDidResolver) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, KeyDidResolver) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, PeerDidResolver) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarToken, KeyDidRegistrar) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarToken, SovDidRegistrar) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarToken, PeerDidRegistrar) + }) + + test('takes the values from the dids config', () => { + const registrar = {} as Constructor + const resolver = {} as Constructor + + new DidsModule({ + registrars: [registrar], + resolvers: [resolver], + }).register(dependencyManager) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, resolver) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarToken, registrar) }) }) diff --git a/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts b/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts new file mode 100644 index 0000000000..08edee502e --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts @@ -0,0 +1,46 @@ +import type { Constructor } from '../../../utils/mixins' +import type { DidRegistrar, DidResolver } from '../domain' + +import { + KeyDidRegistrar, + SovDidRegistrar, + PeerDidRegistrar, + KeyDidResolver, + PeerDidResolver, + SovDidResolver, + WebDidResolver, +} from '..' +import { DidsModuleConfig } from '../DidsModuleConfig' + +describe('DidsModuleConfig', () => { + test('sets default values', () => { + const config = new DidsModuleConfig() + + expect(config.registrars).toEqual([KeyDidRegistrar, SovDidRegistrar, PeerDidRegistrar]) + expect(config.resolvers).toEqual([SovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver]) + }) + + test('sets values', () => { + const registrars = [PeerDidRegistrar, {} as Constructor] + const resolvers = [PeerDidResolver, {} as Constructor] + const config = new DidsModuleConfig({ + registrars, + resolvers, + }) + + expect(config.registrars).toEqual(registrars) + expect(config.resolvers).toEqual(resolvers) + }) + + test('adds peer did resolver and registrar if not provided in config', () => { + const registrar = {} as Constructor + const resolver = {} as Constructor + const config = new DidsModuleConfig({ + registrars: [registrar], + resolvers: [resolver], + }) + + expect(config.registrars).toEqual([registrar, PeerDidRegistrar]) + expect(config.resolvers).toEqual([resolver, PeerDidResolver]) + }) +}) diff --git a/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts new file mode 100644 index 0000000000..264dffe6f9 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts @@ -0,0 +1,267 @@ +import type { KeyDidCreateOptions } from '../methods/key/KeyDidRegistrar' +import type { PeerDidNumAlgo0CreateOptions } from '../methods/peer/PeerDidRegistrar' +import type { SovDidCreateOptions } from '../methods/sov/SovDidRegistrar' +import type { Wallet } from '@aries-framework/core' + +import { convertPublicKeyToX25519, generateKeyPairFromSeed } from '@stablelib/ed25519' + +import { genesisPath, getBaseConfig } from '../../../../tests/helpers' +import { Agent } from '../../../agent/Agent' +import { KeyType } from '../../../crypto' +import { TypedArrayEncoder } from '../../../utils' +import { indyDidFromPublicKeyBase58 } from '../../../utils/did' +import { PeerDidNumAlgo } from '../methods/peer/didPeer' + +import { InjectionSymbols, JsonTransformer } from '@aries-framework/core' + +const { config, agentDependencies } = getBaseConfig('Faber Dids Registrar', { + indyLedgers: [ + { + id: `localhost`, + isProduction: false, + genesisPath, + transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + }, + ], +}) + +describe('dids', () => { + let agent: Agent + + beforeAll(async () => { + agent = new Agent(config, agentDependencies) + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + it('should create a did:key did', async () => { + const did = await agent.dids.create({ + method: 'key', + options: { + keyType: KeyType.Ed25519, + }, + secret: { + seed: '96213c3d7fc8d4d6754c7a0fd969598e', + }, + }) + + // Same seed should resolve to same did:key + expect(JsonTransformer.toJSON(did)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + id: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + type: 'Ed25519VerificationKey2018', + controller: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + publicKeyBase58: 'ApA26cozGW5Maa62TNTwtgcxrb7bYjAmf9aQ5cYruCDE', + }, + ], + service: undefined, + authentication: [ + 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + ], + assertionMethod: [ + 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + ], + keyAgreement: [ + { + id: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6LSjDbRQQKm9HM4qPBErYyX93BCSzSk1XkwP5EgDrL6eNhh', + type: 'X25519KeyAgreementKey2019', + controller: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + publicKeyBase58: '8YRFt6Wu3pdKjzoUKuTZpSxibqudJvanW6WzjPgZvzvw', + }, + ], + capabilityInvocation: [ + 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + ], + capabilityDelegation: [ + 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + ], + id: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + }, + secret: { seed: '96213c3d7fc8d4d6754c7a0fd969598e' }, + }, + }) + }) + + it('should create a did:peer did', async () => { + const did = await agent.dids.create({ + method: 'peer', + options: { + keyType: KeyType.Ed25519, + numAlgo: PeerDidNumAlgo.InceptionKeyWithoutDoc, + }, + secret: { + seed: 'e008ef10b7c163114b3857542b3736eb', + }, + }) + + // Same seed should resolve to same did:peer + expect(JsonTransformer.toJSON(did)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:peer:0z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + id: 'did:peer:0z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh#z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh', + type: 'Ed25519VerificationKey2018', + controller: 'did:peer:0z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh', + publicKeyBase58: 'GLsyPBT2AgMne8XUvmZKkqLUuFkSjLp3ibkcjc6gjhyK', + }, + ], + service: undefined, + authentication: [ + 'did:peer:0z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh#z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh', + ], + assertionMethod: [ + 'did:peer:0z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh#z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh', + ], + keyAgreement: [ + { + id: 'did:peer:0z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh#z6LSdqscQpQy12kNU1kYf7odtabo2Nhr3x3coUjsUZgwxwCj', + type: 'X25519KeyAgreementKey2019', + controller: 'did:peer:0z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh', + publicKeyBase58: '3AhStWc6ua2dNdNn8UHgZzPKBEAjMLsTvW2Bz73RFZRy', + }, + ], + capabilityInvocation: [ + 'did:peer:0z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh#z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh', + ], + capabilityDelegation: [ + 'did:peer:0z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh#z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh', + ], + id: 'did:peer:0z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh', + }, + secret: { seed: 'e008ef10b7c163114b3857542b3736eb' }, + }, + }) + }) + + it('should create a did:sov did', async () => { + // Generate a seed and the indy did. This allows us to create a new did every time + // but still check if the created output document is as expected. + const seed = Array(32 + 1) + .join((Math.random().toString(36) + '00000000000000000').slice(2, 18)) + .slice(0, 32) + + const publicKeyEd25519 = generateKeyPairFromSeed(TypedArrayEncoder.fromString(seed)).publicKey + const x25519PublicKeyBase58 = TypedArrayEncoder.toBase58(convertPublicKeyToX25519(publicKeyEd25519)) + const ed25519PublicKeyBase58 = TypedArrayEncoder.toBase58(publicKeyEd25519) + const indyDid = indyDidFromPublicKeyBase58(ed25519PublicKeyBase58) + + const wallet = agent.injectionContainer.resolve(InjectionSymbols.Wallet) + // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain, @typescript-eslint/no-non-null-assertion + const submitterDid = `did:sov:${wallet.publicDid?.did!}` + + const did = await agent.dids.create({ + method: 'sov', + options: { + submitterDid, + alias: 'Alias', + endpoints: { + endpoint: 'https://example.com/endpoint', + types: ['DIDComm', 'did-communication', 'endpoint'], + routingKeys: ['a-routing-key'], + }, + }, + secret: { + seed, + }, + }) + + expect(JsonTransformer.toJSON(did)).toMatchObject({ + didDocumentMetadata: { + qualifiedIndyDid: `did:indy:localhost:${indyDid}`, + }, + didRegistrationMetadata: { + indyNamespace: 'localhost', + }, + didState: { + state: 'finished', + did: `did:sov:${indyDid}`, + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + id: `did:sov:${indyDid}#key-1`, + type: 'Ed25519VerificationKey2018', + controller: `did:sov:${indyDid}`, + publicKeyBase58: ed25519PublicKeyBase58, + }, + { + id: `did:sov:${indyDid}#key-agreement-1`, + type: 'X25519KeyAgreementKey2019', + controller: `did:sov:${indyDid}`, + publicKeyBase58: x25519PublicKeyBase58, + }, + ], + service: [ + { + id: `did:sov:${indyDid}#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + accept: ['didcomm/aip2;env=rfc19'], + id: `did:sov:${indyDid}#did-communication`, + priority: 0, + recipientKeys: [`did:sov:${indyDid}#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + }, + { + accept: ['didcomm/v2'], + id: `did:sov:${indyDid}#didcomm-1`, + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDComm', + }, + ], + authentication: [`did:sov:${indyDid}#key-1`], + assertionMethod: [`did:sov:${indyDid}#key-1`], + keyAgreement: [`did:sov:${indyDid}#key-agreement-1`], + capabilityInvocation: undefined, + capabilityDelegation: undefined, + id: `did:sov:${indyDid}`, + }, + secret: { + seed, + }, + }, + }) + }) +}) diff --git a/packages/core/tests/dids.test.ts b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts similarity index 77% rename from packages/core/tests/dids.test.ts rename to packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts index 50c90c704d..39e3710c19 100644 --- a/packages/core/tests/dids.test.ts +++ b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts @@ -1,9 +1,13 @@ -import { Agent } from '../src/agent/Agent' -import { JsonTransformer } from '../src/utils/JsonTransformer' +import type { Wallet } from '../../../wallet' -import { getBaseConfig } from './helpers' +import { convertPublicKeyToX25519 } from '@stablelib/ed25519' -const { config, agentDependencies } = getBaseConfig('Faber Dids', {}) +import { getBaseConfig } from '../../../../tests/helpers' +import { sleep } from '../../../utils/sleep' + +import { InjectionSymbols, Key, KeyType, JsonTransformer, Agent } from '@aries-framework/core' + +const { config, agentDependencies } = getBaseConfig('Faber Dids Resolver', {}) describe('dids', () => { let agent: Agent @@ -19,37 +23,51 @@ describe('dids', () => { }) it('should resolve a did:sov did', async () => { - const did = await agent.dids.resolve(`did:sov:TL1EaPFCZ8Si5aUrqScBDt`) + const wallet = agent.injectionContainer.resolve(InjectionSymbols.Wallet) + const { did: unqualifiedDid, verkey: publicKeyBase58 } = await wallet.createDid() - expect(JsonTransformer.toJSON(did)).toMatchObject({ + await agent.ledger.registerPublicDid(unqualifiedDid, publicKeyBase58, 'Alias', 'TRUSTEE') + + // Terrible, but the did can't be immediately resolved, so we need to wait a bit + await sleep(1000) + + const did = `did:sov:${unqualifiedDid}` + const didResult = await agent.dids.resolve(did) + + const x25519PublicKey = convertPublicKeyToX25519( + Key.fromPublicKeyBase58(publicKeyBase58, KeyType.Ed25519).publicKey + ) + const x25519PublicKeyBase58 = Key.fromPublicKey(x25519PublicKey, KeyType.X25519).publicKeyBase58 + + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ didDocument: { '@context': [ 'https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1', 'https://w3id.org/security/suites/x25519-2019/v1', ], - id: 'did:sov:TL1EaPFCZ8Si5aUrqScBDt', + id: did, alsoKnownAs: undefined, controller: undefined, verificationMethod: [ { type: 'Ed25519VerificationKey2018', - controller: 'did:sov:TL1EaPFCZ8Si5aUrqScBDt', - id: 'did:sov:TL1EaPFCZ8Si5aUrqScBDt#key-1', - publicKeyBase58: 'FMGcFuU3QwAQLywxvmEnSorQT3NwU9wgDMMTaDFtvswm', + controller: did, + id: `${did}#key-1`, + publicKeyBase58, }, { - controller: 'did:sov:TL1EaPFCZ8Si5aUrqScBDt', + controller: did, type: 'X25519KeyAgreementKey2019', - id: 'did:sov:TL1EaPFCZ8Si5aUrqScBDt#key-agreement-1', - publicKeyBase58: '6oKfyWDYRpbutQWDUu8ots6GoqAZJ9HYRzPuuEiqfyM', + id: `${did}#key-agreement-1`, + publicKeyBase58: x25519PublicKeyBase58, }, ], capabilityDelegation: undefined, capabilityInvocation: undefined, - authentication: ['did:sov:TL1EaPFCZ8Si5aUrqScBDt#key-1'], - assertionMethod: ['did:sov:TL1EaPFCZ8Si5aUrqScBDt#key-1'], - keyAgreement: ['did:sov:TL1EaPFCZ8Si5aUrqScBDt#key-agreement-1'], + authentication: [`${did}#key-1`], + assertionMethod: [`${did}#key-1`], + keyAgreement: [`${did}#key-agreement-1`], service: undefined, }, didDocumentMetadata: {}, diff --git a/packages/core/src/modules/dids/__tests__/peer-did.test.ts b/packages/core/src/modules/dids/__tests__/peer-did.test.ts index 38f5747b17..14eac24fdd 100644 --- a/packages/core/src/modules/dids/__tests__/peer-did.test.ts +++ b/packages/core/src/modules/dids/__tests__/peer-did.test.ts @@ -1,5 +1,4 @@ import type { AgentContext } from '../../../agent' -import type { IndyLedgerService } from '../../ledger' import { Subject } from 'rxjs' @@ -14,6 +13,7 @@ import { DidCommV1Service, DidDocument, DidDocumentBuilder } from '../domain' import { DidDocumentRole } from '../domain/DidDocumentRole' import { convertPublicKeyToX25519, getEd25519VerificationMethod } from '../domain/key-type/ed25519' import { getX25519VerificationMethod } from '../domain/key-type/x25519' +import { PeerDidResolver } from '../methods' import { DidKey } from '../methods/key' import { getNumAlgoFromPeerDid, PeerDidNumAlgo } from '../methods/peer/didPeer' import { didDocumentJsonToNumAlgo1Did } from '../methods/peer/peerDidNumAlgo1' @@ -42,7 +42,7 @@ describe('peer dids', () => { didRepository = new DidRepository(storageService, eventEmitter) // Mocking IndyLedgerService as we're only interested in the did:peer resolver - didResolverService = new DidResolverService({} as unknown as IndyLedgerService, didRepository, config.logger) + didResolverService = new DidResolverService(config.logger, [new PeerDidResolver(didRepository)]) }) afterEach(async () => { diff --git a/packages/core/src/modules/dids/domain/DidRegistrar.ts b/packages/core/src/modules/dids/domain/DidRegistrar.ts new file mode 100644 index 0000000000..200cda6ab6 --- /dev/null +++ b/packages/core/src/modules/dids/domain/DidRegistrar.ts @@ -0,0 +1,19 @@ +import type { AgentContext } from '../../../agent' +import type { + DidCreateOptions, + DidDeactivateOptions, + DidUpdateOptions, + DidCreateResult, + DidUpdateResult, + DidDeactivateResult, +} from '../types' + +export const DidRegistrarToken = Symbol('DidRegistrar') + +export interface DidRegistrar { + readonly supportedMethods: string[] + + create(agentContext: AgentContext, options: DidCreateOptions): Promise + update(agentContext: AgentContext, options: DidUpdateOptions): Promise + deactivate(agentContext: AgentContext, options: DidDeactivateOptions): Promise +} diff --git a/packages/core/src/modules/dids/domain/DidResolver.ts b/packages/core/src/modules/dids/domain/DidResolver.ts index 050ea2cd97..e4512a1e57 100644 --- a/packages/core/src/modules/dids/domain/DidResolver.ts +++ b/packages/core/src/modules/dids/domain/DidResolver.ts @@ -1,6 +1,8 @@ import type { AgentContext } from '../../../agent' import type { ParsedDid, DidResolutionResult, DidResolutionOptions } from '../types' +export const DidResolverToken = Symbol('DidResolver') + export interface DidResolver { readonly supportedMethods: string[] resolve( diff --git a/packages/core/src/modules/dids/domain/__tests__/DidDocument.test.ts b/packages/core/src/modules/dids/domain/__tests__/DidDocument.test.ts index 607df90e01..09837a0d2e 100644 --- a/packages/core/src/modules/dids/domain/__tests__/DidDocument.test.ts +++ b/packages/core/src/modules/dids/domain/__tests__/DidDocument.test.ts @@ -213,7 +213,7 @@ describe('Did | DidDocument', () => { }) describe('findVerificationMethodByKeyType', () => { - it('return first verfication method that match key type', async () => { + it('return first verification method that match key type', async () => { expect(await findVerificationMethodByKeyType('Ed25519VerificationKey2018', didDocumentInstance)).toBeInstanceOf( VerificationMethod ) diff --git a/packages/core/src/modules/dids/domain/index.ts b/packages/core/src/modules/dids/domain/index.ts index bf0ff1c854..cae70d066f 100644 --- a/packages/core/src/modules/dids/domain/index.ts +++ b/packages/core/src/modules/dids/domain/index.ts @@ -2,3 +2,6 @@ export * from './service' export * from './verificationMethod' export * from './DidDocument' export * from './DidDocumentBuilder' +export * from './DidDocumentRole' +export * from './DidRegistrar' +export * from './DidResolver' diff --git a/packages/core/src/modules/dids/domain/parse.ts b/packages/core/src/modules/dids/domain/parse.ts index aebeccec6f..ab293ee878 100644 --- a/packages/core/src/modules/dids/domain/parse.ts +++ b/packages/core/src/modules/dids/domain/parse.ts @@ -3,7 +3,7 @@ import type { ParsedDid } from '../types' import { parse } from 'did-resolver' export function parseDid(did: string): ParsedDid { - const parsed = parse(did) + const parsed = tryParseDid(did) if (!parsed) { throw new Error(`Error parsing did '${did}'`) @@ -11,3 +11,7 @@ export function parseDid(did: string): ParsedDid { return parsed } + +export function tryParseDid(did: string): ParsedDid | null { + return parse(did) +} diff --git a/packages/core/src/modules/dids/index.ts b/packages/core/src/modules/dids/index.ts index d9473ea73f..9ad363c0a2 100644 --- a/packages/core/src/modules/dids/index.ts +++ b/packages/core/src/modules/dids/index.ts @@ -4,4 +4,5 @@ export * from './DidsApi' export * from './repository' export * from './services' export * from './DidsModule' -export { DidKey } from './methods/key/DidKey' +export * from './methods' +export * from './DidsModuleConfig' diff --git a/packages/core/src/modules/dids/methods/index.ts b/packages/core/src/modules/dids/methods/index.ts new file mode 100644 index 0000000000..ebacc7f2c2 --- /dev/null +++ b/packages/core/src/modules/dids/methods/index.ts @@ -0,0 +1,4 @@ +export * from './key' +export * from './peer' +export * from './sov' +export * from './web' diff --git a/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts b/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts new file mode 100644 index 0000000000..7fcd557e84 --- /dev/null +++ b/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts @@ -0,0 +1,129 @@ +import type { AgentContext } from '../../../../agent' +import type { KeyType } from '../../../../crypto' +import type { DidRegistrar } from '../../domain/DidRegistrar' +import type { DidCreateOptions, DidCreateResult, DidDeactivateResult, DidUpdateResult } from '../../types' + +import { injectable } from '../../../../plugins' +import { DidDocumentRole } from '../../domain/DidDocumentRole' +import { DidRepository, DidRecord } from '../../repository' + +import { DidKey } from './DidKey' + +@injectable() +export class KeyDidRegistrar implements DidRegistrar { + public readonly supportedMethods = ['key'] + private didRepository: DidRepository + + public constructor(didRepository: DidRepository) { + this.didRepository = didRepository + } + + public async create(agentContext: AgentContext, options: KeyDidCreateOptions): Promise { + const keyType = options.options.keyType + const seed = options.secret?.seed + + if (!keyType) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Missing key type', + }, + } + } + + if (seed && (typeof seed !== 'string' || seed.length !== 32)) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Invalid seed provided', + }, + } + } + + try { + const key = await agentContext.wallet.createKey({ + keyType, + seed, + }) + + const didKey = new DidKey(key) + + // Save the did so we know we created it and can issue with it + const didRecord = new DidRecord({ + id: didKey.did, + role: DidDocumentRole.Created, + }) + await this.didRepository.save(agentContext, didRecord) + + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: didKey.did, + didDocument: didKey.didDocument, + secret: { + // FIXME: the uni-registrar creates the seed in the registrar method + // if it doesn't exist so the seed can always be returned. Currently + // we can only return it if the seed was passed in by the user. Once + // we have a secure method for generating seeds we should use the same + // approach + seed: options.secret?.seed, + }, + }, + } + } catch (error) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async update(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notSupported: cannot update did:key did`, + }, + } + } + + public async deactivate(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notSupported: cannot deactivate did:key did`, + }, + } + } +} + +export interface KeyDidCreateOptions extends DidCreateOptions { + method: 'key' + // For now we don't support creating a did:key with a did or did document + did?: never + didDocument?: never + options: { + keyType: KeyType + } + secret?: { + seed?: string + } +} + +// Update and Deactivate not supported for did:key +export type KeyDidUpdateOptions = never +export type KeyDidDeactivateOptions = never diff --git a/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts b/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts index 41f4a0e221..4929e76fec 100644 --- a/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts +++ b/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts @@ -2,8 +2,11 @@ import type { AgentContext } from '../../../../agent' import type { DidResolver } from '../../domain/DidResolver' import type { DidResolutionResult } from '../../types' +import { injectable } from '../../../../plugins' + import { DidKey } from './DidKey' +@injectable() export class KeyDidResolver implements DidResolver { public readonly supportedMethods = ['key'] diff --git a/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts new file mode 100644 index 0000000000..19bc6bf29e --- /dev/null +++ b/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts @@ -0,0 +1,153 @@ +import type { Wallet } from '../../../../../wallet' + +import { getAgentContext, mockFunction } from '../../../../../../tests/helpers' +import { KeyType } from '../../../../../crypto' +import { Key } from '../../../../../crypto/Key' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { DidDocumentRole } from '../../../domain/DidDocumentRole' +import { DidRepository } from '../../../repository/DidRepository' +import { KeyDidRegistrar } from '../KeyDidRegistrar' + +import didKeyz6MksLeFixture from './__fixtures__/didKeyz6MksLe.json' + +jest.mock('../../../repository/DidRepository') +const DidRepositoryMock = DidRepository as jest.Mock + +const walletMock = { + createKey: jest.fn(() => Key.fromFingerprint('z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU')), +} as unknown as Wallet + +const agentContext = getAgentContext({ + wallet: walletMock, +}) + +describe('DidRegistrar', () => { + describe('KeyDidRegistrar', () => { + let keyDidRegistrar: KeyDidRegistrar + let didRepositoryMock: DidRepository + + beforeEach(() => { + didRepositoryMock = new DidRepositoryMock() + keyDidRegistrar = new KeyDidRegistrar(didRepositoryMock) + }) + + it('should correctly create a did:key document using Ed25519 key type', async () => { + const seed = '96213c3d7fc8d4d6754c712fd969598e' + + const result = await keyDidRegistrar.create(agentContext, { + method: 'key', + options: { + keyType: KeyType.Ed25519, + }, + secret: { + seed, + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:key:z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU', + didDocument: didKeyz6MksLeFixture, + secret: { + seed: '96213c3d7fc8d4d6754c712fd969598e', + }, + }, + }) + + expect(walletMock.createKey).toHaveBeenCalledWith({ keyType: KeyType.Ed25519, seed }) + }) + + it('should return an error state if no key type is provided', async () => { + const result = await keyDidRegistrar.create(agentContext, { + method: 'key', + // @ts-expect-error - key type is required in interface + options: {}, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Missing key type', + }, + }) + }) + + it('should return an error state if an invalid seed is provided', async () => { + const result = await keyDidRegistrar.create(agentContext, { + method: 'key', + + options: { + keyType: KeyType.Ed25519, + }, + secret: { + seed: 'invalid', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Invalid seed provided', + }, + }) + }) + + it('should store the did document', async () => { + const seed = '96213c3d7fc8d4d6754c712fd969598e' + const did = 'did:key:z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU' + + await keyDidRegistrar.create(agentContext, { + method: 'key', + + options: { + keyType: KeyType.Ed25519, + }, + secret: { + seed, + }, + }) + + expect(didRepositoryMock.save).toHaveBeenCalledTimes(1) + const [, didRecord] = mockFunction(didRepositoryMock.save).mock.calls[0] + + expect(didRecord).toMatchObject({ + id: did, + role: DidDocumentRole.Created, + didDocument: undefined, + }) + }) + + it('should return an error state when calling update', async () => { + const result = await keyDidRegistrar.update() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notSupported: cannot update did:key did`, + }, + }) + }) + + it('should return an error state when calling deactivate', async () => { + const result = await keyDidRegistrar.deactivate() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notSupported: cannot deactivate did:key did`, + }, + }) + }) + }) +}) diff --git a/packages/core/src/modules/dids/methods/key/__tests__/__fixtures__/didKeyz6MksLe.json b/packages/core/src/modules/dids/methods/key/__tests__/__fixtures__/didKeyz6MksLe.json new file mode 100644 index 0000000000..4182e6d1ff --- /dev/null +++ b/packages/core/src/modules/dids/methods/key/__tests__/__fixtures__/didKeyz6MksLe.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://w3id.org/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1" + ], + "verificationMethod": [ + { + "id": "did:key:z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU#z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU", + "type": "Ed25519VerificationKey2018", + "controller": "did:key:z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU", + "publicKeyBase58": "DtPcLpky6Yi6zPecfW8VZH3xNoDkvQfiGWp8u5n9nAj6" + } + ], + "authentication": [ + "did:key:z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU#z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU" + ], + "assertionMethod": [ + "did:key:z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU#z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU" + ], + "keyAgreement": [ + { + "id": "did:key:z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU#z6LShxJc8afmt8L1HKjUE56hXwmAkUhdQygrH1VG2jmb1WRz", + "type": "X25519KeyAgreementKey2019", + "controller": "did:key:z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU", + "publicKeyBase58": "7H8ScGrunfcGBwMhhRakDMYguLAWiNWhQ2maYH84J8fE" + } + ], + "capabilityInvocation": [ + "did:key:z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU#z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU" + ], + "capabilityDelegation": [ + "did:key:z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU#z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU" + ], + "id": "did:key:z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU" +} diff --git a/packages/core/src/modules/dids/methods/key/index.ts b/packages/core/src/modules/dids/methods/key/index.ts index c832783193..3c5ea1244d 100644 --- a/packages/core/src/modules/dids/methods/key/index.ts +++ b/packages/core/src/modules/dids/methods/key/index.ts @@ -1 +1,3 @@ export { DidKey } from './DidKey' +export * from './KeyDidRegistrar' +export * from './KeyDidResolver' diff --git a/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts b/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts new file mode 100644 index 0000000000..d194c4cbf1 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts @@ -0,0 +1,205 @@ +import type { AgentContext } from '../../../../agent' +import type { KeyType } from '../../../../crypto' +import type { DidRegistrar } from '../../domain/DidRegistrar' +import type { DidCreateOptions, DidCreateResult, DidDeactivateResult, DidUpdateResult } from '../../types' + +import { injectable } from '../../../../plugins' +import { JsonTransformer } from '../../../../utils' +import { DidDocument } from '../../domain' +import { DidDocumentRole } from '../../domain/DidDocumentRole' +import { DidRepository, DidRecord } from '../../repository' + +import { PeerDidNumAlgo } from './didPeer' +import { keyToNumAlgo0DidDocument } from './peerDidNumAlgo0' +import { didDocumentJsonToNumAlgo1Did } from './peerDidNumAlgo1' +import { didDocumentToNumAlgo2Did } from './peerDidNumAlgo2' + +@injectable() +export class PeerDidRegistrar implements DidRegistrar { + public readonly supportedMethods = ['peer'] + private didRepository: DidRepository + + public constructor(didRepository: DidRepository) { + this.didRepository = didRepository + } + + public async create( + agentContext: AgentContext, + options: PeerDidNumAlgo0CreateOptions | PeerDidNumAlgo1CreateOptions | PeerDidNumAlgo2CreateOptions + ): Promise { + let didDocument: DidDocument + + try { + if (isPeerDidNumAlgo0CreateOptions(options)) { + const keyType = options.options.keyType + const seed = options.secret?.seed + + if (!keyType) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Missing key type', + }, + } + } + + if (seed && (typeof seed !== 'string' || seed.length !== 32)) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Invalid seed provided', + }, + } + } + + const key = await agentContext.wallet.createKey({ + keyType, + seed, + }) + + // TODO: validate did:peer document + + didDocument = keyToNumAlgo0DidDocument(key) + } else if (isPeerDidNumAlgo1CreateOptions(options)) { + const didDocumentJson = options.didDocument.toJSON() + const did = didDocumentJsonToNumAlgo1Did(didDocumentJson) + + didDocument = JsonTransformer.fromJSON({ ...didDocumentJson, id: did }, DidDocument) + } else if (isPeerDidNumAlgo2CreateOptions(options)) { + const didDocumentJson = options.didDocument.toJSON() + const did = didDocumentToNumAlgo2Did(options.didDocument) + + didDocument = JsonTransformer.fromJSON({ ...didDocumentJson, id: did }, DidDocument) + } else { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `Missing or incorrect numAlgo provided`, + }, + } + } + + // Save the did so we know we created it and can use it for didcomm + const didRecord = new DidRecord({ + id: didDocument.id, + role: DidDocumentRole.Created, + didDocument: isPeerDidNumAlgo1CreateOptions(options) ? didDocument : undefined, + tags: { + // We need to save the recipientKeys, so we can find the associated did + // of a key when we receive a message from another connection. + recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint), + }, + }) + await this.didRepository.save(agentContext, didRecord) + + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: didDocument.id, + didDocument, + secret: { + // FIXME: the uni-registrar creates the seed in the registrar method + // if it doesn't exist so the seed can always be returned. Currently + // we can only return it if the seed was passed in by the user. Once + // we have a secure method for generating seeds we should use the same + // approach + seed: options.secret?.seed, + }, + }, + } + } catch (error) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `unknown error: ${error.message}`, + }, + } + } + } + + public async update(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:peer not implemented yet`, + }, + } + } + + public async deactivate(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:peer not implemented yet`, + }, + } + } +} + +function isPeerDidNumAlgo1CreateOptions(options: PeerDidCreateOptions): options is PeerDidNumAlgo1CreateOptions { + return options.options.numAlgo === PeerDidNumAlgo.GenesisDoc +} + +function isPeerDidNumAlgo0CreateOptions(options: PeerDidCreateOptions): options is PeerDidNumAlgo0CreateOptions { + return options.options.numAlgo === PeerDidNumAlgo.InceptionKeyWithoutDoc +} + +function isPeerDidNumAlgo2CreateOptions(options: PeerDidCreateOptions): options is PeerDidNumAlgo2CreateOptions { + return options.options.numAlgo === PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc +} + +export type PeerDidCreateOptions = + | PeerDidNumAlgo0CreateOptions + | PeerDidNumAlgo1CreateOptions + | PeerDidNumAlgo2CreateOptions + +export interface PeerDidNumAlgo0CreateOptions extends DidCreateOptions { + method: 'peer' + did?: never + didDocument?: never + options: { + keyType: KeyType.Ed25519 + numAlgo: PeerDidNumAlgo.InceptionKeyWithoutDoc + } + secret?: { + seed?: string + } +} + +export interface PeerDidNumAlgo1CreateOptions extends DidCreateOptions { + method: 'peer' + did?: never + didDocument: DidDocument + options: { + numAlgo: PeerDidNumAlgo.GenesisDoc + } + secret?: undefined +} + +export interface PeerDidNumAlgo2CreateOptions extends DidCreateOptions { + method: 'peer' + did?: never + didDocument: DidDocument + options: { + numAlgo: PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc + } + secret?: undefined +} + +// Update and Deactivate not supported for did:peer +export type PeerDidUpdateOptions = never +export type PeerDidDeactivateOptions = never diff --git a/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts index 85fad84c54..3ee2c1b473 100644 --- a/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts +++ b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts @@ -1,15 +1,17 @@ import type { AgentContext } from '../../../../agent' import type { DidDocument } from '../../domain' import type { DidResolver } from '../../domain/DidResolver' -import type { DidRepository } from '../../repository' import type { DidResolutionResult } from '../../types' import { AriesFrameworkError } from '../../../../error' +import { injectable } from '../../../../plugins' +import { DidRepository } from '../../repository' import { getNumAlgoFromPeerDid, isValidPeerDid, PeerDidNumAlgo } from './didPeer' import { didToNumAlgo0DidDocument } from './peerDidNumAlgo0' import { didToNumAlgo2DidDocument } from './peerDidNumAlgo2' +@injectable() export class PeerDidResolver implements DidResolver { public readonly supportedMethods = ['peer'] diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/didPeer.test.ts b/packages/core/src/modules/dids/methods/peer/__tests__/DidPeer.test.ts similarity index 100% rename from packages/core/src/modules/dids/methods/peer/__tests__/didPeer.test.ts rename to packages/core/src/modules/dids/methods/peer/__tests__/DidPeer.test.ts diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts new file mode 100644 index 0000000000..c6843609a3 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts @@ -0,0 +1,367 @@ +import type { Wallet } from '../../../../../wallet' + +import { getAgentContext, mockFunction } from '../../../../../../tests/helpers' +import { KeyType } from '../../../../../crypto' +import { Key } from '../../../../../crypto/Key' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { DidCommV1Service, DidDocumentBuilder } from '../../../domain' +import { DidDocumentRole } from '../../../domain/DidDocumentRole' +import { getEd25519VerificationMethod } from '../../../domain/key-type/ed25519' +import { DidRepository } from '../../../repository/DidRepository' +import { PeerDidRegistrar } from '../PeerDidRegistrar' +import { PeerDidNumAlgo } from '../didPeer' + +import didPeer0z6MksLeFixture from './__fixtures__/didPeer0z6MksLe.json' + +jest.mock('../../../repository/DidRepository') +const DidRepositoryMock = DidRepository as jest.Mock + +const walletMock = { + createKey: jest.fn(() => Key.fromFingerprint('z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU')), +} as unknown as Wallet +const agentContext = getAgentContext({ wallet: walletMock }) + +describe('DidRegistrar', () => { + describe('PeerDidRegistrar', () => { + let peerDidRegistrar: PeerDidRegistrar + let didRepositoryMock: DidRepository + + beforeEach(() => { + didRepositoryMock = new DidRepositoryMock() + peerDidRegistrar = new PeerDidRegistrar(didRepositoryMock) + }) + + describe('did:peer:0', () => { + it('should correctly create a did:peer:0 document using Ed25519 key type', async () => { + const seed = '96213c3d7fc8d4d6754c712fd969598e' + + const result = await peerDidRegistrar.create(agentContext, { + method: 'peer', + options: { + keyType: KeyType.Ed25519, + numAlgo: PeerDidNumAlgo.InceptionKeyWithoutDoc, + }, + secret: { + seed, + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:peer:0z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU', + didDocument: didPeer0z6MksLeFixture, + secret: { + seed: '96213c3d7fc8d4d6754c712fd969598e', + }, + }, + }) + }) + + it('should return an error state if no key type is provided', async () => { + const result = await peerDidRegistrar.create(agentContext, { + method: 'peer', + // @ts-expect-error - key type is required in interface + options: { + numAlgo: PeerDidNumAlgo.InceptionKeyWithoutDoc, + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Missing key type', + }, + }) + }) + + it('should return an error state if an invalid seed is provided', async () => { + const result = await peerDidRegistrar.create(agentContext, { + method: 'peer', + options: { + keyType: KeyType.Ed25519, + numAlgo: PeerDidNumAlgo.InceptionKeyWithoutDoc, + }, + secret: { + seed: 'invalid', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Invalid seed provided', + }, + }) + }) + + it('should store the did without the did document', async () => { + const seed = '96213c3d7fc8d4d6754c712fd969598e' + const did = 'did:peer:0z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU' + + await peerDidRegistrar.create(agentContext, { + method: 'peer', + options: { + keyType: KeyType.Ed25519, + numAlgo: PeerDidNumAlgo.InceptionKeyWithoutDoc, + }, + secret: { + seed, + }, + }) + + expect(didRepositoryMock.save).toHaveBeenCalledTimes(1) + const [, didRecord] = mockFunction(didRepositoryMock.save).mock.calls[0] + + expect(didRecord).toMatchObject({ + id: did, + role: DidDocumentRole.Created, + _tags: { + recipientKeyFingerprints: [], + }, + didDocument: undefined, + }) + }) + }) + + describe('did:peer:1', () => { + const verificationMethod = getEd25519VerificationMethod({ + key: Key.fromFingerprint('z6LShxJc8afmt8L1HKjUE56hXwmAkUhdQygrH1VG2jmb1WRz'), + // controller in method 1 did should be #id + controller: '#id', + id: '#41fb2ec7-1f8b-42bf-91a2-4ef9092ddc16', + }) + + const didDocument = new DidDocumentBuilder('') + .addVerificationMethod(verificationMethod) + .addAuthentication(verificationMethod.id) + .addService( + new DidCommV1Service({ + id: '#service-0', + recipientKeys: [verificationMethod.id], + serviceEndpoint: 'https://example.com', + accept: ['didcomm/aip2;env=rfc19'], + }) + ) + .build() + + it('should correctly create a did:peer:1 document from a did document', async () => { + const result = await peerDidRegistrar.create(agentContext, { + method: 'peer', + didDocument: didDocument, + options: { + numAlgo: PeerDidNumAlgo.GenesisDoc, + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:peer:1zQmUTNcSy2J2sAmX6Ad2bdPvhVnHPUaod8Skpt8DWPpZaiL', + didDocument: { + '@context': ['https://w3id.org/did/v1'], + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + id: '#41fb2ec7-1f8b-42bf-91a2-4ef9092ddc16', + type: 'Ed25519VerificationKey2018', + controller: '#id', + publicKeyBase58: '7H8ScGrunfcGBwMhhRakDMYguLAWiNWhQ2maYH84J8fE', + }, + ], + service: [ + { + id: '#service-0', + serviceEndpoint: 'https://example.com', + type: 'did-communication', + priority: 0, + recipientKeys: ['#41fb2ec7-1f8b-42bf-91a2-4ef9092ddc16'], + accept: ['didcomm/aip2;env=rfc19'], + }, + ], + authentication: ['#41fb2ec7-1f8b-42bf-91a2-4ef9092ddc16'], + assertionMethod: undefined, + keyAgreement: undefined, + capabilityInvocation: undefined, + capabilityDelegation: undefined, + id: 'did:peer:1zQmUTNcSy2J2sAmX6Ad2bdPvhVnHPUaod8Skpt8DWPpZaiL', + }, + }, + }) + }) + + it('should store the did with the did document', async () => { + const did = 'did:peer:1zQmUTNcSy2J2sAmX6Ad2bdPvhVnHPUaod8Skpt8DWPpZaiL' + + const { didState } = await peerDidRegistrar.create(agentContext, { + method: 'peer', + didDocument, + options: { + numAlgo: PeerDidNumAlgo.GenesisDoc, + }, + }) + + expect(didRepositoryMock.save).toHaveBeenCalledTimes(1) + const [, didRecord] = mockFunction(didRepositoryMock.save).mock.calls[0] + + expect(didRecord).toMatchObject({ + id: did, + didDocument: didState.didDocument, + role: DidDocumentRole.Created, + _tags: { + recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint), + }, + }) + }) + }) + + describe('did:peer:2', () => { + const key = Key.fromFingerprint('z6LShxJc8afmt8L1HKjUE56hXwmAkUhdQygrH1VG2jmb1WRz') + const verificationMethod = getEd25519VerificationMethod({ + key, + // controller in method 1 did should be #id + controller: '#id', + // Use relative id for peer dids + id: '#41fb2ec7-1f8b-42bf-91a2-4ef9092ddc16', + }) + + const didDocument = new DidDocumentBuilder('') + .addVerificationMethod(verificationMethod) + .addAuthentication(verificationMethod.id) + .addService( + new DidCommV1Service({ + id: '#service-0', + recipientKeys: [verificationMethod.id], + serviceEndpoint: 'https://example.com', + accept: ['didcomm/aip2;env=rfc19'], + }) + ) + .build() + + it('should correctly create a did:peer:2 document from a did document', async () => { + const result = await peerDidRegistrar.create(agentContext, { + method: 'peer', + didDocument: didDocument, + options: { + numAlgo: PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc, + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:peer:2.Vz6MkkjPVCX7M8D6jJSCQNzYb4T6giuSN8Fm463gWNZ65DMSc.SeyJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbIiM0MWZiMmVjNy0xZjhiLTQyYmYtOTFhMi00ZWY5MDkyZGRjMTYiXSwiYSI6WyJkaWRjb21tL2FpcDI7ZW52PXJmYzE5Il19', + didDocument: { + '@context': ['https://w3id.org/did/v1'], + id: 'did:peer:2.Vz6MkkjPVCX7M8D6jJSCQNzYb4T6giuSN8Fm463gWNZ65DMSc.SeyJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbIiM0MWZiMmVjNy0xZjhiLTQyYmYtOTFhMi00ZWY5MDkyZGRjMTYiXSwiYSI6WyJkaWRjb21tL2FpcDI7ZW52PXJmYzE5Il19', + service: [ + { + serviceEndpoint: 'https://example.com', + type: 'did-communication', + priority: 0, + recipientKeys: ['#41fb2ec7-1f8b-42bf-91a2-4ef9092ddc16'], + accept: ['didcomm/aip2;env=rfc19'], + id: '#service-0', + }, + ], + verificationMethod: [ + { + id: '#41fb2ec7-1f8b-42bf-91a2-4ef9092ddc16', + type: 'Ed25519VerificationKey2018', + controller: '#id', + publicKeyBase58: '7H8ScGrunfcGBwMhhRakDMYguLAWiNWhQ2maYH84J8fE', + }, + ], + authentication: ['#41fb2ec7-1f8b-42bf-91a2-4ef9092ddc16'], + }, + secret: {}, + }, + }) + }) + + it('should store the did without the did document', async () => { + const did = + 'did:peer:2.Vz6MkkjPVCX7M8D6jJSCQNzYb4T6giuSN8Fm463gWNZ65DMSc.SeyJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbIiM0MWZiMmVjNy0xZjhiLTQyYmYtOTFhMi00ZWY5MDkyZGRjMTYiXSwiYSI6WyJkaWRjb21tL2FpcDI7ZW52PXJmYzE5Il19' + + await peerDidRegistrar.create(agentContext, { + method: 'peer', + didDocument, + options: { + numAlgo: PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc, + }, + }) + + expect(didRepositoryMock.save).toHaveBeenCalledTimes(1) + const [, didRecord] = mockFunction(didRepositoryMock.save).mock.calls[0] + + expect(didRecord).toMatchObject({ + id: did, + role: DidDocumentRole.Created, + _tags: { + recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint), + }, + didDocument: undefined, + }) + }) + }) + + it('should return an error state if an unsupported numAlgo is provided', async () => { + const result = await peerDidRegistrar.create( + agentContext, + // @ts-expect-error - this is not a valid numAlgo + { + method: 'peer', + options: { + numAlgo: 4, + }, + } + ) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Missing or incorrect numAlgo provided', + }, + }) + }) + + it('should return an error state when calling update', async () => { + const result = await peerDidRegistrar.update() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:peer not implemented yet`, + }, + }) + }) + + it('should return an error state when calling deactivate', async () => { + const result = await peerDidRegistrar.deactivate() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:peer not implemented yet`, + }, + }) + }) + }) +}) diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer0z6MksLe.json b/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer0z6MksLe.json new file mode 100644 index 0000000000..21142434f5 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer0z6MksLe.json @@ -0,0 +1,36 @@ +{ + "id": "did:peer:0z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU", + "@context": [ + "https://w3id.org/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1" + ], + "verificationMethod": [ + { + "id": "did:peer:0z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU#z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU", + "type": "Ed25519VerificationKey2018", + "controller": "did:peer:0z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU", + "publicKeyBase58": "DtPcLpky6Yi6zPecfW8VZH3xNoDkvQfiGWp8u5n9nAj6" + } + ], + "authentication": [ + "did:peer:0z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU#z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU" + ], + "assertionMethod": [ + "did:peer:0z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU#z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU" + ], + "keyAgreement": [ + { + "id": "did:peer:0z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU#z6LShxJc8afmt8L1HKjUE56hXwmAkUhdQygrH1VG2jmb1WRz", + "type": "X25519KeyAgreementKey2019", + "controller": "did:peer:0z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU", + "publicKeyBase58": "7H8ScGrunfcGBwMhhRakDMYguLAWiNWhQ2maYH84J8fE" + } + ], + "capabilityInvocation": [ + "did:peer:0z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU#z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU" + ], + "capabilityDelegation": [ + "did:peer:0z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU#z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU" + ] +} diff --git a/packages/core/src/modules/dids/domain/createPeerDidFromServices.ts b/packages/core/src/modules/dids/methods/peer/createPeerDidDocumentFromServices.ts similarity index 77% rename from packages/core/src/modules/dids/domain/createPeerDidFromServices.ts rename to packages/core/src/modules/dids/methods/peer/createPeerDidDocumentFromServices.ts index 6f4dfe6a00..8f34254a92 100644 --- a/packages/core/src/modules/dids/domain/createPeerDidFromServices.ts +++ b/packages/core/src/modules/dids/methods/peer/createPeerDidDocumentFromServices.ts @@ -1,18 +1,17 @@ -import type { ResolvedDidCommService } from '../../../agent/MessageSender' +import type { ResolvedDidCommService } from '../../../../agent/MessageSender' import { convertPublicKeyToX25519 } from '@stablelib/ed25519' -import { KeyType, Key } from '../../../crypto' -import { AriesFrameworkError } from '../../../error' -import { uuid } from '../../../utils/uuid' -import { DidKey } from '../methods/key' +import { KeyType, Key } from '../../../../crypto' +import { AriesFrameworkError } from '../../../../error' +import { uuid } from '../../../../utils/uuid' +import { DidDocumentBuilder } from '../../domain/DidDocumentBuilder' +import { getEd25519VerificationMethod } from '../../domain/key-type/ed25519' +import { getX25519VerificationMethod } from '../../domain/key-type/x25519' +import { DidCommV1Service } from '../../domain/service/DidCommV1Service' +import { DidKey } from '../key' -import { DidDocumentBuilder } from './DidDocumentBuilder' -import { getEd25519VerificationMethod } from './key-type/ed25519' -import { getX25519VerificationMethod } from './key-type/x25519' -import { DidCommV1Service } from './service/DidCommV1Service' - -export function createDidDocumentFromServices(services: ResolvedDidCommService[]) { +export function createPeerDidDocumentFromServices(services: ResolvedDidCommService[]) { const didDocumentBuilder = new DidDocumentBuilder('') // Keep track off all added key id based on the fingerprint so we can add them to the recipientKeys as references diff --git a/packages/core/src/modules/dids/methods/peer/index.ts b/packages/core/src/modules/dids/methods/peer/index.ts new file mode 100644 index 0000000000..aa2eb72e57 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/index.ts @@ -0,0 +1,4 @@ +export * from './PeerDidRegistrar' +export * from './PeerDidResolver' +export * from './didPeer' +export * from './createPeerDidDocumentFromServices' diff --git a/packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts b/packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts new file mode 100644 index 0000000000..cf5350ccbd --- /dev/null +++ b/packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts @@ -0,0 +1,195 @@ +import type { AgentContext } from '../../../../agent' +import type { IndyEndpointAttrib } from '../../../ledger' +import type { DidRegistrar } from '../../domain/DidRegistrar' +import type { DidCreateOptions, DidCreateResult, DidDeactivateResult, DidUpdateResult } from '../../types' +import type * as Indy from 'indy-sdk' + +import { AgentDependencies } from '../../../../agent/AgentDependencies' +import { InjectionSymbols } from '../../../../constants' +import { inject, injectable } from '../../../../plugins' +import { assertIndyWallet } from '../../../../wallet/util/assertIndyWallet' +import { IndyLedgerService, IndyPoolService } from '../../../ledger' +import { DidDocumentRole } from '../../domain/DidDocumentRole' +import { DidRecord, DidRepository } from '../../repository' + +import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './util' + +@injectable() +export class SovDidRegistrar implements DidRegistrar { + public readonly supportedMethods = ['sov'] + private didRepository: DidRepository + private indy: typeof Indy + private indyLedgerService: IndyLedgerService + private indyPoolService: IndyPoolService + + public constructor( + didRepository: DidRepository, + indyLedgerService: IndyLedgerService, + indyPoolService: IndyPoolService, + @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies + ) { + this.didRepository = didRepository + this.indy = agentDependencies.indy + this.indyLedgerService = indyLedgerService + this.indyPoolService = indyPoolService + } + + public async create(agentContext: AgentContext, options: SovDidCreateOptions): Promise { + const { alias, role, submitterDid } = options.options + const seed = options.secret?.seed + + if (seed && (typeof seed !== 'string' || seed.length !== 32)) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Invalid seed provided', + }, + } + } + + if (!submitterDid.startsWith('did:sov:')) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Submitter did must be a valid did:sov did', + }, + } + } + + try { + // NOTE: we need to use the createAndStoreMyDid method from indy to create the did + // If we just create a key and handle the creating of the did ourselves, indy will throw a + // WalletItemNotFound when it needs to sign ledger transactions using this did. This means we need + // to rely directly on the indy SDK, as we don't want to expose a createDid method just for. + // FIXME: once askar/indy-vdr is supported we need to adjust this to work with both indy-sdk and askar + assertIndyWallet(agentContext.wallet) + const [unqualifiedIndyDid, verkey] = await this.indy.createAndStoreMyDid(agentContext.wallet.handle, { + seed, + }) + + const qualifiedSovDid = `did:sov:${unqualifiedIndyDid}` + const unqualifiedSubmitterDid = submitterDid.replace('did:sov:', '') + + // TODO: it should be possible to pass the pool used for writing to the indy ledger service. + // The easiest way to do this would be to make the submitterDid a fully qualified did, including the indy namespace. + await this.indyLedgerService.registerPublicDid( + agentContext, + unqualifiedSubmitterDid, + unqualifiedIndyDid, + verkey, + alias, + role + ) + + // Create did document + const didDocumentBuilder = sovDidDocumentFromDid(qualifiedSovDid, verkey) + + // Add services if endpoints object was passed. + if (options.options.endpoints) { + await this.indyLedgerService.setEndpointsForDid(agentContext, unqualifiedIndyDid, options.options.endpoints) + addServicesFromEndpointsAttrib( + didDocumentBuilder, + qualifiedSovDid, + options.options.endpoints, + `${qualifiedSovDid}#key-agreement-1` + ) + } + + // Build did document. + const didDocument = didDocumentBuilder.build() + + // FIXME: we need to update this to the `indyNamespace` once https://github.com/hyperledger/aries-framework-javascript/issues/944 has been resolved + const indyNamespace = this.indyPoolService.ledgerWritePool.config.id + const qualifiedIndyDid = `did:indy:${indyNamespace}:${unqualifiedIndyDid}` + + // Save the did so we know we created it and can issue with it + const didRecord = new DidRecord({ + id: qualifiedSovDid, + role: DidDocumentRole.Created, + tags: { + recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint), + qualifiedIndyDid, + }, + }) + await this.didRepository.save(agentContext, didRecord) + + return { + didDocumentMetadata: { + qualifiedIndyDid, + }, + didRegistrationMetadata: { + indyNamespace, + }, + didState: { + state: 'finished', + did: qualifiedSovDid, + didDocument, + secret: { + // FIXME: the uni-registrar creates the seed in the registrar method + // if it doesn't exist so the seed can always be returned. Currently + // we can only return it if the seed was passed in by the user. Once + // we have a secure method for generating seeds we should use the same + // approach + seed: options.secret?.seed, + }, + }, + } + } catch (error) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async update(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:sov not implemented yet`, + }, + } + } + + public async deactivate(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:sov not implemented yet`, + }, + } + } +} + +export interface SovDidCreateOptions extends DidCreateOptions { + method: 'sov' + did?: undefined + // As did:sov is so limited, we require everything needed to construct the did document to be passed + // through the options object. Once we support did:indy we can allow the didDocument property. + didDocument?: never + options: { + alias: string + role?: Indy.NymRole + endpoints?: IndyEndpointAttrib + submitterDid: string + } + secret?: { + seed?: string + } +} + +// Update and Deactivate not supported for did:sov +export type IndyDidUpdateOptions = never +export type IndyDidDeactivateOptions = never diff --git a/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts b/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts index 325b5cf185..79636d3fff 100644 --- a/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts +++ b/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts @@ -1,19 +1,13 @@ import type { AgentContext } from '../../../../agent' -import type { IndyEndpointAttrib, IndyLedgerService } from '../../../ledger' import type { DidResolver } from '../../domain/DidResolver' -import type { ParsedDid, DidResolutionResult } from '../../types' +import type { DidResolutionResult, ParsedDid } from '../../types' -import { convertPublicKeyToX25519 } from '@stablelib/ed25519' +import { injectable } from '../../../../plugins' +import { IndyLedgerService } from '../../../ledger' -import { TypedArrayEncoder } from '../../../../utils/TypedArrayEncoder' -import { getFullVerkey } from '../../../../utils/did' -import { SECURITY_X25519_CONTEXT_URL } from '../../../vc/constants' -import { ED25519_SUITE_CONTEXT_URL_2018 } from '../../../vc/signature-suites/ed25519/constants' -import { DidDocumentService } from '../../domain' -import { DidDocumentBuilder } from '../../domain/DidDocumentBuilder' -import { DidCommV1Service } from '../../domain/service/DidCommV1Service' -import { DidCommV2Service } from '../../domain/service/DidCommV2Service' +import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './util' +@injectable() export class SovDidResolver implements DidResolver { private indyLedgerService: IndyLedgerService @@ -28,37 +22,11 @@ export class SovDidResolver implements DidResolver { try { const nym = await this.indyLedgerService.getPublicDid(agentContext, parsed.id) - const endpoints = await this.indyLedgerService.getEndpointsForDid(agentContext, did) + const endpoints = await this.indyLedgerService.getEndpointsForDid(agentContext, parsed.id) - const verificationMethodId = `${parsed.did}#key-1` const keyAgreementId = `${parsed.did}#key-agreement-1` - - const publicKeyBase58 = getFullVerkey(nym.did, nym.verkey) - const publicKeyX25519 = TypedArrayEncoder.toBase58( - convertPublicKeyToX25519(TypedArrayEncoder.fromBase58(publicKeyBase58)) - ) - - const builder = new DidDocumentBuilder(parsed.did) - - .addContext(ED25519_SUITE_CONTEXT_URL_2018) - .addContext(SECURITY_X25519_CONTEXT_URL) - .addVerificationMethod({ - controller: parsed.did, - id: verificationMethodId, - publicKeyBase58: getFullVerkey(nym.did, nym.verkey), - type: 'Ed25519VerificationKey2018', - }) - .addVerificationMethod({ - controller: parsed.did, - id: keyAgreementId, - publicKeyBase58: publicKeyX25519, - type: 'X25519KeyAgreementKey2019', - }) - .addAuthentication(verificationMethodId) - .addAssertionMethod(verificationMethodId) - .addKeyAgreement(keyAgreementId) - - this.addServices(builder, parsed, endpoints, keyAgreementId) + const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) + addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) return { didDocument: builder.build(), @@ -76,88 +44,4 @@ export class SovDidResolver implements DidResolver { } } } - - // Process Indy Attrib Endpoint Types according to: https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html > Read (Resolve) > DID Service Endpoint - private processEndpointTypes(types?: string[]) { - const expectedTypes = ['endpoint', 'did-communication', 'DIDComm'] - const defaultTypes = ['endpoint', 'did-communication'] - - // Return default types if types "is NOT present [or] empty" - if (!types || types?.length <= 0) { - return defaultTypes - } - - // Return default types if types "contain any other values" - for (const type of types) { - if (!expectedTypes.includes(type)) { - return defaultTypes - } - } - - // Return provided types - return types - } - - private addServices( - builder: DidDocumentBuilder, - parsed: ParsedDid, - endpoints: IndyEndpointAttrib, - keyAgreementId: string - ) { - const { endpoint, routingKeys, types, ...otherEndpoints } = endpoints - - if (endpoint) { - const processedTypes = this.processEndpointTypes(types) - - // If 'endpoint' included in types, add id to the services array - if (processedTypes.includes('endpoint')) { - builder.addService( - new DidDocumentService({ - id: `${parsed.did}#endpoint`, - serviceEndpoint: endpoint, - type: 'endpoint', - }) - ) - } - - // If 'did-communication' included in types, add DIDComm v1 entry - if (processedTypes.includes('did-communication')) { - builder.addService( - new DidCommV1Service({ - id: `${parsed.did}#did-communication`, - serviceEndpoint: endpoint, - priority: 0, - routingKeys: routingKeys ?? [], - recipientKeys: [keyAgreementId], - accept: ['didcomm/aip2;env=rfc19'], - }) - ) - - // If 'DIDComm' included in types, add DIDComm v2 entry - if (processedTypes.includes('DIDComm')) { - builder - .addService( - new DidCommV2Service({ - id: `${parsed.did}#didcomm-1`, - serviceEndpoint: endpoint, - routingKeys: routingKeys ?? [], - accept: ['didcomm/v2'], - }) - ) - .addContext('https://didcomm.org/messaging/contexts/v2') - } - } - } - - // Add other endpoint types - for (const [type, endpoint] of Object.entries(otherEndpoints)) { - builder.addService( - new DidDocumentService({ - id: `${parsed.did}#${type}`, - serviceEndpoint: endpoint as string, - type, - }) - ) - } - } } diff --git a/packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts new file mode 100644 index 0000000000..e2a3041652 --- /dev/null +++ b/packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts @@ -0,0 +1,345 @@ +import type { Wallet } from '../../../../../wallet' +import type { IndyPool } from '../../../../ledger' +import type * as Indy from 'indy-sdk' + +import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../../../tests/helpers' +import { SigningProviderRegistry } from '../../../../../crypto/signing-provider' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { IndyWallet } from '../../../../../wallet/IndyWallet' +import { IndyLedgerService } from '../../../../ledger/services/IndyLedgerService' +import { IndyPoolService } from '../../../../ledger/services/IndyPoolService' +import { DidDocumentRole } from '../../../domain/DidDocumentRole' +import { DidRepository } from '../../../repository/DidRepository' +import { SovDidRegistrar } from '../SovDidRegistrar' + +jest.mock('../../../repository/DidRepository') +const DidRepositoryMock = DidRepository as jest.Mock + +jest.mock('../../../../ledger/services/IndyLedgerService') +const IndyLedgerServiceMock = IndyLedgerService as jest.Mock + +jest.mock('../../../../ledger/services/IndyPoolService') +const IndyPoolServiceMock = IndyPoolService as jest.Mock +const indyPoolServiceMock = new IndyPoolServiceMock() +mockProperty(indyPoolServiceMock, 'ledgerWritePool', { config: { id: 'pool1' } } as IndyPool) + +const agentConfig = getAgentConfig('SovDidRegistrar') + +const createDidMock = jest.fn(async () => ['R1xKJw17sUoXhejEpugMYJ', 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu']) + +const wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) +mockProperty(wallet, 'handle', 10) + +const agentContext = getAgentContext({ + wallet, +}) + +const didRepositoryMock = new DidRepositoryMock() +const indyLedgerServiceMock = new IndyLedgerServiceMock() +const sovDidRegistrar = new SovDidRegistrar(didRepositoryMock, indyLedgerServiceMock, indyPoolServiceMock, { + ...agentConfig.agentDependencies, + indy: { createAndStoreMyDid: createDidMock } as unknown as typeof Indy, +}) + +describe('DidRegistrar', () => { + describe('SovDidRegistrar', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + it('should return an error state if an invalid seed is provided', async () => { + const result = await sovDidRegistrar.create(agentContext, { + method: 'sov', + + options: { + submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + secret: { + seed: 'invalid', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Invalid seed provided', + }, + }) + }) + + it('should return an error state if the wallet is not an indy wallet', async () => { + const agentContext = getAgentContext({ + wallet: {} as unknown as Wallet, + }) + + const result = await sovDidRegistrar.create(agentContext, { + method: 'sov', + + options: { + submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + secret: { + seed: '12345678901234567890123456789012', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'unknownError: Expected wallet to be instance of IndyWallet, found Object', + }, + }) + }) + + it('should return an error state if the submitter did is not qualified with did:sov', async () => { + const result = await sovDidRegistrar.create(agentContext, { + method: 'sov', + options: { + submitterDid: 'BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Submitter did must be a valid did:sov did', + }, + }) + }) + + it('should correctly create a did:sov document without services', async () => { + const seed = '96213c3d7fc8d4d6754c712fd969598e' + const result = await sovDidRegistrar.create(agentContext, { + method: 'sov', + options: { + alias: 'Hello', + submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + }, + secret: { + seed, + }, + }) + + expect(indyLedgerServiceMock.registerPublicDid).toHaveBeenCalledWith( + agentContext, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // Unqualified created indy did + 'R1xKJw17sUoXhejEpugMYJ', + // Verkey + 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + // Alias + 'Hello', + // Role + 'STEWARD' + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: { + qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + }, + didRegistrationMetadata: { + indyNamespace: 'pool1', + }, + didState: { + state: 'finished', + did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + verificationMethod: [ + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-1', + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1', + type: 'X25519KeyAgreementKey2019', + controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + }, + ], + authentication: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], + assertionMethod: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], + keyAgreement: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], + }, + secret: { + seed, + }, + }, + }) + }) + + it('should correctly create a did:sov document with services', async () => { + const seed = '96213c3d7fc8d4d6754c712fd969598e' + const result = await sovDidRegistrar.create(agentContext, { + method: 'sov', + options: { + alias: 'Hello', + submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + endpoints: { + endpoint: 'https://example.com/endpoint', + routingKeys: ['key-1'], + types: ['DIDComm', 'did-communication', 'endpoint'], + }, + }, + secret: { + seed, + }, + }) + + expect(indyLedgerServiceMock.registerPublicDid).toHaveBeenCalledWith( + agentContext, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // Unqualified created indy did + 'R1xKJw17sUoXhejEpugMYJ', + // Verkey + 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + // Alias + 'Hello', + // Role + 'STEWARD' + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: { + qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + }, + didRegistrationMetadata: { + indyNamespace: 'pool1', + }, + didState: { + state: 'finished', + did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + verificationMethod: [ + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-1', + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1', + type: 'X25519KeyAgreementKey2019', + controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + }, + ], + service: [ + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#endpoint', + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#did-communication', + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + priority: 0, + recipientKeys: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], + routingKeys: ['key-1'], + accept: ['didcomm/aip2;env=rfc19'], + }, + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#didcomm-1', + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDComm', + routingKeys: ['key-1'], + accept: ['didcomm/v2'], + }, + ], + authentication: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], + assertionMethod: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], + keyAgreement: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], + }, + secret: { + seed, + }, + }, + }) + }) + + it('should store the did document', async () => { + const seed = '96213c3d7fc8d4d6754c712fd969598e' + await sovDidRegistrar.create(agentContext, { + method: 'sov', + options: { + alias: 'Hello', + submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + endpoints: { + endpoint: 'https://example.com/endpoint', + routingKeys: ['key-1'], + types: ['DIDComm', 'did-communication', 'endpoint'], + }, + }, + secret: { + seed, + }, + }) + + expect(didRepositoryMock.save).toHaveBeenCalledTimes(1) + const [, didRecord] = mockFunction(didRepositoryMock.save).mock.calls[0] + + expect(didRecord).toMatchObject({ + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + role: DidDocumentRole.Created, + _tags: { + recipientKeyFingerprints: ['z6LSrH6AdsQeZuKKmG6Ehx7abEQZsVg2psR2VU536gigUoAe'], + qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + }, + didDocument: undefined, + }) + }) + + it('should return an error state when calling update', async () => { + const result = await sovDidRegistrar.update() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:sov not implemented yet`, + }, + }) + }) + + it('should return an error state when calling deactivate', async () => { + const result = await sovDidRegistrar.deactivate() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:sov not implemented yet`, + }, + }) + }) + }) +}) diff --git a/packages/core/src/modules/dids/methods/sov/index.ts b/packages/core/src/modules/dids/methods/sov/index.ts new file mode 100644 index 0000000000..82c05ea971 --- /dev/null +++ b/packages/core/src/modules/dids/methods/sov/index.ts @@ -0,0 +1,2 @@ +export * from './SovDidRegistrar' +export * from './SovDidResolver' diff --git a/packages/core/src/modules/dids/methods/sov/util.ts b/packages/core/src/modules/dids/methods/sov/util.ts new file mode 100644 index 0000000000..638779dd21 --- /dev/null +++ b/packages/core/src/modules/dids/methods/sov/util.ts @@ -0,0 +1,123 @@ +import type { IndyEndpointAttrib } from '../../../ledger' + +import { TypedArrayEncoder } from '../../../../utils' +import { getFullVerkey } from '../../../../utils/did' +import { SECURITY_X25519_CONTEXT_URL } from '../../../vc/constants' +import { ED25519_SUITE_CONTEXT_URL_2018 } from '../../../vc/signature-suites/ed25519/constants' +import { DidDocumentService, DidDocumentBuilder, DidCommV1Service, DidCommV2Service } from '../../domain' +import { convertPublicKeyToX25519 } from '../../domain/key-type/ed25519' + +export function sovDidDocumentFromDid(fullDid: string, verkey: string) { + const verificationMethodId = `${fullDid}#key-1` + const keyAgreementId = `${fullDid}#key-agreement-1` + + const publicKeyBase58 = getFullVerkey(fullDid, verkey) + const publicKeyX25519 = TypedArrayEncoder.toBase58( + convertPublicKeyToX25519(TypedArrayEncoder.fromBase58(publicKeyBase58)) + ) + + const builder = new DidDocumentBuilder(fullDid) + .addContext(ED25519_SUITE_CONTEXT_URL_2018) + .addContext(SECURITY_X25519_CONTEXT_URL) + .addVerificationMethod({ + controller: fullDid, + id: verificationMethodId, + publicKeyBase58: publicKeyBase58, + type: 'Ed25519VerificationKey2018', + }) + .addVerificationMethod({ + controller: fullDid, + id: keyAgreementId, + publicKeyBase58: publicKeyX25519, + type: 'X25519KeyAgreementKey2019', + }) + .addAuthentication(verificationMethodId) + .addAssertionMethod(verificationMethodId) + .addKeyAgreement(keyAgreementId) + + return builder +} + +// Process Indy Attrib Endpoint Types according to: https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html > Read (Resolve) > DID Service Endpoint +function processEndpointTypes(types?: string[]) { + const expectedTypes = ['endpoint', 'did-communication', 'DIDComm'] + const defaultTypes = ['endpoint', 'did-communication'] + + // Return default types if types "is NOT present [or] empty" + if (!types || types.length <= 0) { + return defaultTypes + } + + // Return default types if types "contain any other values" + for (const type of types) { + if (!expectedTypes.includes(type)) { + return defaultTypes + } + } + + // Return provided types + return types +} + +export function addServicesFromEndpointsAttrib( + builder: DidDocumentBuilder, + did: string, + endpoints: IndyEndpointAttrib, + keyAgreementId: string +) { + const { endpoint, routingKeys, types, ...otherEndpoints } = endpoints + + if (endpoint) { + const processedTypes = processEndpointTypes(types) + + // If 'endpoint' included in types, add id to the services array + if (processedTypes.includes('endpoint')) { + builder.addService( + new DidDocumentService({ + id: `${did}#endpoint`, + serviceEndpoint: endpoint, + type: 'endpoint', + }) + ) + } + + // If 'did-communication' included in types, add DIDComm v1 entry + if (processedTypes.includes('did-communication')) { + builder.addService( + new DidCommV1Service({ + id: `${did}#did-communication`, + serviceEndpoint: endpoint, + priority: 0, + routingKeys: routingKeys ?? [], + recipientKeys: [keyAgreementId], + accept: ['didcomm/aip2;env=rfc19'], + }) + ) + + // If 'DIDComm' included in types, add DIDComm v2 entry + if (processedTypes.includes('DIDComm')) { + builder + .addService( + new DidCommV2Service({ + id: `${did}#didcomm-1`, + serviceEndpoint: endpoint, + routingKeys: routingKeys ?? [], + accept: ['didcomm/v2'], + }) + ) + .addContext('https://didcomm.org/messaging/contexts/v2') + } + } + } + + // Add other endpoint types + for (const [type, endpoint] of Object.entries(otherEndpoints)) { + builder.addService( + new DidDocumentService({ + id: `${did}#${type}`, + serviceEndpoint: endpoint as string, + type, + }) + ) + } +} diff --git a/packages/core/src/modules/dids/methods/web/WebDidResolver.ts b/packages/core/src/modules/dids/methods/web/WebDidResolver.ts index 77d9b1e295..84cac28e59 100644 --- a/packages/core/src/modules/dids/methods/web/WebDidResolver.ts +++ b/packages/core/src/modules/dids/methods/web/WebDidResolver.ts @@ -5,9 +5,11 @@ import type { ParsedDid, DidResolutionResult, DidResolutionOptions } from '../.. import { Resolver } from 'did-resolver' import * as didWeb from 'web-did-resolver' +import { injectable } from '../../../../plugins' import { JsonTransformer } from '../../../../utils/JsonTransformer' import { DidDocument } from '../../domain' +@injectable() export class WebDidResolver implements DidResolver { public readonly supportedMethods diff --git a/packages/core/src/modules/dids/methods/web/index.ts b/packages/core/src/modules/dids/methods/web/index.ts new file mode 100644 index 0000000000..59e66593dd --- /dev/null +++ b/packages/core/src/modules/dids/methods/web/index.ts @@ -0,0 +1 @@ +export * from './WebDidResolver' diff --git a/packages/core/src/modules/dids/repository/DidRepository.ts b/packages/core/src/modules/dids/repository/DidRepository.ts index 3384558c7a..854d2e6d46 100644 --- a/packages/core/src/modules/dids/repository/DidRepository.ts +++ b/packages/core/src/modules/dids/repository/DidRepository.ts @@ -6,6 +6,7 @@ import { InjectionSymbols } from '../../../constants' import { inject, injectable } from '../../../plugins' import { Repository } from '../../../storage/Repository' import { StorageService } from '../../../storage/StorageService' +import { DidDocumentRole } from '../domain/DidDocumentRole' import { DidRecord } from './DidRecord' @@ -25,4 +26,11 @@ export class DidRepository extends Repository { public findAllByRecipientKey(agentContext: AgentContext, recipientKey: Key) { return this.findByQuery(agentContext, { recipientKeyFingerprints: [recipientKey.fingerprint] }) } + + public getCreatedDids(agentContext: AgentContext, { method }: { method?: string }) { + return this.findByQuery(agentContext, { + role: DidDocumentRole.Created, + method, + }) + } } diff --git a/packages/core/src/modules/dids/services/DidRegistrarService.ts b/packages/core/src/modules/dids/services/DidRegistrarService.ts new file mode 100644 index 0000000000..43a77c8c25 --- /dev/null +++ b/packages/core/src/modules/dids/services/DidRegistrarService.ts @@ -0,0 +1,159 @@ +import type { AgentContext } from '../../../agent' +import type { DidRegistrar } from '../domain/DidRegistrar' +import type { + DidCreateOptions, + DidCreateResult, + DidDeactivateOptions, + DidDeactivateResult, + DidUpdateOptions, + DidUpdateResult, +} from '../types' + +import { InjectionSymbols } from '../../../constants' +import { Logger } from '../../../logger' +import { inject, injectable, injectAll } from '../../../plugins' +import { DidRegistrarToken } from '../domain/DidRegistrar' +import { tryParseDid } from '../domain/parse' + +@injectable() +export class DidRegistrarService { + private logger: Logger + private registrars: DidRegistrar[] + + public constructor( + @inject(InjectionSymbols.Logger) logger: Logger, + @injectAll(DidRegistrarToken) registrars: DidRegistrar[] + ) { + this.logger = logger + this.registrars = registrars + } + + public async create( + agentContext: AgentContext, + options: CreateOptions + ): Promise { + this.logger.debug(`creating did ${options.did ?? options.method}`) + + const errorResult = { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + did: options.did, + }, + } as const + + if ((!options.did && !options.method) || (options.did && options.method)) { + return { + ...errorResult, + didState: { + ...errorResult.didState, + reason: 'Either did OR method must be specified', + }, + } + } + + const method = options.method ?? tryParseDid(options.did as string)?.method + if (!method) { + return { + ...errorResult, + didState: { + ...errorResult.didState, + reason: `Could not extract method from did ${options.did}`, + }, + } + } + + const registrar = this.findRegistrarForMethod(method) + if (!registrar) { + return { + ...errorResult, + didState: { + ...errorResult.didState, + reason: `Unsupported did method: '${method}'`, + }, + } + } + + return await registrar.create(agentContext, options) + } + + public async update(agentContext: AgentContext, options: DidUpdateOptions): Promise { + this.logger.debug(`updating did ${options.did}`) + + const method = tryParseDid(options.did)?.method + + const errorResult = { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + did: options.did, + }, + } as const + + if (!method) { + return { + ...errorResult, + didState: { + ...errorResult.didState, + reason: `Could not extract method from did ${options.did}`, + }, + } + } + + const registrar = this.findRegistrarForMethod(method) + if (!registrar) { + return { + ...errorResult, + didState: { + ...errorResult.didState, + reason: `Unsupported did method: '${method}'`, + }, + } + } + + return await registrar.update(agentContext, options) + } + + public async deactivate(agentContext: AgentContext, options: DidDeactivateOptions): Promise { + this.logger.debug(`deactivating did ${options.did}`) + + const errorResult = { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + did: options.did, + }, + } as const + + const method = tryParseDid(options.did)?.method + if (!method) { + return { + ...errorResult, + didState: { + ...errorResult.didState, + reason: `Could not extract method from did ${options.did}`, + }, + } + } + + const registrar = this.findRegistrarForMethod(method) + if (!registrar) { + return { + ...errorResult, + didState: { + ...errorResult.didState, + reason: `Unsupported did method: '${method}'`, + }, + } + } + + return await registrar.deactivate(agentContext, options) + } + + private findRegistrarForMethod(method: string): DidRegistrar | null { + return this.registrars.find((r) => r.supportedMethods.includes(method)) ?? null + } +} diff --git a/packages/core/src/modules/dids/services/DidResolverService.ts b/packages/core/src/modules/dids/services/DidResolverService.ts index 3a0020a8b5..a365706dc4 100644 --- a/packages/core/src/modules/dids/services/DidResolverService.ts +++ b/packages/core/src/modules/dids/services/DidResolverService.ts @@ -5,14 +5,9 @@ import type { DidResolutionOptions, DidResolutionResult, ParsedDid } from '../ty import { InjectionSymbols } from '../../../constants' import { AriesFrameworkError } from '../../../error' import { Logger } from '../../../logger' -import { injectable, inject } from '../../../plugins' -import { IndyLedgerService } from '../../ledger' +import { injectable, inject, injectAll } from '../../../plugins' +import { DidResolverToken } from '../domain/DidResolver' import { parseDid } from '../domain/parse' -import { KeyDidResolver } from '../methods/key/KeyDidResolver' -import { PeerDidResolver } from '../methods/peer/PeerDidResolver' -import { SovDidResolver } from '../methods/sov/SovDidResolver' -import { WebDidResolver } from '../methods/web/WebDidResolver' -import { DidRepository } from '../repository' @injectable() export class DidResolverService { @@ -20,18 +15,11 @@ export class DidResolverService { private resolvers: DidResolver[] public constructor( - indyLedgerService: IndyLedgerService, - didRepository: DidRepository, - @inject(InjectionSymbols.Logger) logger: Logger + @inject(InjectionSymbols.Logger) logger: Logger, + @injectAll(DidResolverToken) resolvers: DidResolver[] ) { this.logger = logger - - this.resolvers = [ - new SovDidResolver(indyLedgerService), - new WebDidResolver(), - new KeyDidResolver(), - new PeerDidResolver(didRepository), - ] + this.resolvers = resolvers } public async resolve( diff --git a/packages/core/src/modules/dids/services/__tests__/DidRegistrarService.test.ts b/packages/core/src/modules/dids/services/__tests__/DidRegistrarService.test.ts new file mode 100644 index 0000000000..b92659ebe4 --- /dev/null +++ b/packages/core/src/modules/dids/services/__tests__/DidRegistrarService.test.ts @@ -0,0 +1,199 @@ +import type { DidDocument, DidRegistrar } from '../../domain' + +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' +import { DidRegistrarService } from '../DidRegistrarService' + +const agentConfig = getAgentConfig('DidResolverService') +const agentContext = getAgentContext() + +const didRegistrarMock = { + supportedMethods: ['key'], + create: jest.fn(), + update: jest.fn(), + deactivate: jest.fn(), +} as DidRegistrar + +const didRegistrarService = new DidRegistrarService(agentConfig.logger, [didRegistrarMock]) + +describe('DidResolverService', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + describe('create', () => { + it('should correctly find and call the correct registrar for a specified did', async () => { + const returnValue = { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: ':(', + }, + } as const + mockFunction(didRegistrarMock.create).mockResolvedValue(returnValue) + + const result = await didRegistrarService.create(agentContext, { did: 'did:key:xxxx' }) + expect(result).toEqual(returnValue) + + expect(didRegistrarMock.create).toHaveBeenCalledTimes(1) + expect(didRegistrarMock.create).toHaveBeenCalledWith(agentContext, { did: 'did:key:xxxx' }) + }) + + it('should return error state failed if no did or method is provided', async () => { + const result = await didRegistrarService.create(agentContext, {}) + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + did: undefined, + reason: 'Either did OR method must be specified', + }, + }) + }) + + it('should return error state failed if both did and method are provided', async () => { + const result = await didRegistrarService.create(agentContext, { did: 'did:key:xxxx', method: 'key' }) + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + did: 'did:key:xxxx', + reason: 'Either did OR method must be specified', + }, + }) + }) + + it('should return error state failed if no method could be extracted from the did or method', async () => { + const result = await didRegistrarService.create(agentContext, { did: 'did:a' }) + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + did: 'did:a', + reason: 'Could not extract method from did did:a', + }, + }) + }) + + it('should return error with state failed if the did has no registrar', async () => { + const result = await didRegistrarService.create(agentContext, { did: 'did:something:123' }) + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + did: 'did:something:123', + reason: "Unsupported did method: 'something'", + }, + }) + }) + }) + + describe('update', () => { + it('should correctly find and call the correct registrar for a specified did', async () => { + const returnValue = { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: ':(', + }, + } as const + mockFunction(didRegistrarMock.update).mockResolvedValue(returnValue) + + const didDocument = {} as unknown as DidDocument + + const result = await didRegistrarService.update(agentContext, { did: 'did:key:xxxx', didDocument }) + expect(result).toEqual(returnValue) + + expect(didRegistrarMock.update).toHaveBeenCalledTimes(1) + expect(didRegistrarMock.update).toHaveBeenCalledWith(agentContext, { did: 'did:key:xxxx', didDocument }) + }) + + it('should return error state failed if no method could be extracted from the did', async () => { + const result = await didRegistrarService.update(agentContext, { did: 'did:a', didDocument: {} as DidDocument }) + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + did: 'did:a', + reason: 'Could not extract method from did did:a', + }, + }) + }) + + it('should return error with state failed if the did has no registrar', async () => { + const result = await didRegistrarService.update(agentContext, { + did: 'did:something:123', + didDocument: {} as DidDocument, + }) + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + did: 'did:something:123', + reason: "Unsupported did method: 'something'", + }, + }) + }) + }) + + describe('deactivate', () => { + it('should correctly find and call the correct registrar for a specified did', async () => { + const returnValue = { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: ':(', + }, + } as const + mockFunction(didRegistrarMock.deactivate).mockResolvedValue(returnValue) + + const result = await didRegistrarService.deactivate(agentContext, { did: 'did:key:xxxx' }) + expect(result).toEqual(returnValue) + + expect(didRegistrarMock.deactivate).toHaveBeenCalledTimes(1) + expect(didRegistrarMock.deactivate).toHaveBeenCalledWith(agentContext, { did: 'did:key:xxxx' }) + }) + + it('should return error state failed if no method could be extracted from the did', async () => { + const result = await didRegistrarService.deactivate(agentContext, { did: 'did:a' }) + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + did: 'did:a', + reason: 'Could not extract method from did did:a', + }, + }) + }) + + it('should return error with state failed if the did has no registrar', async () => { + const result = await didRegistrarService.deactivate(agentContext, { did: 'did:something:123' }) + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + did: 'did:something:123', + reason: "Unsupported did method: 'something'", + }, + }) + }) + }) +}) diff --git a/packages/core/src/modules/dids/__tests__/DidResolverService.test.ts b/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts similarity index 53% rename from packages/core/src/modules/dids/__tests__/DidResolverService.test.ts rename to packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts index 7dff728532..c472ec8899 100644 --- a/packages/core/src/modules/dids/__tests__/DidResolverService.test.ts +++ b/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts @@ -1,33 +1,24 @@ -import type { IndyLedgerService } from '../../ledger' -import type { DidRepository } from '../repository' +import type { DidResolver } from '../../domain' -import { getAgentConfig, getAgentContext, mockProperty } from '../../../../tests/helpers' -import { JsonTransformer } from '../../../utils/JsonTransformer' -import { DidDocument } from '../domain' -import { parseDid } from '../domain/parse' -import { KeyDidResolver } from '../methods/key/KeyDidResolver' -import { DidResolverService } from '../services/DidResolverService' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' +import { JsonTransformer } from '../../../../utils/JsonTransformer' +import didKeyEd25519Fixture from '../../__tests__/__fixtures__/didKeyEd25519.json' +import { DidDocument } from '../../domain' +import { parseDid } from '../../domain/parse' +import { DidResolverService } from '../DidResolverService' -import didKeyEd25519Fixture from './__fixtures__/didKeyEd25519.json' - -jest.mock('../methods/key/KeyDidResolver') +const didResolverMock = { + supportedMethods: ['key'], + resolve: jest.fn(), +} as DidResolver const agentConfig = getAgentConfig('DidResolverService') const agentContext = getAgentContext() describe('DidResolverService', () => { - const indyLedgerServiceMock = jest.fn() as unknown as IndyLedgerService - const didDocumentRepositoryMock = jest.fn() as unknown as DidRepository - const didResolverService = new DidResolverService( - indyLedgerServiceMock, - didDocumentRepositoryMock, - agentConfig.logger - ) + const didResolverService = new DidResolverService(agentConfig.logger, [didResolverMock]) it('should correctly find and call the correct resolver for a specified did', async () => { - const didKeyResolveSpy = jest.spyOn(KeyDidResolver.prototype, 'resolve') - mockProperty(KeyDidResolver.prototype, 'supportedMethods', ['key']) - const returnValue = { didDocument: JsonTransformer.fromJSON(didKeyEd25519Fixture, DidDocument), didDocumentMetadata: {}, @@ -35,13 +26,13 @@ describe('DidResolverService', () => { contentType: 'application/did+ld+json', }, } - didKeyResolveSpy.mockResolvedValue(returnValue) + mockFunction(didResolverMock.resolve).mockResolvedValue(returnValue) const result = await didResolverService.resolve(agentContext, 'did:key:xxxx', { someKey: 'string' }) expect(result).toEqual(returnValue) - expect(didKeyResolveSpy).toHaveBeenCalledTimes(1) - expect(didKeyResolveSpy).toHaveBeenCalledWith(agentContext, 'did:key:xxxx', parseDid('did:key:xxxx'), { + expect(didResolverMock.resolve).toHaveBeenCalledTimes(1) + expect(didResolverMock.resolve).toHaveBeenCalledWith(agentContext, 'did:key:xxxx', parseDid('did:key:xxxx'), { someKey: 'string', }) }) diff --git a/packages/core/src/modules/dids/services/index.ts b/packages/core/src/modules/dids/services/index.ts index 1b4265132d..9c86ace87a 100644 --- a/packages/core/src/modules/dids/services/index.ts +++ b/packages/core/src/modules/dids/services/index.ts @@ -1 +1,2 @@ export * from './DidResolverService' +export * from './DidRegistrarService' diff --git a/packages/core/src/modules/dids/types.ts b/packages/core/src/modules/dids/types.ts index 8c5231aa6e..257260f2e8 100644 --- a/packages/core/src/modules/dids/types.ts +++ b/packages/core/src/modules/dids/types.ts @@ -14,3 +14,74 @@ export interface DidResolutionResult { didDocument: DidDocument | null didDocumentMetadata: DidDocumentMetadata } + +// Based on https://identity.foundation/did-registration +export type DidRegistrationExtraOptions = Record +export type DidRegistrationSecretOptions = Record +export type DidRegistrationMetadata = Record +export type DidDocumentOperation = 'setDidDocument' | 'addToDidDocument' | 'removeFromDidDocument' + +export interface DidOperationStateFinished { + state: 'finished' + did: string + secret?: DidRegistrationSecretOptions + didDocument: DidDocument +} + +export interface DidOperationStateFailed { + state: 'failed' + did?: string + secret?: DidRegistrationSecretOptions + didDocument?: DidDocument + reason: string +} + +export interface DidOperationState { + state: 'action' | 'wait' + did?: string + secret?: DidRegistrationSecretOptions + didDocument?: DidDocument +} + +export interface DidCreateOptions { + method?: string + did?: string + options?: DidRegistrationExtraOptions + secret?: DidRegistrationSecretOptions + didDocument?: DidDocument +} + +export interface DidCreateResult { + jobId?: string + didState: DidOperationState | DidOperationStateFinished | DidOperationStateFailed + didRegistrationMetadata: DidRegistrationMetadata + didDocumentMetadata: DidResolutionMetadata +} + +export interface DidUpdateOptions { + did: string + options?: DidRegistrationExtraOptions + secret?: DidRegistrationSecretOptions + didDocumentOperation?: DidDocumentOperation + didDocument: DidDocument | Partial +} + +export interface DidUpdateResult { + jobId?: string + didState: DidOperationState | DidOperationStateFinished | DidOperationStateFailed + didRegistrationMetadata: DidRegistrationMetadata + didDocumentMetadata: DidResolutionMetadata +} + +export interface DidDeactivateOptions { + did: string + options?: DidRegistrationExtraOptions + secret?: DidRegistrationSecretOptions +} + +export interface DidDeactivateResult { + jobId?: string + didState: DidOperationState | DidOperationStateFinished | DidOperationStateFailed + didRegistrationMetadata: DidRegistrationMetadata + didDocumentMetadata: DidResolutionMetadata +} diff --git a/packages/core/src/modules/ledger/services/IndyLedgerService.ts b/packages/core/src/modules/ledger/services/IndyLedgerService.ts index 99142004ef..1e7916a84a 100644 --- a/packages/core/src/modules/ledger/services/IndyLedgerService.ts +++ b/packages/core/src/modules/ledger/services/IndyLedgerService.ts @@ -85,7 +85,7 @@ export class IndyLedgerService { verkey, alias, role, - pool, + pool: pool.id, }) throw error @@ -99,6 +99,34 @@ export class IndyLedgerService { return didResponse } + public async setEndpointsForDid( + agentContext: AgentContext, + did: string, + endpoints: IndyEndpointAttrib + ): Promise { + const pool = this.indyPoolService.ledgerWritePool + + try { + this.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.id}'`, endpoints) + + const request = await this.indy.buildAttribRequest(did, did, null, { endpoint: endpoints }, null) + + const response = await this.submitWriteRequest(agentContext, pool, request, did) + this.logger.debug(`Successfully set endpoints for did '${did}' on ledger '${pool.id}'`, { + response, + endpoints, + }) + } catch (error) { + this.logger.error(`Error setting endpoints for did '${did}' on ledger '${pool.id}'`, { + error, + did, + endpoints, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + public async getEndpointsForDid(agentContext: AgentContext, did: string) { const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) diff --git a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts index 210fac58d3..fe7e16a128 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts @@ -17,6 +17,7 @@ import { DidExchangeState } from '../../../connections' import { ConnectionRepository } from '../../../connections/repository/ConnectionRepository' import { ConnectionService } from '../../../connections/services/ConnectionService' import { DidRepository } from '../../../dids/repository/DidRepository' +import { DidRegistrarService } from '../../../dids/services/DidRegistrarService' import { RecipientModuleConfig } from '../../RecipientModuleConfig' import { MediationGrantMessage } from '../../messages' import { MediationRole, MediationState } from '../../models' @@ -40,6 +41,9 @@ const EventEmitterMock = EventEmitter as jest.Mock jest.mock('../../../../agent/MessageSender') const MessageSenderMock = MessageSender as jest.Mock +jest.mock('../../../dids/services/DidRegistrarService') +const DidRegistrarServiceMock = DidRegistrarService as jest.Mock + const connectionImageUrl = 'https://example.com/image.png' const mockConnection = getMockConnection({ @@ -55,6 +59,7 @@ describe('MediationRecipientService', () => { let wallet: Wallet let mediationRepository: MediationRepository let didRepository: DidRepository + let didRegistrarService: DidRegistrarService let eventEmitter: EventEmitter let connectionService: ConnectionService let connectionRepository: ConnectionRepository @@ -80,7 +85,14 @@ describe('MediationRecipientService', () => { eventEmitter = new EventEmitterMock() connectionRepository = new ConnectionRepositoryMock() didRepository = new DidRepositoryMock() - connectionService = new ConnectionService(config.logger, connectionRepository, didRepository, eventEmitter) + didRegistrarService = new DidRegistrarServiceMock() + connectionService = new ConnectionService( + config.logger, + connectionRepository, + didRepository, + didRegistrarService, + eventEmitter + ) mediationRepository = new MediationRepositoryMock() messageSender = new MessageSenderMock() diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index f6edc17ce4..beccb899ef 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -8,8 +8,6 @@ import { JsonTransformer } from '../../../utils/JsonTransformer' import { IndyWallet } from '../../../wallet/IndyWallet' import { WalletError } from '../../../wallet/error' import { DidKey, DidResolverService } from '../../dids' -import { DidRepository } from '../../dids/repository' -import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' import { SignatureSuiteRegistry } from '../SignatureSuiteRegistry' import { W3cCredentialService } from '../W3cCredentialService' import { orArrayToArray } from '../jsonldUtil' @@ -52,9 +50,6 @@ const signingProviderRegistry = new SigningProviderRegistry([new Bls12381g2Signi jest.mock('../../ledger/services/IndyLedgerService') -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock -const DidRepositoryMock = DidRepository as unknown as jest.Mock - jest.mock('../repository/W3cCredentialRepository') const W3cCredentialRepositoryMock = W3cCredentialRepository as jest.Mock @@ -89,11 +84,7 @@ describe('W3cCredentialService', () => { agentConfig, wallet, }) - didResolverService = new DidResolverService( - new IndyLedgerServiceMock(), - new DidRepositoryMock(), - agentConfig.logger - ) + didResolverService = new DidResolverService(agentConfig.logger, []) w3cCredentialRepository = new W3cCredentialRepositoryMock() w3cCredentialService = new W3cCredentialService(w3cCredentialRepository, didResolverService, signatureSuiteRegistry) w3cCredentialService.documentLoaderWithContext = () => customDocumentLoader diff --git a/packages/core/src/utils/__tests__/MultiBaseEncoder.test.ts b/packages/core/src/utils/__tests__/MultibaseEncoder.test.ts similarity index 100% rename from packages/core/src/utils/__tests__/MultiBaseEncoder.test.ts rename to packages/core/src/utils/__tests__/MultibaseEncoder.test.ts diff --git a/packages/core/src/utils/__tests__/MultiHashEncoder.test.ts b/packages/core/src/utils/__tests__/MultihashEncoder.test.ts similarity index 100% rename from packages/core/src/utils/__tests__/MultiHashEncoder.test.ts rename to packages/core/src/utils/__tests__/MultihashEncoder.test.ts diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts index 0e1462cb3e..caced3613d 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/core/src/wallet/IndyWallet.ts @@ -18,15 +18,17 @@ import type { } from './Wallet' import type { default as Indy, WalletStorageConfig } from 'indy-sdk' +import { inject, injectable } from 'tsyringe' + import { AgentDependencies } from '../agent/AgentDependencies' import { InjectionSymbols } from '../constants' +import { KeyType } from '../crypto' import { Key } from '../crypto/Key' -import { KeyType } from '../crypto/KeyType' import { SigningProviderRegistry } from '../crypto/signing-provider/SigningProviderRegistry' import { AriesFrameworkError, IndySdkError, RecordDuplicateError, RecordNotFoundError } from '../error' import { Logger } from '../logger' -import { inject, injectable } from '../plugins' -import { JsonEncoder, TypedArrayEncoder } from '../utils' +import { TypedArrayEncoder } from '../utils' +import { JsonEncoder } from '../utils/JsonEncoder' import { isError } from '../utils/error' import { isIndyError } from '../utils/indyError' diff --git a/packages/core/src/wallet/Wallet.ts b/packages/core/src/wallet/Wallet.ts index 57f128830d..20218f3928 100644 --- a/packages/core/src/wallet/Wallet.ts +++ b/packages/core/src/wallet/Wallet.ts @@ -2,9 +2,9 @@ import type { Key, KeyType } from '../crypto' import type { Disposable } from '../plugins' import type { EncryptedMessage, + PlaintextMessage, WalletConfig, WalletConfigRekey, - PlaintextMessage, WalletExportImportConfig, } from '../types' import type { Buffer } from '../utils/buffer' diff --git a/packages/core/src/wallet/util/assertIndyWallet.ts b/packages/core/src/wallet/util/assertIndyWallet.ts index 6c6ac4a4eb..a26c43f0fe 100644 --- a/packages/core/src/wallet/util/assertIndyWallet.ts +++ b/packages/core/src/wallet/util/assertIndyWallet.ts @@ -5,6 +5,8 @@ import { IndyWallet } from '../IndyWallet' export function assertIndyWallet(wallet: Wallet): asserts wallet is IndyWallet { if (!(wallet instanceof IndyWallet)) { - throw new AriesFrameworkError(`Expected wallet to be instance of IndyWallet, found ${wallet}`) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const walletClassName = (wallet as any).constructor?.name ?? 'unknown' + throw new AriesFrameworkError(`Expected wallet to be instance of IndyWallet, found ${walletClassName}`) } } diff --git a/packages/core/tests/__fixtures__/didKeyz6Mkqbe1.json b/packages/core/tests/__fixtures__/didKeyz6Mkqbe1.json new file mode 100644 index 0000000000..ed4d179434 --- /dev/null +++ b/packages/core/tests/__fixtures__/didKeyz6Mkqbe1.json @@ -0,0 +1,38 @@ +{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1" + ], + "id": "did:key:z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG", + "verificationMethod": [ + { + "id": "did:key:z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG#z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG", + "type": "Ed25519VerificationKey2018", + "controller": "did:key:z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG", + "publicKeyBase58": "C9Ny7yk9PRKsfW9EJsTJVY12Xn1yke1Jfm24JSy8MLUt" + }, + { + "id": "did:key:z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG#z6LSmTBUAjnhVwsrkbDQgJgViTH5cjozFjFMaguyvpUq2kcz", + "type": "X25519KeyAgreementKey2019", + "controller": "did:key:z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG", + "publicKeyBase58": "An1JeRyqQVA7fCqe9fAYPs4bmbGsZ85ChiCJSMqJKNrE" + } + ], + "authentication": [ + "did:key:z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG#z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG" + ], + "assertionMethod": [ + "did:key:z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG#z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG" + ], + "capabilityInvocation": [ + "did:key:z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG#z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG" + ], + "capabilityDelegation": [ + "did:key:z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG#z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG" + ], + "keyAgreement": [ + "did:key:z6Mkqbe1iDzaixpLmzyvzSR9LdZ2MMHqAXFfMmvz8iw9GZGG#z6LSmTBUAjnhVwsrkbDQgJgViTH5cjozFjFMaguyvpUq2kcz" + ], + "service": [] +} diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 087554b902..e350c12d6d 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -30,7 +30,7 @@ }, "devDependencies": { "@animo-id/react-native-bbs-signatures": "^0.1.0", - "@types/indy-sdk-react-native": "npm:@types/indy-sdk@^1.16.19", + "@types/indy-sdk-react-native": "npm:@types/indy-sdk@^1.16.21", "@types/react-native": "^0.64.10", "indy-sdk-react-native": "^0.2.2", "react": "17.0.1", diff --git a/yarn.lock b/yarn.lock index a36ad3ec64..856c644730 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2408,10 +2408,10 @@ dependencies: "@types/node" "*" -"@types/indy-sdk-react-native@npm:@types/indy-sdk@^1.16.19", "@types/indy-sdk@^1.16.19": - version "1.16.19" - resolved "https://registry.yarnpkg.com/@types/indy-sdk/-/indy-sdk-1.16.19.tgz#f58fc4b5ae67f34cd95c2559fe259b43e0042ead" - integrity sha512-OVgBpLdghrWqPmxEMg76MgIUHo/MvR3xvUeFUJirqdnXGwOs5rQYiZvyECBYeaBEGrSleyAnn5+m4pUfweJyJw== +"@types/indy-sdk-react-native@npm:@types/indy-sdk@^1.16.21", "@types/indy-sdk@^1.16.21": + version "1.16.21" + resolved "https://registry.yarnpkg.com/@types/indy-sdk/-/indy-sdk-1.16.21.tgz#bb6178e2a515115b1bf225fb78506a3017d08aa8" + integrity sha512-SIu1iOa77lkxkGlW09OinFwebe7U5oDYwI70NnPoe9nbDr63i0FozITWEyIdC1BloKvZRXne6nM4i9zy6E3n6g== dependencies: buffer "^6.0.0" From ed69dac7784feea7abe430ad685911faa477fa11 Mon Sep 17 00:00:00 2001 From: Karim Stekelenburg Date: Thu, 11 Aug 2022 14:15:01 +0200 Subject: [PATCH 020/125] feat: fetch verification method types by proof type (#913) Signed-off-by: Karim --- .../domain/key-type/__tests__/ed25519.test.ts | 21 ++++++- .../dids/domain/key-type/bls12381g2.ts | 2 +- .../modules/dids/domain/key-type/ed25519.ts | 22 +++++-- .../src/modules/vc/SignatureSuiteRegistry.ts | 20 ++++++- .../src/modules/vc/W3cCredentialService.ts | 12 +++- packages/core/src/modules/vc/W3cVcModule.ts | 20 +++++-- .../vc/__tests__/W3cCredentialService.test.ts | 59 +++++++++++++++++-- .../modules/vc/__tests__/W3cVcModule.test.ts | 12 ++-- 8 files changed, 137 insertions(+), 31 deletions(-) diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts index cd93ada9cd..c66b4fc7aa 100644 --- a/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/ed25519.test.ts @@ -52,7 +52,10 @@ describe('ed25519', () => { }) it('supports Ed25519VerificationKey2018 verification method type', () => { - expect(keyDidEd25519.supportedVerificationMethodTypes).toMatchObject(['Ed25519VerificationKey2018']) + expect(keyDidEd25519.supportedVerificationMethodTypes).toMatchObject([ + 'Ed25519VerificationKey2018', + 'Ed25519VerificationKey2020', + ]) }) it('returns key for Ed25519VerificationKey2018 verification method', () => { @@ -63,6 +66,22 @@ describe('ed25519', () => { expect(key.fingerprint).toBe(TEST_ED25519_FINGERPRINT) }) + it('returns key for Ed25519VerificationKey2020 verification method', () => { + const verificationMethod = JsonTransformer.fromJSON( + { + id: 'did:example:123', + type: 'Ed25519VerificationKey2020', + controller: 'did:example:123', + publicKeyMultibase: 'z6MkkBWg1AnNxxWiq77gJDeHsLhGN6JV9Y3d6WiTifUs1sZi', + }, + VerificationMethod + ) + + const key = keyDidEd25519.getKeyFromVerificationMethod(verificationMethod) + + expect(key.publicKeyBase58).toBe('6jFdQvXwdR2FicGycegT2F9GYX2djeoGQVoXtPWr6enL') + }) + it('throws an error if an invalid verification method is passed', () => { const verificationMethod = JsonTransformer.fromJSON(didKeyEd25519Fixture.verificationMethod[0], VerificationMethod) diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts index a17d20130a..f7cc4b2a6f 100644 --- a/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts @@ -4,7 +4,7 @@ import type { KeyDidMapping } from './keyDidMapping' import { KeyType } from '../../../../crypto' import { Key } from '../../../../crypto/Key' -const VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020 = 'Bls12381G2Key2020' +export const VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020 = 'Bls12381G2Key2020' export function getBls12381g2VerificationMethod(did: string, key: Key) { return { diff --git a/packages/core/src/modules/dids/domain/key-type/ed25519.ts b/packages/core/src/modules/dids/domain/key-type/ed25519.ts index eb360c72fb..4098d230b5 100644 --- a/packages/core/src/modules/dids/domain/key-type/ed25519.ts +++ b/packages/core/src/modules/dids/domain/key-type/ed25519.ts @@ -6,7 +6,8 @@ import { convertPublicKeyToX25519 } from '@stablelib/ed25519' import { KeyType } from '../../../../crypto' import { Key } from '../../../../crypto/Key' -const VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018 = 'Ed25519VerificationKey2018' +export const VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018 = 'Ed25519VerificationKey2018' +export const VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020 = 'Ed25519VerificationKey2020' export function getEd25519VerificationMethod({ key, id, controller }: { id: string; key: Key; controller: string }) { return { @@ -18,19 +19,28 @@ export function getEd25519VerificationMethod({ key, id, controller }: { id: stri } export const keyDidEd25519: KeyDidMapping = { - supportedVerificationMethodTypes: [VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018], + supportedVerificationMethodTypes: [ + VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, + VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020, + ], getVerificationMethods: (did, key) => [ getEd25519VerificationMethod({ id: `${did}#${key.fingerprint}`, key, controller: did }), ], getKeyFromVerificationMethod: (verificationMethod: VerificationMethod) => { if ( - verificationMethod.type !== VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018 || - !verificationMethod.publicKeyBase58 + verificationMethod.type === VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018 && + verificationMethod.publicKeyBase58 + ) { + return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.Ed25519) + } + if ( + verificationMethod.type === VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020 && + verificationMethod.publicKeyMultibase ) { - throw new Error('Invalid verification method passed') + return Key.fromFingerprint(verificationMethod.publicKeyMultibase) } - return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.Ed25519) + throw new Error('Invalid verification method passed') }, } diff --git a/packages/core/src/modules/vc/SignatureSuiteRegistry.ts b/packages/core/src/modules/vc/SignatureSuiteRegistry.ts index 925fa3f334..b3ac5f4316 100644 --- a/packages/core/src/modules/vc/SignatureSuiteRegistry.ts +++ b/packages/core/src/modules/vc/SignatureSuiteRegistry.ts @@ -11,8 +11,8 @@ export const SignatureSuiteToken = Symbol('SignatureSuiteToken') export interface SuiteInfo { suiteClass: typeof LinkedDataSignature proofType: string - requiredKeyType: string - keyType: string + verificationMethodTypes: string[] + keyTypes: KeyType[] } @injectable() @@ -27,8 +27,12 @@ export class SignatureSuiteRegistry { return this.suiteMapping.map((x) => x.proofType) } + public getByVerificationMethodType(verificationMethodType: string) { + return this.suiteMapping.find((x) => x.verificationMethodTypes.includes(verificationMethodType)) + } + public getByKeyType(keyType: KeyType) { - return this.suiteMapping.find((x) => x.keyType === keyType) + return this.suiteMapping.find((x) => x.keyTypes.includes(keyType)) } public getByProofType(proofType: string) { @@ -40,4 +44,14 @@ export class SignatureSuiteRegistry { return suiteInfo } + + public getVerificationMethodTypesByProofType(proofType: string): string[] { + const suiteInfo = this.suiteMapping.find((suiteInfo) => suiteInfo.proofType === proofType) + + if (!suiteInfo) { + throw new AriesFrameworkError(`No verification method type found for proof type: ${proofType}`) + } + + return suiteInfo.verificationMethodTypes + } } diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 4b9ccef0aa..18fa986542 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -63,7 +63,7 @@ export class W3cCredentialService { const signingKey = await this.getPublicKeyFromVerificationMethod(agentContext, options.verificationMethod) const suiteInfo = this.suiteRegistry.getByProofType(options.proofType) - if (signingKey.keyType !== suiteInfo.keyType) { + if (!suiteInfo.keyTypes.includes(signingKey.keyType)) { throw new AriesFrameworkError('The key type of the verification method does not match the suite') } @@ -169,7 +169,7 @@ export class W3cCredentialService { const signingKey = await this.getPublicKeyFromVerificationMethod(agentContext, options.verificationMethod) - if (signingKey.keyType !== suiteInfo.keyType) { + if (!suiteInfo.keyTypes.includes(signingKey.keyType)) { throw new AriesFrameworkError('The key type of the verification method does not match the suite') } @@ -379,6 +379,14 @@ export class W3cCredentialService { return result.map((record) => record.credential) } + public getVerificationMethodTypesByProofType(proofType: string): string[] { + return this.suiteRegistry.getByProofType(proofType).verificationMethodTypes + } + + public getKeyTypesByProofType(proofType: string): string[] { + return this.suiteRegistry.getByProofType(proofType).keyTypes + } + public async findCredentialRecordByQuery( agentContext: AgentContext, query: Query diff --git a/packages/core/src/modules/vc/W3cVcModule.ts b/packages/core/src/modules/vc/W3cVcModule.ts index 30d5eb3be9..64a8782501 100644 --- a/packages/core/src/modules/vc/W3cVcModule.ts +++ b/packages/core/src/modules/vc/W3cVcModule.ts @@ -1,6 +1,11 @@ import type { DependencyManager, Module } from '../../plugins' import { KeyType } from '../../crypto' +import { VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020 } from '../dids/domain/key-type/bls12381g2' +import { + VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, + VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020, +} from '../dids/domain/key-type/ed25519' import { SignatureSuiteRegistry, SignatureSuiteToken } from './SignatureSuiteRegistry' import { W3cCredentialService } from './W3cCredentialService' @@ -19,22 +24,25 @@ export class W3cVcModule implements Module { dependencyManager.registerInstance(SignatureSuiteToken, { suiteClass: Ed25519Signature2018, proofType: 'Ed25519Signature2018', - requiredKeyType: 'Ed25519VerificationKey2018', - keyType: KeyType.Ed25519, + verificationMethodTypes: [ + VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, + VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020, + ], + keyTypes: [KeyType.Ed25519], }) // This will be moved out of core into the bbs module dependencyManager.registerInstance(SignatureSuiteToken, { suiteClass: BbsBlsSignature2020, proofType: 'BbsBlsSignature2020', - requiredKeyType: 'BbsBlsSignatureProof2020', - keyType: KeyType.Bls12381g2, + verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], + keyTypes: [KeyType.Bls12381g2], }) dependencyManager.registerInstance(SignatureSuiteToken, { suiteClass: BbsBlsSignatureProof2020, proofType: 'BbsBlsSignatureProof2020', - requiredKeyType: 'BbsBlsSignatureProof2020', - keyType: KeyType.Bls12381g2, + verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], + keyTypes: [KeyType.Bls12381g2], }) } } diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index beccb899ef..6f81ce43c7 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -8,6 +8,11 @@ import { JsonTransformer } from '../../../utils/JsonTransformer' import { IndyWallet } from '../../../wallet/IndyWallet' import { WalletError } from '../../../wallet/error' import { DidKey, DidResolverService } from '../../dids' +import { VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020 } from '../../dids/domain/key-type/bls12381g2' +import { + VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, + VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020, +} from '../../dids/domain/key-type/ed25519' import { SignatureSuiteRegistry } from '../SignatureSuiteRegistry' import { W3cCredentialService } from '../W3cCredentialService' import { orArrayToArray } from '../jsonldUtil' @@ -29,20 +34,24 @@ const signatureSuiteRegistry = new SignatureSuiteRegistry([ { suiteClass: Ed25519Signature2018, proofType: 'Ed25519Signature2018', - requiredKeyType: 'Ed25519VerificationKey2018', - keyType: KeyType.Ed25519, + + verificationMethodTypes: [ + VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, + VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020, + ], + keyTypes: [KeyType.Ed25519], }, { suiteClass: BbsBlsSignature2020, proofType: 'BbsBlsSignature2020', - requiredKeyType: 'BbsBlsSignatureProof2020', - keyType: KeyType.Bls12381g2, + verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], + keyTypes: [KeyType.Bls12381g2], }, { suiteClass: BbsBlsSignatureProof2020, proofType: 'BbsBlsSignatureProof2020', - requiredKeyType: 'BbsBlsSignatureProof2020', - keyType: KeyType.Bls12381g2, + verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], + keyTypes: [KeyType.Bls12381g2], }, ]) @@ -94,6 +103,44 @@ describe('W3cCredentialService', () => { await wallet.delete() }) + describe('Utility methods', () => { + describe('getKeyTypesByProofType', () => { + it('should return the correct key types for Ed25519Signature2018 proof type', async () => { + const keyTypes = w3cCredentialService.getKeyTypesByProofType('Ed25519Signature2018') + expect(keyTypes).toEqual([KeyType.Ed25519]) + }) + it('should return the correct key types for BbsBlsSignature2020 proof type', async () => { + const keyTypes = w3cCredentialService.getKeyTypesByProofType('BbsBlsSignature2020') + expect(keyTypes).toEqual([KeyType.Bls12381g2]) + }) + it('should return the correct key types for BbsBlsSignatureProof2020 proof type', async () => { + const keyTypes = w3cCredentialService.getKeyTypesByProofType('BbsBlsSignatureProof2020') + expect(keyTypes).toEqual([KeyType.Bls12381g2]) + }) + }) + + describe('getVerificationMethodTypesByProofType', () => { + it('should return the correct key types for Ed25519Signature2018 proof type', async () => { + const verificationMethodTypes = + w3cCredentialService.getVerificationMethodTypesByProofType('Ed25519Signature2018') + expect(verificationMethodTypes).toEqual([ + VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, + VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020, + ]) + }) + it('should return the correct key types for BbsBlsSignature2020 proof type', async () => { + const verificationMethodTypes = + w3cCredentialService.getVerificationMethodTypesByProofType('BbsBlsSignature2020') + expect(verificationMethodTypes).toEqual([VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020]) + }) + it('should return the correct key types for BbsBlsSignatureProof2020 proof type', async () => { + const verificationMethodTypes = + w3cCredentialService.getVerificationMethodTypesByProofType('BbsBlsSignatureProof2020') + expect(verificationMethodTypes).toEqual([VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020]) + }) + }) + }) + describe('Ed25519Signature2018', () => { let issuerDidKey: DidKey let verificationMethod: string diff --git a/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts b/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts index e60807c1b6..d48d1672e6 100644 --- a/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts @@ -24,23 +24,23 @@ describe('W3cVcModule', () => { expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(3) expect(dependencyManager.registerInstance).toHaveBeenCalledWith(SignatureSuiteToken, { suiteClass: Ed25519Signature2018, + verificationMethodTypes: ['Ed25519VerificationKey2018', 'Ed25519VerificationKey2020'], proofType: 'Ed25519Signature2018', - requiredKeyType: 'Ed25519VerificationKey2018', - keyType: KeyType.Ed25519, + keyTypes: [KeyType.Ed25519], }) expect(dependencyManager.registerInstance).toHaveBeenCalledWith(SignatureSuiteToken, { suiteClass: BbsBlsSignature2020, + verificationMethodTypes: ['Bls12381G2Key2020'], proofType: 'BbsBlsSignature2020', - requiredKeyType: 'BbsBlsSignatureProof2020', - keyType: KeyType.Bls12381g2, + keyTypes: [KeyType.Bls12381g2], }) expect(dependencyManager.registerInstance).toHaveBeenCalledWith(SignatureSuiteToken, { suiteClass: BbsBlsSignatureProof2020, proofType: 'BbsBlsSignatureProof2020', - requiredKeyType: 'BbsBlsSignatureProof2020', - keyType: KeyType.Bls12381g2, + verificationMethodTypes: ['Bls12381G2Key2020'], + keyTypes: [KeyType.Bls12381g2], }) }) }) From f38ac05875e38b6cc130bcb9f603e82657aabe9c Mon Sep 17 00:00:00 2001 From: NB-MikeRichardson <93971245+NB-MikeRichardson@users.noreply.github.com> Date: Wed, 17 Aug 2022 15:33:52 +0300 Subject: [PATCH 021/125] feat: add present proof v2 (#979) Signed-off-by: Mike Richardson Co-authored-by: NB Prasad Katkar Co-authored-by: Karim Stekelenburg --- demo/src/Alice.ts | 14 +- demo/src/Faber.ts | 22 +- packages/core/src/agent/AgentConfig.ts | 2 +- packages/core/src/agent/BaseAgent.ts | 2 +- .../core/src/agent/__tests__/Agent.test.ts | 10 +- .../src/decorators/ack/AckDecorator.test.ts | 3 +- .../formats/CredentialFormatService.ts | 11 - .../indy/IndyCredentialFormatService.ts | 16 +- .../indy/services/IndyHolderService.ts | 2 +- .../indy/services/IndyRevocationService.ts | 2 +- .../core/src/modules/proofs/ProofEvents.ts | 2 +- .../proofs/ProofResponseCoordinator.ts | 42 +- .../core/src/modules/proofs/ProofService.ts | 232 ++ packages/core/src/modules/proofs/ProofsApi.ts | 588 ++--- .../src/modules/proofs/ProofsApiOptions.ts | 74 + .../core/src/modules/proofs/ProofsModule.ts | 10 +- .../src/modules/proofs/ProofsModuleConfig.ts | 2 +- .../proofs/__tests__/ProofRequest.test.ts | 8 +- .../proofs/__tests__/ProofState.test.ts | 2 +- .../proofs/__tests__/ProofsModule.test.ts | 10 +- ...Service.test.ts => V1ProofService.test.ts} | 63 +- .../proofs/__tests__/V2ProofService.test.ts | 274 +++ .../errors/PresentationProblemReportError.ts | 24 - .../core/src/modules/proofs/errors/index.ts | 1 - .../src/modules/proofs/formats/ProofFormat.ts | 45 + .../proofs/formats/ProofFormatConstants.ts | 4 + .../proofs/formats/ProofFormatService.ts | 79 + .../formats/ProofFormatServiceOptions.ts | 31 + .../proofs/formats/indy/IndyProofFormat.ts | 77 + .../formats/indy/IndyProofFormatService.ts | 646 ++++++ .../indy/IndyProofFormatsServiceOptions.ts | 44 + .../proofs/formats/indy/IndyProofUtils.ts | 115 + .../indy/errors/InvalidEncodedValueError.ts | 3 + .../errors/MissingIndyProofMessageError.ts | 3 + .../indy}/models/AttributeFilter.ts | 2 +- .../indy}/models/PredicateType.ts | 0 .../indy}/models/ProofAttributeInfo.ts | 4 +- .../indy}/models/ProofPredicateInfo.ts | 2 +- .../{ => formats/indy}/models/ProofRequest.ts | 8 +- .../indy}/models/RequestedAttribute.ts | 2 +- .../indy}/models/RequestedCredentials.ts | 8 +- .../indy}/models/RequestedPredicate.ts | 2 +- .../indy}/models/RetrievedCredentials.ts | 0 .../proofs/formats/indy/models/index.ts | 7 + .../formats/models/ProofAttachmentFormat.ts | 7 + .../models/ProofFormatServiceOptions.ts | 64 + .../proofs/handlers/PresentationAckHandler.ts | 17 - .../proofs/handlers/PresentationHandler.ts | 50 - .../PresentationProblemReportHandler.ts | 17 - .../handlers/ProposePresentationHandler.ts | 61 - .../handlers/RequestPresentationHandler.ts | 96 - .../core/src/modules/proofs/handlers/index.ts | 5 - packages/core/src/modules/proofs/index.ts | 11 +- .../proofs/messages/PresentationAckMessage.ts | 17 +- .../PresentationProblemReportMessage.ts | 23 - .../core/src/modules/proofs/messages/index.ts | 6 - .../models/GetRequestedCredentialsConfig.ts | 19 + .../modules/proofs/models/ModuleOptions.ts | 20 + .../{ => models}/ProofAutoAcceptType.ts | 0 .../modules/proofs/models/ProofFormatSpec.ts | 25 + .../proofs/models/ProofProtocolVersion.ts | 4 + .../proofs/models/ProofServiceOptions.ts | 73 + .../modules/proofs/{ => models}/ProofState.ts | 1 + .../modules/proofs/models/SharedOptions.ts | 62 + .../core/src/modules/proofs/models/index.ts | 17 +- .../proofs/protocol/v1/V1ProofService.ts | 1046 +++++++++ .../__tests__/indy-proof-presentation.test.ts | 246 ++ .../v1/__tests__/indy-proof-proposal.test.ts | 108 + .../v1/__tests__/indy-proof-request.test.ts | 156 ++ .../V1PresentationProblemReportError.ts | 23 + .../proofs/protocol/v1/errors/index.ts | 1 + .../v1/handlers/V1PresentationAckHandler.ts | 17 + .../v1/handlers/V1PresentationHandler.ts | 72 + .../V1PresentationProblemReportHandler.ts | 17 + .../handlers/V1ProposePresentationHandler.ts | 104 + .../handlers/V1RequestPresentationHandler.ts | 123 + .../proofs/protocol/v1/handlers/index.ts | 5 + .../src/modules/proofs/protocol/v1/index.ts | 1 + .../v1/messages/V1PresentationAckMessage.ts | 14 + .../v1/messages/V1PresentationMessage.ts} | 37 +- .../V1PresentationProblemReportMessage.ts | 23 + .../messages/V1ProposePresentationMessage.ts} | 13 +- .../messages/V1RequestPresentationMessage.ts} | 43 +- .../proofs/protocol/v1/messages/index.ts | 4 + .../{ => protocol/v1}/models/PartialProof.ts | 0 .../v1}/models/ProofAttribute.ts | 0 .../v1}/models/ProofIdentifier.ts | 2 +- .../v1}/models/RequestedProof.ts | 0 .../v1/models/V1PresentationPreview.ts} | 8 +- .../proofs/protocol/v1/models/index.ts | 4 + .../proofs/protocol/v2/V2ProofService.ts | 862 +++++++ .../__tests__/indy-proof-presentation.test.ts | 254 +++ .../v2/__tests__/indy-proof-proposal.test.ts | 100 + .../v2/__tests__/indy-proof-request.test.ts | 156 ++ .../V2PresentationProblemReportError.ts | 23 + .../proofs/protocol/v2/errors/index.ts | 1 + .../v2/handlers/V2PresentationAckHandler.ts | 17 + .../v2/handlers/V2PresentationHandler.ts | 72 + .../V2PresentationProblemReportHandler.ts | 17 + .../handlers/V2ProposePresentationHandler.ts | 95 + .../handlers/V2RequestPresentationHandler.ts | 106 + .../src/modules/proofs/protocol/v2/index.ts | 1 + .../v2/messages/V2PresentationAckMessage.ts | 14 + .../v2/messages/V2PresentationMessage.ts | 93 + .../V2PresentationProblemReportMessage.ts | 23 + .../messages/V2ProposalPresentationMessage.ts | 100 + .../messages/V2RequestPresentationMessage.ts | 125 ++ .../proofs/protocol/v2/messages/index.ts | 5 + .../repository/PresentationExchangeRecord.ts | 4 + .../modules/proofs/repository/ProofRecord.ts | 34 +- .../proofs/repository/ProofRepository.ts | 34 + .../src/modules/proofs/repository/index.ts | 1 + .../modules/proofs/services/ProofService.ts | 1222 ---------- .../core/src/modules/proofs/services/index.ts | 1 - packages/core/src/types.ts | 1 + .../src/utils/__tests__/objectCheck.test.ts | 34 + packages/core/src/utils/indyProofRequest.ts | 2 +- packages/core/src/utils/objectCheck.ts | 70 + packages/core/tests/helpers.ts | 73 +- .../core/tests/proofs-sub-protocol.test.ts | 278 ++- packages/core/tests/proofs.test.ts | 370 --- ...st.ts => v1-connectionless-proofs.test.ts} | 152 +- packages/core/tests/v1-indy-proofs.test.ts | 589 +++++ .../core/tests/v1-proofs-auto-accept.test.ts | 253 +++ .../tests/v2-connectionless-proofs.test.ts | 386 ++++ packages/core/tests/v2-indy-proofs.test.ts | 538 +++++ ....test.ts => v2-proofs-auto-accept.test.ts} | 158 +- yarn.lock | 1989 +++++++++-------- 128 files changed, 9937 insertions(+), 3476 deletions(-) create mode 100644 packages/core/src/modules/proofs/ProofService.ts create mode 100644 packages/core/src/modules/proofs/ProofsApiOptions.ts rename packages/core/src/modules/proofs/__tests__/{ProofService.test.ts => V1ProofService.test.ts} (81%) create mode 100644 packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts delete mode 100644 packages/core/src/modules/proofs/errors/PresentationProblemReportError.ts create mode 100644 packages/core/src/modules/proofs/formats/ProofFormat.ts create mode 100644 packages/core/src/modules/proofs/formats/ProofFormatConstants.ts create mode 100644 packages/core/src/modules/proofs/formats/ProofFormatService.ts create mode 100644 packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts create mode 100644 packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts create mode 100644 packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts create mode 100644 packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts create mode 100644 packages/core/src/modules/proofs/formats/indy/IndyProofUtils.ts create mode 100644 packages/core/src/modules/proofs/formats/indy/errors/InvalidEncodedValueError.ts create mode 100644 packages/core/src/modules/proofs/formats/indy/errors/MissingIndyProofMessageError.ts rename packages/core/src/modules/proofs/{ => formats/indy}/models/AttributeFilter.ts (98%) rename packages/core/src/modules/proofs/{ => formats/indy}/models/PredicateType.ts (100%) rename packages/core/src/modules/proofs/{ => formats/indy}/models/ProofAttributeInfo.ts (84%) rename packages/core/src/modules/proofs/{ => formats/indy}/models/ProofPredicateInfo.ts (94%) rename packages/core/src/modules/proofs/{ => formats/indy}/models/ProofRequest.ts (90%) rename packages/core/src/modules/proofs/{ => formats/indy}/models/RequestedAttribute.ts (89%) rename packages/core/src/modules/proofs/{ => formats/indy}/models/RequestedCredentials.ts (87%) rename packages/core/src/modules/proofs/{ => formats/indy}/models/RequestedPredicate.ts (92%) rename packages/core/src/modules/proofs/{ => formats/indy}/models/RetrievedCredentials.ts (100%) create mode 100644 packages/core/src/modules/proofs/formats/indy/models/index.ts create mode 100644 packages/core/src/modules/proofs/formats/models/ProofAttachmentFormat.ts create mode 100644 packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts delete mode 100644 packages/core/src/modules/proofs/handlers/PresentationAckHandler.ts delete mode 100644 packages/core/src/modules/proofs/handlers/PresentationHandler.ts delete mode 100644 packages/core/src/modules/proofs/handlers/PresentationProblemReportHandler.ts delete mode 100644 packages/core/src/modules/proofs/handlers/ProposePresentationHandler.ts delete mode 100644 packages/core/src/modules/proofs/handlers/RequestPresentationHandler.ts delete mode 100644 packages/core/src/modules/proofs/handlers/index.ts delete mode 100644 packages/core/src/modules/proofs/messages/PresentationProblemReportMessage.ts delete mode 100644 packages/core/src/modules/proofs/messages/index.ts create mode 100644 packages/core/src/modules/proofs/models/GetRequestedCredentialsConfig.ts create mode 100644 packages/core/src/modules/proofs/models/ModuleOptions.ts rename packages/core/src/modules/proofs/{ => models}/ProofAutoAcceptType.ts (100%) create mode 100644 packages/core/src/modules/proofs/models/ProofFormatSpec.ts create mode 100644 packages/core/src/modules/proofs/models/ProofProtocolVersion.ts create mode 100644 packages/core/src/modules/proofs/models/ProofServiceOptions.ts rename packages/core/src/modules/proofs/{ => models}/ProofState.ts (94%) create mode 100644 packages/core/src/modules/proofs/models/SharedOptions.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/errors/V1PresentationProblemReportError.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/errors/index.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/handlers/index.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/index.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationAckMessage.ts rename packages/core/src/modules/proofs/{messages/PresentationMessage.ts => protocol/v1/messages/V1PresentationMessage.ts} (57%) create mode 100644 packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationProblemReportMessage.ts rename packages/core/src/modules/proofs/{messages/ProposePresentationMessage.ts => protocol/v1/messages/V1ProposePresentationMessage.ts} (77%) rename packages/core/src/modules/proofs/{messages/RequestPresentationMessage.ts => protocol/v1/messages/V1RequestPresentationMessage.ts} (58%) create mode 100644 packages/core/src/modules/proofs/protocol/v1/messages/index.ts rename packages/core/src/modules/proofs/{ => protocol/v1}/models/PartialProof.ts (100%) rename packages/core/src/modules/proofs/{ => protocol/v1}/models/ProofAttribute.ts (100%) rename packages/core/src/modules/proofs/{ => protocol/v1}/models/ProofIdentifier.ts (92%) rename packages/core/src/modules/proofs/{ => protocol/v1}/models/RequestedProof.ts (100%) rename packages/core/src/modules/proofs/{messages/PresentationPreview.ts => protocol/v1/models/V1PresentationPreview.ts} (93%) create mode 100644 packages/core/src/modules/proofs/protocol/v1/models/index.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/errors/V2PresentationProblemReportError.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/errors/index.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/index.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationAckMessage.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationMessage.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationProblemReportMessage.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/messages/V2ProposalPresentationMessage.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/messages/V2RequestPresentationMessage.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/messages/index.ts create mode 100644 packages/core/src/modules/proofs/repository/PresentationExchangeRecord.ts delete mode 100644 packages/core/src/modules/proofs/services/ProofService.ts delete mode 100644 packages/core/src/modules/proofs/services/index.ts create mode 100644 packages/core/src/utils/__tests__/objectCheck.test.ts create mode 100644 packages/core/src/utils/objectCheck.ts delete mode 100644 packages/core/tests/proofs.test.ts rename packages/core/tests/{connectionless-proofs.test.ts => v1-connectionless-proofs.test.ts} (75%) create mode 100644 packages/core/tests/v1-indy-proofs.test.ts create mode 100644 packages/core/tests/v1-proofs-auto-accept.test.ts create mode 100644 packages/core/tests/v2-connectionless-proofs.test.ts create mode 100644 packages/core/tests/v2-indy-proofs.test.ts rename packages/core/tests/{proofs-auto-accept.test.ts => v2-proofs-auto-accept.test.ts} (58%) diff --git a/demo/src/Alice.ts b/demo/src/Alice.ts index 359cd7ac31..54eae3b019 100644 --- a/demo/src/Alice.ts +++ b/demo/src/Alice.ts @@ -103,11 +103,17 @@ export class Alice extends BaseAgent { } public async acceptProofRequest(proofRecord: ProofRecord) { - const retrievedCredentials = await this.agent.proofs.getRequestedCredentialsForProofRequest(proofRecord.id, { - filterByPresentationPreview: true, + const requestedCredentials = await this.agent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: proofRecord.id, + config: { + filterByPresentationPreview: true, + }, + }) + + await this.agent.proofs.acceptRequest({ + proofRecordId: proofRecord.id, + proofFormats: requestedCredentials.proofFormats, }) - const requestedCredentials = this.agent.proofs.autoSelectCredentialsForProofRequest(retrievedCredentials) - await this.agent.proofs.acceptRequest(proofRecord.id, requestedCredentials) console.log(greenText('\nProof request accepted!\n')) } diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index c5b23058e6..e94b3a922b 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -2,7 +2,13 @@ import type { ConnectionRecord } from '@aries-framework/core' import type { CredDef, Schema } from 'indy-sdk' import type BottomBar from 'inquirer/lib/ui/bottom-bar' -import { V1CredentialPreview, AttributeFilter, ProofAttributeInfo, utils } from '@aries-framework/core' +import { + AttributeFilter, + ProofAttributeInfo, + ProofProtocolVersion, + utils, + V1CredentialPreview, +} from '@aries-framework/core' import { ui } from 'inquirer' import { BaseAgent } from './BaseAgent' @@ -137,8 +143,18 @@ export class Faber extends BaseAgent { const connectionRecord = await this.getConnectionRecord() const proofAttribute = await this.newProofAttribute() await this.printProofFlow(greenText('\nRequesting proof...\n', false)) - await this.agent.proofs.requestProof(connectionRecord.id, { - requestedAttributes: proofAttribute, + + await this.agent.proofs.requestProof({ + protocolVersion: ProofProtocolVersion.V1, + connectionId: connectionRecord.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324864', + requestedAttributes: proofAttribute, + }, + }, }) this.ui.updateBottomBar( `\nProof request sent!\n\nGo to the Alice agent to accept the proof request\n\n${Color.Reset}` diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index 1f391d481d..be90bdf17b 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -6,7 +6,7 @@ import { DID_COMM_TRANSPORT_QUEUE } from '../constants' import { AriesFrameworkError } from '../error' import { ConsoleLogger, LogLevel } from '../logger' import { AutoAcceptCredential } from '../modules/credentials/models/CredentialAutoAcceptType' -import { AutoAcceptProof } from '../modules/proofs/ProofAutoAcceptType' +import { AutoAcceptProof } from '../modules/proofs/models/ProofAutoAcceptType' import { DidCommMimeType } from '../types' export class AgentConfig { diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index 2a895f777b..c18f937f25 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -83,7 +83,7 @@ export abstract class BaseAgent { // We set the modules in the constructor because that allows to set them as read-only this.connections = this.dependencyManager.resolve(ConnectionsApi) this.credentials = this.dependencyManager.resolve(CredentialsApi) as CredentialsApi - this.proofs = this.dependencyManager.resolve(ProofsApi) + this.proofs = this.dependencyManager.resolve(ProofsApi) as ProofsApi this.mediator = this.dependencyManager.resolve(MediatorApi) this.mediationRecipient = this.dependencyManager.resolve(RecipientApi) this.basicMessages = this.dependencyManager.resolve(BasicMessagesApi) diff --git a/packages/core/src/agent/__tests__/Agent.test.ts b/packages/core/src/agent/__tests__/Agent.test.ts index 7c92573fa4..c890630ca9 100644 --- a/packages/core/src/agent/__tests__/Agent.test.ts +++ b/packages/core/src/agent/__tests__/Agent.test.ts @@ -10,8 +10,10 @@ import { CredentialRepository } from '../../modules/credentials' import { CredentialsApi } from '../../modules/credentials/CredentialsApi' import { IndyLedgerService } from '../../modules/ledger' import { LedgerApi } from '../../modules/ledger/LedgerApi' -import { ProofRepository, ProofService } from '../../modules/proofs' +import { ProofRepository } from '../../modules/proofs' import { ProofsApi } from '../../modules/proofs/ProofsApi' +import { V1ProofService } from '../../modules/proofs/protocol/v1' +import { V2ProofService } from '../../modules/proofs/protocol/v2' import { MediatorApi, RecipientApi, @@ -115,8 +117,9 @@ describe('Agent', () => { expect(container.resolve(ConnectionRepository)).toBeInstanceOf(ConnectionRepository) expect(container.resolve(TrustPingService)).toBeInstanceOf(TrustPingService) + expect(container.resolve(V1ProofService)).toBeInstanceOf(V1ProofService) + expect(container.resolve(V2ProofService)).toBeInstanceOf(V2ProofService) expect(container.resolve(ProofsApi)).toBeInstanceOf(ProofsApi) - expect(container.resolve(ProofService)).toBeInstanceOf(ProofService) expect(container.resolve(ProofRepository)).toBeInstanceOf(ProofRepository) expect(container.resolve(CredentialsApi)).toBeInstanceOf(CredentialsApi) @@ -157,8 +160,9 @@ describe('Agent', () => { expect(container.resolve(ConnectionRepository)).toBe(container.resolve(ConnectionRepository)) expect(container.resolve(TrustPingService)).toBe(container.resolve(TrustPingService)) + expect(container.resolve(V1ProofService)).toBe(container.resolve(V1ProofService)) + expect(container.resolve(V2ProofService)).toBe(container.resolve(V2ProofService)) expect(container.resolve(ProofsApi)).toBe(container.resolve(ProofsApi)) - expect(container.resolve(ProofService)).toBe(container.resolve(ProofService)) expect(container.resolve(ProofRepository)).toBe(container.resolve(ProofRepository)) expect(container.resolve(CredentialsApi)).toBe(container.resolve(CredentialsApi)) diff --git a/packages/core/src/decorators/ack/AckDecorator.test.ts b/packages/core/src/decorators/ack/AckDecorator.test.ts index 324d997d99..257039be98 100644 --- a/packages/core/src/decorators/ack/AckDecorator.test.ts +++ b/packages/core/src/decorators/ack/AckDecorator.test.ts @@ -4,6 +4,7 @@ import { JsonTransformer } from '../../utils/JsonTransformer' import { MessageValidator } from '../../utils/MessageValidator' import { Compose } from '../../utils/mixins' +import { AckValues } from './AckDecorator' import { AckDecorated } from './AckDecoratorExtension' describe('Decorators | AckDecoratorExtension', () => { @@ -15,7 +16,7 @@ describe('Decorators | AckDecoratorExtension', () => { test('transforms AckDecorator class to JSON', () => { const message = new TestMessage() - message.setPleaseAck() + message.setPleaseAck([AckValues.Receipt]) expect(message.toJSON()).toEqual({ '@id': undefined, '@type': undefined, diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts index 9d3f6f5da9..bf3c842d29 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts @@ -1,6 +1,4 @@ import type { AgentContext } from '../../../agent' -import type { EventEmitter } from '../../../agent/EventEmitter' -import type { CredentialRepository } from '../repository' import type { CredentialFormat } from './CredentialFormat' import type { FormatCreateProposalOptions, @@ -23,14 +21,6 @@ import { Attachment, AttachmentData } from '../../../decorators/attachment/Attac import { JsonEncoder } from '../../../utils/JsonEncoder' export abstract class CredentialFormatService { - protected credentialRepository: CredentialRepository - protected eventEmitter: EventEmitter - - public constructor(credentialRepository: CredentialRepository, eventEmitter: EventEmitter) { - this.credentialRepository = credentialRepository - this.eventEmitter = eventEmitter - } - abstract readonly formatKey: CF['formatKey'] abstract readonly credentialRecordType: CF['credentialRecordType'] @@ -86,7 +76,6 @@ export abstract class CredentialFormatService { - const credProposalJson = attachment.getDataAsJson() + const proposalJson = attachment.getDataAsJson() - if (!credProposalJson) { - throw new AriesFrameworkError('Missing indy credential proposal data payload') - } - - const credProposal = JsonTransformer.fromJSON(credProposalJson, IndyCredPropose) - MessageValidator.validateSync(credProposal) + // fromJSON also validates + JsonTransformer.fromJSON(proposalJson, IndyCredPropose) } public async acceptProposal( diff --git a/packages/core/src/modules/indy/services/IndyHolderService.ts b/packages/core/src/modules/indy/services/IndyHolderService.ts index e92b20896f..a53f2b7049 100644 --- a/packages/core/src/modules/indy/services/IndyHolderService.ts +++ b/packages/core/src/modules/indy/services/IndyHolderService.ts @@ -1,5 +1,5 @@ import type { AgentContext } from '../../../agent' -import type { RequestedCredentials } from '../../proofs' +import type { RequestedCredentials } from '../../proofs/formats/indy/models/RequestedCredentials' import type * as Indy from 'indy-sdk' import { AgentDependencies } from '../../../agent/AgentDependencies' diff --git a/packages/core/src/modules/indy/services/IndyRevocationService.ts b/packages/core/src/modules/indy/services/IndyRevocationService.ts index fa84997876..52c666d3ca 100644 --- a/packages/core/src/modules/indy/services/IndyRevocationService.ts +++ b/packages/core/src/modules/indy/services/IndyRevocationService.ts @@ -1,6 +1,6 @@ import type { AgentContext } from '../../../agent' import type { IndyRevocationInterval } from '../../credentials' -import type { RequestedCredentials } from '../../proofs' +import type { RequestedCredentials } from '../../proofs/formats/indy/models/RequestedCredentials' import type { default as Indy } from 'indy-sdk' import { AgentDependencies } from '../../../agent/AgentDependencies' diff --git a/packages/core/src/modules/proofs/ProofEvents.ts b/packages/core/src/modules/proofs/ProofEvents.ts index b3612dc9c6..ba1c7b047b 100644 --- a/packages/core/src/modules/proofs/ProofEvents.ts +++ b/packages/core/src/modules/proofs/ProofEvents.ts @@ -1,5 +1,5 @@ import type { BaseEvent } from '../../agent/Events' -import type { ProofState } from './ProofState' +import type { ProofState } from './models/ProofState' import type { ProofRecord } from './repository' export enum ProofEventTypes { diff --git a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts index e101bd003d..24bf56dda5 100644 --- a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts +++ b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts @@ -1,10 +1,10 @@ -import type { AgentContext } from '../../agent/context' +import type { AgentContext } from '../../agent/context/AgentContext' import type { ProofRecord } from './repository' import { injectable } from '../../plugins' -import { AutoAcceptProof } from './ProofAutoAcceptType' -import { ProofsModuleConfig } from './ProofsModuleConfig' +import { ProofService } from './ProofService' +import { AutoAcceptProof } from './models/ProofAutoAcceptType' /** * This class handles all the automation with all the messages in the present proof protocol @@ -12,11 +12,12 @@ import { ProofsModuleConfig } from './ProofsModuleConfig' */ @injectable() export class ProofResponseCoordinator { - private proofsModuleConfig: ProofsModuleConfig + private proofService: ProofService - public constructor(proofsModuleConfig: ProofsModuleConfig) { - this.proofsModuleConfig = proofsModuleConfig + public constructor(proofService: ProofService) { + this.proofService = proofService } + /** * Returns the proof auto accept config based on priority: * - The record config takes first priority @@ -36,12 +37,17 @@ export class ProofResponseCoordinator { public shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, - this.proofsModuleConfig.autoAcceptProofs + agentContext.config.autoAcceptProofs ) if (autoAccept === AutoAcceptProof.Always) { return true } + + if (autoAccept === AutoAcceptProof.ContentApproved) { + return this.proofService.shouldAutoRespondToProposal(agentContext, proofRecord) + } + return false } @@ -51,16 +57,17 @@ export class ProofResponseCoordinator { public shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, - this.proofsModuleConfig.autoAcceptProofs + agentContext.config.autoAcceptProofs ) - if ( - autoAccept === AutoAcceptProof.Always || - (autoAccept === AutoAcceptProof.ContentApproved && proofRecord.proposalMessage) - ) { + if (autoAccept === AutoAcceptProof.Always) { return true } + if (autoAccept === AutoAcceptProof.ContentApproved) { + return this.proofService.shouldAutoRespondToRequest(agentContext, proofRecord) + } + return false } @@ -70,16 +77,17 @@ export class ProofResponseCoordinator { public shouldAutoRespondToPresentation(agentContext: AgentContext, proofRecord: ProofRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, - this.proofsModuleConfig.autoAcceptProofs + agentContext.config.autoAcceptProofs ) - if ( - autoAccept === AutoAcceptProof.Always || - (autoAccept === AutoAcceptProof.ContentApproved && proofRecord.requestMessage) - ) { + if (autoAccept === AutoAcceptProof.Always) { return true } + if (autoAccept === AutoAcceptProof.ContentApproved) { + return this.proofService.shouldAutoRespondToPresentation(agentContext, proofRecord) + } + return false } } diff --git a/packages/core/src/modules/proofs/ProofService.ts b/packages/core/src/modules/proofs/ProofService.ts new file mode 100644 index 0000000000..e52376fd92 --- /dev/null +++ b/packages/core/src/modules/proofs/ProofService.ts @@ -0,0 +1,232 @@ +import type { AgentConfig } from '../../agent/AgentConfig' +import type { AgentMessage } from '../../agent/AgentMessage' +import type { Dispatcher } from '../../agent/Dispatcher' +import type { EventEmitter } from '../../agent/EventEmitter' +import type { AgentContext } from '../../agent/context/AgentContext' +import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' +import type { Logger } from '../../logger' +import type { DidCommMessageRepository, DidCommMessageRole } from '../../storage' +import type { Wallet } from '../../wallet/Wallet' +import type { ConnectionService } from '../connections/services' +import type { MediationRecipientService, RoutingService } from '../routing' +import type { ProofStateChangedEvent } from './ProofEvents' +import type { ProofResponseCoordinator } from './ProofResponseCoordinator' +import type { ProofFormat } from './formats/ProofFormat' +import type { CreateProblemReportOptions } from './formats/models/ProofFormatServiceOptions' +import type { + CreateAckOptions, + CreatePresentationOptions, + CreateProofRequestFromProposalOptions, + CreateProposalAsResponseOptions, + CreateProposalOptions, + CreateRequestAsResponseOptions, + CreateRequestOptions, + FormatRequestedCredentialReturn, + FormatRetrievedCredentialOptions, + GetRequestedCredentialsForProofRequestOptions, + ProofRequestFromProposalOptions, +} from './models/ProofServiceOptions' +import type { ProofState } from './models/ProofState' +import type { ProofRecord, ProofRepository } from './repository' + +import { JsonTransformer } from '../../utils/JsonTransformer' + +import { ProofEventTypes } from './ProofEvents' + +export abstract class ProofService { + protected proofRepository: ProofRepository + protected didCommMessageRepository: DidCommMessageRepository + protected eventEmitter: EventEmitter + protected connectionService: ConnectionService + protected wallet: Wallet + protected logger: Logger + + public constructor( + agentConfig: AgentConfig, + proofRepository: ProofRepository, + connectionService: ConnectionService, + didCommMessageRepository: DidCommMessageRepository, + wallet: Wallet, + eventEmitter: EventEmitter + ) { + this.proofRepository = proofRepository + this.connectionService = connectionService + this.didCommMessageRepository = didCommMessageRepository + this.eventEmitter = eventEmitter + this.wallet = wallet + this.logger = agentConfig.logger + } + abstract readonly version: string + + public async generateProofRequestNonce() { + return await this.wallet.generateNonce() + } + + public emitStateChangedEvent(agentContext: AgentContext, proofRecord: ProofRecord, previousState: ProofState | null) { + const clonedProof = JsonTransformer.clone(proofRecord) + + this.eventEmitter.emit(agentContext, { + type: ProofEventTypes.ProofStateChanged, + payload: { + proofRecord: clonedProof, + previousState: previousState, + }, + }) + } + + /** + * Update the record to a new state and emit an state changed event. Also updates the record + * in storage. + * + * @param proofRecord The proof record to update the state for + * @param newState The state to update to + * + */ + public async updateState(agentContext: AgentContext, proofRecord: ProofRecord, newState: ProofState) { + const previousState = proofRecord.state + proofRecord.state = newState + await this.proofRepository.update(agentContext, proofRecord) + + this.emitStateChangedEvent(agentContext, proofRecord, previousState) + } + + public update(agentContext: AgentContext, proofRecord: ProofRecord) { + return this.proofRepository.update(agentContext, proofRecord) + } + + /** + * 1. Assert (connection ready, record state) + * 2. Create proposal message + * 3. loop through all formats from ProposeProofOptions and call format service + * 4. Create and store proof record + * 5. Store proposal message + * 6. Return proposal message + proof record + */ + abstract createProposal( + agentContext: AgentContext, + options: CreateProposalOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + + /** + * Create a proposal message in response to a received proof request message + * + * 1. assert record state + * 2. Create proposal message + * 3. loop through all formats from ProposeProofOptions and call format service + * 4. Update proof record + * 5. Create or update proposal message + * 6. Return proposal message + proof record + */ + abstract createProposalAsResponse( + agentContext: AgentContext, + options: CreateProposalAsResponseOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + + /** + * Process a received proposal message (does not accept yet) + * + * 1. Find proof record by thread and connection id + * + * Two flows possible: + * - Proof record already exist + * 2. Assert state + * 3. Save or update proposal message in storage (didcomm message record) + * 4. Loop through all format services to process proposal message + * 5. Update & return record + * + * - Proof record does not exist yet + * 2. Create record + * 3. Save proposal message + * 4. Loop through all format services to process proposal message + * 5. Save & return record + */ + abstract processProposal(messageContext: InboundMessageContext): Promise + + abstract createRequest( + agentContext: AgentContext, + options: CreateRequestOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + + abstract createRequestAsResponse( + agentContext: AgentContext, + options: CreateRequestAsResponseOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + + abstract processRequest(messageContext: InboundMessageContext): Promise + + abstract createPresentation( + agentContext: AgentContext, + options: CreatePresentationOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + + abstract processPresentation(messageContext: InboundMessageContext): Promise + + abstract createAck( + agentContext: AgentContext, + options: CreateAckOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + + abstract processAck(messageContext: InboundMessageContext): Promise + + abstract createProblemReport( + agentContext: AgentContext, + options: CreateProblemReportOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + + abstract processProblemReport(messageContext: InboundMessageContext): Promise + + public abstract shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofRecord): Promise + + public abstract shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofRecord): Promise + + public abstract shouldAutoRespondToPresentation( + agentContext: AgentContext, + proofRecord: ProofRecord + ): Promise + + public abstract registerHandlers( + dispatcher: Dispatcher, + agentConfig: AgentConfig, + proofResponseCoordinator: ProofResponseCoordinator, + mediationRecipientService: MediationRecipientService, + routingService: RoutingService + ): void + + public abstract findRequestMessage(agentContext: AgentContext, proofRecordId: string): Promise + + public abstract findPresentationMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise + + public abstract findProposalMessage(agentContext: AgentContext, proofRecordId: string): Promise + + public async saveOrUpdatePresentationMessage( + agentContext: AgentContext, + options: { + proofRecord: ProofRecord + message: AgentMessage + role: DidCommMessageRole + } + ): Promise { + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + associatedRecordId: options.proofRecord.id, + agentMessage: options.message, + role: options.role, + }) + } + + public abstract getRequestedCredentialsForProofRequest( + agentContext: AgentContext, + options: GetRequestedCredentialsForProofRequestOptions + ): Promise> + + public abstract autoSelectCredentialsForProofRequest( + options: FormatRetrievedCredentialOptions + ): Promise> + + public abstract createProofRequestFromProposal( + agentContext: AgentContext, + options: CreateProofRequestFromProposalOptions + ): Promise> +} diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index b1a7075957..0bc08886b6 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -1,91 +1,169 @@ -import type { AutoAcceptProof } from './ProofAutoAcceptType' -import type { PresentationPreview, RequestPresentationMessage } from './messages' -import type { RequestedCredentials, RetrievedCredentials } from './models' -import type { ProofRequestOptions } from './models/ProofRequest' +import type { AgentMessage } from '../../agent/AgentMessage' +import type { ProofService } from './ProofService' +import type { + AcceptPresentationOptions, + AcceptProposalOptions, + OutOfBandRequestOptions, + ProposeProofOptions, + RequestProofOptions, + ServiceMap, +} from './ProofsApiOptions' +import type { ProofFormat } from './formats/ProofFormat' +import type { IndyProofFormat } from './formats/indy/IndyProofFormat' +import type { AutoSelectCredentialsForProofRequestOptions } from './models/ModuleOptions' +import type { + CreateOutOfBandRequestOptions, + CreatePresentationOptions, + CreateProposalOptions, + CreateRequestOptions, + CreateRequestAsResponseOptions, + CreateProofRequestFromProposalOptions, + FormatRequestedCredentialReturn, + FormatRetrievedCredentialOptions, +} from './models/ProofServiceOptions' import type { ProofRecord } from './repository/ProofRecord' -import { AgentContext } from '../../agent' +import { inject, injectable } from 'tsyringe' + +import { AgentConfig } from '../../agent/AgentConfig' import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' +import { AgentContext } from '../../agent/context/AgentContext' import { createOutboundMessage } from '../../agent/helpers' import { InjectionSymbols } from '../../constants' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' import { Logger } from '../../logger' -import { inject, injectable } from '../../plugins' +import { DidCommMessageRole } from '../../storage/didcomm/DidCommMessageRole' import { ConnectionService } from '../connections/services/ConnectionService' +import { MediationRecipientService } from '../routing/services/MediationRecipientService' import { RoutingService } from '../routing/services/RoutingService' import { ProofResponseCoordinator } from './ProofResponseCoordinator' -import { PresentationProblemReportReason } from './errors' -import { - PresentationAckHandler, - PresentationHandler, - PresentationProblemReportHandler, - ProposePresentationHandler, - RequestPresentationHandler, -} from './handlers' -import { PresentationProblemReportMessage } from './messages/PresentationProblemReportMessage' -import { ProofRequest } from './models/ProofRequest' -import { ProofService } from './services' +import { ProofState } from './models/ProofState' +import { V1ProofService } from './protocol/v1/V1ProofService' +import { V2ProofService } from './protocol/v2/V2ProofService' +import { ProofRepository } from './repository/ProofRepository' + +export interface ProofsApi[]> { + // Proposal methods + proposeProof(options: ProposeProofOptions): Promise + acceptProposal(options: AcceptProposalOptions): Promise + + // Request methods + requestProof(options: RequestProofOptions): Promise + acceptRequest(options: AcceptPresentationOptions): Promise + declineRequest(proofRecordId: string): Promise + + // out of band + createOutOfBandRequest(options: OutOfBandRequestOptions): Promise<{ + message: AgentMessage + proofRecord: ProofRecord + }> + + // Present + acceptPresentation(proofRecordId: string): Promise + + // Auto Select + autoSelectCredentialsForProofRequest( + options: AutoSelectCredentialsForProofRequestOptions + ): Promise> + + sendProblemReport(proofRecordId: string, message: string): Promise + + // Record Methods + getAll(): Promise + getById(proofRecordId: string): Promise + deleteById(proofId: string): Promise + findById(proofRecordId: string): Promise + update(proofRecord: ProofRecord): Promise +} @injectable() -export class ProofsApi { - private proofService: ProofService +export class ProofsApi< + PFs extends ProofFormat[] = [IndyProofFormat], + PSs extends ProofService[] = [V1ProofService, V2ProofService] +> implements ProofsApi +{ private connectionService: ConnectionService private messageSender: MessageSender private routingService: RoutingService + private proofRepository: ProofRepository private agentContext: AgentContext - private proofResponseCoordinator: ProofResponseCoordinator + private agentConfig: AgentConfig private logger: Logger + private serviceMap: ServiceMap public constructor( dispatcher: Dispatcher, - proofService: ProofService, + mediationRecipientService: MediationRecipientService, + messageSender: MessageSender, connectionService: ConnectionService, - routingService: RoutingService, agentContext: AgentContext, - messageSender: MessageSender, - proofResponseCoordinator: ProofResponseCoordinator, - @inject(InjectionSymbols.Logger) logger: Logger + agentConfig: AgentConfig, + routingService: RoutingService, + @inject(InjectionSymbols.Logger) logger: Logger, + proofRepository: ProofRepository, + v1Service: V1ProofService, + v2Service: V2ProofService ) { - this.proofService = proofService - this.connectionService = connectionService this.messageSender = messageSender - this.routingService = routingService + this.connectionService = connectionService + this.proofRepository = proofRepository this.agentContext = agentContext - this.proofResponseCoordinator = proofResponseCoordinator + this.agentConfig = agentConfig + this.routingService = routingService this.logger = logger - this.registerHandlers(dispatcher) + // Dynamically build service map. This will be extracted once services are registered dynamically + this.serviceMap = [v1Service, v2Service].reduce( + (serviceMap, service) => ({ + ...serviceMap, + [service.version]: service, + }), + {} + ) as ServiceMap + + this.logger.debug(`Initializing Proofs Module for agent ${this.agentContext.config.label}`) + + this.registerHandlers(dispatcher, mediationRecipientService) + } + + public getService(protocolVersion: PVT): ProofService { + if (!this.serviceMap[protocolVersion]) { + throw new AriesFrameworkError(`No proof service registered for protocol version ${protocolVersion}`) + } + + return this.serviceMap[protocolVersion] } /** * Initiate a new presentation exchange as prover by sending a presentation proposal message * to the connection with the specified connection id. * - * @param connectionId The connection to send the proof proposal to - * @param presentationProposal The presentation proposal to include in the message - * @param config Additional configuration to use for the proposal + * @param options multiple properties like protocol version, connection id, proof format (indy/ presentation exchange) + * to include in the message * @returns Proof record associated with the sent proposal message - * */ - public async proposeProof( - connectionId: string, - presentationProposal: PresentationPreview, - config?: { - comment?: string - autoAcceptProof?: AutoAcceptProof - parentThreadId?: string - } - ): Promise { + public async proposeProof(options: ProposeProofOptions): Promise { + const service = this.getService(options.protocolVersion) + + const { connectionId } = options + const connection = await this.connectionService.getById(this.agentContext, connectionId) - const { message, proofRecord } = await this.proofService.createProposal( - this.agentContext, - connection, - presentationProposal, - config - ) + // Assert + connection.assertReady() + + const proposalOptions: CreateProposalOptions = { + connectionRecord: connection, + proofFormats: options.proofFormats, + autoAcceptProof: options.autoAcceptProof, + goalCode: options.goalCode, + comment: options.comment, + parentThreadId: options.parentThreadId, + } + + const { message, proofRecord } = await service.createProposal(this.agentContext, proposalOptions) const outbound = createOutboundMessage(connection, message) await this.messageSender.sendMessage(this.agentContext, outbound) @@ -97,23 +175,14 @@ export class ProofsApi { * Accept a presentation proposal as verifier (by sending a presentation request message) to the connection * associated with the proof record. * - * @param proofRecordId The id of the proof record for which to accept the proposal - * @param config Additional configuration to use for the request + * @param options multiple properties like proof record id, additional configuration for creating the request * @returns Proof record associated with the presentation request - * */ - public async acceptProposal( - proofRecordId: string, - config?: { - request?: { - name?: string - version?: string - nonce?: string - } - comment?: string - } - ): Promise { - const proofRecord = await this.proofService.getById(this.agentContext, proofRecordId) + public async acceptProposal(options: AcceptProposalOptions): Promise { + const { proofRecordId } = options + const proofRecord = await this.getById(proofRecordId) + + const service = this.getService(proofRecord.protocolVersion) if (!proofRecord.connectionId) { throw new AriesFrameworkError( @@ -123,24 +192,27 @@ export class ProofsApi { const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) - const presentationProposal = proofRecord.proposalMessage?.presentationProposal - if (!presentationProposal) { - throw new AriesFrameworkError(`Proof record with id ${proofRecordId} is missing required presentation proposal`) + // Assert + connection.assertReady() + + const proofRequestFromProposalOptions: CreateProofRequestFromProposalOptions = { + proofRecord, } - const proofRequest = await this.proofService.createProofRequestFromProposal( + const proofRequest = await service.createProofRequestFromProposal( this.agentContext, - presentationProposal, - { - name: config?.request?.name ?? 'proof-request', - version: config?.request?.version ?? '1.0', - nonce: config?.request?.nonce, - } + proofRequestFromProposalOptions ) - const { message } = await this.proofService.createRequestAsResponse(this.agentContext, proofRecord, proofRequest, { - comment: config?.comment, - }) + const requestOptions: CreateRequestAsResponseOptions = { + proofRecord: proofRecord, + proofFormats: proofRequest.proofFormats, + goalCode: options.goalCode, + willConfirm: options.willConfirm ?? true, + comment: options.comment, + } + + const { message } = await service.createRequestAsResponse(this.agentContext, requestOptions) const outboundMessage = createOutboundMessage(connection, message) await this.messageSender.sendMessage(this.agentContext, outboundMessage) @@ -152,34 +224,25 @@ export class ProofsApi { * Initiate a new presentation exchange as verifier by sending a presentation request message * to the connection with the specified connection id * - * @param connectionId The connection to send the proof request to - * @param proofRequestOptions Options to build the proof request + * @param options multiple properties like connection id, protocol version, proof Formats to build the proof request * @returns Proof record associated with the sent request message - * */ - public async requestProof( - connectionId: string, - proofRequestOptions: CreateProofRequestOptions, - config?: ProofRequestConfig - ): Promise { - const connection = await this.connectionService.getById(this.agentContext, connectionId) + public async requestProof(options: RequestProofOptions): Promise { + const service = this.getService(options.protocolVersion) - const nonce = proofRequestOptions.nonce ?? (await this.proofService.generateProofRequestNonce(this.agentContext)) + const connection = await this.connectionService.getById(this.agentContext, options.connectionId) - const proofRequest = new ProofRequest({ - name: proofRequestOptions.name ?? 'proof-request', - version: proofRequestOptions.name ?? '1.0', - nonce, - requestedAttributes: proofRequestOptions.requestedAttributes, - requestedPredicates: proofRequestOptions.requestedPredicates, - }) + // Assert + connection.assertReady() - const { message, proofRecord } = await this.proofService.createRequest( - this.agentContext, - proofRequest, - connection, - config - ) + const createProofRequest: CreateRequestOptions = { + connectionRecord: connection, + proofFormats: options.proofFormats, + autoAcceptProof: options.autoAcceptProof, + parentThreadId: options.parentThreadId, + comment: options.comment, + } + const { message, proofRecord } = await service.createRequest(this.agentContext, createProofRequest) const outboundMessage = createOutboundMessage(connection, message) await this.messageSender.sendMessage(this.agentContext, outboundMessage) @@ -187,108 +250,67 @@ export class ProofsApi { return proofRecord } - /** - * Initiate a new presentation exchange as verifier by creating a presentation request - * not bound to any connection. The request must be delivered out-of-band to the holder - * - * @param proofRequestOptions Options to build the proof request - * @returns The proof record and proof request message - * - */ - public async createOutOfBandRequest( - proofRequestOptions: CreateProofRequestOptions, - config?: ProofRequestConfig - ): Promise<{ - requestMessage: RequestPresentationMessage - proofRecord: ProofRecord - }> { - const nonce = proofRequestOptions.nonce ?? (await this.proofService.generateProofRequestNonce(this.agentContext)) - - const proofRequest = new ProofRequest({ - name: proofRequestOptions.name ?? 'proof-request', - version: proofRequestOptions.name ?? '1.0', - nonce, - requestedAttributes: proofRequestOptions.requestedAttributes, - requestedPredicates: proofRequestOptions.requestedPredicates, - }) - - const { message, proofRecord } = await this.proofService.createRequest( - this.agentContext, - proofRequest, - undefined, - config - ) - - // Create and set ~service decorator - const routing = await this.routingService.getRouting(this.agentContext) - message.service = new ServiceDecorator({ - serviceEndpoint: routing.endpoints[0], - recipientKeys: [routing.recipientKey.publicKeyBase58], - routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), - }) - - // Save ~service decorator to record (to remember our verkey) - proofRecord.requestMessage = message - await this.proofService.update(this.agentContext, proofRecord) - - return { proofRecord, requestMessage: message } - } - /** * Accept a presentation request as prover (by sending a presentation message) to the connection * associated with the proof record. * - * @param proofRecordId The id of the proof record for which to accept the request - * @param requestedCredentials The requested credentials object specifying which credentials to use for the proof - * @param config Additional configuration to use for the presentation + * @param options multiple properties like proof record id, proof formats to accept requested credentials object + * specifying which credentials to use for the proof * @returns Proof record associated with the sent presentation message - * */ - public async acceptRequest( - proofRecordId: string, - requestedCredentials: RequestedCredentials, - config?: { - comment?: string + public async acceptRequest(options: AcceptPresentationOptions): Promise { + const { proofRecordId, proofFormats, comment } = options + + const record = await this.getById(proofRecordId) + + const service = this.getService(record.protocolVersion) + + const presentationOptions: CreatePresentationOptions = { + proofFormats, + proofRecord: record, + comment, } - ): Promise { - const record = await this.proofService.getById(this.agentContext, proofRecordId) - const { message, proofRecord } = await this.proofService.createPresentation( - this.agentContext, - record, - requestedCredentials, - config - ) + const { message, proofRecord } = await service.createPresentation(this.agentContext, presentationOptions) + + const requestMessage = await service.findRequestMessage(this.agentContext, proofRecord.id) // Use connection if present if (proofRecord.connectionId) { const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + // Assert + connection.assertReady() + const outboundMessage = createOutboundMessage(connection, message) await this.messageSender.sendMessage(this.agentContext, outboundMessage) return proofRecord } + // Use ~service decorator otherwise - else if (proofRecord.requestMessage?.service) { + else if (requestMessage?.service) { // Create ~service decorator const routing = await this.routingService.getRouting(this.agentContext) - const ourService = new ServiceDecorator({ + message.service = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), }) - const recipientService = proofRecord.requestMessage.service + const recipientService = requestMessage.service // Set and save ~service decorator to record (to remember our verkey) - message.service = ourService - proofRecord.presentationMessage = message - await this.proofService.update(this.agentContext, proofRecord) + + await service.saveOrUpdatePresentationMessage(this.agentContext, { + proofRecord: proofRecord, + message: message, + role: DidCommMessageRole.Sender, + }) await this.messageSender.sendMessageToService(this.agentContext, { message, service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], + senderKey: message.service.resolvedDidCommService.recipientKeys[0], returnRoute: true, }) @@ -302,14 +324,49 @@ export class ProofsApi { } } - /** - * Declines a proof request as holder - * @param proofRecordId the id of the proof request to be declined - * @returns proof record that was declined - */ - public async declineRequest(proofRecordId: string) { - const proofRecord = await this.proofService.getById(this.agentContext, proofRecordId) - await this.proofService.declineRequest(this.agentContext, proofRecord) + public async createOutOfBandRequest(options: OutOfBandRequestOptions): Promise<{ + message: AgentMessage + proofRecord: ProofRecord + }> { + const service = this.getService(options.protocolVersion) + + const createProofRequest: CreateOutOfBandRequestOptions = { + proofFormats: options.proofFormats, + autoAcceptProof: options.autoAcceptProof, + comment: options.comment, + } + + const { message, proofRecord } = await service.createRequest(this.agentContext, createProofRequest) + + // Create and set ~service decorator + + const routing = await this.routingService.getRouting(this.agentContext) + message.service = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.recipientKey.publicKeyBase58], + routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), + }) + // Save ~service decorator to record (to remember our verkey) + + await service.saveOrUpdatePresentationMessage(this.agentContext, { + message, + proofRecord: proofRecord, + role: DidCommMessageRole.Sender, + }) + + await service.update(this.agentContext, proofRecord) + + return { proofRecord, message } + } + + public async declineRequest(proofRecordId: string): Promise { + const proofRecord = await this.getById(proofRecordId) + const service = this.getService(proofRecord.protocolVersion) + + proofRecord.assertState(ProofState.RequestReceived) + + await service.updateState(this.agentContext, proofRecord, ProofState.Declined) + return proofRecord } @@ -322,19 +379,31 @@ export class ProofsApi { * */ public async acceptPresentation(proofRecordId: string): Promise { - const record = await this.proofService.getById(this.agentContext, proofRecordId) - const { message, proofRecord } = await this.proofService.createAck(this.agentContext, record) + const record = await this.getById(proofRecordId) + const service = this.getService(record.protocolVersion) + + const { message, proofRecord } = await service.createAck(this.agentContext, { + proofRecord: record, + }) + + const requestMessage = await service.findRequestMessage(this.agentContext, record.id) + + const presentationMessage = await service.findPresentationMessage(this.agentContext, record.id) // Use connection if present if (proofRecord.connectionId) { const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + + // Assert + connection.assertReady() + const outboundMessage = createOutboundMessage(connection, message) await this.messageSender.sendMessage(this.agentContext, outboundMessage) } // Use ~service decorator otherwise - else if (proofRecord.requestMessage?.service && proofRecord.presentationMessage?.service) { - const recipientService = proofRecord.presentationMessage?.service - const ourService = proofRecord.requestMessage.service + else if (requestMessage?.service && presentationMessage?.service) { + const recipientService = presentationMessage?.service + const ourService = requestMessage.service await this.messageSender.sendMessageToService(this.agentContext, { message, @@ -343,7 +412,6 @@ export class ProofsApi { returnRoute: true, }) } - // Cannot send message without credentialId or ~service decorator else { throw new AriesFrameworkError( @@ -351,7 +419,7 @@ export class ProofsApi { ) } - return proofRecord + return record } /** @@ -359,85 +427,59 @@ export class ProofsApi { * use credentials in the wallet to build indy requested credentials object for input to proof creation. * If restrictions allow, self attested attributes will be used. * - * - * @param proofRecordId the id of the proof request to get the matching credentials for - * @param config optional configuration for credential selection process. Use `filterByPresentationPreview` (default `true`) to only include - * credentials that match the presentation preview from the presentation proposal (if available). - - * @returns RetrievedCredentials object + * @param options multiple properties like proof record id and optional configuration + * @returns RequestedCredentials */ - public async getRequestedCredentialsForProofRequest( - proofRecordId: string, - config?: GetRequestedCredentialsConfig - ): Promise { - const proofRecord = await this.proofService.getById(this.agentContext, proofRecordId) - - const indyProofRequest = proofRecord.requestMessage?.indyProofRequest - const presentationPreview = config?.filterByPresentationPreview - ? proofRecord.proposalMessage?.presentationProposal - : undefined - - if (!indyProofRequest) { - throw new AriesFrameworkError( - 'Unable to get requested credentials for proof request. No proof request message was found or the proof request message does not contain an indy proof request.' - ) - } + public async autoSelectCredentialsForProofRequest( + options: AutoSelectCredentialsForProofRequestOptions + ): Promise> { + const proofRecord = await this.getById(options.proofRecordId) - return this.proofService.getRequestedCredentialsForProofRequest(this.agentContext, indyProofRequest, { - presentationProposal: presentationPreview, - filterByNonRevocationRequirements: config?.filterByNonRevocationRequirements ?? true, - }) - } + const service = this.getService(proofRecord.protocolVersion) - /** - * Takes a RetrievedCredentials object and auto selects credentials in a RequestedCredentials object - * - * Use the return value of this method as input to {@link ProofService.createPresentation} to - * automatically accept a received presentation request. - * - * @param retrievedCredentials The retrieved credentials object to get credentials from - * - * @returns RequestedCredentials - */ - public autoSelectCredentialsForProofRequest(retrievedCredentials: RetrievedCredentials): RequestedCredentials { - return this.proofService.autoSelectCredentialsForProofRequest(retrievedCredentials) + const retrievedCredentials: FormatRetrievedCredentialOptions = + await service.getRequestedCredentialsForProofRequest(this.agentContext, { + proofRecord: proofRecord, + config: options.config, + }) + return await service.autoSelectCredentialsForProofRequest(retrievedCredentials) } /** * Send problem report message for a proof record + * * @param proofRecordId The id of the proof record for which to send problem report * @param message message to send * @returns proof record associated with the proof problem report message */ - public async sendProblemReport(proofRecordId: string, message: string) { - const record = await this.proofService.getById(this.agentContext, proofRecordId) + public async sendProblemReport(proofRecordId: string, message: string): Promise { + const record = await this.getById(proofRecordId) + const service = this.getService(record.protocolVersion) if (!record.connectionId) { throw new AriesFrameworkError(`No connectionId found for proof record '${record.id}'.`) } const connection = await this.connectionService.getById(this.agentContext, record.connectionId) - const presentationProblemReportMessage = new PresentationProblemReportMessage({ - description: { - en: message, - code: PresentationProblemReportReason.Abandoned, - }, - }) - presentationProblemReportMessage.setThread({ - threadId: record.threadId, - parentThreadId: record.parentThreadId, + + // Assert + connection.assertReady() + + const { message: problemReport } = await service.createProblemReport(this.agentContext, { + proofRecord: record, + description: message, }) - const outboundMessage = createOutboundMessage(connection, presentationProblemReportMessage) + + const outboundMessage = createOutboundMessage(connection, problemReport) await this.messageSender.sendMessage(this.agentContext, outboundMessage) return record } - /** * Retrieve all proof records * * @returns List containing all proof records */ - public getAll(): Promise { - return this.proofService.getAll(this.agentContext) + public async getAll(): Promise { + return this.proofRepository.getAll(this.agentContext) } /** @@ -445,12 +487,11 @@ export class ProofsApi { * * @param proofRecordId The proof record id * @throws {RecordNotFoundError} If no record is found - * @throws {RecordDuplicateError} If multiple records are found * @return The proof record * */ public async getById(proofRecordId: string): Promise { - return this.proofService.getById(this.agentContext, proofRecordId) + return await this.proofRepository.getById(this.agentContext, proofRecordId) } /** @@ -461,7 +502,7 @@ export class ProofsApi { * */ public async findById(proofRecordId: string): Promise { - return this.proofService.findById(this.agentContext, proofRecordId) + return await this.proofRepository.findById(this.agentContext, proofRecordId) } /** @@ -470,7 +511,8 @@ export class ProofsApi { * @param proofId the proof record id */ public async deleteById(proofId: string) { - return this.proofService.deleteById(this.agentContext, proofId) + const proofRecord = await this.getById(proofId) + return await this.proofRepository.delete(this.agentContext, proofRecord) } /** @@ -483,7 +525,7 @@ export class ProofsApi { * @returns The proof record */ public async getByThreadAndConnectionId(threadId: string, connectionId?: string): Promise { - return this.proofService.getByThreadAndConnectionId(this.agentContext, threadId, connectionId) + return this.proofRepository.getByThreadAndConnectionId(this.agentContext, threadId, connectionId) } /** @@ -494,48 +536,28 @@ export class ProofsApi { * @returns List containing all proof records matching the given query */ public async getByParentThreadAndConnectionId(parentThreadId: string, connectionId?: string): Promise { - return this.proofService.getByParentThreadAndConnectionId(this.agentContext, parentThreadId, connectionId) + return this.proofRepository.getByParentThreadAndConnectionId(this.agentContext, parentThreadId, connectionId) } - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler( - new ProposePresentationHandler(this.proofService, this.proofResponseCoordinator, this.logger) - ) - dispatcher.registerHandler( - new RequestPresentationHandler(this.proofService, this.proofResponseCoordinator, this.routingService, this.logger) - ) - dispatcher.registerHandler(new PresentationHandler(this.proofService, this.proofResponseCoordinator, this.logger)) - dispatcher.registerHandler(new PresentationAckHandler(this.proofService)) - dispatcher.registerHandler(new PresentationProblemReportHandler(this.proofService)) - } -} - -export type CreateProofRequestOptions = Partial< - Pick -> - -export interface ProofRequestConfig { - comment?: string - autoAcceptProof?: AutoAcceptProof - parentThreadId?: string -} - -export interface GetRequestedCredentialsConfig { /** - * Whether to filter the retrieved credentials using the presentation preview. - * This configuration will only have effect if a presentation proposal message is available - * containing a presentation preview. + * Update a proof record by * - * @default false + * @param proofRecord the proof record */ - filterByPresentationPreview?: boolean + public async update(proofRecord: ProofRecord) { + await this.proofRepository.update(this.agentContext, proofRecord) + } - /** - * Whether to filter the retrieved credentials using the non-revocation request in the proof request. - * This configuration will only have effect if the proof request requires proof on non-revocation of any kind. - * Default to true - * - * @default true - */ - filterByNonRevocationRequirements?: boolean + private registerHandlers(dispatcher: Dispatcher, mediationRecipientService: MediationRecipientService) { + for (const service of Object.values(this.serviceMap)) { + const proofService = service as ProofService + proofService.registerHandlers( + dispatcher, + this.agentConfig, + new ProofResponseCoordinator(proofService), + mediationRecipientService, + this.routingService + ) + } + } } diff --git a/packages/core/src/modules/proofs/ProofsApiOptions.ts b/packages/core/src/modules/proofs/ProofsApiOptions.ts new file mode 100644 index 0000000000..798a56c30c --- /dev/null +++ b/packages/core/src/modules/proofs/ProofsApiOptions.ts @@ -0,0 +1,74 @@ +import type { ProofService } from './ProofService' +import type { ProofFormat, ProofFormatPayload } from './formats/ProofFormat' +import type { AutoAcceptProof } from './models' +import type { ProofConfig } from './models/ModuleOptions' + +/** + * Get the supported protocol versions based on the provided credential services. + */ +export type ProtocolVersionType = PSs[number]['version'] + +/** + * Get the service map for usage in the credentials module. Will return a type mapping of protocol version to service. + * + * @example + * ``` + * type CredentialServiceMap = ServiceMap<[IndyCredentialFormat], [V1CredentialService]> + * + * // equal to + * type CredentialServiceMap = { + * v1: V1CredentialService + * } + * ``` + */ +export type ServiceMap[]> = { + [PS in PSs[number] as PS['version']]: ProofService +} + +export interface ProposeProofOptions< + PFs extends ProofFormat[] = ProofFormat[], + PSs extends ProofService[] = ProofService[] +> { + connectionId: string + protocolVersion: ProtocolVersionType + proofFormats: ProofFormatPayload + comment?: string + goalCode?: string + autoAcceptProof?: AutoAcceptProof + parentThreadId?: string +} +export interface AcceptPresentationOptions { + proofRecordId: string + comment?: string + proofFormats: ProofFormatPayload +} + +export interface AcceptProposalOptions { + proofRecordId: string + config?: ProofConfig + goalCode?: string + willConfirm?: boolean + comment?: string +} + +export interface RequestProofOptions< + PFs extends ProofFormat[] = ProofFormat[], + PSs extends ProofService[] = ProofService[] +> { + protocolVersion: ProtocolVersionType + connectionId: string + proofFormats: ProofFormatPayload + comment?: string + autoAcceptProof?: AutoAcceptProof + parentThreadId?: string +} + +export interface OutOfBandRequestOptions< + PFs extends ProofFormat[] = ProofFormat[], + PSs extends ProofService[] = ProofService[] +> { + protocolVersion: ProtocolVersionType + proofFormats: ProofFormatPayload + comment?: string + autoAcceptProof?: AutoAcceptProof +} diff --git a/packages/core/src/modules/proofs/ProofsModule.ts b/packages/core/src/modules/proofs/ProofsModule.ts index 829db07281..329c1c17e4 100644 --- a/packages/core/src/modules/proofs/ProofsModule.ts +++ b/packages/core/src/modules/proofs/ProofsModule.ts @@ -3,8 +3,10 @@ import type { ProofsModuleConfigOptions } from './ProofsModuleConfig' import { ProofsApi } from './ProofsApi' import { ProofsModuleConfig } from './ProofsModuleConfig' +import { IndyProofFormatService } from './formats/indy/IndyProofFormatService' +import { V1ProofService } from './protocol/v1' +import { V2ProofService } from './protocol/v2' import { ProofRepository } from './repository' -import { ProofService } from './services' export class ProofsModule implements Module { public readonly config: ProofsModuleConfig @@ -24,9 +26,13 @@ export class ProofsModule implements Module { dependencyManager.registerInstance(ProofsModuleConfig, this.config) // Services - dependencyManager.registerSingleton(ProofService) + dependencyManager.registerSingleton(V1ProofService) + dependencyManager.registerSingleton(V2ProofService) // Repositories dependencyManager.registerSingleton(ProofRepository) + + // Proof Formats + dependencyManager.registerSingleton(IndyProofFormatService) } } diff --git a/packages/core/src/modules/proofs/ProofsModuleConfig.ts b/packages/core/src/modules/proofs/ProofsModuleConfig.ts index 88fd470c0b..e0b12449e2 100644 --- a/packages/core/src/modules/proofs/ProofsModuleConfig.ts +++ b/packages/core/src/modules/proofs/ProofsModuleConfig.ts @@ -1,4 +1,4 @@ -import { AutoAcceptProof } from './ProofAutoAcceptType' +import { AutoAcceptProof } from './models/ProofAutoAcceptType' /** * ProofsModuleConfigOptions defines the interface for the options of the ProofsModuleConfig class. diff --git a/packages/core/src/modules/proofs/__tests__/ProofRequest.test.ts b/packages/core/src/modules/proofs/__tests__/ProofRequest.test.ts index 4a94da5aa9..fbfab93e5f 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofRequest.test.ts +++ b/packages/core/src/modules/proofs/__tests__/ProofRequest.test.ts @@ -1,15 +1,15 @@ import { ClassValidationError } from '../../../error/ClassValidationError' import { JsonTransformer } from '../../../utils/JsonTransformer' import { MessageValidator } from '../../../utils/MessageValidator' -import { ProofRequest } from '../models' +import { ProofRequest } from '../formats/indy/models/ProofRequest' describe('ProofRequest', () => { - it('should successfully validate if the proof request json contains a valid structure', async () => { + it('should successfully validate if the proof request JSON contains a valid structure', async () => { const proofRequest = JsonTransformer.fromJSON( { name: 'ProofRequest', version: '1.0', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + nonce: '947121108704767252195123', requested_attributes: { First: { name: 'Timo', @@ -43,7 +43,7 @@ describe('ProofRequest', () => { const proofRequest = { name: 'ProofRequest', version: '1.0', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + nonce: '947121108704767252195123', requested_attributes: { First: { names: [], diff --git a/packages/core/src/modules/proofs/__tests__/ProofState.test.ts b/packages/core/src/modules/proofs/__tests__/ProofState.test.ts index 9cabafd183..4b67ed11d0 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofState.test.ts +++ b/packages/core/src/modules/proofs/__tests__/ProofState.test.ts @@ -1,4 +1,4 @@ -import { ProofState } from '../ProofState' +import { ProofState } from '../models/ProofState' describe('ProofState', () => { test('state matches Present Proof 1.0 (RFC 0037) state value', () => { diff --git a/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts index 98ab74e7bc..6bba3fd99e 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts +++ b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts @@ -1,8 +1,10 @@ import { DependencyManager } from '../../../plugins/DependencyManager' import { ProofsApi } from '../ProofsApi' import { ProofsModule } from '../ProofsModule' +import { IndyProofFormatService } from '../formats/indy/IndyProofFormatService' +import { V1ProofService } from '../protocol/v1/V1ProofService' +import { V2ProofService } from '../protocol/v2/V2ProofService' import { ProofRepository } from '../repository' -import { ProofService } from '../services' jest.mock('../../../plugins/DependencyManager') const DependencyManagerMock = DependencyManager as jest.Mock @@ -16,8 +18,10 @@ describe('ProofsModule', () => { expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(ProofsApi) - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(2) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(ProofService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(4) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V1ProofService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V2ProofService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(ProofRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyProofFormatService) }) }) diff --git a/packages/core/src/modules/proofs/__tests__/ProofService.test.ts b/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts similarity index 81% rename from packages/core/src/modules/proofs/__tests__/ProofService.test.ts rename to packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts index b64ded9b1c..b093e049dc 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofService.test.ts +++ b/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts @@ -1,4 +1,5 @@ import type { AgentContext } from '../../../agent' +import type { Wallet } from '../../../wallet/Wallet' import type { CredentialRepository } from '../../credentials/repository' import type { ProofStateChangedEvent } from '../ProofEvents' import type { CustomProofTags } from './../repository/ProofRecord' @@ -9,21 +10,22 @@ import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from import { EventEmitter } from '../../../agent/EventEmitter' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' +import { DidCommMessageRepository } from '../../../storage' import { ConnectionService, DidExchangeState } from '../../connections' import { IndyHolderService } from '../../indy/services/IndyHolderService' import { IndyRevocationService } from '../../indy/services/IndyRevocationService' import { IndyLedgerService } from '../../ledger/services' import { ProofEventTypes } from '../ProofEvents' -import { ProofState } from '../ProofState' import { PresentationProblemReportReason } from '../errors/PresentationProblemReportReason' -import { INDY_PROOF_REQUEST_ATTACHMENT_ID } from '../messages' +import { IndyProofFormatService } from '../formats/indy/IndyProofFormatService' +import { ProofProtocolVersion } from '../models/ProofProtocolVersion' +import { ProofState } from '../models/ProofState' +import { V1ProofService } from '../protocol/v1' +import { INDY_PROOF_REQUEST_ATTACHMENT_ID, V1RequestPresentationMessage } from '../protocol/v1/messages' +import { V1PresentationProblemReportMessage } from '../protocol/v1/messages/V1PresentationProblemReportMessage' import { ProofRecord } from '../repository/ProofRecord' import { ProofRepository } from '../repository/ProofRepository' -import { ProofService } from '../services' -import { IndyVerifierService } from './../../indy/services/IndyVerifierService' -import { PresentationProblemReportMessage } from './../messages/PresentationProblemReportMessage' -import { RequestPresentationMessage } from './../messages/RequestPresentationMessage' import { credDef } from './fixtures' // Mock classes @@ -34,14 +36,16 @@ jest.mock('../../indy/services/IndyIssuerService') jest.mock('../../indy/services/IndyVerifierService') jest.mock('../../indy/services/IndyRevocationService') jest.mock('../../connections/services/ConnectionService') +jest.mock('../../../storage/Repository') // Mock typed object const ProofRepositoryMock = ProofRepository as jest.Mock const IndyLedgerServiceMock = IndyLedgerService as jest.Mock const IndyHolderServiceMock = IndyHolderService as jest.Mock -const IndyVerifierServiceMock = IndyVerifierService as jest.Mock const IndyRevocationServiceMock = IndyRevocationService as jest.Mock const connectionServiceMock = ConnectionService as jest.Mock +const didCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock +const indyProofFormatServiceMock = IndyProofFormatService as jest.Mock const connection = getMockConnection({ id: '123', @@ -61,26 +65,25 @@ const requestAttachment = new Attachment({ // object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. const mockProofRecord = ({ state, - requestMessage, threadId, connectionId, tags, id, }: { state?: ProofState - requestMessage?: RequestPresentationMessage + requestMessage?: V1RequestPresentationMessage tags?: CustomProofTags threadId?: string connectionId?: string id?: string } = {}) => { - const requestPresentationMessage = new RequestPresentationMessage({ + const requestPresentationMessage = new V1RequestPresentationMessage({ comment: 'some comment', requestPresentationAttachments: [requestAttachment], }) const proofRecord = new ProofRecord({ - requestMessage, + protocolVersion: ProofProtocolVersion.V1, id, state: state || ProofState.RequestSent, threadId: threadId ?? requestPresentationMessage.id, @@ -91,50 +94,56 @@ const mockProofRecord = ({ return proofRecord } -describe('ProofService', () => { +describe('V1ProofService', () => { let proofRepository: ProofRepository - let proofService: ProofService + let proofService: V1ProofService let ledgerService: IndyLedgerService - let indyVerifierService: IndyVerifierService + let wallet: Wallet let indyHolderService: IndyHolderService let indyRevocationService: IndyRevocationService let eventEmitter: EventEmitter let credentialRepository: CredentialRepository let connectionService: ConnectionService + let didCommMessageRepository: DidCommMessageRepository + let indyProofFormatService: IndyProofFormatService let agentContext: AgentContext beforeEach(() => { - const agentConfig = getAgentConfig('ProofServiceTest') + const agentConfig = getAgentConfig('V1ProofServiceTest') + agentContext = getAgentContext() proofRepository = new ProofRepositoryMock() - indyVerifierService = new IndyVerifierServiceMock() indyHolderService = new IndyHolderServiceMock() indyRevocationService = new IndyRevocationServiceMock() ledgerService = new IndyLedgerServiceMock() eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) connectionService = new connectionServiceMock() + didCommMessageRepository = new didCommMessageRepositoryMock() + indyProofFormatService = new indyProofFormatServiceMock() agentContext = getAgentContext() - proofService = new ProofService( + proofService = new V1ProofService( proofRepository, + didCommMessageRepository, ledgerService, - indyHolderService, - indyVerifierService, - indyRevocationService, + wallet, + agentConfig, connectionService, eventEmitter, credentialRepository, - agentConfig.logger + indyProofFormatService, + indyHolderService, + indyRevocationService ) mockFunction(ledgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) }) describe('processProofRequest', () => { - let presentationRequest: RequestPresentationMessage - let messageContext: InboundMessageContext + let presentationRequest: V1RequestPresentationMessage + let messageContext: InboundMessageContext beforeEach(() => { - presentationRequest = new RequestPresentationMessage({ + presentationRequest = new V1RequestPresentationMessage({ comment: 'abcd', requestPresentationAttachments: [requestAttachment], }) @@ -205,7 +214,7 @@ describe('ProofService', () => { mockFunction(proofRepository.getById).mockReturnValue(Promise.resolve(proof)) // when - const presentationProblemReportMessage = await new PresentationProblemReportMessage({ + const presentationProblemReportMessage = await new V1PresentationProblemReportMessage({ description: { en: 'Indy error', code: PresentationProblemReportReason.Abandoned, @@ -226,14 +235,14 @@ describe('ProofService', () => { describe('processProblemReport', () => { let proof: ProofRecord - let messageContext: InboundMessageContext + let messageContext: InboundMessageContext beforeEach(() => { proof = mockProofRecord({ state: ProofState.RequestReceived, }) - const presentationProblemReportMessage = new PresentationProblemReportMessage({ + const presentationProblemReportMessage = new V1PresentationProblemReportMessage({ description: { en: 'Indy error', code: PresentationProblemReportReason.Abandoned, diff --git a/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts b/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts new file mode 100644 index 0000000000..34348720d8 --- /dev/null +++ b/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts @@ -0,0 +1,274 @@ +import type { AgentContext } from '../../../agent' +import type { Wallet } from '../../../wallet/Wallet' +import type { ProofStateChangedEvent } from '../ProofEvents' +import type { CustomProofTags } from '../repository/ProofRecord' + +import { Subject } from 'rxjs' + +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../tests/helpers' +import { EventEmitter } from '../../../agent/EventEmitter' +import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' +import { DidCommMessageRepository } from '../../../storage' +import { ConnectionService, DidExchangeState } from '../../connections' +import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' +import { ProofEventTypes } from '../ProofEvents' +import { PresentationProblemReportReason } from '../errors/PresentationProblemReportReason' +import { V2_INDY_PRESENTATION, V2_INDY_PRESENTATION_REQUEST } from '../formats/ProofFormatConstants' +import { IndyProofFormatService } from '../formats/indy/IndyProofFormatService' +import { ProofProtocolVersion } from '../models/ProofProtocolVersion' +import { ProofState } from '../models/ProofState' +import { V2ProofService } from '../protocol/v2/V2ProofService' +import { V2PresentationProblemReportMessage, V2RequestPresentationMessage } from '../protocol/v2/messages' +import { ProofRecord } from '../repository/ProofRecord' +import { ProofRepository } from '../repository/ProofRepository' + +import { credDef } from './fixtures' + +// Mock classes +jest.mock('../repository/ProofRepository') +jest.mock('../../../modules/ledger/services/IndyLedgerService') +jest.mock('../../indy/services/IndyHolderService') +jest.mock('../../indy/services/IndyIssuerService') +jest.mock('../../indy/services/IndyVerifierService') +jest.mock('../../connections/services/ConnectionService') +jest.mock('../../../storage/Repository') + +// Mock typed object +const ProofRepositoryMock = ProofRepository as jest.Mock +const IndyLedgerServiceMock = IndyLedgerService as jest.Mock +const connectionServiceMock = ConnectionService as jest.Mock +const didCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock +const indyProofFormatServiceMock = IndyProofFormatService as jest.Mock + +const connection = getMockConnection({ + id: '123', + state: DidExchangeState.Completed, +}) + +const requestAttachment = new Attachment({ + id: 'abdc8b63-29c6-49ad-9e10-98f9d85db9a2', + mimeType: 'application/json', + data: new AttachmentData({ + base64: + 'eyJuYW1lIjogIlByb29mIHJlcXVlc3QiLCAibm9uX3Jldm9rZWQiOiB7ImZyb20iOiAxNjQwOTk1MTk5LCAidG8iOiAxNjQwOTk1MTk5fSwgIm5vbmNlIjogIjEiLCAicmVxdWVzdGVkX2F0dHJpYnV0ZXMiOiB7ImFkZGl0aW9uYWxQcm9wMSI6IHsibmFtZSI6ICJmYXZvdXJpdGVEcmluayIsICJub25fcmV2b2tlZCI6IHsiZnJvbSI6IDE2NDA5OTUxOTksICJ0byI6IDE2NDA5OTUxOTl9LCAicmVzdHJpY3Rpb25zIjogW3siY3JlZF9kZWZfaWQiOiAiV2dXeHF6dHJOb29HOTJSWHZ4U1RXdjozOkNMOjIwOnRhZyJ9XX19LCAicmVxdWVzdGVkX3ByZWRpY2F0ZXMiOiB7fSwgInZlcnNpb24iOiAiMS4wIn0=', + }), +}) + +// A record is deserialized to JSON when it's stored into the storage. We want to simulate this behaviour for `offer` +// object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. +const mockProofRecord = ({ + state, + threadId, + connectionId, + tags, + id, +}: { + state?: ProofState + requestMessage?: V2RequestPresentationMessage + tags?: CustomProofTags + threadId?: string + connectionId?: string + id?: string +} = {}) => { + const requestPresentationMessage = new V2RequestPresentationMessage({ + attachmentInfo: [ + { + format: { + attachmentId: 'abdc8b63-29c6-49ad-9e10-98f9d85db9a2', + format: V2_INDY_PRESENTATION, + }, + attachment: requestAttachment, + }, + ], + comment: 'some comment', + }) + + const proofRecord = new ProofRecord({ + protocolVersion: ProofProtocolVersion.V2, + id, + state: state || ProofState.RequestSent, + threadId: threadId ?? requestPresentationMessage.id, + connectionId: connectionId ?? '123', + tags, + }) + + return proofRecord +} + +describe('V2ProofService', () => { + let proofRepository: ProofRepository + let proofService: V2ProofService + let ledgerService: IndyLedgerService + let wallet: Wallet + let eventEmitter: EventEmitter + let connectionService: ConnectionService + let didCommMessageRepository: DidCommMessageRepository + let indyProofFormatService: IndyProofFormatService + let agentContext: AgentContext + + beforeEach(() => { + agentContext = getAgentContext() + const agentConfig = getAgentConfig('V2ProofServiceTest') + proofRepository = new ProofRepositoryMock() + ledgerService = new IndyLedgerServiceMock() + eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) + connectionService = new connectionServiceMock() + didCommMessageRepository = new didCommMessageRepositoryMock() + indyProofFormatService = new indyProofFormatServiceMock() + + proofService = new V2ProofService( + agentConfig, + connectionService, + proofRepository, + didCommMessageRepository, + eventEmitter, + indyProofFormatService, + wallet + ) + + mockFunction(ledgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) + }) + + describe('processProofRequest', () => { + let presentationRequest: V2RequestPresentationMessage + let messageContext: InboundMessageContext + + beforeEach(() => { + presentationRequest = new V2RequestPresentationMessage({ + attachmentInfo: [ + { + format: { + attachmentId: 'abdc8b63-29c6-49ad-9e10-98f9d85db9a2', + format: V2_INDY_PRESENTATION_REQUEST, + }, + attachment: requestAttachment, + }, + ], + comment: 'Proof Request', + }) + messageContext = new InboundMessageContext(presentationRequest, { agentContext, connection }) + }) + + test(`creates and return proof record in ${ProofState.PresentationReceived} state with offer, without thread ID`, async () => { + const repositorySaveSpy = jest.spyOn(proofRepository, 'save') + + // when + const returnedProofRecord = await proofService.processRequest(messageContext) + + // then + const expectedProofRecord = { + type: ProofRecord.name, + id: expect.any(String), + createdAt: expect.any(Date), + state: ProofState.RequestReceived, + threadId: presentationRequest.id, + connectionId: connection.id, + } + expect(repositorySaveSpy).toHaveBeenCalledTimes(1) + const [[, createdProofRecord]] = repositorySaveSpy.mock.calls + expect(createdProofRecord).toMatchObject(expectedProofRecord) + expect(returnedProofRecord).toMatchObject(expectedProofRecord) + }) + + test(`emits stateChange event with ${ProofState.RequestReceived}`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(ProofEventTypes.ProofStateChanged, eventListenerMock) + + // when + await proofService.processRequest(messageContext) + + // then + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'ProofStateChanged', + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState: null, + proofRecord: expect.objectContaining({ + state: ProofState.RequestReceived, + }), + }, + }) + }) + }) + + describe('createProblemReport', () => { + const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' + let proof: ProofRecord + + beforeEach(() => { + proof = mockProofRecord({ + state: ProofState.RequestReceived, + threadId, + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }) + }) + + test('returns problem report message base once get error', async () => { + // given + mockFunction(proofRepository.getById).mockReturnValue(Promise.resolve(proof)) + + // when + const presentationProblemReportMessage = await new V2PresentationProblemReportMessage({ + description: { + en: 'Indy error', + code: PresentationProblemReportReason.Abandoned, + }, + }) + + presentationProblemReportMessage.setThread({ threadId }) + // then + expect(presentationProblemReportMessage.toJSON()).toMatchObject({ + '@id': expect.any(String), + '@type': 'https://didcomm.org/present-proof/2.0/problem-report', + '~thread': { + thid: 'fd9c5ddb-ec11-4acd-bc32-540736249746', + }, + }) + }) + }) + + describe('processProblemReport', () => { + let proof: ProofRecord + let messageContext: InboundMessageContext + beforeEach(() => { + proof = mockProofRecord({ + state: ProofState.RequestReceived, + }) + + const presentationProblemReportMessage = new V2PresentationProblemReportMessage({ + description: { + en: 'Indy error', + code: PresentationProblemReportReason.Abandoned, + }, + }) + presentationProblemReportMessage.setThread({ threadId: 'somethreadid' }) + messageContext = new InboundMessageContext(presentationProblemReportMessage, { agentContext, connection }) + }) + + test(`updates problem report error message and returns proof record`, async () => { + const repositoryUpdateSpy = jest.spyOn(proofRepository, 'update') + + // given + mockFunction(proofRepository.getSingleByQuery).mockReturnValue(Promise.resolve(proof)) + + // when + const returnedCredentialRecord = await proofService.processProblemReport(messageContext) + + // then + const expectedCredentialRecord = { + errorMessage: 'abandoned: Indy error', + } + expect(proofRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { + threadId: 'somethreadid', + connectionId: connection.id, + }) + expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) + const [[, updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls + expect(updatedCredentialRecord).toMatchObject(expectedCredentialRecord) + expect(returnedCredentialRecord).toMatchObject(expectedCredentialRecord) + }) + }) +}) diff --git a/packages/core/src/modules/proofs/errors/PresentationProblemReportError.ts b/packages/core/src/modules/proofs/errors/PresentationProblemReportError.ts deleted file mode 100644 index 2869a026d5..0000000000 --- a/packages/core/src/modules/proofs/errors/PresentationProblemReportError.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { ProblemReportErrorOptions } from '../../problem-reports' -import type { PresentationProblemReportReason } from './PresentationProblemReportReason' - -import { PresentationProblemReportMessage } from '../messages' - -import { ProblemReportError } from './../../problem-reports/errors/ProblemReportError' - -interface PresentationProblemReportErrorOptions extends ProblemReportErrorOptions { - problemCode: PresentationProblemReportReason -} - -export class PresentationProblemReportError extends ProblemReportError { - public problemReport: PresentationProblemReportMessage - - public constructor(public message: string, { problemCode }: PresentationProblemReportErrorOptions) { - super(message, { problemCode }) - this.problemReport = new PresentationProblemReportMessage({ - description: { - en: message, - code: problemCode, - }, - }) - } -} diff --git a/packages/core/src/modules/proofs/errors/index.ts b/packages/core/src/modules/proofs/errors/index.ts index 5e0ca1453b..b14650ff96 100644 --- a/packages/core/src/modules/proofs/errors/index.ts +++ b/packages/core/src/modules/proofs/errors/index.ts @@ -1,2 +1 @@ -export * from './PresentationProblemReportError' export * from './PresentationProblemReportReason' diff --git a/packages/core/src/modules/proofs/formats/ProofFormat.ts b/packages/core/src/modules/proofs/formats/ProofFormat.ts new file mode 100644 index 0000000000..18fbba278d --- /dev/null +++ b/packages/core/src/modules/proofs/formats/ProofFormat.ts @@ -0,0 +1,45 @@ +/** + * Get the payload for a specific method from a list of ProofFormat interfaces and a method + * + * @example + * ``` + * + * type CreateRequestProofFormats = ProofFormatPayload<[IndyProofFormat, PresentationExchangeProofFormat], 'createRequest'> + * + * // equal to + * type CreateRequestProofFormats = { + * indy: { + * // ... params for indy create request ... + * }, + * presentationExchange: { + * // ... params for pex create request ... + * } + * } + * ``` + */ +export type ProofFormatPayload = { + [ProofFormat in PFs[number] as ProofFormat['formatKey']]?: ProofFormat['proofFormats'][M] +} + +export interface ProofFormat { + formatKey: string // e.g. 'ProofManifest', cannot be shared between different formats + proofFormats: { + createProposal: unknown + acceptProposal: unknown + createRequest: unknown + acceptRequest: unknown + createPresentation: unknown + acceptPresentation: unknown + createProposalAsResponse: unknown + createOutOfBandRequest: unknown + createRequestAsResponse: unknown + createProofRequestFromProposal: unknown + requestCredentials: unknown + retrieveCredentials: unknown + } + formatData: { + proposal: unknown + request: unknown + presentation: unknown + } +} diff --git a/packages/core/src/modules/proofs/formats/ProofFormatConstants.ts b/packages/core/src/modules/proofs/formats/ProofFormatConstants.ts new file mode 100644 index 0000000000..35e1ce33ab --- /dev/null +++ b/packages/core/src/modules/proofs/formats/ProofFormatConstants.ts @@ -0,0 +1,4 @@ +export const INDY_ATTACH_ID = 'indy' +export const V2_INDY_PRESENTATION_PROPOSAL = 'hlindy/proof-req@v2.0' +export const V2_INDY_PRESENTATION_REQUEST = 'hlindy/proof-req@v2.0' +export const V2_INDY_PRESENTATION = 'hlindy/proof@v2.0' diff --git a/packages/core/src/modules/proofs/formats/ProofFormatService.ts b/packages/core/src/modules/proofs/formats/ProofFormatService.ts new file mode 100644 index 0000000000..92e00838ff --- /dev/null +++ b/packages/core/src/modules/proofs/formats/ProofFormatService.ts @@ -0,0 +1,79 @@ +import type { AgentContext } from '../../../agent' +import type { AgentConfig } from '../../../agent/AgentConfig' +import type { DidCommMessageRepository } from '../../../storage' +import type { + CreateRequestAsResponseOptions, + FormatRequestedCredentialReturn, + FormatRetrievedCredentialOptions, +} from '../models/ProofServiceOptions' +import type { ProofRequestFormats } from '../models/SharedOptions' +import type { ProofFormat } from './ProofFormat' +import type { IndyProofFormat } from './indy/IndyProofFormat' +import type { GetRequestedCredentialsFormat } from './indy/IndyProofFormatsServiceOptions' +import type { ProofAttachmentFormat } from './models/ProofAttachmentFormat' +import type { + CreatePresentationFormatsOptions, + CreateProposalOptions, + CreateRequestOptions, + FormatCreatePresentationOptions, + ProcessPresentationOptions, + ProcessProposalOptions, + ProcessRequestOptions, +} from './models/ProofFormatServiceOptions' + +/** + * This abstract class is the base class for any proof format + * specific service. + * + * @export + * @abstract + * @class ProofFormatService + */ +export abstract class ProofFormatService { + protected didCommMessageRepository: DidCommMessageRepository + protected agentConfig: AgentConfig + + abstract readonly formatKey: PF['formatKey'] + + public constructor(didCommMessageRepository: DidCommMessageRepository, agentConfig: AgentConfig) { + this.didCommMessageRepository = didCommMessageRepository + this.agentConfig = agentConfig + } + + abstract createProposal(options: CreateProposalOptions): Promise + + abstract processProposal(options: ProcessProposalOptions): Promise + + abstract createRequest(options: CreateRequestOptions): Promise + + abstract processRequest(options: ProcessRequestOptions): Promise + + abstract createPresentation( + agentContext: AgentContext, + options: FormatCreatePresentationOptions + ): Promise + + abstract processPresentation(agentContext: AgentContext, options: ProcessPresentationOptions): Promise + + abstract createProofRequestFromProposal(options: CreatePresentationFormatsOptions): Promise + + public abstract getRequestedCredentialsForProofRequest( + agentContext: AgentContext, + options: GetRequestedCredentialsFormat + ): Promise> + + public abstract autoSelectCredentialsForProofRequest( + options: FormatRetrievedCredentialOptions<[PF]> + ): Promise> + + abstract proposalAndRequestAreEqual( + proposalAttachments: ProofAttachmentFormat[], + requestAttachments: ProofAttachmentFormat[] + ): boolean + + abstract supportsFormat(formatIdentifier: string): boolean + + abstract createRequestAsResponse( + options: CreateRequestAsResponseOptions<[IndyProofFormat]> + ): Promise +} diff --git a/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts b/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts new file mode 100644 index 0000000000..12a0a69dc9 --- /dev/null +++ b/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts @@ -0,0 +1,31 @@ +import type { Attachment } from '../../../decorators/attachment/Attachment' +import type { ProofFormatSpec } from '../models/ProofFormatSpec' +import type { ProofFormat } from './ProofFormat' +import type { ProofFormatService } from './ProofFormatService' + +/** + * Get the service map for usage in the proofs module. Will return a type mapping of protocol version to service. + * + * @example + * ``` + * type ProofFormatServiceMap = FormatServiceMap<[IndyProofFormat]> + * + * // equal to + * type ProofFormatServiceMap = { + * indy: ProofFormatService + * } + * ``` + */ +export type FormatServiceMap = { + [PF in PFs[number] as PF['formatKey']]: ProofFormatService +} + +/** + * Base return type for all methods that create an attachment format. + * + * It requires an attachment and a format to be returned. + */ +export interface FormatCreateReturn { + format: ProofFormatSpec + attachment: Attachment +} diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts new file mode 100644 index 0000000000..a0e9d893c4 --- /dev/null +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts @@ -0,0 +1,77 @@ +import type { LinkedAttachment } from '../../../../utils/LinkedAttachment' +import type { CredentialPreviewAttributeOptions } from '../../../credentials' +import type { + PresentationPreviewAttribute, + PresentationPreviewPredicate, +} from '../../protocol/v1/models/V1PresentationPreview' +import type { ProofFormat } from '../ProofFormat' +import type { IndyRequestProofFormat } from '../indy/IndyProofFormatsServiceOptions' +import type { RequestedAttribute } from './models/RequestedAttribute' +import type { IndyRequestedCredentialsOptions } from './models/RequestedCredentials' +import type { RequestedPredicate } from './models/RequestedPredicate' + +export interface IndyProposeProofFormat { + attributes?: PresentationPreviewAttribute[] + predicates?: PresentationPreviewPredicate[] + nonce: string + name: string + version: string +} + +/** + * This defines the module payload for calling CredentialsApi.acceptProposal + */ +export interface IndyAcceptProposalFormat { + credentialDefinitionId?: string + attributes?: CredentialPreviewAttributeOptions[] + linkedAttachments?: LinkedAttachment[] +} + +export interface IndyAcceptOfferFormat { + holderDid?: string +} + +export interface IndyRequestedCredentialsFormat { + requestedAttributes: Record + requestedPredicates: Record + selfAttestedAttributes: Record +} + +export interface IndyRetrievedCredentialsFormat { + requestedAttributes: Record + requestedPredicates: Record +} + +export interface IndyProofFormat extends ProofFormat { + formatKey: 'indy' + proofRecordType: 'indy' + proofFormats: { + createProposal: IndyProposeProofFormat + acceptProposal: unknown + createRequest: IndyRequestProofFormat + acceptRequest: unknown + createPresentation: IndyRequestedCredentialsOptions + acceptPresentation: unknown + createProposalAsResponse: IndyProposeProofFormat + createOutOfBandRequest: unknown + createRequestAsResponse: IndyRequestProofFormat + createProofRequestFromProposal: IndyRequestProofFormat + requestCredentials: IndyRequestedCredentialsFormat + retrieveCredentials: IndyRetrievedCredentialsFormat + } + // Format data is based on RFC 0592 + // https://github.com/hyperledger/aries-rfcs/tree/main/features/0592-indy-attachments + // formatData: { + // proposal: { + // schema_issuer_did?: string + // schema_name?: string + // schema_version?: string + // schema_id?: string + // issuer_did?: string + // cred_def_id?: string + // } + // offer: CredOffer + // request: CredReq + // credential: Cred + // } +} diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts new file mode 100644 index 0000000000..83aca03ca6 --- /dev/null +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts @@ -0,0 +1,646 @@ +import type { AgentContext } from '../../../../agent' +import type { Logger } from '../../../../logger' +import type { + CreateRequestAsResponseOptions, + FormatRequestedCredentialReturn, + FormatRetrievedCredentialOptions, +} from '../../models/ProofServiceOptions' +import type { ProofRequestFormats } from '../../models/SharedOptions' +import type { ProofAttachmentFormat } from '../models/ProofAttachmentFormat' +import type { + CreatePresentationFormatsOptions, + CreateProofAttachmentOptions, + CreateProposalOptions, + CreateRequestAttachmentOptions, + CreateRequestOptions, + FormatCreatePresentationOptions, + ProcessPresentationOptions, + ProcessProposalOptions, + ProcessRequestOptions, + VerifyProofOptions, +} from '../models/ProofFormatServiceOptions' +import type { IndyProofFormat } from './IndyProofFormat' +import type { GetRequestedCredentialsFormat } from './IndyProofFormatsServiceOptions' +import type { ProofAttributeInfo, ProofPredicateInfo } from './models' +import type { CredDef, IndyProof, Schema } from 'indy-sdk' + +import { Lifecycle, scoped } from 'tsyringe' + +import { AgentConfig } from '../../../../agent/AgentConfig' +import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' +import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' +import { ConsoleLogger, LogLevel } from '../../../../logger' +import { DidCommMessageRepository } from '../../../../storage/didcomm/DidCommMessageRepository' +import { checkProofRequestForDuplicates } from '../../../../utils' +import { JsonEncoder } from '../../../../utils/JsonEncoder' +import { JsonTransformer } from '../../../../utils/JsonTransformer' +import { MessageValidator } from '../../../../utils/MessageValidator' +import { objectEquals } from '../../../../utils/objectCheck' +import { uuid } from '../../../../utils/uuid' +import { IndyWallet } from '../../../../wallet/IndyWallet' +import { IndyCredential, IndyCredentialInfo } from '../../../credentials' +import { IndyCredentialUtils } from '../../../credentials/formats/indy/IndyCredentialUtils' +import { IndyHolderService, IndyVerifierService, IndyRevocationService } from '../../../indy' +import { IndyLedgerService } from '../../../ledger' +import { ProofFormatSpec } from '../../models/ProofFormatSpec' +import { PartialProof } from '../../protocol/v1/models' +import { + V2_INDY_PRESENTATION_REQUEST, + V2_INDY_PRESENTATION_PROPOSAL, + V2_INDY_PRESENTATION, +} from '../ProofFormatConstants' +import { ProofFormatService } from '../ProofFormatService' + +import { InvalidEncodedValueError } from './errors/InvalidEncodedValueError' +import { MissingIndyProofMessageError } from './errors/MissingIndyProofMessageError' +import { RequestedAttribute, RequestedPredicate } from './models' +import { ProofRequest } from './models/ProofRequest' +import { RequestedCredentials } from './models/RequestedCredentials' +import { RetrievedCredentials } from './models/RetrievedCredentials' + +@scoped(Lifecycle.ContainerScoped) +export class IndyProofFormatService extends ProofFormatService { + private indyHolderService: IndyHolderService + private indyVerifierService: IndyVerifierService + private indyRevocationService: IndyRevocationService + private ledgerService: IndyLedgerService + private logger: Logger + private wallet: IndyWallet + + public constructor( + agentConfig: AgentConfig, + indyHolderService: IndyHolderService, + indyVerifierService: IndyVerifierService, + indyRevocationService: IndyRevocationService, + ledgerService: IndyLedgerService, + didCommMessageRepository: DidCommMessageRepository, + wallet: IndyWallet + ) { + super(didCommMessageRepository, agentConfig) + this.indyHolderService = indyHolderService + this.indyVerifierService = indyVerifierService + this.indyRevocationService = indyRevocationService + this.ledgerService = ledgerService + this.wallet = wallet + this.logger = new ConsoleLogger(LogLevel.off) + } + public readonly formatKey = 'indy' as const + public readonly proofRecordType = 'indy' as const + + private createRequestAttachment(options: CreateRequestAttachmentOptions): ProofAttachmentFormat { + const format = new ProofFormatSpec({ + attachmentId: options.id, + format: V2_INDY_PRESENTATION_REQUEST, + }) + + const request = new ProofRequest(options.proofRequestOptions) + + // Assert attribute and predicate (group) names do not match + checkProofRequestForDuplicates(request) + + const attachment = new Attachment({ + id: options.id, + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(request), + }), + }) + return { format, attachment } + } + + private async createProofAttachment(options: CreateProofAttachmentOptions): Promise { + const format = new ProofFormatSpec({ + attachmentId: options.id, + format: V2_INDY_PRESENTATION_PROPOSAL, + }) + + const request = new ProofRequest(options.proofProposalOptions) + await MessageValidator.validateSync(request) + + const attachment = new Attachment({ + id: options.id, + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(JsonTransformer.toJSON(request)), + }), + }) + return { format, attachment } + } + + public async createProposal(options: CreateProposalOptions): Promise { + if (!options.formats.indy) { + throw Error('Missing indy format to create proposal attachment format') + } + const indyFormat = options.formats.indy + + return await this.createProofAttachment({ + id: options.id ?? uuid(), + proofProposalOptions: indyFormat, + }) + } + + public async processProposal(options: ProcessProposalOptions): Promise { + const proofProposalJson = options.proposal.attachment.getDataAsJson() + + // Assert attachment + if (!proofProposalJson) { + throw new AriesFrameworkError( + `Missing required base64 or json encoded attachment data for presentation proposal with thread id ${options.record?.threadId}` + ) + } + + const proposalMessage = JsonTransformer.fromJSON(proofProposalJson, ProofRequest) + + await MessageValidator.validateSync(proposalMessage) + } + + public async createRequestAsResponse( + options: CreateRequestAsResponseOptions<[IndyProofFormat]> + ): Promise { + if (!options.proofFormats.indy) { + throw Error('Missing indy format to create proposal attachment format') + } + + const id = options.id ?? uuid() + + const format = new ProofFormatSpec({ + attachmentId: id, + format: V2_INDY_PRESENTATION_REQUEST, + }) + + const attachment = new Attachment({ + id: id, + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(options.proofFormats.indy), + }), + }) + return { format, attachment } + } + + public async createRequest(options: CreateRequestOptions): Promise { + if (!options.formats.indy) { + throw new AriesFrameworkError('Missing indy format to create proof request attachment format.') + } + + return this.createRequestAttachment({ + id: options.id ?? uuid(), + proofRequestOptions: options.formats.indy, + }) + } + + public async processRequest(options: ProcessRequestOptions): Promise { + const proofRequestJson = options.requestAttachment.attachment.getDataAsJson() + + const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) + + // Assert attachment + if (!proofRequest) { + throw new AriesFrameworkError( + `Missing required base64 or json encoded attachment data for presentation request with thread id ${options.record?.threadId}` + ) + } + await MessageValidator.validateSync(proofRequest) + + // Assert attribute and predicate (group) names do not match + checkProofRequestForDuplicates(proofRequest) + } + + public async createPresentation( + agentContext: AgentContext, + options: FormatCreatePresentationOptions + ): Promise { + // Extract proof request from attachment + const proofRequestJson = options.attachment.getDataAsJson() ?? null + const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) + + // verify everything is there + if (!options.proofFormats.indy) { + throw new AriesFrameworkError('Missing indy format to create proof presentation attachment format.') + } + + const requestedCredentials = new RequestedCredentials({ + requestedAttributes: options.proofFormats.indy.requestedAttributes, + requestedPredicates: options.proofFormats.indy.requestedPredicates, + selfAttestedAttributes: options.proofFormats.indy.selfAttestedAttributes, + }) + + const proof = await this.createProof(agentContext, proofRequest, requestedCredentials) + + const attachmentId = options.id ?? uuid() + + const format = new ProofFormatSpec({ + attachmentId, + format: V2_INDY_PRESENTATION, + }) + + const attachment = new Attachment({ + id: attachmentId, + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(proof), + }), + }) + return { format, attachment } + } + + public async processPresentation(agentContext: AgentContext, options: ProcessPresentationOptions): Promise { + const requestFormat = options.formatAttachments.request.find( + (x) => x.format.format === V2_INDY_PRESENTATION_REQUEST + ) + + if (!requestFormat) { + throw new MissingIndyProofMessageError( + 'Missing Indy Proof Request format while trying to process an Indy proof presentation.' + ) + } + + const proofFormat = options.formatAttachments.presentation.find((x) => x.format.format === V2_INDY_PRESENTATION) + + if (!proofFormat) { + throw new MissingIndyProofMessageError( + 'Missing Indy Proof Presentation format while trying to process an Indy proof presentation.' + ) + } + + return await this.verifyProof(agentContext, { request: requestFormat.attachment, proof: proofFormat.attachment }) + } + + public async verifyProof(agentContext: AgentContext, options: VerifyProofOptions): Promise { + if (!options) { + throw new AriesFrameworkError('No Indy proof was provided.') + } + const proofRequestJson = options.request.getDataAsJson() ?? null + const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) + + const proofJson = options.proof.getDataAsJson() ?? null + + const proof = JsonTransformer.fromJSON(proofJson, PartialProof) + + for (const [referent, attribute] of proof.requestedProof.revealedAttributes.entries()) { + if (!IndyCredentialUtils.checkValidEncoding(attribute.raw, attribute.encoded)) { + throw new InvalidEncodedValueError( + `The encoded value for '${referent}' is invalid. ` + + `Expected '${IndyCredentialUtils.encode(attribute.raw)}'. ` + + `Actual '${attribute.encoded}'` + ) + } + } + + // TODO: pre verify proof json + // I'm not 100% sure how much indy does. Also if it checks whether the proof requests matches the proof + // @see https://github.com/hyperledger/aries-cloudagent-python/blob/master/aries_cloudagent/indy/sdk/verifier.py#L79-L164 + + const schemas = await this.getSchemas(agentContext, new Set(proof.identifiers.map((i) => i.schemaId))) + const credentialDefinitions = await this.getCredentialDefinitions( + agentContext, + new Set(proof.identifiers.map((i) => i.credentialDefinitionId)) + ) + + return await this.indyVerifierService.verifyProof(agentContext, { + proofRequest: proofRequest.toJSON(), + proof: proofJson, + schemas, + credentialDefinitions, + }) + } + + public supportsFormat(formatIdentifier: string): boolean { + const supportedFormats = [V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST, V2_INDY_PRESENTATION] + return supportedFormats.includes(formatIdentifier) + } + + /** + * Compare presentation attrs with request/proposal attrs (auto-accept) + * + * @param proposalAttachments attachment data from the proposal + * @param requestAttachments attachment data from the request + * @returns boolean value + */ + public proposalAndRequestAreEqual( + proposalAttachments: ProofAttachmentFormat[], + requestAttachments: ProofAttachmentFormat[] + ) { + const proposalAttachment = proposalAttachments.find( + (x) => x.format.format === V2_INDY_PRESENTATION_PROPOSAL + )?.attachment + const requestAttachment = requestAttachments.find( + (x) => x.format.format === V2_INDY_PRESENTATION_REQUEST + )?.attachment + + if (!proposalAttachment) { + throw new AriesFrameworkError('Proposal message has no attachment linked to it') + } + + if (!requestAttachment) { + throw new AriesFrameworkError('Request message has no attachment linked to it') + } + + const proposalAttachmentJson = proposalAttachment.getDataAsJson() + const proposalAttachmentData = JsonTransformer.fromJSON(proposalAttachmentJson, ProofRequest) + + const requestAttachmentJson = requestAttachment.getDataAsJson() + const requestAttachmentData = JsonTransformer.fromJSON(requestAttachmentJson, ProofRequest) + + if ( + objectEquals(proposalAttachmentData.requestedAttributes, requestAttachmentData.requestedAttributes) && + objectEquals(proposalAttachmentData.requestedPredicates, requestAttachmentData.requestedPredicates) + ) { + return true + } + + return false + } + + /** + * Build credential definitions object needed to create and verify proof objects. + * + * Creates object with `{ credentialDefinitionId: CredentialDefinition }` mapping + * + * @param credentialDefinitionIds List of credential definition ids + * @returns Object containing credential definitions for specified credential definition ids + * + */ + private async getCredentialDefinitions(agentContext: AgentContext, credentialDefinitionIds: Set) { + const credentialDefinitions: { [key: string]: CredDef } = {} + + for (const credDefId of credentialDefinitionIds) { + const credDef = await this.ledgerService.getCredentialDefinition(agentContext, credDefId) + credentialDefinitions[credDefId] = credDef + } + + return credentialDefinitions + } + + public async getRequestedCredentialsForProofRequest( + agentContext: AgentContext, + options: GetRequestedCredentialsFormat + ): Promise> { + const retrievedCredentials = new RetrievedCredentials({}) + const { attachment, presentationProposal } = options + const filterByNonRevocationRequirements = options.config?.filterByNonRevocationRequirements + + const proofRequestJson = attachment.getDataAsJson() ?? null + const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) + + for (const [referent, requestedAttribute] of proofRequest.requestedAttributes.entries()) { + let credentialMatch: IndyCredential[] = [] + const credentials = await this.getCredentialsForProofRequest(agentContext, proofRequest, referent) + + // If we have exactly one credential, or no proposal to pick preferences + // on the credentials to use, we will use the first one + if (credentials.length === 1 || !presentationProposal) { + credentialMatch = credentials + } + // If we have a proposal we will use that to determine the credentials to use + else { + const names = requestedAttribute.names ?? [requestedAttribute.name] + + // Find credentials that matches all parameters from the proposal + credentialMatch = credentials.filter((credential) => { + const { attributes, credentialDefinitionId } = credential.credentialInfo + + // Check if credentials matches all parameters from proposal + return names.every((name) => + presentationProposal.attributes.find( + (a) => + a.name === name && + a.credentialDefinitionId === credentialDefinitionId && + (!a.value || a.value === attributes[name]) + ) + ) + }) + } + + retrievedCredentials.requestedAttributes[referent] = await Promise.all( + credentialMatch.map(async (credential: IndyCredential) => { + const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { + proofRequest, + requestedItem: requestedAttribute, + credential, + }) + + return new RequestedAttribute({ + credentialId: credential.credentialInfo.referent, + revealed: true, + credentialInfo: credential.credentialInfo, + timestamp: deltaTimestamp, + revoked, + }) + }) + ) + + // We only attach revoked state if non-revocation is requested. So if revoked is true it means + // the credential is not applicable to the proof request + if (filterByNonRevocationRequirements) { + retrievedCredentials.requestedAttributes[referent] = retrievedCredentials.requestedAttributes[referent].filter( + (r) => !r.revoked + ) + } + } + + for (const [referent, requestedPredicate] of proofRequest.requestedPredicates.entries()) { + const credentials = await this.getCredentialsForProofRequest(agentContext, proofRequest, referent) + + retrievedCredentials.requestedPredicates[referent] = await Promise.all( + credentials.map(async (credential) => { + const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { + proofRequest, + requestedItem: requestedPredicate, + credential, + }) + + return new RequestedPredicate({ + credentialId: credential.credentialInfo.referent, + credentialInfo: credential.credentialInfo, + timestamp: deltaTimestamp, + revoked, + }) + }) + ) + + // We only attach revoked state if non-revocation is requested. So if revoked is true it means + // the credential is not applicable to the proof request + if (filterByNonRevocationRequirements) { + retrievedCredentials.requestedPredicates[referent] = retrievedCredentials.requestedPredicates[referent].filter( + (r) => !r.revoked + ) + } + } + + return { + proofFormats: { + indy: retrievedCredentials, + }, + } + } + + private async getCredentialsForProofRequest( + agentContext: AgentContext, + proofRequest: ProofRequest, + attributeReferent: string + ): Promise { + const credentialsJson = await this.indyHolderService.getCredentialsForProofRequest(agentContext, { + proofRequest: proofRequest.toJSON(), + attributeReferent, + }) + + return JsonTransformer.fromJSON(credentialsJson, IndyCredential) as unknown as IndyCredential[] + } + + public async autoSelectCredentialsForProofRequest( + options: FormatRetrievedCredentialOptions<[IndyProofFormat]> + ): Promise> { + const { proofFormats } = options + const indy = proofFormats.indy + + if (!indy) { + throw new AriesFrameworkError('No indy options provided') + } + + const requestedCredentials = new RequestedCredentials({}) + + Object.keys(indy.requestedAttributes).forEach((attributeName) => { + const attributeArray = indy.requestedAttributes[attributeName] + + if (attributeArray.length === 0) { + throw new AriesFrameworkError('Unable to automatically select requested attributes.') + } else { + requestedCredentials.requestedAttributes[attributeName] = attributeArray[0] + } + }) + + Object.keys(indy.requestedPredicates).forEach((attributeName) => { + if (indy.requestedPredicates[attributeName].length === 0) { + throw new AriesFrameworkError('Unable to automatically select requested predicates.') + } else { + requestedCredentials.requestedPredicates[attributeName] = indy.requestedPredicates[attributeName][0] + } + }) + + return { + proofFormats: { + indy: requestedCredentials, + }, + } + } + + /** + * Build schemas object needed to create and verify proof objects. + * + * Creates object with `{ schemaId: Schema }` mapping + * + * @param schemaIds List of schema ids + * @returns Object containing schemas for specified schema ids + * + */ + private async getSchemas(agentContext: AgentContext, schemaIds: Set) { + const schemas: { [key: string]: Schema } = {} + + for (const schemaId of schemaIds) { + const schema = await this.ledgerService.getSchema(agentContext, schemaId) + schemas[schemaId] = schema + } + + return schemas + } + + /** + * Create indy proof from a given proof request and requested credential object. + * + * @param proofRequest The proof request to create the proof for + * @param requestedCredentials The requested credentials object specifying which credentials to use for the proof + * @returns indy proof object + */ + private async createProof( + agentContext: AgentContext, + proofRequest: ProofRequest, + requestedCredentials: RequestedCredentials + ): Promise { + const credentialObjects = await Promise.all( + [ + ...Object.values(requestedCredentials.requestedAttributes), + ...Object.values(requestedCredentials.requestedPredicates), + ].map(async (c) => { + if (c.credentialInfo) { + return c.credentialInfo + } + const credentialInfo = await this.indyHolderService.getCredential(agentContext, c.credentialId) + return JsonTransformer.fromJSON(credentialInfo, IndyCredentialInfo) + }) + ) + + const schemas = await this.getSchemas(agentContext, new Set(credentialObjects.map((c) => c.schemaId))) + const credentialDefinitions = await this.getCredentialDefinitions( + agentContext, + new Set(credentialObjects.map((c) => c.credentialDefinitionId)) + ) + + return await this.indyHolderService.createProof(agentContext, { + proofRequest: proofRequest.toJSON(), + requestedCredentials: requestedCredentials, + schemas, + credentialDefinitions, + }) + } + + public async createProofRequestFromProposal(options: CreatePresentationFormatsOptions): Promise { + const proofRequestJson = options.presentationAttachment.getDataAsJson() + + const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) + + // Assert attachment + if (!proofRequest) { + throw new AriesFrameworkError(`Missing required base64 or json encoded attachment data for presentation request.`) + } + await MessageValidator.validateSync(proofRequest) + + // Assert attribute and predicate (group) names do not match + checkProofRequestForDuplicates(proofRequest) + + return { + indy: proofRequest, + } + } + + private async getRevocationStatusForRequestedItem( + agentContext: AgentContext, + { + proofRequest, + requestedItem, + credential, + }: { + proofRequest: ProofRequest + requestedItem: ProofAttributeInfo | ProofPredicateInfo + credential: IndyCredential + } + ) { + const requestNonRevoked = requestedItem.nonRevoked ?? proofRequest.nonRevoked + const credentialRevocationId = credential.credentialInfo.credentialRevocationId + const revocationRegistryId = credential.credentialInfo.revocationRegistryId + + // If revocation interval is present and the credential is revocable then fetch the revocation status of credentials for display + if (requestNonRevoked && credentialRevocationId && revocationRegistryId) { + this.logger.trace( + `Presentation is requesting proof of non revocation, getting revocation status for credential`, + { + requestNonRevoked, + credentialRevocationId, + revocationRegistryId, + } + ) + + // Note presentation from-to's vs ledger from-to's: https://github.com/hyperledger/indy-hipe/blob/master/text/0011-cred-revocation/README.md#indy-node-revocation-registry-intervals + const status = await this.indyRevocationService.getRevocationStatus( + agentContext, + credentialRevocationId, + revocationRegistryId, + requestNonRevoked + ) + + return status + } + + return { revoked: undefined, deltaTimestamp: undefined } + } +} diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts new file mode 100644 index 0000000000..f5bc85f69a --- /dev/null +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts @@ -0,0 +1,44 @@ +import type { Attachment } from '../../../../decorators/attachment/Attachment' +import type { IndyRevocationInterval } from '../../../credentials' +import type { GetRequestedCredentialsConfig } from '../../models/GetRequestedCredentialsConfig' +import type { PresentationPreview } from '../../protocol/v1/models/V1PresentationPreview' +import type { ProofRecord } from '../../repository/ProofRecord' +import type { RequestedAttribute, RequestedPredicate } from '.././indy/models' +import type { ProofAttributeInfo } from '.././indy/models/ProofAttributeInfo' +import type { ProofPredicateInfo } from '.././indy/models/ProofPredicateInfo' +import type { ProofRequest } from '.././indy/models/ProofRequest' + +export interface IndyRequestProofFormat { + name: string + version: string + nonce: string + nonRevoked?: IndyRevocationInterval + ver?: '1.0' | '2.0' + requestedAttributes?: Record | Map + requestedPredicates?: Record | Map + proofRequest?: ProofRequest +} + +export interface IndyVerifyProofFormat { + proofJson: Attachment + proofRequest: Attachment +} + +export interface IndyPresentationProofFormat { + requestedAttributes?: Record + requestedPredicates?: Record + selfAttestedAttributes?: Record +} + +export interface GetRequestedCredentialsFormat { + attachment: Attachment + presentationProposal?: PresentationPreview + config?: GetRequestedCredentialsConfig +} + +export interface IndyProofRequestFromProposalOptions { + proofRecord: ProofRecord + name?: string + version?: string + nonce?: string +} diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofUtils.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofUtils.ts new file mode 100644 index 0000000000..137d9edb25 --- /dev/null +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofUtils.ts @@ -0,0 +1,115 @@ +import type { CreateProposalOptions } from '../../models/ProofServiceOptions' +import type { ProofRequestFormats } from '../../models/SharedOptions' +import type { PresentationPreviewAttribute } from '../../protocol/v1/models/V1PresentationPreview' +import type { IndyProofFormat, IndyProposeProofFormat } from './IndyProofFormat' + +import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' +import { uuid } from '../../../../utils/uuid' +import { PresentationPreview } from '../../protocol/v1/models/V1PresentationPreview' + +import { AttributeFilter } from './models/AttributeFilter' +import { ProofAttributeInfo } from './models/ProofAttributeInfo' +import { ProofPredicateInfo } from './models/ProofPredicateInfo' +import { ProofRequest } from './models/ProofRequest' + +export class IndyProofUtils { + public static async createRequestFromPreview( + options: CreateProposalOptions<[IndyProofFormat]> + ): Promise { + const indyFormat = options.proofFormats?.indy + + if (!indyFormat) { + throw new AriesFrameworkError('No Indy format found.') + } + + const preview = new PresentationPreview({ + attributes: indyFormat.attributes, + predicates: indyFormat.predicates, + }) + + if (!preview) { + throw new AriesFrameworkError(`No preview found`) + } + + const proofRequest = IndyProofUtils.createReferentForProofRequest(indyFormat, preview) + + return { + indy: proofRequest, + } + } + + public static createReferentForProofRequest( + indyFormat: IndyProposeProofFormat, + preview: PresentationPreview + ): ProofRequest { + const proofRequest = new ProofRequest({ + name: indyFormat.name, + version: indyFormat.version, + nonce: indyFormat.nonce, + }) + + /** + * Create mapping of attributes by referent. This required the + * attributes to come from the same credential. + * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#referent + * + * { + * "referent1": [Attribute1, Attribute2], + * "referent2": [Attribute3] + * } + */ + const attributesByReferent: Record = {} + for (const proposedAttributes of preview.attributes) { + if (!proposedAttributes.referent) proposedAttributes.referent = uuid() + + const referentAttributes = attributesByReferent[proposedAttributes.referent] + + // Referent key already exist, add to list + if (referentAttributes) { + referentAttributes.push(proposedAttributes) + } + + // Referent key does not exist yet, create new entry + else { + attributesByReferent[proposedAttributes.referent] = [proposedAttributes] + } + } + + // Transform attributes by referent to requested attributes + for (const [referent, proposedAttributes] of Object.entries(attributesByReferent)) { + // Either attributeName or attributeNames will be undefined + const attributeName = proposedAttributes.length == 1 ? proposedAttributes[0].name : undefined + const attributeNames = proposedAttributes.length > 1 ? proposedAttributes.map((a) => a.name) : undefined + + const requestedAttribute = new ProofAttributeInfo({ + name: attributeName, + names: attributeNames, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: proposedAttributes[0].credentialDefinitionId, + }), + ], + }) + + proofRequest.requestedAttributes.set(referent, requestedAttribute) + } + + // Transform proposed predicates to requested predicates + for (const proposedPredicate of preview.predicates) { + const requestedPredicate = new ProofPredicateInfo({ + name: proposedPredicate.name, + predicateType: proposedPredicate.predicate, + predicateValue: proposedPredicate.threshold, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: proposedPredicate.credentialDefinitionId, + }), + ], + }) + + proofRequest.requestedPredicates.set(uuid(), requestedPredicate) + } + + return proofRequest + } +} diff --git a/packages/core/src/modules/proofs/formats/indy/errors/InvalidEncodedValueError.ts b/packages/core/src/modules/proofs/formats/indy/errors/InvalidEncodedValueError.ts new file mode 100644 index 0000000000..84ac6e1385 --- /dev/null +++ b/packages/core/src/modules/proofs/formats/indy/errors/InvalidEncodedValueError.ts @@ -0,0 +1,3 @@ +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' + +export class InvalidEncodedValueError extends AriesFrameworkError {} diff --git a/packages/core/src/modules/proofs/formats/indy/errors/MissingIndyProofMessageError.ts b/packages/core/src/modules/proofs/formats/indy/errors/MissingIndyProofMessageError.ts new file mode 100644 index 0000000000..2ab9c3f15e --- /dev/null +++ b/packages/core/src/modules/proofs/formats/indy/errors/MissingIndyProofMessageError.ts @@ -0,0 +1,3 @@ +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' + +export class MissingIndyProofMessageError extends AriesFrameworkError {} diff --git a/packages/core/src/modules/proofs/models/AttributeFilter.ts b/packages/core/src/modules/proofs/formats/indy/models/AttributeFilter.ts similarity index 98% rename from packages/core/src/modules/proofs/models/AttributeFilter.ts rename to packages/core/src/modules/proofs/formats/indy/models/AttributeFilter.ts index 90b628799e..b2a804ab2d 100644 --- a/packages/core/src/modules/proofs/models/AttributeFilter.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/AttributeFilter.ts @@ -1,7 +1,7 @@ import { Expose, Transform, TransformationType, Type } from 'class-transformer' import { IsInstance, IsOptional, IsString, Matches, ValidateNested } from 'class-validator' -import { credDefIdRegex, indyDidRegex, schemaIdRegex, schemaVersionRegex } from '../../../utils' +import { credDefIdRegex, indyDidRegex, schemaIdRegex, schemaVersionRegex } from '../../../../../utils/regex' export class AttributeValue { public constructor(options: AttributeValue) { diff --git a/packages/core/src/modules/proofs/models/PredicateType.ts b/packages/core/src/modules/proofs/formats/indy/models/PredicateType.ts similarity index 100% rename from packages/core/src/modules/proofs/models/PredicateType.ts rename to packages/core/src/modules/proofs/formats/indy/models/PredicateType.ts diff --git a/packages/core/src/modules/proofs/models/ProofAttributeInfo.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts similarity index 84% rename from packages/core/src/modules/proofs/models/ProofAttributeInfo.ts rename to packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts index bc2edd724b..4bf1f136b0 100644 --- a/packages/core/src/modules/proofs/models/ProofAttributeInfo.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts @@ -1,7 +1,7 @@ import { Expose, Type } from 'class-transformer' -import { IsString, IsOptional, IsArray, ValidateNested, IsInstance, ValidateIf, ArrayNotEmpty } from 'class-validator' +import { ArrayNotEmpty, IsArray, IsInstance, IsOptional, IsString, ValidateIf, ValidateNested } from 'class-validator' -import { IndyRevocationInterval } from '../../credentials' +import { IndyRevocationInterval } from '../../../../credentials' import { AttributeFilter } from './AttributeFilter' diff --git a/packages/core/src/modules/proofs/models/ProofPredicateInfo.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts similarity index 94% rename from packages/core/src/modules/proofs/models/ProofPredicateInfo.ts rename to packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts index 00aa1f310b..8f246746bf 100644 --- a/packages/core/src/modules/proofs/models/ProofPredicateInfo.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts @@ -1,7 +1,7 @@ import { Expose, Type } from 'class-transformer' import { IsArray, IsEnum, IsInstance, IsInt, IsOptional, IsString, ValidateNested } from 'class-validator' -import { IndyRevocationInterval } from '../../credentials' +import { IndyRevocationInterval } from '../../../../credentials' import { AttributeFilter } from './AttributeFilter' import { PredicateType } from './PredicateType' diff --git a/packages/core/src/modules/proofs/models/ProofRequest.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts similarity index 90% rename from packages/core/src/modules/proofs/models/ProofRequest.ts rename to packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts index 17d92f8f35..224169c864 100644 --- a/packages/core/src/modules/proofs/models/ProofRequest.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts @@ -1,11 +1,11 @@ import type { IndyProofRequest } from 'indy-sdk' import { Expose, Type } from 'class-transformer' -import { IsString, ValidateNested, IsOptional, IsIn, IsInstance } from 'class-validator' +import { IsIn, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' -import { JsonTransformer } from '../../../utils/JsonTransformer' -import { IsMap } from '../../../utils/transformers' -import { IndyRevocationInterval } from '../../credentials' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { IsMap } from '../../../../../utils/transformers' +import { IndyRevocationInterval } from '../../../../credentials' import { ProofAttributeInfo } from './ProofAttributeInfo' import { ProofPredicateInfo } from './ProofPredicateInfo' diff --git a/packages/core/src/modules/proofs/models/RequestedAttribute.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts similarity index 89% rename from packages/core/src/modules/proofs/models/RequestedAttribute.ts rename to packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts index 4998a8b097..048a89cf82 100644 --- a/packages/core/src/modules/proofs/models/RequestedAttribute.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts @@ -1,7 +1,7 @@ import { Exclude, Expose } from 'class-transformer' import { IsBoolean, IsInt, IsOptional, IsString } from 'class-validator' -import { IndyCredentialInfo } from '../../credentials' +import { IndyCredentialInfo } from '../../../../credentials/formats/indy/models/IndyCredentialInfo' /** * Requested Attribute for Indy proof creation diff --git a/packages/core/src/modules/proofs/models/RequestedCredentials.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts similarity index 87% rename from packages/core/src/modules/proofs/models/RequestedCredentials.ts rename to packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts index 5d15cae028..b2824bf7bd 100644 --- a/packages/core/src/modules/proofs/models/RequestedCredentials.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts @@ -3,13 +3,13 @@ import type { IndyRequestedCredentials } from 'indy-sdk' import { Expose } from 'class-transformer' import { ValidateNested } from 'class-validator' -import { JsonTransformer } from '../../../utils/JsonTransformer' -import { RecordTransformer } from '../../../utils/transformers' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { RecordTransformer } from '../../../../../utils/transformers' import { RequestedAttribute } from './RequestedAttribute' import { RequestedPredicate } from './RequestedPredicate' -interface RequestedCredentialsOptions { +export interface IndyRequestedCredentialsOptions { requestedAttributes?: Record requestedPredicates?: Record selfAttestedAttributes?: Record @@ -21,7 +21,7 @@ interface RequestedCredentialsOptions { * @see https://github.com/hyperledger/indy-sdk/blob/57dcdae74164d1c7aa06f2cccecaae121cefac25/libindy/src/api/anoncreds.rs#L1433-L1445 */ export class RequestedCredentials { - public constructor(options: RequestedCredentialsOptions = {}) { + public constructor(options: IndyRequestedCredentialsOptions = {}) { if (options) { this.requestedAttributes = options.requestedAttributes ?? {} this.requestedPredicates = options.requestedPredicates ?? {} diff --git a/packages/core/src/modules/proofs/models/RequestedPredicate.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts similarity index 92% rename from packages/core/src/modules/proofs/models/RequestedPredicate.ts rename to packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts index 5e7d4dc5f9..9109b51a4d 100644 --- a/packages/core/src/modules/proofs/models/RequestedPredicate.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts @@ -1,7 +1,7 @@ import { Exclude, Expose } from 'class-transformer' import { IsInt, IsOptional, IsString } from 'class-validator' -import { IndyCredentialInfo } from '../../credentials' +import { IndyCredentialInfo } from '../../../../credentials' /** * Requested Predicate for Indy proof creation diff --git a/packages/core/src/modules/proofs/models/RetrievedCredentials.ts b/packages/core/src/modules/proofs/formats/indy/models/RetrievedCredentials.ts similarity index 100% rename from packages/core/src/modules/proofs/models/RetrievedCredentials.ts rename to packages/core/src/modules/proofs/formats/indy/models/RetrievedCredentials.ts diff --git a/packages/core/src/modules/proofs/formats/indy/models/index.ts b/packages/core/src/modules/proofs/formats/indy/models/index.ts new file mode 100644 index 0000000000..b38776d360 --- /dev/null +++ b/packages/core/src/modules/proofs/formats/indy/models/index.ts @@ -0,0 +1,7 @@ +export * from './ProofAttributeInfo' +export * from './ProofPredicateInfo' +export * from './RequestedAttribute' +export * from './RequestedPredicate' +export * from './ProofRequest' +export * from './AttributeFilter' +export * from './PredicateType' diff --git a/packages/core/src/modules/proofs/formats/models/ProofAttachmentFormat.ts b/packages/core/src/modules/proofs/formats/models/ProofAttachmentFormat.ts new file mode 100644 index 0000000000..5bc2fc881b --- /dev/null +++ b/packages/core/src/modules/proofs/formats/models/ProofAttachmentFormat.ts @@ -0,0 +1,7 @@ +import type { Attachment } from '../../../../decorators/attachment/Attachment' +import type { ProofFormatSpec } from '../../models/ProofFormatSpec' + +export interface ProofAttachmentFormat { + format: ProofFormatSpec + attachment: Attachment +} diff --git a/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts b/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts new file mode 100644 index 0000000000..8212d2b6dd --- /dev/null +++ b/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts @@ -0,0 +1,64 @@ +import type { Attachment } from '../../../../decorators/attachment/Attachment' +import type { ProposeProofFormats } from '../../models/SharedOptions' +import type { ProofRecord } from '../../repository' +import type { ProofFormat, ProofFormatPayload } from '../ProofFormat' +import type { ProofRequestOptions } from '../indy/models/ProofRequest' +import type { ProofAttachmentFormat } from './ProofAttachmentFormat' + +export interface CreateRequestAttachmentOptions { + id?: string + proofRequestOptions: ProofRequestOptions +} + +export interface CreateProofAttachmentOptions { + id?: string + proofProposalOptions: ProofRequestOptions +} + +export interface CreateProposalOptions { + id?: string + formats: ProposeProofFormats +} + +export interface ProcessProposalOptions { + proposal: ProofAttachmentFormat + record?: ProofRecord +} + +export interface CreateRequestOptions { + id?: string + formats: ProposeProofFormats +} + +export interface ProcessRequestOptions { + requestAttachment: ProofAttachmentFormat + record?: ProofRecord +} + +export interface FormatCreatePresentationOptions { + id?: string + attachment: Attachment + proofFormats: ProofFormatPayload<[PF], 'createPresentation'> +} + +export interface ProcessPresentationOptions { + record: ProofRecord + formatAttachments: { + request: ProofAttachmentFormat[] + presentation: ProofAttachmentFormat[] + } +} + +export interface VerifyProofOptions { + request: Attachment + proof: Attachment +} + +export interface CreateProblemReportOptions { + proofRecord: ProofRecord + description: string +} + +export interface CreatePresentationFormatsOptions { + presentationAttachment: Attachment +} diff --git a/packages/core/src/modules/proofs/handlers/PresentationAckHandler.ts b/packages/core/src/modules/proofs/handlers/PresentationAckHandler.ts deleted file mode 100644 index fa7b194df6..0000000000 --- a/packages/core/src/modules/proofs/handlers/PresentationAckHandler.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' -import type { ProofService } from '../services' - -import { PresentationAckMessage } from '../messages' - -export class PresentationAckHandler implements Handler { - private proofService: ProofService - public supportedMessages = [PresentationAckMessage] - - public constructor(proofService: ProofService) { - this.proofService = proofService - } - - public async handle(messageContext: HandlerInboundMessage) { - await this.proofService.processAck(messageContext) - } -} diff --git a/packages/core/src/modules/proofs/handlers/PresentationHandler.ts b/packages/core/src/modules/proofs/handlers/PresentationHandler.ts deleted file mode 100644 index 8f651a9562..0000000000 --- a/packages/core/src/modules/proofs/handlers/PresentationHandler.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' -import type { Logger } from '../../../logger' -import type { ProofResponseCoordinator } from '../ProofResponseCoordinator' -import type { ProofRecord } from '../repository' -import type { ProofService } from '../services' - -import { createOutboundMessage, createOutboundServiceMessage } from '../../../agent/helpers' -import { PresentationMessage } from '../messages' - -export class PresentationHandler implements Handler { - private proofService: ProofService - private proofResponseCoordinator: ProofResponseCoordinator - private logger: Logger - public supportedMessages = [PresentationMessage] - - public constructor(proofService: ProofService, proofResponseCoordinator: ProofResponseCoordinator, logger: Logger) { - this.proofService = proofService - this.proofResponseCoordinator = proofResponseCoordinator - this.logger = logger - } - - public async handle(messageContext: HandlerInboundMessage) { - const proofRecord = await this.proofService.processPresentation(messageContext) - - if (this.proofResponseCoordinator.shouldAutoRespondToPresentation(messageContext.agentContext, proofRecord)) { - return await this.createAck(proofRecord, messageContext) - } - } - - private async createAck(record: ProofRecord, messageContext: HandlerInboundMessage) { - this.logger.info(`Automatically sending acknowledgement with autoAccept`) - - const { message, proofRecord } = await this.proofService.createAck(messageContext.agentContext, record) - - if (messageContext.connection) { - return createOutboundMessage(messageContext.connection, message) - } else if (proofRecord.requestMessage?.service && proofRecord.presentationMessage?.service) { - const recipientService = proofRecord.presentationMessage?.service - const ourService = proofRecord.requestMessage?.service - - return createOutboundServiceMessage({ - payload: message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - }) - } - - this.logger.error(`Could not automatically create presentation ack`) - } -} diff --git a/packages/core/src/modules/proofs/handlers/PresentationProblemReportHandler.ts b/packages/core/src/modules/proofs/handlers/PresentationProblemReportHandler.ts deleted file mode 100644 index 925941e3a4..0000000000 --- a/packages/core/src/modules/proofs/handlers/PresentationProblemReportHandler.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' -import type { ProofService } from '../services' - -import { PresentationProblemReportMessage } from '../messages' - -export class PresentationProblemReportHandler implements Handler { - private proofService: ProofService - public supportedMessages = [PresentationProblemReportMessage] - - public constructor(proofService: ProofService) { - this.proofService = proofService - } - - public async handle(messageContext: HandlerInboundMessage) { - await this.proofService.processProblemReport(messageContext) - } -} diff --git a/packages/core/src/modules/proofs/handlers/ProposePresentationHandler.ts b/packages/core/src/modules/proofs/handlers/ProposePresentationHandler.ts deleted file mode 100644 index 3e07ebfc60..0000000000 --- a/packages/core/src/modules/proofs/handlers/ProposePresentationHandler.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' -import type { Logger } from '../../../logger' -import type { ProofResponseCoordinator } from '../ProofResponseCoordinator' -import type { ProofRecord } from '../repository' -import type { ProofService } from '../services' - -import { createOutboundMessage } from '../../../agent/helpers' -import { ProposePresentationMessage } from '../messages' - -export class ProposePresentationHandler implements Handler { - private proofService: ProofService - private proofResponseCoordinator: ProofResponseCoordinator - private logger: Logger - public supportedMessages = [ProposePresentationMessage] - - public constructor(proofService: ProofService, proofResponseCoordinator: ProofResponseCoordinator, logger: Logger) { - this.proofService = proofService - this.proofResponseCoordinator = proofResponseCoordinator - this.logger = logger - } - - public async handle(messageContext: HandlerInboundMessage) { - const proofRecord = await this.proofService.processProposal(messageContext) - - if (this.proofResponseCoordinator.shouldAutoRespondToProposal(messageContext.agentContext, proofRecord)) { - return await this.createRequest(proofRecord, messageContext) - } - } - - private async createRequest( - proofRecord: ProofRecord, - messageContext: HandlerInboundMessage - ) { - this.logger.info(`Automatically sending request with autoAccept`) - - if (!messageContext.connection) { - this.logger.error('No connection on the messageContext') - return - } - if (!proofRecord.proposalMessage) { - this.logger.error(`Proof record with id ${proofRecord.id} is missing required credential proposal`) - return - } - const proofRequest = await this.proofService.createProofRequestFromProposal( - messageContext.agentContext, - proofRecord.proposalMessage.presentationProposal, - { - name: 'proof-request', - version: '1.0', - } - ) - - const { message } = await this.proofService.createRequestAsResponse( - messageContext.agentContext, - proofRecord, - proofRequest - ) - - return createOutboundMessage(messageContext.connection, message) - } -} diff --git a/packages/core/src/modules/proofs/handlers/RequestPresentationHandler.ts b/packages/core/src/modules/proofs/handlers/RequestPresentationHandler.ts deleted file mode 100644 index e2839783c8..0000000000 --- a/packages/core/src/modules/proofs/handlers/RequestPresentationHandler.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' -import type { Logger } from '../../../logger' -import type { RoutingService } from '../../routing/services/RoutingService' -import type { ProofResponseCoordinator } from '../ProofResponseCoordinator' -import type { ProofRecord } from '../repository' -import type { ProofService } from '../services' - -import { createOutboundMessage, createOutboundServiceMessage } from '../../../agent/helpers' -import { ServiceDecorator } from '../../../decorators/service/ServiceDecorator' -import { RequestPresentationMessage } from '../messages' - -export class RequestPresentationHandler implements Handler { - private proofService: ProofService - private proofResponseCoordinator: ProofResponseCoordinator - private routingService: RoutingService - private logger: Logger - public supportedMessages = [RequestPresentationMessage] - - public constructor( - proofService: ProofService, - proofResponseCoordinator: ProofResponseCoordinator, - routingService: RoutingService, - logger: Logger - ) { - this.proofService = proofService - this.proofResponseCoordinator = proofResponseCoordinator - this.routingService = routingService - this.logger = logger - } - - public async handle(messageContext: HandlerInboundMessage) { - const proofRecord = await this.proofService.processRequest(messageContext) - - if (this.proofResponseCoordinator.shouldAutoRespondToRequest(messageContext.agentContext, proofRecord)) { - return await this.createPresentation(proofRecord, messageContext) - } - } - - private async createPresentation( - record: ProofRecord, - messageContext: HandlerInboundMessage - ) { - const indyProofRequest = record.requestMessage?.indyProofRequest - const presentationProposal = record.proposalMessage?.presentationProposal - - this.logger.info(`Automatically sending presentation with autoAccept`) - - if (!indyProofRequest) { - this.logger.error('Proof request is undefined.') - return - } - - const retrievedCredentials = await this.proofService.getRequestedCredentialsForProofRequest( - messageContext.agentContext, - indyProofRequest, - { - presentationProposal, - } - ) - - const requestedCredentials = this.proofService.autoSelectCredentialsForProofRequest(retrievedCredentials) - - const { message, proofRecord } = await this.proofService.createPresentation( - messageContext.agentContext, - record, - requestedCredentials - ) - - if (messageContext.connection) { - return createOutboundMessage(messageContext.connection, message) - } else if (proofRecord.requestMessage?.service) { - // Create ~service decorator - const routing = await this.routingService.getRouting(messageContext.agentContext) - const ourService = new ServiceDecorator({ - serviceEndpoint: routing.endpoints[0], - recipientKeys: [routing.recipientKey.publicKeyBase58], - routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), - }) - - const recipientService = proofRecord.requestMessage.service - - // Set and save ~service decorator to record (to remember our verkey) - message.service = ourService - proofRecord.presentationMessage = message - await this.proofService.update(messageContext.agentContext, proofRecord) - - return createOutboundServiceMessage({ - payload: message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - }) - } - - this.logger.error(`Could not automatically create presentation`) - } -} diff --git a/packages/core/src/modules/proofs/handlers/index.ts b/packages/core/src/modules/proofs/handlers/index.ts deleted file mode 100644 index ba30911942..0000000000 --- a/packages/core/src/modules/proofs/handlers/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './PresentationAckHandler' -export * from './PresentationHandler' -export * from './ProposePresentationHandler' -export * from './RequestPresentationHandler' -export * from './PresentationProblemReportHandler' diff --git a/packages/core/src/modules/proofs/index.ts b/packages/core/src/modules/proofs/index.ts index 44efac8eba..7660d50fa2 100644 --- a/packages/core/src/modules/proofs/index.ts +++ b/packages/core/src/modules/proofs/index.ts @@ -1,9 +1,10 @@ -export * from './messages' +export * from './protocol/v1/messages' +export * from './protocol/v1/models' +export * from './protocol/v2/messages' +export * from './ProofService' export * from './models' -export * from './services' -export * from './ProofState' export * from './repository' export * from './ProofEvents' -export * from './ProofsApi' -export * from './ProofAutoAcceptType' +export * from './formats/indy/models' +export * from './formats/indy/IndyProofUtils' export * from './ProofsModule' diff --git a/packages/core/src/modules/proofs/messages/PresentationAckMessage.ts b/packages/core/src/modules/proofs/messages/PresentationAckMessage.ts index 12d405f6dc..64e60f56b2 100644 --- a/packages/core/src/modules/proofs/messages/PresentationAckMessage.ts +++ b/packages/core/src/modules/proofs/messages/PresentationAckMessage.ts @@ -1,19 +1,10 @@ +import type { ProtocolVersion } from '../../../types' import type { AckMessageOptions } from '../../common' -import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' -import { AckMessage } from '../../common' - export type PresentationAckMessageOptions = AckMessageOptions -/** - * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0015-acks/README.md#explicit-acks - */ -export class PresentationAckMessage extends AckMessage { - public constructor(options: PresentationAckMessageOptions) { - super(options) - } +type PresentationAckMessageType = `https://didcomm.org/present-proof/${ProtocolVersion}/ack` - @IsValidMessageType(PresentationAckMessage.type) - public readonly type = PresentationAckMessage.type.messageTypeUri - public static readonly type = parseMessageType('https://didcomm.org/present-proof/1.0/ack') +export interface PresentationAckMessage { + type: PresentationAckMessageType } diff --git a/packages/core/src/modules/proofs/messages/PresentationProblemReportMessage.ts b/packages/core/src/modules/proofs/messages/PresentationProblemReportMessage.ts deleted file mode 100644 index 2d62a6e2b9..0000000000 --- a/packages/core/src/modules/proofs/messages/PresentationProblemReportMessage.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { ProblemReportMessageOptions } from '../../problem-reports/messages/ProblemReportMessage' - -import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' -import { ProblemReportMessage } from '../../problem-reports/messages/ProblemReportMessage' - -export type PresentationProblemReportMessageOptions = ProblemReportMessageOptions - -/** - * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md - */ -export class PresentationProblemReportMessage extends ProblemReportMessage { - /** - * Create new PresentationProblemReportMessage instance. - * @param options - */ - public constructor(options: PresentationProblemReportMessageOptions) { - super(options) - } - - @IsValidMessageType(PresentationProblemReportMessage.type) - public readonly type = PresentationProblemReportMessage.type.messageTypeUri - public static readonly type = parseMessageType('https://didcomm.org/present-proof/1.0/problem-report') -} diff --git a/packages/core/src/modules/proofs/messages/index.ts b/packages/core/src/modules/proofs/messages/index.ts deleted file mode 100644 index f2ad906c75..0000000000 --- a/packages/core/src/modules/proofs/messages/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './ProposePresentationMessage' -export * from './RequestPresentationMessage' -export * from './PresentationMessage' -export * from './PresentationPreview' -export * from './PresentationAckMessage' -export * from './PresentationProblemReportMessage' diff --git a/packages/core/src/modules/proofs/models/GetRequestedCredentialsConfig.ts b/packages/core/src/modules/proofs/models/GetRequestedCredentialsConfig.ts new file mode 100644 index 0000000000..9041bbabe3 --- /dev/null +++ b/packages/core/src/modules/proofs/models/GetRequestedCredentialsConfig.ts @@ -0,0 +1,19 @@ +export interface GetRequestedCredentialsConfig { + /** + * Whether to filter the retrieved credentials using the presentation preview. + * This configuration will only have effect if a presentation proposal message is available + * containing a presentation preview. + * + * @default false + */ + filterByPresentationPreview?: boolean + + /** + * Whether to filter the retrieved credentials using the non-revocation request in the proof request. + * This configuration will only have effect if the proof request requires proof on non-revocation of any kind. + * Default to true + * + * @default true + */ + filterByNonRevocationRequirements?: boolean +} diff --git a/packages/core/src/modules/proofs/models/ModuleOptions.ts b/packages/core/src/modules/proofs/models/ModuleOptions.ts new file mode 100644 index 0000000000..f60e9d853b --- /dev/null +++ b/packages/core/src/modules/proofs/models/ModuleOptions.ts @@ -0,0 +1,20 @@ +import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' +import type { AutoAcceptProof } from './ProofAutoAcceptType' +import type { ProposeProofFormats } from './SharedOptions' + +export interface ProofConfig { + name: string + version: string +} + +export interface NegotiateRequestOptions { + proofRecordId: string + proofFormats: ProposeProofFormats + comment?: string + autoAcceptProof?: AutoAcceptProof +} + +export interface AutoSelectCredentialsForProofRequestOptions { + proofRecordId: string + config?: GetRequestedCredentialsConfig +} diff --git a/packages/core/src/modules/proofs/ProofAutoAcceptType.ts b/packages/core/src/modules/proofs/models/ProofAutoAcceptType.ts similarity index 100% rename from packages/core/src/modules/proofs/ProofAutoAcceptType.ts rename to packages/core/src/modules/proofs/models/ProofAutoAcceptType.ts diff --git a/packages/core/src/modules/proofs/models/ProofFormatSpec.ts b/packages/core/src/modules/proofs/models/ProofFormatSpec.ts new file mode 100644 index 0000000000..54c0b40f73 --- /dev/null +++ b/packages/core/src/modules/proofs/models/ProofFormatSpec.ts @@ -0,0 +1,25 @@ +import { Expose } from 'class-transformer' +import { IsString } from 'class-validator' + +import { uuid } from '../../../utils/uuid' + +export interface ProofFormatSpecOptions { + attachmentId?: string + format: string +} + +export class ProofFormatSpec { + public constructor(options: ProofFormatSpecOptions) { + if (options) { + this.attachmentId = options.attachmentId ?? uuid() + this.format = options.format + } + } + + @Expose({ name: 'attach_id' }) + @IsString() + public attachmentId!: string + + @IsString() + public format!: string +} diff --git a/packages/core/src/modules/proofs/models/ProofProtocolVersion.ts b/packages/core/src/modules/proofs/models/ProofProtocolVersion.ts new file mode 100644 index 0000000000..6027d21111 --- /dev/null +++ b/packages/core/src/modules/proofs/models/ProofProtocolVersion.ts @@ -0,0 +1,4 @@ +export enum ProofProtocolVersion { + V1 = 'v1', + V2 = 'v2', +} diff --git a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts new file mode 100644 index 0000000000..ed43d6babd --- /dev/null +++ b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts @@ -0,0 +1,73 @@ +import type { ConnectionRecord } from '../../connections' +import type { ProofFormat, ProofFormatPayload } from '../formats/ProofFormat' +import type { ProofRecord } from '../repository' +import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' +import type { AutoAcceptProof } from './ProofAutoAcceptType' + +interface BaseOptions { + willConfirm?: boolean + goalCode?: string + comment?: string + autoAcceptProof?: AutoAcceptProof +} + +export interface CreateProposalOptions extends BaseOptions { + connectionRecord: ConnectionRecord + proofFormats: ProofFormatPayload + parentThreadId?: string +} + +export interface CreateProposalAsResponseOptions extends BaseOptions { + proofRecord: ProofRecord + proofFormats: ProofFormatPayload +} + +export interface CreateRequestAsResponseOptions extends BaseOptions { + id?: string + proofRecord: ProofRecord + proofFormats: ProofFormatPayload +} + +// ----- Out Of Band Proof ----- // +export interface CreateOutOfBandRequestOptions extends BaseOptions { + proofFormats: ProofFormatPayload +} + +export interface CreateRequestOptions extends BaseOptions { + connectionRecord?: ConnectionRecord + proofFormats: ProofFormatPayload + parentThreadId?: string +} + +export interface CreateProofRequestFromProposalOptions extends BaseOptions { + id?: string + proofRecord: ProofRecord +} + +export interface FormatRetrievedCredentialOptions { + proofFormats: ProofFormatPayload +} + +export interface FormatRequestedCredentialReturn { + proofFormats: ProofFormatPayload +} + +export interface CreatePresentationOptions extends BaseOptions { + proofRecord: ProofRecord + proofFormats: ProofFormatPayload // + lastPresentation?: boolean +} + +export interface CreateAckOptions { + proofRecord: ProofRecord +} + +export interface GetRequestedCredentialsForProofRequestOptions { + proofRecord: ProofRecord + config?: GetRequestedCredentialsConfig +} + +export interface ProofRequestFromProposalOptions { + proofRecord: ProofRecord + proofFormats: ProofFormatPayload +} diff --git a/packages/core/src/modules/proofs/ProofState.ts b/packages/core/src/modules/proofs/models/ProofState.ts similarity index 94% rename from packages/core/src/modules/proofs/ProofState.ts rename to packages/core/src/modules/proofs/models/ProofState.ts index 73869e80aa..e10b5d1ff8 100644 --- a/packages/core/src/modules/proofs/ProofState.ts +++ b/packages/core/src/modules/proofs/models/ProofState.ts @@ -11,5 +11,6 @@ export enum ProofState { PresentationSent = 'presentation-sent', PresentationReceived = 'presentation-received', Declined = 'declined', + Abandoned = 'abandoned', Done = 'done', } diff --git a/packages/core/src/modules/proofs/models/SharedOptions.ts b/packages/core/src/modules/proofs/models/SharedOptions.ts new file mode 100644 index 0000000000..e479dea456 --- /dev/null +++ b/packages/core/src/modules/proofs/models/SharedOptions.ts @@ -0,0 +1,62 @@ +import type { IndyProposeProofFormat } from '../formats/indy/IndyProofFormat' +import type { IndyRequestProofFormat, IndyVerifyProofFormat } from '../formats/indy/IndyProofFormatsServiceOptions' +import type { ProofRequest } from '../formats/indy/models/ProofRequest' +import type { IndyRequestedCredentialsOptions } from '../formats/indy/models/RequestedCredentials' +import type { RetrievedCredentials } from '../formats/indy/models/RetrievedCredentials' +import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' + +export interface ProposeProofFormats { + // If you want to propose an indy proof without attributes or + // any of the other properties you should pass an empty object + indy?: IndyProposeProofFormat + presentationExchange?: never +} + +export interface RequestProofFormats { + indy?: IndyRequestProofFormat + presentationExchange?: never +} + +export interface CreatePresentationFormats { + indy?: IndyRequestedCredentialsOptions + presentationExchange?: never +} + +export interface AcceptProposalFormats { + indy?: IndyAcceptProposalOptions + presentationExchange?: never +} + +export interface VerifyProofFormats { + indy?: IndyVerifyProofFormat + presentationExchange?: never +} + +export interface RequestedCredentialConfigOptions { + indy?: GetRequestedCredentialsConfig + presentationExchange?: never +} + +// export interface RetrievedCredentialOptions { +// indy?: RetrievedCredentials +// presentationExchange?: undefined +// } + +export interface ProofRequestFormats { + indy?: ProofRequest + presentationExchange?: undefined +} + +// export interface RequestedCredentialsFormats { +// indy?: RequestedCredentials +// presentationExchange?: undefined +// } + +interface IndyAcceptProposalOptions { + request: ProofRequest +} + +export interface AutoSelectCredentialOptions { + indy?: RetrievedCredentials + presentationExchange?: undefined +} diff --git a/packages/core/src/modules/proofs/models/index.ts b/packages/core/src/modules/proofs/models/index.ts index d313158b63..827448009e 100644 --- a/packages/core/src/modules/proofs/models/index.ts +++ b/packages/core/src/modules/proofs/models/index.ts @@ -1,13 +1,4 @@ -export * from './AttributeFilter' -export * from './PartialProof' -export * from './PredicateType' -export * from './ProofAttribute' -export * from './ProofAttributeInfo' -export * from './ProofIdentifier' -export * from './ProofPredicateInfo' -export * from './ProofRequest' -export * from './RequestedAttribute' -export * from './RequestedCredentials' -export * from './RequestedPredicate' -export * from './RequestedProof' -export * from './RetrievedCredentials' +export * from './GetRequestedCredentialsConfig' +export * from './ProofAutoAcceptType' +export * from './ProofProtocolVersion' +export * from './ProofState' diff --git a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts new file mode 100644 index 0000000000..a6aa201594 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts @@ -0,0 +1,1046 @@ +import type { AgentContext } from '../../../../agent' +import type { AgentMessage } from '../../../../agent/AgentMessage' +import type { Dispatcher } from '../../../../agent/Dispatcher' +import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { Attachment } from '../../../../decorators/attachment/Attachment' +import type { MediationRecipientService } from '../../../routing/services/MediationRecipientService' +import type { RoutingService } from '../../../routing/services/RoutingService' +import type { ProofResponseCoordinator } from '../../ProofResponseCoordinator' +import type { ProofFormat } from '../../formats/ProofFormat' +import type { ProofFormatService } from '../../formats/ProofFormatService' +import type { IndyProofFormat, IndyProposeProofFormat } from '../../formats/indy/IndyProofFormat' +import type { ProofAttributeInfo } from '../../formats/indy/models' +import type { + CreateProblemReportOptions, + FormatCreatePresentationOptions, +} from '../../formats/models/ProofFormatServiceOptions' +import type { + CreateAckOptions, + CreatePresentationOptions, + CreateProofRequestFromProposalOptions, + CreateProposalAsResponseOptions, + CreateProposalOptions, + CreateRequestAsResponseOptions, + CreateRequestOptions, + FormatRequestedCredentialReturn, + FormatRetrievedCredentialOptions, + GetRequestedCredentialsForProofRequestOptions, + ProofRequestFromProposalOptions, +} from '../../models/ProofServiceOptions' + +import { validateOrReject } from 'class-validator' +import { inject, Lifecycle, scoped } from 'tsyringe' + +import { AgentConfig } from '../../../../agent/AgentConfig' +import { EventEmitter } from '../../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../../constants' +import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' +import { DidCommMessageRole } from '../../../../storage' +import { DidCommMessageRepository } from '../../../../storage/didcomm/DidCommMessageRepository' +import { checkProofRequestForDuplicates } from '../../../../utils' +import { JsonTransformer } from '../../../../utils/JsonTransformer' +import { MessageValidator } from '../../../../utils/MessageValidator' +import { Wallet } from '../../../../wallet' +import { AckStatus } from '../../../common/messages/AckMessage' +import { ConnectionService } from '../../../connections' +import { CredentialRepository } from '../../../credentials' +import { IndyCredentialInfo } from '../../../credentials/formats/indy/models/IndyCredentialInfo' +import { IndyHolderService, IndyRevocationService } from '../../../indy' +import { IndyLedgerService } from '../../../ledger/services/IndyLedgerService' +import { ProofService } from '../../ProofService' +import { PresentationProblemReportReason } from '../../errors/PresentationProblemReportReason' +import { IndyProofFormatService } from '../../formats/indy/IndyProofFormatService' +import { IndyProofUtils } from '../../formats/indy/IndyProofUtils' +import { ProofRequest } from '../../formats/indy/models/ProofRequest' +import { RequestedCredentials } from '../../formats/indy/models/RequestedCredentials' +import { ProofProtocolVersion } from '../../models/ProofProtocolVersion' +import { ProofState } from '../../models/ProofState' +import { ProofRecord } from '../../repository/ProofRecord' +import { ProofRepository } from '../../repository/ProofRepository' + +import { V1PresentationProblemReportError } from './errors' +import { + V1PresentationAckHandler, + V1PresentationHandler, + V1PresentationProblemReportHandler, + V1ProposePresentationHandler, + V1RequestPresentationHandler, +} from './handlers' +import { + INDY_PROOF_ATTACHMENT_ID, + INDY_PROOF_REQUEST_ATTACHMENT_ID, + V1PresentationAckMessage, + V1PresentationMessage, + V1ProposePresentationMessage, + V1RequestPresentationMessage, +} from './messages' +import { V1PresentationProblemReportMessage } from './messages/V1PresentationProblemReportMessage' +import { PresentationPreview } from './models/V1PresentationPreview' + +/** + * @todo add method to check if request matches proposal. Useful to see if a request I received is the same as the proposal I sent. + * @todo add method to reject / revoke messages + * @todo validate attachments / messages + */ +@scoped(Lifecycle.ContainerScoped) +export class V1ProofService extends ProofService<[IndyProofFormat]> { + private credentialRepository: CredentialRepository + private ledgerService: IndyLedgerService + private indyHolderService: IndyHolderService + private indyRevocationService: IndyRevocationService + private indyProofFormatService: ProofFormatService + + public constructor( + proofRepository: ProofRepository, + didCommMessageRepository: DidCommMessageRepository, + ledgerService: IndyLedgerService, + @inject(InjectionSymbols.Wallet) wallet: Wallet, + agentConfig: AgentConfig, + connectionService: ConnectionService, + eventEmitter: EventEmitter, + credentialRepository: CredentialRepository, + formatService: IndyProofFormatService, + indyHolderService: IndyHolderService, + indyRevocationService: IndyRevocationService + ) { + super(agentConfig, proofRepository, connectionService, didCommMessageRepository, wallet, eventEmitter) + this.credentialRepository = credentialRepository + this.ledgerService = ledgerService + this.wallet = wallet + this.indyProofFormatService = formatService + this.indyHolderService = indyHolderService + this.indyRevocationService = indyRevocationService + } + + public readonly version = 'v1' as const + + public async createProposal( + agentContext: AgentContext, + options: CreateProposalOptions<[IndyProofFormat]> + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + const { connectionRecord, proofFormats } = options + + // Assert + connectionRecord.assertReady() + + if (!proofFormats.indy || Object.keys(proofFormats).length !== 1) { + throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') + } + + const presentationProposal = new PresentationPreview({ + attributes: proofFormats.indy?.attributes, + predicates: proofFormats.indy?.predicates, + }) + + // Create message + const proposalMessage = new V1ProposePresentationMessage({ + comment: options?.comment, + presentationProposal, + parentThreadId: options.parentThreadId, + }) + + // Create record + const proofRecord = new ProofRecord({ + connectionId: connectionRecord.id, + threadId: proposalMessage.threadId, + parentThreadId: proposalMessage.thread?.parentThreadId, + state: ProofState.ProposalSent, + autoAcceptProof: options?.autoAcceptProof, + protocolVersion: ProofProtocolVersion.V1, + }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: proposalMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + await this.proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + + return { proofRecord, message: proposalMessage } + } + + public async createProposalAsResponse( + agentContext: AgentContext, + options: CreateProposalAsResponseOptions<[IndyProofFormat]> + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + const { proofRecord, proofFormats, comment } = options + + // Assert + proofRecord.assertState(ProofState.RequestReceived) + + if (!proofFormats.indy || Object.keys(proofFormats).length !== 1) { + throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') + } + + // Create message + const presentationPreview = new PresentationPreview({ + attributes: proofFormats.indy?.attributes, + predicates: proofFormats.indy?.predicates, + }) + + const proposalMessage: V1ProposePresentationMessage = new V1ProposePresentationMessage({ + comment, + presentationProposal: presentationPreview, + }) + + proposalMessage.setThread({ threadId: proofRecord.threadId }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: proposalMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + // Update record + await this.updateState(agentContext, proofRecord, ProofState.ProposalSent) + + return { proofRecord, message: proposalMessage } + } + + public async processProposal( + messageContext: InboundMessageContext + ): Promise { + let proofRecord: ProofRecord + const { message: proposalMessage, connection } = messageContext + + this.logger.debug(`Processing presentation proposal with id ${proposalMessage.id}`) + + try { + // Proof record already exists + proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + proposalMessage.threadId, + connection?.id + ) + + const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + // Assert + proofRecord.assertState(ProofState.RequestSent) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: proposalMessage, + previousSentMessage: requestMessage ?? undefined, + }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + agentMessage: proposalMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + + // Update record + await this.updateState(messageContext.agentContext, proofRecord, ProofState.ProposalReceived) + } catch { + // No proof record exists with thread id + proofRecord = new ProofRecord({ + connectionId: connection?.id, + threadId: proposalMessage.threadId, + parentThreadId: proposalMessage.thread?.parentThreadId, + state: ProofState.ProposalReceived, + protocolVersion: ProofProtocolVersion.V1, + }) + + // Assert + this.connectionService.assertConnectionOrServiceDecorator(messageContext) + + // Save record + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + agentMessage: proposalMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + await this.proofRepository.save(messageContext.agentContext, proofRecord) + + this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) + } + + return proofRecord + } + + public async createRequestAsResponse( + agentContext: AgentContext, + options: CreateRequestAsResponseOptions<[IndyProofFormat]> + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + const { proofRecord, comment, proofFormats } = options + if (!proofFormats.indy) { + throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') + } + + // Assert + proofRecord.assertState(ProofState.ProposalReceived) + + // Create message + const { attachment } = await this.indyProofFormatService.createRequest({ + id: INDY_PROOF_REQUEST_ATTACHMENT_ID, + formats: proofFormats, + }) + + const requestPresentationMessage = new V1RequestPresentationMessage({ + comment, + requestPresentationAttachments: [attachment], + }) + requestPresentationMessage.setThread({ + threadId: proofRecord.threadId, + }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: requestPresentationMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + // Update record + await this.updateState(agentContext, proofRecord, ProofState.RequestSent) + + return { message: requestPresentationMessage, proofRecord } + } + + public async createRequest( + agentContext: AgentContext, + options: CreateRequestOptions<[IndyProofFormat]> + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + this.logger.debug(`Creating proof request`) + + // Assert + if (options.connectionRecord) { + options.connectionRecord.assertReady() + } + + if (!options.proofFormats.indy || Object.keys(options.proofFormats).length !== 1) { + throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') + } + + // Create message + const { attachment } = await this.indyProofFormatService.createRequest({ + id: INDY_PROOF_REQUEST_ATTACHMENT_ID, + formats: options.proofFormats, + }) + + const requestPresentationMessage = new V1RequestPresentationMessage({ + comment: options?.comment, + requestPresentationAttachments: [attachment], + parentThreadId: options.parentThreadId, + }) + + // Create record + const proofRecord = new ProofRecord({ + connectionId: options.connectionRecord?.id, + threadId: requestPresentationMessage.threadId, + parentThreadId: requestPresentationMessage.thread?.parentThreadId, + state: ProofState.RequestSent, + autoAcceptProof: options?.autoAcceptProof, + protocolVersion: ProofProtocolVersion.V1, + }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: requestPresentationMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + await this.proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + + return { message: requestPresentationMessage, proofRecord } + } + + public async processRequest( + messageContext: InboundMessageContext + ): Promise { + let proofRecord: ProofRecord + const { message: proofRequestMessage, connection } = messageContext + + this.logger.debug(`Processing presentation request with id ${proofRequestMessage.id}`) + + const requestAttachments = proofRequestMessage.getAttachmentFormats() + + for (const attachmentFormat of requestAttachments) { + await this.indyProofFormatService.processRequest({ + requestAttachment: attachmentFormat, + }) + } + + const proofRequest = proofRequestMessage.indyProofRequest + + // Assert attachment + if (!proofRequest) { + throw new V1PresentationProblemReportError( + `Missing required base64 or json encoded attachment data for presentation request with thread id ${proofRequestMessage.threadId}`, + { problemCode: PresentationProblemReportReason.Abandoned } + ) + } + await validateOrReject(proofRequest) + + // Assert attribute and predicate (group) names do not match + checkProofRequestForDuplicates(proofRequest) + + this.logger.debug('received proof request', proofRequest) + + try { + // Proof record already exists + proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + proofRequestMessage.threadId, + connection?.id + ) + + const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + // Assert + proofRecord.assertState(ProofState.ProposalSent) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: requestMessage ?? undefined, + previousSentMessage: proposalMessage ?? undefined, + }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + agentMessage: proofRequestMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + + // Update record + await this.updateState(messageContext.agentContext, proofRecord, ProofState.RequestReceived) + } catch { + // No proof record exists with thread id + proofRecord = new ProofRecord({ + connectionId: connection?.id, + threadId: proofRequestMessage.threadId, + parentThreadId: proofRequestMessage.thread?.parentThreadId, + state: ProofState.RequestReceived, + protocolVersion: ProofProtocolVersion.V1, + }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + agentMessage: proofRequestMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + + // Assert + this.connectionService.assertConnectionOrServiceDecorator(messageContext) + + // Save in repository + await this.proofRepository.save(messageContext.agentContext, proofRecord) + this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) + } + + return proofRecord + } + + public async createPresentation( + agentContext: AgentContext, + options: CreatePresentationOptions<[IndyProofFormat]> + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + const { proofRecord, proofFormats } = options + + this.logger.debug(`Creating presentation for proof record with id ${proofRecord.id}`) + + if (!proofFormats.indy || Object.keys(proofFormats).length !== 1) { + throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') + } + + // Assert + proofRecord.assertState(ProofState.RequestReceived) + + const requestMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + const requestAttachment = requestMessage?.indyAttachment + + if (!requestAttachment) { + throw new V1PresentationProblemReportError( + `Missing required base64 or json encoded attachment data for presentation with thread id ${proofRecord.threadId}`, + { problemCode: PresentationProblemReportReason.Abandoned } + ) + } + + const presentationOptions: FormatCreatePresentationOptions = { + id: INDY_PROOF_ATTACHMENT_ID, + attachment: requestAttachment, + proofFormats: proofFormats, + } + + const proof = await this.indyProofFormatService.createPresentation(agentContext, presentationOptions) + + // Extract proof request from attachment + const proofRequestJson = requestAttachment.getDataAsJson() ?? null + const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) + + const requestedCredentials = new RequestedCredentials({ + requestedAttributes: proofFormats.indy?.requestedAttributes, + requestedPredicates: proofFormats.indy?.requestedPredicates, + selfAttestedAttributes: proofFormats.indy?.selfAttestedAttributes, + }) + + // Get the matching attachments to the requested credentials + const linkedAttachments = await this.getRequestedAttachmentsForRequestedCredentials( + agentContext, + proofRequest, + requestedCredentials + ) + + const presentationMessage = new V1PresentationMessage({ + comment: options?.comment, + presentationAttachments: [proof.attachment], + attachments: linkedAttachments, + }) + presentationMessage.setThread({ threadId: proofRecord.threadId }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: presentationMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + // Update record + await this.updateState(agentContext, proofRecord, ProofState.PresentationSent) + + return { message: presentationMessage, proofRecord } + } + + public async processPresentation(messageContext: InboundMessageContext): Promise { + const { message: presentationMessage, connection } = messageContext + + this.logger.debug(`Processing presentation with id ${presentationMessage.id}`) + + const proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + presentationMessage.threadId, + connection?.id + ) + + const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + // Assert + proofRecord.assertState(ProofState.RequestSent) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: proposalMessage ?? undefined, + previousSentMessage: requestMessage ?? undefined, + }) + + try { + const isValid = await this.indyProofFormatService.processPresentation(messageContext.agentContext, { + record: proofRecord, + formatAttachments: { + presentation: presentationMessage.getAttachmentFormats(), + request: requestMessage.getAttachmentFormats(), + }, + }) + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + agentMessage: presentationMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + + // Update record + proofRecord.isVerified = isValid + await this.updateState(messageContext.agentContext, proofRecord, ProofState.PresentationReceived) + } catch (e) { + if (e instanceof AriesFrameworkError) { + throw new V1PresentationProblemReportError(e.message, { + problemCode: PresentationProblemReportReason.Abandoned, + }) + } + throw e + } + + return proofRecord + } + + public async processAck(messageContext: InboundMessageContext): Promise { + const { message: presentationAckMessage, connection } = messageContext + + this.logger.debug(`Processing presentation ack with id ${presentationAckMessage.id}`) + + const proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + presentationAckMessage.threadId, + connection?.id + ) + + const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + const presentationMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1PresentationMessage, + }) + + // Assert + proofRecord.assertState(ProofState.PresentationSent) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: requestMessage ?? undefined, + previousSentMessage: presentationMessage ?? undefined, + }) + + // Update record + await this.updateState(messageContext.agentContext, proofRecord, ProofState.Done) + + return proofRecord + } + + public async createProblemReport( + agentContext: AgentContext, + options: CreateProblemReportOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + const msg = new V1PresentationProblemReportMessage({ + description: { + code: PresentationProblemReportReason.Abandoned, + en: options.description, + }, + }) + + msg.setThread({ + threadId: options.proofRecord.threadId, + parentThreadId: options.proofRecord.parentThreadId, + }) + + return { + proofRecord: options.proofRecord, + message: msg, + } + } + + public async processProblemReport( + messageContext: InboundMessageContext + ): Promise { + const { message: presentationProblemReportMessage } = messageContext + + const connection = messageContext.assertReadyConnection() + + this.logger.debug(`Processing problem report with id ${presentationProblemReportMessage.id}`) + + const proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + presentationProblemReportMessage.threadId, + connection?.id + ) + + proofRecord.errorMessage = `${presentationProblemReportMessage.description.code}: ${presentationProblemReportMessage.description.en}` + await this.updateState(messageContext.agentContext, proofRecord, ProofState.Abandoned) + return proofRecord + } + + public async generateProofRequestNonce() { + return this.wallet.generateNonce() + } + + public async createProofRequestFromProposal( + agentContext: AgentContext, + options: CreateProofRequestFromProposalOptions + ): Promise> { + const proofRecordId = options.proofRecord.id + const proposalMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V1ProposePresentationMessage, + }) + + if (!proposalMessage) { + throw new AriesFrameworkError(`Proof record with id ${proofRecordId} is missing required presentation proposal`) + } + + const indyProposeProofFormat: IndyProposeProofFormat = { + name: 'Proof Request', + version: '1.0', + nonce: await this.generateProofRequestNonce(), + } + + const proofRequest: ProofRequest = IndyProofUtils.createReferentForProofRequest( + indyProposeProofFormat, + proposalMessage.presentationProposal + ) + + return { + proofRecord: options.proofRecord, + proofFormats: { + indy: proofRequest, + }, + } + } + + /** + * Retrieves the linked attachments for an {@link indyProofRequest} + * @param indyProofRequest The proof request for which the linked attachments have to be found + * @param requestedCredentials The requested credentials + * @returns a list of attachments that are linked to the requested credentials + */ + public async getRequestedAttachmentsForRequestedCredentials( + agentContext: AgentContext, + indyProofRequest: ProofRequest, + requestedCredentials: RequestedCredentials + ): Promise { + const attachments: Attachment[] = [] + const credentialIds = new Set() + const requestedAttributesNames: (string | undefined)[] = [] + + // Get the credentialIds if it contains a hashlink + for (const [referent, requestedAttribute] of Object.entries(requestedCredentials.requestedAttributes)) { + // Find the requested Attributes + const requestedAttributes = indyProofRequest.requestedAttributes.get(referent) as ProofAttributeInfo + + // List the requested attributes + requestedAttributesNames.push(...(requestedAttributes.names ?? [requestedAttributes.name])) + + //Get credentialInfo + if (!requestedAttribute.credentialInfo) { + const indyCredentialInfo = await this.indyHolderService.getCredential( + agentContext, + requestedAttribute.credentialId + ) + requestedAttribute.credentialInfo = JsonTransformer.fromJSON(indyCredentialInfo, IndyCredentialInfo) + } + + // Find the attributes that have a hashlink as a value + for (const attribute of Object.values(requestedAttribute.credentialInfo.attributes)) { + if (attribute.toLowerCase().startsWith('hl:')) { + credentialIds.add(requestedAttribute.credentialId) + } + } + } + + // Only continues if there is an attribute value that contains a hashlink + for (const credentialId of credentialIds) { + // Get the credentialRecord that matches the ID + + const credentialRecord = await this.credentialRepository.getSingleByQuery(agentContext, { + credentialIds: [credentialId], + }) + + if (credentialRecord.linkedAttachments) { + // Get the credentials that have a hashlink as value and are requested + const requestedCredentials = credentialRecord.credentialAttributes?.filter( + (credential) => + credential.value.toLowerCase().startsWith('hl:') && requestedAttributesNames.includes(credential.name) + ) + + // Get the linked attachments that match the requestedCredentials + const linkedAttachments = credentialRecord.linkedAttachments.filter((attachment) => + requestedCredentials?.map((credential) => credential.value.split(':')[1]).includes(attachment.id) + ) + + if (linkedAttachments) { + attachments.push(...linkedAttachments) + } + } + } + + return attachments.length ? attachments : undefined + } + + public async shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofRecord): Promise { + const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + if (!proposal) { + return false + } + await MessageValidator.validateSync(proposal) + + // check the proposal against a possible previous request + const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + if (!request) { + return false + } + + const proofRequest = request.indyProofRequest + + if (!proofRequest) { + throw new V1PresentationProblemReportError( + `Missing required base64 or json encoded attachment data for presentation request with thread id ${request.threadId}`, + { problemCode: PresentationProblemReportReason.Abandoned } + ) + } + await validateOrReject(proofRequest) + + // Assert attribute and predicate (group) names do not match + checkProofRequestForDuplicates(proofRequest) + + const proposalAttributes = proposal.presentationProposal.attributes + const requestedAttributes = proofRequest.requestedAttributes + + const proposedAttributeNames = proposalAttributes.map((x) => x.name) + let requestedAttributeNames: string[] = [] + + const requestedAttributeList = Array.from(requestedAttributes.values()) + + requestedAttributeList.forEach((x) => { + if (x.name) { + requestedAttributeNames.push(x.name) + } else if (x.names) { + requestedAttributeNames = requestedAttributeNames.concat(x.names) + } + }) + + if (requestedAttributeNames.length > proposedAttributeNames.length) { + // more attributes are requested than have been proposed + return false + } + + requestedAttributeNames.forEach((x) => { + if (!proposedAttributeNames.includes(x)) { + this.logger.debug(`Attribute ${x} was requested but wasn't proposed.`) + return false + } + }) + + // assert that all requested attributes are provided + const providedPredicateNames = proposal.presentationProposal.predicates.map((x) => x.name) + proofRequest.requestedPredicates.forEach((x) => { + if (!providedPredicateNames.includes(x.name)) { + return false + } + }) + return true + } + + public async shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofRecord): Promise { + const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + if (!proposal) { + return false + } + + const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + if (!request) { + throw new AriesFrameworkError(`Expected to find a request message for ProofRecord with id ${proofRecord.id}`) + } + + const proofRequest = request.indyProofRequest + + // Assert attachment + if (!proofRequest) { + throw new V1PresentationProblemReportError( + `Missing required base64 or json encoded attachment data for presentation request with thread id ${request.threadId}`, + { problemCode: PresentationProblemReportReason.Abandoned } + ) + } + await validateOrReject(proofRequest) + + // Assert attribute and predicate (group) names do not match + checkProofRequestForDuplicates(proofRequest) + + const proposalAttributes = proposal.presentationProposal.attributes + const requestedAttributes = proofRequest.requestedAttributes + + const proposedAttributeNames = proposalAttributes.map((x) => x.name) + let requestedAttributeNames: string[] = [] + + const requestedAttributeList = Array.from(requestedAttributes.values()) + + requestedAttributeList.forEach((x) => { + if (x.name) { + requestedAttributeNames.push(x.name) + } else if (x.names) { + requestedAttributeNames = requestedAttributeNames.concat(x.names) + } + }) + + if (requestedAttributeNames.length > proposedAttributeNames.length) { + // more attributes are requested than have been proposed + return false + } + + requestedAttributeNames.forEach((x) => { + if (!proposedAttributeNames.includes(x)) { + this.logger.debug(`Attribute ${x} was requested but wasn't proposed.`) + return false + } + }) + + // assert that all requested attributes are provided + const providedPredicateNames = proposal.presentationProposal.predicates.map((x) => x.name) + proofRequest.requestedPredicates.forEach((x) => { + if (!providedPredicateNames.includes(x.name)) { + return false + } + }) + + return true + } + + public async shouldAutoRespondToPresentation(agentContext: AgentContext, proofRecord: ProofRecord): Promise { + this.logger.debug(`Should auto respond to presentation for proof record id: ${proofRecord.id}`) + return true + } + + public async getRequestedCredentialsForProofRequest( + agentContext: AgentContext, + options: GetRequestedCredentialsForProofRequestOptions + ): Promise> { + const requestMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: options.proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + const proposalMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: options.proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + const indyProofRequest = requestMessage?.requestPresentationAttachments + + if (!indyProofRequest) { + throw new AriesFrameworkError('Could not find proof request') + } + + const requestedCredentials: FormatRetrievedCredentialOptions<[IndyProofFormat]> = + await this.indyProofFormatService.getRequestedCredentialsForProofRequest(agentContext, { + attachment: indyProofRequest[0], + presentationProposal: proposalMessage?.presentationProposal, + config: options.config ?? undefined, + }) + return requestedCredentials + } + + public async autoSelectCredentialsForProofRequest( + options: FormatRetrievedCredentialOptions + ): Promise> { + return await this.indyProofFormatService.autoSelectCredentialsForProofRequest(options) + } + + public registerHandlers( + dispatcher: Dispatcher, + agentConfig: AgentConfig, + proofResponseCoordinator: ProofResponseCoordinator, + mediationRecipientService: MediationRecipientService, + routingService: RoutingService + ): void { + dispatcher.registerHandler( + new V1ProposePresentationHandler(this, agentConfig, proofResponseCoordinator, this.didCommMessageRepository) + ) + + dispatcher.registerHandler( + new V1RequestPresentationHandler( + this, + agentConfig, + proofResponseCoordinator, + mediationRecipientService, + this.didCommMessageRepository, + routingService + ) + ) + + dispatcher.registerHandler( + new V1PresentationHandler(this, agentConfig, proofResponseCoordinator, this.didCommMessageRepository) + ) + dispatcher.registerHandler(new V1PresentationAckHandler(this)) + dispatcher.registerHandler(new V1PresentationProblemReportHandler(this)) + } + + public async findRequestMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + return await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V1RequestPresentationMessage, + }) + } + public async findPresentationMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + return await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V1PresentationMessage, + }) + } + + public async findProposalMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + return await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V1ProposePresentationMessage, + }) + } + + /** + * Retrieve all proof records + * + * @returns List containing all proof records + */ + public async getAll(agentContext: AgentContext): Promise { + return this.proofRepository.getAll(agentContext) + } + + /** + * Retrieve a proof record by connection id and thread id + * + * @param connectionId The connection id + * @param threadId The thread id + * @throws {RecordNotFoundError} If no record is found + * @throws {RecordDuplicateError} If multiple records are found + * @returns The proof record + */ + public async getByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise { + return this.proofRepository.getSingleByQuery(agentContext, { threadId, connectionId }) + } + + public async createAck( + gentContext: AgentContext, + options: CreateAckOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + const { proofRecord } = options + this.logger.debug(`Creating presentation ack for proof record with id ${proofRecord.id}`) + + // Assert + proofRecord.assertState(ProofState.PresentationReceived) + + // Create message + const ackMessage = new V1PresentationAckMessage({ + status: AckStatus.OK, + threadId: proofRecord.threadId, + }) + + // Update record + await this.updateState(gentContext, proofRecord, ProofState.Done) + + return { message: ackMessage, proofRecord } + } +} diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts new file mode 100644 index 0000000000..eb9effdb0d --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts @@ -0,0 +1,246 @@ +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' +import type { ProofRecord } from '../../../repository/ProofRecord' +import type { PresentationPreview } from '../models/V1PresentationPreview' + +import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { DidCommMessageRepository } from '../../../../../storage/didcomm' +import { ProofProtocolVersion } from '../../../models/ProofProtocolVersion' +import { ProofState } from '../../../models/ProofState' +import { V1PresentationMessage, V1ProposePresentationMessage, V1RequestPresentationMessage } from '../messages' + +describe('Present Proof', () => { + let faberAgent: Agent + let aliceAgent: Agent + let aliceConnection: ConnectionRecord + let presentationPreview: PresentationPreview + let faberProofRecord: ProofRecord + let aliceProofRecord: ProofRecord + let didCommMessageRepository: DidCommMessageRepository + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( + 'Faber agent', + 'Alice agent' + )) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test(`Alice Creates and sends Proof Proposal to Faber`, async () => { + testLogger.test('Alice sends proof proposal to Faber') + + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V1, + proofFormats: { + indy: { + name: 'ProofRequest', + nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + version: '1.0', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + }, + }, + comment: 'V1 propose proof test', + }) + + testLogger.test('Faber waits for presentation from Alice') + + faberProofRecord = await faberProofRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/propose-presentation', + id: expect.any(String), + comment: 'V1 propose proof test', + presentationProposal: { + type: 'https://didcomm.org/present-proof/1.0/presentation-preview', + attributes: [ + { + name: 'name', + credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + value: 'John', + referent: '0', + }, + { + name: 'image_0', + credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + predicate: '>=', + threshold: 50, + }, + ], + }, + }) + expect(faberProofRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: ProofProtocolVersion.V1, + }) + }) + + test(`Faber accepts the Proposal send by Alice`, async () => { + // Accept Proposal + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + threadId: faberProofRecord.threadId, + state: ProofState.RequestReceived, + }) + + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofRecord = await faberAgent.proofs.acceptProposal({ + proofRecordId: faberProofRecord.id, + }) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofRecord = await aliceProofRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/request-presentation', + id: expect.any(String), + requestPresentationAttachments: [ + { + id: 'libindy-request-presentation-0', + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + thread: { + threadId: faberProofRecord.threadId, + }, + }) + expect(aliceProofRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: ProofProtocolVersion.V1, + }) + }) + + test(`Alice accepts presentation request from Faber`, async () => { + const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: aliceProofRecord.id, + config: { + filterByPresentationPreview: true, + }, + }) + + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.PresentationReceived, + }) + + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofRecord.id, + proofFormats: { indy: requestedCredentials.proofFormats.indy }, + }) + + // Faber waits for the presentation from Alice + testLogger.test('Faber waits for presentation from Alice') + faberProofRecord = await faberProofRecordPromise + + const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V1PresentationMessage, + }) + + expect(presentation).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/presentation', + id: expect.any(String), + presentationAttachments: [ + { + id: 'libindy-presentation-0', + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + appendedAttachments: [ + { + id: expect.any(String), + filename: expect.any(String), + data: { + base64: expect.any(String), + }, + }, + ], + thread: { + threadId: expect.any(String), + }, + }) + + expect(faberProofRecord.id).not.toBeNull() + expect(faberProofRecord).toMatchObject({ + threadId: faberProofRecord.threadId, + state: ProofState.PresentationReceived, + protocolVersion: ProofProtocolVersion.V1, + }) + }) + + test(`Faber accepts the presentation provided by Alice`, async () => { + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.Done, + }) + + // Faber accepts the presentation provided by Alice + await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + + // Alice waits until she received a presentation acknowledgement + testLogger.test('Alice waits until she receives a presentation acknowledgement') + aliceProofRecord = await aliceProofRecordPromise + + expect(faberProofRecord).toMatchObject({ + // type: ProofRecord.name, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: aliceProofRecord.threadId, + connectionId: expect.any(String), + isVerified: true, + state: ProofState.PresentationReceived, + }) + + expect(aliceProofRecord).toMatchObject({ + // type: ProofRecord.name, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: faberProofRecord.threadId, + connectionId: expect.any(String), + state: ProofState.Done, + }) + }) +}) diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts new file mode 100644 index 0000000000..5461c91a16 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts @@ -0,0 +1,108 @@ +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' +import type { ProposeProofOptions } from '../../../ProofsApiOptions' +import type { ProofRecord } from '../../../repository/ProofRecord' +import type { PresentationPreview } from '../models/V1PresentationPreview' + +import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { DidCommMessageRepository } from '../../../../../storage' +import { ProofProtocolVersion } from '../../../models/ProofProtocolVersion' +import { ProofState } from '../../../models/ProofState' +import { V1ProposePresentationMessage } from '../messages' + +describe('Present Proof', () => { + let faberAgent: Agent + let aliceAgent: Agent + let aliceConnection: ConnectionRecord + let presentationPreview: PresentationPreview + let faberProofRecord: ProofRecord + let didCommMessageRepository: DidCommMessageRepository + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( + 'Faber agent', + 'Alice agent' + )) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test(`Alice Creates and sends Proof Proposal to Faber`, async () => { + testLogger.test('Alice sends proof proposal to Faber') + + const proposeOptions: ProposeProofOptions = { + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V1, + proofFormats: { + indy: { + name: 'ProofRequest', + nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + version: '1.0', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + }, + }, + comment: 'V1 propose proof test', + } + + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + await aliceAgent.proofs.proposeProof(proposeOptions) + + testLogger.test('Faber waits for presentation from Alice') + faberProofRecord = await faberProofRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/propose-presentation', + id: expect.any(String), + comment: 'V1 propose proof test', + presentationProposal: { + type: 'https://didcomm.org/present-proof/1.0/presentation-preview', + attributes: [ + { + name: 'name', + credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + value: 'John', + referent: '0', + }, + { + name: 'image_0', + credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + predicate: '>=', + threshold: 50, + }, + ], + }, + }) + + expect(faberProofRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: ProofProtocolVersion.V1, + }) + }) +}) diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts new file mode 100644 index 0000000000..6b096cd21c --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts @@ -0,0 +1,156 @@ +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' +import type { ProposeProofOptions, AcceptProposalOptions } from '../../../ProofsApiOptions' +import type { ProofRecord } from '../../../repository/ProofRecord' +import type { PresentationPreview } from '../models/V1PresentationPreview' + +import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { DidCommMessageRepository } from '../../../../../storage/didcomm' +import { ProofProtocolVersion } from '../../../models/ProofProtocolVersion' +import { ProofState } from '../../../models/ProofState' +import { V1ProposePresentationMessage, V1RequestPresentationMessage } from '../messages' + +describe('Present Proof', () => { + let faberAgent: Agent + let aliceAgent: Agent + let aliceConnection: ConnectionRecord + let presentationPreview: PresentationPreview + let faberProofRecord: ProofRecord + let aliceProofRecord: ProofRecord + let didCommMessageRepository: DidCommMessageRepository + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( + 'Faber agent', + 'Alice agent' + )) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test(`Alice Creates and sends Proof Proposal to Faber`, async () => { + testLogger.test('Alice sends proof proposal to Faber') + + const proposeOptions: ProposeProofOptions = { + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V1, + proofFormats: { + indy: { + name: 'ProofRequest', + nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + version: '1.0', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + }, + }, + comment: 'V1 propose proof test', + } + + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofRecord = await aliceAgent.proofs.proposeProof(proposeOptions) + + testLogger.test('Faber waits for presentation from Alice') + faberProofRecord = await faberProofRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/propose-presentation', + id: expect.any(String), + comment: 'V1 propose proof test', + presentationProposal: { + type: 'https://didcomm.org/present-proof/1.0/presentation-preview', + attributes: [ + { + name: 'name', + credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + value: 'John', + referent: '0', + }, + { + name: 'image_0', + credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + predicate: '>=', + threshold: 50, + }, + ], + }, + }) + expect(faberProofRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: ProofProtocolVersion.V1, + }) + }) + + test(`Faber accepts the Proposal send by Alice and Creates Proof Request`, async () => { + // Accept Proposal + const acceptProposalOptions: AcceptProposalOptions = { + proofRecordId: faberProofRecord.id, + } + + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + threadId: faberProofRecord.threadId, + state: ProofState.RequestReceived, + }) + + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofRecord = await aliceProofRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/request-presentation', + id: expect.any(String), + requestPresentationAttachments: [ + { + id: 'libindy-request-presentation-0', + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + thread: { + threadId: faberProofRecord.threadId, + }, + }) + expect(aliceProofRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: ProofProtocolVersion.V1, + }) + }) +}) diff --git a/packages/core/src/modules/proofs/protocol/v1/errors/V1PresentationProblemReportError.ts b/packages/core/src/modules/proofs/protocol/v1/errors/V1PresentationProblemReportError.ts new file mode 100644 index 0000000000..27c77c0f82 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/errors/V1PresentationProblemReportError.ts @@ -0,0 +1,23 @@ +import type { ProblemReportErrorOptions } from '../../../../problem-reports' +import type { PresentationProblemReportReason } from '../../../errors/PresentationProblemReportReason' + +import { ProblemReportError } from '../../../../problem-reports' +import { V1PresentationProblemReportMessage } from '../messages/V1PresentationProblemReportMessage' + +interface V1PresentationProblemReportErrorOptions extends ProblemReportErrorOptions { + problemCode: PresentationProblemReportReason +} + +export class V1PresentationProblemReportError extends ProblemReportError { + public problemReport: V1PresentationProblemReportMessage + + public constructor(public message: string, { problemCode }: V1PresentationProblemReportErrorOptions) { + super(message, { problemCode }) + this.problemReport = new V1PresentationProblemReportMessage({ + description: { + en: message, + code: problemCode, + }, + }) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v1/errors/index.ts b/packages/core/src/modules/proofs/protocol/v1/errors/index.ts new file mode 100644 index 0000000000..75d23e13a1 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/errors/index.ts @@ -0,0 +1 @@ +export * from './V1PresentationProblemReportError' diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts new file mode 100644 index 0000000000..aa0c050c82 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { V1ProofService } from '../V1ProofService' + +import { V1PresentationAckMessage } from '../messages' + +export class V1PresentationAckHandler implements Handler { + private proofService: V1ProofService + public supportedMessages = [V1PresentationAckMessage] + + public constructor(proofService: V1ProofService) { + this.proofService = proofService + } + + public async handle(messageContext: HandlerInboundMessage) { + await this.proofService.processAck(messageContext) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts new file mode 100644 index 0000000000..9c97a5991d --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts @@ -0,0 +1,72 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { DidCommMessageRepository } from '../../../../../storage' +import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' +import type { ProofRecord } from '../../../repository' +import type { V1ProofService } from '../V1ProofService' + +import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { V1PresentationMessage, V1RequestPresentationMessage } from '../messages' + +export class V1PresentationHandler implements Handler { + private proofService: V1ProofService + private agentConfig: AgentConfig + private proofResponseCoordinator: ProofResponseCoordinator + private didCommMessageRepository: DidCommMessageRepository + public supportedMessages = [V1PresentationMessage] + + public constructor( + proofService: V1ProofService, + agentConfig: AgentConfig, + proofResponseCoordinator: ProofResponseCoordinator, + didCommMessageRepository: DidCommMessageRepository + ) { + this.proofService = proofService + this.agentConfig = agentConfig + this.proofResponseCoordinator = proofResponseCoordinator + this.didCommMessageRepository = didCommMessageRepository + } + + public async handle(messageContext: HandlerInboundMessage) { + const proofRecord = await this.proofService.processPresentation(messageContext) + + if (this.proofResponseCoordinator.shouldAutoRespondToPresentation(messageContext.agentContext, proofRecord)) { + return await this.createAck(proofRecord, messageContext) + } + } + + private async createAck(record: ProofRecord, messageContext: HandlerInboundMessage) { + this.agentConfig.logger.info( + `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptProofs}` + ) + + const { message, proofRecord } = await this.proofService.createAck(messageContext.agentContext, { + proofRecord: record, + }) + + const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + const presentationMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1PresentationMessage, + }) + + if (messageContext.connection) { + return createOutboundMessage(messageContext.connection, message) + } else if (requestMessage?.service && presentationMessage?.service) { + const recipientService = presentationMessage?.service + const ourService = requestMessage?.service + + return createOutboundServiceMessage({ + payload: message, + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + }) + } + + this.agentConfig.logger.error(`Could not automatically create presentation ack`) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts new file mode 100644 index 0000000000..da2af78c18 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { V1ProofService } from '../V1ProofService' + +import { V1PresentationProblemReportMessage } from '../messages/V1PresentationProblemReportMessage' + +export class V1PresentationProblemReportHandler implements Handler { + private proofService: V1ProofService + public supportedMessages = [V1PresentationProblemReportMessage] + + public constructor(proofService: V1ProofService) { + this.proofService = proofService + } + + public async handle(messageContext: HandlerInboundMessage) { + await this.proofService.processProblemReport(messageContext) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts new file mode 100644 index 0000000000..19c88f33ab --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts @@ -0,0 +1,104 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' +import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' +import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' +import type { IndyProofRequestFromProposalOptions } from '../../../formats/indy/IndyProofFormatsServiceOptions' +import type { ProofRequestFromProposalOptions } from '../../../models/ProofServiceOptions' +import type { ProofRecord } from '../../../repository/ProofRecord' +import type { V1ProofService } from '../V1ProofService' + +import { createOutboundMessage } from '../../../../../agent/helpers' +import { AriesFrameworkError } from '../../../../../error' +import { V1ProposePresentationMessage } from '../messages' + +export class V1ProposePresentationHandler implements Handler { + private proofService: V1ProofService + private agentConfig: AgentConfig + private didCommMessageRepository: DidCommMessageRepository + private proofResponseCoordinator: ProofResponseCoordinator + public supportedMessages = [V1ProposePresentationMessage] + + public constructor( + proofService: V1ProofService, + agentConfig: AgentConfig, + proofResponseCoordinator: ProofResponseCoordinator, + didCommMessageRepository: DidCommMessageRepository + ) { + this.proofService = proofService + this.agentConfig = agentConfig + this.proofResponseCoordinator = proofResponseCoordinator + this.didCommMessageRepository = didCommMessageRepository + } + + public async handle(messageContext: HandlerInboundMessage) { + const proofRecord = await this.proofService.processProposal(messageContext) + if (this.proofResponseCoordinator.shouldAutoRespondToProposal(messageContext.agentContext, proofRecord)) { + return await this.createRequest(proofRecord, messageContext) + } + } + + private async createRequest( + proofRecord: ProofRecord, + messageContext: HandlerInboundMessage + ) { + this.agentConfig.logger.info( + `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptProofs}` + ) + + if (!messageContext.connection) { + this.agentConfig.logger.error('No connection on the messageContext') + throw new AriesFrameworkError('No connection on the messageContext') + } + + const proposalMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + if (!proposalMessage) { + this.agentConfig.logger.error(`Proof record with id ${proofRecord.id} is missing required credential proposal`) + throw new AriesFrameworkError(`Proof record with id ${proofRecord.id} is missing required credential proposal`) + } + + const proofRequestFromProposalOptions: IndyProofRequestFromProposalOptions = { + name: 'proof-request', + version: '1.0', + nonce: await this.proofService.generateProofRequestNonce(), + proofRecord, + } + + const proofRequest: ProofRequestFromProposalOptions<[IndyProofFormat]> = + await this.proofService.createProofRequestFromProposal( + messageContext.agentContext, + proofRequestFromProposalOptions + ) + + const indyProofRequest = proofRequest.proofFormats + + if (!indyProofRequest || !indyProofRequest.indy) { + this.agentConfig.logger.error(`No Indy proof request was found`) + throw new AriesFrameworkError('No Indy proof request was found') + } + + const { message } = await this.proofService.createRequestAsResponse(messageContext.agentContext, { + proofFormats: { + indy: { + name: indyProofRequest.indy?.name, + version: indyProofRequest.indy?.version, + nonRevoked: indyProofRequest.indy?.nonRevoked, + requestedAttributes: indyProofRequest.indy?.requestedAttributes, + requestedPredicates: indyProofRequest.indy?.requestedPredicates, + ver: indyProofRequest.indy?.ver, + proofRequest: indyProofRequest.indy?.proofRequest, + nonce: indyProofRequest.indy?.nonce, + }, + }, + proofRecord: proofRecord, + autoAcceptProof: proofRecord.autoAcceptProof, + willConfirm: true, + }) + + return createOutboundMessage(messageContext.connection, message) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts new file mode 100644 index 0000000000..e38f3ba0cb --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts @@ -0,0 +1,123 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' +import type { MediationRecipientService, RoutingService } from '../../../../routing' +import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' +import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' +import type { + FormatRequestedCredentialReturn, + FormatRetrievedCredentialOptions, +} from '../../../models/ProofServiceOptions' +import type { ProofRecord } from '../../../repository/ProofRecord' +import type { V1ProofService } from '../V1ProofService' + +import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' +import { AriesFrameworkError } from '../../../../../error' +import { DidCommMessageRole } from '../../../../../storage' +import { V1RequestPresentationMessage } from '../messages' + +export class V1RequestPresentationHandler implements Handler { + private proofService: V1ProofService + private agentConfig: AgentConfig + private proofResponseCoordinator: ProofResponseCoordinator + private mediationRecipientService: MediationRecipientService + private didCommMessageRepository: DidCommMessageRepository + private routingService: RoutingService + public supportedMessages = [V1RequestPresentationMessage] + + public constructor( + proofService: V1ProofService, + agentConfig: AgentConfig, + proofResponseCoordinator: ProofResponseCoordinator, + mediationRecipientService: MediationRecipientService, + didCommMessageRepository: DidCommMessageRepository, + routingService: RoutingService + ) { + this.proofService = proofService + this.agentConfig = agentConfig + this.proofResponseCoordinator = proofResponseCoordinator + this.mediationRecipientService = mediationRecipientService + this.didCommMessageRepository = didCommMessageRepository + this.routingService = routingService + } + + public async handle(messageContext: HandlerInboundMessage) { + const proofRecord = await this.proofService.processRequest(messageContext) + if (this.proofResponseCoordinator.shouldAutoRespondToRequest(messageContext.agentContext, proofRecord)) { + return await this.createPresentation(proofRecord, messageContext) + } + } + + private async createPresentation( + record: ProofRecord, + messageContext: HandlerInboundMessage + ) { + const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + associatedRecordId: record.id, + messageClass: V1RequestPresentationMessage, + }) + + const indyProofRequest = requestMessage.indyProofRequest + + this.agentConfig.logger.info( + `Automatically sending presentation with autoAccept on ${this.agentConfig.autoAcceptProofs}` + ) + + if (!indyProofRequest) { + this.agentConfig.logger.error('Proof request is undefined.') + throw new AriesFrameworkError('No proof request found.') + } + + const retrievedCredentials: FormatRetrievedCredentialOptions<[IndyProofFormat]> = + await this.proofService.getRequestedCredentialsForProofRequest(messageContext.agentContext, { + proofRecord: record, + config: { + filterByPresentationPreview: true, + }, + }) + if (!retrievedCredentials.proofFormats.indy) { + this.agentConfig.logger.error('No matching Indy credentials could be retrieved.') + throw new AriesFrameworkError('No matching Indy credentials could be retrieved.') + } + + const options: FormatRetrievedCredentialOptions<[IndyProofFormat]> = { + proofFormats: retrievedCredentials.proofFormats, + } + const requestedCredentials: FormatRequestedCredentialReturn<[IndyProofFormat]> = + await this.proofService.autoSelectCredentialsForProofRequest(options) + + const { message, proofRecord } = await this.proofService.createPresentation(messageContext.agentContext, { + proofRecord: record, + proofFormats: { + indy: requestedCredentials.proofFormats.indy, + }, + willConfirm: true, + }) + + if (messageContext.connection) { + return createOutboundMessage(messageContext.connection, message) + } else if (requestMessage.service) { + const routing = await this.routingService.getRouting(messageContext.agentContext) + message.service = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.recipientKey.publicKeyBase58], + routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), + }) + const recipientService = requestMessage.service + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + return createOutboundServiceMessage({ + payload: message, + service: recipientService.resolvedDidCommService, + senderKey: message.service.resolvedDidCommService.recipientKeys[0], + }) + } + + this.agentConfig.logger.error(`Could not automatically create presentation`) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/index.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/index.ts new file mode 100644 index 0000000000..c202042b9b --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/index.ts @@ -0,0 +1,5 @@ +export * from './V1PresentationAckHandler' +export * from './V1PresentationHandler' +export * from './V1ProposePresentationHandler' +export * from './V1RequestPresentationHandler' +export * from './V1PresentationProblemReportHandler' diff --git a/packages/core/src/modules/proofs/protocol/v1/index.ts b/packages/core/src/modules/proofs/protocol/v1/index.ts new file mode 100644 index 0000000000..1b43254564 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/index.ts @@ -0,0 +1 @@ +export * from './V1ProofService' diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationAckMessage.ts b/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationAckMessage.ts new file mode 100644 index 0000000000..29f12a8dd9 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationAckMessage.ts @@ -0,0 +1,14 @@ +import type { AckMessageOptions } from '../../../../common' + +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { AckMessage } from '../../../../common' + +export class V1PresentationAckMessage extends AckMessage { + public constructor(options: AckMessageOptions) { + super(options) + } + + @IsValidMessageType(V1PresentationAckMessage.type) + public readonly type = V1PresentationAckMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/present-proof/1.0/ack') +} diff --git a/packages/core/src/modules/proofs/messages/PresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationMessage.ts similarity index 57% rename from packages/core/src/modules/proofs/messages/PresentationMessage.ts rename to packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationMessage.ts index 72d68cbdcc..d66360d0e5 100644 --- a/packages/core/src/modules/proofs/messages/PresentationMessage.ts +++ b/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationMessage.ts @@ -1,11 +1,15 @@ +import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' import type { IndyProof } from 'indy-sdk' import { Expose, Type } from 'class-transformer' import { IsArray, IsString, ValidateNested, IsOptional, IsInstance } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' -import { Attachment } from '../../../decorators/attachment/Attachment' -import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Attachment } from '../../../../../decorators/attachment/Attachment' +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { V2_INDY_PRESENTATION } from '../../../formats/ProofFormatConstants' +import { ProofFormatSpec } from '../../../models/ProofFormatSpec' export const INDY_PROOF_ATTACHMENT_ID = 'libindy-presentation-0' @@ -22,7 +26,7 @@ export interface PresentationOptions { * * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#presentation */ -export class PresentationMessage extends AgentMessage { +export class V1PresentationMessage extends AgentMessage { public constructor(options: PresentationOptions) { super() @@ -34,8 +38,8 @@ export class PresentationMessage extends AgentMessage { } } - @IsValidMessageType(PresentationMessage.type) - public readonly type = PresentationMessage.type.messageTypeUri + @IsValidMessageType(V1PresentationMessage.type) + public readonly type = V1PresentationMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/present-proof/1.0/presentation') /** @@ -57,8 +61,27 @@ export class PresentationMessage extends AgentMessage { @IsInstance(Attachment, { each: true }) public presentationAttachments!: Attachment[] + public getAttachmentFormats(): ProofAttachmentFormat[] { + const attachment = this.indyAttachment + + if (!attachment) { + throw new AriesFrameworkError(`Could not find a presentation attachment`) + } + + return [ + { + format: new ProofFormatSpec({ format: V2_INDY_PRESENTATION }), + attachment: attachment, + }, + ] + } + + public get indyAttachment(): Attachment | null { + return this.presentationAttachments.find((attachment) => attachment.id === INDY_PROOF_ATTACHMENT_ID) ?? null + } + public get indyProof(): IndyProof | null { - const attachment = this.presentationAttachments.find((attachment) => attachment.id === INDY_PROOF_ATTACHMENT_ID) + const attachment = this.indyAttachment const proofJson = attachment?.getDataAsJson() ?? null diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationProblemReportMessage.ts b/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationProblemReportMessage.ts new file mode 100644 index 0000000000..87901ce6a8 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationProblemReportMessage.ts @@ -0,0 +1,23 @@ +import type { ProblemReportMessageOptions } from '../../../../problem-reports/messages/ProblemReportMessage' + +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { ProblemReportMessage } from '../../../../problem-reports/messages/ProblemReportMessage' + +export type V1PresentationProblemReportMessageOptions = ProblemReportMessageOptions + +/** + * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md + */ +export class V1PresentationProblemReportMessage extends ProblemReportMessage { + /** + * Create new PresentationProblemReportMessage instance. + * @param options description of error and multiple optional fields for reporting problem + */ + public constructor(options: V1PresentationProblemReportMessageOptions) { + super(options) + } + + @IsValidMessageType(V1PresentationProblemReportMessage.type) + public readonly type = V1PresentationProblemReportMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/present-proof/1.0/problem-report') +} diff --git a/packages/core/src/modules/proofs/messages/ProposePresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v1/messages/V1ProposePresentationMessage.ts similarity index 77% rename from packages/core/src/modules/proofs/messages/ProposePresentationMessage.ts rename to packages/core/src/modules/proofs/protocol/v1/messages/V1ProposePresentationMessage.ts index 9a41814e09..fab2d86765 100644 --- a/packages/core/src/modules/proofs/messages/ProposePresentationMessage.ts +++ b/packages/core/src/modules/proofs/protocol/v1/messages/V1ProposePresentationMessage.ts @@ -1,10 +1,9 @@ import { Expose, Type } from 'class-transformer' import { IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' -import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' - -import { PresentationPreview } from './PresentationPreview' +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { PresentationPreview } from '../models/V1PresentationPreview' export interface ProposePresentationMessageOptions { id?: string @@ -18,7 +17,7 @@ export interface ProposePresentationMessageOptions { * * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#propose-presentation */ -export class ProposePresentationMessage extends AgentMessage { +export class V1ProposePresentationMessage extends AgentMessage { public constructor(options: ProposePresentationMessageOptions) { super() @@ -34,8 +33,8 @@ export class ProposePresentationMessage extends AgentMessage { } } - @IsValidMessageType(ProposePresentationMessage.type) - public readonly type = ProposePresentationMessage.type.messageTypeUri + @IsValidMessageType(V1ProposePresentationMessage.type) + public readonly type = V1ProposePresentationMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/present-proof/1.0/propose-presentation') /** diff --git a/packages/core/src/modules/proofs/messages/RequestPresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts similarity index 58% rename from packages/core/src/modules/proofs/messages/RequestPresentationMessage.ts rename to packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts index 19688c7cd6..b582170e39 100644 --- a/packages/core/src/modules/proofs/messages/RequestPresentationMessage.ts +++ b/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts @@ -1,11 +1,16 @@ +import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' + import { Expose, Type } from 'class-transformer' import { IsArray, IsString, ValidateNested, IsOptional, IsInstance } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' -import { Attachment } from '../../../decorators/attachment/Attachment' -import { JsonTransformer } from '../../../utils/JsonTransformer' -import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' -import { ProofRequest } from '../models' +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Attachment } from '../../../../../decorators/attachment/Attachment' +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { V2_INDY_PRESENTATION_REQUEST } from '../../../formats/ProofFormatConstants' +import { ProofRequest } from '../../../formats/indy/models/ProofRequest' +import { ProofFormatSpec } from '../../../models/ProofFormatSpec' export interface RequestPresentationOptions { id?: string @@ -21,7 +26,7 @@ export const INDY_PROOF_REQUEST_ATTACHMENT_ID = 'libindy-request-presentation-0' * * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#request-presentation */ -export class RequestPresentationMessage extends AgentMessage { +export class V1RequestPresentationMessage extends AgentMessage { public constructor(options: RequestPresentationOptions) { super() @@ -37,8 +42,8 @@ export class RequestPresentationMessage extends AgentMessage { } } - @IsValidMessageType(RequestPresentationMessage.type) - public readonly type = RequestPresentationMessage.type.messageTypeUri + @IsValidMessageType(V1RequestPresentationMessage.type) + public readonly type = V1RequestPresentationMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/present-proof/1.0/request-presentation') /** @@ -70,4 +75,26 @@ export class RequestPresentationMessage extends AgentMessage { return proofRequest } + + public getAttachmentFormats(): ProofAttachmentFormat[] { + const attachment = this.indyAttachment + + if (!attachment) { + throw new AriesFrameworkError(`Could not find a request presentation attachment`) + } + + return [ + { + format: new ProofFormatSpec({ format: V2_INDY_PRESENTATION_REQUEST }), + attachment: attachment, + }, + ] + } + + public get indyAttachment(): Attachment | null { + return ( + this.requestPresentationAttachments.find((attachment) => attachment.id === INDY_PROOF_REQUEST_ATTACHMENT_ID) ?? + null + ) + } } diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/index.ts b/packages/core/src/modules/proofs/protocol/v1/messages/index.ts new file mode 100644 index 0000000000..01d16d4e87 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/messages/index.ts @@ -0,0 +1,4 @@ +export * from './V1ProposePresentationMessage' +export * from './V1RequestPresentationMessage' +export * from './V1PresentationMessage' +export * from './V1PresentationAckMessage' diff --git a/packages/core/src/modules/proofs/models/PartialProof.ts b/packages/core/src/modules/proofs/protocol/v1/models/PartialProof.ts similarity index 100% rename from packages/core/src/modules/proofs/models/PartialProof.ts rename to packages/core/src/modules/proofs/protocol/v1/models/PartialProof.ts diff --git a/packages/core/src/modules/proofs/models/ProofAttribute.ts b/packages/core/src/modules/proofs/protocol/v1/models/ProofAttribute.ts similarity index 100% rename from packages/core/src/modules/proofs/models/ProofAttribute.ts rename to packages/core/src/modules/proofs/protocol/v1/models/ProofAttribute.ts diff --git a/packages/core/src/modules/proofs/models/ProofIdentifier.ts b/packages/core/src/modules/proofs/protocol/v1/models/ProofIdentifier.ts similarity index 92% rename from packages/core/src/modules/proofs/models/ProofIdentifier.ts rename to packages/core/src/modules/proofs/protocol/v1/models/ProofIdentifier.ts index 66f337e8b2..241ac74aaa 100644 --- a/packages/core/src/modules/proofs/models/ProofIdentifier.ts +++ b/packages/core/src/modules/proofs/protocol/v1/models/ProofIdentifier.ts @@ -1,7 +1,7 @@ import { Expose } from 'class-transformer' import { IsNumber, IsOptional, IsString, Matches } from 'class-validator' -import { credDefIdRegex } from '../../../utils' +import { credDefIdRegex } from '../../../../../utils/regex' export class ProofIdentifier { public constructor(options: ProofIdentifier) { diff --git a/packages/core/src/modules/proofs/models/RequestedProof.ts b/packages/core/src/modules/proofs/protocol/v1/models/RequestedProof.ts similarity index 100% rename from packages/core/src/modules/proofs/models/RequestedProof.ts rename to packages/core/src/modules/proofs/protocol/v1/models/RequestedProof.ts diff --git a/packages/core/src/modules/proofs/messages/PresentationPreview.ts b/packages/core/src/modules/proofs/protocol/v1/models/V1PresentationPreview.ts similarity index 93% rename from packages/core/src/modules/proofs/messages/PresentationPreview.ts rename to packages/core/src/modules/proofs/protocol/v1/models/V1PresentationPreview.ts index c309aaee85..2ac71e903e 100644 --- a/packages/core/src/modules/proofs/messages/PresentationPreview.ts +++ b/packages/core/src/modules/proofs/protocol/v1/models/V1PresentationPreview.ts @@ -11,10 +11,10 @@ import { ValidateNested, } from 'class-validator' -import { credDefIdRegex } from '../../../utils' -import { JsonTransformer } from '../../../utils/JsonTransformer' -import { IsValidMessageType, parseMessageType, replaceLegacyDidSovPrefix } from '../../../utils/messageType' -import { PredicateType } from '../models/PredicateType' +import { credDefIdRegex } from '../../../../../utils' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { IsValidMessageType, parseMessageType, replaceLegacyDidSovPrefix } from '../../../../../utils/messageType' +import { PredicateType } from '../../../formats/indy/models/PredicateType' export interface PresentationPreviewAttributeOptions { name: string diff --git a/packages/core/src/modules/proofs/protocol/v1/models/index.ts b/packages/core/src/modules/proofs/protocol/v1/models/index.ts new file mode 100644 index 0000000000..f9d9b12a47 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/models/index.ts @@ -0,0 +1,4 @@ +export * from './PartialProof' +export * from './ProofAttribute' +export * from './ProofIdentifier' +export * from './RequestedProof' diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts new file mode 100644 index 0000000000..662ef1db33 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts @@ -0,0 +1,862 @@ +import type { AgentContext } from '../../../../agent' +import type { AgentMessage } from '../../../../agent/AgentMessage' +import type { Dispatcher } from '../../../../agent/Dispatcher' +import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { MediationRecipientService } from '../../../routing/services/MediationRecipientService' +import type { RoutingService } from '../../../routing/services/RoutingService' +import type { ProofResponseCoordinator } from '../../ProofResponseCoordinator' +import type { ProofFormat } from '../../formats/ProofFormat' +import type { ProofFormatService } from '../../formats/ProofFormatService' +import type { CreateProblemReportOptions } from '../../formats/models/ProofFormatServiceOptions' +import type { ProofFormatSpec } from '../../models/ProofFormatSpec' +import type { + CreateAckOptions, + CreatePresentationOptions, + CreateProofRequestFromProposalOptions, + CreateProposalAsResponseOptions, + CreateProposalOptions, + CreateRequestAsResponseOptions, + CreateRequestOptions, + FormatRequestedCredentialReturn, + FormatRetrievedCredentialOptions, + GetRequestedCredentialsForProofRequestOptions, + ProofRequestFromProposalOptions, +} from '../../models/ProofServiceOptions' + +import { inject, Lifecycle, scoped } from 'tsyringe' + +import { AgentConfig } from '../../../../agent/AgentConfig' +import { EventEmitter } from '../../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../../constants' +import { AriesFrameworkError } from '../../../../error' +import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' +import { MessageValidator } from '../../../../utils/MessageValidator' +import { Wallet } from '../../../../wallet/Wallet' +import { AckStatus } from '../../../common' +import { ConnectionService } from '../../../connections' +import { ProofService } from '../../ProofService' +import { PresentationProblemReportReason } from '../../errors/PresentationProblemReportReason' +import { V2_INDY_PRESENTATION_REQUEST } from '../../formats/ProofFormatConstants' +import { IndyProofFormatService } from '../../formats/indy/IndyProofFormatService' +import { IndyProofUtils } from '../../formats/indy/IndyProofUtils' +import { ProofProtocolVersion } from '../../models/ProofProtocolVersion' +import { ProofState } from '../../models/ProofState' +import { PresentationRecordType, ProofRecord, ProofRepository } from '../../repository' + +import { V2PresentationProblemReportError } from './errors' +import { V2PresentationAckHandler } from './handlers/V2PresentationAckHandler' +import { V2PresentationHandler } from './handlers/V2PresentationHandler' +import { V2PresentationProblemReportHandler } from './handlers/V2PresentationProblemReportHandler' +import { V2ProposePresentationHandler } from './handlers/V2ProposePresentationHandler' +import { V2RequestPresentationHandler } from './handlers/V2RequestPresentationHandler' +import { V2PresentationAckMessage } from './messages' +import { V2PresentationMessage } from './messages/V2PresentationMessage' +import { V2PresentationProblemReportMessage } from './messages/V2PresentationProblemReportMessage' +import { V2ProposalPresentationMessage } from './messages/V2ProposalPresentationMessage' +import { V2RequestPresentationMessage } from './messages/V2RequestPresentationMessage' + +@scoped(Lifecycle.ContainerScoped) +export class V2ProofService extends ProofService { + private formatServiceMap: { [key: string]: ProofFormatService } + + public constructor( + agentConfig: AgentConfig, + connectionService: ConnectionService, + proofRepository: ProofRepository, + didCommMessageRepository: DidCommMessageRepository, + eventEmitter: EventEmitter, + indyProofFormatService: IndyProofFormatService, + @inject(InjectionSymbols.Wallet) wallet: Wallet + ) { + super(agentConfig, proofRepository, connectionService, didCommMessageRepository, wallet, eventEmitter) + this.wallet = wallet + this.formatServiceMap = { + [PresentationRecordType.Indy]: indyProofFormatService, + // other format services to be added to the map + } + } + + /** + * The version of the issue credential protocol this service supports + */ + public readonly version = 'v2' as const + + public async createProposal( + agentContext: AgentContext, + options: CreateProposalOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + const formats = [] + for (const key of Object.keys(options.proofFormats)) { + const service = this.formatServiceMap[key] + formats.push( + await service.createProposal({ + formats: + key === PresentationRecordType.Indy + ? await IndyProofUtils.createRequestFromPreview(options) + : options.proofFormats, + }) + ) + } + + const proposalMessage = new V2ProposalPresentationMessage({ + attachmentInfo: formats, + comment: options.comment, + willConfirm: options.willConfirm, + goalCode: options.goalCode, + parentThreadId: options.parentThreadId, + }) + + const proofRecord = new ProofRecord({ + connectionId: options.connectionRecord.id, + threadId: proposalMessage.threadId, + parentThreadId: proposalMessage.thread?.parentThreadId, + state: ProofState.ProposalSent, + protocolVersion: ProofProtocolVersion.V2, + }) + + await this.proofRepository.save(agentContext, proofRecord) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: proposalMessage, + role: DidCommMessageRole.Sender, + associatedRecordId: proofRecord.id, + }) + + this.emitStateChangedEvent(agentContext, proofRecord, null) + + return { + proofRecord: proofRecord, + message: proposalMessage, + } + } + + public async createProposalAsResponse( + agentContext: AgentContext, + options: CreateProposalAsResponseOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + options.proofRecord.assertState(ProofState.RequestReceived) + + const formats = [] + for (const key of Object.keys(options.proofFormats)) { + const service = this.formatServiceMap[key] + formats.push( + await service.createProposal({ + formats: options.proofFormats, + }) + ) + } + + const proposalMessage = new V2ProposalPresentationMessage({ + attachmentInfo: formats, + comment: options.comment, + goalCode: options.goalCode, + willConfirm: options.willConfirm, + }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: proposalMessage, + role: DidCommMessageRole.Sender, + associatedRecordId: options.proofRecord.id, + }) + + await this.updateState(agentContext, options.proofRecord, ProofState.ProposalSent) + + return { message: proposalMessage, proofRecord: options.proofRecord } + } + + public async processProposal( + messageContext: InboundMessageContext + ): Promise { + const { message: proposalMessage, connection: connectionRecord } = messageContext + let proofRecord: ProofRecord + + const proposalAttachments = proposalMessage.getAttachmentFormats() + + for (const attachmentFormat of proposalAttachments) { + const service = this.getFormatServiceForFormat(attachmentFormat.format) + await service?.processProposal({ + proposal: attachmentFormat, + }) + } + + try { + proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { + threadId: proposalMessage.threadId, + connectionId: connectionRecord?.id, + }) + + const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + // Assert + proofRecord.assertState(ProofState.RequestSent) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: proposalMessage, + previousSentMessage: requestMessage ?? undefined, + }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + agentMessage: proposalMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + + await this.updateState(messageContext.agentContext, proofRecord, ProofState.ProposalReceived) + } catch { + // No proof record exists with thread id + proofRecord = new ProofRecord({ + connectionId: connectionRecord?.id, + threadId: proposalMessage.threadId, + parentThreadId: proposalMessage.thread?.parentThreadId, + state: ProofState.ProposalReceived, + protocolVersion: ProofProtocolVersion.V2, + }) + + // Assert + this.connectionService.assertConnectionOrServiceDecorator(messageContext) + + // Save record + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + agentMessage: proposalMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + + await this.proofRepository.save(messageContext.agentContext, proofRecord) + this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) + } + + return proofRecord + } + + public async createRequest( + agentContext: AgentContext, + options: CreateRequestOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + // create attachment formats + const formats = [] + for (const key of Object.keys(options.proofFormats)) { + const service = this.formatServiceMap[key] + formats.push( + await service.createRequest({ + formats: options.proofFormats, + }) + ) + } + + // create request message + const requestMessage = new V2RequestPresentationMessage({ + attachmentInfo: formats, + comment: options.comment, + willConfirm: options.willConfirm, + goalCode: options.goalCode, + parentThreadId: options.parentThreadId, + }) + + // create & store proof record + const proofRecord = new ProofRecord({ + connectionId: options.connectionRecord?.id, + threadId: requestMessage.threadId, + parentThreadId: requestMessage.thread?.parentThreadId, + state: ProofState.RequestSent, + protocolVersion: ProofProtocolVersion.V2, + }) + + await this.proofRepository.save(agentContext, proofRecord) + + // create DIDComm message + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: requestMessage, + role: DidCommMessageRole.Sender, + associatedRecordId: proofRecord.id, + }) + + this.emitStateChangedEvent(agentContext, proofRecord, null) + + return { + proofRecord: proofRecord, + message: requestMessage, + } + } + + public async createRequestAsResponse( + agentContext: AgentContext, + options: CreateRequestAsResponseOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + options.proofRecord.assertState(ProofState.ProposalReceived) + + const proposal = await this.didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: options.proofRecord.id, + messageClass: V2ProposalPresentationMessage, + }) + + if (!proposal) { + throw new AriesFrameworkError( + `Proof record with id ${options.proofRecord.id} is missing required presentation proposal` + ) + } + + // create attachment formats + const formats = [] + + for (const key of Object.keys(options.proofFormats)) { + const service = this.formatServiceMap[key] + const requestOptions: CreateRequestAsResponseOptions = { + proofFormats: options.proofFormats, + proofRecord: options.proofRecord, + } + formats.push(await service.createRequestAsResponse(requestOptions)) + } + + // create request message + const requestMessage = new V2RequestPresentationMessage({ + attachmentInfo: formats, + comment: options.comment, + willConfirm: options.willConfirm, + goalCode: options.goalCode, + }) + requestMessage.setThread({ threadId: options.proofRecord.threadId }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: requestMessage, + role: DidCommMessageRole.Sender, + associatedRecordId: options.proofRecord.id, + }) + + await this.updateState(agentContext, options.proofRecord, ProofState.RequestSent) + + return { message: requestMessage, proofRecord: options.proofRecord } + } + + public async processRequest( + messageContext: InboundMessageContext + ): Promise { + const { message: proofRequestMessage, connection: connectionRecord } = messageContext + + const requestAttachments = proofRequestMessage.getAttachmentFormats() + + for (const attachmentFormat of requestAttachments) { + const service = this.getFormatServiceForFormat(attachmentFormat.format) + service?.processRequest({ + requestAttachment: attachmentFormat, + }) + } + + // assert + if (proofRequestMessage.requestPresentationsAttach.length === 0) { + throw new V2PresentationProblemReportError( + `Missing required base64 or json encoded attachment data for presentation request with thread id ${proofRequestMessage.threadId}`, + { problemCode: PresentationProblemReportReason.Abandoned } + ) + } + + this.logger.debug(`Received proof request`, proofRequestMessage) + + let proofRecord: ProofRecord + + try { + proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { + threadId: proofRequestMessage.threadId, + connectionId: connectionRecord?.id, + }) + + const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposalPresentationMessage, + }) + + // Assert + proofRecord.assertState(ProofState.ProposalSent) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: requestMessage ?? undefined, + previousSentMessage: proposalMessage ?? undefined, + }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + agentMessage: proofRequestMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + + // Update record + await this.updateState(messageContext.agentContext, proofRecord, ProofState.RequestReceived) + } catch { + // No proof record exists with thread id + proofRecord = new ProofRecord({ + connectionId: connectionRecord?.id, + threadId: proofRequestMessage.threadId, + parentThreadId: proofRequestMessage.thread?.parentThreadId, + state: ProofState.RequestReceived, + protocolVersion: ProofProtocolVersion.V2, + }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + agentMessage: proofRequestMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + + // Assert + this.connectionService.assertConnectionOrServiceDecorator(messageContext) + + // Save in repository + await this.proofRepository.save(messageContext.agentContext, proofRecord) + this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) + } + + return proofRecord + } + + public async createPresentation( + agentContext: AgentContext, + options: CreatePresentationOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + // assert state + options.proofRecord.assertState(ProofState.RequestReceived) + + const proofRequest = await this.didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: options.proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const formats = [] + for (const key of Object.keys(options.proofFormats)) { + const service = this.formatServiceMap[key] + formats.push( + await service.createPresentation(agentContext, { + attachment: proofRequest.getAttachmentByFormatIdentifier(V2_INDY_PRESENTATION_REQUEST), + proofFormats: options.proofFormats, + }) + ) + } + + const presentationMessage = new V2PresentationMessage({ + comment: options.comment, + attachmentInfo: formats, + goalCode: options.goalCode, + lastPresentation: options.lastPresentation, + }) + presentationMessage.setThread({ threadId: options.proofRecord.threadId }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: presentationMessage, + associatedRecordId: options.proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + await this.updateState(agentContext, options.proofRecord, ProofState.PresentationSent) + + return { message: presentationMessage, proofRecord: options.proofRecord } + } + + public async processPresentation(messageContext: InboundMessageContext): Promise { + const { message: presentationMessage, connection: connectionRecord } = messageContext + + this.logger.debug(`Processing presentation with id ${presentationMessage.id}`) + + const proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { + threadId: presentationMessage.threadId, + connectionId: connectionRecord?.id, + }) + + const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposalPresentationMessage, + }) + + const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + // Assert + proofRecord.assertState(ProofState.RequestSent) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: proposalMessage ?? undefined, + previousSentMessage: requestMessage ?? undefined, + }) + + const formatVerificationResults = [] + for (const attachmentFormat of presentationMessage.getAttachmentFormats()) { + const service = this.getFormatServiceForFormat(attachmentFormat.format) + if (service) { + try { + formatVerificationResults.push( + await service.processPresentation(messageContext.agentContext, { + record: proofRecord, + formatAttachments: { + request: requestMessage?.getAttachmentFormats(), + presentation: presentationMessage.getAttachmentFormats(), + }, + }) + ) + } catch (e) { + if (e instanceof AriesFrameworkError) { + throw new V2PresentationProblemReportError(e.message, { + problemCode: PresentationProblemReportReason.Abandoned, + }) + } + throw e + } + } + } + if (formatVerificationResults.length === 0) { + throw new V2PresentationProblemReportError('None of the received formats are supported.', { + problemCode: PresentationProblemReportReason.Abandoned, + }) + } + + const isValid = formatVerificationResults.every((x) => x === true) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + agentMessage: presentationMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + + // Update record + proofRecord.isVerified = isValid + await this.updateState(messageContext.agentContext, proofRecord, ProofState.PresentationReceived) + + return proofRecord + } + + public async createAck( + agentContext: AgentContext, + options: CreateAckOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + // assert we've received the final presentation + const presentation = await this.didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: options.proofRecord.id, + messageClass: V2PresentationMessage, + }) + + if (!presentation.lastPresentation) { + throw new AriesFrameworkError( + `Trying to send an ack message while presentation with id ${presentation.id} indicates this is not the last presentation (presentation.lastPresentation is set to false)` + ) + } + + const msg = new V2PresentationAckMessage({ + threadId: options.proofRecord.threadId, + status: AckStatus.OK, + }) + + await this.updateState(agentContext, options.proofRecord, ProofState.Done) + + return { + message: msg, + proofRecord: options.proofRecord, + } + } + + public async processAck(messageContext: InboundMessageContext): Promise { + const { message: ackMessage, connection: connectionRecord } = messageContext + + const proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { + threadId: ackMessage.threadId, + connectionId: connectionRecord?.id, + }) + + const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const presentationMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2PresentationMessage, + }) + + // Assert + proofRecord.assertState(ProofState.PresentationSent) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: requestMessage ?? undefined, + previousSentMessage: presentationMessage ?? undefined, + }) + + // Update record + await this.updateState(messageContext.agentContext, proofRecord, ProofState.Done) + + return proofRecord + } + + public async createProblemReport( + agentContext: AgentContext, + options: CreateProblemReportOptions + ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + const msg = new V2PresentationProblemReportMessage({ + description: { + code: PresentationProblemReportReason.Abandoned, + en: options.description, + }, + }) + + msg.setThread({ + threadId: options.proofRecord.threadId, + parentThreadId: options.proofRecord.threadId, + }) + + return { + proofRecord: options.proofRecord, + message: msg, + } + } + + public async processProblemReport( + messageContext: InboundMessageContext + ): Promise { + const { message: presentationProblemReportMessage } = messageContext + + const connectionRecord = messageContext.assertReadyConnection() + + this.logger.debug(`Processing problem report with id ${presentationProblemReportMessage.id}`) + + const proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { + threadId: presentationProblemReportMessage.threadId, + connectionId: connectionRecord?.id, + }) + + proofRecord.errorMessage = `${presentationProblemReportMessage.description.code}: ${presentationProblemReportMessage.description.en}` + await this.updateState(messageContext.agentContext, proofRecord, ProofState.Abandoned) + return proofRecord + } + + public async createProofRequestFromProposal( + agentContext: AgentContext, + options: CreateProofRequestFromProposalOptions + ): Promise> { + const proofRecordId = options.proofRecord.id + const proposalMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V2ProposalPresentationMessage, + }) + + if (!proposalMessage) { + throw new AriesFrameworkError(`Proof record with id ${proofRecordId} is missing required presentation proposal`) + } + + const proposalAttachments = proposalMessage.getAttachmentFormats() + + let result = {} + + for (const attachmentFormat of proposalAttachments) { + const service = this.getFormatServiceForFormat(attachmentFormat.format) + + if (!service) { + throw new AriesFrameworkError('No format service found for getting requested.') + } + + result = { + ...result, + ...(await service.createProofRequestFromProposal({ + presentationAttachment: attachmentFormat.attachment, + })), + } + } + + const retVal: ProofRequestFromProposalOptions = { + proofRecord: options.proofRecord, + proofFormats: result, + } + return retVal + } + + public async shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofRecord): Promise { + const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposalPresentationMessage, + }) + + if (!proposal) { + return false + } + const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + if (!request) { + return true + } + await MessageValidator.validateSync(proposal) + + const proposalAttachments = proposal.getAttachmentFormats() + const requestAttachments = request.getAttachmentFormats() + + const equalityResults = [] + for (const attachmentFormat of proposalAttachments) { + const service = this.getFormatServiceForFormat(attachmentFormat.format) + equalityResults.push(service?.proposalAndRequestAreEqual(proposalAttachments, requestAttachments)) + } + return true + } + + public async shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofRecord): Promise { + const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposalPresentationMessage, + }) + + if (!proposal) { + return false + } + + const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + if (!request) { + throw new AriesFrameworkError(`Expected to find a request message for ProofRecord with id ${proofRecord.id}`) + } + + const proposalAttachments = proposal.getAttachmentFormats() + const requestAttachments = request.getAttachmentFormats() + + const equalityResults = [] + for (const attachmentFormat of proposalAttachments) { + const service = this.getFormatServiceForFormat(attachmentFormat.format) + equalityResults.push(service?.proposalAndRequestAreEqual(proposalAttachments, requestAttachments)) + } + + return equalityResults.every((x) => x === true) + } + + public async shouldAutoRespondToPresentation(agentContext: AgentContext, proofRecord: ProofRecord): Promise { + const request = await this.didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + return request.willConfirm + } + + public async findRequestMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + return await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V2RequestPresentationMessage, + }) + } + + public async findPresentationMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + return await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V2PresentationMessage, + }) + } + + public async findProposalMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + return await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V2ProposalPresentationMessage, + }) + } + + public async getRequestedCredentialsForProofRequest( + agentContext: AgentContext, + options: GetRequestedCredentialsForProofRequestOptions + ): Promise> { + const requestMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: options.proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + if (!requestMessage) { + throw new AriesFrameworkError('No proof request found.') + } + + const requestAttachments = requestMessage.getAttachmentFormats() + + let result = { + proofFormats: {}, + } + for (const attachmentFormat of requestAttachments) { + const service = this.getFormatServiceForFormat(attachmentFormat.format) + + if (!service) { + throw new AriesFrameworkError('No format service found for getting requested.') + } + + result = { + ...result, + ...(await service.getRequestedCredentialsForProofRequest(agentContext, { + attachment: attachmentFormat.attachment, + presentationProposal: undefined, + config: options.config, + })), + } + } + + return result + } + + public async autoSelectCredentialsForProofRequest( + options: FormatRetrievedCredentialOptions + ): Promise> { + let returnValue = { + proofFormats: {}, + } + + for (const [id] of Object.entries(options.proofFormats)) { + const service = this.formatServiceMap[id] + const credentials = await service.autoSelectCredentialsForProofRequest(options) + returnValue = { ...returnValue, ...credentials } + } + + return returnValue + } + + public registerHandlers( + dispatcher: Dispatcher, + agentConfig: AgentConfig, + proofResponseCoordinator: ProofResponseCoordinator, + mediationRecipientService: MediationRecipientService, + routingService: RoutingService + ): void { + dispatcher.registerHandler( + new V2ProposePresentationHandler(this, agentConfig, this.didCommMessageRepository, proofResponseCoordinator) + ) + + dispatcher.registerHandler( + new V2RequestPresentationHandler( + this, + agentConfig, + proofResponseCoordinator, + mediationRecipientService, + this.didCommMessageRepository, + routingService + ) + ) + + dispatcher.registerHandler( + new V2PresentationHandler(this, agentConfig, proofResponseCoordinator, this.didCommMessageRepository) + ) + dispatcher.registerHandler(new V2PresentationAckHandler(this)) + dispatcher.registerHandler(new V2PresentationProblemReportHandler(this)) + } + + private getFormatServiceForFormat(format: ProofFormatSpec) { + for (const service of Object.values(this.formatServiceMap)) { + if (service.supportsFormat(format.format)) { + return service + } + } + return null + } +} diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts new file mode 100644 index 0000000000..d02975e18b --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts @@ -0,0 +1,254 @@ +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' +import type { ProposeProofOptions, AcceptProposalOptions } from '../../../ProofsApiOptions' +import type { ProofRecord } from '../../../repository/ProofRecord' +import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' + +import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { DidCommMessageRepository } from '../../../../../storage' +import { + V2_INDY_PRESENTATION_PROPOSAL, + V2_INDY_PRESENTATION_REQUEST, + V2_INDY_PRESENTATION, +} from '../../../formats/ProofFormatConstants' +import { ProofProtocolVersion } from '../../../models/ProofProtocolVersion' +import { ProofState } from '../../../models/ProofState' +import { V2PresentationMessage, V2RequestPresentationMessage } from '../messages' +import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' + +describe('Present Proof', () => { + let faberAgent: Agent + let aliceAgent: Agent + let aliceConnection: ConnectionRecord + let presentationPreview: PresentationPreview + let faberProofRecord: ProofRecord + let aliceProofRecord: ProofRecord + let didCommMessageRepository: DidCommMessageRepository + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( + 'Faber agent', + 'Alice agent' + )) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test(`Alice Creates and sends Proof Proposal to Faber`, async () => { + testLogger.test('Alice sends proof proposal to Faber') + + const proposeOptions: ProposeProofOptions = { + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V2, + proofFormats: { + indy: { + name: 'ProofRequest', + nonce: '947121108704767252195126', + version: '1.0', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + }, + }, + comment: 'V2 propose proof test', + } + + const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofRecord = await aliceAgent.proofs.proposeProof(proposeOptions) + + testLogger.test('Faber waits for presentation from Alice') + faberProofRecord = await faberPresentationRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V2ProposalPresentationMessage, + }) + + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/propose-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION_PROPOSAL, + }, + ], + proposalsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + comment: 'V2 propose proof test', + }) + expect(faberProofRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: ProofProtocolVersion.V2, + }) + }) + + test(`Faber accepts the Proposal send by Alice`, async () => { + // Accept Proposal + const acceptProposalOptions: AcceptProposalOptions = { + proofRecordId: faberProofRecord.id, + } + + const alicePresentationRecordPromise = waitForProofRecord(aliceAgent, { + threadId: faberProofRecord.threadId, + state: ProofState.RequestReceived, + }) + + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofRecord = await alicePresentationRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/request-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION_REQUEST, + }, + ], + requestPresentationsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + thread: { + threadId: faberProofRecord.threadId, + }, + }) + expect(aliceProofRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: ProofProtocolVersion.V2, + }) + }) + + test(`Alice accepts presentation request from Faber`, async () => { + // Alice retrieves the requested credentials and accepts the presentation request + testLogger.test('Alice accepts presentation request from Faber') + + const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: aliceProofRecord.id, + config: { + filterByPresentationPreview: true, + }, + }) + + const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.PresentationReceived, + }) + + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofRecord.id, + proofFormats: { indy: requestedCredentials.proofFormats.indy }, + }) + + // Faber waits for the presentation from Alice + testLogger.test('Faber waits for presentation from Alice') + faberProofRecord = await faberPresentationRecordPromise + + const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V2PresentationMessage, + }) + + expect(presentation).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION, + }, + ], + presentationsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + thread: { + threadId: faberProofRecord.threadId, + }, + }) + expect(faberProofRecord.id).not.toBeNull() + expect(faberProofRecord).toMatchObject({ + threadId: faberProofRecord.threadId, + state: ProofState.PresentationReceived, + protocolVersion: ProofProtocolVersion.V2, + }) + }) + + test(`Faber accepts the presentation provided by Alice`, async () => { + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.Done, + }) + + // Faber accepts the presentation provided by Alice + testLogger.test('Faber accepts the presentation provided by Alice') + await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + + // Alice waits until she received a presentation acknowledgement + testLogger.test('Alice waits until she receives a presentation acknowledgement') + aliceProofRecord = await aliceProofRecordPromise + + expect(faberProofRecord).toMatchObject({ + // type: ProofRecord.name, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: aliceProofRecord.threadId, + connectionId: expect.any(String), + isVerified: true, + state: ProofState.PresentationReceived, + }) + + expect(aliceProofRecord).toMatchObject({ + // type: ProofRecord.name, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: faberProofRecord.threadId, + connectionId: expect.any(String), + state: ProofState.Done, + }) + }) +}) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts new file mode 100644 index 0000000000..5bed51e09b --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts @@ -0,0 +1,100 @@ +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' +import type { ProposeProofOptions } from '../../../ProofsApiOptions' +import type { ProofRecord } from '../../../repository' +import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' + +import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { DidCommMessageRepository } from '../../../../../storage' +import { V2_INDY_PRESENTATION_PROPOSAL } from '../../../formats/ProofFormatConstants' +import { ProofProtocolVersion } from '../../../models/ProofProtocolVersion' +import { ProofState } from '../../../models/ProofState' +import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' + +describe('Present Proof', () => { + let faberAgent: Agent + let aliceAgent: Agent + let aliceConnection: ConnectionRecord + let presentationPreview: PresentationPreview + let faberPresentationRecord: ProofRecord + let didCommMessageRepository: DidCommMessageRepository + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( + 'Faber agent', + 'Alice agent' + )) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test(`Alice Creates and sends Proof Proposal to Faber`, async () => { + testLogger.test('Alice sends proof proposal to Faber') + + const proposeOptions: ProposeProofOptions = { + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V2, + proofFormats: { + indy: { + name: 'ProofRequest', + nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + version: '1.0', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + }, + }, + comment: 'V2 propose proof test', + } + + const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + await aliceAgent.proofs.proposeProof(proposeOptions) + + testLogger.test('Faber waits for presentation from Alice') + faberPresentationRecord = await faberPresentationRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberPresentationRecord.id, + messageClass: V2ProposalPresentationMessage, + }) + + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/propose-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION_PROPOSAL, + }, + ], + proposalsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + comment: 'V2 propose proof test', + }) + expect(faberPresentationRecord).toMatchObject({ + id: expect.anything(), + threadId: faberPresentationRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: ProofProtocolVersion.V2, + }) + }) +}) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts new file mode 100644 index 0000000000..9101a956f7 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts @@ -0,0 +1,156 @@ +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' +import type { ProposeProofOptions, AcceptProposalOptions } from '../../../ProofsApiOptions' +import type { ProofRecord } from '../../../repository/ProofRecord' +import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' + +import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { DidCommMessageRepository } from '../../../../../storage' +import { V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST } from '../../../formats/ProofFormatConstants' +import { ProofProtocolVersion } from '../../../models/ProofProtocolVersion' +import { ProofState } from '../../../models/ProofState' +import { V2RequestPresentationMessage } from '../messages' +import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' + +describe('Present Proof', () => { + let faberAgent: Agent + let aliceAgent: Agent + let aliceConnection: ConnectionRecord + let presentationPreview: PresentationPreview + let faberProofRecord: ProofRecord + let aliceProofRecord: ProofRecord + let didCommMessageRepository: DidCommMessageRepository + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( + 'Faber agent', + 'Alice agent' + )) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test(`Alice Creates and sends Proof Proposal to Faber`, async () => { + testLogger.test('Alice sends proof proposal to Faber') + + const proposeOptions: ProposeProofOptions = { + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V2, + proofFormats: { + indy: { + name: 'ProofRequest', + nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + version: '1.0', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + }, + }, + comment: 'V2 propose proof test', + } + + const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofRecord = await aliceAgent.proofs.proposeProof(proposeOptions) + + testLogger.test('Faber waits for presentation from Alice') + faberProofRecord = await faberPresentationRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V2ProposalPresentationMessage, + }) + + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/propose-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION_PROPOSAL, + }, + ], + proposalsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + comment: 'V2 propose proof test', + }) + expect(faberProofRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: ProofProtocolVersion.V2, + }) + }) + + test(`Faber accepts the Proposal send by Alice`, async () => { + // Accept Proposal + const acceptProposalOptions: AcceptProposalOptions = { + proofRecordId: faberProofRecord.id, + } + + const alicePresentationRecordPromise = waitForProofRecord(aliceAgent, { + threadId: faberProofRecord.threadId, + state: ProofState.RequestReceived, + }) + + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofRecord = await alicePresentationRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/request-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION_REQUEST, + }, + ], + requestPresentationsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + thread: { + threadId: faberProofRecord.threadId, + }, + }) + expect(aliceProofRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: ProofProtocolVersion.V2, + }) + }) +}) diff --git a/packages/core/src/modules/proofs/protocol/v2/errors/V2PresentationProblemReportError.ts b/packages/core/src/modules/proofs/protocol/v2/errors/V2PresentationProblemReportError.ts new file mode 100644 index 0000000000..76fa789a6e --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/errors/V2PresentationProblemReportError.ts @@ -0,0 +1,23 @@ +import type { ProblemReportErrorOptions } from '../../../../problem-reports' +import type { PresentationProblemReportReason } from '../../../errors/PresentationProblemReportReason' + +import { ProblemReportError } from '../../../../problem-reports/errors/ProblemReportError' +import { V2PresentationProblemReportMessage } from '../messages' + +interface V2PresentationProblemReportErrorOptions extends ProblemReportErrorOptions { + problemCode: PresentationProblemReportReason +} + +export class V2PresentationProblemReportError extends ProblemReportError { + public problemReport: V2PresentationProblemReportMessage + + public constructor(public message: string, { problemCode }: V2PresentationProblemReportErrorOptions) { + super(message, { problemCode }) + this.problemReport = new V2PresentationProblemReportMessage({ + description: { + en: message, + code: problemCode, + }, + }) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v2/errors/index.ts b/packages/core/src/modules/proofs/protocol/v2/errors/index.ts new file mode 100644 index 0000000000..7064b070aa --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/errors/index.ts @@ -0,0 +1 @@ +export * from './V2PresentationProblemReportError' diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts new file mode 100644 index 0000000000..9ffa2f32cc --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { ProofService } from '../../../ProofService' + +import { V2PresentationAckMessage } from '../messages' + +export class V2PresentationAckHandler implements Handler { + private proofService: ProofService + public supportedMessages = [V2PresentationAckMessage] + + public constructor(proofService: ProofService) { + this.proofService = proofService + } + + public async handle(messageContext: HandlerInboundMessage) { + await this.proofService.processAck(messageContext) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts new file mode 100644 index 0000000000..d75a4a855c --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts @@ -0,0 +1,72 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { DidCommMessageRepository } from '../../../../../storage' +import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' +import type { ProofRecord } from '../../../repository' +import type { V2ProofService } from '../V2ProofService' + +import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { V2PresentationMessage, V2RequestPresentationMessage } from '../messages' + +export class V2PresentationHandler implements Handler { + private proofService: V2ProofService + private agentConfig: AgentConfig + private proofResponseCoordinator: ProofResponseCoordinator + private didCommMessageRepository: DidCommMessageRepository + public supportedMessages = [V2PresentationMessage] + + public constructor( + proofService: V2ProofService, + agentConfig: AgentConfig, + proofResponseCoordinator: ProofResponseCoordinator, + didCommMessageRepository: DidCommMessageRepository + ) { + this.proofService = proofService + this.agentConfig = agentConfig + this.proofResponseCoordinator = proofResponseCoordinator + this.didCommMessageRepository = didCommMessageRepository + } + + public async handle(messageContext: HandlerInboundMessage) { + const proofRecord = await this.proofService.processPresentation(messageContext) + + if (this.proofResponseCoordinator.shouldAutoRespondToPresentation(messageContext.agentContext, proofRecord)) { + return await this.createAck(proofRecord, messageContext) + } + } + + private async createAck(record: ProofRecord, messageContext: HandlerInboundMessage) { + this.agentConfig.logger.info( + `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptProofs}` + ) + + const { message, proofRecord } = await this.proofService.createAck(messageContext.agentContext, { + proofRecord: record, + }) + + const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const presentationMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2PresentationMessage, + }) + + if (messageContext.connection) { + return createOutboundMessage(messageContext.connection, message) + } else if (requestMessage?.service && presentationMessage?.service) { + const recipientService = presentationMessage?.service + const ourService = requestMessage?.service + + return createOutboundServiceMessage({ + payload: message, + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + }) + } + + this.agentConfig.logger.error(`Could not automatically create presentation ack`) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts new file mode 100644 index 0000000000..77bdab2160 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { V2ProofService } from '../V2ProofService' + +import { V2PresentationProblemReportMessage } from '../messages' + +export class V2PresentationProblemReportHandler implements Handler { + private proofService: V2ProofService + public supportedMessages = [V2PresentationProblemReportMessage] + + public constructor(proofService: V2ProofService) { + this.proofService = proofService + } + + public async handle(messageContext: HandlerInboundMessage) { + await this.proofService.processProblemReport(messageContext) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts new file mode 100644 index 0000000000..232065fd58 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts @@ -0,0 +1,95 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { DidCommMessageRepository } from '../../../../../storage' +import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' +import type { ProofFormat } from '../../../formats/ProofFormat' +import type { + CreateProofRequestFromProposalOptions, + CreateRequestAsResponseOptions, + ProofRequestFromProposalOptions, +} from '../../../models/ProofServiceOptions' +import type { ProofRecord } from '../../../repository/ProofRecord' +import type { V2ProofService } from '../V2ProofService' + +import { createOutboundMessage } from '../../../../../agent/helpers' +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' + +export class V2ProposePresentationHandler implements Handler { + private proofService: V2ProofService + private agentConfig: AgentConfig + private didCommMessageRepository: DidCommMessageRepository + private proofResponseCoordinator: ProofResponseCoordinator + public supportedMessages = [V2ProposalPresentationMessage] + + public constructor( + proofService: V2ProofService, + agentConfig: AgentConfig, + didCommMessageRepository: DidCommMessageRepository, + proofResponseCoordinator: ProofResponseCoordinator + ) { + this.proofService = proofService + this.agentConfig = agentConfig + this.didCommMessageRepository = didCommMessageRepository + this.proofResponseCoordinator = proofResponseCoordinator + } + + public async handle(messageContext: HandlerInboundMessage) { + const proofRecord = await this.proofService.processProposal(messageContext) + + if (this.proofResponseCoordinator.shouldAutoRespondToProposal(messageContext.agentContext, proofRecord)) { + return this.createRequest(proofRecord, messageContext) + } + } + + private async createRequest( + proofRecord: ProofRecord, + messageContext: HandlerInboundMessage + ) { + this.agentConfig.logger.info( + `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptProofs}` + ) + + if (!messageContext.connection) { + this.agentConfig.logger.error('No connection on the messageContext') + throw new AriesFrameworkError('No connection on the messageContext') + } + + const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposalPresentationMessage, + }) + + if (!proposalMessage) { + this.agentConfig.logger.error(`Proof record with id ${proofRecord.id} is missing required credential proposal`) + throw new AriesFrameworkError(`Proof record with id ${proofRecord.id} is missing required credential proposal`) + } + + const proofRequestFromProposalOptions: CreateProofRequestFromProposalOptions = { + proofRecord, + } + + const proofRequest: ProofRequestFromProposalOptions = await this.proofService.createProofRequestFromProposal( + messageContext.agentContext, + proofRequestFromProposalOptions + ) + + const indyProofRequest = proofRequest.proofFormats + + if (!indyProofRequest) { + this.agentConfig.logger.error('Failed to create proof request') + throw new AriesFrameworkError('Failed to create proof request.') + } + + const options: CreateRequestAsResponseOptions = { + proofRecord: proofRecord, + autoAcceptProof: proofRecord.autoAcceptProof, + proofFormats: indyProofRequest, + willConfirm: true, + } + + const { message } = await this.proofService.createRequestAsResponse(messageContext.agentContext, options) + + return createOutboundMessage(messageContext.connection, message) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts new file mode 100644 index 0000000000..4f3c5ee123 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts @@ -0,0 +1,106 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' +import type { MediationRecipientService, RoutingService } from '../../../../routing' +import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' +import type { ProofFormat } from '../../../formats/ProofFormat' +import type { + FormatRequestedCredentialReturn, + FormatRetrievedCredentialOptions, +} from '../../../models/ProofServiceOptions' +import type { ProofRecord } from '../../../repository/ProofRecord' +import type { V2ProofService } from '../V2ProofService' + +import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' +import { DidCommMessageRole } from '../../../../../storage' +import { V2RequestPresentationMessage } from '../messages/V2RequestPresentationMessage' + +export class V2RequestPresentationHandler implements Handler { + private proofService: V2ProofService + private agentConfig: AgentConfig + private proofResponseCoordinator: ProofResponseCoordinator + private mediationRecipientService: MediationRecipientService + private didCommMessageRepository: DidCommMessageRepository + private routingService: RoutingService + public supportedMessages = [V2RequestPresentationMessage] + + public constructor( + proofService: V2ProofService, + agentConfig: AgentConfig, + proofResponseCoordinator: ProofResponseCoordinator, + mediationRecipientService: MediationRecipientService, + didCommMessageRepository: DidCommMessageRepository, + routingService: RoutingService + ) { + this.proofService = proofService + this.agentConfig = agentConfig + this.proofResponseCoordinator = proofResponseCoordinator + this.mediationRecipientService = mediationRecipientService + this.didCommMessageRepository = didCommMessageRepository + this.routingService = routingService + } + + public async handle(messageContext: HandlerInboundMessage) { + const proofRecord = await this.proofService.processRequest(messageContext) + if (this.proofResponseCoordinator.shouldAutoRespondToRequest(messageContext.agentContext, proofRecord)) { + return await this.createPresentation(proofRecord, messageContext) + } + } + + private async createPresentation( + record: ProofRecord, + messageContext: HandlerInboundMessage + ) { + const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + associatedRecordId: record.id, + messageClass: V2RequestPresentationMessage, + }) + + this.agentConfig.logger.info( + `Automatically sending presentation with autoAccept on ${this.agentConfig.autoAcceptProofs}` + ) + + const retrievedCredentials: FormatRetrievedCredentialOptions = + await this.proofService.getRequestedCredentialsForProofRequest(messageContext.agentContext, { + proofRecord: record, + config: { + filterByPresentationPreview: false, + }, + }) + + const requestedCredentials: FormatRequestedCredentialReturn = + await this.proofService.autoSelectCredentialsForProofRequest(retrievedCredentials) + + const { message, proofRecord } = await this.proofService.createPresentation(messageContext.agentContext, { + proofRecord: record, + proofFormats: requestedCredentials.proofFormats, + }) + + if (messageContext.connection) { + return createOutboundMessage(messageContext.connection, message) + } else if (requestMessage.service) { + const routing = await this.routingService.getRouting(messageContext.agentContext) + message.service = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.recipientKey.publicKeyBase58], + routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), + }) + const recipientService = requestMessage.service + + await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + return createOutboundServiceMessage({ + payload: message, + service: recipientService.resolvedDidCommService, + senderKey: message.service.resolvedDidCommService.recipientKeys[0], + }) + } + + this.agentConfig.logger.error(`Could not automatically create presentation`) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v2/index.ts b/packages/core/src/modules/proofs/protocol/v2/index.ts new file mode 100644 index 0000000000..960bb99e85 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/index.ts @@ -0,0 +1 @@ +export * from './V2ProofService' diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationAckMessage.ts b/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationAckMessage.ts new file mode 100644 index 0000000000..06918adb5b --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationAckMessage.ts @@ -0,0 +1,14 @@ +import type { PresentationAckMessageOptions } from '../../../messages/PresentationAckMessage' + +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { AckMessage } from '../../../../common/messages/AckMessage' + +export class V2PresentationAckMessage extends AckMessage { + public constructor(options: PresentationAckMessageOptions) { + super(options) + } + + @IsValidMessageType(V2PresentationAckMessage.type) + public readonly type = V2PresentationAckMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/present-proof/2.0/ack') +} diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationMessage.ts new file mode 100644 index 0000000000..f13915bc25 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationMessage.ts @@ -0,0 +1,93 @@ +import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' + +import { Expose, Type } from 'class-transformer' +import { IsArray, IsBoolean, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' + +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Attachment } from '../../../../../decorators/attachment/Attachment' +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { uuid } from '../../../../../utils/uuid' +import { ProofFormatSpec } from '../../../models/ProofFormatSpec' + +export interface V2PresentationMessageOptions { + id?: string + goalCode?: string + comment?: string + lastPresentation?: boolean + attachmentInfo: ProofAttachmentFormat[] +} + +export class V2PresentationMessage extends AgentMessage { + public constructor(options: V2PresentationMessageOptions) { + super() + + if (options) { + this.formats = [] + this.presentationsAttach = [] + this.id = options.id ?? uuid() + this.comment = options.comment + this.goalCode = options.goalCode + this.lastPresentation = options.lastPresentation ?? true + + for (const entry of options.attachmentInfo) { + this.addPresentationsAttachment(entry) + } + } + } + + public addPresentationsAttachment(attachment: ProofAttachmentFormat) { + this.formats.push(attachment.format) + this.presentationsAttach.push(attachment.attachment) + } + + /** + * Every attachment has a corresponding entry in the formats array. + * This method pairs those together in a {@link ProofAttachmentFormat} object. + */ + public getAttachmentFormats(): ProofAttachmentFormat[] { + const attachmentFormats: ProofAttachmentFormat[] = [] + + this.formats.forEach((format) => { + const attachment = this.presentationsAttach.find((attachment) => attachment.id === format.attachmentId) + + if (!attachment) { + throw new AriesFrameworkError(`Could not find a matching attachment with attachmentId: ${format.attachmentId}`) + } + + attachmentFormats.push({ format, attachment }) + }) + return attachmentFormats + } + + @IsValidMessageType(V2PresentationMessage.type) + public readonly type = V2PresentationMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/present-proof/2.0/presentation') + + @IsString() + @IsOptional() + public comment?: string + + @Expose({ name: 'goal_code' }) + @IsString() + @IsOptional() + public goalCode?: string + + @Expose({ name: 'last_presentation' }) + @IsBoolean() + public lastPresentation = true + + @Expose({ name: 'formats' }) + @Type(() => ProofFormatSpec) + @IsArray() + @ValidateNested({ each: true }) + @IsInstance(ProofFormatSpec, { each: true }) + public formats!: ProofFormatSpec[] + + @Expose({ name: 'presentations~attach' }) + @Type(() => Attachment) + @IsArray() + @ValidateNested({ each: true }) + @IsInstance(Attachment, { each: true }) + public presentationsAttach!: Attachment[] +} diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationProblemReportMessage.ts b/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationProblemReportMessage.ts new file mode 100644 index 0000000000..b36a69a7fe --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationProblemReportMessage.ts @@ -0,0 +1,23 @@ +import type { ProblemReportMessageOptions } from '../../../../problem-reports/messages/ProblemReportMessage' + +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { ProblemReportMessage } from '../../../../problem-reports/messages/ProblemReportMessage' + +export type V2PresentationProblemReportMessageOptions = ProblemReportMessageOptions + +/** + * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md + */ +export class V2PresentationProblemReportMessage extends ProblemReportMessage { + /** + * Create new PresentationProblemReportMessage instance. + * @param options + */ + public constructor(options: V2PresentationProblemReportMessageOptions) { + super(options) + } + + @IsValidMessageType(V2PresentationProblemReportMessage.type) + public readonly type = V2PresentationProblemReportMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/present-proof/2.0/problem-report') +} diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposalPresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposalPresentationMessage.ts new file mode 100644 index 0000000000..265ed8ae7e --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposalPresentationMessage.ts @@ -0,0 +1,100 @@ +import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' + +import { Expose, Type } from 'class-transformer' +import { IsArray, IsBoolean, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' + +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Attachment } from '../../../../../decorators/attachment/Attachment' +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { uuid } from '../../../../../utils/uuid' +import { ProofFormatSpec } from '../../../models/ProofFormatSpec' + +export interface V2ProposePresentationMessageOptions { + id?: string + comment?: string + goalCode?: string + willConfirm?: boolean + parentThreadId?: string + attachmentInfo: ProofAttachmentFormat[] +} + +export class V2ProposalPresentationMessage extends AgentMessage { + public constructor(options: V2ProposePresentationMessageOptions) { + super() + + if (options) { + this.formats = [] + this.proposalsAttach = [] + this.id = options.id ?? uuid() + this.comment = options.comment + this.goalCode = options.goalCode + this.willConfirm = options.willConfirm ?? false + + if (options.parentThreadId) { + this.setThread({ + parentThreadId: options.parentThreadId, + }) + } + + for (const entry of options.attachmentInfo) { + this.addProposalsAttachment(entry) + } + } + } + + public addProposalsAttachment(attachment: ProofAttachmentFormat) { + this.formats.push(attachment.format) + this.proposalsAttach.push(attachment.attachment) + } + + /** + * Every attachment has a corresponding entry in the formats array. + * This method pairs those together in a {@link ProofAttachmentFormat} object. + */ + public getAttachmentFormats(): ProofAttachmentFormat[] { + const attachmentFormats: ProofAttachmentFormat[] = [] + + this.formats.forEach((format) => { + const attachment = this.proposalsAttach.find((attachment) => attachment.id === format.attachmentId) + + if (!attachment) { + throw new AriesFrameworkError(`Could not find a matching attachment with attachmentId: ${format.attachmentId}`) + } + + attachmentFormats.push({ format, attachment }) + }) + return attachmentFormats + } + + @IsValidMessageType(V2ProposalPresentationMessage.type) + public readonly type = V2ProposalPresentationMessage.type.messageTypeUri + public static readonly type = parseMessageType(`https://didcomm.org/present-proof/2.0/propose-presentation`) + + @IsString() + @IsOptional() + public comment?: string + + @Expose({ name: 'goal_code' }) + @IsString() + @IsOptional() + public goalCode?: string + + @Expose({ name: 'will_confirm' }) + @IsBoolean() + public willConfirm = false + + @Expose({ name: 'formats' }) + @Type(() => ProofFormatSpec) + @IsArray() + @ValidateNested({ each: true }) + @IsInstance(ProofFormatSpec, { each: true }) + public formats!: ProofFormatSpec[] + + @Expose({ name: 'proposals~attach' }) + @Type(() => Attachment) + @IsArray() + @ValidateNested({ each: true }) + @IsInstance(Attachment, { each: true }) + public proposalsAttach!: Attachment[] +} diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/V2RequestPresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v2/messages/V2RequestPresentationMessage.ts new file mode 100644 index 0000000000..060badc050 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/messages/V2RequestPresentationMessage.ts @@ -0,0 +1,125 @@ +import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' + +import { Expose, Type } from 'class-transformer' +import { IsArray, IsBoolean, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' + +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Attachment } from '../../../../../decorators/attachment/Attachment' +import { AriesFrameworkError } from '../../../../../error' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { uuid } from '../../../../../utils/uuid' +import { ProofFormatSpec } from '../../../models/ProofFormatSpec' + +export interface V2RequestPresentationMessageOptions { + id?: string + comment?: string + goalCode?: string + presentMultiple?: boolean + willConfirm?: boolean + parentThreadId?: string + attachmentInfo: ProofAttachmentFormat[] +} + +export class V2RequestPresentationMessage extends AgentMessage { + public constructor(options: V2RequestPresentationMessageOptions) { + super() + + if (options) { + this.formats = [] + this.requestPresentationsAttach = [] + this.id = options.id ?? uuid() + this.comment = options.comment + this.goalCode = options.goalCode + this.willConfirm = options.willConfirm ?? true + this.presentMultiple = options.presentMultiple ?? false + + if (options.parentThreadId) { + this.setThread({ + parentThreadId: options.parentThreadId, + }) + } + + for (const entry of options.attachmentInfo) { + this.addRequestPresentationsAttachment(entry) + } + } + } + + public addRequestPresentationsAttachment(attachment: ProofAttachmentFormat) { + this.formats.push(attachment.format) + this.requestPresentationsAttach.push(attachment.attachment) + } + + public getAttachmentByFormatIdentifier(formatIdentifier: string) { + const format = this.formats.find((x) => x.format === formatIdentifier) + if (!format) { + throw new AriesFrameworkError( + `Expected to find a format entry of type: ${formatIdentifier}, but none could be found.` + ) + } + + const attachment = this.requestPresentationsAttach.find((x) => x.id === format.attachmentId) + + if (!attachment) { + throw new AriesFrameworkError( + `Expected to find an attachment entry with id: ${format.attachmentId}, but none could be found.` + ) + } + + return attachment + } + + /** + * Every attachment has a corresponding entry in the formats array. + * This method pairs those together in a {@link ProofAttachmentFormat} object. + */ + public getAttachmentFormats(): ProofAttachmentFormat[] { + const attachmentFormats: ProofAttachmentFormat[] = [] + + this.formats.forEach((format) => { + const attachment = this.requestPresentationsAttach.find((attachment) => attachment.id === format.attachmentId) + + if (!attachment) { + throw new AriesFrameworkError(`Could not find a matching attachment with attachmentId: ${format.attachmentId}`) + } + + attachmentFormats.push({ format, attachment }) + }) + return attachmentFormats + } + + @IsValidMessageType(V2RequestPresentationMessage.type) + public readonly type = V2RequestPresentationMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/present-proof/2.0/request-presentation') + + @IsString() + @IsOptional() + public comment?: string + + @Expose({ name: 'goal_code' }) + @IsString() + @IsOptional() + public goalCode?: string + + @Expose({ name: 'will_confirm' }) + @IsBoolean() + public willConfirm = false + + @Expose({ name: 'present_multiple' }) + @IsBoolean() + public presentMultiple = false + + @Expose({ name: 'formats' }) + @Type(() => ProofFormatSpec) + @IsArray() + @ValidateNested({ each: true }) + @IsInstance(ProofFormatSpec, { each: true }) + public formats!: ProofFormatSpec[] + + @Expose({ name: 'request_presentations~attach' }) + @Type(() => Attachment) + @IsArray() + @ValidateNested({ each: true }) + @IsInstance(Attachment, { each: true }) + public requestPresentationsAttach!: Attachment[] +} diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/index.ts b/packages/core/src/modules/proofs/protocol/v2/messages/index.ts new file mode 100644 index 0000000000..8b0c4a005d --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/messages/index.ts @@ -0,0 +1,5 @@ +export * from './V2PresentationAckMessage' +export * from './V2PresentationMessage' +export * from './V2PresentationProblemReportMessage' +export * from './V2ProposalPresentationMessage' +export * from './V2RequestPresentationMessage' diff --git a/packages/core/src/modules/proofs/repository/PresentationExchangeRecord.ts b/packages/core/src/modules/proofs/repository/PresentationExchangeRecord.ts new file mode 100644 index 0000000000..f9fa20f6b9 --- /dev/null +++ b/packages/core/src/modules/proofs/repository/PresentationExchangeRecord.ts @@ -0,0 +1,4 @@ +export enum PresentationRecordType { + Indy = 'indy', + PresentationExchange = 'presentationExchange', +} diff --git a/packages/core/src/modules/proofs/repository/ProofRecord.ts b/packages/core/src/modules/proofs/repository/ProofRecord.ts index bd2cc487da..bf03ad2bf6 100644 --- a/packages/core/src/modules/proofs/repository/ProofRecord.ts +++ b/packages/core/src/modules/proofs/repository/ProofRecord.ts @@ -1,32 +1,24 @@ import type { TagsBase } from '../../../storage/BaseRecord' -import type { AutoAcceptProof } from '../ProofAutoAcceptType' -import type { ProofState } from '../ProofState' - -import { Type } from 'class-transformer' +import type { AutoAcceptProof } from '../models/ProofAutoAcceptType' +import type { ProofProtocolVersion } from '../models/ProofProtocolVersion' +import type { ProofState } from '../models/ProofState' import { AriesFrameworkError } from '../../../error' import { BaseRecord } from '../../../storage/BaseRecord' import { uuid } from '../../../utils/uuid' -import { ProposePresentationMessage, RequestPresentationMessage, PresentationMessage } from '../messages' export interface ProofRecordProps { id?: string createdAt?: Date - + protocolVersion: ProofProtocolVersion isVerified?: boolean state: ProofState connectionId?: string threadId: string parentThreadId?: string - presentationId?: string tags?: CustomProofTags autoAcceptProof?: AutoAcceptProof errorMessage?: string - - // message data - proposalMessage?: ProposePresentationMessage - requestMessage?: RequestPresentationMessage - presentationMessage?: PresentationMessage } export type CustomProofTags = TagsBase @@ -37,24 +29,18 @@ export type DefaultProofTags = { state: ProofState } +// T-TODO: rename to proof exchange record + export class ProofRecord extends BaseRecord { public connectionId?: string public threadId!: string + public protocolVersion!: ProofProtocolVersion public parentThreadId?: string public isVerified?: boolean - public presentationId?: string public state!: ProofState public autoAcceptProof?: AutoAcceptProof public errorMessage?: string - // message data - @Type(() => ProposePresentationMessage) - public proposalMessage?: ProposePresentationMessage - @Type(() => RequestPresentationMessage) - public requestMessage?: RequestPresentationMessage - @Type(() => PresentationMessage) - public presentationMessage?: PresentationMessage - public static readonly type = 'ProofRecord' public readonly type = ProofRecord.type @@ -64,15 +50,13 @@ export class ProofRecord extends BaseRecord { if (props) { this.id = props.id ?? uuid() this.createdAt = props.createdAt ?? new Date() - this.proposalMessage = props.proposalMessage - this.requestMessage = props.requestMessage - this.presentationMessage = props.presentationMessage + this.protocolVersion = props.protocolVersion + this.isVerified = props.isVerified this.state = props.state this.connectionId = props.connectionId this.threadId = props.threadId this.parentThreadId = props.parentThreadId - this.presentationId = props.presentationId this.autoAcceptProof = props.autoAcceptProof this._tags = props.tags ?? {} this.errorMessage = props.errorMessage diff --git a/packages/core/src/modules/proofs/repository/ProofRepository.ts b/packages/core/src/modules/proofs/repository/ProofRepository.ts index 5acf95218a..cb7a5033ef 100644 --- a/packages/core/src/modules/proofs/repository/ProofRepository.ts +++ b/packages/core/src/modules/proofs/repository/ProofRepository.ts @@ -1,3 +1,5 @@ +import type { AgentContext } from '../../../agent/context' + import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' import { inject, injectable } from '../../../plugins' @@ -14,4 +16,36 @@ export class ProofRepository extends Repository { ) { super(ProofRecord, storageService, eventEmitter) } + + /** + * Retrieve a proof record by connection id and thread id + * + * @param connectionId The connection id + * @param threadId The thread id + * @throws {RecordNotFoundError} If no record is found + * @throws {RecordDuplicateError} If multiple records are found + * @returns The proof record + */ + public async getByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise { + return this.getSingleByQuery(agentContext, { threadId, connectionId }) + } + + /** + * Retrieve proof records by connection id and parent thread id + * + * @param connectionId The connection id + * @param parentThreadId The parent thread id + * @returns List containing all proof records matching the given query + */ + public async getByParentThreadAndConnectionId( + agentContext: AgentContext, + parentThreadId: string, + connectionId?: string + ): Promise { + return this.findByQuery(agentContext, { parentThreadId, connectionId }) + } } diff --git a/packages/core/src/modules/proofs/repository/index.ts b/packages/core/src/modules/proofs/repository/index.ts index 23ca5ba9a3..8c1bf5235e 100644 --- a/packages/core/src/modules/proofs/repository/index.ts +++ b/packages/core/src/modules/proofs/repository/index.ts @@ -1,2 +1,3 @@ export * from './ProofRecord' export * from './ProofRepository' +export * from './PresentationExchangeRecord' diff --git a/packages/core/src/modules/proofs/services/ProofService.ts b/packages/core/src/modules/proofs/services/ProofService.ts deleted file mode 100644 index 5105a17a97..0000000000 --- a/packages/core/src/modules/proofs/services/ProofService.ts +++ /dev/null @@ -1,1222 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type { AgentMessage } from '../../../agent/AgentMessage' -import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import type { ConnectionRecord } from '../../connections' -import type { AutoAcceptProof } from '../ProofAutoAcceptType' -import type { ProofStateChangedEvent } from '../ProofEvents' -import type { PresentationPreview, PresentationPreviewAttribute } from '../messages' -import type { PresentationProblemReportMessage } from './../messages/PresentationProblemReportMessage' -import type { CredDef, IndyProof, Schema } from 'indy-sdk' - -import { validateOrReject } from 'class-validator' - -import { EventEmitter } from '../../../agent/EventEmitter' -import { InjectionSymbols } from '../../../constants' -import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' -import { AriesFrameworkError } from '../../../error' -import { Logger } from '../../../logger' -import { inject, injectable } from '../../../plugins' -import { JsonEncoder } from '../../../utils/JsonEncoder' -import { JsonTransformer } from '../../../utils/JsonTransformer' -import { checkProofRequestForDuplicates } from '../../../utils/indyProofRequest' -import { uuid } from '../../../utils/uuid' -import { AckStatus } from '../../common' -import { ConnectionService } from '../../connections' -import { CredentialRepository, IndyCredential, IndyCredentialInfo } from '../../credentials' -import { IndyCredentialUtils } from '../../credentials/formats/indy/IndyCredentialUtils' -import { IndyHolderService, IndyRevocationService, IndyVerifierService } from '../../indy' -import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' -import { ProofEventTypes } from '../ProofEvents' -import { ProofState } from '../ProofState' -import { PresentationProblemReportError, PresentationProblemReportReason } from '../errors' -import { - INDY_PROOF_ATTACHMENT_ID, - INDY_PROOF_REQUEST_ATTACHMENT_ID, - PresentationAckMessage, - PresentationMessage, - ProposePresentationMessage, - RequestPresentationMessage, -} from '../messages' -import { - AttributeFilter, - PartialProof, - ProofAttributeInfo, - ProofPredicateInfo, - ProofRequest, - RequestedAttribute, - RequestedCredentials, - RequestedPredicate, - RetrievedCredentials, -} from '../models' -import { ProofRepository } from '../repository' -import { ProofRecord } from '../repository/ProofRecord' - -/** - * @todo add method to check if request matches proposal. Useful to see if a request I received is the same as the proposal I sent. - * @todo add method to reject / revoke messages - * @todo validate attachments / messages - */ -@injectable() -export class ProofService { - private proofRepository: ProofRepository - private credentialRepository: CredentialRepository - private ledgerService: IndyLedgerService - private logger: Logger - private indyHolderService: IndyHolderService - private indyVerifierService: IndyVerifierService - private indyRevocationService: IndyRevocationService - private connectionService: ConnectionService - private eventEmitter: EventEmitter - - public constructor( - proofRepository: ProofRepository, - ledgerService: IndyLedgerService, - indyHolderService: IndyHolderService, - indyVerifierService: IndyVerifierService, - indyRevocationService: IndyRevocationService, - connectionService: ConnectionService, - eventEmitter: EventEmitter, - credentialRepository: CredentialRepository, - @inject(InjectionSymbols.Logger) logger: Logger - ) { - this.proofRepository = proofRepository - this.credentialRepository = credentialRepository - this.ledgerService = ledgerService - this.logger = logger - this.indyHolderService = indyHolderService - this.indyVerifierService = indyVerifierService - this.indyRevocationService = indyRevocationService - this.connectionService = connectionService - this.eventEmitter = eventEmitter - } - - /** - * Create a {@link ProposePresentationMessage} not bound to an existing presentation exchange. - * To create a proposal as response to an existing presentation exchange, use {@link ProofService.createProposalAsResponse}. - * - * @param connectionRecord The connection for which to create the presentation proposal - * @param presentationProposal The presentation proposal to include in the message - * @param config Additional configuration to use for the proposal - * @returns Object containing proposal message and associated proof record - * - */ - public async createProposal( - agentContext: AgentContext, - connectionRecord: ConnectionRecord, - presentationProposal: PresentationPreview, - config?: { - comment?: string - autoAcceptProof?: AutoAcceptProof - parentThreadId?: string - } - ): Promise> { - // Assert - connectionRecord.assertReady() - - // Create message - const proposalMessage = new ProposePresentationMessage({ - comment: config?.comment, - presentationProposal, - parentThreadId: config?.parentThreadId, - }) - - // Create record - const proofRecord = new ProofRecord({ - connectionId: connectionRecord.id, - threadId: proposalMessage.threadId, - parentThreadId: proposalMessage.thread?.parentThreadId, - state: ProofState.ProposalSent, - proposalMessage, - autoAcceptProof: config?.autoAcceptProof, - }) - await this.proofRepository.save(agentContext, proofRecord) - this.emitStateChangedEvent(agentContext, proofRecord, null) - - return { message: proposalMessage, proofRecord } - } - - /** - * Create a {@link ProposePresentationMessage} as response to a received presentation request. - * To create a proposal not bound to an existing presentation exchange, use {@link ProofService.createProposal}. - * - * @param proofRecord The proof record for which to create the presentation proposal - * @param presentationProposal The presentation proposal to include in the message - * @param config Additional configuration to use for the proposal - * @returns Object containing proposal message and associated proof record - * - */ - public async createProposalAsResponse( - agentContext: AgentContext, - proofRecord: ProofRecord, - presentationProposal: PresentationPreview, - config?: { - comment?: string - } - ): Promise> { - // Assert - proofRecord.assertState(ProofState.RequestReceived) - - // Create message - const proposalMessage = new ProposePresentationMessage({ - comment: config?.comment, - presentationProposal, - }) - proposalMessage.setThread({ threadId: proofRecord.threadId }) - - // Update record - proofRecord.proposalMessage = proposalMessage - await this.updateState(agentContext, proofRecord, ProofState.ProposalSent) - - return { message: proposalMessage, proofRecord } - } - - /** - * Decline a proof request - * @param proofRecord The proof request to be declined - */ - public async declineRequest(agentContext: AgentContext, proofRecord: ProofRecord): Promise { - proofRecord.assertState(ProofState.RequestReceived) - - await this.updateState(agentContext, proofRecord, ProofState.Declined) - - return proofRecord - } - - /** - * Process a received {@link ProposePresentationMessage}. This will not accept the presentation proposal - * or send a presentation request. It will only create a new, or update the existing proof record with - * the information from the presentation proposal message. Use {@link ProofService.createRequestAsResponse} - * after calling this method to create a presentation request. - * - * @param messageContext The message context containing a presentation proposal message - * @returns proof record associated with the presentation proposal message - * - */ - public async processProposal( - messageContext: InboundMessageContext - ): Promise { - let proofRecord: ProofRecord - const { message: proposalMessage, connection } = messageContext - - this.logger.debug(`Processing presentation proposal with id ${proposalMessage.id}`) - - try { - // Proof record already exists - proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - proposalMessage.threadId, - connection?.id - ) - - // Assert - proofRecord.assertState(ProofState.RequestSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proofRecord.proposalMessage, - previousSentMessage: proofRecord.requestMessage, - }) - - // Update record - proofRecord.proposalMessage = proposalMessage - await this.updateState(messageContext.agentContext, proofRecord, ProofState.ProposalReceived) - } catch { - // No proof record exists with thread id - proofRecord = new ProofRecord({ - connectionId: connection?.id, - threadId: proposalMessage.threadId, - parentThreadId: proposalMessage.thread?.parentThreadId, - proposalMessage, - state: ProofState.ProposalReceived, - }) - - // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) - - // Save record - await this.proofRepository.save(messageContext.agentContext, proofRecord) - this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) - } - - return proofRecord - } - - /** - * Create a {@link RequestPresentationMessage} as response to a received presentation proposal. - * To create a request not bound to an existing presentation exchange, use {@link ProofService.createRequest}. - * - * @param proofRecord The proof record for which to create the presentation request - * @param proofRequest The proof request to include in the message - * @param config Additional configuration to use for the request - * @returns Object containing request message and associated proof record - * - */ - public async createRequestAsResponse( - agentContext: AgentContext, - proofRecord: ProofRecord, - proofRequest: ProofRequest, - config?: { - comment?: string - } - ): Promise> { - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(proofRequest) - - // Assert - proofRecord.assertState(ProofState.ProposalReceived) - - // Create message - const attachment = new Attachment({ - id: INDY_PROOF_REQUEST_ATTACHMENT_ID, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(proofRequest), - }), - }) - const requestPresentationMessage = new RequestPresentationMessage({ - comment: config?.comment, - requestPresentationAttachments: [attachment], - }) - requestPresentationMessage.setThread({ - threadId: proofRecord.threadId, - }) - - // Update record - proofRecord.requestMessage = requestPresentationMessage - await this.updateState(agentContext, proofRecord, ProofState.RequestSent) - - return { message: requestPresentationMessage, proofRecord } - } - - /** - * Create a {@link RequestPresentationMessage} not bound to an existing presentation exchange. - * To create a request as response to an existing presentation exchange, use {@link ProofService#createRequestAsResponse}. - * - * @param proofRequestTemplate The proof request template - * @param connectionRecord The connection for which to create the presentation request - * @returns Object containing request message and associated proof record - * - */ - public async createRequest( - agentContext: AgentContext, - proofRequest: ProofRequest, - connectionRecord?: ConnectionRecord, - config?: { - comment?: string - autoAcceptProof?: AutoAcceptProof - parentThreadId?: string - } - ): Promise> { - this.logger.debug(`Creating proof request`) - - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(proofRequest) - - // Assert - connectionRecord?.assertReady() - - // Create message - const attachment = new Attachment({ - id: INDY_PROOF_REQUEST_ATTACHMENT_ID, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(proofRequest), - }), - }) - const requestPresentationMessage = new RequestPresentationMessage({ - comment: config?.comment, - requestPresentationAttachments: [attachment], - parentThreadId: config?.parentThreadId, - }) - - // Create record - const proofRecord = new ProofRecord({ - connectionId: connectionRecord?.id, - threadId: requestPresentationMessage.threadId, - parentThreadId: requestPresentationMessage.thread?.parentThreadId, - requestMessage: requestPresentationMessage, - state: ProofState.RequestSent, - autoAcceptProof: config?.autoAcceptProof, - }) - - await this.proofRepository.save(agentContext, proofRecord) - this.emitStateChangedEvent(agentContext, proofRecord, null) - - return { message: requestPresentationMessage, proofRecord } - } - - /** - * Process a received {@link RequestPresentationMessage}. This will not accept the presentation request - * or send a presentation. It will only create a new, or update the existing proof record with - * the information from the presentation request message. Use {@link ProofService.createPresentation} - * after calling this method to create a presentation. - * - * @param messageContext The message context containing a presentation request message - * @returns proof record associated with the presentation request message - * - */ - public async processRequest(messageContext: InboundMessageContext): Promise { - let proofRecord: ProofRecord - const { message: proofRequestMessage, connection } = messageContext - - this.logger.debug(`Processing presentation request with id ${proofRequestMessage.id}`) - - const proofRequest = proofRequestMessage.indyProofRequest - - // Assert attachment - if (!proofRequest) { - throw new PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation request with thread id ${proofRequestMessage.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - await validateOrReject(proofRequest) - - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(proofRequest) - - this.logger.debug('received proof request', proofRequest) - - try { - // Proof record already exists - proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - proofRequestMessage.threadId, - connection?.id - ) - - // Assert - proofRecord.assertState(ProofState.ProposalSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proofRecord.requestMessage, - previousSentMessage: proofRecord.proposalMessage, - }) - - // Update record - proofRecord.requestMessage = proofRequestMessage - await this.updateState(messageContext.agentContext, proofRecord, ProofState.RequestReceived) - } catch { - // No proof record exists with thread id - proofRecord = new ProofRecord({ - connectionId: connection?.id, - threadId: proofRequestMessage.threadId, - parentThreadId: proofRequestMessage.thread?.parentThreadId, - requestMessage: proofRequestMessage, - state: ProofState.RequestReceived, - }) - - // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) - - // Save in repository - await this.proofRepository.save(messageContext.agentContext, proofRecord) - this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) - } - - return proofRecord - } - - /** - * Create a {@link PresentationMessage} as response to a received presentation request. - * - * @param proofRecord The proof record for which to create the presentation - * @param requestedCredentials The requested credentials object specifying which credentials to use for the proof - * @param config Additional configuration to use for the presentation - * @returns Object containing presentation message and associated proof record - * - */ - public async createPresentation( - agentContext: AgentContext, - proofRecord: ProofRecord, - requestedCredentials: RequestedCredentials, - config?: { - comment?: string - } - ): Promise> { - this.logger.debug(`Creating presentation for proof record with id ${proofRecord.id}`) - - // Assert - proofRecord.assertState(ProofState.RequestReceived) - - const indyProofRequest = proofRecord.requestMessage?.indyProofRequest - if (!indyProofRequest) { - throw new PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation with thread id ${proofRecord.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - - // Get the matching attachments to the requested credentials - const attachments = await this.getRequestedAttachmentsForRequestedCredentials( - agentContext, - indyProofRequest, - requestedCredentials - ) - - // Create proof - const proof = await this.createProof(agentContext, indyProofRequest, requestedCredentials) - - // Create message - const attachment = new Attachment({ - id: INDY_PROOF_ATTACHMENT_ID, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(proof), - }), - }) - - const presentationMessage = new PresentationMessage({ - comment: config?.comment, - presentationAttachments: [attachment], - attachments, - }) - presentationMessage.setThread({ threadId: proofRecord.threadId }) - - // Update record - proofRecord.presentationMessage = presentationMessage - await this.updateState(agentContext, proofRecord, ProofState.PresentationSent) - - return { message: presentationMessage, proofRecord } - } - - /** - * Process a received {@link PresentationMessage}. This will not accept the presentation - * or send a presentation acknowledgement. It will only update the existing proof record with - * the information from the presentation message. Use {@link ProofService.createAck} - * after calling this method to create a presentation acknowledgement. - * - * @param messageContext The message context containing a presentation message - * @returns proof record associated with the presentation message - * - */ - public async processPresentation(messageContext: InboundMessageContext): Promise { - const { message: presentationMessage, connection } = messageContext - - this.logger.debug(`Processing presentation with id ${presentationMessage.id}`) - - const proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - presentationMessage.threadId, - connection?.id - ) - - // Assert - proofRecord.assertState(ProofState.RequestSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proofRecord.proposalMessage, - previousSentMessage: proofRecord.requestMessage, - }) - - // TODO: add proof class with validator - const indyProofJson = presentationMessage.indyProof - const indyProofRequest = proofRecord.requestMessage?.indyProofRequest - - if (!indyProofJson) { - throw new PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation with thread id ${presentationMessage.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - - if (!indyProofRequest) { - throw new PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation request with thread id ${presentationMessage.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - - const isValid = await this.verifyProof(messageContext.agentContext, indyProofJson, indyProofRequest) - - // Update record - proofRecord.isVerified = isValid - proofRecord.presentationMessage = presentationMessage - await this.updateState(messageContext.agentContext, proofRecord, ProofState.PresentationReceived) - - return proofRecord - } - - /** - * Create a {@link PresentationAckMessage} as response to a received presentation. - * - * @param proofRecord The proof record for which to create the presentation acknowledgement - * @returns Object containing presentation acknowledgement message and associated proof record - * - */ - public async createAck( - agentContext: AgentContext, - proofRecord: ProofRecord - ): Promise> { - this.logger.debug(`Creating presentation ack for proof record with id ${proofRecord.id}`) - - // Assert - proofRecord.assertState(ProofState.PresentationReceived) - - // Create message - const ackMessage = new PresentationAckMessage({ - status: AckStatus.OK, - threadId: proofRecord.threadId, - }) - - // Update record - await this.updateState(agentContext, proofRecord, ProofState.Done) - - return { message: ackMessage, proofRecord } - } - - /** - * Process a received {@link PresentationAckMessage}. - * - * @param messageContext The message context containing a presentation acknowledgement message - * @returns proof record associated with the presentation acknowledgement message - * - */ - public async processAck(messageContext: InboundMessageContext): Promise { - const { message: presentationAckMessage, connection } = messageContext - - this.logger.debug(`Processing presentation ack with id ${presentationAckMessage.id}`) - - const proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - presentationAckMessage.threadId, - connection?.id - ) - - // Assert - proofRecord.assertState(ProofState.PresentationSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proofRecord.requestMessage, - previousSentMessage: proofRecord.presentationMessage, - }) - - // Update record - await this.updateState(messageContext.agentContext, proofRecord, ProofState.Done) - - return proofRecord - } - - /** - * Process a received {@link PresentationProblemReportMessage}. - * - * @param messageContext The message context containing a presentation problem report message - * @returns proof record associated with the presentation acknowledgement message - * - */ - public async processProblemReport( - messageContext: InboundMessageContext - ): Promise { - const { message: presentationProblemReportMessage } = messageContext - - const connection = messageContext.assertReadyConnection() - - this.logger.debug(`Processing problem report with id ${presentationProblemReportMessage.id}`) - - const proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - presentationProblemReportMessage.threadId, - connection?.id - ) - - proofRecord.errorMessage = `${presentationProblemReportMessage.description.code}: ${presentationProblemReportMessage.description.en}` - await this.update(messageContext.agentContext, proofRecord) - return proofRecord - } - - public async generateProofRequestNonce(agentContext: AgentContext) { - return agentContext.wallet.generateNonce() - } - - /** - * Create a {@link ProofRequest} from a presentation proposal. This method can be used to create the - * proof request from a received proposal for use in {@link ProofService.createRequestAsResponse} - * - * @param presentationProposal The presentation proposal to create a proof request from - * @param config Additional configuration to use for the proof request - * @returns proof request object - * - */ - public async createProofRequestFromProposal( - agentContext: AgentContext, - presentationProposal: PresentationPreview, - config: { name: string; version: string; nonce?: string } - ): Promise { - const nonce = config.nonce ?? (await this.generateProofRequestNonce(agentContext)) - - const proofRequest = new ProofRequest({ - name: config.name, - version: config.version, - nonce, - }) - - /** - * Create mapping of attributes by referent. This required the - * attributes to come from the same credential. - * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#referent - * - * { - * "referent1": [Attribute1, Attribute2], - * "referent2": [Attribute3] - * } - */ - const attributesByReferent: Record = {} - for (const proposedAttributes of presentationProposal.attributes) { - if (!proposedAttributes.referent) proposedAttributes.referent = uuid() - - const referentAttributes = attributesByReferent[proposedAttributes.referent] - - // Referent key already exist, add to list - if (referentAttributes) { - referentAttributes.push(proposedAttributes) - } - // Referent key does not exist yet, create new entry - else { - attributesByReferent[proposedAttributes.referent] = [proposedAttributes] - } - } - - // Transform attributes by referent to requested attributes - for (const [referent, proposedAttributes] of Object.entries(attributesByReferent)) { - // Either attributeName or attributeNames will be undefined - const attributeName = proposedAttributes.length == 1 ? proposedAttributes[0].name : undefined - const attributeNames = proposedAttributes.length > 1 ? proposedAttributes.map((a) => a.name) : undefined - - const requestedAttribute = new ProofAttributeInfo({ - name: attributeName, - names: attributeNames, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: proposedAttributes[0].credentialDefinitionId, - }), - ], - }) - - proofRequest.requestedAttributes.set(referent, requestedAttribute) - } - - this.logger.debug('proposal predicates', presentationProposal.predicates) - // Transform proposed predicates to requested predicates - for (const proposedPredicate of presentationProposal.predicates) { - const requestedPredicate = new ProofPredicateInfo({ - name: proposedPredicate.name, - predicateType: proposedPredicate.predicate, - predicateValue: proposedPredicate.threshold, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: proposedPredicate.credentialDefinitionId, - }), - ], - }) - - proofRequest.requestedPredicates.set(uuid(), requestedPredicate) - } - - return proofRequest - } - - /** - * Retrieves the linked attachments for an {@link indyProofRequest} - * @param indyProofRequest The proof request for which the linked attachments have to be found - * @param requestedCredentials The requested credentials - * @returns a list of attachments that are linked to the requested credentials - */ - public async getRequestedAttachmentsForRequestedCredentials( - agentContext: AgentContext, - indyProofRequest: ProofRequest, - requestedCredentials: RequestedCredentials - ): Promise { - const attachments: Attachment[] = [] - const credentialIds = new Set() - const requestedAttributesNames: (string | undefined)[] = [] - - // Get the credentialIds if it contains a hashlink - for (const [referent, requestedAttribute] of Object.entries(requestedCredentials.requestedAttributes)) { - // Find the requested Attributes - const requestedAttributes = indyProofRequest.requestedAttributes.get(referent) as ProofAttributeInfo - - // List the requested attributes - requestedAttributesNames.push(...(requestedAttributes.names ?? [requestedAttributes.name])) - - //Get credentialInfo - if (!requestedAttribute.credentialInfo) { - const indyCredentialInfo = await this.indyHolderService.getCredential( - agentContext, - requestedAttribute.credentialId - ) - requestedAttribute.credentialInfo = JsonTransformer.fromJSON(indyCredentialInfo, IndyCredentialInfo) - } - - // Find the attributes that have a hashlink as a value - for (const attribute of Object.values(requestedAttribute.credentialInfo.attributes)) { - if (attribute.toLowerCase().startsWith('hl:')) { - credentialIds.add(requestedAttribute.credentialId) - } - } - } - - // Only continues if there is an attribute value that contains a hashlink - for (const credentialId of credentialIds) { - // Get the credentialRecord that matches the ID - - const credentialRecord = await this.credentialRepository.getSingleByQuery(agentContext, { - credentialIds: [credentialId], - }) - - if (credentialRecord.linkedAttachments) { - // Get the credentials that have a hashlink as value and are requested - const requestedCredentials = credentialRecord.credentialAttributes?.filter( - (credential) => - credential.value.toLowerCase().startsWith('hl:') && requestedAttributesNames.includes(credential.name) - ) - - // Get the linked attachments that match the requestedCredentials - const linkedAttachments = credentialRecord.linkedAttachments.filter((attachment) => - requestedCredentials?.map((credential) => credential.value.split(':')[1]).includes(attachment.id) - ) - - if (linkedAttachments) { - attachments.push(...linkedAttachments) - } - } - } - - return attachments.length ? attachments : undefined - } - - /** - * Create a {@link RetrievedCredentials} object. Given input proof request and presentation proposal, - * use credentials in the wallet to build indy requested credentials object for input to proof creation. - * If restrictions allow, self attested attributes will be used. - * - * - * @param proofRequest The proof request to build the requested credentials object from - * @param presentationProposal Optional presentation proposal to improve credential selection algorithm - * @returns RetrievedCredentials object - */ - public async getRequestedCredentialsForProofRequest( - agentContext: AgentContext, - proofRequest: ProofRequest, - config: { - presentationProposal?: PresentationPreview - filterByNonRevocationRequirements?: boolean - } = {} - ): Promise { - const retrievedCredentials = new RetrievedCredentials({}) - - for (const [referent, requestedAttribute] of proofRequest.requestedAttributes.entries()) { - let credentialMatch: IndyCredential[] = [] - const credentials = await this.getCredentialsForProofRequest(agentContext, proofRequest, referent) - - // If we have exactly one credential, or no proposal to pick preferences - // on the credentials to use, we will use the first one - if (credentials.length === 1 || !config.presentationProposal) { - credentialMatch = credentials - } - // If we have a proposal we will use that to determine the credentials to use - else { - const names = requestedAttribute.names ?? [requestedAttribute.name] - - // Find credentials that matches all parameters from the proposal - credentialMatch = credentials.filter((credential) => { - const { attributes, credentialDefinitionId } = credential.credentialInfo - - // Check if credentials matches all parameters from proposal - return names.every((name) => - config.presentationProposal?.attributes.find( - (a) => - a.name === name && - a.credentialDefinitionId === credentialDefinitionId && - (!a.value || a.value === attributes[name]) - ) - ) - }) - } - - retrievedCredentials.requestedAttributes[referent] = await Promise.all( - credentialMatch.map(async (credential: IndyCredential) => { - const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { - proofRequest, - requestedItem: requestedAttribute, - credential, - }) - - return new RequestedAttribute({ - credentialId: credential.credentialInfo.referent, - revealed: true, - credentialInfo: credential.credentialInfo, - timestamp: deltaTimestamp, - revoked, - }) - }) - ) - - // We only attach revoked state if non-revocation is requested. So if revoked is true it means - // the credential is not applicable to the proof request - if (config.filterByNonRevocationRequirements) { - retrievedCredentials.requestedAttributes[referent] = retrievedCredentials.requestedAttributes[referent].filter( - (r) => !r.revoked - ) - } - } - - for (const [referent, requestedPredicate] of proofRequest.requestedPredicates.entries()) { - const credentials = await this.getCredentialsForProofRequest(agentContext, proofRequest, referent) - - retrievedCredentials.requestedPredicates[referent] = await Promise.all( - credentials.map(async (credential) => { - const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { - proofRequest, - requestedItem: requestedPredicate, - credential, - }) - - return new RequestedPredicate({ - credentialId: credential.credentialInfo.referent, - credentialInfo: credential.credentialInfo, - timestamp: deltaTimestamp, - revoked, - }) - }) - ) - - // We only attach revoked state if non-revocation is requested. So if revoked is true it means - // the credential is not applicable to the proof request - if (config.filterByNonRevocationRequirements) { - retrievedCredentials.requestedPredicates[referent] = retrievedCredentials.requestedPredicates[referent].filter( - (r) => !r.revoked - ) - } - } - - return retrievedCredentials - } - - /** - * Takes a RetrievedCredentials object and auto selects credentials in a RequestedCredentials object - * - * Use the return value of this method as input to {@link ProofService.createPresentation} to - * automatically accept a received presentation request. - * - * @param retrievedCredentials The retrieved credentials object to get credentials from - * - * @returns RequestedCredentials - */ - public autoSelectCredentialsForProofRequest(retrievedCredentials: RetrievedCredentials): RequestedCredentials { - const requestedCredentials = new RequestedCredentials({}) - - Object.keys(retrievedCredentials.requestedAttributes).forEach((attributeName) => { - const attributeArray = retrievedCredentials.requestedAttributes[attributeName] - - if (attributeArray.length === 0) { - throw new AriesFrameworkError('Unable to automatically select requested attributes.') - } else { - requestedCredentials.requestedAttributes[attributeName] = attributeArray[0] - } - }) - - Object.keys(retrievedCredentials.requestedPredicates).forEach((attributeName) => { - if (retrievedCredentials.requestedPredicates[attributeName].length === 0) { - throw new AriesFrameworkError('Unable to automatically select requested predicates.') - } else { - requestedCredentials.requestedPredicates[attributeName] = - retrievedCredentials.requestedPredicates[attributeName][0] - } - }) - - return requestedCredentials - } - - /** - * Verify an indy proof object. Will also verify raw values against encodings. - * - * @param proofRequest The proof request to use for proof verification - * @param proofJson The proof object to verify - * @throws {Error} If the raw values do not match the encoded values - * @returns Boolean whether the proof is valid - * - */ - public async verifyProof( - agentContext: AgentContext, - proofJson: IndyProof, - proofRequest: ProofRequest - ): Promise { - const proof = JsonTransformer.fromJSON(proofJson, PartialProof) - - for (const [referent, attribute] of proof.requestedProof.revealedAttributes.entries()) { - if (!IndyCredentialUtils.checkValidEncoding(attribute.raw, attribute.encoded)) { - throw new PresentationProblemReportError( - `The encoded value for '${referent}' is invalid. ` + - `Expected '${IndyCredentialUtils.encode(attribute.raw)}'. ` + - `Actual '${attribute.encoded}'`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - } - - // TODO: pre verify proof json - // I'm not 100% sure how much indy does. Also if it checks whether the proof requests matches the proof - // @see https://github.com/hyperledger/aries-cloudagent-python/blob/master/aries_cloudagent/indy/sdk/verifier.py#L79-L164 - - const schemas = await this.getSchemas(agentContext, new Set(proof.identifiers.map((i) => i.schemaId))) - const credentialDefinitions = await this.getCredentialDefinitions( - agentContext, - new Set(proof.identifiers.map((i) => i.credentialDefinitionId)) - ) - - return await this.indyVerifierService.verifyProof(agentContext, { - proofRequest: proofRequest.toJSON(), - proof: proofJson, - schemas, - credentialDefinitions, - }) - } - - /** - * Retrieve all proof records - * - * @returns List containing all proof records - */ - public async getAll(agentContext: AgentContext): Promise { - return this.proofRepository.getAll(agentContext) - } - - /** - * Retrieve a proof record by id - * - * @param proofRecordId The proof record id - * @throws {RecordNotFoundError} If no record is found - * @return The proof record - * - */ - public async getById(agentContext: AgentContext, proofRecordId: string): Promise { - return this.proofRepository.getById(agentContext, proofRecordId) - } - - /** - * Retrieve a proof record by id - * - * @param proofRecordId The proof record id - * @return The proof record or null if not found - * - */ - public async findById(agentContext: AgentContext, proofRecordId: string): Promise { - return this.proofRepository.findById(agentContext, proofRecordId) - } - - /** - * Delete a proof record by id - * - * @param proofId the proof record id - */ - public async deleteById(agentContext: AgentContext, proofId: string) { - const proofRecord = await this.getById(agentContext, proofId) - return this.proofRepository.delete(agentContext, proofRecord) - } - - /** - * Retrieve a proof record by connection id and thread id - * - * @param connectionId The connection id - * @param threadId The thread id - * @throws {RecordNotFoundError} If no record is found - * @throws {RecordDuplicateError} If multiple records are found - * @returns The proof record - */ - public async getByThreadAndConnectionId( - agentContext: AgentContext, - threadId: string, - connectionId?: string - ): Promise { - return this.proofRepository.getSingleByQuery(agentContext, { threadId, connectionId }) - } - - /** - * Retrieve proof records by connection id and parent thread id - * - * @param connectionId The connection id - * @param parentThreadId The parent thread id - * @returns List containing all proof records matching the given query - */ - public async getByParentThreadAndConnectionId( - agentContext: AgentContext, - parentThreadId: string, - connectionId?: string - ): Promise { - return this.proofRepository.findByQuery(agentContext, { parentThreadId, connectionId }) - } - - public update(agentContext: AgentContext, proofRecord: ProofRecord) { - return this.proofRepository.update(agentContext, proofRecord) - } - - /** - * Create indy proof from a given proof request and requested credential object. - * - * @param proofRequest The proof request to create the proof for - * @param requestedCredentials The requested credentials object specifying which credentials to use for the proof - * @returns indy proof object - */ - private async createProof( - agentContext: AgentContext, - proofRequest: ProofRequest, - requestedCredentials: RequestedCredentials - ): Promise { - const credentialObjects = await Promise.all( - [ - ...Object.values(requestedCredentials.requestedAttributes), - ...Object.values(requestedCredentials.requestedPredicates), - ].map(async (c) => { - if (c.credentialInfo) { - return c.credentialInfo - } - const credentialInfo = await this.indyHolderService.getCredential(agentContext, c.credentialId) - return JsonTransformer.fromJSON(credentialInfo, IndyCredentialInfo) - }) - ) - - const schemas = await this.getSchemas(agentContext, new Set(credentialObjects.map((c) => c.schemaId))) - const credentialDefinitions = await this.getCredentialDefinitions( - agentContext, - new Set(credentialObjects.map((c) => c.credentialDefinitionId)) - ) - - return this.indyHolderService.createProof(agentContext, { - proofRequest: proofRequest.toJSON(), - requestedCredentials: requestedCredentials, - schemas, - credentialDefinitions, - }) - } - - private async getCredentialsForProofRequest( - agentContext: AgentContext, - proofRequest: ProofRequest, - attributeReferent: string - ): Promise { - const credentialsJson = await this.indyHolderService.getCredentialsForProofRequest(agentContext, { - proofRequest: proofRequest.toJSON(), - attributeReferent, - }) - - return JsonTransformer.fromJSON(credentialsJson, IndyCredential) as unknown as IndyCredential[] - } - - private async getRevocationStatusForRequestedItem( - agentContext: AgentContext, - { - proofRequest, - requestedItem, - credential, - }: { - proofRequest: ProofRequest - requestedItem: ProofAttributeInfo | ProofPredicateInfo - credential: IndyCredential - } - ) { - const requestNonRevoked = requestedItem.nonRevoked ?? proofRequest.nonRevoked - const credentialRevocationId = credential.credentialInfo.credentialRevocationId - const revocationRegistryId = credential.credentialInfo.revocationRegistryId - - // If revocation interval is present and the credential is revocable then fetch the revocation status of credentials for display - if (requestNonRevoked && credentialRevocationId && revocationRegistryId) { - this.logger.trace( - `Presentation is requesting proof of non revocation, getting revocation status for credential`, - { - requestNonRevoked, - credentialRevocationId, - revocationRegistryId, - } - ) - - // Note presentation from-to's vs ledger from-to's: https://github.com/hyperledger/indy-hipe/blob/master/text/0011-cred-revocation/README.md#indy-node-revocation-registry-intervals - const status = await this.indyRevocationService.getRevocationStatus( - agentContext, - credentialRevocationId, - revocationRegistryId, - requestNonRevoked - ) - - return status - } - - return { revoked: undefined, deltaTimestamp: undefined } - } - - /** - * Update the record to a new state and emit an state changed event. Also updates the record - * in storage. - * - * @param proofRecord The proof record to update the state for - * @param newState The state to update to - * - */ - private async updateState(agentContext: AgentContext, proofRecord: ProofRecord, newState: ProofState) { - const previousState = proofRecord.state - proofRecord.state = newState - await this.proofRepository.update(agentContext, proofRecord) - - this.emitStateChangedEvent(agentContext, proofRecord, previousState) - } - - private emitStateChangedEvent( - agentContext: AgentContext, - proofRecord: ProofRecord, - previousState: ProofState | null - ) { - const clonedProof = JsonTransformer.clone(proofRecord) - - this.eventEmitter.emit(agentContext, { - type: ProofEventTypes.ProofStateChanged, - payload: { - proofRecord: clonedProof, - previousState: previousState, - }, - }) - } - - /** - * Build schemas object needed to create and verify proof objects. - * - * Creates object with `{ schemaId: Schema }` mapping - * - * @param schemaIds List of schema ids - * @returns Object containing schemas for specified schema ids - * - */ - private async getSchemas(agentContext: AgentContext, schemaIds: Set) { - const schemas: { [key: string]: Schema } = {} - - for (const schemaId of schemaIds) { - const schema = await this.ledgerService.getSchema(agentContext, schemaId) - schemas[schemaId] = schema - } - - return schemas - } - - /** - * Build credential definitions object needed to create and verify proof objects. - * - * Creates object with `{ credentialDefinitionId: CredentialDefinition }` mapping - * - * @param credentialDefinitionIds List of credential definition ids - * @returns Object containing credential definitions for specified credential definition ids - * - */ - private async getCredentialDefinitions(agentContext: AgentContext, credentialDefinitionIds: Set) { - const credentialDefinitions: { [key: string]: CredDef } = {} - - for (const credDefId of credentialDefinitionIds) { - const credDef = await this.ledgerService.getCredentialDefinition(agentContext, credDefId) - credentialDefinitions[credDefId] = credDef - } - - return credentialDefinitions - } -} - -export interface ProofRequestTemplate { - proofRequest: ProofRequest - comment?: string -} - -export interface ProofProtocolMsgReturnType { - message: MessageType - proofRecord: ProofRecord -} diff --git a/packages/core/src/modules/proofs/services/index.ts b/packages/core/src/modules/proofs/services/index.ts deleted file mode 100644 index 0233c56665..0000000000 --- a/packages/core/src/modules/proofs/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ProofService' diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 5c2f404260..587443ea2d 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -82,6 +82,7 @@ export interface InitConfig { autoUpdateStorageOnStartup?: boolean } +export type ProtocolVersion = `${number}.${number}` export interface PlaintextMessage { '@type': string '@id': string diff --git a/packages/core/src/utils/__tests__/objectCheck.test.ts b/packages/core/src/utils/__tests__/objectCheck.test.ts new file mode 100644 index 0000000000..d2517c2d0e --- /dev/null +++ b/packages/core/src/utils/__tests__/objectCheck.test.ts @@ -0,0 +1,34 @@ +import { objectEquals } from '../objectCheck' + +describe('objectEquals', () => { + it('correctly evaluates whether two map objects are equal', () => { + const mapA: Map = new Map([ + ['foo', 1], + ['bar', 2], + ['baz', 3], + ]) + const mapB: Map = new Map([ + ['foo', 1], + ['bar', 2], + ['baz', 3], + ]) + let retVal = objectEquals(mapA, mapB) + + // if the order is different, maps still equal + const mapC: Map = new Map([ + ['foo', 1], + ['baz', 3], + ['bar', 2], + ]) + retVal = objectEquals(mapA, mapC) + expect(retVal).toBe(true) + + const mapD: Map = new Map([ + ['foo', 1], + ['bar', 2], + ['qux', 3], + ]) + retVal = objectEquals(mapA, mapD) + expect(retVal).toBe(false) + }) +}) diff --git a/packages/core/src/utils/indyProofRequest.ts b/packages/core/src/utils/indyProofRequest.ts index 6f128b4849..853bf4f742 100644 --- a/packages/core/src/utils/indyProofRequest.ts +++ b/packages/core/src/utils/indyProofRequest.ts @@ -1,4 +1,4 @@ -import type { ProofRequest } from '../modules/proofs/models/ProofRequest' +import type { ProofRequest } from '../modules/proofs/formats/indy/models/ProofRequest' import { AriesFrameworkError } from '../error/AriesFrameworkError' diff --git a/packages/core/src/utils/objectCheck.ts b/packages/core/src/utils/objectCheck.ts new file mode 100644 index 0000000000..a511b28df6 --- /dev/null +++ b/packages/core/src/utils/objectCheck.ts @@ -0,0 +1,70 @@ +export function objectEquals(x: Map, y: Map): boolean { + if (x === null || x === undefined || y === null || y === undefined) { + return x === y + } + + // after this just checking type of one would be enough + if (x.constructor !== y.constructor) { + return false + } + // if they are functions, they should exactly refer to same one (because of closures) + if (x instanceof Function) { + return x === y + } + // if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES) + if (x instanceof RegExp) { + return x === y + } + if (x === y || x.valueOf() === y.valueOf()) { + return true + } + + // if they are dates, they must had equal valueOf + if (x instanceof Date) { + return false + } + + // if they are strictly equal, they both need to be object at least + if (!(x instanceof Object)) { + return false + } + if (!(y instanceof Object)) { + return false + } + + const xkeys = Array.from(x.keys()) + const ykeys = Array.from(y.keys()) + if (!equalsIgnoreOrder(xkeys, ykeys)) { + return false + } + return ( + xkeys.every(function (i) { + return xkeys.indexOf(i) !== -1 + }) && + xkeys.every(function (xkey) { + // get the x map entries for this key + + const xval: any = x.get(xkey) + const yval: any = y.get(xkey) + + const a: Map = new Map([[xkey, xval]]) + if (a.size === 1) { + return xval === yval + } + // get the y map entries for this key + const b: Map = new Map([[xkey, yval]]) + return objectEquals(a, b) + }) + ) +} + +function equalsIgnoreOrder(a: string[], b: string[]): boolean { + if (a.length !== b.length) return false + const uniqueValues = new Set([...a, ...b]) + for (const v of uniqueValues) { + const aCount = a.filter((e) => e === v).length + const bCount = b.filter((e) => e === v).length + if (aCount !== bCount) return false + } + return true +} diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 5c5be4adaf..d98a2160d2 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -1,27 +1,27 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { - AutoAcceptProof, + AcceptOfferOptions, BasicMessage, BasicMessageStateChangedEvent, ConnectionRecordProps, CredentialDefinitionTemplate, CredentialStateChangedEvent, InitConfig, - ProofAttributeInfo, - ProofPredicateInfo, ProofStateChangedEvent, SchemaTemplate, Wallet, } from '../src' -import type { AcceptOfferOptions } from '../src/modules/credentials' import type { IndyOfferCredentialFormat } from '../src/modules/credentials/formats/indy/IndyCredentialFormat' +import type { RequestProofOptions } from '../src/modules/proofs/ProofsApiOptions' +import type { ProofAttributeInfo, ProofPredicateInfo } from '../src/modules/proofs/formats/indy/models' +import type { AutoAcceptProof } from '../src/modules/proofs/models/ProofAutoAcceptType' import type { CredDef, Schema } from 'indy-sdk' import type { Observable } from 'rxjs' import path from 'path' import { firstValueFrom, ReplaySubject, Subject } from 'rxjs' -import { catchError, filter, map, timeout } from 'rxjs/operators' +import { catchError, filter, map, tap, timeout } from 'rxjs/operators' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' @@ -41,12 +41,7 @@ import { HandshakeProtocol, InjectionSymbols, LogLevel, - PredicateType, - PresentationPreview, - PresentationPreviewAttribute, - PresentationPreviewPredicate, ProofEventTypes, - ProofState, } from '../src' import { Key, KeyType } from '../src/crypto' import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment' @@ -57,6 +52,14 @@ import { OutOfBandRole } from '../src/modules/oob/domain/OutOfBandRole' import { OutOfBandState } from '../src/modules/oob/domain/OutOfBandState' import { OutOfBandInvitation } from '../src/modules/oob/messages' import { OutOfBandRecord } from '../src/modules/oob/repository' +import { PredicateType } from '../src/modules/proofs/formats/indy/models' +import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' +import { ProofState } from '../src/modules/proofs/models/ProofState' +import { + PresentationPreview, + PresentationPreviewAttribute, + PresentationPreviewPredicate, +} from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' import { LinkedAttachment } from '../src/utils/LinkedAttachment' import { uuid } from '../src/utils/uuid' @@ -198,6 +201,7 @@ export function waitForProofRecordSubject( `ProofStateChangedEvent event not emitted within specified timeout: { previousState: ${previousState}, threadId: ${threadId}, + parentThreadId: ${parentThreadId}, state: ${state} }` ) @@ -566,35 +570,58 @@ export async function presentProof({ verifierAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(verifierReplay) holderAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(holderReplay) - let verifierRecord = await verifierAgent.proofs.requestProof(verifierConnectionId, { - name: 'test-proof-request', - requestedAttributes: attributes, - requestedPredicates: predicates, - }) + const requestProofsOptions: RequestProofOptions = { + protocolVersion: ProofProtocolVersion.V1, + connectionId: verifierConnectionId, + proofFormats: { + indy: { + name: 'test-proof-request', + requestedAttributes: attributes, + requestedPredicates: predicates, + version: '1.0', + nonce: '947121108704767252195123', + }, + }, + } - let holderRecord = await waitForProofRecordSubject(holderReplay, { - threadId: verifierRecord.threadId, + let holderProofRecordPromise = waitForProofRecordSubject(holderReplay, { state: ProofState.RequestReceived, }) - const retrievedCredentials = await holderAgent.proofs.getRequestedCredentialsForProofRequest(holderRecord.id) - const requestedCredentials = holderAgent.proofs.autoSelectCredentialsForProofRequest(retrievedCredentials) - await holderAgent.proofs.acceptRequest(holderRecord.id, requestedCredentials) + let verifierRecord = await verifierAgent.proofs.requestProof(requestProofsOptions) + + let holderRecord = await holderProofRecordPromise + + const requestedCredentials = await holderAgent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: holderRecord.id, + config: { + filterByPresentationPreview: true, + }, + }) - verifierRecord = await waitForProofRecordSubject(verifierReplay, { + const verifierProofRecordPromise = waitForProofRecordSubject(verifierReplay, { threadId: holderRecord.threadId, state: ProofState.PresentationReceived, }) + await holderAgent.proofs.acceptRequest({ + proofRecordId: holderRecord.id, + proofFormats: { indy: requestedCredentials.proofFormats.indy }, + }) + + verifierRecord = await verifierProofRecordPromise + // assert presentation is valid expect(verifierRecord.isVerified).toBe(true) - verifierRecord = await verifierAgent.proofs.acceptPresentation(verifierRecord.id) - holderRecord = await waitForProofRecordSubject(holderReplay, { + holderProofRecordPromise = waitForProofRecordSubject(holderReplay, { threadId: holderRecord.threadId, state: ProofState.Done, }) + verifierRecord = await verifierAgent.proofs.acceptPresentation(verifierRecord.id) + holderRecord = await holderProofRecordPromise + return { verifierProof: verifierRecord, holderProof: holderRecord, diff --git a/packages/core/tests/proofs-sub-protocol.test.ts b/packages/core/tests/proofs-sub-protocol.test.ts index 924ee69bad..398454f5c3 100644 --- a/packages/core/tests/proofs-sub-protocol.test.ts +++ b/packages/core/tests/proofs-sub-protocol.test.ts @@ -1,17 +1,27 @@ -import type { Agent, ConnectionRecord, PresentationPreview } from '../src' +import type { Agent, ConnectionRecord, ProofRecord } from '../src' +import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' +import type { CredDefId } from 'indy-sdk' -import { ProofState, ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../src' +import { + ProofAttributeInfo, + AttributeFilter, + ProofPredicateInfo, + PredicateType, +} from '../src/modules/proofs/formats/indy/models' +import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' +import { ProofState } from '../src/modules/proofs/models/ProofState' import { uuid } from '../src/utils/uuid' import { setupProofsTest, waitForProofRecord } from './helpers' import testLogger from './logger' -describe('Present proof started as a sub-protocol', () => { +describe('Present Proof Subprotocol', () => { let faberAgent: Agent let aliceAgent: Agent - let credDefId: string + let credDefId: CredDefId let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord + let aliceProofRecord: ProofRecord let presentationPreview: PresentationPreview beforeAll(async () => { @@ -22,18 +32,37 @@ describe('Present proof started as a sub-protocol', () => { }) afterAll(async () => { + testLogger.test('Shutting down both agents') await faberAgent.shutdown() await faberAgent.wallet.delete() await aliceAgent.shutdown() await aliceAgent.wallet.delete() }) - test('Alice starts with proof proposal to Faber, ', async () => { + test('Alice starts with v1 proof proposal to Faber with parentThreadId', async () => { const parentThreadId = uuid() - testLogger.test('Alice sends presentation proposal to Faber') - const aliceProofRecord = await aliceAgent.proofs.proposeProof(aliceConnection.id, presentationPreview, { + // Alice sends a presentation proposal to Faber + testLogger.test('Alice sends a presentation proposal to Faber') + + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + parentThreadId, + state: ProofState.ProposalReceived, + }) + + aliceProofRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V1, parentThreadId, + proofFormats: { + indy: { + name: 'abc', + version: '1.0', + nonce: '947121108704767252195126', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + }, + }, }) expect(aliceProofRecord.parentThreadId).toBe(parentThreadId) @@ -44,15 +73,11 @@ describe('Present proof started as a sub-protocol', () => { const threadId = aliceProofRecord.threadId testLogger.test('Faber waits for a presentation proposal from Alice') - let faberProofRecord = await waitForProofRecord(faberAgent, { - threadId, - parentThreadId, - state: ProofState.ProposalReceived, - }) + let faberProofRecord = await faberProofRecordPromise // Faber accepts the presentation proposal from Alice testLogger.test('Faber accepts the presentation proposal from Alice') - await faberAgent.proofs.acceptProposal(faberProofRecord.id) + await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofRecord.id }) testLogger.test('Alice waits till it receives presentation ack') await waitForProofRecord(aliceAgent, { @@ -63,11 +88,16 @@ describe('Present proof started as a sub-protocol', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const retrievedCredentials = await aliceAgent.proofs.getRequestedCredentialsForProofRequest(aliceProofRecord.id, { - filterByPresentationPreview: true, + const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: aliceProofRecord.id, + config: { + filterByPresentationPreview: true, + }, + }) + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofRecord.id, + proofFormats: requestedCredentials.proofFormats, }) - const requestedCredentials = aliceAgent.proofs.autoSelectCredentialsForProofRequest(retrievedCredentials) - await aliceAgent.proofs.acceptRequest(aliceProofRecord.id, requestedCredentials) testLogger.test('Faber waits for presentation from Alice') faberProofRecord = await waitForProofRecord(faberAgent, { @@ -89,7 +119,7 @@ describe('Present proof started as a sub-protocol', () => { }) }) - test('Faber starts with proof requests to Alice', async () => { + test('Faber starts with v1 proof requests to Alice with parentThreadId', async () => { const parentThreadId = uuid() testLogger.test('Faber sends presentation request to Alice') @@ -117,17 +147,27 @@ describe('Present proof started as a sub-protocol', () => { }), } + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + parentThreadId, + state: ProofState.RequestReceived, + }) + // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') - const faberProofRecord = await faberAgent.proofs.requestProof( - faberConnection.id, - { - name: 'test-proof-request', - requestedAttributes: attributes, - requestedPredicates: predicates, + const faberProofRecord = await faberAgent.proofs.requestProof({ + connectionId: faberConnection.id, + parentThreadId, + protocolVersion: ProofProtocolVersion.V1, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324864', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, }, - { parentThreadId } - ) + }) expect(faberProofRecord.parentThreadId).toBe(parentThreadId) const proofsByParentThread = await faberAgent.proofs.getByParentThreadAndConnectionId(parentThreadId) @@ -138,19 +178,195 @@ describe('Present proof started as a sub-protocol', () => { // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - const aliceProofRecord = await waitForProofRecord(aliceAgent, { + const aliceProofRecord = await aliceProofRecordPromise + + // Alice retrieves the requested credentials and accepts the presentation request + testLogger.test('Alice accepts presentation request from Faber') + const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: aliceProofRecord.id, + config: { + filterByPresentationPreview: true, + }, + }) + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofRecord.id, + proofFormats: requestedCredentials.proofFormats, + }) + + // Faber waits until it receives a presentation from Alice + testLogger.test('Faber waits for presentation from Alice') + await waitForProofRecord(faberAgent, { + threadId, + parentThreadId, + state: ProofState.PresentationReceived, + }) + + // Faber accepts the presentation + testLogger.test('Faber accept the presentation from Alice') + await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + + // Alice waits until she receives a presentation acknowledgement + testLogger.test('Alice waits for acceptance by Faber') + await waitForProofRecord(aliceAgent, { + threadId, + parentThreadId, + state: ProofState.Done, + }) + }) + + test('Alice starts with v2 proof proposal to Faber with parentThreadId', async () => { + const parentThreadId = uuid() + + // Alice sends a presentation proposal to Faber + testLogger.test('Alice sends a presentation proposal to Faber') + + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + parentThreadId, + state: ProofState.ProposalReceived, + }) + + aliceProofRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V2, + parentThreadId, + proofFormats: { + indy: { + name: 'abc', + version: '1.0', + nonce: '947121108704767252195126', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + }, + }, + }) + + expect(aliceProofRecord.parentThreadId).toBe(parentThreadId) + const proofsByParentThread = await aliceAgent.proofs.getByParentThreadAndConnectionId(parentThreadId) + expect(proofsByParentThread.length).toEqual(1) + expect(proofsByParentThread[0].parentThreadId).toBe(parentThreadId) + + const threadId = aliceProofRecord.threadId + + testLogger.test('Faber waits for a presentation proposal from Alice') + let faberProofRecord = await faberProofRecordPromise + + // Faber accepts the presentation proposal from Alice + testLogger.test('Faber accepts the presentation proposal from Alice') + await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofRecord.id }) + + testLogger.test('Alice waits till it receives presentation ack') + await waitForProofRecord(aliceAgent, { + threadId, + parentThreadId, + state: ProofState.RequestReceived, + }) + + // Alice retrieves the requested credentials and accepts the presentation request + testLogger.test('Alice accepts presentation request from Faber') + const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: aliceProofRecord.id, + config: { + filterByPresentationPreview: true, + }, + }) + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofRecord.id, + proofFormats: requestedCredentials.proofFormats, + }) + + testLogger.test('Faber waits for presentation from Alice') + faberProofRecord = await waitForProofRecord(faberAgent, { threadId, + parentThreadId, + state: ProofState.PresentationReceived, + }) + + // Faber accepts the presentation provided by Alice + testLogger.test('Faber accepts the presentation provided by Alice') + await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + + // Alice waits until she received a presentation acknowledgement + testLogger.test('Alice waits until she receives a presentation acknowledgement') + await waitForProofRecord(aliceAgent, { + threadId, + parentThreadId, + state: ProofState.Done, + }) + }) + + test('Faber starts with v2 proof requests to Alice with parentThreadId', async () => { + const parentThreadId = uuid() + testLogger.test('Faber sends presentation request to Alice') + + const attributes = { + name: new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { parentThreadId, state: ProofState.RequestReceived, }) + // Faber sends a presentation request to Alice + testLogger.test('Faber sends a presentation request to Alice') + const faberProofRecord = await faberAgent.proofs.requestProof({ + connectionId: faberConnection.id, + parentThreadId, + protocolVersion: ProofProtocolVersion.V2, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324864', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + }) + + expect(faberProofRecord.parentThreadId).toBe(parentThreadId) + const proofsByParentThread = await faberAgent.proofs.getByParentThreadAndConnectionId(parentThreadId) + expect(proofsByParentThread.length).toEqual(1) + expect(proofsByParentThread[0].parentThreadId).toBe(parentThreadId) + + const threadId = faberProofRecord.threadId + + // Alice waits for presentation request from Faber + testLogger.test('Alice waits for presentation request from Faber') + const aliceProofRecord = await aliceProofRecordPromise + // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const retrievedCredentials = await aliceAgent.proofs.getRequestedCredentialsForProofRequest(aliceProofRecord.id, { - filterByPresentationPreview: true, + const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: aliceProofRecord.id, + config: { + filterByPresentationPreview: true, + }, + }) + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofRecord.id, + proofFormats: requestedCredentials.proofFormats, }) - const requestedCredentials = aliceAgent.proofs.autoSelectCredentialsForProofRequest(retrievedCredentials) - await aliceAgent.proofs.acceptRequest(aliceProofRecord.id, requestedCredentials) // Faber waits until it receives a presentation from Alice testLogger.test('Faber waits for presentation from Alice') diff --git a/packages/core/tests/proofs.test.ts b/packages/core/tests/proofs.test.ts deleted file mode 100644 index a09784167d..0000000000 --- a/packages/core/tests/proofs.test.ts +++ /dev/null @@ -1,370 +0,0 @@ -import type { Agent, ConnectionRecord, PresentationPreview } from '../src' -import type { CredDefId } from 'indy-sdk' - -import { - AttributeFilter, - JsonTransformer, - PredicateType, - PresentationMessage, - ProofAttributeInfo, - ProofPredicateInfo, - ProofRecord, - ProofState, - ProposePresentationMessage, - RequestPresentationMessage, -} from '../src' - -import { setupProofsTest, waitForProofRecord } from './helpers' -import testLogger from './logger' - -describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: CredDefId - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview - - beforeAll(async () => { - testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest('Faber agent', 'Alice agent')) - testLogger.test('Issuing second credential') - }) - - afterAll(async () => { - testLogger.test('Shutting down both agents') - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test('Alice starts with proof proposal to Faber', async () => { - // Alice sends a presentation proposal to Faber - testLogger.test('Alice sends a presentation proposal to Faber') - let aliceProofRecord = await aliceAgent.proofs.proposeProof(aliceConnection.id, presentationPreview) - - // Faber waits for a presentation proposal from Alice - testLogger.test('Faber waits for a presentation proposal from Alice') - let faberProofRecord = await waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, - state: ProofState.ProposalReceived, - }) - - expect(JsonTransformer.toJSON(aliceProofRecord)).toMatchObject({ - createdAt: expect.any(String), - id: expect.any(String), - proposalMessage: { - '@type': 'https://didcomm.org/present-proof/1.0/propose-presentation', - '@id': expect.any(String), - presentation_proposal: { - '@type': 'https://didcomm.org/present-proof/1.0/presentation-preview', - attributes: [ - { - name: 'name', - value: 'John', - }, - { - name: 'image_0', - value: undefined, - }, - ], - predicates: [ - { - name: 'age', - predicate: '>=', - threshold: 50, - }, - ], - }, - }, - }) - - // Faber accepts the presentation proposal from Alice - testLogger.test('Faber accepts the presentation proposal from Alice') - faberProofRecord = await faberAgent.proofs.acceptProposal(faberProofRecord.id) - - // Alice waits for presentation request from Faber - testLogger.test('Alice waits for presentation request from Faber') - aliceProofRecord = await waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, - state: ProofState.RequestReceived, - }) - - // Alice retrieves the requested credentials and accepts the presentation request - testLogger.test('Alice accepts presentation request from Faber') - const retrievedCredentials = await aliceAgent.proofs.getRequestedCredentialsForProofRequest(aliceProofRecord.id, { - filterByPresentationPreview: true, - }) - const requestedCredentials = aliceAgent.proofs.autoSelectCredentialsForProofRequest(retrievedCredentials) - await aliceAgent.proofs.acceptRequest(aliceProofRecord.id, requestedCredentials) - - // Faber waits for the presentation from Alice - testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, - state: ProofState.PresentationReceived, - }) - expect(JsonTransformer.toJSON(faberProofRecord)).toMatchObject({ - createdAt: expect.any(String), - state: ProofState.PresentationReceived, - isVerified: true, - presentationMessage: { - '@id': expect.any(String), - '@type': 'https://didcomm.org/present-proof/1.0/presentation', - 'presentations~attach': [ - { - '@id': 'libindy-presentation-0', - 'mime-type': 'application/json', - }, - ], - '~attach': [ - { - '@id': expect.any(String), - filename: 'picture-of-a-cat.png', - }, - ], - }, - }) - - expect(aliceProofRecord).toMatchObject({ - type: ProofRecord.name, - id: expect.any(String), - _tags: { - threadId: faberProofRecord.threadId, - connectionId: aliceProofRecord.connectionId, - state: ProofState.ProposalSent, - }, - }) - - // Faber accepts the presentation provided by Alice - testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) - - // Alice waits until she received a presentation acknowledgement - testLogger.test('Alice waits until she receives a presentation acknowledgement') - aliceProofRecord = await waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, - state: ProofState.Done, - }) - - expect(faberProofRecord).toMatchObject({ - type: ProofRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - threadId: aliceProofRecord.threadId, - connectionId: expect.any(String), - isVerified: true, - state: ProofState.PresentationReceived, - proposalMessage: expect.any(ProposePresentationMessage), - requestMessage: expect.any(RequestPresentationMessage), - presentationMessage: expect.any(PresentationMessage), - }) - - expect(aliceProofRecord).toMatchObject({ - type: ProofRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - threadId: faberProofRecord.threadId, - connectionId: expect.any(String), - state: ProofState.Done, - proposalMessage: expect.any(ProposePresentationMessage), - requestMessage: expect.any(RequestPresentationMessage), - presentationMessage: expect.any(PresentationMessage), - }) - }) - - test('Faber starts with proof request to Alice', async () => { - // Sample attributes - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_1: new ProofAttributeInfo({ - name: 'image_1', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Sample predicates - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Faber sends a presentation request to Alice - testLogger.test('Faber sends a presentation request to Alice') - let faberProofRecord = await faberAgent.proofs.requestProof(faberConnection.id, { - name: 'test-proof-request', - requestedAttributes: attributes, - requestedPredicates: predicates, - }) - - // Alice waits for presentation request from Faber - testLogger.test('Alice waits for presentation request from Faber') - let aliceProofRecord = await waitForProofRecord(aliceAgent, { - threadId: faberProofRecord.threadId, - state: ProofState.RequestReceived, - }) - - expect(JsonTransformer.toJSON(aliceProofRecord)).toMatchObject({ - id: expect.any(String), - createdAt: expect.any(String), - requestMessage: { - '@id': expect.any(String), - '@type': 'https://didcomm.org/present-proof/1.0/request-presentation', - 'request_presentations~attach': [ - { - '@id': 'libindy-request-presentation-0', - 'mime-type': 'application/json', - }, - ], - }, - }) - - // Alice retrieves the requested credentials and accepts the presentation request - testLogger.test('Alice accepts presentation request from Faber') - const retrievedCredentials = await aliceAgent.proofs.getRequestedCredentialsForProofRequest(aliceProofRecord.id, { - filterByPresentationPreview: true, - }) - const requestedCredentials = aliceAgent.proofs.autoSelectCredentialsForProofRequest(retrievedCredentials) - await aliceAgent.proofs.acceptRequest(aliceProofRecord.id, requestedCredentials) - - // Faber waits until it receives a presentation from Alice - testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, - state: ProofState.PresentationReceived, - }) - - expect(faberProofRecord).toMatchObject({ - id: expect.any(String), - createdAt: expect.any(Date), - state: ProofState.PresentationReceived, - requestMessage: expect.any(RequestPresentationMessage), - isVerified: true, - presentationMessage: { - type: 'https://didcomm.org/present-proof/1.0/presentation', - id: expect.any(String), - presentationAttachments: [ - { - id: 'libindy-presentation-0', - mimeType: 'application/json', - }, - ], - appendedAttachments: [ - { - id: 'zQmfDXo7T3J43j3CTkEZaz7qdHuABhWktksZ7JEBueZ5zUS', - filename: 'picture-of-a-cat.png', - data: { - base64: expect.any(String), - }, - }, - { - id: 'zQmRHBT9rDs5QhsnYuPY3mNpXxgLcnNXkhjWJvTSAPMmcVd', - filename: 'picture-of-a-dog.png', - }, - ], - thread: { - threadId: aliceProofRecord.threadId, - }, - }, - }) - - // Faber accepts the presentation - testLogger.test('Faber accept the presentation from Alice') - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) - - // Alice waits until she receives a presentation acknowledgement - testLogger.test('Alice waits for acceptance by Faber') - aliceProofRecord = await waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, - state: ProofState.Done, - }) - - expect(faberProofRecord).toMatchObject({ - type: ProofRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - threadId: aliceProofRecord.threadId, - connectionId: expect.any(String), - isVerified: true, - state: ProofState.PresentationReceived, - requestMessage: expect.any(RequestPresentationMessage), - presentationMessage: expect.any(PresentationMessage), - }) - - expect(aliceProofRecord).toMatchObject({ - type: ProofRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - threadId: faberProofRecord.threadId, - connectionId: expect.any(String), - state: ProofState.Done, - requestMessage: expect.any(RequestPresentationMessage), - presentationMessage: expect.any(PresentationMessage), - }) - }) - - test('an attribute group name matches with a predicate group name so an error is thrown', async () => { - // Age attribute - const attributes = { - age: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Age predicate - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - await expect( - faberAgent.proofs.requestProof(faberConnection.id, { - name: 'test-proof-request', - requestedAttributes: attributes, - requestedPredicates: predicates, - }) - ).rejects.toThrowError('The proof request contains duplicate predicates and attributes: age') - }) -}) diff --git a/packages/core/tests/connectionless-proofs.test.ts b/packages/core/tests/v1-connectionless-proofs.test.ts similarity index 75% rename from packages/core/tests/connectionless-proofs.test.ts rename to packages/core/tests/v1-connectionless-proofs.test.ts index ab49b8c838..68b4a4c5ac 100644 --- a/packages/core/tests/connectionless-proofs.test.ts +++ b/packages/core/tests/v1-connectionless-proofs.test.ts @@ -1,5 +1,8 @@ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { ProofStateChangedEvent } from '../src/modules/proofs' +import type { OutOfBandRequestOptions } from '../src/modules/proofs/ProofsApiOptions' +import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' +import type { V1ProofService } from '../src/modules/proofs/protocol/v1' import { Subject, ReplaySubject } from 'rxjs' @@ -11,6 +14,7 @@ import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachm import { HandshakeProtocol } from '../src/modules/connections' import { V1CredentialPreview } from '../src/modules/credentials' import { + ProofProtocolVersion, PredicateType, ProofState, ProofAttributeInfo, @@ -77,45 +81,67 @@ describe('Present Proof', () => { }), } - // eslint-disable-next-line prefer-const - let { proofRecord: faberProofRecord, requestMessage } = await faberAgent.proofs.createOutOfBandRequest({ - name: 'test-proof-request', - requestedAttributes: attributes, - requestedPredicates: predicates, + const outOfBandRequestOptions: OutOfBandRequestOptions<[IndyProofFormat], [V1ProofService]> = { + protocolVersion: ProofProtocolVersion.V1, + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + nonce: '12345678901', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } + + let aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + state: ProofState.RequestReceived, }) - await aliceAgent.receiveMessage(requestMessage.toJSON()) + // eslint-disable-next-line prefer-const + let { proofRecord: faberProofRecord, message } = await faberAgent.proofs.createOutOfBandRequest( + outOfBandRequestOptions + ) + + await aliceAgent.receiveMessage(message.toJSON()) testLogger.test('Alice waits for presentation request from Faber') - let aliceProofRecord = await waitForProofRecordSubject(aliceReplay, { - threadId: faberProofRecord.threadId, - state: ProofState.RequestReceived, - }) + let aliceProofRecord = await aliceProofRecordPromise testLogger.test('Alice accepts presentation request from Faber') - const retrievedCredentials = await aliceAgent.proofs.getRequestedCredentialsForProofRequest(aliceProofRecord.id, { - filterByPresentationPreview: true, + const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: aliceProofRecord.id, + config: { + filterByPresentationPreview: true, + }, }) - const requestedCredentials = aliceAgent.proofs.autoSelectCredentialsForProofRequest(retrievedCredentials) - await aliceAgent.proofs.acceptRequest(aliceProofRecord.id, requestedCredentials) - testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await waitForProofRecordSubject(faberReplay, { + const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { threadId: aliceProofRecord.threadId, state: ProofState.PresentationReceived, }) + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofRecord.id, + proofFormats: { indy: requestedCredentials.proofFormats.indy }, + }) + + testLogger.test('Faber waits for presentation from Alice') + faberProofRecord = await faberProofRecordPromise + // assert presentation is valid expect(faberProofRecord.isVerified).toBe(true) + aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + threadId: aliceProofRecord.threadId, + state: ProofState.Done, + }) + // Faber accepts presentation await faberAgent.proofs.acceptPresentation(faberProofRecord.id) // Alice waits till it receives presentation ack - aliceProofRecord = await waitForProofRecordSubject(aliceReplay, { - threadId: aliceProofRecord.threadId, - state: ProofState.Done, - }) + aliceProofRecord = await aliceProofRecordPromise }) test('Faber starts with connection-less proof requests to Alice with auto-accept enabled', async () => { @@ -153,29 +179,36 @@ describe('Present Proof', () => { }), } - // eslint-disable-next-line prefer-const - let { proofRecord: faberProofRecord, requestMessage } = await faberAgent.proofs.createOutOfBandRequest( - { - name: 'test-proof-request', - requestedAttributes: attributes, - requestedPredicates: predicates, + const outOfBandRequestOptions: OutOfBandRequestOptions<[IndyProofFormat], [V1ProofService]> = { + protocolVersion: ProofProtocolVersion.V1, + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + nonce: '12345678901', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, }, - { - autoAcceptProof: AutoAcceptProof.ContentApproved, - } - ) - - await aliceAgent.receiveMessage(requestMessage.toJSON()) + autoAcceptProof: AutoAcceptProof.ContentApproved, + } - await waitForProofRecordSubject(aliceReplay, { - threadId: faberProofRecord.threadId, + const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { state: ProofState.Done, }) - await waitForProofRecordSubject(faberReplay, { - threadId: faberProofRecord.threadId, + const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { state: ProofState.Done, }) + + // eslint-disable-next-line prefer-const + let { message } = await faberAgent.proofs.createOutOfBandRequest(outOfBandRequestOptions) + + await aliceAgent.receiveMessage(message.toJSON()) + + await aliceProofRecordPromise + + await faberProofRecordPromise }) test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => { @@ -306,24 +339,37 @@ describe('Present Proof', () => { }), } - // eslint-disable-next-line prefer-const - let { proofRecord: faberProofRecord, requestMessage } = await faberAgent.proofs.createOutOfBandRequest( - { - name: 'test-proof-request', - requestedAttributes: attributes, - requestedPredicates: predicates, + const outOfBandRequestOptions: OutOfBandRequestOptions<[IndyProofFormat], [V1ProofService]> = { + protocolVersion: ProofProtocolVersion.V1, + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + nonce: '12345678901', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, }, - { - autoAcceptProof: AutoAcceptProof.ContentApproved, - } - ) + autoAcceptProof: AutoAcceptProof.ContentApproved, + } + + const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + state: ProofState.Done, + }) + + const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { + state: ProofState.Done, + }) + + // eslint-disable-next-line prefer-const + let { message } = await faberAgent.proofs.createOutOfBandRequest(outOfBandRequestOptions) const mediationRecord = await faberAgent.mediationRecipient.findDefaultMediator() if (!mediationRecord) { throw new Error('Faber agent has no default mediator') } - expect(requestMessage).toMatchObject({ + expect(message).toMatchObject({ service: { recipientKeys: [expect.any(String)], routingKeys: mediationRecord.routingKeys, @@ -331,17 +377,11 @@ describe('Present Proof', () => { }, }) - await aliceAgent.receiveMessage(requestMessage.toJSON()) + await aliceAgent.receiveMessage(message.toJSON()) - await waitForProofRecordSubject(aliceReplay, { - threadId: faberProofRecord.threadId, - state: ProofState.Done, - }) + await aliceProofRecordPromise - await waitForProofRecordSubject(faberReplay, { - threadId: faberProofRecord.threadId, - state: ProofState.Done, - }) + await faberProofRecordPromise // We want to stop the mediator polling before the agent is shutdown. // FIXME: add a way to stop mediator polling from the public api, and make sure this is diff --git a/packages/core/tests/v1-indy-proofs.test.ts b/packages/core/tests/v1-indy-proofs.test.ts new file mode 100644 index 0000000000..85e694ead1 --- /dev/null +++ b/packages/core/tests/v1-indy-proofs.test.ts @@ -0,0 +1,589 @@ +import type { Agent, ConnectionRecord, ProofRecord } from '../src' +import type { + AcceptProposalOptions, + ProposeProofOptions, + RequestProofOptions, +} from '../src/modules/proofs/ProofsApiOptions' +import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' +import type { CredDefId } from 'indy-sdk' + +import { + ProofAttributeInfo, + AttributeFilter, + ProofPredicateInfo, + PredicateType, +} from '../src/modules/proofs/formats/indy/models' +import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' +import { ProofState } from '../src/modules/proofs/models/ProofState' +import { + V1ProposePresentationMessage, + V1RequestPresentationMessage, + V1PresentationMessage, +} from '../src/modules/proofs/protocol/v1/messages' +import { DidCommMessageRepository } from '../src/storage/didcomm' + +import { setupProofsTest, waitForProofRecord } from './helpers' +import testLogger from './logger' + +describe('Present Proof', () => { + let faberAgent: Agent + let aliceAgent: Agent + let credDefId: CredDefId + let faberConnection: ConnectionRecord + let aliceConnection: ConnectionRecord + let faberProofRecord: ProofRecord + let aliceProofRecord: ProofRecord + let presentationPreview: PresentationPreview + let didCommMessageRepository: DidCommMessageRepository + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = + await setupProofsTest('Faber agent', 'Alice agent')) + testLogger.test('Issuing second credential') + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice starts with proof proposal to Faber', async () => { + // Alice sends a presentation proposal to Faber + testLogger.test('Alice sends a presentation proposal to Faber') + + const proposeProofOptions: ProposeProofOptions = { + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V1, + proofFormats: { + indy: { + name: 'abc', + version: '1.0', + nonce: '947121108704767252195126', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + }, + }, + } + + let faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofRecord = await aliceAgent.proofs.proposeProof(proposeProofOptions) + + // Faber waits for a presentation proposal from Alice + testLogger.test('Faber waits for a presentation proposal from Alice') + faberProofRecord = await faberProofRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/propose-presentation', + id: expect.any(String), + presentationProposal: { + type: 'https://didcomm.org/present-proof/1.0/presentation-preview', + attributes: [ + { + name: 'name', + credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + value: 'John', + referent: '0', + }, + { + name: 'image_0', + credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + predicate: '>=', + threshold: 50, + }, + ], + }, + }) + expect(faberProofRecord.id).not.toBeNull() + expect(faberProofRecord).toMatchObject({ + threadId: faberProofRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: ProofProtocolVersion.V1, + }) + + const acceptProposalOptions: AcceptProposalOptions = { + proofRecordId: faberProofRecord.id, + } + + let aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.RequestReceived, + }) + + // Faber accepts the presentation proposal from Alice + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + + // Alice waits for presentation request from Faber + testLogger.test('Alice waits for presentation request from Faber') + aliceProofRecord = await aliceProofRecordPromise + + const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/request-presentation', + id: expect.any(String), + requestPresentationAttachments: [ + { + id: 'libindy-request-presentation-0', + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + thread: { + threadId: faberProofRecord.threadId, + }, + }) + + // Alice retrieves the requested credentials and accepts the presentation request + testLogger.test('Alice accepts presentation request from Faber') + + const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: aliceProofRecord.id, + config: { + filterByPresentationPreview: true, + }, + }) + + faberProofRecordPromise = waitForProofRecord(faberAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.PresentationReceived, + }) + + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofRecord.id, + proofFormats: { indy: requestedCredentials.proofFormats.indy }, + }) + + // Faber waits for the presentation from Alice + testLogger.test('Faber waits for presentation from Alice') + faberProofRecord = await faberProofRecordPromise + + const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V1PresentationMessage, + }) + + expect(presentation).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/presentation', + id: expect.any(String), + presentationAttachments: [ + { + id: 'libindy-presentation-0', + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + appendedAttachments: [ + { + id: expect.any(String), + filename: expect.any(String), + data: { + base64: expect.any(String), + }, + }, + ], + thread: { + threadId: expect.any(String), + }, + }) + + expect(faberProofRecord.id).not.toBeNull() + expect(faberProofRecord).toMatchObject({ + threadId: faberProofRecord.threadId, + state: ProofState.PresentationReceived, + protocolVersion: ProofProtocolVersion.V1, + }) + + aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.Done, + }) + + // Faber accepts the presentation provided by Alice + testLogger.test('Faber accepts the presentation provided by Alice') + await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + + // Alice waits until she received a presentation acknowledgement + testLogger.test('Alice waits until she receives a presentation acknowledgement') + aliceProofRecord = await aliceProofRecordPromise + + expect(faberProofRecord).toMatchObject({ + // type: ProofRecord.name, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: aliceProofRecord.threadId, + connectionId: expect.any(String), + isVerified: true, + state: ProofState.PresentationReceived, + }) + + expect(aliceProofRecord).toMatchObject({ + // type: ProofRecord.name, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: faberProofRecord.threadId, + connectionId: expect.any(String), + state: ProofState.Done, + }) + }) + + test('Faber starts with proof request to Alice', async () => { + const attributes = { + name: new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + image_0: new ProofAttributeInfo({ + name: 'image_0', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + // Sample predicates + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const requestProofsOptions: RequestProofOptions = { + protocolVersion: ProofProtocolVersion.V1, + connectionId: faberConnection.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324864', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } + + let aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + // Faber sends a presentation request to Alice + testLogger.test('Faber sends a presentation request to Alice') + faberProofRecord = await faberAgent.proofs.requestProof(requestProofsOptions) + + // Alice waits for presentation request from Faber + testLogger.test('Alice waits for presentation request from Faber') + aliceProofRecord = await aliceProofRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/request-presentation', + id: expect.any(String), + requestPresentationAttachments: [ + { + id: 'libindy-request-presentation-0', + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + }) + + expect(aliceProofRecord.id).not.toBeNull() + expect(aliceProofRecord).toMatchObject({ + threadId: aliceProofRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: ProofProtocolVersion.V1, + }) + + // Alice retrieves the requested credentials and accepts the presentation request + testLogger.test('Alice accepts presentation request from Faber') + + const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: aliceProofRecord.id, + config: { + filterByPresentationPreview: true, + }, + }) + + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.PresentationReceived, + }) + + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofRecord.id, + proofFormats: { indy: requestedCredentials.proofFormats.indy }, + }) + + // Faber waits until it receives a presentation from Alice + testLogger.test('Faber waits for presentation from Alice') + faberProofRecord = await faberProofRecordPromise + + const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V1PresentationMessage, + }) + + expect(presentation).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/presentation', + id: expect.any(String), + presentationAttachments: [ + { + id: 'libindy-presentation-0', + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + appendedAttachments: [ + { + id: expect.any(String), + filename: expect.any(String), + data: { + base64: expect.any(String), + }, + }, + ], + thread: { + threadId: expect.any(String), + }, + }) + + expect(faberProofRecord.id).not.toBeNull() + expect(faberProofRecord).toMatchObject({ + threadId: faberProofRecord.threadId, + state: ProofState.PresentationReceived, + protocolVersion: ProofProtocolVersion.V1, + }) + + aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.Done, + }) + + // Faber accepts the presentation + testLogger.test('Faber accept the presentation from Alice') + await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + + // Alice waits until she receives a presentation acknowledgement + testLogger.test('Alice waits for acceptance by Faber') + aliceProofRecord = await aliceProofRecordPromise + + expect(faberProofRecord).toMatchObject({ + // type: ProofRecord.name, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: aliceProofRecord.threadId, + connectionId: expect.any(String), + isVerified: true, + state: ProofState.PresentationReceived, + }) + + expect(aliceProofRecord).toMatchObject({ + // type: ProofRecord.name, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: faberProofRecord.threadId, + connectionId: expect.any(String), + state: ProofState.Done, + }) + }) + + test('an attribute group name matches with a predicate group name so an error is thrown', async () => { + // Age attribute + const attributes = { + age: new ProofAttributeInfo({ + name: 'age', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + // Age predicate + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const requestProofsOptions: RequestProofOptions = { + protocolVersion: ProofProtocolVersion.V1, + connectionId: faberConnection.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324864', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } + + await expect(faberAgent.proofs.requestProof(requestProofsOptions)).rejects.toThrowError( + `The proof request contains duplicate predicates and attributes: age` + ) + }) + + test('Faber starts with proof request to Alice but gets Problem Reported', async () => { + const attributes = { + name: new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + image_0: new ProofAttributeInfo({ + name: 'image_0', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + // Sample predicates + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const requestProofsOptions: RequestProofOptions = { + protocolVersion: ProofProtocolVersion.V1, + connectionId: faberConnection.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324864', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } + + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + // Faber sends a presentation request to Alice + testLogger.test('Faber sends a presentation request to Alice') + faberProofRecord = await faberAgent.proofs.requestProof(requestProofsOptions) + + // Alice waits for presentation request from Faber + testLogger.test('Alice waits for presentation request from Faber') + aliceProofRecord = await aliceProofRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/request-presentation', + id: expect.any(String), + requestPresentationAttachments: [ + { + id: 'libindy-request-presentation-0', + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + }) + + expect(aliceProofRecord.id).not.toBeNull() + expect(aliceProofRecord).toMatchObject({ + threadId: aliceProofRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: ProofProtocolVersion.V1, + }) + + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.Abandoned, + }) + + aliceProofRecord = await aliceAgent.proofs.sendProblemReport(aliceProofRecord.id, 'Problem inside proof request') + + faberProofRecord = await faberProofRecordPromise + + expect(faberProofRecord).toMatchObject({ + threadId: aliceProofRecord.threadId, + state: ProofState.Abandoned, + protocolVersion: ProofProtocolVersion.V1, + }) + }) +}) diff --git a/packages/core/tests/v1-proofs-auto-accept.test.ts b/packages/core/tests/v1-proofs-auto-accept.test.ts new file mode 100644 index 0000000000..6b74330c4f --- /dev/null +++ b/packages/core/tests/v1-proofs-auto-accept.test.ts @@ -0,0 +1,253 @@ +import type { Agent, ConnectionRecord } from '../src' +import type { ProposeProofOptions, RequestProofOptions } from '../src/modules/proofs/ProofsApiOptions' +import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' + +import { + AutoAcceptProof, + ProofState, + ProofAttributeInfo, + AttributeFilter, + ProofPredicateInfo, + PredicateType, +} from '../src' +import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' + +import { setupProofsTest, waitForProofRecord } from './helpers' +import testLogger from './logger' + +describe('Auto accept present proof', () => { + let faberAgent: Agent + let aliceAgent: Agent + let credDefId: string + let faberConnection: ConnectionRecord + let aliceConnection: ConnectionRecord + let presentationPreview: PresentationPreview + + describe('Auto accept on `always`', () => { + beforeAll(async () => { + ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = + await setupProofsTest( + 'Faber Auto Accept Always Proofs', + 'Alice Auto Accept Always Proofs', + AutoAcceptProof.Always + )) + }) + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `always`', async () => { + testLogger.test('Alice sends presentation proposal to Faber') + + const proposeProofOptions: ProposeProofOptions = { + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V1, + proofFormats: { + indy: { + nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + name: 'abc', + version: '1.0', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + }, + }, + } + + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.Done, + }) + + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.Done, + }) + + await aliceAgent.proofs.proposeProof(proposeProofOptions) + + testLogger.test('Faber waits for presentation from Alice') + await faberProofRecordPromise + + testLogger.test('Alice waits till it receives presentation ack') + await aliceProofRecordPromise + }) + + test('Faber starts with proof requests to Alice, both with autoAcceptProof on `always`', async () => { + testLogger.test('Faber sends presentation request to Alice') + const attributes = { + name: new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const requestProofsOptions: RequestProofOptions = { + protocolVersion: ProofProtocolVersion.V1, + connectionId: faberConnection.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324864', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.Done, + }) + + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.Done, + }) + + await faberAgent.proofs.requestProof(requestProofsOptions) + + testLogger.test('Faber waits for presentation from Alice') + + await faberProofRecordPromise + + // Alice waits till it receives presentation ack + await aliceProofRecordPromise + }) + }) + + describe('Auto accept on `contentApproved`', () => { + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = + await setupProofsTest( + 'Faber Auto Accept Content Approved Proofs', + 'Alice Auto Accept Content Approved Proofs', + AutoAcceptProof.ContentApproved + )) + }) + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice starts with proof proposal to Faber, both with autoacceptproof on `contentApproved`', async () => { + testLogger.test('Alice sends presentation proposal to Faber') + + const proposal: ProposeProofOptions = { + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V1, + proofFormats: { + indy: { + nonce: '1298236324864', + name: 'abc', + version: '1.0', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + }, + }, + } + + let faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + const aliceProofRecord = await aliceAgent.proofs.proposeProof(proposal) + + testLogger.test('Faber waits for presentation proposal from Alice') + + await faberProofRecordPromise + + testLogger.test('Faber accepts presentation proposal from Alice') + + faberProofRecordPromise = waitForProofRecord(faberAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.Done, + }) + + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.Done, + }) + + testLogger.test('Faber waits for presentation from Alice') + + await faberProofRecordPromise + // Alice waits till it receives presentation ack + await aliceProofRecordPromise + }) + + test('Faber starts with proof requests to Alice, both with autoacceptproof on `contentApproved`', async () => { + testLogger.test('Faber sends presentation request to Alice') + const attributes = { + name: new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const requestProofsOptions: RequestProofOptions = { + protocolVersion: ProofProtocolVersion.V1, + connectionId: faberConnection.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324866', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } + + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.Done, + }) + + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.Done, + }) + + await faberAgent.proofs.requestProof(requestProofsOptions) + + testLogger.test('Faber waits for presentation from Alice') + await faberProofRecordPromise + + // Alice waits till it receives presentation ack + await aliceProofRecordPromise + }) + }) +}) diff --git a/packages/core/tests/v2-connectionless-proofs.test.ts b/packages/core/tests/v2-connectionless-proofs.test.ts new file mode 100644 index 0000000000..b426713e3e --- /dev/null +++ b/packages/core/tests/v2-connectionless-proofs.test.ts @@ -0,0 +1,386 @@ +import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' +import type { ProofStateChangedEvent } from '../src/modules/proofs' +import type { OutOfBandRequestOptions } from '../src/modules/proofs/ProofsApiOptions' +import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' +import type { V2ProofService } from '../src/modules/proofs/protocol/v2' + +import { Subject, ReplaySubject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { V1CredentialPreview } from '../src' +import { Agent } from '../src/agent/Agent' +import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment' +import { HandshakeProtocol } from '../src/modules/connections/models/HandshakeProtocol' +import { + PredicateType, + ProofState, + ProofAttributeInfo, + AttributeFilter, + ProofPredicateInfo, + AutoAcceptProof, + ProofEventTypes, +} from '../src/modules/proofs' +import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' +import { MediatorPickupStrategy } from '../src/modules/routing/MediatorPickupStrategy' +import { LinkedAttachment } from '../src/utils/LinkedAttachment' +import { uuid } from '../src/utils/uuid' + +import { + getBaseConfig, + issueCredential, + makeConnection, + prepareForIssuance, + setupProofsTest, + waitForProofRecordSubject, +} from './helpers' +import testLogger from './logger' + +describe('Present Proof', () => { + let agents: Agent[] + + afterEach(async () => { + for (const agent of agents) { + await agent.shutdown() + await agent.wallet.delete() + } + }) + + test('Faber starts with connection-less proof requests to Alice', async () => { + const { aliceAgent, faberAgent, aliceReplay, credDefId, faberReplay } = await setupProofsTest( + 'Faber connection-less Proofs', + 'Alice connection-less Proofs', + AutoAcceptProof.Never + ) + agents = [aliceAgent, faberAgent] + testLogger.test('Faber sends presentation request to Alice') + + const attributes = { + name: new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const outOfBandRequestOptions: OutOfBandRequestOptions<[IndyProofFormat], [V2ProofService]> = { + protocolVersion: ProofProtocolVersion.V2, + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + nonce: '12345678901', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } + + let aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + state: ProofState.RequestReceived, + }) + + // eslint-disable-next-line prefer-const + let { proofRecord: faberProofRecord, message } = await faberAgent.proofs.createOutOfBandRequest( + outOfBandRequestOptions + ) + + await aliceAgent.receiveMessage(message.toJSON()) + + testLogger.test('Alice waits for presentation request from Faber') + let aliceProofRecord = await aliceProofRecordPromise + + testLogger.test('Alice accepts presentation request from Faber') + + const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: aliceProofRecord.id, + config: { + filterByPresentationPreview: true, + }, + }) + + const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { + threadId: aliceProofRecord.threadId, + state: ProofState.PresentationReceived, + }) + + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofRecord.id, + proofFormats: { indy: requestedCredentials.proofFormats.indy }, + }) + + testLogger.test('Faber waits for presentation from Alice') + faberProofRecord = await faberProofRecordPromise + + // assert presentation is valid + expect(faberProofRecord.isVerified).toBe(true) + + aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + threadId: aliceProofRecord.threadId, + state: ProofState.Done, + }) + + // Faber accepts presentation + await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + + // Alice waits till it receives presentation ack + aliceProofRecord = await aliceProofRecordPromise + }) + + test('Faber starts with connection-less proof requests to Alice with auto-accept enabled', async () => { + testLogger.test('Faber sends presentation request to Alice') + + const { aliceAgent, faberAgent, aliceReplay, credDefId, faberReplay } = await setupProofsTest( + 'Faber connection-less Proofs - Auto Accept', + 'Alice connection-less Proofs - Auto Accept', + AutoAcceptProof.Always + ) + + agents = [aliceAgent, faberAgent] + + const attributes = { + name: new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const outOfBandRequestOptions: OutOfBandRequestOptions<[IndyProofFormat], [V2ProofService]> = { + protocolVersion: ProofProtocolVersion.V2, + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + nonce: '12345678901', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + autoAcceptProof: AutoAcceptProof.ContentApproved, + } + + const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + state: ProofState.Done, + }) + + const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { + state: ProofState.Done, + }) + + // eslint-disable-next-line prefer-const + let { message } = await faberAgent.proofs.createOutOfBandRequest(outOfBandRequestOptions) + + await aliceAgent.receiveMessage(message.toJSON()) + + await aliceProofRecordPromise + + await faberProofRecordPromise + }) + + test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => { + testLogger.test('Faber sends presentation request to Alice') + + const credentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', + }) + + const unique = uuid().substring(0, 4) + + const mediatorConfig = getBaseConfig(`Connectionless proofs with mediator Mediator-${unique}`, { + autoAcceptMediationRequests: true, + endpoints: ['rxjs:mediator'], + }) + + const faberMessages = new Subject() + const aliceMessages = new Subject() + const mediatorMessages = new Subject() + + const subjectMap = { + 'rxjs:mediator': mediatorMessages, + } + + // Initialize mediator + const mediatorAgent = new Agent(mediatorConfig.config, mediatorConfig.agentDependencies) + mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) + await mediatorAgent.initialize() + + const faberMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ + label: 'faber invitation', + handshakeProtocols: [HandshakeProtocol.Connections], + }) + + const aliceMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ + label: 'alice invitation', + handshakeProtocols: [HandshakeProtocol.Connections], + }) + + const faberConfig = getBaseConfig(`Connectionless proofs with mediator Faber-${unique}`, { + autoAcceptProofs: AutoAcceptProof.Always, + mediatorConnectionsInvite: faberMediationOutOfBandRecord.outOfBandInvitation.toUrl({ + domain: 'https://example.com', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }) + + const aliceConfig = getBaseConfig(`Connectionless proofs with mediator Alice-${unique}`, { + autoAcceptProofs: AutoAcceptProof.Always, + // logger: new TestLogger(LogLevel.test), + mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation.toUrl({ + domain: 'https://example.com', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }) + + const faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) + await faberAgent.initialize() + + const aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + await aliceAgent.initialize() + + agents = [aliceAgent, faberAgent, mediatorAgent] + + const { definition } = await prepareForIssuance(faberAgent, ['name', 'age', 'image_0', 'image_1']) + + const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) + expect(faberConnection.isReady).toBe(true) + expect(aliceConnection.isReady).toBe(true) + + await issueCredential({ + issuerAgent: faberAgent, + issuerConnectionId: faberConnection.id, + holderAgent: aliceAgent, + credentialTemplate: { + credentialDefinitionId: definition.id, + attributes: credentialPreview.attributes, + linkedAttachments: [ + new LinkedAttachment({ + name: 'image_0', + attachment: new Attachment({ + filename: 'picture-of-a-cat.png', + data: new AttachmentData({ base64: 'cGljdHVyZSBvZiBhIGNhdA==' }), + }), + }), + new LinkedAttachment({ + name: 'image_1', + attachment: new Attachment({ + filename: 'picture-of-a-dog.png', + data: new AttachmentData({ base64: 'UGljdHVyZSBvZiBhIGRvZw==' }), + }), + }), + ], + }, + }) + const faberReplay = new ReplaySubject() + const aliceReplay = new ReplaySubject() + + faberAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(faberReplay) + aliceAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(aliceReplay) + + const attributes = { + name: new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: definition.id, + }), + ], + }), + } + + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: definition.id, + }), + ], + }), + } + + const outOfBandRequestOptions: OutOfBandRequestOptions<[IndyProofFormat], [V2ProofService]> = { + protocolVersion: ProofProtocolVersion.V2, + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + nonce: '12345678901', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + autoAcceptProof: AutoAcceptProof.ContentApproved, + } + + const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + state: ProofState.Done, + }) + + const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { + state: ProofState.Done, + }) + + // eslint-disable-next-line prefer-const + let { message } = await faberAgent.proofs.createOutOfBandRequest(outOfBandRequestOptions) + + const mediationRecord = await faberAgent.mediationRecipient.findDefaultMediator() + if (!mediationRecord) { + throw new Error('Faber agent has no default mediator') + } + + expect(message).toMatchObject({ + service: { + recipientKeys: [expect.any(String)], + routingKeys: mediationRecord.routingKeys, + serviceEndpoint: mediationRecord.endpoint, + }, + }) + + await aliceAgent.receiveMessage(message.toJSON()) + + await aliceProofRecordPromise + + await faberProofRecordPromise + }) +}) diff --git a/packages/core/tests/v2-indy-proofs.test.ts b/packages/core/tests/v2-indy-proofs.test.ts new file mode 100644 index 0000000000..4bfac719ec --- /dev/null +++ b/packages/core/tests/v2-indy-proofs.test.ts @@ -0,0 +1,538 @@ +import type { Agent, ConnectionRecord, ProofRecord } from '../src' +import type { + AcceptProposalOptions, + ProposeProofOptions, + RequestProofOptions, +} from '../src/modules/proofs/ProofsApiOptions' +import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' +import type { CredDefId } from 'indy-sdk' + +import { AttributeFilter, PredicateType, ProofAttributeInfo, ProofPredicateInfo, ProofState } from '../src' +import { + V2_INDY_PRESENTATION_PROPOSAL, + V2_INDY_PRESENTATION_REQUEST, + V2_INDY_PRESENTATION, +} from '../src/modules/proofs/formats/ProofFormatConstants' +import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' +import { + V2PresentationMessage, + V2ProposalPresentationMessage, + V2RequestPresentationMessage, +} from '../src/modules/proofs/protocol/v2/messages' +import { DidCommMessageRepository } from '../src/storage/didcomm' + +import { setupProofsTest, waitForProofRecord } from './helpers' +import testLogger from './logger' + +describe('Present Proof', () => { + let faberAgent: Agent + let aliceAgent: Agent + let credDefId: CredDefId + let aliceConnection: ConnectionRecord + let faberConnection: ConnectionRecord + let faberProofRecord: ProofRecord + let aliceProofRecord: ProofRecord + let presentationPreview: PresentationPreview + let didCommMessageRepository: DidCommMessageRepository + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = + await setupProofsTest('Faber agent', 'Alice agent')) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice starts with proof proposal to Faber', async () => { + // Alice sends a presentation proposal to Faber + testLogger.test('Alice sends a presentation proposal to Faber') + + const proposeProofOptions: ProposeProofOptions = { + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V2, + proofFormats: { + indy: { + name: 'abc', + version: '1.0', + nonce: '947121108704767252195126', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + }, + }, + } + + let faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofRecord = await aliceAgent.proofs.proposeProof(proposeProofOptions) + + // Faber waits for a presentation proposal from Alice + testLogger.test('Faber waits for a presentation proposal from Alice') + faberProofRecord = await faberProofRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V2ProposalPresentationMessage, + }) + + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/propose-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION_PROPOSAL, + }, + ], + proposalsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + }) + expect(faberProofRecord.id).not.toBeNull() + expect(faberProofRecord).toMatchObject({ + threadId: faberProofRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: ProofProtocolVersion.V2, + }) + + const acceptProposalOptions: AcceptProposalOptions = { + proofRecordId: faberProofRecord.id, + } + + let aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + // Faber accepts the presentation proposal from Alice + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + + // Alice waits for presentation request from Faber + testLogger.test('Alice waits for presentation request from Faber') + aliceProofRecord = await aliceProofRecordPromise + + const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/request-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION_REQUEST, + }, + ], + requestPresentationsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + thread: { + threadId: faberProofRecord.threadId, + }, + }) + + // Alice retrieves the requested credentials and accepts the presentation request + testLogger.test('Alice accepts presentation request from Faber') + + const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: aliceProofRecord.id, + config: { + filterByPresentationPreview: true, + }, + }) + + faberProofRecordPromise = waitForProofRecord(faberAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.PresentationReceived, + }) + + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofRecord.id, + proofFormats: { indy: requestedCredentials.proofFormats.indy }, + }) + + // Faber waits for the presentation from Alice + testLogger.test('Faber waits for presentation from Alice') + faberProofRecord = await faberProofRecordPromise + + const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V2PresentationMessage, + }) + + expect(presentation).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION, + }, + ], + presentationsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + thread: { + threadId: faberProofRecord.threadId, + }, + }) + expect(faberProofRecord.id).not.toBeNull() + expect(faberProofRecord).toMatchObject({ + threadId: faberProofRecord.threadId, + state: ProofState.PresentationReceived, + protocolVersion: ProofProtocolVersion.V2, + }) + + aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.Done, + }) + + // Faber accepts the presentation provided by Alice + testLogger.test('Faber accepts the presentation provided by Alice') + await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + + // Alice waits until she received a presentation acknowledgement + testLogger.test('Alice waits until she receives a presentation acknowledgement') + aliceProofRecord = await aliceProofRecordPromise + + expect(faberProofRecord).toMatchObject({ + // type: ProofRecord.name, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: aliceProofRecord.threadId, + connectionId: expect.any(String), + isVerified: true, + state: ProofState.PresentationReceived, + }) + + expect(aliceProofRecord).toMatchObject({ + // type: ProofRecord.name, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: faberProofRecord.threadId, + connectionId: expect.any(String), + state: ProofState.Done, + }) + }) + + test('Faber starts with proof request to Alice', async () => { + const attributes = { + name: new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + image_0: new ProofAttributeInfo({ + name: 'image_0', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + // Sample predicates + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const requestProofsOptions: RequestProofOptions = { + protocolVersion: ProofProtocolVersion.V2, + connectionId: faberConnection.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324864', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } + + let aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + // Faber sends a presentation request to Alice + testLogger.test('Faber sends a presentation request to Alice') + faberProofRecord = await faberAgent.proofs.requestProof(requestProofsOptions) + + // Alice waits for presentation request from Faber + testLogger.test('Alice waits for presentation request from Faber') + aliceProofRecord = await aliceProofRecordPromise + + const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/request-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION_REQUEST, + }, + ], + requestPresentationsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + }) + + expect(aliceProofRecord.id).not.toBeNull() + expect(aliceProofRecord).toMatchObject({ + threadId: aliceProofRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: ProofProtocolVersion.V2, + }) + + // Alice retrieves the requested credentials and accepts the presentation request + testLogger.test('Alice accepts presentation request from Faber') + + const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + proofRecordId: aliceProofRecord.id, + config: { + filterByPresentationPreview: true, + }, + }) + + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.PresentationReceived, + }) + + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofRecord.id, + proofFormats: { indy: requestedCredentials.proofFormats.indy }, + }) + + // Faber waits until it receives a presentation from Alice + testLogger.test('Faber waits for presentation from Alice') + faberProofRecord = await faberProofRecordPromise + + const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V2PresentationMessage, + }) + + expect(presentation).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION, + }, + ], + presentationsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + thread: { + threadId: faberProofRecord.threadId, + }, + }) + expect(faberProofRecord.id).not.toBeNull() + expect(faberProofRecord).toMatchObject({ + threadId: faberProofRecord.threadId, + state: ProofState.PresentationReceived, + protocolVersion: ProofProtocolVersion.V2, + }) + + aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.Done, + }) + + // Faber accepts the presentation + testLogger.test('Faber accept the presentation from Alice') + await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + + // Alice waits until she receives a presentation acknowledgement + testLogger.test('Alice waits for acceptance by Faber') + aliceProofRecord = await aliceProofRecordPromise + + expect(faberProofRecord).toMatchObject({ + // type: ProofRecord.name, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: aliceProofRecord.threadId, + connectionId: expect.any(String), + isVerified: true, + state: ProofState.PresentationReceived, + }) + + expect(aliceProofRecord).toMatchObject({ + // type: ProofRecord.name, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: faberProofRecord.threadId, + connectionId: expect.any(String), + state: ProofState.Done, + }) + }) + + test('Faber starts with proof request to Alice but gets Problem Reported', async () => { + const attributes = { + name: new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + image_0: new ProofAttributeInfo({ + name: 'image_0', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + // Sample predicates + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const requestProofsOptions: RequestProofOptions = { + protocolVersion: ProofProtocolVersion.V2, + connectionId: faberConnection.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324864', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } + + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + // Faber sends a presentation request to Alice + testLogger.test('Faber sends a presentation request to Alice') + faberProofRecord = await faberAgent.proofs.requestProof(requestProofsOptions) + + // Alice waits for presentation request from Faber + testLogger.test('Alice waits for presentation request from Faber') + aliceProofRecord = await aliceProofRecordPromise + + const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/request-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION_REQUEST, + }, + ], + requestPresentationsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + }) + + expect(aliceProofRecord.id).not.toBeNull() + expect(aliceProofRecord).toMatchObject({ + threadId: aliceProofRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: ProofProtocolVersion.V2, + }) + + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + threadId: aliceProofRecord.threadId, + state: ProofState.Abandoned, + }) + + aliceProofRecord = await aliceAgent.proofs.sendProblemReport(aliceProofRecord.id, 'Problem inside proof request') + + faberProofRecord = await faberProofRecordPromise + + expect(faberProofRecord).toMatchObject({ + threadId: aliceProofRecord.threadId, + state: ProofState.Abandoned, + protocolVersion: ProofProtocolVersion.V2, + }) + }) +}) diff --git a/packages/core/tests/proofs-auto-accept.test.ts b/packages/core/tests/v2-proofs-auto-accept.test.ts similarity index 58% rename from packages/core/tests/proofs-auto-accept.test.ts rename to packages/core/tests/v2-proofs-auto-accept.test.ts index a990c3d070..9104d20bc0 100644 --- a/packages/core/tests/proofs-auto-accept.test.ts +++ b/packages/core/tests/v2-proofs-auto-accept.test.ts @@ -1,4 +1,6 @@ -import type { Agent, ConnectionRecord, PresentationPreview } from '../src' +import type { Agent, ConnectionRecord } from '../src' +import type { ProposeProofOptions, RequestProofOptions } from '../src/modules/proofs/ProofsApiOptions' +import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' import { AutoAcceptProof, @@ -8,6 +10,7 @@ import { ProofPredicateInfo, PredicateType, } from '../src' +import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' import { setupProofsTest, waitForProofRecord } from './helpers' import testLogger from './logger' @@ -29,7 +32,6 @@ describe('Auto accept present proof', () => { AutoAcceptProof.Always )) }) - afterAll(async () => { await faberAgent.shutdown() await faberAgent.wallet.delete() @@ -39,24 +41,40 @@ describe('Auto accept present proof', () => { test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `always`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - const aliceProofRecord = await aliceAgent.proofs.proposeProof(aliceConnection.id, presentationPreview) - testLogger.test('Faber waits for presentation from Alice') - await waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, + const proposeProofOptions: ProposeProofOptions = { + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V2, + proofFormats: { + indy: { + nonce: '1298236324864', + name: 'abc', + version: '1.0', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + }, + }, + } + + const faberProofRecordPromise = waitForProofRecord(faberAgent, { state: ProofState.Done, }) - testLogger.test('Alice waits till it receives presentation ack') - await waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { state: ProofState.Done, }) + + await aliceAgent.proofs.proposeProof(proposeProofOptions) + + testLogger.test('Faber waits for presentation from Alice') + await faberProofRecordPromise + + testLogger.test('Alice waits till it receives presentation ack') + await aliceProofRecordPromise }) test('Faber starts with proof requests to Alice, both with autoAcceptProof on `always`', async () => { testLogger.test('Faber sends presentation request to Alice') - const attributes = { name: new ProofAttributeInfo({ name: 'name', @@ -67,7 +85,6 @@ describe('Auto accept present proof', () => { ], }), } - const predicates = { age: new ProofPredicateInfo({ name: 'age', @@ -81,28 +98,40 @@ describe('Auto accept present proof', () => { }), } - const faberProofRecord = await faberAgent.proofs.requestProof(faberConnection.id, { - name: 'test-proof-request', - requestedAttributes: attributes, - requestedPredicates: predicates, - }) + const requestProofsOptions: RequestProofOptions = { + protocolVersion: ProofProtocolVersion.V2, + connectionId: faberConnection.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324864', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } - testLogger.test('Faber waits for presentation from Alice') - await waitForProofRecord(faberAgent, { - threadId: faberProofRecord.threadId, + const faberProofRecordPromise = waitForProofRecord(faberAgent, { state: ProofState.Done, }) - // Alice waits till it receives presentation ack - await waitForProofRecord(aliceAgent, { - threadId: faberProofRecord.threadId, + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { state: ProofState.Done, }) + + await faberAgent.proofs.requestProof(requestProofsOptions) + + testLogger.test('Faber waits for presentation from Alice') + await faberProofRecordPromise + // Alice waits till it receives presentation ack + await aliceProofRecordPromise }) }) describe('Auto accept on `contentApproved`', () => { beforeAll(async () => { + testLogger.test('Initializing the agents') ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = await setupProofsTest( 'Faber Auto Accept Content Approved Proofs', @@ -110,8 +139,8 @@ describe('Auto accept present proof', () => { AutoAcceptProof.ContentApproved )) }) - afterAll(async () => { + testLogger.test('Shutting down both agents') await faberAgent.shutdown() await faberAgent.wallet.delete() await aliceAgent.shutdown() @@ -120,33 +149,52 @@ describe('Auto accept present proof', () => { test('Alice starts with proof proposal to Faber, both with autoacceptproof on `contentApproved`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - const aliceProofRecord = await aliceAgent.proofs.proposeProof(aliceConnection.id, presentationPreview) - testLogger.test('Faber waits for presentation proposal from Alice') - const faberProofRecord = await waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, + const proposal: ProposeProofOptions = { + connectionId: aliceConnection.id, + protocolVersion: ProofProtocolVersion.V2, + proofFormats: { + indy: { + nonce: '1298236324864', + attributes: presentationPreview.attributes, + predicates: presentationPreview.predicates, + name: 'abc', + version: '1.0', + }, + }, + } + + let faberProofRecordPromise = waitForProofRecord(faberAgent, { state: ProofState.ProposalReceived, }) + const aliceProofRecord = await aliceAgent.proofs.proposeProof(proposal) + + testLogger.test('Faber waits for presentation proposal from Alice') + + await faberProofRecordPromise + testLogger.test('Faber accepts presentation proposal from Alice') - await faberAgent.proofs.acceptProposal(faberProofRecord.id) - testLogger.test('Faber waits for presentation from Alice') - await waitForProofRecord(faberAgent, { + faberProofRecordPromise = waitForProofRecord(faberAgent, { threadId: aliceProofRecord.threadId, state: ProofState.Done, }) - // Alice waits till it receives presentation ack - await waitForProofRecord(aliceAgent, { + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { threadId: aliceProofRecord.threadId, state: ProofState.Done, }) + + testLogger.test('Faber waits for presentation from Alice') + + await faberProofRecordPromise + // Alice waits till it receives presentation ack + await aliceProofRecordPromise }) test('Faber starts with proof requests to Alice, both with autoacceptproof on `contentApproved`', async () => { testLogger.test('Faber sends presentation request to Alice') - const attributes = { name: new ProofAttributeInfo({ name: 'name', @@ -157,7 +205,6 @@ describe('Auto accept present proof', () => { ], }), } - const predicates = { age: new ProofPredicateInfo({ name: 'age', @@ -171,36 +218,35 @@ describe('Auto accept present proof', () => { }), } - const faberProofRecord = await faberAgent.proofs.requestProof(faberConnection.id, { - name: 'test-proof-request', - requestedAttributes: attributes, - requestedPredicates: predicates, - }) + const requestProofsOptions: RequestProofOptions = { + protocolVersion: ProofProtocolVersion.V2, + connectionId: faberConnection.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324866', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } - testLogger.test('Alice waits for presentation request from Faber') - const aliceProofRecord = await waitForProofRecord(aliceAgent, { - threadId: faberProofRecord.threadId, - state: ProofState.RequestReceived, + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.Done, }) - testLogger.test('Alice accepts presentation request from Faber') - const retrievedCredentials = await aliceAgent.proofs.getRequestedCredentialsForProofRequest(aliceProofRecord.id, { - filterByPresentationPreview: true, + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.Done, }) - const requestedCredentials = aliceAgent.proofs.autoSelectCredentialsForProofRequest(retrievedCredentials) - await aliceAgent.proofs.acceptRequest(aliceProofRecord.id, requestedCredentials) + + await faberAgent.proofs.requestProof(requestProofsOptions) testLogger.test('Faber waits for presentation from Alice') - await waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, - state: ProofState.Done, - }) + await faberProofRecordPromise // Alice waits till it receives presentation ack - await waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, - state: ProofState.Done, - }) + await aliceProofRecordPromise }) }) }) diff --git a/yarn.lock b/yarn.lock index 856c644730..dd7f0e014b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27,292 +27,288 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" - integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== dependencies: - "@babel/highlight" "^7.16.7" + "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.17.10": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.10.tgz#711dc726a492dfc8be8220028b1b92482362baab" - integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d" + integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ== "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.12.tgz#b4eb2d7ebc3449b062381644c93050db545b70ee" - integrity sha512-44ODe6O1IVz9s2oJE3rZ4trNNKTX9O7KpQpfAP4t8QII/zwrVRHL7i2pxhqtcY7tqMLrrKfMlBKnm1QlrRFs5w== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.9.tgz#805461f967c77ff46c74ca0460ccf4fe933ddd59" + integrity sha512-1LIb1eL8APMy91/IMW+31ckrfBM4yCoLaVzoDhZUKSM4cu1L1nIidyxkCgzPAgrC5WEz36IPEr/eSeSF9pIn+g== dependencies: "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.12" - "@babel/helper-compilation-targets" "^7.17.10" - "@babel/helper-module-transforms" "^7.17.12" - "@babel/helpers" "^7.17.9" - "@babel/parser" "^7.17.12" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.12" - "@babel/types" "^7.17.12" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.9" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-module-transforms" "^7.18.9" + "@babel/helpers" "^7.18.9" + "@babel/parser" "^7.18.9" + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.17.12", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.12.tgz#5970e6160e9be0428e02f4aba62d8551ec366cc8" - integrity sha512-V49KtZiiiLjH/CnIW6OjJdrenrGoyh6AmKQ3k2AZFKozC1h846Q4NYlZ5nqAigPDUXfGzC88+LOUuG8yKd2kCw== +"@babel/generator@^7.18.9", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.9.tgz#68337e9ea8044d6ddc690fb29acae39359cca0a5" + integrity sha512-wt5Naw6lJrL1/SGkipMiFxJjtyczUWTP38deiP1PO60HsBjDeKk08CGC3S8iVuvf0FmTdgKwU1KIXzSKL1G0Ug== dependencies: - "@babel/types" "^7.17.12" - "@jridgewell/gen-mapping" "^0.3.0" + "@babel/types" "^7.18.9" + "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" - integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== dependencies: - "@babel/types" "^7.16.7" + "@babel/types" "^7.18.6" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz#38d138561ea207f0f69eb1626a418e4f7e6a580b" - integrity sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" + integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== dependencies: - "@babel/helper-explode-assignable-expression" "^7.16.7" - "@babel/types" "^7.16.7" + "@babel/helper-explode-assignable-expression" "^7.18.6" + "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.10": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz#09c63106d47af93cf31803db6bc49fef354e2ebe" - integrity sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf" + integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg== dependencies: - "@babel/compat-data" "^7.17.10" - "@babel/helper-validator-option" "^7.16.7" + "@babel/compat-data" "^7.18.8" + "@babel/helper-validator-option" "^7.18.6" browserslist "^4.20.2" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.12.tgz#d4f8393fc4838cbff6b7c199af5229aee16d07cf" - integrity sha512-sZoOeUTkFJMyhqCei2+Z+wtH/BehW8NVKQt7IRUQlRiOARuXymJYfN/FCcI8CvVbR0XVyDM6eLFOlR7YtiXnew== +"@babel/helper-create-class-features-plugin@^7.18.6": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz#d802ee16a64a9e824fcbf0a2ffc92f19d58550ce" + integrity sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.17.9" - "@babel/helper-member-expression-to-functions" "^7.17.7" - "@babel/helper-optimise-call-expression" "^7.16.7" - "@babel/helper-replace-supers" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" -"@babel/helper-create-regexp-features-plugin@^7.16.7": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.12.tgz#bb37ca467f9694bbe55b884ae7a5cc1e0084e4fd" - integrity sha512-b2aZrV4zvutr9AIa6/gA3wsZKRwTKYoDxYiFKcESS3Ug2GTXzwBEvMuuFLhCQpEnRXs1zng4ISAXSUxxKBIcxw== +"@babel/helper-create-regexp-features-plugin@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz#3e35f4e04acbbf25f1b3534a657610a000543d3c" + integrity sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - regexpu-core "^5.0.1" + "@babel/helper-annotate-as-pure" "^7.18.6" + regexpu-core "^5.1.0" -"@babel/helper-define-polyfill-provider@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665" - integrity sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA== +"@babel/helper-define-polyfill-provider@^0.3.1", "@babel/helper-define-polyfill-provider@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz#bd10d0aca18e8ce012755395b05a79f45eca5073" + integrity sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg== dependencies: - "@babel/helper-compilation-targets" "^7.13.0" - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/traverse" "^7.13.0" + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" debug "^4.1.1" lodash.debounce "^4.0.8" resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" - integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-explode-assignable-expression@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz#12a6d8522fdd834f194e868af6354e8650242b7a" - integrity sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-function-name@^7.16.7", "@babel/helper-function-name@^7.17.9": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" - integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== - dependencies: - "@babel/template" "^7.16.7" - "@babel/types" "^7.17.0" - -"@babel/helper-hoist-variables@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" - integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-member-expression-to-functions@^7.16.7", "@babel/helper-member-expression-to-functions@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz#a34013b57d8542a8c4ff8ba3f747c02452a4d8c4" - integrity sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw== - dependencies: - "@babel/types" "^7.17.0" - -"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" - integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== - dependencies: - "@babel/types" "^7.16.7" +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== -"@babel/helper-module-transforms@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.12.tgz#bec00139520cb3feb078ef7a4578562480efb77e" - integrity sha512-t5s2BeSWIghhFRPh9XMn6EIGmvn8Lmw5RVASJzkIx1mSemubQQBNIZiQD7WzaFmaHIrjAec4x8z9Yx8SjJ1/LA== +"@babel/helper-explode-assignable-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" + integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== dependencies: - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-simple-access" "^7.17.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/helper-validator-identifier" "^7.16.7" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.12" - "@babel/types" "^7.17.12" - -"@babel/helper-optimise-call-expression@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2" - integrity sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" - integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== - -"@babel/helper-replace-supers@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz#e9f5f5f32ac90429c1a4bdec0f231ef0c2838ab1" - integrity sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw== - dependencies: - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-member-expression-to-functions" "^7.16.7" - "@babel/helper-optimise-call-expression" "^7.16.7" - "@babel/traverse" "^7.16.7" - "@babel/types" "^7.16.7" - -"@babel/helper-simple-access@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz#aaa473de92b7987c6dfa7ce9a7d9674724823367" - integrity sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA== - dependencies: - "@babel/types" "^7.17.0" - -"@babel/helper-skip-transparent-expression-wrappers@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09" - integrity sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw== - dependencies: - "@babel/types" "^7.16.0" - -"@babel/helper-split-export-declaration@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" - integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-validator-identifier@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" - integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== - -"@babel/helper-validator-option@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" - integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== - -"@babel/helpers@^7.17.9": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.9.tgz#b2af120821bfbe44f9907b1826e168e819375a1a" - integrity sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q== - dependencies: - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.9" - "@babel/types" "^7.17.0" - -"@babel/highlight@^7.10.4", "@babel/highlight@^7.16.7": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.12.tgz#257de56ee5afbd20451ac0a75686b6b404257351" - integrity sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg== - dependencies: - "@babel/helper-validator-identifier" "^7.16.7" + "@babel/types" "^7.18.6" + +"@babel/helper-function-name@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz#940e6084a55dee867d33b4e487da2676365e86b0" + integrity sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A== + dependencies: + "@babel/template" "^7.18.6" + "@babel/types" "^7.18.9" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-member-expression-to-functions@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" + integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== + dependencies: + "@babel/types" "^7.18.9" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz#5a1079c005135ed627442df31a42887e80fcb712" + integrity sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/helper-optimise-call-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" + integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.8.0": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz#4b8aea3b069d8cb8a72cdfe28ddf5ceca695ef2f" + integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w== + +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz#1092e002feca980fbbb0bd4d51b74a65c6a500e6" + integrity sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/helper-simple-access@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" + integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818" + integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw== + dependencies: + "@babel/types" "^7.18.9" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helpers@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.9.tgz#4bef3b893f253a1eced04516824ede94dcfe7ff9" + integrity sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ== + dependencies: + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.12.tgz#36c2ed06944e3691ba82735fc4cf62d12d491a23" - integrity sha512-FLzHmN9V3AJIrWfOpvRlZCeVg/WLdicSnTMsLur6uDj9TT8ymUlG9XxURdW/XvuygK+2CW0poOJABdA4m/YKxA== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.18.6", "@babel/parser@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539" + integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg== "@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.1.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.17.12.tgz#84f65c0cc247d46f40a6da99aadd6438315d80a4" - integrity sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" + integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== dependencies: - "@babel/helper-create-class-features-plugin" "^7.17.12" - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-proposal-export-default-from@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.17.12.tgz#df785e638618d8ffa14e08c78c44d9695d083b73" - integrity sha512-LpsTRw725eBAXXKUOnJJct+SEaOzwR78zahcLuripD2+dKc2Sj+8Q2DzA+GC/jOpOu/KlDXuxrzG214o1zTauQ== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.9.tgz#9dfad26452e53cae8f045c6153e82dc50e9bee89" + integrity sha512-1qtsLNCDm5awHLIt+2qAFDi31XC94r4QepMQcOosC7FpY6O+Bgay5f2IyAQt2wvm1TARumpFprnQt5pTIJ9nUg== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/plugin-syntax-export-default-from" "^7.16.7" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-export-default-from" "^7.18.6" "@babel/plugin-proposal-nullish-coalescing-operator@^7.0.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.1.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.17.12.tgz#1e93079bbc2cbc756f6db6a1925157c4a92b94be" - integrity sha512-ws/g3FSGVzv+VH86+QvgtuJL/kR67xaEIF2x0iPqdDfYW6ra6JF3lKVBkWynRLcNtIC1oCTfDRVxmm2mKzy+ag== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" + integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" "@babel/plugin-proposal-object-rest-spread@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.12.tgz#f94a91715a7f2f8cfb3c06af820c776440bc0148" - integrity sha512-6l9cO3YXXRh4yPCPRA776ZyJ3RobG4ZKJZhp7NDRbKIOeV3dBPG8FXCF7ZtiO2RTCIOkQOph1xDDcc01iWVNjQ== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz#f9434f6beb2c8cae9dfcf97d2a5941bbbf9ad4e7" + integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q== dependencies: - "@babel/compat-data" "^7.17.10" - "@babel/helper-compilation-targets" "^7.17.10" - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/compat-data" "^7.18.8" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.17.12" + "@babel/plugin-transform-parameters" "^7.18.8" "@babel/plugin-proposal-optional-catch-binding@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz#c623a430674ffc4ab732fd0a0ae7722b67cb74cf" - integrity sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" + integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-proposal-optional-chaining@^7.0.0", "@babel/plugin-proposal-optional-chaining@^7.1.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.17.12.tgz#f96949e9bacace3a9066323a5cf90cfb9de67174" - integrity sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993" + integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-async-generators@^7.8.4": @@ -343,19 +339,19 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-export-default-from@^7.0.0", "@babel/plugin-syntax-export-default-from@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.16.7.tgz#fa89cf13b60de2c3f79acdc2b52a21174c6de060" - integrity sha512-4C3E4NsrLOgftKaTYTULhHsuQrGv3FHrBzOMDiS7UYKIpgGBkAdawg4h+EI8zPeK9M0fiIIh72hIwsI24K7MbA== +"@babel/plugin-syntax-export-default-from@^7.0.0", "@babel/plugin-syntax-export-default-from@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz#8df076711a4818c4ce4f23e61d622b0ba2ff84bc" + integrity sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-syntax-flow@^7.0.0", "@babel/plugin-syntax-flow@^7.17.12", "@babel/plugin-syntax-flow@^7.2.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.17.12.tgz#23d852902acd19f42923fca9d0f196984d124e73" - integrity sha512-B8QIgBvkIG6G2jgsOHQUist7Sm0EBLDCx8sen072IwqNuzMegZNXrYnSv77cYzA8mLDZAfQYqsLIhimiP1s2HQ== +"@babel/plugin-syntax-flow@^7.0.0", "@babel/plugin-syntax-flow@^7.18.6", "@babel/plugin-syntax-flow@^7.2.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz#774d825256f2379d06139be0c723c4dd444f3ca1" + integrity sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" @@ -371,12 +367,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.17.12.tgz#834035b45061983a491f60096f61a2e7c5674a47" - integrity sha512-spyY3E3AURfxh/RHtjx5j6hs8am5NbUBGfcZ2vB3uShSpZdQyXSf5rR5Mk76vbtlAZOelyVQ71Fg0x9SG4fsog== +"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" @@ -427,266 +423,267 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.17.12", "@babel/plugin-syntax-typescript@^7.7.2": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz#b54fc3be6de734a56b87508f99d6428b5b605a7b" - integrity sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw== +"@babel/plugin-syntax-typescript@^7.18.6", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz#1c09cd25795c7c2b8a4ba9ae49394576d4133285" + integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-arrow-functions@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.17.12.tgz#dddd783b473b1b1537ef46423e3944ff24898c45" - integrity sha512-PHln3CNi/49V+mza4xMwrg+WGYevSF1oaiXaC2EQfdp4HWlSjRsrDXWJiQBKpP7749u6vQ9mcry2uuFOv5CXvA== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" + integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-block-scoped-functions@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz#4d0d57d9632ef6062cdf354bb717102ee042a620" - integrity sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" + integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-block-scoping@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.17.12.tgz#68fc3c4b3bb7dfd809d97b7ed19a584052a2725c" - integrity sha512-jw8XW/B1i7Lqwqj2CbrViPcZijSxfguBWZP2aN59NHgxUyO/OcO1mfdCxH13QhN5LbWhPkX+f+brKGhZTiqtZQ== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz#f9b7e018ac3f373c81452d6ada8bd5a18928926d" + integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-classes@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.17.12.tgz#da889e89a4d38375eeb24985218edeab93af4f29" - integrity sha512-cvO7lc7pZat6BsvH6l/EGaI8zpl8paICaoGk+7x7guvtfak/TbIf66nYmJOH13EuG0H+Xx3M+9LQDtSvZFKXKw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.17.9" - "@babel/helper-optimise-call-expression" "^7.16.7" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-replace-supers" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz#90818efc5b9746879b869d5ce83eb2aa48bbc3da" + integrity sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" "@babel/plugin-transform-computed-properties@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.17.12.tgz#bca616a83679698f3258e892ed422546e531387f" - integrity sha512-a7XINeplB5cQUWMg1E/GI1tFz3LfK021IjV1rj1ypE+R7jHm+pIHmHl25VNkZxtx9uuYp7ThGk8fur1HHG7PgQ== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" + integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-destructuring@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.12.tgz#0861d61e75e2401aca30f2570d46dfc85caacf35" - integrity sha512-P8pt0YiKtX5UMUL5Xzsc9Oyij+pJE6JuC+F1k0/brq/OOGs5jDa1If3OY0LRWGvJsJhI+8tsiecL3nJLc0WTlg== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz#68906549c021cb231bee1db21d3b5b095f8ee292" + integrity sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-exponentiation-operator@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz#efa9862ef97e9e9e5f653f6ddc7b665e8536fe9b" - integrity sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" + integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-flow-strip-types@^7.0.0", "@babel/plugin-transform-flow-strip-types@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.17.12.tgz#5e070f99a4152194bd9275de140e83a92966cab3" - integrity sha512-g8cSNt+cHCpG/uunPQELdq/TeV3eg1OLJYwxypwHtAWo9+nErH3lQx9CSO2uI9lF74A0mR0t4KoMjs1snSgnTw== +"@babel/plugin-transform-flow-strip-types@^7.0.0", "@babel/plugin-transform-flow-strip-types@^7.18.6": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.18.9.tgz#5b4cc521426263b5ce08893a2db41097ceba35bf" + integrity sha512-+G6rp2zRuOAInY5wcggsx4+QVao1qPM0osC9fTUVlAV3zOrzTCnrMAFVnR6+a3T8wz1wFIH7KhYMcMB3u1n80A== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/plugin-syntax-flow" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-flow" "^7.18.6" "@babel/plugin-transform-for-of@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.17.12.tgz#5397c22554ec737a27918e7e7e0e7b679b05f5ec" - integrity sha512-76lTwYaCxw8ldT7tNmye4LLwSoKDbRCBzu6n/DcK/P3FOR29+38CIIaVIZfwol9By8W/QHORYEnYSLuvcQKrsg== + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" + integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-function-name@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz#5ab34375c64d61d083d7d2f05c38d90b97ec65cf" - integrity sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" + integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== dependencies: - "@babel/helper-compilation-targets" "^7.16.7" - "@babel/helper-function-name" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-literals@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.17.12.tgz#97131fbc6bbb261487105b4b3edbf9ebf9c830ae" - integrity sha512-8iRkvaTjJciWycPIZ9k9duu663FT7VrBdNqNgxnVXEFwOIp55JWcZd23VBRySYbnS3PwQ3rGiabJBBBGj5APmQ== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" + integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-member-expression-literals@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz#6e5dcf906ef8a098e630149d14c867dd28f92384" - integrity sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" + integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.1.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.12.tgz#37691c7404320d007288edd5a2d8600bcef61c34" - integrity sha512-tVPs6MImAJz+DiX8Y1xXEMdTk5Lwxu9jiPjlS+nv5M2A59R7+/d1+9A8C/sbuY0b3QjIxqClkj6KAplEtRvzaA== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz#afd243afba166cca69892e24a8fd8c9f2ca87883" + integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q== dependencies: - "@babel/helper-module-transforms" "^7.17.12" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-simple-access" "^7.17.7" + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" babel-plugin-dynamic-import-node "^2.3.3" "@babel/plugin-transform-object-assign@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.16.7.tgz#5fe08d63dccfeb6a33aa2638faf98e5c584100f8" - integrity sha512-R8mawvm3x0COTJtveuoqZIjNypn2FjfvXZr4pSQ8VhEFBuQGBz4XhHasZtHXjgXU4XptZ4HtGof3NoYc93ZH9Q== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.18.6.tgz#7830b4b6f83e1374a5afb9f6111bcfaea872cdd2" + integrity sha512-mQisZ3JfqWh2gVXvfqYCAAyRs6+7oev+myBsTwW5RnPhYXOTuCEw2oe3YgxlXMViXUS53lG8koulI7mJ+8JE+A== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-object-super@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz#ac359cf8d32cf4354d27a46867999490b6c32a94" - integrity sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" + integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.6" -"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.17.12.tgz#eb467cd9586ff5ff115a9880d6fdbd4a846b7766" - integrity sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA== +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz#ee9f1a0ce6d78af58d0956a9378ea3427cccb48a" + integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-property-literals@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz#2dadac85155436f22c696c4827730e0fe1057a55" - integrity sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" + integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-react-display-name@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz#7b6d40d232f4c0f550ea348593db3b21e2404340" - integrity sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz#8b1125f919ef36ebdfff061d664e266c666b9415" + integrity sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-react-jsx-self@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.17.12.tgz#7f2e9b8c08d6a4204733138d8c29d4dba4bb66c2" - integrity sha512-7S9G2B44EnYOx74mue02t1uD8ckWZ/ee6Uz/qfdzc35uWHX5NgRy9i+iJSb2LFRgMd+QV9zNcStQaazzzZ3n3Q== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz#3849401bab7ae8ffa1e3e5687c94a753fc75bda7" + integrity sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-react-jsx-source@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.16.7.tgz#1879c3f23629d287cc6186a6c683154509ec70c0" - integrity sha512-rONFiQz9vgbsnaMtQlZCjIRwhJvlrPET8TabIUK2hzlXw9B9s2Ieaxte1SCOOXMbWRHodbKixNf3BLcWVOQ8Bw== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.18.6.tgz#06e9ae8a14d2bc19ce6e3c447d842032a50598fc" + integrity sha512-utZmlASneDfdaMh0m/WausbjUjEdGrQJz0vFK93d7wD3xf5wBtX219+q6IlCNZeguIcxS2f/CvLZrlLSvSHQXw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-react-jsx@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.12.tgz#2aa20022709cd6a3f40b45d60603d5f269586dba" - integrity sha512-Lcaw8bxd1DKht3thfD4A12dqo1X16he1Lm8rIv8sTwjAYNInRS1qHa9aJoqvzpscItXvftKDCfaEQzwoVyXpEQ== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.18.6.tgz#2721e96d31df96e3b7ad48ff446995d26bc028ff" + integrity sha512-Mz7xMPxoy9kPS/JScj6fJs03TZ/fZ1dJPlMjRAgTaxaS0fUBk8FV/A2rRgfPsVCZqALNwMexD+0Uaf5zlcKPpw== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/plugin-syntax-jsx" "^7.17.12" - "@babel/types" "^7.17.12" + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-jsx" "^7.18.6" + "@babel/types" "^7.18.6" "@babel/plugin-transform-regenerator@^7.0.0": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.17.9.tgz#0a33c3a61cf47f45ed3232903683a0afd2d3460c" - integrity sha512-Lc2TfbxR1HOyn/c6b4Y/b6NHoTb67n/IoWLxTu4kC7h4KQnWlhCq2S8Tx0t2SVvv5Uu87Hs+6JEJ5kt2tYGylQ== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73" + integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== dependencies: + "@babel/helper-plugin-utils" "^7.18.6" regenerator-transform "^0.15.0" "@babel/plugin-transform-runtime@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.12.tgz#5dc79735c4038c6f4fc0490f68f2798ce608cadd" - integrity sha512-xsl5MeGjWnmV6Ui9PfILM2+YRpa3GqLOrczPpXV3N2KCgQGU+sU8OfzuMbjkIdfvZEZIm+3y0V7w58sk0SGzlw== - dependencies: - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-plugin-utils" "^7.17.12" - babel-plugin-polyfill-corejs2 "^0.3.0" - babel-plugin-polyfill-corejs3 "^0.5.0" - babel-plugin-polyfill-regenerator "^0.3.0" + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.9.tgz#d9e4b1b25719307bfafbf43065ed7fb3a83adb8f" + integrity sha512-wS8uJwBt7/b/mzE13ktsJdmS4JP/j7PQSaADtnb4I2wL0zK51MQ0pmF8/Jy0wUIS96fr+fXT6S/ifiPXnvrlSg== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.9" + babel-plugin-polyfill-corejs2 "^0.3.1" + babel-plugin-polyfill-corejs3 "^0.5.2" + babel-plugin-polyfill-regenerator "^0.3.1" semver "^6.3.0" "@babel/plugin-transform-shorthand-properties@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz#e8549ae4afcf8382f711794c0c7b6b934c5fbd2a" - integrity sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" + integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-spread@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.17.12.tgz#c112cad3064299f03ea32afed1d659223935d1f5" - integrity sha512-9pgmuQAtFi3lpNUstvG9nGfk9DkrdmWNp9KeKPFmuZCpEnxRzYlS8JgwPjYj+1AWDOSvoGN0H30p1cBOmT/Svg== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz#6ea7a6297740f381c540ac56caf75b05b74fb664" + integrity sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-transform-sticky-regex@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz#c84741d4f4a38072b9a1e2e3fd56d359552e8660" - integrity sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" + integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-template-literals@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.17.12.tgz#4aec0a18f39dd86c442e1d077746df003e362c6e" - integrity sha512-kAKJ7DX1dSRa2s7WN1xUAuaQmkTpN+uig4wCKWivVXIObqGbVTUlSavHyfI2iZvz89GFAMGm9p2DBJ4Y1Tp0hw== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" + integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-typescript@^7.17.12", "@babel/plugin-transform-typescript@^7.5.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.17.12.tgz#9654587131bc776ff713218d929fa9a2e98ca16d" - integrity sha512-ICbXZqg6hgenjmwciVI/UfqZtExBrZOrS8sLB5mTHGO/j08Io3MmooULBiijWk9JBknjM3CbbtTc/0ZsqLrjXQ== +"@babel/plugin-transform-typescript@^7.18.6", "@babel/plugin-transform-typescript@^7.5.0": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.8.tgz#303feb7a920e650f2213ef37b36bbf327e6fa5a0" + integrity sha512-p2xM8HI83UObjsZGofMV/EdYjamsDm6MoN3hXPYIT0+gxIoopE+B7rPYKAxfrz9K9PK7JafTTjqYC6qipLExYA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.17.12" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/plugin-syntax-typescript" "^7.17.12" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-typescript" "^7.18.6" "@babel/plugin-transform-unicode-regex@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz#0f7aa4a501198976e25e82702574c34cfebe9ef2" - integrity sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" + integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/preset-flow@^7.0.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.17.12.tgz#664a5df59190260939eee862800a255bef3bd66f" - integrity sha512-7QDz7k4uiaBdu7N89VKjUn807pJRXmdirQu0KyR9LXnQrr5Jt41eIMKTS7ljej+H29erwmMrwq9Io9mJHLI3Lw== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.18.6.tgz#83f7602ba566e72a9918beefafef8ef16d2810cb" + integrity sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-validator-option" "^7.16.7" - "@babel/plugin-transform-flow-strip-types" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-flow-strip-types" "^7.18.6" "@babel/preset-typescript@^7.1.0": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.17.12.tgz#40269e0a0084d56fc5731b6c40febe1c9a4a3e8c" - integrity sha512-S1ViF8W2QwAKUGJXxP9NAfNaqGDdEBJKpYkxHf5Yy2C4NPPzXGeR3Lhk7G8xJaaLcFTRfNjVbtbVtm8Gb0mqvg== + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399" + integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-validator-option" "^7.16.7" - "@babel/plugin-transform-typescript" "^7.17.12" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-typescript" "^7.18.6" "@babel/register@^7.0.0": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.17.7.tgz#5eef3e0f4afc07e25e847720e7b987ae33f08d0b" - integrity sha512-fg56SwvXRifootQEDQAu1mKdjh5uthPzdO0N6t358FktfL4XjAVXuH58ULoiW8mesxiOgNIrxiImqEwv0+hRRA== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.18.9.tgz#1888b24bc28d5cc41c412feb015e9ff6b96e439c" + integrity sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw== dependencies: clone-deep "^4.0.1" find-cache-dir "^2.0.0" @@ -695,43 +692,43 @@ source-map-support "^0.5.16" "@babel/runtime@^7.8.4": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" - integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" + integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.0.0", "@babel/template@^7.16.7", "@babel/template@^7.3.3": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" - integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/parser" "^7.16.7" - "@babel/types" "^7.16.7" - -"@babel/traverse@^7.0.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.17.12", "@babel/traverse@^7.17.9", "@babel/traverse@^7.7.2": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.12.tgz#011874d2abbca0ccf1adbe38f6f7a4ff1747599c" - integrity sha512-zULPs+TbCvOkIFd4FrG53xrpxvCBwLIgo6tO0tJorY7YV2IWFxUfS/lXDJbGgfyYt9ery/Gxj2niwttNnB0gIw== - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.12" - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.17.9" - "@babel/helper-hoist-variables" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.17.12" - "@babel/types" "^7.17.12" +"@babel/template@^7.0.0", "@babel/template@^7.18.6", "@babel/template@^7.3.3": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" + integrity sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/traverse@^7.0.0", "@babel/traverse@^7.18.9", "@babel/traverse@^7.7.2": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.9.tgz#deeff3e8f1bad9786874cb2feda7a2d77a904f98" + integrity sha512-LcPAnujXGwBgv3/WHv01pHtb2tihcyW1XuL9wd7jqh1Z8AQkTd+QVjMrMijrln0T7ED3UXLIy36P9Ao7W75rYg== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.9" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.18.9" + "@babel/types" "^7.18.9" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.17.12", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.12.tgz#1210690a516489c0200f355d87619157fbbd69a0" - integrity sha512-rH8i29wcZ6x9xjzI5ILHL/yZkbQnCERdHlogKuIb4PUr7do4iT8DPekrTbBLWTnRQm6U0GYABbTMSzijmEqlAg== +"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.9.tgz#7148d64ba133d8d73a41b3172ac4b83a1452205f" + integrity sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg== dependencies: - "@babel/helper-validator-identifier" "^7.16.7" + "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -747,17 +744,12 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@cspotcode/source-map-consumer@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" - integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== - -"@cspotcode/source-map-support@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" - integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== dependencies: - "@cspotcode/source-map-consumer" "0.8.0" + "@jridgewell/trace-mapping" "0.3.9" "@digitalbazaar/security-context@^1.0.0": version "1.0.0" @@ -1072,34 +1064,42 @@ "@jridgewell/set-array" "^1.0.0" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/gen-mapping@^0.3.0": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9" - integrity sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg== +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== dependencies: - "@jridgewell/set-array" "^1.0.0" + "@jridgewell/set-array" "^1.0.1" "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" "@jridgewell/resolve-uri@^3.0.3": - version "3.0.7" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" - integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== -"@jridgewell/set-array@^1.0.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" - integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.13" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" - integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping@^0.3.9": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" - integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w== + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== dependencies: "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" @@ -1775,7 +1775,22 @@ npmlog "^4.1.2" write-file-atomic "^3.0.3" -"@mattrglobal/bbs-signatures@1.0.0", "@mattrglobal/bbs-signatures@^1.0.0": +"@mapbox/node-pre-gyp@1.0.9": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz#09a8781a3a036151cdebbe8719d6f8b25d4058bc" + integrity sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + +"@mattrglobal/bbs-signatures@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@mattrglobal/bbs-signatures/-/bbs-signatures-1.0.0.tgz#8ff272c6d201aadab7e08bd84dbfd6e0d48ba12d" integrity sha512-FFzybdKqSCrS/e7pl5s6Tl/m/x8ZD5EMBbcTBQaqSOms/lebm91lFukYOIe2qc0a5o+gLhtRKye8OfKwD1Ex/g== @@ -1784,6 +1799,15 @@ optionalDependencies: "@mattrglobal/node-bbs-signatures" "0.13.0" +"@mattrglobal/bbs-signatures@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@mattrglobal/bbs-signatures/-/bbs-signatures-1.1.0.tgz#031418c6a003d1782e3aff95995bda6a0605f9f1" + integrity sha512-uf74y3S7kAxL3FZrva6u+BF3VY3El5QI9IMkyBCoNoJvO+nWJewmTqLMLWEh6QJ1N5egZfDCI4PuS9ISrZJTZg== + dependencies: + "@stablelib/random" "1.0.0" + optionalDependencies: + "@mattrglobal/node-bbs-signatures" "0.15.0" + "@mattrglobal/bls12381-key-pair@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@mattrglobal/bls12381-key-pair/-/bls12381-key-pair-1.0.0.tgz#2959f8663595de0209bebe88517235ae34f1e2b1" @@ -1801,6 +1825,14 @@ neon-cli "0.8.2" node-pre-gyp "0.17.0" +"@mattrglobal/node-bbs-signatures@0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@mattrglobal/node-bbs-signatures/-/node-bbs-signatures-0.15.0.tgz#acbe578253fa95d96141bd44cb65e75cd0b79c67" + integrity sha512-ipIwL1neAW/gwC0jg0aOFWiIipdvn6kq0Ta2ccSgc1pZUg6xPYj7dRMEaR6QJ9zV6qOBe7awETd1CoLunwwsLA== + dependencies: + "@mapbox/node-pre-gyp" "1.0.9" + neon-cli "0.10.1" + "@multiformats/base-x@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@multiformats/base-x/-/base-x-4.0.1.tgz#95ff0fa58711789d53aefb2590a8b7a4e715d121" @@ -1930,10 +1962,10 @@ "@octokit/types" "^6.0.3" universal-user-agent "^6.0.0" -"@octokit/openapi-types@^11.2.0": - version "11.2.0" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-11.2.0.tgz#b38d7fc3736d52a1e96b230c1ccd4a58a2f400a6" - integrity sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA== +"@octokit/openapi-types@^12.11.0": + version "12.11.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-12.11.0.tgz#da5638d64f2b919bca89ce6602d059f1b52d3ef0" + integrity sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ== "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" @@ -1941,11 +1973,11 @@ integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw== "@octokit/plugin-paginate-rest@^2.16.8": - version "2.17.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz#32e9c7cab2a374421d3d0de239102287d791bce7" - integrity sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw== + version "2.21.3" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz#7f12532797775640dbb8224da577da7dc210c87e" + integrity sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw== dependencies: - "@octokit/types" "^6.34.0" + "@octokit/types" "^6.40.0" "@octokit/plugin-request-log@^1.0.4": version "1.0.4" @@ -1953,11 +1985,11 @@ integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== "@octokit/plugin-rest-endpoint-methods@^5.12.0": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz#8c46109021a3412233f6f50d28786f8e552427ba" - integrity sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA== + version "5.16.2" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz#7ee8bf586df97dd6868cf68f641354e908c25342" + integrity sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw== dependencies: - "@octokit/types" "^6.34.0" + "@octokit/types" "^6.39.0" deprecation "^2.3.1" "@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": @@ -1991,19 +2023,19 @@ "@octokit/plugin-request-log" "^1.0.4" "@octokit/plugin-rest-endpoint-methods" "^5.12.0" -"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.34.0": - version "6.34.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.34.0.tgz#c6021333334d1ecfb5d370a8798162ddf1ae8218" - integrity sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw== +"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.39.0", "@octokit/types@^6.40.0": + version "6.41.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.41.0.tgz#e58ef78d78596d2fb7df9c6259802464b5f84a04" + integrity sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg== dependencies: - "@octokit/openapi-types" "^11.2.0" + "@octokit/openapi-types" "^12.11.0" "@peculiar/asn1-schema@^2.1.6": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.1.8.tgz#552300a1ed7991b22c9abf789a3920a3cb94c26b" - integrity sha512-u34H/bpqCdDuqrCVZvH0vpwFBT/dNEdNY+eE8u4IuC26yYnhDkXF4+Hliqca88Avbb7hyN2EF/eokyDdyS7G/A== + version "2.2.0" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.2.0.tgz#d8a54527685c8dee518e6448137349444310ad64" + integrity sha512-1ENEJNY7Lwlua/1wvzpYP194WtjQBfFxvde2FlzfBFh/ln6wvChrtxlORhbKEnYswzn6fOC4c7HdC5izLPMTJg== dependencies: - asn1js "^3.0.4" + asn1js "^3.0.5" pvtsutils "^1.3.2" tslib "^2.4.0" @@ -2271,24 +2303,24 @@ integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== "@tsconfig/node10@^1.0.7": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" - integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== "@tsconfig/node12@^1.0.7": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" - integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== "@tsconfig/node14@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" - integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== "@tsconfig/node16@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" - integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": version "7.1.19" @@ -2359,9 +2391,9 @@ "@types/json-schema" "*" "@types/estree@*": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== "@types/events@^3.0.0": version "3.0.0" @@ -2369,9 +2401,9 @@ integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== "@types/express-serve-static-core@^4.17.18": - version "4.17.28" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" - integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== + version "4.17.30" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz#0f2f99617fa8f9696170c46152ccf7500b34ac04" + integrity sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ== dependencies: "@types/node" "*" "@types/qs" "*" @@ -2465,10 +2497,10 @@ resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-1.27.1.tgz#aceeb2d5be8fccf541237e184e37ecff5faa9096" integrity sha512-cPiXpOvPFDr2edMnOXlz3UBDApwUfR+cpizvxCy0n3vp9bz/qe8BWzHPIEFcy+ogUOyjKuCISgyq77ELZPmkkg== -"@types/mime@^1": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" - integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== +"@types/mime@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.0.tgz#e9a9903894405c6a6551f1774df4e64d9804d69c" + integrity sha512-fccbsHKqFDXClBZTDLA43zl0+TbxyIwyzIzwwhvoJvhNjOErCdeX2xJbURimv2EbSVUGav001PaCJg4mZxMl4w== "@types/minimatch@^3.0.3": version "3.0.5" @@ -2481,9 +2513,9 @@ integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== "@types/node-fetch@^2.5.10": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.1.tgz#8f127c50481db65886800ef496f20bbf15518975" - integrity sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA== + version "2.6.2" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" + integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A== dependencies: "@types/node" "*" form-data "^3.0.0" @@ -2509,9 +2541,9 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prettier@^2.1.5": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.1.tgz#76e72d8a775eef7ce649c63c8acae1a0824bbaed" - integrity sha512-XFjFHmaLVifrAKaZ+EKghFHtHSUonyw8P2Qmy2/+osBnrKbH9UYtlK10zg8/kCt47MFilll/DEDKy3DHfJ0URw== + version "2.6.4" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.4.tgz#ad899dad022bab6b5a9f0a0fe67c2f7a4a8950ed" + integrity sha512-fOwvpvQYStpb/zHMx0Cauwywu9yLDmzWiiQBC7gJyq5tYLUXFZvDG7VK1B7WBxxjBJNKFOZ0zLoOQn8vmATbhw== "@types/prop-types@*": version "15.7.5" @@ -2529,16 +2561,16 @@ integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== "@types/react-native@^0.64.10": - version "0.64.24" - resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.64.24.tgz#54d8aaadc12d429004b0a573dc3a65ad27430cc6" - integrity sha512-qgqOJub7BYsAkcg3VSL3w63cgJdLoMmAX6TSTAPL53heCzUkIdtpWqjyNRH0n7jPjxPGG1Qmsv6GSUh7IfyqRg== + version "0.64.26" + resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.64.26.tgz#f19fdfe7f0d8a36f6864409b28e69c5f7d602cf3" + integrity sha512-L1U0+wM7GSq1uGu6d/Z9wKB705xyr7Pg47JiXjp9P8DZAFpqP4xsEM/PQ8hcCopEupo6ltQ8ipqoDJ4Nb9Lw5Q== dependencies: - "@types/react" "*" + "@types/react" "^17" -"@types/react@*": - version "18.0.9" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.9.tgz#d6712a38bd6cd83469603e7359511126f122e878" - integrity sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw== +"@types/react@^17": + version "17.0.48" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.48.tgz#a4532a8b91d7b27b8768b6fc0c3bccb760d15a6c" + integrity sha512-zJ6IYlJ8cYYxiJfUaZOQee4lh99mFihBoqkOSEGV+dFi9leROW6+PgstzQ+w3gWTnUfskALtQPGHK6dYmPj+2A== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -2564,11 +2596,11 @@ integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== "@types/serve-static@*": - version "1.13.10" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" - integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + version "1.15.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" + integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== dependencies: - "@types/mime" "^1" + "@types/mime" "*" "@types/node" "*" "@types/stack-utils@^2.0.0": @@ -2589,9 +2621,9 @@ integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/validator@^13.1.3": - version "13.7.2" - resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.2.tgz#a2114225d9be743fb154b06c29b8257aaca42922" - integrity sha512-KFcchQ3h0OPQgFirBRPZr5F/sVjxZsOrQHedj3zi8AH3Zv/hOLx2OLR4hxR5HcfoU+33n69ZuOfzthKVdMoTiw== + version "13.7.4" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.4.tgz#33cc949ee87dd47c63e35ba4ad94f6888852be04" + integrity sha512-uAaSWegu2lymY18l+s5nmcXu3sFeeTOl1zhSGoYzcr6T3wz1M+3OcW4UjfPhIhHGd13tIMRDsEpR+d8w/MexwQ== "@types/varint@^6.0.0": version "6.0.0" @@ -2778,9 +2810,9 @@ acorn@^7.1.1, acorn@^7.4.0: integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.2.4, acorn@^8.4.1: - version "8.7.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" - integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== add-stream@^1.0.0: version "1.0.0" @@ -2908,9 +2940,9 @@ anymatch@^3.0.3: picomatch "^2.0.4" appdirsjs@^1.2.4: - version "1.2.6" - resolved "https://registry.yarnpkg.com/appdirsjs/-/appdirsjs-1.2.6.tgz#fccf9ee543315492867cacfcfd4a2b32257d30ac" - integrity sha512-D8wJNkqMCeQs3kLasatELsddox/Xqkhp+J07iXGyL54fVN7oc+nmNfYzGuCs1IEP6uBw+TfpuO3JKwc+lECy4w== + version "1.2.7" + resolved "https://registry.yarnpkg.com/appdirsjs/-/appdirsjs-1.2.7.tgz#50b4b7948a26ba6090d4aede2ae2dc2b051be3b3" + integrity sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw== aproba@^1.0.3: version "1.2.0" @@ -2922,10 +2954,18 @@ aproba@^1.0.3: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + are-we-there-yet@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz#ba20bd6b553e31d62fc8c31bd23d22b95734390d" - integrity sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw== + version "3.0.1" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== dependencies: delegates "^1.0.0" readable-stream "^3.6.0" @@ -3036,6 +3076,17 @@ array.prototype.flat@^1.2.5: es-abstract "^1.19.2" es-shim-unscopables "^1.0.0" +array.prototype.reduce@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz#8167e80089f78bff70a99e20bd4201d4663b0a6f" + integrity sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.2" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.7" + arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -3063,10 +3114,10 @@ asn1@~0.2.3: dependencies: safer-buffer "~2.1.0" -asn1js@^3.0.1, asn1js@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.4.tgz#65fece61bd30d0ef1e31b39fcd383810f44c9fb5" - integrity sha512-ZibuNYyfODvHiVyRFs80xLAUjCwBSkLbE+r1TasjlRKwdodENGT4AlLdaN12Pl/EcK3lFMDYXU6lE2g7Sq9VVQ== +asn1js@^3.0.1, asn1js@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38" + integrity sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ== dependencies: pvtsutils "^1.3.2" pvutils "^1.1.3" @@ -3204,24 +3255,24 @@ babel-plugin-jest-hoist@^27.5.1: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" -babel-plugin-polyfill-corejs2@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz#440f1b70ccfaabc6b676d196239b138f8a2cfba5" - integrity sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w== +babel-plugin-polyfill-corejs2@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz#e4c31d4c89b56f3cf85b92558954c66b54bd972d" + integrity sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q== dependencies: - "@babel/compat-data" "^7.13.11" - "@babel/helper-define-polyfill-provider" "^0.3.1" + "@babel/compat-data" "^7.17.7" + "@babel/helper-define-polyfill-provider" "^0.3.2" semver "^6.1.1" -babel-plugin-polyfill-corejs3@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72" - integrity sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ== +babel-plugin-polyfill-corejs3@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7" + integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.1" + "@babel/helper-define-polyfill-provider" "^0.3.2" core-js-compat "^3.21.0" -babel-plugin-polyfill-regenerator@^0.3.0: +babel-plugin-polyfill-regenerator@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990" integrity sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A== @@ -3357,9 +3408,9 @@ bindings@^1.3.1: file-uri-to-path "1.0.0" bn.js@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" - integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== body-parser@1.20.0: version "1.20.0" @@ -3442,16 +3493,15 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.20.2, browserslist@^4.20.3: - version "4.20.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf" - integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg== +browserslist@^4.20.2, browserslist@^4.21.3: + version "4.21.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" + integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== dependencies: - caniuse-lite "^1.0.30001332" - electron-to-chromium "^1.4.118" - escalade "^3.1.1" - node-releases "^2.0.3" - picocolors "^1.0.0" + caniuse-lite "^1.0.30001370" + electron-to-chromium "^1.4.202" + node-releases "^2.0.6" + update-browserslist-db "^1.0.5" bs-logger@0.x: version "0.2.6" @@ -3602,10 +3652,10 @@ camelcase@^6.0.0, camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001332: - version "1.0.30001341" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001341.tgz#59590c8ffa8b5939cf4161f00827b8873ad72498" - integrity sha512-2SodVrFFtvGENGCv0ChVJIDQ0KPaS1cg7/qtfMaICgeMolDdo/Z2OD32F0Aq9yl6F4YFwGPBS5AaPqNYiW4PoA== +caniuse-lite@^1.0.30001370: + version "1.0.30001373" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001373.tgz#2dc3bc3bfcb5d5a929bec11300883040d7b4b4be" + integrity sha512-pJYArGHrPp3TUqQzFYRmP/lwJlj8RCbVe3Gd3eJQkAV8SAC6b19XS9BjMvRdvaS8RMkaTN8ZhoHP6S1y8zzwEQ== canonicalize@^1.0.1: version "1.0.8" @@ -3675,9 +3725,9 @@ ci-info@^2.0.0: integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== ci-info@^3.2.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.1.tgz#58331f6f472a25fe3a50a351ae3052936c2c7f32" - integrity sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg== + version "3.3.2" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.2.tgz#6d2967ffa407466481c6c90b6e16b3098f080128" + integrity sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg== cjs-module-lexer@^1.0.0: version "1.2.2" @@ -3733,9 +3783,9 @@ cli-cursor@^3.1.0: restore-cursor "^3.1.0" cli-spinners@^2.0.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" - integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== + version "2.7.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a" + integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw== cli-width@^3.0.0: version "3.0.0" @@ -3772,7 +3822,7 @@ clone-deep@^4.0.1: clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== cmd-shim@^4.1.0: version "4.1.0" @@ -3784,12 +3834,12 @@ cmd-shim@^4.1.0: co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA== collect-v8-coverage@^1.0.0: version "1.0.1" @@ -3799,7 +3849,7 @@ collect-v8-coverage@^1.0.0: collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== dependencies: map-visit "^1.0.0" object-visit "^1.0.0" @@ -3821,14 +3871,14 @@ color-convert@^2.0.1: color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-support@^1.1.3: +color-support@^1.1.2, color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== @@ -3913,7 +3963,7 @@ commander@~2.13.0: commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== compare-func@^2.0.0: version "2.0.0" @@ -3956,7 +4006,7 @@ compression@^1.7.1: concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== concat-stream@^2.0.0: version "2.0.0" @@ -3989,7 +4039,7 @@ connect@^3.6.5: console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== content-disposition@0.5.4: version "0.5.4" @@ -4104,7 +4154,7 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== cookie@0.5.0: version "0.5.0" @@ -4114,20 +4164,20 @@ cookie@0.5.0: copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== core-js-compat@^3.21.0: - version "3.22.5" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.22.5.tgz#7fffa1d20cb18405bd22756ca1353c6f1a0e8614" - integrity sha512-rEF75n3QtInrYICvJjrAgV03HwKiYvtKHdPtaba1KucG+cNZ4NJnH9isqt979e67KZlhpbCOTwnsvnIr+CVeOg== + version "3.24.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.24.1.tgz#d1af84a17e18dfdd401ee39da9996f9a7ba887de" + integrity sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw== dependencies: - browserslist "^4.20.3" + browserslist "^4.21.3" semver "7.0.0" core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== core-util-is@~1.0.0: version "1.0.3" @@ -4173,7 +4223,7 @@ credentials-context@^2.0.0: resolved "https://registry.yarnpkg.com/credentials-context/-/credentials-context-2.0.0.tgz#68a9a1a88850c398d3bba4976c8490530af093e8" integrity sha512-/mFKax6FK26KjgV2KW2D4YqKgoJ5DVJpNt87X2Jc9IxT2HBMy7nEIlc+n7pEi+YFFe721XqrvZPd+jbyyBjsvQ== -cross-fetch@^3.1.2: +cross-fetch@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== @@ -4230,7 +4280,7 @@ dargs@^7.0.0: dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== dependencies: assert-plus "^1.0.0" @@ -4254,9 +4304,9 @@ dateformat@^3.0.0: integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== dayjs@^1.8.15: - version "1.11.2" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.2.tgz#fa0f5223ef0d6724b3d8327134890cfe3d72fbe5" - integrity sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw== + version "1.11.4" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.4.tgz#3b3c10ca378140d8917e06ebc13a4922af4f433e" + integrity sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g== debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" @@ -4282,12 +4332,12 @@ debug@^3.1.0, debug@^3.2.6, debug@^3.2.7: debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" - integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= + integrity sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw== decamelize-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" - integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= + integrity sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg== dependencies: decamelize "^1.1.0" map-obj "^1.0.0" @@ -4295,7 +4345,7 @@ decamelize-keys@^1.1.0: decamelize@^1.1.0, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== decimal.js@^10.2.1: version "10.3.1" @@ -4310,7 +4360,7 @@ decode-uri-component@^0.2.0: dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== deep-extend@^0.6.0, deep-extend@~0.6.0: version "0.6.0" @@ -4335,7 +4385,7 @@ deepmerge@^4.2.2: defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + integrity sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA== dependencies: clone "^1.0.2" @@ -4350,14 +4400,14 @@ define-properties@^1.1.3, define-properties@^1.1.4: define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== dependencies: is-descriptor "^0.1.0" define-property@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== dependencies: is-descriptor "^1.0.0" @@ -4372,17 +4422,17 @@ define-property@^2.0.2: delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== denodeify@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" - integrity sha1-OjYof1A05pnnV3kBBSwubJQlFjE= + integrity sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg== depd@2.0.0: version "2.0.0" @@ -4392,7 +4442,7 @@ depd@2.0.0: depd@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" @@ -4407,7 +4457,7 @@ destroy@1.2.0: detect-indent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" - integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= + integrity sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g== detect-indent@^6.0.0: version "6.1.0" @@ -4417,7 +4467,12 @@ detect-indent@^6.0.0: detect-libc@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + +detect-libc@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== detect-newline@^3.0.0: version "3.1.0" @@ -4432,10 +4487,10 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" -did-resolver@^3.1.3, did-resolver@^3.1.5: - version "3.2.0" - resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-3.2.0.tgz#b89edd0dd70ad6f1c65ca1285472e021c2239707" - integrity sha512-8YiTRitfGt9hJYDIzjc254gXgJptO4zq6Q2BMZMNqkbCf9EFkV6BD4QIh5BUF4YjBglBgJY+duQRzO3UZAlZsw== +did-resolver@^3.1.3, did-resolver@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-3.2.2.tgz#6f4e252a810f785d1b28a10265fad6dffee25158" + integrity sha512-Eeo2F524VM5N3W4GwglZrnul2y6TLTwMQP3In62JdG34NZoqihYyOZLk+5wUW8sSgvIYIcJM8Dlt3xsdKZZ3tg== diff-sequences@^26.6.2: version "26.6.2" @@ -4507,7 +4562,7 @@ duplexer@^0.1.1: ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== dependencies: jsbn "~0.1.0" safer-buffer "^2.1.0" @@ -4515,12 +4570,12 @@ ecc-jsbn@~0.1.1: ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.118: - version "1.4.137" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz#186180a45617283f1c012284458510cd99d6787f" - integrity sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA== +electron-to-chromium@^1.4.202: + version "1.4.206" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.206.tgz#580ff85b54d7ec0c05f20b1e37ea0becdd7b0ee4" + integrity sha512-h+Fadt1gIaQ06JaIiyqPsBjJ08fV5Q7md+V8bUvQW/9OvXfL2LRICTz2EcnnCP7QzrFTS6/27MRV6Bl9Yn97zA== emittery@^0.8.1: version "0.8.1" @@ -4535,7 +4590,7 @@ emoji-regex@^8.0.0: encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== encoding@^0.1.12: version "0.1.13" @@ -4581,11 +4636,11 @@ error-ex@^1.3.1: is-arrayish "^0.2.1" error-stack-parser@^2.0.6: - version "2.0.7" - resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.7.tgz#b0c6e2ce27d0495cf78ad98715e0cad1219abb57" - integrity sha512-chLOW0ZGRf4s8raLrDxa5sdkvPec5YdvwbFnqJme4rk0rFajP8mPtrDL1+I+CwrQDCjswDA5sREX7jYQDQs9vA== + version "2.1.4" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" + integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== dependencies: - stackframe "^1.1.1" + stackframe "^1.3.4" errorhandler@^1.5.0: version "1.5.1" @@ -4595,7 +4650,7 @@ errorhandler@^1.5.0: accepts "~1.3.7" escape-html "~1.0.3" -es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5: +es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.1: version "1.20.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814" integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== @@ -4624,6 +4679,11 @@ es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19 string.prototype.trimstart "^1.0.5" unbox-primitive "^1.0.2" +es-array-method-boxes-properly@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" + integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== + es-shim-unscopables@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" @@ -4648,12 +4708,12 @@ escalade@^3.1.1: escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== escape-string-regexp@^2.0.0: version "2.0.0" @@ -4859,7 +4919,7 @@ esutils@^2.0.2: etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== event-target-shim@^5.0.0, event-target-shim@^5.0.1: version "5.0.1" @@ -4912,12 +4972,12 @@ execa@^5.0.0: exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== dependencies: debug "^2.3.3" define-property "^0.2.5" @@ -4995,14 +5055,14 @@ express@^4.17.1: extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== dependencies: is-extendable "^0.1.0" extend-shallow@^3.0.0, extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== dependencies: assign-symbols "^1.0.0" is-extendable "^1.0.1" @@ -5038,7 +5098,7 @@ extglob@^2.0.4: extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== extsprintf@^1.2.0: version "1.4.1" @@ -5079,12 +5139,12 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-text-encoding@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" - integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== + version "1.0.4" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz#bf1898ad800282a4e53c0ea9690704dd26e4298e" + integrity sha512-x6lDDm/tBAzX9kmsPcZsNbvDs3Zey3+scsxaZElS8xWLgUMAg/oFLeewfUz0mu1CblHhhsu15jGkraldkFh8KQ== fastq@^1.6.0: version "1.13.0" @@ -5144,7 +5204,7 @@ file-uri-to-path@1.0.0: fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== dependencies: extend-shallow "^2.0.1" is-number "^3.0.0" @@ -5208,7 +5268,7 @@ find-replace@^3.0.0: find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== dependencies: locate-path "^2.0.0" @@ -5244,14 +5304,14 @@ flat-cache@^3.0.4: rimraf "^3.0.2" flatted@^3.1.0: - version "3.2.5" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" - integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== + version "3.2.6" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.6.tgz#022e9218c637f9f3fc9c35ab9c9193f05add60b2" + integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ== flow-parser@0.*: - version "0.178.0" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.178.0.tgz#85d300e29b146b54cb79e277e092ffd401b05f0c" - integrity sha512-OviMR2Y/sMSyUzR1xLLAmQvmHXTsD1Sq69OTmP5AckVulld7sVNsCfwsw7t3uK00dO9A7k4fD+wodbzzaaEn5g== + version "0.183.1" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.183.1.tgz#633387855028cbeb38d65ed0a0d64729e1599a3b" + integrity sha512-xBnvBk8D7aBY7gAilyjjGaNJe+9PGU6I/D2g6lGkkKyl4dW8nzn2eAc7Sc7RNRRr2NNYwpgHOOxBTjJKdKOXcA== flow-parser@^0.121.0: version "0.121.0" @@ -5261,12 +5321,12 @@ flow-parser@^0.121.0: for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== form-data@^3.0.0: version "3.0.1" @@ -5294,19 +5354,19 @@ forwarded@0.2.0: fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== dependencies: map-cache "^0.2.2" fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== fs-extra@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" - integrity sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA= + integrity sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ== dependencies: graceful-fs "^4.1.2" jsonfile "^2.1.0" @@ -5348,7 +5408,7 @@ fs-minipass@^2.0.0, fs-minipass@^2.1.0: fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@^2.1.2, fsevents@^2.3.2: version "2.3.2" @@ -5373,13 +5433,28 @@ function.prototype.name@^1.1.5: functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== functions-have-names@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + gauge@^4.0.3: version "4.0.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" @@ -5397,7 +5472,7 @@ gauge@^4.0.3: gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + integrity sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg== dependencies: aproba "^1.0.3" console-control-strings "^1.0.0" @@ -5418,7 +5493,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== @@ -5427,15 +5502,6 @@ get-intrinsic@^1.0.2: has "^1.0.3" has-symbols "^1.0.3" -get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -5491,19 +5557,19 @@ get-uv-event-loop-napi-h@^1.0.5: get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== dependencies: assert-plus "^1.0.0" git-config@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/git-config/-/git-config-0.0.7.tgz#a9c8a3ef07a776c3d72261356d8b727b62202b28" - integrity sha1-qcij7wendsPXImE1bYtye2IgKyg= + integrity sha512-LidZlYZXWzVjS+M3TEwhtYBaYwLeOZrXci1tBgqp/vDdZTBMl02atvwb6G35L64ibscYoPnxfbwwUS+VZAISLA== dependencies: iniparser "~1.0.5" @@ -5521,7 +5587,7 @@ git-raw-commits@^2.0.8: git-remote-origin-url@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f" - integrity sha1-UoJlna4hBxRaERJhEq0yFuxfpl8= + integrity sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw== dependencies: gitconfiglocal "^1.0.0" pify "^2.3.0" @@ -5552,7 +5618,7 @@ git-url-parse@^11.4.4: gitconfiglocal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b" - integrity sha1-QdBF84UaXqiPA/JMocYXgRRGS5s= + integrity sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ== dependencies: ini "^1.3.2" @@ -5581,9 +5647,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.6.0, globals@^13.9.0: - version "13.15.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" - integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== + version "13.17.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" + integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== dependencies: type-fest "^0.20.2" @@ -5619,7 +5685,7 @@ handlebars@^4.7.6, handlebars@^4.7.7: har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== har-validator@~5.1.3: version "5.1.5" @@ -5642,7 +5708,7 @@ has-bigints@^1.0.1, has-bigints@^1.0.2: has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" @@ -5671,12 +5737,12 @@ has-tostringtag@^1.0.0: has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== dependencies: get-value "^2.0.3" has-values "^0.1.4" @@ -5685,7 +5751,7 @@ has-value@^0.3.1: has-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== dependencies: get-value "^2.0.6" has-values "^1.0.0" @@ -5694,12 +5760,12 @@ has-value@^1.0.0: has-values@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== has-values@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== dependencies: is-number "^3.0.0" kind-of "^4.0.0" @@ -5775,7 +5841,7 @@ http-proxy-agent@^4.0.1: http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== dependencies: assert-plus "^1.0.0" jsprim "^1.2.2" @@ -5797,7 +5863,7 @@ human-signals@^2.1.0: humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== dependencies: ms "^2.0.0" @@ -5850,7 +5916,7 @@ image-size@^0.6.0: import-fresh@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + integrity sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg== dependencies: caller-path "^2.0.0" resolve-from "^3.0.0" @@ -5874,7 +5940,7 @@ import-local@^3.0.2: imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== indent-string@^4.0.0: version "4.0.0" @@ -5905,7 +5971,7 @@ infer-owner@^1.0.4: inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" @@ -5923,7 +5989,7 @@ ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: iniparser@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/iniparser/-/iniparser-1.0.5.tgz#836d6befe6dfbfcee0bccf1cf9f2acc7027f783d" - integrity sha1-g21r7+bfv87gvM8c+fKsxwJ/eD0= + integrity sha512-i40MWqgTU6h/70NtMsDVVDLjDYWwcIR1yIEVDPfxZIJno9z9L4s83p/V7vAu2i48Vj0gpByrkGFub7ko9XvPrw== init-package-json@^2.0.2: version "2.0.5" @@ -5983,6 +6049,11 @@ ip@^1.1.5: resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48" integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg== +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -5991,7 +6062,7 @@ ipaddr.js@1.9.1: is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A== dependencies: kind-of "^3.0.2" @@ -6005,7 +6076,7 @@ is-accessor-descriptor@^1.0.0: is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-bigint@^1.0.1: version "1.0.4" @@ -6039,7 +6110,7 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.5.0, is-core-module@^2.8.1: +is-core-module@^2.5.0, is-core-module@^2.8.1, is-core-module@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== @@ -6049,7 +6120,7 @@ is-core-module@^2.5.0, is-core-module@^2.8.1: is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg== dependencies: kind-of "^3.0.2" @@ -6088,12 +6159,12 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-directory@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + integrity sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw== is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== is-extendable@^1.0.1: version "1.0.1" @@ -6105,19 +6176,19 @@ is-extendable@^1.0.1: is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw== dependencies: number-is-nan "^1.0.0" is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -6139,7 +6210,7 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" - integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== is-negative-zero@^2.0.2: version "2.0.2" @@ -6156,7 +6227,7 @@ is-number-object@^1.0.4: is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== dependencies: kind-of "^3.0.2" @@ -6173,7 +6244,7 @@ is-obj@^2.0.0: is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== is-plain-obj@^2.0.0: version "2.1.0" @@ -6222,7 +6293,7 @@ is-ssh@^1.3.0: is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== is-stream@^2.0.0: version "2.0.1" @@ -6246,14 +6317,14 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: is-text-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" - integrity sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4= + integrity sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w== dependencies: text-extensions "^1.0.0" is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== is-weakref@^1.0.2: version "1.0.2" @@ -6270,17 +6341,17 @@ is-windows@^1.0.2: is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== iso-url@^1.1.5: version "1.2.1" @@ -6290,14 +6361,14 @@ iso-url@^1.1.5: isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== dependencies: isarray "1.0.0" isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== isomorphic-webcrypto@^2.3.8: version "2.3.8" @@ -6320,7 +6391,7 @@ isomorphic-webcrypto@^2.3.8: isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" @@ -6357,9 +6428,9 @@ istanbul-lib-source-maps@^4.0.0: source-map "^0.6.1" istanbul-reports@^3.1.3: - version "3.1.4" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" - integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== + version "3.1.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" + integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" @@ -6883,7 +6954,7 @@ js-yaml@^3.13.1: jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== jsc-android@^245459.0.0: version "245459.0.0" @@ -6956,7 +7027,7 @@ jsesc@^2.5.1: jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== json-parse-better-errors@^1.0.1: version "1.0.2" @@ -6986,12 +7057,12 @@ json-schema@0.4.0: json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== json-text-sequence@~0.3.0: version "0.3.0" @@ -7015,14 +7086,14 @@ json5@^1.0.1: jsonfile@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" - integrity sha1-NzaitCi4e72gzIO1P6PWM6NcKug= + integrity sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw== optionalDependencies: graceful-fs "^4.1.6" jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== optionalDependencies: graceful-fs "^4.1.6" @@ -7038,12 +7109,12 @@ jsonfile@^6.0.1: jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= + integrity sha512-trvBk1ki43VZptdBI5rIlG4YOzyeH/WefQt5rj1grasPn4iiZWKet8nkgc4GlsAylaztn0qZfUYOiTsASJFdNA== jsonparse@^1.2.0, jsonparse@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== jsprim@^1.2.2: version "1.4.2" @@ -7058,14 +7129,14 @@ jsprim@^1.2.2: kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== dependencies: is-buffer "^1.1.5" kind-of@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== dependencies: is-buffer "^1.1.5" @@ -7082,7 +7153,7 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: klaw@^1.0.0: version "1.3.1" resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" - integrity sha1-QIhDO0azsbolnXh4XY6W9zugJDk= + integrity sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw== optionalDependencies: graceful-fs "^4.1.9" @@ -7144,7 +7215,7 @@ levn@^0.4.1: levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== dependencies: prelude-ls "~1.1.2" type-check "~0.3.2" @@ -7171,9 +7242,9 @@ libnpmpublish@^4.0.0: ssri "^8.0.1" libphonenumber-js@^1.9.7: - version "1.10.4" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.4.tgz#90397f0ed620262570a32244c9fbc389cc417ce4" - integrity sha512-9QWxEk4GW5RDnFzt8UtyRENfFpAN8u7Sbf9wf32tcXY9tdtnz1dKHIBwW2Wnfx8ypXJb9zUnTpK9aQJ/B8AlnA== + version "1.10.11" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.11.tgz#0078756857bcc5c9dfe097123c6e04ea930e309b" + integrity sha512-ehoihx4HpRXO6FH/uJ0EnaEV4dVU+FDny+jv0S6k9JPyPsAIr0eXDAFvGRMBKE1daCtyHAaFSKCiuCxrOjVAzQ== lines-and-columns@^1.1.6: version "1.2.4" @@ -7183,7 +7254,7 @@ lines-and-columns@^1.1.6: load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== dependencies: graceful-fs "^4.1.2" parse-json "^4.0.0" @@ -7203,7 +7274,7 @@ load-json-file@^6.2.0: locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA== dependencies: p-locate "^2.0.0" path-exists "^3.0.0" @@ -7233,27 +7304,27 @@ locate-path@^6.0.0: lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= + integrity sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA== lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" - integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= + integrity sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g== lodash.memoize@4.x: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== lodash.merge@^4.6.2: version "4.6.2" @@ -7278,12 +7349,12 @@ lodash.templatesettings@^4.0.0: lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" - integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= + integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.7.0: version "4.17.21" @@ -7338,7 +7409,7 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0: +make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -7408,12 +7479,12 @@ makeerror@1.0.12: map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== map-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== map-obj@^4.0.0: version "4.3.0" @@ -7423,14 +7494,14 @@ map-obj@^4.0.0: map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== dependencies: object-visit "^1.0.0" media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== meow@^8.0.0: version "8.1.2" @@ -7452,7 +7523,7 @@ meow@^8.0.0: merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== merge-stream@^2.0.0: version "2.0.0" @@ -7467,7 +7538,7 @@ merge2@^1.3.0, merge2@^1.4.1: methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== metro-babel-register@0.64.0: version "0.64.0" @@ -7872,9 +7943,9 @@ minipass@^2.6.0, minipass@^2.9.0: yallist "^3.0.0" minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: - version "3.1.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee" - integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ== + version "3.3.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae" + integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== dependencies: yallist "^4.0.0" @@ -7930,7 +8001,7 @@ modify-values@^1.0.0: ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== ms@2.1.2: version "2.1.2" @@ -7964,9 +8035,9 @@ mute-stream@0.0.8, mute-stream@~0.0.4: integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== nan@^2.11.1: - version "2.15.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" - integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== + version "2.16.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916" + integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA== nanomatch@^1.2.9: version "1.2.13" @@ -7988,7 +8059,7 @@ nanomatch@^1.2.9: natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== needle@^2.5.2: version "2.9.1" @@ -8009,6 +8080,26 @@ neo-async@^2.5.0, neo-async@^2.6.0: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +neon-cli@0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/neon-cli/-/neon-cli-0.10.1.tgz#9705b7b860550faffe6344fc9ab6f6e389c95ed6" + integrity sha512-kOd9ELaYETe1J1nBEOYD7koAZVj6xR9TGwOPccAsWmwL5amkaXXXwXHCUHkBAWujlgSZY5f2pT+pFGkzoHExYQ== + dependencies: + chalk "^4.1.0" + command-line-args "^5.1.1" + command-line-commands "^3.0.1" + command-line-usage "^6.1.0" + git-config "0.0.7" + handlebars "^4.7.6" + inquirer "^7.3.3" + make-promises-safe "^5.1.0" + rimraf "^3.0.2" + semver "^7.3.2" + toml "^3.0.0" + ts-typed-json "^0.3.2" + validate-npm-package-license "^3.0.4" + validate-npm-package-name "^3.0.0" + neon-cli@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/neon-cli/-/neon-cli-0.8.2.tgz#5111b0e9d5d90273bdf85a9aa40a1a47a32df2ef" @@ -8047,7 +8138,7 @@ node-addon-api@^3.0.0: node-dir@^0.1.17: version "0.1.17" resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" - integrity sha1-X1Zl2TNRM1yqvvjxxVRRbPXx5OU= + integrity sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg== dependencies: minimatch "^3.0.2" @@ -8067,9 +8158,9 @@ node-fetch@3.0.0-beta.9: fetch-blob "^2.1.1" node-gyp-build@^4.2.1: - version "4.4.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.4.0.tgz#42e99687ce87ddeaf3a10b99dc06abc11021f3f4" - integrity sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ== + version "4.5.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" + integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== node-gyp@^5.0.2: version "5.1.1" @@ -8123,7 +8214,7 @@ node-gyp@^8.0.0: node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-pre-gyp@0.17.0: version "0.17.0" @@ -8141,10 +8232,10 @@ node-pre-gyp@0.17.0: semver "^5.7.1" tar "^4.4.13" -node-releases@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476" - integrity sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ== +node-releases@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== node-stream-zip@^1.9.1: version "1.15.0" @@ -8189,7 +8280,7 @@ normalize-package-data@^3.0.0, normalize-package-data@^3.0.2: normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== dependencies: remove-trailing-separator "^1.0.1" @@ -8303,7 +8394,7 @@ npm-registry-fetch@^9.0.0: npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== dependencies: path-key "^2.0.0" @@ -8324,6 +8415,16 @@ npmlog@^4.1.2: gauge "~2.7.3" set-blocking "~2.0.0" +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + npmlog@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" @@ -8342,12 +8443,12 @@ nullthrows@^1.1.1: number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== nwsapi@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" - integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + version "2.2.1" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.1.tgz#10a9f268fbf4c461249ebcfe38e359aa36e2577c" + integrity sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg== oauth-sign@~0.9.0: version "0.9.0" @@ -8362,23 +8463,18 @@ ob1@0.64.0: object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== dependencies: copy-descriptor "^0.1.0" define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.10.3, object-inspect@^1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" - integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== - -object-inspect@^1.9.0: +object-inspect@^1.10.3, object-inspect@^1.12.0, object-inspect@^1.9.0: version "1.12.2" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== @@ -8391,7 +8487,7 @@ object-keys@^1.1.1: object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== dependencies: isobject "^3.0.0" @@ -8406,18 +8502,19 @@ object.assign@^4.1.0, object.assign@^4.1.2: object-keys "^1.1.1" object.getownpropertydescriptors@^2.0.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz#b223cf38e17fefb97a63c10c91df72ccb386df9e" - integrity sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw== + version "2.1.4" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz#7965e6437a57278b587383831a9b829455a4bc37" + integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== dependencies: + array.prototype.reduce "^1.0.4" call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.1.4" + es-abstract "^1.20.1" object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== dependencies: isobject "^3.0.1" @@ -8440,7 +8537,7 @@ on-finished@2.4.1: on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== dependencies: ee-first "1.1.1" @@ -8452,14 +8549,14 @@ on-headers@~1.0.2: once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== dependencies: mimic-fn "^1.0.0" @@ -8504,7 +8601,7 @@ optionator@^0.9.1: options@>=0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" - integrity sha1-7CLTEoBrtT5zF3Pnza788cZDEo8= + integrity sha512-bOj3L1ypm++N+n7CEbbe473A414AB7z+amKYshRb//iuL3MpdDCLhPnw6aVTdKB9g5ZRVHIEp8eUln6L2NUStg== ora@^3.4.0: version "3.4.0" @@ -8521,12 +8618,12 @@ ora@^3.4.0: os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== osenv@^0.1.4: version "0.1.5" @@ -8539,7 +8636,7 @@ osenv@^0.1.4: p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== p-limit@^1.1.0: version "1.3.0" @@ -8565,7 +8662,7 @@ p-limit@^3.0.2: p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg== dependencies: p-limit "^1.1.0" @@ -8630,7 +8727,7 @@ p-timeout@^3.2.0: p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== p-try@^2.0.0: version "2.2.0" @@ -8679,7 +8776,7 @@ parent-module@^1.0.0: parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== dependencies: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" @@ -8705,13 +8802,13 @@ parse-path@^4.0.4: query-string "^6.13.8" parse-url@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-6.0.2.tgz#4a30b057bfc452af64512dfb1a7755c103db3ea1" - integrity sha512-uCSjOvD3T+6B/sPWhR+QowAZcU/o4bjPrVBQBGFxcDF6J6FraCGIaDBsdoQawiaaAVdHvtqBe3w3vKlfBKySOQ== + version "6.0.5" + resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-6.0.5.tgz#4acab8982cef1846a0f8675fa686cef24b2f6f9b" + integrity sha512-e35AeLTSIlkw/5GFq70IN7po8fmDUjpDPY1rIK+VubRfsUvBonjQ+PBZG+vWMACnQSmNlvl524IucoDmcioMxA== dependencies: is-ssh "^1.3.0" normalize-url "^6.1.0" - parse-path "^4.0.4" + parse-path "^4.0.0" protocols "^1.4.0" parse5@6.0.1: @@ -8727,12 +8824,12 @@ parseurl@~1.3.3: pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== path-exists@^4.0.0: version "4.0.0" @@ -8742,12 +8839,12 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" @@ -8762,7 +8859,7 @@ path-parse@^1.0.7: path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== path-type@^3.0.0: version "3.0.0" @@ -8779,7 +8876,7 @@ path-type@^4.0.0: performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== picocolors@^1.0.0: version "1.0.0" @@ -8794,12 +8891,12 @@ picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== pify@^4.0.1: version "4.0.1" @@ -8831,17 +8928,17 @@ pkg-dir@^4.2.0: find-up "^4.0.0" plist@^3.0.1, plist@^3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.5.tgz#2cbeb52d10e3cdccccf0c11a63a85d830970a987" - integrity sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA== + version "3.0.6" + resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.6.tgz#7cfb68a856a7834bca6dbfe3218eb9c7740145d3" + integrity sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA== dependencies: base64-js "^1.5.1" - xmlbuilder "^9.0.7" + xmlbuilder "^15.1.1" posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== prelude-ls@^1.2.1: version "1.2.1" @@ -8851,7 +8948,7 @@ prelude-ls@^1.2.1: prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== prettier-linter-helpers@^1.0.0: version "1.0.0" @@ -8861,9 +8958,9 @@ prettier-linter-helpers@^1.0.0: fast-diff "^1.1.2" prettier@^2.3.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" - integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== + version "2.7.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" + integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== pretty-format@^26.0.0, pretty-format@^26.5.2, pretty-format@^26.6.2: version "26.6.2" @@ -8897,7 +8994,7 @@ progress@^2.0.0: promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== promise-retry@^2.0.1: version "2.0.1" @@ -8925,7 +9022,7 @@ prompts@^2.0.1, prompts@^2.4.0: promzard@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" - integrity sha1-JqXW7ox97kyxIggwWs+5O6OCqe4= + integrity sha512-JZeYqd7UAcHCwI+sTOeUDYkvEU+1bQ7iE0UT1MgB/tERkAPkesW46MrpIySzODi+owTjZtiF8Ay5j9m60KmMBw== dependencies: read "1" @@ -8941,7 +9038,7 @@ prop-types@^15.7.2: proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" - integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= + integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== protocols@^1.4.0: version "1.4.8" @@ -8962,9 +9059,9 @@ proxy-addr@~2.0.7: ipaddr.js "1.9.1" psl@^1.1.28, psl@^1.1.33: - version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== pump@^3.0.0: version "3.0.0" @@ -8994,7 +9091,7 @@ pvutils@^1.1.3: q@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== qs@6.10.3: version "6.10.3" @@ -9071,9 +9168,9 @@ rc@^1.2.8: strip-json-comments "~2.0.1" react-devtools-core@^4.6.0: - version "4.24.6" - resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.24.6.tgz#3262114f483465179c97a49b7ada845048f4f97e" - integrity sha512-+6y6JAtAo1NUUxaCwCYTb13ViBpc7RjNTlj1HZRlDJmi7UYToj5+BNn8Duzz2YizzAzmRUWZkRM7OtqxnN6TnA== + version "4.25.0" + resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.25.0.tgz#78b11a2c9f81dd9ebff3745ab4ee2147cc96c12a" + integrity sha512-iewRrnu0ZnmfL+jJayKphXj04CFh6i3ezVnpCtcnZbTPSQgN09XqHAzXbKbqNDl7aTg9QLNkQRP6M3DvdrinWA== dependencies: shell-quote "^1.6.1" ws "^7" @@ -9115,7 +9212,7 @@ react-native-get-random-values@^1.7.0: react-native-securerandom@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/react-native-securerandom/-/react-native-securerandom-0.1.1.tgz#f130623a412c338b0afadedbc204c5cbb8bf2070" - integrity sha1-8TBiOkEsM4sK+t7bwgTFy7i/IHA= + integrity sha512-CozcCx0lpBLevxiXEb86kwLRalBCHNjiGPlw3P7Fi27U6ZLdfjOCNRHD1LtBKcvPvI3TvkBXB3GOtLvqaYJLGw== dependencies: base64-js "*" @@ -9225,7 +9322,7 @@ read-package-tree@^5.3.1: read-pkg-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" - integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= + integrity sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw== dependencies: find-up "^2.0.0" read-pkg "^3.0.0" @@ -9242,7 +9339,7 @@ read-pkg-up@^7.0.1: read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= + integrity sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA== dependencies: load-json-file "^4.0.0" normalize-package-data "^2.3.2" @@ -9261,7 +9358,7 @@ read-pkg@^5.2.0: read@1, read@~1.0.1: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" - integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= + integrity sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ== dependencies: mute-stream "~0.0.4" @@ -9310,7 +9407,7 @@ recast@^0.20.3: rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== dependencies: resolve "^1.1.6" @@ -9395,10 +9492,10 @@ regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -regexpu-core@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.0.1.tgz#c531122a7840de743dcf9c83e923b5560323ced3" - integrity sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw== +regexpu-core@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.1.0.tgz#2f8504c3fd0ebe11215783a41541e21c79942c6d" + integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA== dependencies: regenerate "^1.4.2" regenerate-unicode-properties "^10.0.1" @@ -9422,7 +9519,7 @@ regjsparser@^0.8.2: remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== repeat-element@^1.1.2: version "1.1.4" @@ -9432,7 +9529,7 @@ repeat-element@^1.1.2: repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== request@^2.88.0, request@^2.88.2: version "2.88.2" @@ -9463,7 +9560,7 @@ request@^2.88.0, request@^2.88.2: require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== require-from-string@^2.0.2: version "2.0.2" @@ -9485,7 +9582,7 @@ resolve-cwd@^3.0.0: resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= + integrity sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw== resolve-from@^4.0.0: version "4.0.0" @@ -9500,7 +9597,7 @@ resolve-from@^5.0.0: resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== resolve.exports@^1.1.0: version "1.1.0" @@ -9508,18 +9605,18 @@ resolve.exports@^1.1.0: integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.0: - version "1.22.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" - integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== dependencies: - is-core-module "^2.8.1" + is-core-module "^2.9.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== dependencies: onetime "^2.0.0" signal-exit "^3.0.2" @@ -9540,7 +9637,7 @@ ret@~0.1.10: retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== reusify@^1.0.4: version "1.0.4" @@ -9569,7 +9666,7 @@ rimraf@^3.0.0, rimraf@^3.0.2, rimraf@~3.0.2: rimraf@~2.2.6: version "2.2.8" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" - integrity sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI= + integrity sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg== rimraf@~2.6.2: version "2.6.3" @@ -9603,9 +9700,9 @@ rxjs@^6.6.0: tslib "^1.9.0" rxjs@^7.2.0: - version "7.5.5" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f" - integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw== + version "7.5.6" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.6.tgz#0446577557862afd6903517ce7cae79ecb9662bc" + integrity sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw== dependencies: tslib "^2.1.0" @@ -9622,7 +9719,7 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, s safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== dependencies: ret "~0.1.10" @@ -9646,7 +9743,7 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" -sax@^1.2.1, sax@^1.2.4: +sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -9710,7 +9807,7 @@ send@0.18.0: serialize-error@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" - integrity sha1-ULZ51WNc34Rme9yOWa9OW4HV9go= + integrity sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw== serialize-error@^8.0.1: version "8.1.0" @@ -9732,7 +9829,7 @@ serve-static@1.15.0, serve-static@^1.13.1: set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" @@ -9759,7 +9856,7 @@ shallow-clone@^3.0.0: shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== dependencies: shebang-regex "^1.0.0" @@ -9773,7 +9870,7 @@ shebang-command@^2.0.0: shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== shebang-regex@^3.0.0: version "3.0.0" @@ -9783,7 +9880,7 @@ shebang-regex@^3.0.0: shell-quote@1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" - integrity sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c= + integrity sha512-V0iQEZ/uoem3NmD91rD8XiuozJnq9/ZJnbHVXHnWqP1ucAhS3yJ7sLIIzEi57wFFcK3oi3kFUC46uSyWr35mxg== dependencies: array-filter "~0.0.0" array-map "~0.0.0" @@ -9858,7 +9955,7 @@ slice-ansi@^4.0.0: slide@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" - integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= + integrity sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw== smart-buffer@^4.2.0: version "4.2.0" @@ -9905,26 +10002,26 @@ socks-proxy-agent@^5.0.0: socks "^2.3.3" socks-proxy-agent@^6.0.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.0.tgz#f6b5229cc0cbd6f2f202d9695f09d871e951c85e" - integrity sha512-wWqJhjb32Q6GsrUqzuFkukxb/zzide5quXYcMVpIjxalDBBYy2nqKCFQ/9+Ie4dvOYSQdOk3hUlZSdzZOd3zMQ== + version "6.2.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" + integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== dependencies: agent-base "^6.0.2" debug "^4.3.3" socks "^2.6.2" socks@^2.3.3, socks@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.2.tgz#ec042d7960073d40d94268ff3bb727dc685f111a" - integrity sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA== + version "2.7.0" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.0.tgz#f9225acdb841e874dca25f870e9130990f3913d0" + integrity sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA== dependencies: - ip "^1.1.5" + ip "^2.0.0" smart-buffer "^4.2.0" sort-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= + integrity sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg== dependencies: is-plain-obj "^1.0.0" @@ -9962,7 +10059,7 @@ source-map-url@^0.4.0: source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" @@ -9970,9 +10067,9 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== spdx-correct@^3.0.0: version "3.1.1" @@ -10029,7 +10126,7 @@ split@^1.0.0: sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== sshpk@^1.7.0: version "1.17.0" @@ -10060,10 +10157,10 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" -stackframe@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.1.tgz#1033a3473ee67f08e2f2fc8eba6aef4f845124e1" - integrity sha512-h88QkzREN/hy8eRdyNhhsO7RSJ5oyTqxxmmn0dzBIMUclZsjpfmrsg81vp8mjjAs2vAZ72nyWxRUwSwmh0e4xg== +stackframe@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" + integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== stacktrace-parser@^0.1.3: version "0.1.10" @@ -10075,7 +10172,7 @@ stacktrace-parser@^0.1.3: static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== dependencies: define-property "^0.2.5" object-copy "^0.1.0" @@ -10088,7 +10185,7 @@ statuses@2.0.1: statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== str2buf@^1.3.0: version "1.3.0" @@ -10098,7 +10195,7 @@ str2buf@^1.3.0: stream-buffers@2.2.x: version "2.2.0" resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" - integrity sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ= + integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== strict-uri-encode@^2.0.0: version "2.0.0" @@ -10116,7 +10213,7 @@ string-length@^4.0.1: string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + integrity sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw== dependencies: code-point-at "^1.0.0" is-fullwidth-code-point "^1.0.0" @@ -10166,7 +10263,7 @@ string_decoder@~1.1.1: strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== dependencies: ansi-regex "^2.0.0" @@ -10187,7 +10284,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== strip-bom@^4.0.0: version "4.0.0" @@ -10197,7 +10294,7 @@ strip-bom@^4.0.0: strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== strip-final-newline@^2.0.0: version "2.0.0" @@ -10219,7 +10316,7 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== strong-log-transformer@^2.1.0: version "2.1.0" @@ -10308,7 +10405,7 @@ tar@^4.4.12, tar@^4.4.13: safe-buffer "^5.2.1" yallist "^3.1.1" -tar@^6.0.2, tar@^6.1.0, tar@^6.1.2: +tar@^6.0.2, tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: version "6.1.11" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== @@ -10323,7 +10420,7 @@ tar@^6.0.2, tar@^6.1.0, tar@^6.1.2: temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" - integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= + integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== temp-write@^4.0.0: version "4.0.0" @@ -10339,7 +10436,7 @@ temp-write@^4.0.0: temp@0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59" - integrity sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k= + integrity sha512-jtnWJs6B1cZlHs9wPG7BrowKxZw/rf6+UpGAkr8AaYmiTyTO7zQlLoST8zx/8TcUPnZmeBoB+H8ARuHZaSijVw== dependencies: os-tmpdir "^1.0.0" rimraf "~2.2.6" @@ -10376,7 +10473,7 @@ text-extensions@^1.0.0: text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== throat@^5.0.0: version "5.0.0" @@ -10406,7 +10503,7 @@ through2@^4.0.0: through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== tmp@^0.0.33: version "0.0.33" @@ -10423,19 +10520,19 @@ tmpl@1.0.5: to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== dependencies: kind-of "^3.0.2" to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== dependencies: is-number "^3.0.0" repeat-string "^1.6.1" @@ -10494,7 +10591,7 @@ tr46@^2.1.0: tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== trim-newlines@^3.0.0: version "3.0.1" @@ -10516,11 +10613,11 @@ ts-jest@^27.0.3: yargs-parser "20.x" ts-node@^10.0.0, ts-node@^10.4.0: - version "10.7.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" - integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== dependencies: - "@cspotcode/source-map-support" "0.7.0" + "@cspotcode/source-map-support" "^0.8.0" "@tsconfig/node10" "^1.0.7" "@tsconfig/node12" "^1.0.7" "@tsconfig/node14" "^1.0.0" @@ -10531,7 +10628,7 @@ ts-node@^10.0.0, ts-node@^10.4.0: create-require "^1.1.0" diff "^4.0.1" make-error "^1.1.1" - v8-compile-cache-lib "^3.0.0" + v8-compile-cache-lib "^3.0.1" yn "3.1.1" ts-typed-json@^0.3.2: @@ -10583,14 +10680,14 @@ tsyringe@^4.7.0: tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== dependencies: safe-buffer "^5.0.1" tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" @@ -10602,7 +10699,7 @@ type-check@^0.4.0, type-check@~0.4.0: type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== dependencies: prelude-ls "~1.1.2" @@ -10664,7 +10761,7 @@ typedarray-to-buffer@^3.1.5: typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== typescript@~4.3.0: version "4.3.5" @@ -10690,24 +10787,24 @@ uglify-es@^3.1.9: source-map "~0.6.1" uglify-js@^3.1.4: - version "3.15.5" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.5.tgz#2b10f9e0bfb3f5c15a8e8404393b6361eaeb33b3" - integrity sha512-hNM5q5GbBRB5xB+PMqVRcgYe4c8jbyZ1pzZhS6jbq54/4F2gFK869ZheiE5A8/t+W5jtTNpWef/5Q9zk639FNQ== + version "3.16.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.16.3.tgz#94c7a63337ee31227a18d03b8a3041c210fd1f1d" + integrity sha512-uVbFqx9vvLhQg0iBaau9Z75AxWJ8tqM9AV890dIZCLApF4rTcyHwmAvLeEdYRs+BzYWu8Iw81F79ah0EfTXbaw== uid-number@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" - integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE= + integrity sha512-c461FXIljswCuscZn67xq9PpszkPT6RjheWFQTgCyabJrTUozElanb0YEqv2UGgk247YpcJkFBuSGNvBlpXM9w== ultron@1.0.x: version "1.0.2" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" - integrity sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po= + integrity sha512-QMpnpVtYaWEeY+MwKDN/UdKlE/LsFZXM5lO1u7GaZzNgmIbGixHEmVMIKT+vqYOALu3m5GYQy9kz4Xu4IVn7Ow== umask@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" - integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= + integrity sha512-lE/rxOhmiScJu9L6RTNVgB/zZbF+vGC0/p6D3xnkAePI2o0sMyFG966iR5Ki50OI/0mNi2yaRnxfLsPmEZF/JA== unbox-primitive@^1.0.2: version "1.0.2" @@ -10784,12 +10881,12 @@ universalify@^2.0.0: unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== unset-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== dependencies: has-value "^0.3.1" isobject "^3.0.0" @@ -10799,6 +10896,14 @@ upath@^2.0.1: resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== +update-browserslist-db@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38" + integrity sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -10809,19 +10914,19 @@ uri-js@^4.2.2: urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== use-subscription@^1.0.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.7.0.tgz#c7505263315deac9fd2581cdf4ab1e3ff2585d0f" - integrity sha512-87x6MjiIVE/BWqtxfiRvM6jfvGudN+UeVOnWi7qKYp2c0YJn5+Z5Jt0kZw6Tt+8hs7kw/BWo2WBhizJSAZsQJA== + version "1.8.0" + resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.8.0.tgz#f118938c29d263c2bce12fc5585d3fe694d4dbce" + integrity sha512-LISuG0/TmmoDoCRmV5XAqYkd3UCBNM0ML3gGBndze65WITcsExCD3DTvXXTLyNcOC0heFQZzluW88bN/oC1DQQ== dependencies: - use-sync-external-store "^1.1.0" + use-sync-external-store "^1.2.0" -use-sync-external-store@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz#3343c3fe7f7e404db70f8c687adf5c1652d34e82" - integrity sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ== +use-sync-external-store@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== use@^3.1.0: version "3.1.1" @@ -10836,19 +10941,19 @@ utf8@^3.0.0: util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== util-promisify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/util-promisify/-/util-promisify-2.1.0.tgz#3c2236476c4d32c5ff3c47002add7c13b9a82a53" - integrity sha1-PCI2R2xNMsX/PEcAKt18E7moKlM= + integrity sha512-K+5eQPYs14b3+E+hmE2J6gCZ4JmMl9DbYS6BeP2CHq6WMuNxErxf5B/n0fz85L8zUuoO6rIzNNmIQDu/j+1OcA== dependencies: object.getownpropertydescriptors "^2.0.3" utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== uuid@^3.3.2: version "3.4.0" @@ -10860,7 +10965,7 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -v8-compile-cache-lib@^3.0.0: +v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== @@ -10890,7 +10995,7 @@ validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: validate-npm-package-name@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" - integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34= + integrity sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw== dependencies: builtins "^1.0.3" @@ -10907,12 +11012,12 @@ varint@^6.0.0: vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== dependencies: assert-plus "^1.0.0" core-util-is "1.0.2" @@ -10947,17 +11052,17 @@ walker@^1.0.7, walker@~1.0.5: wcwidth@^1.0.0, wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== dependencies: defaults "^1.0.3" web-did-resolver@^2.0.8: - version "2.0.16" - resolved "https://registry.yarnpkg.com/web-did-resolver/-/web-did-resolver-2.0.16.tgz#23e6607a6a068218ff8403d967b8a70af2e0cc25" - integrity sha512-PNGO9nP8H1mTxBRzg/AdzB40HXHhQ99BMCMEQYLK1fatohdmEDetJglgTFwavKQEbBexDG3xknCIzryWD7iS0A== + version "2.0.19" + resolved "https://registry.yarnpkg.com/web-did-resolver/-/web-did-resolver-2.0.19.tgz#25f11fd89f510b2650ce77f50baae496ae20d104" + integrity sha512-KRnLWTOApVAVvx20k5Fn2e4Fwhbo7cZbALruOv/lcW3Fr/1UTfGXFg0hnFYcscxk/hBrT+wBORoJf/VeQIOMSQ== dependencies: - cross-fetch "^3.1.2" - did-resolver "^3.1.5" + cross-fetch "^3.1.5" + did-resolver "^3.2.2" webcrypto-core@^1.7.4: version "1.7.5" @@ -10978,7 +11083,7 @@ webcrypto-shim@^0.1.4: webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== webidl-conversions@^5.0.0: version "5.0.0" @@ -11010,7 +11115,7 @@ whatwg-mimetype@^2.3.0: whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== dependencies: tr46 "~0.0.3" webidl-conversions "^3.0.0" @@ -11038,7 +11143,7 @@ which-boxed-primitive@^1.0.2: which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== which@^1.2.9, which@^1.3.1: version "1.3.1" @@ -11054,7 +11159,7 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" -wide-align@^1.1.0, wide-align@^1.1.5: +wide-align@^1.1.0, wide-align@^1.1.2, wide-align@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== @@ -11069,7 +11174,7 @@ word-wrap@^1.2.3, word-wrap@~1.2.3: wordwrap@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== wordwrapjs@^4.0.0: version "4.0.1" @@ -11100,7 +11205,7 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== write-file-atomic@^2.3.0, write-file-atomic@^2.4.2: version "2.4.3" @@ -11170,9 +11275,9 @@ ws@^6.1.4: async-limiter "~1.0.0" ws@^7, ws@^7.4.6, ws@^7.5.3: - version "7.5.7" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" - integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== xcode@^2.0.0: version "2.1.0" @@ -11187,10 +11292,10 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== -xmlbuilder@^9.0.7: - version "9.0.7" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" - integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= +xmlbuilder@^15.1.1: + version "15.1.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" + integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== xmlchars@^2.2.0: version "2.2.0" @@ -11198,11 +11303,11 @@ xmlchars@^2.2.0: integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== xmldoc@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/xmldoc/-/xmldoc-1.1.2.tgz#6666e029fe25470d599cd30e23ff0d1ed50466d7" - integrity sha512-ruPC/fyPNck2BD1dpz0AZZyrEwMOrWTO5lDdIXS91rs3wtm4j+T8Rp2o+zoOYkkAxJTZRPOSnOGei1egoRmKMQ== + version "1.2.0" + resolved "https://registry.yarnpkg.com/xmldoc/-/xmldoc-1.2.0.tgz#7554371bfd8c138287cff01841ae4566d26e5541" + integrity sha512-2eN8QhjBsMW2uVj7JHLHkMytpvGHLHxKXBy4J3fAT/HujsEtM6yU84iGjpESYGHg6XwK0Vu4l+KgqQ2dv2cCqg== dependencies: - sax "^1.2.1" + sax "^1.2.4" xtend@~4.0.1: version "4.0.2" From c99f3c9152a79ca6a0a24fdc93e7f3bebbb9d084 Mon Sep 17 00:00:00 2001 From: Iskander508 Date: Mon, 29 Aug 2022 16:45:27 +0200 Subject: [PATCH 022/125] feat: OOB public did (#930) Signed-off-by: Pavel Zarecky --- packages/core/src/agent/MessageSender.ts | 74 +++--------- .../src/agent/__tests__/MessageSender.test.ts | 45 +++++-- packages/core/src/agent/helpers.ts | 2 +- .../decorators/service/ServiceDecorator.ts | 2 +- .../connections/DidExchangeProtocol.ts | 12 +- .../handlers/ConnectionResponseHandler.ts | 1 - .../connections/services/ConnectionService.ts | 34 +++--- packages/core/src/modules/didcomm/index.ts | 2 + .../services/DidCommDocumentService.ts | 71 +++++++++++ .../__tests__/DidCommDocumentService.test.ts | 113 ++++++++++++++++++ .../src/modules/didcomm/services/index.ts | 1 + packages/core/src/modules/didcomm/types.ts | 8 ++ .../util/__tests__/matchingEd25519Key.test.ts | 84 +++++++++++++ .../didcomm/util/matchingEd25519Key.ts | 32 +++++ .../dids/domain/createPeerDidFromServices.ts | 2 +- .../core/src/modules/oob/OutOfBandModule.ts | 82 +++++++++---- packages/core/src/modules/oob/helpers.ts | 2 +- .../oob/messages/OutOfBandInvitation.ts | 35 +++--- .../modules/oob/repository/OutOfBandRecord.ts | 24 ++-- .../__tests__/OutOfBandRecord.test.ts | 3 + .../__tests__/__snapshots__/0.1.test.ts.snap | 36 +++++- .../0.1-0.2/__tests__/connection.test.ts | 2 +- .../migration/updates/0.1-0.2/connection.ts | 10 +- packages/core/src/types.ts | 2 +- packages/core/src/utils/validators.ts | 8 +- packages/core/tests/helpers.ts | 11 +- packages/core/tests/oob.test.ts | 23 +--- 27 files changed, 537 insertions(+), 184 deletions(-) create mode 100644 packages/core/src/modules/didcomm/index.ts create mode 100644 packages/core/src/modules/didcomm/services/DidCommDocumentService.ts create mode 100644 packages/core/src/modules/didcomm/services/__tests__/DidCommDocumentService.test.ts create mode 100644 packages/core/src/modules/didcomm/services/index.ts create mode 100644 packages/core/src/modules/didcomm/types.ts create mode 100644 packages/core/src/modules/didcomm/util/__tests__/matchingEd25519Key.test.ts create mode 100644 packages/core/src/modules/didcomm/util/matchingEd25519Key.ts diff --git a/packages/core/src/agent/MessageSender.ts b/packages/core/src/agent/MessageSender.ts index 83e5a717b7..e8a284e450 100644 --- a/packages/core/src/agent/MessageSender.ts +++ b/packages/core/src/agent/MessageSender.ts @@ -1,4 +1,5 @@ import type { ConnectionRecord } from '../modules/connections' +import type { ResolvedDidCommService } from '../modules/didcomm' import type { DidDocument, Key } from '../modules/dids' import type { OutOfBandRecord } from '../modules/oob/repository' import type { OutboundTransport } from '../transport/OutboundTransport' @@ -11,11 +12,11 @@ import { DID_COMM_TRANSPORT_QUEUE, InjectionSymbols } from '../constants' import { ReturnRouteTypes } from '../decorators/transport/TransportDecorator' import { AriesFrameworkError } from '../error' import { Logger } from '../logger' -import { keyReferenceToKey } from '../modules/dids' +import { DidCommDocumentService } from '../modules/didcomm' import { getKeyDidMappingByVerificationMethod } from '../modules/dids/domain/key-type' -import { DidCommV1Service, IndyAgentService } from '../modules/dids/domain/service' -import { didKeyToInstanceOfKey, verkeyToInstanceOfKey } from '../modules/dids/helpers' +import { didKeyToInstanceOfKey } from '../modules/dids/helpers' import { DidResolverService } from '../modules/dids/services/DidResolverService' +import { OutOfBandRepository } from '../modules/oob/repository' import { inject, injectable } from '../plugins' import { MessageRepository } from '../storage/MessageRepository' import { MessageValidator } from '../utils/MessageValidator' @@ -24,13 +25,6 @@ import { getProtocolScheme } from '../utils/uri' import { EnvelopeService } from './EnvelopeService' import { TransportService } from './TransportService' -export interface ResolvedDidCommService { - id: string - serviceEndpoint: string - recipientKeys: Key[] - routingKeys: Key[] -} - export interface TransportPriorityOptions { schemes: string[] restrictive?: boolean @@ -43,6 +37,8 @@ export class MessageSender { private messageRepository: MessageRepository private logger: Logger private didResolverService: DidResolverService + private didCommDocumentService: DidCommDocumentService + private outOfBandRepository: OutOfBandRepository public readonly outboundTransports: OutboundTransport[] = [] public constructor( @@ -50,13 +46,17 @@ export class MessageSender { transportService: TransportService, @inject(InjectionSymbols.MessageRepository) messageRepository: MessageRepository, @inject(InjectionSymbols.Logger) logger: Logger, - didResolverService: DidResolverService + didResolverService: DidResolverService, + didCommDocumentService: DidCommDocumentService, + outOfBandRepository: OutOfBandRepository ) { this.envelopeService = envelopeService this.transportService = transportService this.messageRepository = messageRepository this.logger = logger this.didResolverService = didResolverService + this.didCommDocumentService = didCommDocumentService + this.outOfBandRepository = outOfBandRepository this.outboundTransports = [] } @@ -342,49 +342,6 @@ export class MessageSender { throw new AriesFrameworkError(`Unable to send message to service: ${service.serviceEndpoint}`) } - private async retrieveServicesFromDid(did: string) { - this.logger.debug(`Resolving services for did ${did}.`) - const didDocument = await this.didResolverService.resolveDidDocument(did) - - const didCommServices: ResolvedDidCommService[] = [] - - // FIXME: we currently retrieve did documents for all didcomm services in the did document, and we don't have caching - // yet so this will re-trigger ledger resolves for each one. Should we only resolve the first service, then the second service, etc...? - for (const didCommService of didDocument.didCommServices) { - if (didCommService instanceof IndyAgentService) { - // IndyAgentService (DidComm v0) has keys encoded as raw publicKeyBase58 (verkeys) - didCommServices.push({ - id: didCommService.id, - recipientKeys: didCommService.recipientKeys.map(verkeyToInstanceOfKey), - routingKeys: didCommService.routingKeys?.map(verkeyToInstanceOfKey) || [], - serviceEndpoint: didCommService.serviceEndpoint, - }) - } else if (didCommService instanceof DidCommV1Service) { - // Resolve dids to DIDDocs to retrieve routingKeys - const routingKeys = [] - for (const routingKey of didCommService.routingKeys ?? []) { - const routingDidDocument = await this.didResolverService.resolveDidDocument(routingKey) - routingKeys.push(keyReferenceToKey(routingDidDocument, routingKey)) - } - - // Dereference recipientKeys - const recipientKeys = didCommService.recipientKeys.map((recipientKey) => - keyReferenceToKey(didDocument, recipientKey) - ) - - // DidCommV1Service has keys encoded as key references - didCommServices.push({ - id: didCommService.id, - recipientKeys, - routingKeys, - serviceEndpoint: didCommService.serviceEndpoint, - }) - } - } - - return didCommServices - } - private async retrieveServicesByConnection( connection: ConnectionRecord, transportPriority?: TransportPriorityOptions, @@ -399,14 +356,15 @@ export class MessageSender { if (connection.theirDid) { this.logger.debug(`Resolving services for connection theirDid ${connection.theirDid}.`) - didCommServices = await this.retrieveServicesFromDid(connection.theirDid) + didCommServices = await this.didCommDocumentService.resolveServicesFromDid(connection.theirDid) } else if (outOfBand) { - this.logger.debug(`Resolving services from out-of-band record ${outOfBand?.id}.`) + this.logger.debug(`Resolving services from out-of-band record ${outOfBand.id}.`) if (connection.isRequester) { - for (const service of outOfBand.outOfBandInvitation.services) { + for (const service of outOfBand.outOfBandInvitation.getServices()) { // Resolve dids to DIDDocs to retrieve services if (typeof service === 'string') { - didCommServices = await this.retrieveServicesFromDid(service) + this.logger.debug(`Resolving services for did ${service}.`) + didCommServices.push(...(await this.didCommDocumentService.resolveServicesFromDid(service))) } else { // Out of band inline service contains keys encoded as did:key references didCommServices.push({ diff --git a/packages/core/src/agent/__tests__/MessageSender.test.ts b/packages/core/src/agent/__tests__/MessageSender.test.ts index f2c0d363e1..3a9f9df1cb 100644 --- a/packages/core/src/agent/__tests__/MessageSender.test.ts +++ b/packages/core/src/agent/__tests__/MessageSender.test.ts @@ -1,18 +1,20 @@ import type { ConnectionRecord } from '../../modules/connections' +import type { ResolvedDidCommService } from '../../modules/didcomm' import type { DidDocumentService } from '../../modules/dids' import type { MessageRepository } from '../../storage/MessageRepository' import type { OutboundTransport } from '../../transport' import type { OutboundMessage, EncryptedMessage } from '../../types' -import type { ResolvedDidCommService } from '../MessageSender' import { TestMessage } from '../../../tests/TestMessage' import { getAgentConfig, getMockConnection, mockFunction } from '../../../tests/helpers' import testLogger from '../../../tests/logger' import { KeyType } from '../../crypto' import { ReturnRouteTypes } from '../../decorators/transport/TransportDecorator' -import { Key, DidDocument, VerificationMethod } from '../../modules/dids' +import { DidCommDocumentService } from '../../modules/didcomm' +import { DidResolverService, Key, DidDocument, VerificationMethod } from '../../modules/dids' import { DidCommV1Service } from '../../modules/dids/domain/service/DidCommV1Service' -import { DidResolverService } from '../../modules/dids/services/DidResolverService' +import { verkeyToInstanceOfKey } from '../../modules/dids/helpers' +import { OutOfBandRepository } from '../../modules/oob' import { InMemoryMessageRepository } from '../../storage/InMemoryMessageRepository' import { EnvelopeService as EnvelopeServiceImpl } from '../EnvelopeService' import { MessageSender } from '../MessageSender' @@ -24,11 +26,15 @@ import { DummyTransportSession } from './stubs' jest.mock('../TransportService') jest.mock('../EnvelopeService') jest.mock('../../modules/dids/services/DidResolverService') +jest.mock('../../modules/didcomm/services/DidCommDocumentService') +jest.mock('../../modules/oob/repository/OutOfBandRepository') const logger = testLogger const TransportServiceMock = TransportService as jest.MockedClass const DidResolverServiceMock = DidResolverService as jest.Mock +const DidCommDocumentServiceMock = DidCommDocumentService as jest.Mock +const OutOfBandRepositoryMock = OutOfBandRepository as jest.Mock class DummyHttpOutboundTransport implements OutboundTransport { public start(): Promise { @@ -76,7 +82,10 @@ describe('MessageSender', () => { const envelopeServicePackMessageMock = mockFunction(enveloperService.packMessage) const didResolverService = new DidResolverServiceMock() + const didCommDocumentService = new DidCommDocumentServiceMock() + const outOfBandRepository = new OutOfBandRepositoryMock() const didResolverServiceResolveMock = mockFunction(didResolverService.resolveDidDocument) + const didResolverServiceResolveDidServicesMock = mockFunction(didCommDocumentService.resolveServicesFromDid) const inboundMessage = new TestMessage() inboundMessage.setReturnRouting(ReturnRouteTypes.all) @@ -130,7 +139,9 @@ describe('MessageSender', () => { transportService, messageRepository, logger, - didResolverService + didResolverService, + didCommDocumentService, + outOfBandRepository ) connection = getMockConnection({ id: 'test-123', @@ -147,6 +158,10 @@ describe('MessageSender', () => { service: [firstDidCommService, secondDidCommService], }) didResolverServiceResolveMock.mockResolvedValue(didDocumentInstance) + didResolverServiceResolveDidServicesMock.mockResolvedValue([ + getMockResolvedDidService(firstDidCommService), + getMockResolvedDidService(secondDidCommService), + ]) }) afterEach(() => { @@ -161,6 +176,7 @@ describe('MessageSender', () => { messageSender.registerOutboundTransport(outboundTransport) didResolverServiceResolveMock.mockResolvedValue(getMockDidDocument({ service: [] })) + didResolverServiceResolveDidServicesMock.mockResolvedValue([]) await expect(messageSender.sendMessage(outboundMessage)).rejects.toThrow( `Message is undeliverable to connection test-123 (Test 123)` @@ -186,14 +202,14 @@ describe('MessageSender', () => { expect(sendMessageSpy).toHaveBeenCalledTimes(1) }) - test("resolves the did document using the did resolver if connection.theirDid starts with 'did:'", async () => { + test("resolves the did service using the did resolver if connection.theirDid starts with 'did:'", async () => { messageSender.registerOutboundTransport(outboundTransport) const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') await messageSender.sendMessage(outboundMessage) - expect(didResolverServiceResolveMock).toHaveBeenCalledWith(connection.theirDid) + expect(didResolverServiceResolveDidServicesMock).toHaveBeenCalledWith(connection.theirDid) expect(sendMessageSpy).toHaveBeenCalledWith({ connectionId: 'test-123', payload: encryptedMessage, @@ -326,7 +342,9 @@ describe('MessageSender', () => { transportService, new InMemoryMessageRepository(getAgentConfig('MessageSenderTest')), logger, - didResolverService + didResolverService, + didCommDocumentService, + outOfBandRepository ) envelopeServicePackMessageMock.mockReturnValue(Promise.resolve(encryptedMessage)) @@ -406,7 +424,9 @@ describe('MessageSender', () => { transportService, messageRepository, logger, - didResolverService + didResolverService, + didCommDocumentService, + outOfBandRepository ) connection = getMockConnection() @@ -454,3 +474,12 @@ function getMockDidDocument({ service }: { service: DidDocumentService[] }) { ], }) } + +function getMockResolvedDidService(service: DidDocumentService): ResolvedDidCommService { + return { + id: service.id, + serviceEndpoint: service.serviceEndpoint, + recipientKeys: [verkeyToInstanceOfKey('EoGusetSxDJktp493VCyh981nUnzMamTRjvBaHZAy68d')], + routingKeys: [], + } +} diff --git a/packages/core/src/agent/helpers.ts b/packages/core/src/agent/helpers.ts index b3516a1f25..029814362b 100644 --- a/packages/core/src/agent/helpers.ts +++ b/packages/core/src/agent/helpers.ts @@ -1,9 +1,9 @@ import type { ConnectionRecord } from '../modules/connections' +import type { ResolvedDidCommService } from '../modules/didcomm' import type { Key } from '../modules/dids/domain/Key' import type { OutOfBandRecord } from '../modules/oob/repository' import type { OutboundMessage, OutboundServiceMessage } from '../types' import type { AgentMessage } from './AgentMessage' -import type { ResolvedDidCommService } from './MessageSender' export function createOutboundMessage( connection: ConnectionRecord, diff --git a/packages/core/src/decorators/service/ServiceDecorator.ts b/packages/core/src/decorators/service/ServiceDecorator.ts index 72ee1226fe..0a105c4831 100644 --- a/packages/core/src/decorators/service/ServiceDecorator.ts +++ b/packages/core/src/decorators/service/ServiceDecorator.ts @@ -1,4 +1,4 @@ -import type { ResolvedDidCommService } from '../../agent/MessageSender' +import type { ResolvedDidCommService } from '../../modules/didcomm' import { IsArray, IsOptional, IsString } from 'class-validator' diff --git a/packages/core/src/modules/connections/DidExchangeProtocol.ts b/packages/core/src/modules/connections/DidExchangeProtocol.ts index e3f2ad741b..bc2a4e939e 100644 --- a/packages/core/src/modules/connections/DidExchangeProtocol.ts +++ b/packages/core/src/modules/connections/DidExchangeProtocol.ts @@ -1,8 +1,7 @@ -import type { ResolvedDidCommService } from '../../agent/MessageSender' import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' import type { Logger } from '../../logger' import type { ParsedMessageType } from '../../utils/messageType' -import type { OutOfBandDidCommService } from '../oob/domain/OutOfBandDidCommService' +import type { ResolvedDidCommService } from '../didcomm' import type { OutOfBandRecord } from '../oob/repository' import type { ConnectionRecord } from './repository' import type { Routing } from './services/ConnectionService' @@ -221,10 +220,7 @@ export class DidExchangeProtocol { if (routing) { services = this.routingToServices(routing) } else if (outOfBandRecord) { - const inlineServices = outOfBandRecord.outOfBandInvitation.services.filter( - (service) => typeof service !== 'string' - ) as OutOfBandDidCommService[] - + const inlineServices = outOfBandRecord.outOfBandInvitation.getInlineServices() services = inlineServices.map((service) => ({ id: service.id, serviceEndpoint: service.serviceEndpoint, @@ -300,7 +296,9 @@ export class DidExchangeProtocol { const didDocument = await this.extractDidDocument( message, - outOfBandRecord.outOfBandInvitation.getRecipientKeys().map((key) => key.publicKeyBase58) + outOfBandRecord + .getTags() + .recipientKeyFingerprints.map((fingerprint) => Key.fromFingerprint(fingerprint).publicKeyBase58) ) const didRecord = new DidRecord({ id: message.did, diff --git a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts index 48ca476cb4..13d6a67a05 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts @@ -66,7 +66,6 @@ export class ConnectionResponseHandler implements Handler { } messageContext.connection = connectionRecord - // The presence of outOfBandRecord is not mandatory when the old connection invitation is used const connection = await this.connectionService.processResponse(messageContext, outOfBandRecord) // TODO: should we only send ping message in case of autoAcceptConnection or always? diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index 570c314de9..1ca5adf5ce 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -109,6 +109,19 @@ export class ConnectionService { didDoc, }) + const { label, imageUrl } = config + const connectionRequest = new ConnectionRequestMessage({ + label: label ?? this.config.label, + did: didDoc.id, + didDoc, + imageUrl: imageUrl ?? this.config.connectionImageUrl, + }) + + connectionRequest.setThread({ + threadId: connectionRequest.id, + parentThreadId: outOfBandInvitation.id, + }) + const connectionRecord = await this.createConnection({ protocol: HandshakeProtocol.Connections, role: DidExchangeRole.Requester, @@ -121,22 +134,9 @@ export class ConnectionService { outOfBandId: outOfBandRecord.id, invitationDid, imageUrl: outOfBandInvitation.imageUrl, + threadId: connectionRequest.id, }) - const { label, imageUrl, autoAcceptConnection } = config - - const connectionRequest = new ConnectionRequestMessage({ - label: label ?? this.config.label, - did: didDoc.id, - didDoc, - imageUrl: imageUrl ?? this.config.connectionImageUrl, - }) - - if (autoAcceptConnection !== undefined || autoAcceptConnection !== null) { - connectionRecord.autoAcceptConnection = config?.autoAcceptConnection - } - - connectionRecord.threadId = connectionRequest.id await this.updateState(connectionRecord, DidExchangeState.RequestSent) return { @@ -204,11 +204,7 @@ export class ConnectionService { const didDoc = routing ? this.createDidDoc(routing) - : this.createDidDocFromOutOfBandDidCommServices( - outOfBandRecord.outOfBandInvitation.services.filter( - (s): s is OutOfBandDidCommService => typeof s !== 'string' - ) - ) + : this.createDidDocFromOutOfBandDidCommServices(outOfBandRecord.outOfBandInvitation.getInlineServices()) const { did: peerDid } = await this.createDid({ role: DidDocumentRole.Created, diff --git a/packages/core/src/modules/didcomm/index.ts b/packages/core/src/modules/didcomm/index.ts new file mode 100644 index 0000000000..ff4d44346c --- /dev/null +++ b/packages/core/src/modules/didcomm/index.ts @@ -0,0 +1,2 @@ +export * from './types' +export * from './services' diff --git a/packages/core/src/modules/didcomm/services/DidCommDocumentService.ts b/packages/core/src/modules/didcomm/services/DidCommDocumentService.ts new file mode 100644 index 0000000000..18c7c9958c --- /dev/null +++ b/packages/core/src/modules/didcomm/services/DidCommDocumentService.ts @@ -0,0 +1,71 @@ +import type { Logger } from '../../../logger' +import type { ResolvedDidCommService } from '../types' + +import { AgentConfig } from '../../../agent/AgentConfig' +import { KeyType } from '../../../crypto' +import { injectable } from '../../../plugins' +import { DidResolverService } from '../../dids' +import { DidCommV1Service, IndyAgentService, keyReferenceToKey } from '../../dids/domain' +import { verkeyToInstanceOfKey } from '../../dids/helpers' +import { findMatchingEd25519Key } from '../util/matchingEd25519Key' + +@injectable() +export class DidCommDocumentService { + private logger: Logger + private didResolverService: DidResolverService + + public constructor(agentConfig: AgentConfig, didResolverService: DidResolverService) { + this.logger = agentConfig.logger + this.didResolverService = didResolverService + } + + public async resolveServicesFromDid(did: string): Promise { + const didDocument = await this.didResolverService.resolveDidDocument(did) + + const didCommServices: ResolvedDidCommService[] = [] + + // FIXME: we currently retrieve did documents for all didcomm services in the did document, and we don't have caching + // yet so this will re-trigger ledger resolves for each one. Should we only resolve the first service, then the second service, etc...? + for (const didCommService of didDocument.didCommServices) { + if (didCommService instanceof IndyAgentService) { + // IndyAgentService (DidComm v0) has keys encoded as raw publicKeyBase58 (verkeys) + didCommServices.push({ + id: didCommService.id, + recipientKeys: didCommService.recipientKeys.map(verkeyToInstanceOfKey), + routingKeys: didCommService.routingKeys?.map(verkeyToInstanceOfKey) || [], + serviceEndpoint: didCommService.serviceEndpoint, + }) + } else if (didCommService instanceof DidCommV1Service) { + // Resolve dids to DIDDocs to retrieve routingKeys + const routingKeys = [] + for (const routingKey of didCommService.routingKeys ?? []) { + const routingDidDocument = await this.didResolverService.resolveDidDocument(routingKey) + routingKeys.push(keyReferenceToKey(routingDidDocument, routingKey)) + } + + // DidCommV1Service has keys encoded as key references + + // Dereference recipientKeys + const recipientKeys = didCommService.recipientKeys.map((recipientKeyReference) => { + const key = keyReferenceToKey(didDocument, recipientKeyReference) + + // try to find a matching Ed25519 key (https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html#did-document-notes) + if (key.keyType === KeyType.X25519) { + const matchingEd25519Key = findMatchingEd25519Key(key, didDocument) + if (matchingEd25519Key) return matchingEd25519Key + } + return key + }) + + didCommServices.push({ + id: didCommService.id, + recipientKeys, + routingKeys, + serviceEndpoint: didCommService.serviceEndpoint, + }) + } + } + + return didCommServices + } +} diff --git a/packages/core/src/modules/didcomm/services/__tests__/DidCommDocumentService.test.ts b/packages/core/src/modules/didcomm/services/__tests__/DidCommDocumentService.test.ts new file mode 100644 index 0000000000..6dacdc4e5e --- /dev/null +++ b/packages/core/src/modules/didcomm/services/__tests__/DidCommDocumentService.test.ts @@ -0,0 +1,113 @@ +import type { VerificationMethod } from '../../../dids' + +import { getAgentConfig, mockFunction } from '../../../../../tests/helpers' +import { KeyType } from '../../../../crypto' +import { DidCommV1Service, DidDocument, IndyAgentService, Key } from '../../../dids' +import { verkeyToInstanceOfKey } from '../../../dids/helpers' +import { DidResolverService } from '../../../dids/services/DidResolverService' +import { DidCommDocumentService } from '../DidCommDocumentService' + +jest.mock('../../../dids/services/DidResolverService') +const DidResolverServiceMock = DidResolverService as jest.Mock + +describe('DidCommDocumentService', () => { + const agentConfig = getAgentConfig('DidCommDocumentService') + let didCommDocumentService: DidCommDocumentService + let didResolverService: DidResolverService + + beforeEach(async () => { + didResolverService = new DidResolverServiceMock() + didCommDocumentService = new DidCommDocumentService(agentConfig, didResolverService) + }) + + describe('resolveServicesFromDid', () => { + test('throw error when resolveDidDocument fails', async () => { + const error = new Error('test') + mockFunction(didResolverService.resolveDidDocument).mockRejectedValue(error) + + await expect(didCommDocumentService.resolveServicesFromDid('did')).rejects.toThrowError(error) + }) + + test('resolves IndyAgentService', async () => { + mockFunction(didResolverService.resolveDidDocument).mockResolvedValue( + new DidDocument({ + context: ['https://w3id.org/did/v1'], + id: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h', + service: [ + new IndyAgentService({ + id: 'test-id', + serviceEndpoint: 'https://test.com', + recipientKeys: ['Q4zqM7aXqm7gDQkUVLng9h'], + routingKeys: ['DADEajsDSaksLng9h'], + priority: 5, + }), + ], + }) + ) + + const resolved = await didCommDocumentService.resolveServicesFromDid('did:sov:Q4zqM7aXqm7gDQkUVLng9h') + expect(didResolverService.resolveDidDocument).toHaveBeenCalledWith('did:sov:Q4zqM7aXqm7gDQkUVLng9h') + + expect(resolved).toHaveLength(1) + expect(resolved[0]).toMatchObject({ + id: 'test-id', + serviceEndpoint: 'https://test.com', + recipientKeys: [verkeyToInstanceOfKey('Q4zqM7aXqm7gDQkUVLng9h')], + routingKeys: [verkeyToInstanceOfKey('DADEajsDSaksLng9h')], + }) + }) + + test('resolves DidCommV1Service', async () => { + const publicKeyBase58Ed25519 = 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8' + const publicKeyBase58X25519 = 'S3AQEEKkGYrrszT9D55ozVVX2XixYp8uynqVm4okbud' + + const Ed25519VerificationMethod: VerificationMethod = { + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h', + id: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h#key-1', + publicKeyBase58: publicKeyBase58Ed25519, + } + const X25519VerificationMethod: VerificationMethod = { + type: 'X25519KeyAgreementKey2019', + controller: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h', + id: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h#key-agreement-1', + publicKeyBase58: publicKeyBase58X25519, + } + + mockFunction(didResolverService.resolveDidDocument).mockResolvedValue( + new DidDocument({ + context: [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h', + verificationMethod: [Ed25519VerificationMethod, X25519VerificationMethod], + authentication: [Ed25519VerificationMethod.id], + keyAgreement: [X25519VerificationMethod.id], + service: [ + new DidCommV1Service({ + id: 'test-id', + serviceEndpoint: 'https://test.com', + recipientKeys: [X25519VerificationMethod.id], + routingKeys: [Ed25519VerificationMethod.id], + priority: 5, + }), + ], + }) + ) + + const resolved = await didCommDocumentService.resolveServicesFromDid('did:sov:Q4zqM7aXqm7gDQkUVLng9h') + expect(didResolverService.resolveDidDocument).toHaveBeenCalledWith('did:sov:Q4zqM7aXqm7gDQkUVLng9h') + + const ed25519Key = Key.fromPublicKeyBase58(publicKeyBase58Ed25519, KeyType.Ed25519) + expect(resolved).toHaveLength(1) + expect(resolved[0]).toMatchObject({ + id: 'test-id', + serviceEndpoint: 'https://test.com', + recipientKeys: [ed25519Key], + routingKeys: [ed25519Key], + }) + }) + }) +}) diff --git a/packages/core/src/modules/didcomm/services/index.ts b/packages/core/src/modules/didcomm/services/index.ts new file mode 100644 index 0000000000..ae2cb50e2f --- /dev/null +++ b/packages/core/src/modules/didcomm/services/index.ts @@ -0,0 +1 @@ +export * from './DidCommDocumentService' diff --git a/packages/core/src/modules/didcomm/types.ts b/packages/core/src/modules/didcomm/types.ts new file mode 100644 index 0000000000..3282c62507 --- /dev/null +++ b/packages/core/src/modules/didcomm/types.ts @@ -0,0 +1,8 @@ +import type { Key } from '../dids/domain' + +export interface ResolvedDidCommService { + id: string + serviceEndpoint: string + recipientKeys: Key[] + routingKeys: Key[] +} diff --git a/packages/core/src/modules/didcomm/util/__tests__/matchingEd25519Key.test.ts b/packages/core/src/modules/didcomm/util/__tests__/matchingEd25519Key.test.ts new file mode 100644 index 0000000000..dac0af79c8 --- /dev/null +++ b/packages/core/src/modules/didcomm/util/__tests__/matchingEd25519Key.test.ts @@ -0,0 +1,84 @@ +import type { VerificationMethod } from '../../../dids' + +import { KeyType } from '../../../../crypto' +import { DidDocument, Key } from '../../../dids' +import { findMatchingEd25519Key } from '../matchingEd25519Key' + +describe('findMatchingEd25519Key', () => { + const publicKeyBase58Ed25519 = 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8' + const Ed25519VerificationMethod: VerificationMethod = { + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:WJz9mHyW9BZksioQnRsrAo', + id: 'did:sov:WJz9mHyW9BZksioQnRsrAo#key-1', + publicKeyBase58: publicKeyBase58Ed25519, + } + + const publicKeyBase58X25519 = 'S3AQEEKkGYrrszT9D55ozVVX2XixYp8uynqVm4okbud' + const X25519VerificationMethod: VerificationMethod = { + type: 'X25519KeyAgreementKey2019', + controller: 'did:sov:WJz9mHyW9BZksioQnRsrAo', + id: 'did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1', + publicKeyBase58: publicKeyBase58X25519, + } + + describe('referenced verification method', () => { + const didDocument = new DidDocument({ + context: [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:sov:WJz9mHyW9BZksioQnRsrAo', + verificationMethod: [Ed25519VerificationMethod, X25519VerificationMethod], + authentication: [Ed25519VerificationMethod.id], + assertionMethod: [Ed25519VerificationMethod.id], + keyAgreement: [X25519VerificationMethod.id], + }) + + test('returns matching Ed25519 key if corresponding X25519 key supplied', () => { + const x25519Key = Key.fromPublicKeyBase58(publicKeyBase58X25519, KeyType.X25519) + const ed25519Key = findMatchingEd25519Key(x25519Key, didDocument) + expect(ed25519Key?.publicKeyBase58).toBe(Ed25519VerificationMethod.publicKeyBase58) + }) + + test('returns undefined if non-corresponding X25519 key supplied', () => { + const differentX25519Key = Key.fromPublicKeyBase58('Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', KeyType.X25519) + expect(findMatchingEd25519Key(differentX25519Key, didDocument)).toBeUndefined() + }) + + test('returns undefined if ed25519 key supplied', () => { + const ed25519Key = Key.fromPublicKeyBase58(publicKeyBase58Ed25519, KeyType.Ed25519) + expect(findMatchingEd25519Key(ed25519Key, didDocument)).toBeUndefined() + }) + }) + + describe('non-referenced authentication', () => { + const didDocument = new DidDocument({ + context: [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:sov:WJz9mHyW9BZksioQnRsrAo', + authentication: [Ed25519VerificationMethod], + assertionMethod: [Ed25519VerificationMethod], + keyAgreement: [X25519VerificationMethod], + }) + + test('returns matching Ed25519 key if corresponding X25519 key supplied', () => { + const x25519Key = Key.fromPublicKeyBase58(publicKeyBase58X25519, KeyType.X25519) + const ed25519Key = findMatchingEd25519Key(x25519Key, didDocument) + expect(ed25519Key?.publicKeyBase58).toBe(Ed25519VerificationMethod.publicKeyBase58) + }) + + test('returns undefined if non-corresponding X25519 key supplied', () => { + const differentX25519Key = Key.fromPublicKeyBase58('Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', KeyType.X25519) + expect(findMatchingEd25519Key(differentX25519Key, didDocument)).toBeUndefined() + }) + + test('returns undefined if ed25519 key supplied', () => { + const ed25519Key = Key.fromPublicKeyBase58(publicKeyBase58Ed25519, KeyType.Ed25519) + expect(findMatchingEd25519Key(ed25519Key, didDocument)).toBeUndefined() + }) + }) +}) diff --git a/packages/core/src/modules/didcomm/util/matchingEd25519Key.ts b/packages/core/src/modules/didcomm/util/matchingEd25519Key.ts new file mode 100644 index 0000000000..e163f0d38f --- /dev/null +++ b/packages/core/src/modules/didcomm/util/matchingEd25519Key.ts @@ -0,0 +1,32 @@ +import type { DidDocument, VerificationMethod } from '../../dids' + +import { KeyType } from '../../../crypto' +import { Key, keyReferenceToKey } from '../../dids' +import { convertPublicKeyToX25519 } from '../../dids/domain/key-type/ed25519' + +/** + * Tries to find a matching Ed25519 key to the supplied X25519 key + * @param x25519Key X25519 key + * @param didDocument Did document containing all the keys + * @returns a matching Ed25519 key or `undefined` (if no matching key found) + */ +export function findMatchingEd25519Key(x25519Key: Key, didDocument: DidDocument): Key | undefined { + if (x25519Key.keyType !== KeyType.X25519) return undefined + + const verificationMethods = didDocument.verificationMethod ?? [] + const keyAgreements = didDocument.keyAgreement ?? [] + const authentications = didDocument.authentication ?? [] + const allKeyReferences: VerificationMethod[] = [ + ...verificationMethods, + ...authentications.filter((keyAgreement): keyAgreement is VerificationMethod => typeof keyAgreement !== 'string'), + ...keyAgreements.filter((keyAgreement): keyAgreement is VerificationMethod => typeof keyAgreement !== 'string'), + ] + + return allKeyReferences + .map((keyReference) => keyReferenceToKey(didDocument, keyReference.id)) + .filter((key) => key?.keyType === KeyType.Ed25519) + .find((keyEd25519) => { + const keyX25519 = Key.fromPublicKey(convertPublicKeyToX25519(keyEd25519.publicKey), KeyType.X25519) + return keyX25519.publicKeyBase58 === x25519Key.publicKeyBase58 + }) +} diff --git a/packages/core/src/modules/dids/domain/createPeerDidFromServices.ts b/packages/core/src/modules/dids/domain/createPeerDidFromServices.ts index 3fe2375a35..b866b0fa33 100644 --- a/packages/core/src/modules/dids/domain/createPeerDidFromServices.ts +++ b/packages/core/src/modules/dids/domain/createPeerDidFromServices.ts @@ -1,4 +1,4 @@ -import type { ResolvedDidCommService } from '../../../agent/MessageSender' +import type { ResolvedDidCommService } from '../../didcomm' import { convertPublicKeyToX25519 } from '@stablelib/ed25519' diff --git a/packages/core/src/modules/oob/OutOfBandModule.ts b/packages/core/src/modules/oob/OutOfBandModule.ts index 333af57332..a513fea180 100644 --- a/packages/core/src/modules/oob/OutOfBandModule.ts +++ b/packages/core/src/modules/oob/OutOfBandModule.ts @@ -24,9 +24,9 @@ import { DidCommMessageRepository, DidCommMessageRole } from '../../storage' import { JsonEncoder, JsonTransformer } from '../../utils' import { parseMessageType, supportsIncomingMessageType } from '../../utils/messageType' import { parseInvitationUrl, parseInvitationShortUrl } from '../../utils/parseInvitation' +import { DidCommDocumentService } from '../didcomm' import { DidKey } from '../dids' import { didKeyToVerkey } from '../dids/helpers' -import { outOfBandServiceToNumAlgo2Did } from '../dids/methods/peer/peerDidNumAlgo2' import { RoutingService } from '../routing/services/RoutingService' import { OutOfBandService } from './OutOfBandService' @@ -89,6 +89,7 @@ export class OutOfBandModule { private eventEmitter: EventEmitter private agentConfig: AgentConfig private logger: Logger + private didCommDocumentService: DidCommDocumentService public constructor( dispatcher: Dispatcher, @@ -98,7 +99,8 @@ export class OutOfBandModule { connectionsModule: ConnectionsModule, didCommMessageRepository: DidCommMessageRepository, messageSender: MessageSender, - eventEmitter: EventEmitter + eventEmitter: EventEmitter, + didCommDocumentService: DidCommDocumentService ) { this.dispatcher = dispatcher this.agentConfig = agentConfig @@ -109,6 +111,7 @@ export class OutOfBandModule { this.didCommMessageRepository = didCommMessageRepository this.messageSender = messageSender this.eventEmitter = eventEmitter + this.didCommDocumentService = didCommDocumentService this.registerHandlers(dispatcher) } @@ -207,6 +210,11 @@ export class OutOfBandModule { outOfBandInvitation: outOfBandInvitation, reusable: multiUseInvitation, autoAcceptConnection, + tags: { + recipientKeyFingerprints: services + .reduce((aggr, { recipientKeys }) => [...aggr, ...recipientKeys], []) + .map((didKey) => DidKey.fromDid(didKey).key.fingerprint), + }, }) await this.outOfBandService.save(outOfBandRecord) @@ -350,12 +358,30 @@ export class OutOfBandModule { ) } + const recipientKeyFingerprints: string[] = [] + for (const service of outOfBandInvitation.getServices()) { + // Resolve dids to DIDDocs to retrieve services + if (typeof service === 'string') { + this.logger.debug(`Resolving services for did ${service}.`) + const resolvedDidCommServices = await this.didCommDocumentService.resolveServicesFromDid(service) + recipientKeyFingerprints.push( + ...resolvedDidCommServices + .reduce((aggr, { recipientKeys }) => [...aggr, ...recipientKeys], []) + .map((key) => key.fingerprint) + ) + } else { + recipientKeyFingerprints.push(...service.recipientKeys.map((didKey) => DidKey.fromDid(didKey).key.fingerprint)) + } + } + outOfBandRecord = new OutOfBandRecord({ role: OutOfBandRole.Receiver, state: OutOfBandState.Initial, outOfBandInvitation: outOfBandInvitation, autoAcceptConnection, + tags: { recipientKeyFingerprints }, }) + await this.outOfBandService.save(outOfBandRecord) this.outOfBandService.emitStateChangedEvent(outOfBandRecord, null) @@ -403,10 +429,11 @@ export class OutOfBandModule { const { outOfBandInvitation } = outOfBandRecord const { label, alias, imageUrl, autoAcceptConnection, reuseConnection, routing } = config - const { handshakeProtocols, services } = outOfBandInvitation + const { handshakeProtocols } = outOfBandInvitation + const services = outOfBandInvitation.getServices() const messages = outOfBandInvitation.getRequests() - const existingConnection = await this.findExistingConnection(services) + const existingConnection = await this.findExistingConnection(outOfBandInvitation) await this.outOfBandService.updateState(outOfBandRecord, OutOfBandState.PrepareResponse) @@ -578,26 +605,20 @@ export class OutOfBandModule { return handshakeProtocol } - private async findExistingConnection(services: Array) { - this.logger.debug('Searching for an existing connection for out-of-band invitation services.', { services }) + private async findExistingConnection(outOfBandInvitation: OutOfBandInvitation) { + this.logger.debug('Searching for an existing connection for out-of-band invitation.', { outOfBandInvitation }) - // TODO: for each did we should look for a connection with the invitation did OR a connection with theirDid that matches the service did - for (const didOrService of services) { - // We need to check if the service is an instance of string because of limitations from class-validator - if (typeof didOrService === 'string' || didOrService instanceof String) { - // TODO await this.connectionsModule.findByTheirDid() - throw new AriesFrameworkError('Dids are not currently supported in out-of-band invitation services attribute.') - } - - const did = outOfBandServiceToNumAlgo2Did(didOrService) - const connections = await this.connectionsModule.findByInvitationDid(did) - this.logger.debug(`Retrieved ${connections.length} connections for invitation did ${did}`) + for (const invitationDid of outOfBandInvitation.invitationDids) { + const connections = await this.connectionsModule.findByInvitationDid(invitationDid) + this.logger.debug(`Retrieved ${connections.length} connections for invitation did ${invitationDid}`) if (connections.length === 1) { const [firstConnection] = connections return firstConnection } else if (connections.length > 1) { - this.logger.warn(`There is more than one connection created from invitationDid ${did}. Taking the first one.`) + this.logger.warn( + `There is more than one connection created from invitationDid ${invitationDid}. Taking the first one.` + ) const [firstConnection] = connections return firstConnection } @@ -644,19 +665,36 @@ export class OutOfBandModule { this.logger.debug(`Message with type ${plaintextMessage['@type']} can be processed.`) + let serviceEndpoint: string | undefined + let recipientKeys: string[] | undefined + let routingKeys: string[] = [] + // The framework currently supports only older OOB messages with `~service` decorator. // TODO: support receiving messages with other services so we don't have to transform the service // to ~service decorator const [service] = services if (typeof service === 'string') { - throw new AriesFrameworkError('Dids are not currently supported in out-of-band invitation services attribute.') + const [didService] = await this.didCommDocumentService.resolveServicesFromDid(service) + if (didService) { + serviceEndpoint = didService.serviceEndpoint + recipientKeys = didService.recipientKeys.map((key) => key.publicKeyBase58) + routingKeys = didService.routingKeys.map((key) => key.publicKeyBase58) || [] + } + } else { + serviceEndpoint = service.serviceEndpoint + recipientKeys = service.recipientKeys.map(didKeyToVerkey) + routingKeys = service.routingKeys?.map(didKeyToVerkey) || [] + } + + if (!serviceEndpoint || !recipientKeys) { + throw new AriesFrameworkError('Service not found') } const serviceDecorator = new ServiceDecorator({ - recipientKeys: service.recipientKeys.map(didKeyToVerkey), - routingKeys: service.routingKeys?.map(didKeyToVerkey) || [], - serviceEndpoint: service.serviceEndpoint, + recipientKeys, + routingKeys, + serviceEndpoint, }) plaintextMessage['~service'] = JsonTransformer.toJSON(serviceDecorator) diff --git a/packages/core/src/modules/oob/helpers.ts b/packages/core/src/modules/oob/helpers.ts index e3677ee76d..be2fdc1b6e 100644 --- a/packages/core/src/modules/oob/helpers.ts +++ b/packages/core/src/modules/oob/helpers.ts @@ -37,7 +37,7 @@ export function convertToNewInvitation(oldInvitation: ConnectionInvitationMessag export function convertToOldInvitation(newInvitation: OutOfBandInvitation) { // Taking first service, as we can only include one service in a legacy invitation. - const [service] = newInvitation.services + const [service] = newInvitation.getServices() let options if (typeof service === 'string') { diff --git a/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts b/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts index 5b6b776499..39aec65941 100644 --- a/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts +++ b/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts @@ -1,6 +1,5 @@ import type { PlaintextMessage } from '../../../types' import type { HandshakeProtocol } from '../../connections' -import type { Key } from '../../dids' import { Expose, Transform, TransformationType, Type } from 'class-transformer' import { ArrayNotEmpty, IsArray, IsInstance, IsOptional, IsUrl, ValidateNested } from 'class-validator' @@ -13,7 +12,6 @@ import { JsonEncoder } from '../../../utils/JsonEncoder' import { JsonTransformer } from '../../../utils/JsonTransformer' import { IsValidMessageType, parseMessageType, replaceLegacyDidSovPrefix } from '../../../utils/messageType' import { IsStringOrInstance } from '../../../utils/validators' -import { DidKey } from '../../dids' import { outOfBandServiceToNumAlgo2Did } from '../../dids/methods/peer/peerDidNumAlgo2' import { OutOfBandDidCommService } from '../domain/OutOfBandDidCommService' @@ -89,7 +87,7 @@ export class OutOfBandInvitation extends AgentMessage { } public get invitationDids() { - const dids = this.services.map((didOrService) => { + const dids = this.getServices().map((didOrService) => { if (typeof didOrService === 'string') { return didOrService } @@ -98,13 +96,18 @@ export class OutOfBandInvitation extends AgentMessage { return dids } - // TODO: this only takes into account inline didcomm services, won't work for public dids - public getRecipientKeys(): Key[] { - return this.services - .filter((s): s is OutOfBandDidCommService => typeof s !== 'string' && !(s instanceof String)) - .map((s) => s.recipientKeys) - .reduce((acc, curr) => [...acc, ...curr], []) - .map((didKey) => DidKey.fromDid(didKey).key) + // shorthand for services without the need to deal with the String DIDs + public getServices(): Array { + return this.services.map((service) => { + if (service instanceof String) return service.toString() + return service + }) + } + public getDidServices(): Array { + return this.getServices().filter((service): service is string => typeof service === 'string') + } + public getInlineServices(): Array { + return this.getServices().filter((service): service is OutOfBandDidCommService => typeof service !== 'string') } @Transform(({ value }) => replaceLegacyDidSovPrefix(value), { @@ -141,7 +144,8 @@ export class OutOfBandInvitation extends AgentMessage { @OutOfBandServiceTransformer() @IsStringOrInstance(OutOfBandDidCommService, { each: true }) @ValidateNested({ each: true }) - public services!: Array + // eslint-disable-next-line @typescript-eslint/ban-types + private services!: Array /** * Custom property. It is not part of the RFC. @@ -152,13 +156,8 @@ export class OutOfBandInvitation extends AgentMessage { } /** - * Decorator that transforms authentication json to corresponding class instances - * - * @example - * class Example { - * VerificationMethodTransformer() - * private authentication: VerificationMethod - * } + * Decorator that transforms services json to corresponding class instances + * @note Because of ValidateNested limitation, this produces instances of String for DID services except plain js string */ function OutOfBandServiceTransformer() { return Transform(({ value, type }: { value: Array; type: TransformationType }) => { diff --git a/packages/core/src/modules/oob/repository/OutOfBandRecord.ts b/packages/core/src/modules/oob/repository/OutOfBandRecord.ts index 02b821b004..3a67aa4aa7 100644 --- a/packages/core/src/modules/oob/repository/OutOfBandRecord.ts +++ b/packages/core/src/modules/oob/repository/OutOfBandRecord.ts @@ -9,11 +9,21 @@ import { BaseRecord } from '../../../storage/BaseRecord' import { uuid } from '../../../utils/uuid' import { OutOfBandInvitation } from '../messages' +type DefaultOutOfBandRecordTags = { + role: OutOfBandRole + state: OutOfBandState + invitationId: string +} + +interface CustomOutOfBandRecordTags extends TagsBase { + recipientKeyFingerprints: string[] +} + export interface OutOfBandRecordProps { id?: string createdAt?: Date updatedAt?: Date - tags?: TagsBase + tags?: CustomOutOfBandRecordTags outOfBandInvitation: OutOfBandInvitation role: OutOfBandRole state: OutOfBandState @@ -23,14 +33,7 @@ export interface OutOfBandRecordProps { reuseConnectionId?: string } -type DefaultOutOfBandRecordTags = { - role: OutOfBandRole - state: OutOfBandState - invitationId: string - recipientKeyFingerprints: string[] -} - -export class OutOfBandRecord extends BaseRecord { +export class OutOfBandRecord extends BaseRecord { @Type(() => OutOfBandInvitation) public outOfBandInvitation!: OutOfBandInvitation public role!: OutOfBandRole @@ -56,7 +59,7 @@ export class OutOfBandRecord extends BaseRecord { this.reusable = props.reusable ?? false this.mediatorId = props.mediatorId this.reuseConnectionId = props.reuseConnectionId - this._tags = props.tags ?? {} + this._tags = props.tags ?? { recipientKeyFingerprints: [] } } } @@ -66,7 +69,6 @@ export class OutOfBandRecord extends BaseRecord { role: this.role, state: this.state, invitationId: this.outOfBandInvitation.id, - recipientKeyFingerprints: this.outOfBandInvitation.getRecipientKeys().map((key) => key.fingerprint), } } diff --git a/packages/core/src/modules/oob/repository/__tests__/OutOfBandRecord.test.ts b/packages/core/src/modules/oob/repository/__tests__/OutOfBandRecord.test.ts index ee649b7710..6c5cef483e 100644 --- a/packages/core/src/modules/oob/repository/__tests__/OutOfBandRecord.test.ts +++ b/packages/core/src/modules/oob/repository/__tests__/OutOfBandRecord.test.ts @@ -22,6 +22,9 @@ describe('OutOfBandRecord', () => { ], id: 'a-message-id', }), + tags: { + recipientKeyFingerprints: ['z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th'], + }, }) expect(outOfBandRecord.getTags()).toEqual({ diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap index 838bdd61b0..fa62c1baad 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap @@ -782,7 +782,11 @@ Object { }, "type": "OutOfBandRecord", "value": Object { - "_tags": Object {}, + "_tags": Object { + "recipientKeyFingerprints": Array [ + "z6MkfiPMPxCQeSDZGMkCvm1Y2rBoPsmw4ZHMv71jXtcWRRiM", + ], + }, "autoAcceptConnection": undefined, "createdAt": "2022-04-30T13:02:21.577Z", "id": "1-4e4f-41d9-94c4-f49351b811f1", @@ -841,7 +845,11 @@ Object { }, "type": "OutOfBandRecord", "value": Object { - "_tags": Object {}, + "_tags": Object { + "recipientKeyFingerprints": Array [ + "z6MktCZAQNGvWb4WHAjwBqPtXhZdDYorbSJkGW9vj1uhw1HD", + ], + }, "autoAcceptConnection": undefined, "createdAt": "2022-04-30T13:02:21.608Z", "id": "2-4e4f-41d9-94c4-f49351b811f1", @@ -900,7 +908,11 @@ Object { }, "type": "OutOfBandRecord", "value": Object { - "_tags": Object {}, + "_tags": Object { + "recipientKeyFingerprints": Array [ + "z6Mkt1tsp15cnDD7wBCFgehiR2SxHX1aPxt4sueE24twH9Bd", + ], + }, "autoAcceptConnection": false, "createdAt": "2022-04-30T13:02:21.628Z", "id": "3-4e4f-41d9-94c4-f49351b811f1", @@ -959,7 +971,11 @@ Object { }, "type": "OutOfBandRecord", "value": Object { - "_tags": Object {}, + "_tags": Object { + "recipientKeyFingerprints": Array [ + "z6Mkmod8vp2nURVktVC5ceQeyr2VUz26iu2ZANLNVg9pMawa", + ], + }, "autoAcceptConnection": undefined, "createdAt": "2022-04-30T13:02:21.635Z", "id": "4-4e4f-41d9-94c4-f49351b811f1", @@ -1018,7 +1034,11 @@ Object { }, "type": "OutOfBandRecord", "value": Object { - "_tags": Object {}, + "_tags": Object { + "recipientKeyFingerprints": Array [ + "z6MkjDJL4X7YGoH6gjamhZR2NzowPZqtJfX5kPuNuWiVdjMr", + ], + }, "autoAcceptConnection": false, "createdAt": "2022-04-30T13:02:21.641Z", "id": "5-4e4f-41d9-94c4-f49351b811f1", @@ -1135,7 +1155,11 @@ Object { }, "type": "OutOfBandRecord", "value": Object { - "_tags": Object {}, + "_tags": Object { + "recipientKeyFingerprints": Array [ + "z6MkuWTEmH1mUo6W96zSWyH612hFHowRzNEscPYBL2CCMyC2", + ], + }, "autoAcceptConnection": true, "createdAt": "2022-04-30T13:02:21.653Z", "id": "7-4e4f-41d9-94c4-f49351b811f1", diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts index c68f5e14d1..24616cd3f3 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts @@ -383,7 +383,7 @@ describe('0.1-0.2 | Connection', () => { expect(outOfBandRecord.toJSON()).toEqual({ id: expect.any(String), - _tags: {}, + _tags: { recipientKeyFingerprints: ['z6MksYU4MHtfmNhNm1uGMvANr9j4CBv2FymjiJtRgA36bSVH'] }, metadata: {}, // Checked below outOfBandInvitation: { diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts b/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts index 30d5058729..5d6ce948be 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts @@ -12,6 +12,7 @@ import { DidExchangeRole, } from '../../../../modules/connections' import { convertToNewDidDocument } from '../../../../modules/connections/services/helpers' +import { DidKey } from '../../../../modules/dids' import { DidDocumentRole } from '../../../../modules/dids/domain/DidDocumentRole' import { DidRecord, DidRepository } from '../../../../modules/dids/repository' import { DidRecordMetadataKeys } from '../../../../modules/dids/repository/didRecordMetadataTypes' @@ -310,9 +311,15 @@ export async function migrateToOobRecord( const outOfBandInvitation = convertToNewInvitation(oldInvitation) // If both the recipientKeys and the @id match we assume the connection was created using the same invitation. + const recipientKeyFingerprints = outOfBandInvitation + .getInlineServices() + .map((s) => s.recipientKeys) + .reduce((acc, curr) => [...acc, ...curr], []) + .map((didKey) => DidKey.fromDid(didKey).key.fingerprint) + const oobRecords = await oobRepository.findByQuery({ invitationId: oldInvitation.id, - recipientKeyFingerprints: outOfBandInvitation.getRecipientKeys().map((key) => key.fingerprint), + recipientKeyFingerprints, }) let oobRecord: OutOfBandRecord | undefined = oobRecords[0] @@ -335,6 +342,7 @@ export async function migrateToOobRecord( reusable: oldMultiUseInvitation, mediatorId: connectionRecord.mediatorId, createdAt: connectionRecord.createdAt, + tags: { recipientKeyFingerprints }, }) await oobRepository.save(oobRecord) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 482a446aca..6b29421d2f 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,8 +1,8 @@ import type { AgentMessage } from './agent/AgentMessage' -import type { ResolvedDidCommService } from './agent/MessageSender' import type { Logger } from './logger' import type { ConnectionRecord } from './modules/connections' import type { AutoAcceptCredential } from './modules/credentials/models/CredentialAutoAcceptType' +import type { ResolvedDidCommService } from './modules/didcomm' import type { Key } from './modules/dids/domain/Key' import type { IndyPoolConfig } from './modules/ledger/IndyPool' import type { OutOfBandRecord } from './modules/oob/repository' diff --git a/packages/core/src/utils/validators.ts b/packages/core/src/utils/validators.ts index 3a822fdca9..6a5dccd528 100644 --- a/packages/core/src/utils/validators.ts +++ b/packages/core/src/utils/validators.ts @@ -4,12 +4,12 @@ import type { ValidationOptions } from 'class-validator' import { isString, ValidateBy, isInstance, buildMessage } from 'class-validator' /** - * Checks if the value is an instance of the specified object. + * Checks if the value is a string or the specified instance */ export function IsStringOrInstance(targetType: Constructor, validationOptions?: ValidationOptions): PropertyDecorator { return ValidateBy( { - name: 'isStringOrVerificationMethod', + name: 'IsStringOrInstance', constraints: [targetType], validator: { validate: (value, args): boolean => isString(value) || isInstance(value, args?.constraints[0]), @@ -17,9 +17,7 @@ export function IsStringOrInstance(targetType: Constructor, validationOptions?: if (args?.constraints[0]) { return eachPrefix + `$property must be of type string or instance of ${args.constraints[0].name as string}` } else { - return ( - eachPrefix + `isStringOrVerificationMethod decorator expects and object as value, but got falsy value.` - ) + return eachPrefix + `IsStringOrInstance decorator expects an object as value, but got falsy value.` } }, validationOptions), }, diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index d021344fb8..7124b7ddc7 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -275,7 +275,9 @@ export function getMockConnection({ export function getMockOutOfBand({ label, serviceEndpoint, - recipientKeys, + recipientKeys = [ + new DidKey(Key.fromPublicKeyBase58('ByHnpUCFb1vAfh9CFZ8ZkmUZguURW8nSw889hy6rD8L7', KeyType.Ed25519)).did, + ], mediatorId, role, state, @@ -303,9 +305,7 @@ export function getMockOutOfBand({ id: `#inline-0`, priority: 0, serviceEndpoint: serviceEndpoint ?? 'http://example.com', - recipientKeys: recipientKeys || [ - new DidKey(Key.fromPublicKeyBase58('ByHnpUCFb1vAfh9CFZ8ZkmUZguURW8nSw889hy6rD8L7', KeyType.Ed25519)).did, - ], + recipientKeys, routingKeys: [], }), ], @@ -318,6 +318,9 @@ export function getMockOutOfBand({ outOfBandInvitation: outOfBandInvitation, reusable, reuseConnectionId, + tags: { + recipientKeyFingerprints: recipientKeys.map((didKey) => DidKey.fromDid(didKey).key.fingerprint), + }, }) return outOfBandRecord } diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index b256a4849e..467532e64a 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -172,7 +172,7 @@ describe('out of band', () => { expect(outOfBandInvitation.getRequests()).toBeUndefined() // expect contains services - const [service] = outOfBandInvitation.services as OutOfBandDidCommService[] + const [service] = outOfBandInvitation.getInlineServices() expect(service).toMatchObject( new OutOfBandDidCommService({ id: expect.any(String), @@ -196,7 +196,7 @@ describe('out of band', () => { expect(outOfBandInvitation.getRequests()).toHaveLength(1) // expect contains services - const [service] = outOfBandInvitation.services + const [service] = outOfBandInvitation.getServices() expect(service).toMatchObject( new OutOfBandDidCommService({ id: expect.any(String), @@ -220,7 +220,7 @@ describe('out of band', () => { expect(outOfBandInvitation.getRequests()).toHaveLength(1) // expect contains services - const [service] = outOfBandInvitation.services as OutOfBandDidCommService[] + const [service] = outOfBandInvitation.getInlineServices() expect(service).toMatchObject( new OutOfBandDidCommService({ id: expect.any(String), @@ -467,8 +467,8 @@ describe('out of band', () => { const outOfBandRecord2 = await faberAgent.oob.createInvitation(makeConnectionConfig) // Take over the recipientKeys from the first invitation so they match when encoded - const firstInvitationService = outOfBandRecord.outOfBandInvitation.services[0] as OutOfBandDidCommService - const secondInvitationService = outOfBandRecord2.outOfBandInvitation.services[0] as OutOfBandDidCommService + const [firstInvitationService] = outOfBandRecord.outOfBandInvitation.getInlineServices() + const [secondInvitationService] = outOfBandRecord2.outOfBandInvitation.getInlineServices() secondInvitationService.recipientKeys = firstInvitationService.recipientKeys aliceAgent.events.on(OutOfBandEventTypes.HandshakeReused, aliceReuseListener) @@ -680,19 +680,6 @@ describe('out of band', () => { new AriesFrameworkError('There is no message in requests~attach supported by agent.') ) }) - - test('throw an error when a did is used in the out of band message', async () => { - const { message } = await faberAgent.credentials.createOffer(credentialTemplate) - const { outOfBandInvitation } = await faberAgent.oob.createInvitation({ - ...issueCredentialConfig, - messages: [message], - }) - outOfBandInvitation.services = ['somedid'] - - await expect(aliceAgent.oob.receiveInvitation(outOfBandInvitation, receiveInvitationConfig)).rejects.toEqual( - new AriesFrameworkError('Dids are not currently supported in out-of-band invitation services attribute.') - ) - }) }) describe('createLegacyConnectionlessInvitation', () => { From 69d4906a0ceb8a311ca6bdad5ed6d2048335109a Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Tue, 30 Aug 2022 05:11:37 -0300 Subject: [PATCH 023/125] feat(routing): manual mediator pickup lifecycle management (#989) Signed-off-by: Ariel Gentile --- .../src/modules/routing/RecipientModule.ts | 95 +++++++++++++------ .../routing/__tests__/mediation.test.ts | 7 -- .../modules/routing/__tests__/pickup.test.ts | 8 +- .../core/tests/connectionless-proofs.test.ts | 9 +- tests/e2e-test.ts | 4 +- 5 files changed, 71 insertions(+), 52 deletions(-) diff --git a/packages/core/src/modules/routing/RecipientModule.ts b/packages/core/src/modules/routing/RecipientModule.ts index c7801b26c1..c4b1f6aec2 100644 --- a/packages/core/src/modules/routing/RecipientModule.ts +++ b/packages/core/src/modules/routing/RecipientModule.ts @@ -7,7 +7,7 @@ import type { MediationStateChangedEvent } from './RoutingEvents' import type { MediationRecord } from './index' import type { GetRoutingOptions } from './services/RoutingService' -import { firstValueFrom, interval, ReplaySubject, timer } from 'rxjs' +import { firstValueFrom, interval, merge, ReplaySubject, Subject, timer } from 'rxjs' import { filter, first, takeUntil, throttleTime, timeout, tap, delayWhen } from 'rxjs/operators' import { AgentConfig } from '../../agent/AgentConfig' @@ -47,6 +47,9 @@ export class RecipientModule { private mediationRepository: MediationRepository private routingService: RoutingService + // stopMessagePickup$ is used for stop message pickup signal + private readonly stopMessagePickup$ = new Subject() + public constructor( dispatcher: Dispatcher, agentConfig: AgentConfig, @@ -144,11 +147,13 @@ export class RecipientModule { // in a recursive back off strategy if it matches the following criteria: // - Agent is not shutdown // - Socket was for current mediator connection id + + const stopConditions$ = merge(this.agentConfig.stop$, this.stopMessagePickup$).pipe() this.eventEmitter .observable(TransportEventTypes.OutboundWebSocketClosedEvent) .pipe( - // Stop when the agent shuts down - takeUntil(this.agentConfig.stop$), + // Stop when the agent shuts down or stop message pickup signal is received + takeUntil(stopConditions$), filter((e) => e.payload.connectionId === mediator.connectionId), // Make sure we're not reconnecting multiple times throttleTime(interval), @@ -157,20 +162,23 @@ export class RecipientModule { // Wait for interval time before reconnecting delayWhen(() => timer(interval)) ) - .subscribe(async () => { - this.logger.debug( - `Websocket connection to mediator with connectionId '${mediator.connectionId}' is closed, attempting to reconnect...` - ) - try { - if (pickupStrategy === MediatorPickupStrategy.PickUpV2) { - // Start Pickup v2 protocol to receive messages received while websocket offline - await this.sendStatusRequest({ mediatorId: mediator.id }) - } else { - await this.openMediationWebSocket(mediator) + .subscribe({ + next: async () => { + this.logger.debug( + `Websocket connection to mediator with connectionId '${mediator.connectionId}' is closed, attempting to reconnect...` + ) + try { + if (pickupStrategy === MediatorPickupStrategy.PickUpV2) { + // Start Pickup v2 protocol to receive messages received while websocket offline + await this.sendStatusRequest({ mediatorId: mediator.id }) + } else { + await this.openMediationWebSocket(mediator) + } + } catch (error) { + this.logger.warn('Unable to re-open websocket connection to mediator', { error }) } - } catch (error) { - this.logger.warn('Unable to re-open websocket connection to mediator', { error }) - } + }, + complete: () => this.agentConfig.logger.info(`Stopping pickup of messages from mediator '${mediator.id}'`), }) try { if (pickupStrategy === MediatorPickupStrategy.Implicit) { @@ -181,40 +189,69 @@ export class RecipientModule { } } - public async initiateMessagePickup(mediator: MediationRecord) { + /** + * Start a Message Pickup flow with a registered Mediator. + * + * @param mediator optional {MediationRecord} corresponding to the mediator to pick messages from. It will use + * default mediator otherwise + * @param pickupStrategy optional {MediatorPickupStrategy} to use in the loop. It will use Agent's default + * strategy or attempt to find it by Discover Features otherwise + * @returns + */ + public async initiateMessagePickup(mediator?: MediationRecord, pickupStrategy?: MediatorPickupStrategy) { const { mediatorPollingInterval } = this.agentConfig - const mediatorPickupStrategy = await this.getPickupStrategyForMediator(mediator) - const mediatorConnection = await this.connectionService.getById(mediator.connectionId) + + const mediatorRecord = mediator ?? (await this.findDefaultMediator()) + if (!mediatorRecord) { + throw new AriesFrameworkError('There is no mediator to pickup messages from') + } + + const mediatorPickupStrategy = pickupStrategy ?? (await this.getPickupStrategyForMediator(mediatorRecord)) + const mediatorConnection = await this.connectionService.getById(mediatorRecord.connectionId) switch (mediatorPickupStrategy) { case MediatorPickupStrategy.PickUpV2: - this.agentConfig.logger.info(`Starting pickup of messages from mediator '${mediator.id}'`) - await this.openWebSocketAndPickUp(mediator, mediatorPickupStrategy) - await this.sendStatusRequest({ mediatorId: mediator.id }) + this.agentConfig.logger.info(`Starting pickup of messages from mediator '${mediatorRecord.id}'`) + await this.openWebSocketAndPickUp(mediatorRecord, mediatorPickupStrategy) + await this.sendStatusRequest({ mediatorId: mediatorRecord.id }) break case MediatorPickupStrategy.PickUpV1: { + const stopConditions$ = merge(this.agentConfig.stop$, this.stopMessagePickup$).pipe() // Explicit means polling every X seconds with batch message - this.agentConfig.logger.info(`Starting explicit (batch) pickup of messages from mediator '${mediator.id}'`) + this.agentConfig.logger.info( + `Starting explicit (batch) pickup of messages from mediator '${mediatorRecord.id}'` + ) const subscription = interval(mediatorPollingInterval) - .pipe(takeUntil(this.agentConfig.stop$)) - .subscribe(async () => { - await this.pickupMessages(mediatorConnection) + .pipe(takeUntil(stopConditions$)) + .subscribe({ + next: async () => { + await this.pickupMessages(mediatorConnection) + }, + complete: () => + this.agentConfig.logger.info(`Stopping pickup of messages from mediator '${mediatorRecord.id}'`), }) return subscription } case MediatorPickupStrategy.Implicit: // Implicit means sending ping once and keeping connection open. This requires a long-lived transport // such as WebSockets to work - this.agentConfig.logger.info(`Starting implicit pickup of messages from mediator '${mediator.id}'`) - await this.openWebSocketAndPickUp(mediator, mediatorPickupStrategy) + this.agentConfig.logger.info(`Starting implicit pickup of messages from mediator '${mediatorRecord.id}'`) + await this.openWebSocketAndPickUp(mediatorRecord, mediatorPickupStrategy) break default: this.agentConfig.logger.info( - `Skipping pickup of messages from mediator '${mediator.id}' due to pickup strategy none` + `Skipping pickup of messages from mediator '${mediatorRecord.id}' due to pickup strategy none` ) } } + /** + * Terminate all ongoing Message Pickup loops + */ + public async stopMessagePickup() { + this.stopMessagePickup$.next(true) + } + private async sendStatusRequest(config: { mediatorId: string; recipientKey?: string }) { const mediationRecord = await this.mediationRecipientService.getById(config.mediatorId) diff --git a/packages/core/src/modules/routing/__tests__/mediation.test.ts b/packages/core/src/modules/routing/__tests__/mediation.test.ts index 42265474fa..7c77711ed0 100644 --- a/packages/core/src/modules/routing/__tests__/mediation.test.ts +++ b/packages/core/src/modules/routing/__tests__/mediation.test.ts @@ -7,7 +7,6 @@ import { SubjectInboundTransport } from '../../../../../../tests/transport/Subje import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' import { getBaseConfig, waitForBasicMessage } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' -import { sleep } from '../../../utils/sleep' import { ConnectionRecord, HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' import { MediationState } from '../models/MediationState' @@ -32,12 +31,6 @@ describe('mediator establishment', () => { let senderAgent: Agent afterEach(async () => { - // We want to stop the mediator polling before the agent is shutdown. - // FIXME: add a way to stop mediator polling from the public api, and make sure this is - // being handled in the agent shutdown so we don't get any errors with wallets being closed. - recipientAgent.config.stop$.next(true) - await sleep(1000) - await recipientAgent?.shutdown() await recipientAgent?.wallet.delete() await mediatorAgent?.shutdown() diff --git a/packages/core/src/modules/routing/__tests__/pickup.test.ts b/packages/core/src/modules/routing/__tests__/pickup.test.ts index 0869fd5c53..5f4dc53f71 100644 --- a/packages/core/src/modules/routing/__tests__/pickup.test.ts +++ b/packages/core/src/modules/routing/__tests__/pickup.test.ts @@ -7,21 +7,17 @@ import { SubjectInboundTransport } from '../../../../../../tests/transport/Subje import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' import { getBaseConfig, waitForBasicMessage } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' -import { ConsoleLogger, LogLevel } from '../../../logger' import { HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' -const logger = new ConsoleLogger(LogLevel.info) -const recipientConfig = getBaseConfig('Mediation: Recipient', { +const recipientConfig = getBaseConfig('Pickup: Recipient', { autoAcceptConnections: true, indyLedgers: [], - logger, }) -const mediatorConfig = getBaseConfig('Mediation: Mediator', { +const mediatorConfig = getBaseConfig('Pickup: Mediator', { autoAcceptConnections: true, endpoints: ['rxjs:mediator'], indyLedgers: [], - logger, }) describe('E2E Pick Up protocol', () => { diff --git a/packages/core/tests/connectionless-proofs.test.ts b/packages/core/tests/connectionless-proofs.test.ts index b38a30c36b..d3b18cae8b 100644 --- a/packages/core/tests/connectionless-proofs.test.ts +++ b/packages/core/tests/connectionless-proofs.test.ts @@ -20,7 +20,6 @@ import { } from '../src/modules/proofs' import { MediatorPickupStrategy } from '../src/modules/routing' import { LinkedAttachment } from '../src/utils/LinkedAttachment' -import { sleep } from '../src/utils/sleep' import { uuid } from '../src/utils/uuid' import { @@ -342,11 +341,7 @@ describe('Present Proof', () => { state: ProofState.Done, }) - // We want to stop the mediator polling before the agent is shutdown. - // FIXME: add a way to stop mediator polling from the public api, and make sure this is - // being handled in the agent shutdown so we don't get any errors with wallets being closed. - faberAgent.config.stop$.next(true) - aliceAgent.config.stop$.next(true) - await sleep(2000) + await aliceAgent.mediationRecipient.stopMessagePickup() + await faberAgent.mediationRecipient.stopMessagePickup() }) }) diff --git a/tests/e2e-test.ts b/tests/e2e-test.ts index d074e0aa6c..86fb6dfe83 100644 --- a/tests/e2e-test.ts +++ b/tests/e2e-test.ts @@ -93,8 +93,6 @@ export async function e2eTest({ expect(verifierProof.state).toBe(ProofState.Done) // We want to stop the mediator polling before the agent is shutdown. - // FIXME: add a way to stop mediator polling from the public api, and make sure this is - // being handled in the agent shutdown so we don't get any errors with wallets being closed. - recipientAgent.config.stop$.next(true) + await recipientAgent.mediationRecipient.stopMessagePickup() await sleep(2000) } From 4ef8f79d861e42cd750527911a5ad788a81c2dcc Mon Sep 17 00:00:00 2001 From: conanoc Date: Tue, 30 Aug 2022 17:43:04 +0900 Subject: [PATCH 024/125] docs(demo): faber creates invitation (#995) Signed-off-by: conanoc --- demo/src/Alice.ts | 85 ++++++++------------------------------- demo/src/AliceInquirer.ts | 12 +++--- demo/src/Faber.ts | 84 ++++++++++++++++++++++++++++++-------- demo/src/FaberInquirer.ts | 14 +++---- demo/src/OutputClass.ts | 2 +- 5 files changed, 98 insertions(+), 99 deletions(-) diff --git a/demo/src/Alice.ts b/demo/src/Alice.ts index 359cd7ac31..21a3b8fd50 100644 --- a/demo/src/Alice.ts +++ b/demo/src/Alice.ts @@ -1,18 +1,11 @@ -import type { - ConnectionRecord, - ConnectionStateChangedEvent, - CredentialExchangeRecord, - ProofRecord, -} from '@aries-framework/core' - -import { ConnectionEventTypes } from '@aries-framework/core' +import type { ConnectionRecord, CredentialExchangeRecord, ProofRecord } from '@aries-framework/core' import { BaseAgent } from './BaseAgent' import { greenText, Output, redText } from './OutputClass' export class Alice extends BaseAgent { - public outOfBandId?: string public connected: boolean + public connectionRecordFaberId?: string public constructor(port: number, name: string) { super(port, name) @@ -26,74 +19,30 @@ export class Alice extends BaseAgent { } private async getConnectionRecord() { - if (!this.outOfBandId) { - throw Error(redText(Output.MissingConnectionRecord)) - } - - const [connection] = await this.agent.connections.findAllByOutOfBandId(this.outOfBandId) - - if (!connection) { + if (!this.connectionRecordFaberId) { throw Error(redText(Output.MissingConnectionRecord)) } - - return connection + return await this.agent.connections.getById(this.connectionRecordFaberId) } - private async printConnectionInvite() { - const outOfBand = await this.agent.oob.createInvitation() - this.outOfBandId = outOfBand.id - - console.log( - Output.ConnectionLink, - outOfBand.outOfBandInvitation.toUrl({ domain: `http://localhost:${this.port}` }), - '\n' - ) - } - - private async waitForConnection() { - if (!this.outOfBandId) { - throw new Error(redText(Output.MissingConnectionRecord)) + private async receiveConnectionRequest(invitationUrl: string) { + const { connectionRecord } = await this.agent.oob.receiveInvitationFromUrl(invitationUrl) + if (!connectionRecord) { + throw new Error(redText(Output.NoConnectionRecordFromOutOfBand)) } + return connectionRecord + } - console.log('Waiting for Faber to finish connection...') - - const getConnectionRecord = (outOfBandId: string) => - new Promise((resolve, reject) => { - // Timeout of 20 seconds - const timeoutId = setTimeout(() => reject(new Error(redText(Output.MissingConnectionRecord))), 20000) - - // Start listener - this.agent.events.on(ConnectionEventTypes.ConnectionStateChanged, (e) => { - if (e.payload.connectionRecord.outOfBandId !== outOfBandId) return - - clearTimeout(timeoutId) - resolve(e.payload.connectionRecord) - }) - - // Also retrieve the connection record by invitation if the event has already fired - void this.agent.connections.findAllByOutOfBandId(outOfBandId).then(([connectionRecord]) => { - if (connectionRecord) { - clearTimeout(timeoutId) - resolve(connectionRecord) - } - }) - }) - - const connectionRecord = await getConnectionRecord(this.outOfBandId) - - try { - await this.agent.connections.returnWhenIsConnected(connectionRecord.id) - } catch (e) { - console.log(redText(`\nTimeout of 20 seconds reached.. Returning to home screen.\n`)) - return - } - console.log(greenText(Output.ConnectionEstablished)) + private async waitForConnection(connectionRecord: ConnectionRecord) { + connectionRecord = await this.agent.connections.returnWhenIsConnected(connectionRecord.id) this.connected = true + console.log(greenText(Output.ConnectionEstablished)) + return connectionRecord.id } - public async setupConnection() { - await this.printConnectionInvite() - await this.waitForConnection() + public async acceptConnection(invitation_url: string) { + const connectionRecord = await this.receiveConnectionRequest(invitation_url) + this.connectionRecordFaberId = await this.waitForConnection(connectionRecord) } public async acceptCredentialOffer(credentialRecord: CredentialExchangeRecord) { diff --git a/demo/src/AliceInquirer.ts b/demo/src/AliceInquirer.ts index 9f82717246..457d33b528 100644 --- a/demo/src/AliceInquirer.ts +++ b/demo/src/AliceInquirer.ts @@ -17,7 +17,7 @@ export const runAlice = async () => { } enum PromptOptions { - CreateConnection = 'Create connection invitation', + ReceiveConnectionUrl = 'Receive connection invitation', SendMessage = 'Send message', Exit = 'Exit', Restart = 'Restart', @@ -42,9 +42,9 @@ export class AliceInquirer extends BaseInquirer { } private async getPromptChoice() { - if (this.alice.outOfBandId) return inquirer.prompt([this.inquireOptions(this.promptOptionsString)]) + if (this.alice.connectionRecordFaberId) return inquirer.prompt([this.inquireOptions(this.promptOptionsString)]) - const reducedOption = [PromptOptions.CreateConnection, PromptOptions.Exit, PromptOptions.Restart] + const reducedOption = [PromptOptions.ReceiveConnectionUrl, PromptOptions.Exit, PromptOptions.Restart] return inquirer.prompt([this.inquireOptions(reducedOption)]) } @@ -53,7 +53,7 @@ export class AliceInquirer extends BaseInquirer { if (this.listener.on) return switch (choice.options) { - case PromptOptions.CreateConnection: + case PromptOptions.ReceiveConnectionUrl: await this.connection() break case PromptOptions.SendMessage: @@ -88,7 +88,9 @@ export class AliceInquirer extends BaseInquirer { } public async connection() { - await this.alice.setupConnection() + const title = Title.InvitationTitle + const getUrl = await inquirer.prompt([this.inquireInput(title)]) + await this.alice.acceptConnection(getUrl.input) if (!this.alice.connected) return this.listener.credentialOfferListener(this.alice, this) diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index c5b23058e6..671e24e3c4 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -1,15 +1,21 @@ -import type { ConnectionRecord } from '@aries-framework/core' +import type { ConnectionRecord, ConnectionStateChangedEvent } from '@aries-framework/core' import type { CredDef, Schema } from 'indy-sdk' import type BottomBar from 'inquirer/lib/ui/bottom-bar' -import { V1CredentialPreview, AttributeFilter, ProofAttributeInfo, utils } from '@aries-framework/core' +import { + V1CredentialPreview, + AttributeFilter, + ProofAttributeInfo, + utils, + ConnectionEventTypes, +} from '@aries-framework/core' import { ui } from 'inquirer' import { BaseAgent } from './BaseAgent' import { Color, greenText, Output, purpleText, redText } from './OutputClass' export class Faber extends BaseAgent { - public connectionRecordAliceId?: string + public outOfBandId?: string public credentialDefinition?: CredDef public ui: BottomBar @@ -25,29 +31,73 @@ export class Faber extends BaseAgent { } private async getConnectionRecord() { - if (!this.connectionRecordAliceId) { + if (!this.outOfBandId) { throw Error(redText(Output.MissingConnectionRecord)) } - return await this.agent.connections.getById(this.connectionRecordAliceId) - } - private async receiveConnectionRequest(invitationUrl: string) { - const { connectionRecord } = await this.agent.oob.receiveInvitationFromUrl(invitationUrl) - if (!connectionRecord) { - throw new Error(redText(Output.NoConnectionRecordFromOutOfBand)) + const [connection] = await this.agent.connections.findAllByOutOfBandId(this.outOfBandId) + + if (!connection) { + throw Error(redText(Output.MissingConnectionRecord)) } - return connectionRecord + + return connection } - private async waitForConnection(connectionRecord: ConnectionRecord) { - connectionRecord = await this.agent.connections.returnWhenIsConnected(connectionRecord.id) + private async printConnectionInvite() { + const outOfBand = await this.agent.oob.createInvitation() + this.outOfBandId = outOfBand.id + + console.log( + Output.ConnectionLink, + outOfBand.outOfBandInvitation.toUrl({ domain: `http://localhost:${this.port}` }), + '\n' + ) + } + + private async waitForConnection() { + if (!this.outOfBandId) { + throw new Error(redText(Output.MissingConnectionRecord)) + } + + console.log('Waiting for Alice to finish connection...') + + const getConnectionRecord = (outOfBandId: string) => + new Promise((resolve, reject) => { + // Timeout of 20 seconds + const timeoutId = setTimeout(() => reject(new Error(redText(Output.MissingConnectionRecord))), 20000) + + // Start listener + this.agent.events.on(ConnectionEventTypes.ConnectionStateChanged, (e) => { + if (e.payload.connectionRecord.outOfBandId !== outOfBandId) return + + clearTimeout(timeoutId) + resolve(e.payload.connectionRecord) + }) + + // Also retrieve the connection record by invitation if the event has already fired + void this.agent.connections.findAllByOutOfBandId(outOfBandId).then(([connectionRecord]) => { + if (connectionRecord) { + clearTimeout(timeoutId) + resolve(connectionRecord) + } + }) + }) + + const connectionRecord = await getConnectionRecord(this.outOfBandId) + + try { + await this.agent.connections.returnWhenIsConnected(connectionRecord.id) + } catch (e) { + console.log(redText(`\nTimeout of 20 seconds reached.. Returning to home screen.\n`)) + return + } console.log(greenText(Output.ConnectionEstablished)) - return connectionRecord.id } - public async acceptConnection(invitation_url: string) { - const connectionRecord = await this.receiveConnectionRequest(invitation_url) - this.connectionRecordAliceId = await this.waitForConnection(connectionRecord) + public async setupConnection() { + await this.printConnectionInvite() + await this.waitForConnection() } private printSchema(name: string, version: string, attributes: string[]) { diff --git a/demo/src/FaberInquirer.ts b/demo/src/FaberInquirer.ts index a61ec60175..98c1ccabb6 100644 --- a/demo/src/FaberInquirer.ts +++ b/demo/src/FaberInquirer.ts @@ -15,7 +15,7 @@ export const runFaber = async () => { } enum PromptOptions { - ReceiveConnectionUrl = 'Receive connection invitation', + CreateConnection = 'Create connection invitation', OfferCredential = 'Offer credential', RequestProof = 'Request proof', SendMessage = 'Send message', @@ -42,9 +42,9 @@ export class FaberInquirer extends BaseInquirer { } private async getPromptChoice() { - if (this.faber.connectionRecordAliceId) return inquirer.prompt([this.inquireOptions(this.promptOptionsString)]) + if (this.faber.outOfBandId) return inquirer.prompt([this.inquireOptions(this.promptOptionsString)]) - const reducedOption = [PromptOptions.ReceiveConnectionUrl, PromptOptions.Exit, PromptOptions.Restart] + const reducedOption = [PromptOptions.CreateConnection, PromptOptions.Exit, PromptOptions.Restart] return inquirer.prompt([this.inquireOptions(reducedOption)]) } @@ -53,7 +53,7 @@ export class FaberInquirer extends BaseInquirer { if (this.listener.on) return switch (choice.options) { - case PromptOptions.ReceiveConnectionUrl: + case PromptOptions.CreateConnection: await this.connection() break case PromptOptions.OfferCredential: @@ -76,9 +76,7 @@ export class FaberInquirer extends BaseInquirer { } public async connection() { - const title = Title.InvitationTitle - const getUrl = await inquirer.prompt([this.inquireInput(title)]) - await this.faber.acceptConnection(getUrl.input) + await this.faber.setupConnection() } public async exitUseCase(title: string) { @@ -104,7 +102,7 @@ export class FaberInquirer extends BaseInquirer { public async message() { const message = await this.inquireMessage() - if (message) return + if (!message) return await this.faber.sendMessage(message) } diff --git a/demo/src/OutputClass.ts b/demo/src/OutputClass.ts index 3d7b9ebff3..b9e69c72f0 100644 --- a/demo/src/OutputClass.ts +++ b/demo/src/OutputClass.ts @@ -9,7 +9,7 @@ export enum Output { NoConnectionRecordFromOutOfBand = `\nNo connectionRecord has been created from invitation\n`, ConnectionEstablished = `\nConnection established!`, MissingConnectionRecord = `\nNo connectionRecord ID has been set yet\n`, - ConnectionLink = `\nRun 'Receive connection invitation' in Faber and paste this invitation link:\n\n`, + ConnectionLink = `\nRun 'Receive connection invitation' in Alice and paste this invitation link:\n\n`, Exit = 'Shutting down agent...\nExiting...', } From f487182d3836d5e286b70b21220fff045d37a945 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 30 Aug 2022 14:30:13 +0200 Subject: [PATCH 025/125] chore(release): v0.2.3 (#999) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 19 +++++++++++++++++++ lerna.json | 2 +- packages/core/CHANGELOG.md | 19 +++++++++++++++++++ packages/core/package.json | 2 +- packages/node/CHANGELOG.md | 4 ++++ packages/node/package.json | 4 ++-- packages/react-native/CHANGELOG.md | 4 ++++ packages/react-native/package.json | 4 ++-- 8 files changed, 52 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02b95e4682..b4c354c278 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.2...v0.2.3) (2022-08-30) + +### Bug Fixes + +- export the KeyDerivationMethod ([#958](https://github.com/hyperledger/aries-framework-javascript/issues/958)) ([04ab1cc](https://github.com/hyperledger/aries-framework-javascript/commit/04ab1cca853284d144fd64d35e26e9dfe77d4a1b)) +- expose oob domain ([#990](https://github.com/hyperledger/aries-framework-javascript/issues/990)) ([dad975d](https://github.com/hyperledger/aries-framework-javascript/commit/dad975d9d9b658c6b37749ece2a91381e2a314c9)) +- **generic-records:** support custom id property ([#964](https://github.com/hyperledger/aries-framework-javascript/issues/964)) ([0f690a0](https://github.com/hyperledger/aries-framework-javascript/commit/0f690a0564a25204cacfae7cd958f660f777567e)) + +### Features + +- always initialize mediator ([#985](https://github.com/hyperledger/aries-framework-javascript/issues/985)) ([b699977](https://github.com/hyperledger/aries-framework-javascript/commit/b69997744ac9e30ffba22daac7789216d2683e36)) +- delete by record id ([#983](https://github.com/hyperledger/aries-framework-javascript/issues/983)) ([d8a30d9](https://github.com/hyperledger/aries-framework-javascript/commit/d8a30d94d336cf3417c2cd00a8110185dde6a106)) +- **ledger:** handle REQNACK response for write request ([#967](https://github.com/hyperledger/aries-framework-javascript/issues/967)) ([6468a93](https://github.com/hyperledger/aries-framework-javascript/commit/6468a9311c8458615871e1e85ba3f3b560453715)) +- OOB public did ([#930](https://github.com/hyperledger/aries-framework-javascript/issues/930)) ([c99f3c9](https://github.com/hyperledger/aries-framework-javascript/commit/c99f3c9152a79ca6a0a24fdc93e7f3bebbb9d084)) +- **proofs:** present proof as nested protocol ([#972](https://github.com/hyperledger/aries-framework-javascript/issues/972)) ([52247d9](https://github.com/hyperledger/aries-framework-javascript/commit/52247d997c5910924d3099c736dd2e20ec86a214)) +- **routing:** manual mediator pickup lifecycle management ([#989](https://github.com/hyperledger/aries-framework-javascript/issues/989)) ([69d4906](https://github.com/hyperledger/aries-framework-javascript/commit/69d4906a0ceb8a311ca6bdad5ed6d2048335109a)) +- **routing:** pickup v2 mediator role basic implementation ([#975](https://github.com/hyperledger/aries-framework-javascript/issues/975)) ([a989556](https://github.com/hyperledger/aries-framework-javascript/commit/a98955666853471d504f8a5c8c4623e18ba8c8ed)) +- **routing:** support promise in message repo ([#959](https://github.com/hyperledger/aries-framework-javascript/issues/959)) ([79c5d8d](https://github.com/hyperledger/aries-framework-javascript/commit/79c5d8d76512b641167bce46e82f34cf22bc285e)) + ## [0.2.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.1...v0.2.2) (2022-07-15) ### Bug Fixes diff --git a/lerna.json b/lerna.json index 6e0c665e15..cfe7bf4adb 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.2.2", + "version": "0.2.3", "useWorkspaces": true, "npmClient": "yarn", "command": { diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index dc994f8cc0..6c088a2e42 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.2...v0.2.3) (2022-08-30) + +### Bug Fixes + +- export the KeyDerivationMethod ([#958](https://github.com/hyperledger/aries-framework-javascript/issues/958)) ([04ab1cc](https://github.com/hyperledger/aries-framework-javascript/commit/04ab1cca853284d144fd64d35e26e9dfe77d4a1b)) +- expose oob domain ([#990](https://github.com/hyperledger/aries-framework-javascript/issues/990)) ([dad975d](https://github.com/hyperledger/aries-framework-javascript/commit/dad975d9d9b658c6b37749ece2a91381e2a314c9)) +- **generic-records:** support custom id property ([#964](https://github.com/hyperledger/aries-framework-javascript/issues/964)) ([0f690a0](https://github.com/hyperledger/aries-framework-javascript/commit/0f690a0564a25204cacfae7cd958f660f777567e)) + +### Features + +- always initialize mediator ([#985](https://github.com/hyperledger/aries-framework-javascript/issues/985)) ([b699977](https://github.com/hyperledger/aries-framework-javascript/commit/b69997744ac9e30ffba22daac7789216d2683e36)) +- delete by record id ([#983](https://github.com/hyperledger/aries-framework-javascript/issues/983)) ([d8a30d9](https://github.com/hyperledger/aries-framework-javascript/commit/d8a30d94d336cf3417c2cd00a8110185dde6a106)) +- **ledger:** handle REQNACK response for write request ([#967](https://github.com/hyperledger/aries-framework-javascript/issues/967)) ([6468a93](https://github.com/hyperledger/aries-framework-javascript/commit/6468a9311c8458615871e1e85ba3f3b560453715)) +- OOB public did ([#930](https://github.com/hyperledger/aries-framework-javascript/issues/930)) ([c99f3c9](https://github.com/hyperledger/aries-framework-javascript/commit/c99f3c9152a79ca6a0a24fdc93e7f3bebbb9d084)) +- **proofs:** present proof as nested protocol ([#972](https://github.com/hyperledger/aries-framework-javascript/issues/972)) ([52247d9](https://github.com/hyperledger/aries-framework-javascript/commit/52247d997c5910924d3099c736dd2e20ec86a214)) +- **routing:** manual mediator pickup lifecycle management ([#989](https://github.com/hyperledger/aries-framework-javascript/issues/989)) ([69d4906](https://github.com/hyperledger/aries-framework-javascript/commit/69d4906a0ceb8a311ca6bdad5ed6d2048335109a)) +- **routing:** pickup v2 mediator role basic implementation ([#975](https://github.com/hyperledger/aries-framework-javascript/issues/975)) ([a989556](https://github.com/hyperledger/aries-framework-javascript/commit/a98955666853471d504f8a5c8c4623e18ba8c8ed)) +- **routing:** support promise in message repo ([#959](https://github.com/hyperledger/aries-framework-javascript/issues/959)) ([79c5d8d](https://github.com/hyperledger/aries-framework-javascript/commit/79c5d8d76512b641167bce46e82f34cf22bc285e)) + ## [0.2.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.1...v0.2.2) (2022-07-15) ### Bug Fixes diff --git a/packages/core/package.json b/packages/core/package.json index 70865022be..7ba5851665 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/core", "main": "build/index", "types": "build/index", - "version": "0.2.2", + "version": "0.2.3", "files": [ "build" ], diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index 2f19ede363..1ec6c5e21e 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.2...v0.2.3) (2022-08-30) + +**Note:** Version bump only for package @aries-framework/node + ## [0.2.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.1...v0.2.2) (2022-07-15) **Note:** Version bump only for package @aries-framework/node diff --git a/packages/node/package.json b/packages/node/package.json index d7cb789c2c..edc898820e 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/node", "main": "build/index", "types": "build/index", - "version": "0.2.2", + "version": "0.2.3", "files": [ "build", "bin" @@ -28,7 +28,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.2", + "@aries-framework/core": "0.2.3", "express": "^4.17.1", "ffi-napi": "^4.0.3", "indy-sdk": "^1.16.0-dev-1636", diff --git a/packages/react-native/CHANGELOG.md b/packages/react-native/CHANGELOG.md index 5035d17fab..73dbc1dee3 100644 --- a/packages/react-native/CHANGELOG.md +++ b/packages/react-native/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.2...v0.2.3) (2022-08-30) + +**Note:** Version bump only for package @aries-framework/react-native + ## [0.2.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.1...v0.2.2) (2022-07-15) **Note:** Version bump only for package @aries-framework/react-native diff --git a/packages/react-native/package.json b/packages/react-native/package.json index f94520af4d..351320472c 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/react-native", "main": "build/index", "types": "build/index", - "version": "0.2.2", + "version": "0.2.3", "files": [ "build" ], @@ -24,7 +24,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.2", + "@aries-framework/core": "0.2.3", "@azure/core-asynciterator-polyfill": "^1.0.0", "events": "^3.3.0" }, From 4b90e876cc8377e7518e05445beb1a6b524840c4 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Wed, 31 Aug 2022 04:24:51 -0300 Subject: [PATCH 026/125] fix(question-answer): question answer protocol state/role check (#1001) Signed-off-by: Ariel Gentile --- .../question-answer/QuestionAnswerModule.ts | 15 +- .../__tests__/QuestionAnswerService.test.ts | 150 +++++++++++++++++- .../question-answer/__tests__/helpers.ts | 64 ++++++++ .../__tests__/question-answer.e2e.test.ts | 92 +++++++++++ .../repository/QuestionAnswerRecord.ts | 6 + .../services/QuestionAnswerService.ts | 55 +++++-- 6 files changed, 363 insertions(+), 19 deletions(-) create mode 100644 packages/core/src/modules/question-answer/__tests__/helpers.ts create mode 100644 packages/core/src/modules/question-answer/__tests__/question-answer.e2e.test.ts diff --git a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts b/packages/core/src/modules/question-answer/QuestionAnswerModule.ts index 6eac5b48d4..59350a2334 100644 --- a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts +++ b/packages/core/src/modules/question-answer/QuestionAnswerModule.ts @@ -1,5 +1,4 @@ import type { DependencyManager } from '../../plugins' -import type { ValidResponse } from './models' import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' @@ -8,6 +7,7 @@ import { injectable, module } from '../../plugins' import { ConnectionService } from '../connections' import { AnswerMessageHandler, QuestionMessageHandler } from './handlers' +import { ValidResponse } from './models' import { QuestionAnswerRepository } from './repository' import { QuestionAnswerService } from './services' @@ -51,7 +51,7 @@ export class QuestionAnswerModule { const { questionMessage, questionAnswerRecord } = await this.questionAnswerService.createQuestion(connectionId, { question: config.question, - validResponses: config.validResponses, + validResponses: config.validResponses.map((item) => new ValidResponse(item)), detail: config?.detail, }) const outboundMessage = createOutboundMessage(connection, questionMessage) @@ -92,6 +92,17 @@ export class QuestionAnswerModule { return this.questionAnswerService.getAll() } + /** + * Retrieve a question answer record by id + * + * @param questionAnswerId The questionAnswer record id + * @return The question answer record or null if not found + * + */ + public findById(questionAnswerId: string) { + return this.questionAnswerService.findById(questionAnswerId) + } + private registerHandlers(dispatcher: Dispatcher) { dispatcher.registerHandler(new QuestionMessageHandler(this.questionAnswerService)) dispatcher.registerHandler(new AnswerMessageHandler(this.questionAnswerService)) diff --git a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts index 3b7f3982a1..25bd042bd6 100644 --- a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts +++ b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts @@ -5,10 +5,12 @@ import type { ValidResponse } from '../models' import { getAgentConfig, getMockConnection, mockFunction } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' +import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import { IndyWallet } from '../../../wallet/IndyWallet' +import { DidExchangeState } from '../../connections' import { QuestionAnswerEventTypes } from '../QuestionAnswerEvents' import { QuestionAnswerRole } from '../QuestionAnswerRole' -import { QuestionMessage } from '../messages' +import { AnswerMessage, QuestionMessage } from '../messages' import { QuestionAnswerState } from '../models' import { QuestionAnswerRecord, QuestionAnswerRepository } from '../repository' import { QuestionAnswerService } from '../services' @@ -20,6 +22,7 @@ describe('QuestionAnswerService', () => { const mockConnectionRecord = getMockConnection({ id: 'd3849ac3-c981-455b-a1aa-a10bea6cead8', did: 'did:sov:C2SsBf5QUQpqSAQfhu3sd2', + state: DidExchangeState.Completed, }) let wallet: IndyWallet @@ -129,7 +132,7 @@ describe('QuestionAnswerService', () => { eventListenerMock ) - mockFunction(questionAnswerRepository.getSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + mockFunction(questionAnswerRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) await questionAnswerService.createAnswer(mockRecord, 'Yes') @@ -147,4 +150,147 @@ describe('QuestionAnswerService', () => { }) }) }) + + describe('processReceiveQuestion', () => { + let mockRecord: QuestionAnswerRecord + + beforeAll(() => { + mockRecord = mockQuestionAnswerRecord({ + questionText: 'Alice, are you on the phone with Bob?', + connectionId: mockConnectionRecord.id, + role: QuestionAnswerRole.Responder, + signatureRequired: false, + state: QuestionAnswerState.QuestionReceived, + threadId: '123', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + }) + + it('creates record when no previous question with that thread exists', async () => { + const questionMessage = new QuestionMessage({ + questionText: 'Alice, are you on the phone with Bob?', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + + const messageContext = new InboundMessageContext(questionMessage, { connection: mockConnectionRecord }) + + const questionAnswerRecord = await questionAnswerService.processReceiveQuestion(messageContext) + + expect(questionAnswerRecord).toMatchObject( + expect.objectContaining({ + role: QuestionAnswerRole.Responder, + state: QuestionAnswerState.QuestionReceived, + threadId: questionMessage.id, + questionText: 'Alice, are you on the phone with Bob?', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + ) + }) + + it(`throws an error when question from the same thread exists `, async () => { + mockFunction(questionAnswerRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + const questionMessage = new QuestionMessage({ + id: '123', + questionText: 'Alice, are you on the phone with Bob?', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + + const messageContext = new InboundMessageContext(questionMessage, { connection: mockConnectionRecord }) + + expect(questionAnswerService.processReceiveQuestion(messageContext)).rejects.toThrowError( + `Question answer record with thread Id ${questionMessage.id} already exists.` + ) + jest.resetAllMocks() + }) + }) + + describe('receiveAnswer', () => { + let mockRecord: QuestionAnswerRecord + + beforeAll(() => { + mockRecord = mockQuestionAnswerRecord({ + questionText: 'Alice, are you on the phone with Bob?', + connectionId: mockConnectionRecord.id, + role: QuestionAnswerRole.Questioner, + signatureRequired: false, + state: QuestionAnswerState.QuestionReceived, + threadId: '123', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + }) + + it('updates state and emits event when valid response is received', async () => { + mockRecord.state = QuestionAnswerState.QuestionSent + mockFunction(questionAnswerRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + const answerMessage = new AnswerMessage({ + response: 'Yes', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(answerMessage, { connection: mockConnectionRecord }) + + const questionAnswerRecord = await questionAnswerService.receiveAnswer(messageContext) + + expect(questionAnswerRecord).toMatchObject( + expect.objectContaining({ + role: QuestionAnswerRole.Questioner, + state: QuestionAnswerState.AnswerReceived, + threadId: '123', + questionText: 'Alice, are you on the phone with Bob?', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + ) + jest.resetAllMocks() + }) + + it(`throws an error when no existing question is found`, async () => { + const answerMessage = new AnswerMessage({ + response: 'Yes', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(answerMessage, { connection: mockConnectionRecord }) + + expect(questionAnswerService.receiveAnswer(messageContext)).rejects.toThrowError( + `Question Answer record with thread Id ${answerMessage.threadId} not found.` + ) + }) + + it(`throws an error when record is in invalid state`, async () => { + mockRecord.state = QuestionAnswerState.AnswerReceived + mockFunction(questionAnswerRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + const answerMessage = new AnswerMessage({ + response: 'Yes', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(answerMessage, { connection: mockConnectionRecord }) + + expect(questionAnswerService.receiveAnswer(messageContext)).rejects.toThrowError( + `Question answer record is in invalid state ${mockRecord.state}. Valid states are: ${QuestionAnswerState.QuestionSent}` + ) + jest.resetAllMocks() + }) + + it(`throws an error when record is in invalid role`, async () => { + mockRecord.state = QuestionAnswerState.QuestionSent + mockRecord.role = QuestionAnswerRole.Responder + mockFunction(questionAnswerRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + const answerMessage = new AnswerMessage({ + response: 'Yes', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(answerMessage, { connection: mockConnectionRecord }) + + expect(questionAnswerService.receiveAnswer(messageContext)).rejects.toThrowError( + `Invalid question answer record role ${mockRecord.role}, expected is ${QuestionAnswerRole.Questioner}` + ) + }) + jest.resetAllMocks() + }) }) diff --git a/packages/core/src/modules/question-answer/__tests__/helpers.ts b/packages/core/src/modules/question-answer/__tests__/helpers.ts new file mode 100644 index 0000000000..3d80e32238 --- /dev/null +++ b/packages/core/src/modules/question-answer/__tests__/helpers.ts @@ -0,0 +1,64 @@ +import type { Agent } from '../../../agent/Agent' +import type { QuestionAnswerStateChangedEvent } from '../QuestionAnswerEvents' +import type { QuestionAnswerRole } from '../QuestionAnswerRole' +import type { QuestionAnswerState } from '../models' +import type { Observable } from 'rxjs' + +import { catchError, filter, firstValueFrom, map, ReplaySubject, timeout } from 'rxjs' + +import { QuestionAnswerEventTypes } from '../QuestionAnswerEvents' + +export async function waitForQuestionAnswerRecord( + agent: Agent, + options: { + threadId?: string + role?: QuestionAnswerRole + state?: QuestionAnswerState + previousState?: QuestionAnswerState | null + timeoutMs?: number + } +) { + const observable = agent.events.observable( + QuestionAnswerEventTypes.QuestionAnswerStateChanged + ) + + return waitForQuestionAnswerRecordSubject(observable, options) +} + +export function waitForQuestionAnswerRecordSubject( + subject: ReplaySubject | Observable, + { + threadId, + role, + state, + previousState, + timeoutMs = 10000, + }: { + threadId?: string + role?: QuestionAnswerRole + state?: QuestionAnswerState + previousState?: QuestionAnswerState | null + timeoutMs?: number + } +) { + const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject + return firstValueFrom( + observable.pipe( + filter((e) => previousState === undefined || e.payload.previousState === previousState), + filter((e) => threadId === undefined || e.payload.questionAnswerRecord.threadId === threadId), + filter((e) => role === undefined || e.payload.questionAnswerRecord.role === role), + filter((e) => state === undefined || e.payload.questionAnswerRecord.state === state), + timeout(timeoutMs), + catchError(() => { + throw new Error( + `QuestionAnswerChangedEvent event not emitted within specified timeout: { + previousState: ${previousState}, + threadId: ${threadId}, + state: ${state} + }` + ) + }), + map((e) => e.payload.questionAnswerRecord) + ) + ) +} diff --git a/packages/core/src/modules/question-answer/__tests__/question-answer.e2e.test.ts b/packages/core/src/modules/question-answer/__tests__/question-answer.e2e.test.ts new file mode 100644 index 0000000000..f1b2558a60 --- /dev/null +++ b/packages/core/src/modules/question-answer/__tests__/question-answer.e2e.test.ts @@ -0,0 +1,92 @@ +import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' +import type { ConnectionRecord } from '../../connections/repository' + +import { Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getBaseConfig, makeConnection } from '../../../../tests/helpers' +import testLogger from '../../../../tests/logger' +import { Agent } from '../../../agent/Agent' +import { QuestionAnswerRole } from '../QuestionAnswerRole' +import { QuestionAnswerState } from '../models' + +import { waitForQuestionAnswerRecord } from './helpers' + +const bobConfig = getBaseConfig('Bob Question Answer', { + endpoints: ['rxjs:bob'], +}) + +const aliceConfig = getBaseConfig('Alice Question Answer', { + endpoints: ['rxjs:alice'], +}) + +describe('Question Answer', () => { + let bobAgent: Agent + let aliceAgent: Agent + let aliceConnection: ConnectionRecord + + beforeEach(async () => { + const bobMessages = new Subject() + const aliceMessages = new Subject() + const subjectMap = { + 'rxjs:bob': bobMessages, + 'rxjs:alice': aliceMessages, + } + + bobAgent = new Agent(bobConfig.config, bobConfig.agentDependencies) + bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) + bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await bobAgent.initialize() + + aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + ;[aliceConnection] = await makeConnection(aliceAgent, bobAgent) + }) + + afterEach(async () => { + await bobAgent.shutdown() + await bobAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice sends a question and Bob answers', async () => { + testLogger.test('Alice sends question to Bob') + let aliceQuestionAnswerRecord = await aliceAgent.questionAnswer.sendQuestion(aliceConnection.id, { + question: 'Do you want to play?', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + + testLogger.test('Bob waits for question from Alice') + const bobQuestionAnswerRecord = await waitForQuestionAnswerRecord(bobAgent, { + threadId: aliceQuestionAnswerRecord.threadId, + state: QuestionAnswerState.QuestionReceived, + }) + + expect(bobQuestionAnswerRecord.questionText).toEqual('Do you want to play?') + expect(bobQuestionAnswerRecord.validResponses).toEqual([{ text: 'Yes' }, { text: 'No' }]) + testLogger.test('Bob sends answer to Alice') + await bobAgent.questionAnswer.sendAnswer(bobQuestionAnswerRecord.id, 'Yes') + + testLogger.test('Alice waits until Bob answers') + aliceQuestionAnswerRecord = await waitForQuestionAnswerRecord(aliceAgent, { + threadId: aliceQuestionAnswerRecord.threadId, + state: QuestionAnswerState.AnswerReceived, + }) + + expect(aliceQuestionAnswerRecord.response).toEqual('Yes') + + const retrievedRecord = await aliceAgent.questionAnswer.findById(aliceQuestionAnswerRecord.id) + expect(retrievedRecord).toMatchObject( + expect.objectContaining({ + id: aliceQuestionAnswerRecord.id, + threadId: aliceQuestionAnswerRecord.threadId, + state: QuestionAnswerState.AnswerReceived, + role: QuestionAnswerRole.Questioner, + }) + ) + }) +}) diff --git a/packages/core/src/modules/question-answer/repository/QuestionAnswerRecord.ts b/packages/core/src/modules/question-answer/repository/QuestionAnswerRecord.ts index c1a4a0259d..7d649a3bc6 100644 --- a/packages/core/src/modules/question-answer/repository/QuestionAnswerRecord.ts +++ b/packages/core/src/modules/question-answer/repository/QuestionAnswerRecord.ts @@ -76,6 +76,12 @@ export class QuestionAnswerRecord extends BaseRecord { return this.questionAnswerRepository.getSingleByQuery({ @@ -224,11 +224,25 @@ export class QuestionAnswerService { } /** - * Retrieve a connection record by id + * Retrieve a question answer record by thread id + * + * @param connectionId The connection id + * @param threadId The thread id + * @returns The question answer record or null if not found + */ + public findByThreadAndConnectionId(connectionId: string, threadId: string): Promise { + return this.questionAnswerRepository.findSingleByQuery({ + connectionId, + threadId, + }) + } + + /** + * Retrieve a question answer record by id * * @param questionAnswerId The questionAnswer record id * @throws {RecordNotFoundError} If no record is found - * @return The connection record + * @return The question answer record * */ public getById(questionAnswerId: string): Promise { @@ -236,9 +250,20 @@ export class QuestionAnswerService { } /** - * Retrieve all QuestionAnswer records + * Retrieve a question answer record by id + * + * @param questionAnswerId The questionAnswer record id + * @return The question answer record or null if not found + * + */ + public findById(questionAnswerId: string): Promise { + return this.questionAnswerRepository.findById(questionAnswerId) + } + + /** + * Retrieve all question answer records * - * @returns List containing all QuestionAnswer records + * @returns List containing all question answer records */ public getAll() { return this.questionAnswerRepository.getAll() From 60a8091d6431c98f764b2b94bff13ee97187b915 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Thu, 1 Sep 2022 08:06:02 -0300 Subject: [PATCH 027/125] feat: Action Menu protocol (Aries RFC 0509) implementation (#974) Signed-off-by: Ariel Gentile --- packages/core/src/agent/Agent.ts | 4 + .../modules/action-menu/ActionMenuEvents.ts | 14 + .../modules/action-menu/ActionMenuModule.ts | 159 ++++ .../action-menu/ActionMenuModuleOptions.ts | 27 + .../src/modules/action-menu/ActionMenuRole.ts | 9 + .../modules/action-menu/ActionMenuState.ts | 13 + .../__tests__/action-menu.e2e.test.ts | 334 ++++++++ .../modules/action-menu/__tests__/helpers.ts | 62 ++ .../errors/ActionMenuProblemReportError.ts | 22 + .../errors/ActionMenuProblemReportReason.ts | 8 + .../ActionMenuProblemReportHandler.ts | 17 + .../handlers/MenuMessageHandler.ts | 19 + .../handlers/MenuRequestMessageHandler.ts | 19 + .../handlers/PerformMessageHandler.ts | 19 + .../src/modules/action-menu/handlers/index.ts | 4 + .../core/src/modules/action-menu/index.ts | 9 + .../ActionMenuProblemReportMessage.ts | 23 + .../action-menu/messages/MenuMessage.ts | 55 ++ .../messages/MenuRequestMessage.ts | 20 + .../action-menu/messages/PerformMessage.ts | 37 + .../src/modules/action-menu/messages/index.ts | 4 + .../modules/action-menu/models/ActionMenu.ts | 32 + .../action-menu/models/ActionMenuOption.ts | 46 + .../models/ActionMenuOptionForm.ts | 33 + .../models/ActionMenuOptionFormParameter.ts | 48 ++ .../action-menu/models/ActionMenuSelection.ts | 22 + .../src/modules/action-menu/models/index.ts | 5 + .../repository/ActionMenuRecord.ts | 92 ++ .../repository/ActionMenuRepository.ts | 17 + .../modules/action-menu/repository/index.ts | 2 + .../action-menu/services/ActionMenuService.ts | 360 ++++++++ .../services/ActionMenuServiceOptions.ts | 29 + .../__tests__/ActionMenuService.test.ts | 810 ++++++++++++++++++ .../src/modules/action-menu/services/index.ts | 2 + 34 files changed, 2376 insertions(+) create mode 100644 packages/core/src/modules/action-menu/ActionMenuEvents.ts create mode 100644 packages/core/src/modules/action-menu/ActionMenuModule.ts create mode 100644 packages/core/src/modules/action-menu/ActionMenuModuleOptions.ts create mode 100644 packages/core/src/modules/action-menu/ActionMenuRole.ts create mode 100644 packages/core/src/modules/action-menu/ActionMenuState.ts create mode 100644 packages/core/src/modules/action-menu/__tests__/action-menu.e2e.test.ts create mode 100644 packages/core/src/modules/action-menu/__tests__/helpers.ts create mode 100644 packages/core/src/modules/action-menu/errors/ActionMenuProblemReportError.ts create mode 100644 packages/core/src/modules/action-menu/errors/ActionMenuProblemReportReason.ts create mode 100644 packages/core/src/modules/action-menu/handlers/ActionMenuProblemReportHandler.ts create mode 100644 packages/core/src/modules/action-menu/handlers/MenuMessageHandler.ts create mode 100644 packages/core/src/modules/action-menu/handlers/MenuRequestMessageHandler.ts create mode 100644 packages/core/src/modules/action-menu/handlers/PerformMessageHandler.ts create mode 100644 packages/core/src/modules/action-menu/handlers/index.ts create mode 100644 packages/core/src/modules/action-menu/index.ts create mode 100644 packages/core/src/modules/action-menu/messages/ActionMenuProblemReportMessage.ts create mode 100644 packages/core/src/modules/action-menu/messages/MenuMessage.ts create mode 100644 packages/core/src/modules/action-menu/messages/MenuRequestMessage.ts create mode 100644 packages/core/src/modules/action-menu/messages/PerformMessage.ts create mode 100644 packages/core/src/modules/action-menu/messages/index.ts create mode 100644 packages/core/src/modules/action-menu/models/ActionMenu.ts create mode 100644 packages/core/src/modules/action-menu/models/ActionMenuOption.ts create mode 100644 packages/core/src/modules/action-menu/models/ActionMenuOptionForm.ts create mode 100644 packages/core/src/modules/action-menu/models/ActionMenuOptionFormParameter.ts create mode 100644 packages/core/src/modules/action-menu/models/ActionMenuSelection.ts create mode 100644 packages/core/src/modules/action-menu/models/index.ts create mode 100644 packages/core/src/modules/action-menu/repository/ActionMenuRecord.ts create mode 100644 packages/core/src/modules/action-menu/repository/ActionMenuRepository.ts create mode 100644 packages/core/src/modules/action-menu/repository/index.ts create mode 100644 packages/core/src/modules/action-menu/services/ActionMenuService.ts create mode 100644 packages/core/src/modules/action-menu/services/ActionMenuServiceOptions.ts create mode 100644 packages/core/src/modules/action-menu/services/__tests__/ActionMenuService.test.ts create mode 100644 packages/core/src/modules/action-menu/services/index.ts diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index dc9da53e93..8063661f64 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -16,6 +16,7 @@ import { CacheRepository } from '../cache' import { InjectionSymbols } from '../constants' import { JwsService } from '../crypto/JwsService' import { AriesFrameworkError } from '../error' +import { ActionMenuModule } from '../modules/action-menu' import { BasicMessagesModule } from '../modules/basic-messages/BasicMessagesModule' import { ConnectionsModule } from '../modules/connections/ConnectionsModule' import { CredentialsModule } from '../modules/credentials/CredentialsModule' @@ -68,6 +69,7 @@ export class Agent { public readonly genericRecords: GenericRecordsModule public readonly ledger: LedgerModule public readonly questionAnswer!: QuestionAnswerModule + public readonly actionMenu!: ActionMenuModule public readonly credentials: CredentialsModule public readonly mediationRecipient: RecipientModule public readonly mediator: MediatorModule @@ -122,6 +124,7 @@ export class Agent { this.mediationRecipient = this.dependencyManager.resolve(RecipientModule) this.basicMessages = this.dependencyManager.resolve(BasicMessagesModule) this.questionAnswer = this.dependencyManager.resolve(QuestionAnswerModule) + this.actionMenu = this.dependencyManager.resolve(ActionMenuModule) this.genericRecords = this.dependencyManager.resolve(GenericRecordsModule) this.ledger = this.dependencyManager.resolve(LedgerModule) this.discovery = this.dependencyManager.resolve(DiscoverFeaturesModule) @@ -342,6 +345,7 @@ export class Agent { RecipientModule, BasicMessagesModule, QuestionAnswerModule, + ActionMenuModule, GenericRecordsModule, LedgerModule, DiscoverFeaturesModule, diff --git a/packages/core/src/modules/action-menu/ActionMenuEvents.ts b/packages/core/src/modules/action-menu/ActionMenuEvents.ts new file mode 100644 index 0000000000..78733fafb7 --- /dev/null +++ b/packages/core/src/modules/action-menu/ActionMenuEvents.ts @@ -0,0 +1,14 @@ +import type { BaseEvent } from '../../agent/Events' +import type { ActionMenuState } from './ActionMenuState' +import type { ActionMenuRecord } from './repository' + +export enum ActionMenuEventTypes { + ActionMenuStateChanged = 'ActionMenuStateChanged', +} +export interface ActionMenuStateChangedEvent extends BaseEvent { + type: typeof ActionMenuEventTypes.ActionMenuStateChanged + payload: { + actionMenuRecord: ActionMenuRecord + previousState: ActionMenuState | null + } +} diff --git a/packages/core/src/modules/action-menu/ActionMenuModule.ts b/packages/core/src/modules/action-menu/ActionMenuModule.ts new file mode 100644 index 0000000000..42c26f5c9f --- /dev/null +++ b/packages/core/src/modules/action-menu/ActionMenuModule.ts @@ -0,0 +1,159 @@ +import type { DependencyManager } from '../../plugins' +import type { + ClearActiveMenuOptions, + FindActiveMenuOptions, + PerformActionOptions, + RequestMenuOptions, + SendMenuOptions, +} from './ActionMenuModuleOptions' + +import { Dispatcher } from '../../agent/Dispatcher' +import { MessageSender } from '../../agent/MessageSender' +import { createOutboundMessage } from '../../agent/helpers' +import { AriesFrameworkError } from '../../error' +import { injectable, module } from '../../plugins' +import { ConnectionService } from '../connections/services' + +import { ActionMenuRole } from './ActionMenuRole' +import { + ActionMenuProblemReportHandler, + MenuMessageHandler, + MenuRequestMessageHandler, + PerformMessageHandler, +} from './handlers' +import { ActionMenuService } from './services' + +@module() +@injectable() +export class ActionMenuModule { + private connectionService: ConnectionService + private messageSender: MessageSender + private actionMenuService: ActionMenuService + + public constructor( + dispatcher: Dispatcher, + connectionService: ConnectionService, + messageSender: MessageSender, + actionMenuService: ActionMenuService + ) { + this.connectionService = connectionService + this.messageSender = messageSender + this.actionMenuService = actionMenuService + this.registerHandlers(dispatcher) + } + + /** + * Start Action Menu protocol as requester, asking for root menu. Any active menu will be cleared. + * + * @param options options for requesting menu + * @returns Action Menu record associated to this new request + */ + public async requestMenu(options: RequestMenuOptions) { + const connection = await this.connectionService.getById(options.connectionId) + + const { message, record } = await this.actionMenuService.createRequest({ + connection, + }) + + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(outboundMessage) + + return record + } + + /** + * Send a new Action Menu as responder. This menu will be sent as response if there is an + * existing menu thread. + * + * @param options options for sending menu + * @returns Action Menu record associated to this action + */ + public async sendMenu(options: SendMenuOptions) { + const connection = await this.connectionService.getById(options.connectionId) + + const { message, record } = await this.actionMenuService.createMenu({ + connection, + menu: options.menu, + }) + + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(outboundMessage) + + return record + } + + /** + * Perform action in active Action Menu, as a requester. The related + * menu will be closed. + * + * @param options options for requesting menu + * @returns Action Menu record associated to this selection + */ + public async performAction(options: PerformActionOptions) { + const connection = await this.connectionService.getById(options.connectionId) + + const actionMenuRecord = await this.actionMenuService.find({ + connectionId: connection.id, + role: ActionMenuRole.Requester, + }) + if (!actionMenuRecord) { + throw new AriesFrameworkError(`No active menu found for connection id ${options.connectionId}`) + } + + const { message, record } = await this.actionMenuService.createPerform({ + actionMenuRecord, + performedAction: options.performedAction, + }) + + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(outboundMessage) + + return record + } + + /** + * Find the current active menu for a given connection and the specified role. + * + * @param options options for requesting active menu + * @returns Active Action Menu record, or null if no active menu found + */ + public async findActiveMenu(options: FindActiveMenuOptions) { + return this.actionMenuService.find({ + connectionId: options.connectionId, + role: options.role, + }) + } + + /** + * Clears the current active menu for a given connection and the specified role. + * + * @param options options for clearing active menu + * @returns Active Action Menu record, or null if no active menu record found + */ + public async clearActiveMenu(options: ClearActiveMenuOptions) { + const actionMenuRecord = await this.actionMenuService.find({ + connectionId: options.connectionId, + role: options.role, + }) + + return actionMenuRecord ? await this.actionMenuService.clearMenu({ actionMenuRecord }) : null + } + + private registerHandlers(dispatcher: Dispatcher) { + dispatcher.registerHandler(new ActionMenuProblemReportHandler(this.actionMenuService)) + dispatcher.registerHandler(new MenuMessageHandler(this.actionMenuService)) + dispatcher.registerHandler(new MenuRequestMessageHandler(this.actionMenuService)) + dispatcher.registerHandler(new PerformMessageHandler(this.actionMenuService)) + } + + /** + * Registers the dependencies of the discover features module on the dependency manager. + */ + public static register(dependencyManager: DependencyManager) { + // Api + dependencyManager.registerContextScoped(ActionMenuModule) + + // Services + dependencyManager.registerSingleton(ActionMenuService) + } +} diff --git a/packages/core/src/modules/action-menu/ActionMenuModuleOptions.ts b/packages/core/src/modules/action-menu/ActionMenuModuleOptions.ts new file mode 100644 index 0000000000..2ad9fcdd54 --- /dev/null +++ b/packages/core/src/modules/action-menu/ActionMenuModuleOptions.ts @@ -0,0 +1,27 @@ +import type { ActionMenuRole } from './ActionMenuRole' +import type { ActionMenu } from './models/ActionMenu' +import type { ActionMenuSelection } from './models/ActionMenuSelection' + +export interface FindActiveMenuOptions { + connectionId: string + role: ActionMenuRole +} + +export interface ClearActiveMenuOptions { + connectionId: string + role: ActionMenuRole +} + +export interface RequestMenuOptions { + connectionId: string +} + +export interface SendMenuOptions { + connectionId: string + menu: ActionMenu +} + +export interface PerformActionOptions { + connectionId: string + performedAction: ActionMenuSelection +} diff --git a/packages/core/src/modules/action-menu/ActionMenuRole.ts b/packages/core/src/modules/action-menu/ActionMenuRole.ts new file mode 100644 index 0000000000..f4ef73f56c --- /dev/null +++ b/packages/core/src/modules/action-menu/ActionMenuRole.ts @@ -0,0 +1,9 @@ +/** + * Action Menu roles based on the flow defined in RFC 0509. + * + * @see https://github.com/hyperledger/aries-rfcs/tree/main/features/0509-action-menu#roles + */ +export enum ActionMenuRole { + Requester = 'requester', + Responder = 'responder', +} diff --git a/packages/core/src/modules/action-menu/ActionMenuState.ts b/packages/core/src/modules/action-menu/ActionMenuState.ts new file mode 100644 index 0000000000..bf158c9b26 --- /dev/null +++ b/packages/core/src/modules/action-menu/ActionMenuState.ts @@ -0,0 +1,13 @@ +/** + * Action Menu states based on the flow defined in RFC 0509. + * + * @see https://github.com/hyperledger/aries-rfcs/tree/main/features/0509-action-menu#states + */ +export enum ActionMenuState { + Null = 'null', + AwaitingRootMenu = 'awaiting-root-menu', + PreparingRootMenu = 'preparing-root-menu', + PreparingSelection = 'preparing-selection', + AwaitingSelection = 'awaiting-selection', + Done = 'done', +} diff --git a/packages/core/src/modules/action-menu/__tests__/action-menu.e2e.test.ts b/packages/core/src/modules/action-menu/__tests__/action-menu.e2e.test.ts new file mode 100644 index 0000000000..3f56a0bcca --- /dev/null +++ b/packages/core/src/modules/action-menu/__tests__/action-menu.e2e.test.ts @@ -0,0 +1,334 @@ +import type { ConnectionRecord } from '../../..' +import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' + +import { Subject } from 'rxjs' + +import { Agent } from '../../..' +import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getBaseConfig, makeConnection } from '../../../../tests/helpers' +import testLogger from '../../../../tests/logger' +import { ActionMenuRole } from '../ActionMenuRole' +import { ActionMenuState } from '../ActionMenuState' +import { ActionMenu } from '../models' +import { ActionMenuRecord } from '../repository' + +import { waitForActionMenuRecord } from './helpers' + +const faberConfig = getBaseConfig('Faber Action Menu', { + endpoints: ['rxjs:faber'], +}) + +const aliceConfig = getBaseConfig('Alice Action Menu', { + endpoints: ['rxjs:alice'], +}) + +describe('Action Menu', () => { + let faberAgent: Agent + let aliceAgent: Agent + let faberConnection: ConnectionRecord + let aliceConnection: ConnectionRecord + + const rootMenu = new ActionMenu({ + title: 'Welcome', + description: 'This is the root menu', + options: [ + { + name: 'option-1', + description: 'Option 1 description', + title: 'Option 1', + }, + { + name: 'option-2', + description: 'Option 2 description', + title: 'Option 2', + }, + ], + }) + + const submenu1 = new ActionMenu({ + title: 'Menu 1', + description: 'This is first submenu', + options: [ + { + name: 'option-1-1', + description: '1-1 desc', + title: '1-1 title', + }, + { + name: 'option-1-2', + description: '1-1 desc', + title: '1-1 title', + }, + ], + }) + + beforeEach(async () => { + const faberMessages = new Subject() + const aliceMessages = new Subject() + const subjectMap = { + 'rxjs:faber': faberMessages, + 'rxjs:alice': aliceMessages, + } + + faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await faberAgent.initialize() + + aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + ;[aliceConnection, faberConnection] = await makeConnection(aliceAgent, faberAgent) + }) + + afterEach(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice requests menu to Faber and selects an option once received', async () => { + testLogger.test('Alice sends menu request to Faber') + let aliceActionMenuRecord = await aliceAgent.actionMenu.requestMenu({ connectionId: aliceConnection.id }) + + testLogger.test('Faber waits for menu request from Alice') + await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.PreparingRootMenu, + }) + + testLogger.test('Faber sends root menu to Alice') + await faberAgent.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) + + testLogger.test('Alice waits until she receives menu') + aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.PreparingSelection, + }) + + expect(aliceActionMenuRecord.menu).toEqual(rootMenu) + const faberActiveMenu = await faberAgent.actionMenu.findActiveMenu({ + connectionId: faberConnection.id, + role: ActionMenuRole.Responder, + }) + expect(faberActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(faberActiveMenu?.state).toBe(ActionMenuState.AwaitingSelection) + + testLogger.test('Alice selects menu item') + await aliceAgent.actionMenu.performAction({ + connectionId: aliceConnection.id, + performedAction: { name: 'option-1' }, + }) + + testLogger.test('Faber waits for menu selection from Alice') + await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.Done, + }) + + // As Alice has responded, menu should be closed (done state) + const aliceActiveMenu = await aliceAgent.actionMenu.findActiveMenu({ + connectionId: aliceConnection.id, + role: ActionMenuRole.Requester, + }) + expect(aliceActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(aliceActiveMenu?.state).toBe(ActionMenuState.Done) + }) + + test('Faber sends root menu and Alice selects an option', async () => { + testLogger.test('Faber sends root menu to Alice') + await faberAgent.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) + + testLogger.test('Alice waits until she receives menu') + const aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.PreparingSelection, + }) + + expect(aliceActionMenuRecord.menu).toEqual(rootMenu) + const faberActiveMenu = await faberAgent.actionMenu.findActiveMenu({ + connectionId: faberConnection.id, + role: ActionMenuRole.Responder, + }) + expect(faberActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(faberActiveMenu?.state).toBe(ActionMenuState.AwaitingSelection) + + testLogger.test('Alice selects menu item') + await aliceAgent.actionMenu.performAction({ + connectionId: aliceConnection.id, + performedAction: { name: 'option-1' }, + }) + + testLogger.test('Faber waits for menu selection from Alice') + await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.Done, + }) + + // As Alice has responded, menu should be closed (done state) + const aliceActiveMenu = await aliceAgent.actionMenu.findActiveMenu({ + connectionId: aliceConnection.id, + role: ActionMenuRole.Requester, + }) + expect(aliceActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(aliceActiveMenu?.state).toBe(ActionMenuState.Done) + }) + + test('Menu navigation', async () => { + testLogger.test('Faber sends root menu ') + let faberActionMenuRecord = await faberAgent.actionMenu.sendMenu({ + connectionId: faberConnection.id, + menu: rootMenu, + }) + + const rootThreadId = faberActionMenuRecord.threadId + + testLogger.test('Alice waits until she receives menu') + let aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.PreparingSelection, + }) + + expect(aliceActionMenuRecord.menu).toEqual(rootMenu) + expect(aliceActionMenuRecord.threadId).toEqual(rootThreadId) + + testLogger.test('Alice selects menu item 1') + await aliceAgent.actionMenu.performAction({ + connectionId: aliceConnection.id, + performedAction: { name: 'option-1' }, + }) + + testLogger.test('Faber waits for menu selection from Alice') + faberActionMenuRecord = await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.Done, + }) + + // As Alice has responded, menu should be closed (done state) + let aliceActiveMenu = await aliceAgent.actionMenu.findActiveMenu({ + connectionId: aliceConnection.id, + role: ActionMenuRole.Requester, + }) + expect(aliceActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(aliceActiveMenu?.state).toBe(ActionMenuState.Done) + expect(aliceActiveMenu?.threadId).toEqual(rootThreadId) + + testLogger.test('Faber sends submenu to Alice') + faberActionMenuRecord = await faberAgent.actionMenu.sendMenu({ + connectionId: faberConnection.id, + menu: submenu1, + }) + + testLogger.test('Alice waits until she receives submenu') + aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.PreparingSelection, + }) + + expect(aliceActionMenuRecord.menu).toEqual(submenu1) + expect(aliceActionMenuRecord.threadId).toEqual(rootThreadId) + + testLogger.test('Alice selects menu item 1-1') + await aliceAgent.actionMenu.performAction({ + connectionId: aliceConnection.id, + performedAction: { name: 'option-1-1' }, + }) + + testLogger.test('Faber waits for menu selection from Alice') + faberActionMenuRecord = await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.Done, + }) + + // As Alice has responded, menu should be closed (done state) + aliceActiveMenu = await aliceAgent.actionMenu.findActiveMenu({ + connectionId: aliceConnection.id, + role: ActionMenuRole.Requester, + }) + expect(aliceActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(aliceActiveMenu?.state).toBe(ActionMenuState.Done) + expect(aliceActiveMenu?.threadId).toEqual(rootThreadId) + + testLogger.test('Alice sends menu request to Faber') + aliceActionMenuRecord = await aliceAgent.actionMenu.requestMenu({ connectionId: aliceConnection.id }) + + testLogger.test('Faber waits for menu request from Alice') + faberActionMenuRecord = await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.PreparingRootMenu, + }) + + testLogger.test('This new menu request must have a different thread Id') + expect(faberActionMenuRecord.menu).toBeUndefined() + expect(aliceActionMenuRecord.threadId).not.toEqual(rootThreadId) + expect(faberActionMenuRecord.threadId).toEqual(aliceActionMenuRecord.threadId) + }) + + test('Menu clearing', async () => { + testLogger.test('Faber sends root menu to Alice') + await faberAgent.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) + + testLogger.test('Alice waits until she receives menu') + let aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.PreparingSelection, + }) + + expect(aliceActionMenuRecord.menu).toEqual(rootMenu) + let faberActiveMenu = await faberAgent.actionMenu.findActiveMenu({ + connectionId: faberConnection.id, + role: ActionMenuRole.Responder, + }) + expect(faberActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(faberActiveMenu?.state).toBe(ActionMenuState.AwaitingSelection) + + await faberAgent.actionMenu.clearActiveMenu({ + connectionId: faberConnection.id, + role: ActionMenuRole.Responder, + }) + + testLogger.test('Alice selects menu item') + await aliceAgent.actionMenu.performAction({ + connectionId: aliceConnection.id, + performedAction: { name: 'option-1' }, + }) + + // Exception + + testLogger.test('Faber rejects selection, as menu has been cleared') + // Faber sends error report to Alice, meaning that her Menu flow will be cleared + aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.Null, + role: ActionMenuRole.Requester, + }) + + testLogger.test('Alice request a new menu') + await aliceAgent.actionMenu.requestMenu({ + connectionId: aliceConnection.id, + }) + + testLogger.test('Faber waits for menu request from Alice') + await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.PreparingRootMenu, + }) + + testLogger.test('Faber sends root menu to Alice') + await faberAgent.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) + + testLogger.test('Alice waits until she receives menu') + aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.PreparingSelection, + }) + + expect(aliceActionMenuRecord.menu).toEqual(rootMenu) + faberActiveMenu = await faberAgent.actionMenu.findActiveMenu({ + connectionId: faberConnection.id, + role: ActionMenuRole.Responder, + }) + expect(faberActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(faberActiveMenu?.state).toBe(ActionMenuState.AwaitingSelection) + + /*testLogger.test('Alice selects menu item') + await aliceAgent.actionMenu.performAction({ + connectionId: aliceConnection.id, + performedAction: { name: 'option-1' }, + }) + + testLogger.test('Faber waits for menu selection from Alice') + await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.Done, + })*/ + }) +}) diff --git a/packages/core/src/modules/action-menu/__tests__/helpers.ts b/packages/core/src/modules/action-menu/__tests__/helpers.ts new file mode 100644 index 0000000000..8d0c6c48d6 --- /dev/null +++ b/packages/core/src/modules/action-menu/__tests__/helpers.ts @@ -0,0 +1,62 @@ +import type { Agent } from '../../../agent/Agent' +import type { ActionMenuStateChangedEvent } from '../ActionMenuEvents' +import type { ActionMenuRole } from '../ActionMenuRole' +import type { ActionMenuState } from '../ActionMenuState' +import type { Observable } from 'rxjs' + +import { catchError, filter, firstValueFrom, map, ReplaySubject, timeout } from 'rxjs' + +import { ActionMenuEventTypes } from '../ActionMenuEvents' + +export async function waitForActionMenuRecord( + agent: Agent, + options: { + threadId?: string + role?: ActionMenuRole + state?: ActionMenuState + previousState?: ActionMenuState | null + timeoutMs?: number + } +) { + const observable = agent.events.observable(ActionMenuEventTypes.ActionMenuStateChanged) + + return waitForActionMenuRecordSubject(observable, options) +} + +export function waitForActionMenuRecordSubject( + subject: ReplaySubject | Observable, + { + threadId, + role, + state, + previousState, + timeoutMs = 10000, + }: { + threadId?: string + role?: ActionMenuRole + state?: ActionMenuState + previousState?: ActionMenuState | null + timeoutMs?: number + } +) { + const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject + return firstValueFrom( + observable.pipe( + filter((e) => previousState === undefined || e.payload.previousState === previousState), + filter((e) => threadId === undefined || e.payload.actionMenuRecord.threadId === threadId), + filter((e) => role === undefined || e.payload.actionMenuRecord.role === role), + filter((e) => state === undefined || e.payload.actionMenuRecord.state === state), + timeout(timeoutMs), + catchError(() => { + throw new Error( + `ActionMenuStateChangedEvent event not emitted within specified timeout: { + previousState: ${previousState}, + threadId: ${threadId}, + state: ${state} + }` + ) + }), + map((e) => e.payload.actionMenuRecord) + ) + ) +} diff --git a/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportError.ts b/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportError.ts new file mode 100644 index 0000000000..2dcd8162e7 --- /dev/null +++ b/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportError.ts @@ -0,0 +1,22 @@ +import type { ProblemReportErrorOptions } from '../../problem-reports' +import type { ActionMenuProblemReportReason } from './ActionMenuProblemReportReason' + +import { ProblemReportError } from '../../problem-reports' +import { ActionMenuProblemReportMessage } from '../messages' + +interface ActionMenuProblemReportErrorOptions extends ProblemReportErrorOptions { + problemCode: ActionMenuProblemReportReason +} +export class ActionMenuProblemReportError extends ProblemReportError { + public problemReport: ActionMenuProblemReportMessage + + public constructor(public message: string, { problemCode }: ActionMenuProblemReportErrorOptions) { + super(message, { problemCode }) + this.problemReport = new ActionMenuProblemReportMessage({ + description: { + en: message, + code: problemCode, + }, + }) + } +} diff --git a/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportReason.ts b/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportReason.ts new file mode 100644 index 0000000000..97e18b9245 --- /dev/null +++ b/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportReason.ts @@ -0,0 +1,8 @@ +/** + * Action Menu errors discussed in RFC 0509. + * + * @see https://github.com/hyperledger/aries-rfcs/tree/main/features/0509-action-menu#unresolved-questions + */ +export enum ActionMenuProblemReportReason { + Timeout = 'timeout', +} diff --git a/packages/core/src/modules/action-menu/handlers/ActionMenuProblemReportHandler.ts b/packages/core/src/modules/action-menu/handlers/ActionMenuProblemReportHandler.ts new file mode 100644 index 0000000000..023ffc5cc1 --- /dev/null +++ b/packages/core/src/modules/action-menu/handlers/ActionMenuProblemReportHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { ActionMenuService } from '../services' + +import { ActionMenuProblemReportMessage } from '../messages' + +export class ActionMenuProblemReportHandler implements Handler { + private actionMenuService: ActionMenuService + public supportedMessages = [ActionMenuProblemReportMessage] + + public constructor(actionMenuService: ActionMenuService) { + this.actionMenuService = actionMenuService + } + + public async handle(messageContext: HandlerInboundMessage) { + await this.actionMenuService.processProblemReport(messageContext) + } +} diff --git a/packages/core/src/modules/action-menu/handlers/MenuMessageHandler.ts b/packages/core/src/modules/action-menu/handlers/MenuMessageHandler.ts new file mode 100644 index 0000000000..0e81788525 --- /dev/null +++ b/packages/core/src/modules/action-menu/handlers/MenuMessageHandler.ts @@ -0,0 +1,19 @@ +import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { ActionMenuService } from '../services' + +import { MenuMessage } from '../messages' + +export class MenuMessageHandler implements Handler { + private actionMenuService: ActionMenuService + public supportedMessages = [MenuMessage] + + public constructor(actionMenuService: ActionMenuService) { + this.actionMenuService = actionMenuService + } + + public async handle(inboundMessage: HandlerInboundMessage) { + inboundMessage.assertReadyConnection() + + await this.actionMenuService.processMenu(inboundMessage) + } +} diff --git a/packages/core/src/modules/action-menu/handlers/MenuRequestMessageHandler.ts b/packages/core/src/modules/action-menu/handlers/MenuRequestMessageHandler.ts new file mode 100644 index 0000000000..33277d2510 --- /dev/null +++ b/packages/core/src/modules/action-menu/handlers/MenuRequestMessageHandler.ts @@ -0,0 +1,19 @@ +import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { ActionMenuService } from '../services' + +import { MenuRequestMessage } from '../messages' + +export class MenuRequestMessageHandler implements Handler { + private actionMenuService: ActionMenuService + public supportedMessages = [MenuRequestMessage] + + public constructor(actionMenuService: ActionMenuService) { + this.actionMenuService = actionMenuService + } + + public async handle(inboundMessage: HandlerInboundMessage) { + inboundMessage.assertReadyConnection() + + await this.actionMenuService.processRequest(inboundMessage) + } +} diff --git a/packages/core/src/modules/action-menu/handlers/PerformMessageHandler.ts b/packages/core/src/modules/action-menu/handlers/PerformMessageHandler.ts new file mode 100644 index 0000000000..65de15dcb0 --- /dev/null +++ b/packages/core/src/modules/action-menu/handlers/PerformMessageHandler.ts @@ -0,0 +1,19 @@ +import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { ActionMenuService } from '../services' + +import { PerformMessage } from '../messages' + +export class PerformMessageHandler implements Handler { + private actionMenuService: ActionMenuService + public supportedMessages = [PerformMessage] + + public constructor(actionMenuService: ActionMenuService) { + this.actionMenuService = actionMenuService + } + + public async handle(inboundMessage: HandlerInboundMessage) { + inboundMessage.assertReadyConnection() + + await this.actionMenuService.processPerform(inboundMessage) + } +} diff --git a/packages/core/src/modules/action-menu/handlers/index.ts b/packages/core/src/modules/action-menu/handlers/index.ts new file mode 100644 index 0000000000..b7ba3b7117 --- /dev/null +++ b/packages/core/src/modules/action-menu/handlers/index.ts @@ -0,0 +1,4 @@ +export * from './ActionMenuProblemReportHandler' +export * from './MenuMessageHandler' +export * from './MenuRequestMessageHandler' +export * from './PerformMessageHandler' diff --git a/packages/core/src/modules/action-menu/index.ts b/packages/core/src/modules/action-menu/index.ts new file mode 100644 index 0000000000..732f2dc3e8 --- /dev/null +++ b/packages/core/src/modules/action-menu/index.ts @@ -0,0 +1,9 @@ +export * from './ActionMenuEvents' +export * from './ActionMenuModule' +export * from './ActionMenuModuleOptions' +export * from './ActionMenuRole' +export * from './ActionMenuState' +export * from './messages' +export * from './models' +export * from './repository' +export * from './services' diff --git a/packages/core/src/modules/action-menu/messages/ActionMenuProblemReportMessage.ts b/packages/core/src/modules/action-menu/messages/ActionMenuProblemReportMessage.ts new file mode 100644 index 0000000000..cfff53ca65 --- /dev/null +++ b/packages/core/src/modules/action-menu/messages/ActionMenuProblemReportMessage.ts @@ -0,0 +1,23 @@ +import type { ProblemReportMessageOptions } from '../../problem-reports/messages/ProblemReportMessage' + +import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' +import { ProblemReportMessage } from '../../problem-reports/messages/ProblemReportMessage' + +export type ActionMenuProblemReportMessageOptions = ProblemReportMessageOptions + +/** + * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md + */ +export class ActionMenuProblemReportMessage extends ProblemReportMessage { + /** + * Create new ConnectionProblemReportMessage instance. + * @param options + */ + public constructor(options: ActionMenuProblemReportMessageOptions) { + super(options) + } + + @IsValidMessageType(ActionMenuProblemReportMessage.type) + public readonly type = ActionMenuProblemReportMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/action-menu/1.0/problem-report') +} diff --git a/packages/core/src/modules/action-menu/messages/MenuMessage.ts b/packages/core/src/modules/action-menu/messages/MenuMessage.ts new file mode 100644 index 0000000000..d1c87dcebe --- /dev/null +++ b/packages/core/src/modules/action-menu/messages/MenuMessage.ts @@ -0,0 +1,55 @@ +import type { ActionMenuOptionOptions } from '../models' + +import { Expose, Type } from 'class-transformer' +import { IsInstance, IsOptional, IsString } from 'class-validator' + +import { AgentMessage } from '../../../agent/AgentMessage' +import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' +import { ActionMenuOption } from '../models' + +export interface MenuMessageOptions { + id?: string + title: string + description: string + errorMessage?: string + options: ActionMenuOptionOptions[] + threadId?: string +} + +export class MenuMessage extends AgentMessage { + public constructor(options: MenuMessageOptions) { + super() + + if (options) { + this.id = options.id ?? this.generateId() + this.title = options.title + this.description = options.description + this.errorMessage = options.errorMessage + this.options = options.options.map((p) => new ActionMenuOption(p)) + if (options.threadId) { + this.setThread({ + threadId: options.threadId, + }) + } + } + } + + @IsValidMessageType(MenuMessage.type) + public readonly type = MenuMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/action-menu/1.0/menu') + + @IsString() + public title!: string + + @IsString() + public description!: string + + @Expose({ name: 'errormsg' }) + @IsString() + @IsOptional() + public errorMessage?: string + + @IsInstance(ActionMenuOption, { each: true }) + @Type(() => ActionMenuOption) + public options!: ActionMenuOption[] +} diff --git a/packages/core/src/modules/action-menu/messages/MenuRequestMessage.ts b/packages/core/src/modules/action-menu/messages/MenuRequestMessage.ts new file mode 100644 index 0000000000..d4961553c6 --- /dev/null +++ b/packages/core/src/modules/action-menu/messages/MenuRequestMessage.ts @@ -0,0 +1,20 @@ +import { AgentMessage } from '../../../agent/AgentMessage' +import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' + +export interface MenuRequestMessageOptions { + id?: string +} + +export class MenuRequestMessage extends AgentMessage { + public constructor(options: MenuRequestMessageOptions) { + super() + + if (options) { + this.id = options.id ?? this.generateId() + } + } + + @IsValidMessageType(MenuRequestMessage.type) + public readonly type = MenuRequestMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/action-menu/1.0/menu-request') +} diff --git a/packages/core/src/modules/action-menu/messages/PerformMessage.ts b/packages/core/src/modules/action-menu/messages/PerformMessage.ts new file mode 100644 index 0000000000..75f03f02f7 --- /dev/null +++ b/packages/core/src/modules/action-menu/messages/PerformMessage.ts @@ -0,0 +1,37 @@ +import { IsOptional, IsString } from 'class-validator' + +import { AgentMessage } from '../../../agent/AgentMessage' +import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' + +export interface PerformMessageOptions { + id?: string + name: string + params?: Record + threadId: string +} + +export class PerformMessage extends AgentMessage { + public constructor(options: PerformMessageOptions) { + super() + + if (options) { + this.id = options.id ?? this.generateId() + this.name = options.name + this.params = options.params + this.setThread({ + threadId: options.threadId, + }) + } + } + + @IsValidMessageType(PerformMessage.type) + public readonly type = PerformMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/action-menu/1.0/perform') + + @IsString() + public name!: string + + @IsString({ each: true }) + @IsOptional() + public params?: Record +} diff --git a/packages/core/src/modules/action-menu/messages/index.ts b/packages/core/src/modules/action-menu/messages/index.ts new file mode 100644 index 0000000000..ecf085a0cb --- /dev/null +++ b/packages/core/src/modules/action-menu/messages/index.ts @@ -0,0 +1,4 @@ +export * from './ActionMenuProblemReportMessage' +export * from './MenuMessage' +export * from './MenuRequestMessage' +export * from './PerformMessage' diff --git a/packages/core/src/modules/action-menu/models/ActionMenu.ts b/packages/core/src/modules/action-menu/models/ActionMenu.ts new file mode 100644 index 0000000000..1123394796 --- /dev/null +++ b/packages/core/src/modules/action-menu/models/ActionMenu.ts @@ -0,0 +1,32 @@ +import type { ActionMenuOptionOptions } from './ActionMenuOption' + +import { Type } from 'class-transformer' +import { IsInstance, IsString } from 'class-validator' + +import { ActionMenuOption } from './ActionMenuOption' + +export interface ActionMenuOptions { + title: string + description: string + options: ActionMenuOptionOptions[] +} + +export class ActionMenu { + public constructor(options: ActionMenuOptions) { + if (options) { + this.title = options.title + this.description = options.description + this.options = options.options.map((p) => new ActionMenuOption(p)) + } + } + + @IsString() + public title!: string + + @IsString() + public description!: string + + @IsInstance(ActionMenuOption, { each: true }) + @Type(() => ActionMenuOption) + public options!: ActionMenuOption[] +} diff --git a/packages/core/src/modules/action-menu/models/ActionMenuOption.ts b/packages/core/src/modules/action-menu/models/ActionMenuOption.ts new file mode 100644 index 0000000000..1418c61e6c --- /dev/null +++ b/packages/core/src/modules/action-menu/models/ActionMenuOption.ts @@ -0,0 +1,46 @@ +import type { ActionMenuFormOptions } from './ActionMenuOptionForm' + +import { Type } from 'class-transformer' +import { IsBoolean, IsInstance, IsOptional, IsString } from 'class-validator' + +import { ActionMenuForm } from './ActionMenuOptionForm' + +export interface ActionMenuOptionOptions { + name: string + title: string + description: string + disabled?: boolean + form?: ActionMenuFormOptions +} + +export class ActionMenuOption { + public constructor(options: ActionMenuOptionOptions) { + if (options) { + this.name = options.name + this.title = options.title + this.description = options.description + this.disabled = options.disabled + if (options.form) { + this.form = new ActionMenuForm(options.form) + } + } + } + + @IsString() + public name!: string + + @IsString() + public title!: string + + @IsString() + public description!: string + + @IsBoolean() + @IsOptional() + public disabled?: boolean + + @IsInstance(ActionMenuForm) + @Type(() => ActionMenuForm) + @IsOptional() + public form?: ActionMenuForm +} diff --git a/packages/core/src/modules/action-menu/models/ActionMenuOptionForm.ts b/packages/core/src/modules/action-menu/models/ActionMenuOptionForm.ts new file mode 100644 index 0000000000..07a027a0a1 --- /dev/null +++ b/packages/core/src/modules/action-menu/models/ActionMenuOptionForm.ts @@ -0,0 +1,33 @@ +import type { ActionMenuFormParameterOptions } from './ActionMenuOptionFormParameter' + +import { Expose, Type } from 'class-transformer' +import { IsInstance, IsString } from 'class-validator' + +import { ActionMenuFormParameter } from './ActionMenuOptionFormParameter' + +export interface ActionMenuFormOptions { + description: string + params: ActionMenuFormParameterOptions[] + submitLabel: string +} + +export class ActionMenuForm { + public constructor(options: ActionMenuFormOptions) { + if (options) { + this.description = options.description + this.params = options.params.map((p) => new ActionMenuFormParameter(p)) + this.submitLabel = options.submitLabel + } + } + + @IsString() + public description!: string + + @Expose({ name: 'submit-label' }) + @IsString() + public submitLabel!: string + + @IsInstance(ActionMenuFormParameter, { each: true }) + @Type(() => ActionMenuFormParameter) + public params!: ActionMenuFormParameter[] +} diff --git a/packages/core/src/modules/action-menu/models/ActionMenuOptionFormParameter.ts b/packages/core/src/modules/action-menu/models/ActionMenuOptionFormParameter.ts new file mode 100644 index 0000000000..2c66ac39dc --- /dev/null +++ b/packages/core/src/modules/action-menu/models/ActionMenuOptionFormParameter.ts @@ -0,0 +1,48 @@ +import { IsBoolean, IsEnum, IsOptional, IsString } from 'class-validator' + +export enum ActionMenuFormInputType { + Text = 'text', +} + +export interface ActionMenuFormParameterOptions { + name: string + title: string + default?: string + description: string + required?: boolean + type?: ActionMenuFormInputType +} + +export class ActionMenuFormParameter { + public constructor(options: ActionMenuFormParameterOptions) { + if (options) { + this.name = options.name + this.title = options.title + this.default = options.default + this.description = options.description + this.required = options.required + this.type = options.type + } + } + + @IsString() + public name!: string + + @IsString() + public title!: string + + @IsString() + @IsOptional() + public default?: string + + @IsString() + public description!: string + + @IsBoolean() + @IsOptional() + public required?: boolean + + @IsEnum(ActionMenuFormInputType) + @IsOptional() + public type?: ActionMenuFormInputType +} diff --git a/packages/core/src/modules/action-menu/models/ActionMenuSelection.ts b/packages/core/src/modules/action-menu/models/ActionMenuSelection.ts new file mode 100644 index 0000000000..ff4299da6d --- /dev/null +++ b/packages/core/src/modules/action-menu/models/ActionMenuSelection.ts @@ -0,0 +1,22 @@ +import { IsOptional, IsString } from 'class-validator' + +export interface ActionMenuSelectionOptions { + name: string + params?: Record +} + +export class ActionMenuSelection { + public constructor(options: ActionMenuSelectionOptions) { + if (options) { + this.name = options.name + this.params = options.params + } + } + + @IsString() + public name!: string + + @IsString({ each: true }) + @IsOptional() + public params?: Record +} diff --git a/packages/core/src/modules/action-menu/models/index.ts b/packages/core/src/modules/action-menu/models/index.ts new file mode 100644 index 0000000000..15c8673f52 --- /dev/null +++ b/packages/core/src/modules/action-menu/models/index.ts @@ -0,0 +1,5 @@ +export * from './ActionMenu' +export * from './ActionMenuOption' +export * from './ActionMenuOptionForm' +export * from './ActionMenuOptionFormParameter' +export * from './ActionMenuSelection' diff --git a/packages/core/src/modules/action-menu/repository/ActionMenuRecord.ts b/packages/core/src/modules/action-menu/repository/ActionMenuRecord.ts new file mode 100644 index 0000000000..a5eb125fc0 --- /dev/null +++ b/packages/core/src/modules/action-menu/repository/ActionMenuRecord.ts @@ -0,0 +1,92 @@ +import type { TagsBase } from '../../../storage/BaseRecord' +import type { ActionMenuRole } from '../ActionMenuRole' +import type { ActionMenuState } from '../ActionMenuState' + +import { Type } from 'class-transformer' + +import { AriesFrameworkError } from '../../../error' +import { BaseRecord } from '../../../storage/BaseRecord' +import { uuid } from '../../../utils/uuid' +import { ActionMenuSelection, ActionMenu } from '../models' + +export interface ActionMenuRecordProps { + id?: string + state: ActionMenuState + role: ActionMenuRole + createdAt?: Date + connectionId: string + threadId: string + menu?: ActionMenu + performedAction?: ActionMenuSelection + tags?: CustomActionMenuTags +} + +export type CustomActionMenuTags = TagsBase + +export type DefaultActionMenuTags = { + role: ActionMenuRole + connectionId: string + threadId: string +} + +export class ActionMenuRecord + extends BaseRecord + implements ActionMenuRecordProps +{ + public state!: ActionMenuState + public role!: ActionMenuRole + public connectionId!: string + public threadId!: string + + @Type(() => ActionMenu) + public menu?: ActionMenu + + @Type(() => ActionMenuSelection) + public performedAction?: ActionMenuSelection + + public static readonly type = 'ActionMenuRecord' + public readonly type = ActionMenuRecord.type + + public constructor(props: ActionMenuRecordProps) { + super() + + if (props) { + this.id = props.id ?? uuid() + this.createdAt = props.createdAt ?? new Date() + this.connectionId = props.connectionId + this.threadId = props.threadId + this.state = props.state + this.role = props.role + this.menu = props.menu + this.performedAction = props.performedAction + this._tags = props.tags ?? {} + } + } + + public getTags() { + return { + ...this._tags, + role: this.role, + connectionId: this.connectionId, + threadId: this.threadId, + } + } + + public assertState(expectedStates: ActionMenuState | ActionMenuState[]) { + if (!Array.isArray(expectedStates)) { + expectedStates = [expectedStates] + } + + if (!expectedStates.includes(this.state)) { + throw new AriesFrameworkError( + `Action Menu record is in invalid state ${this.state}. Valid states are: ${expectedStates.join(', ')}.` + ) + } + } + + public assertRole(expectedRole: ActionMenuRole) { + if (this.role !== expectedRole) { + throw new AriesFrameworkError(`Action Menu record has invalid role ${this.role}. Expected role ${expectedRole}.`) + } + } +} diff --git a/packages/core/src/modules/action-menu/repository/ActionMenuRepository.ts b/packages/core/src/modules/action-menu/repository/ActionMenuRepository.ts new file mode 100644 index 0000000000..e22f014ec7 --- /dev/null +++ b/packages/core/src/modules/action-menu/repository/ActionMenuRepository.ts @@ -0,0 +1,17 @@ +import { EventEmitter } from '../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../constants' +import { inject, injectable } from '../../../plugins' +import { Repository } from '../../../storage/Repository' +import { StorageService } from '../../../storage/StorageService' + +import { ActionMenuRecord } from './ActionMenuRecord' + +@injectable() +export class ActionMenuRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(ActionMenuRecord, storageService, eventEmitter) + } +} diff --git a/packages/core/src/modules/action-menu/repository/index.ts b/packages/core/src/modules/action-menu/repository/index.ts new file mode 100644 index 0000000000..2c34741daf --- /dev/null +++ b/packages/core/src/modules/action-menu/repository/index.ts @@ -0,0 +1,2 @@ +export * from './ActionMenuRepository' +export * from './ActionMenuRecord' diff --git a/packages/core/src/modules/action-menu/services/ActionMenuService.ts b/packages/core/src/modules/action-menu/services/ActionMenuService.ts new file mode 100644 index 0000000000..f96387fa8e --- /dev/null +++ b/packages/core/src/modules/action-menu/services/ActionMenuService.ts @@ -0,0 +1,360 @@ +import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { Logger } from '../../../logger' +import type { ActionMenuStateChangedEvent } from '../ActionMenuEvents' +import type { ActionMenuProblemReportMessage } from '../messages' +import type { + ClearMenuOptions, + CreateMenuOptions, + CreatePerformOptions, + CreateRequestOptions, + FindMenuOptions, +} from './ActionMenuServiceOptions' + +import { AgentConfig } from '../../../agent/AgentConfig' +import { EventEmitter } from '../../../agent/EventEmitter' +import { AriesFrameworkError } from '../../../error' +import { injectable } from '../../../plugins' +import { JsonTransformer } from '../../../utils' +import { ActionMenuEventTypes } from '../ActionMenuEvents' +import { ActionMenuRole } from '../ActionMenuRole' +import { ActionMenuState } from '../ActionMenuState' +import { ActionMenuProblemReportError } from '../errors/ActionMenuProblemReportError' +import { ActionMenuProblemReportReason } from '../errors/ActionMenuProblemReportReason' +import { PerformMessage, MenuMessage, MenuRequestMessage } from '../messages' +import { ActionMenuSelection, ActionMenu } from '../models' +import { ActionMenuRepository, ActionMenuRecord } from '../repository' + +@injectable() +export class ActionMenuService { + private actionMenuRepository: ActionMenuRepository + private eventEmitter: EventEmitter + private logger: Logger + + public constructor(actionMenuRepository: ActionMenuRepository, agentConfig: AgentConfig, eventEmitter: EventEmitter) { + this.actionMenuRepository = actionMenuRepository + this.eventEmitter = eventEmitter + this.logger = agentConfig.logger + } + + public async createRequest(options: CreateRequestOptions) { + // Assert + options.connection.assertReady() + + // Create message + const menuRequestMessage = new MenuRequestMessage({}) + + // Create record if not existant for connection/role + let actionMenuRecord = await this.find({ + connectionId: options.connection.id, + role: ActionMenuRole.Requester, + }) + + if (actionMenuRecord) { + // Protocol will be restarted and menu cleared + const previousState = actionMenuRecord.state + actionMenuRecord.state = ActionMenuState.AwaitingRootMenu + actionMenuRecord.threadId = menuRequestMessage.id + actionMenuRecord.menu = undefined + actionMenuRecord.performedAction = undefined + + await this.actionMenuRepository.update(actionMenuRecord) + this.emitStateChangedEvent(actionMenuRecord, previousState) + } else { + actionMenuRecord = new ActionMenuRecord({ + connectionId: options.connection.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.AwaitingRootMenu, + threadId: menuRequestMessage.id, + }) + + await this.actionMenuRepository.save(actionMenuRecord) + this.emitStateChangedEvent(actionMenuRecord, null) + } + + return { message: menuRequestMessage, record: actionMenuRecord } + } + + public async processRequest(messageContext: InboundMessageContext) { + const { message: menuRequestMessage } = messageContext + + this.logger.debug(`Processing menu request with id ${menuRequestMessage.id}`) + + // Assert + const connection = messageContext.assertReadyConnection() + + let actionMenuRecord = await this.find({ + connectionId: connection.id, + role: ActionMenuRole.Responder, + }) + + if (actionMenuRecord) { + // Protocol will be restarted and menu cleared + const previousState = actionMenuRecord.state + actionMenuRecord.state = ActionMenuState.PreparingRootMenu + actionMenuRecord.threadId = menuRequestMessage.id + actionMenuRecord.menu = undefined + actionMenuRecord.performedAction = undefined + + await this.actionMenuRepository.update(actionMenuRecord) + this.emitStateChangedEvent(actionMenuRecord, previousState) + } else { + // Create record + actionMenuRecord = new ActionMenuRecord({ + connectionId: connection.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.PreparingRootMenu, + threadId: menuRequestMessage.id, + }) + + await this.actionMenuRepository.save(actionMenuRecord) + this.emitStateChangedEvent(actionMenuRecord, null) + } + + return actionMenuRecord + } + + public async createMenu(options: CreateMenuOptions) { + // Assert connection ready + options.connection.assertReady() + + const uniqueNames = new Set(options.menu.options.map((v) => v.name)) + if (uniqueNames.size < options.menu.options.length) { + throw new AriesFrameworkError('Action Menu contains duplicated options') + } + + // Create message + const menuMessage = new MenuMessage({ + title: options.menu.title, + description: options.menu.description, + options: options.menu.options, + }) + + // Check if there is an existing menu for this connection and role + let actionMenuRecord = await this.find({ + connectionId: options.connection.id, + role: ActionMenuRole.Responder, + }) + + // If so, continue existing flow + if (actionMenuRecord) { + actionMenuRecord.assertState([ActionMenuState.Null, ActionMenuState.PreparingRootMenu, ActionMenuState.Done]) + // The new menu will be bound to the existing thread + // unless it is in null state (protocol reset) + if (actionMenuRecord.state !== ActionMenuState.Null) { + menuMessage.setThread({ threadId: actionMenuRecord.threadId }) + } + + const previousState = actionMenuRecord.state + actionMenuRecord.menu = options.menu + actionMenuRecord.state = ActionMenuState.AwaitingSelection + actionMenuRecord.threadId = menuMessage.threadId + + await this.actionMenuRepository.update(actionMenuRecord) + this.emitStateChangedEvent(actionMenuRecord, previousState) + } else { + // Create record + actionMenuRecord = new ActionMenuRecord({ + connectionId: options.connection.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.AwaitingSelection, + menu: options.menu, + threadId: menuMessage.id, + }) + + await this.actionMenuRepository.save(actionMenuRecord) + this.emitStateChangedEvent(actionMenuRecord, null) + } + + return { message: menuMessage, record: actionMenuRecord } + } + + public async processMenu(messageContext: InboundMessageContext) { + const { message: menuMessage } = messageContext + + this.logger.debug(`Processing action menu with id ${menuMessage.id}`) + + // Assert + const connection = messageContext.assertReadyConnection() + + // Check if there is an existing menu for this connection and role + const record = await this.find({ + connectionId: connection.id, + role: ActionMenuRole.Requester, + }) + + if (record) { + // Record found: update with menu details + const previousState = record.state + + record.state = ActionMenuState.PreparingSelection + record.menu = new ActionMenu({ + title: menuMessage.title, + description: menuMessage.description, + options: menuMessage.options, + }) + record.threadId = menuMessage.threadId + record.performedAction = undefined + + await this.actionMenuRepository.update(record) + + this.emitStateChangedEvent(record, previousState) + } else { + // Record not found: create it + const actionMenuRecord = new ActionMenuRecord({ + connectionId: connection.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: menuMessage.id, + menu: new ActionMenu({ + title: menuMessage.title, + description: menuMessage.description, + options: menuMessage.options, + }), + }) + + await this.actionMenuRepository.save(actionMenuRecord) + + this.emitStateChangedEvent(actionMenuRecord, null) + } + } + + public async createPerform(options: CreatePerformOptions) { + const { actionMenuRecord: record, performedAction: performedSelection } = options + + // Assert + record.assertRole(ActionMenuRole.Requester) + record.assertState([ActionMenuState.PreparingSelection]) + + const validSelection = record.menu?.options.some((item) => item.name === performedSelection.name) + if (!validSelection) { + throw new AriesFrameworkError('Selection does not match valid actions') + } + + const previousState = record.state + + // Create message + const menuMessage = new PerformMessage({ + name: performedSelection.name, + params: performedSelection.params, + threadId: record.threadId, + }) + + // Update record + record.performedAction = options.performedAction + record.state = ActionMenuState.Done + + await this.actionMenuRepository.update(record) + + this.emitStateChangedEvent(record, previousState) + + return { message: menuMessage, record } + } + + public async processPerform(messageContext: InboundMessageContext) { + const { message: performMessage } = messageContext + + this.logger.debug(`Processing action menu perform with id ${performMessage.id}`) + + const connection = messageContext.assertReadyConnection() + + // Check if there is an existing menu for this connection and role + const record = await this.find({ + connectionId: connection.id, + role: ActionMenuRole.Responder, + threadId: performMessage.threadId, + }) + + if (record) { + // Record found: check state and update with menu details + + // A Null state means that menu has been cleared by the responder. + // Requester should be informed in order to request another menu + if (record.state === ActionMenuState.Null) { + throw new ActionMenuProblemReportError('Action Menu has been cleared by the responder', { + problemCode: ActionMenuProblemReportReason.Timeout, + }) + } + record.assertState([ActionMenuState.AwaitingSelection]) + + const validSelection = record.menu?.options.some((item) => item.name === performMessage.name) + if (!validSelection) { + throw new AriesFrameworkError('Selection does not match valid actions') + } + + const previousState = record.state + + record.state = ActionMenuState.Done + record.performedAction = new ActionMenuSelection({ name: performMessage.name, params: performMessage.params }) + + await this.actionMenuRepository.update(record) + + this.emitStateChangedEvent(record, previousState) + } else { + throw new AriesFrameworkError(`No Action Menu found with thread id ${messageContext.message.threadId}`) + } + } + + public async clearMenu(options: ClearMenuOptions) { + const { actionMenuRecord: record } = options + + const previousState = record.state + + // Update record + record.state = ActionMenuState.Null + record.menu = undefined + record.performedAction = undefined + + await this.actionMenuRepository.update(record) + + this.emitStateChangedEvent(record, previousState) + + return record + } + + public async processProblemReport( + messageContext: InboundMessageContext + ): Promise { + const { message: actionMenuProblemReportMessage } = messageContext + + const connection = messageContext.assertReadyConnection() + + this.logger.debug(`Processing problem report with id ${actionMenuProblemReportMessage.id}`) + + const actionMenuRecord = await this.find({ + role: ActionMenuRole.Requester, + connectionId: connection.id, + }) + + if (!actionMenuRecord) { + throw new AriesFrameworkError( + `Unable to process action menu problem: record not found for connection id ${connection.id}` + ) + } + // Clear menu to restart flow + return await this.clearMenu({ actionMenuRecord }) + } + + public async findById(actionMenuRecordId: string) { + return await this.actionMenuRepository.findById(actionMenuRecordId) + } + + public async find(options: FindMenuOptions) { + return await this.actionMenuRepository.findSingleByQuery({ + connectionId: options.connectionId, + role: options.role, + threadId: options.threadId, + }) + } + + private emitStateChangedEvent(actionMenuRecord: ActionMenuRecord, previousState: ActionMenuState | null) { + const clonedRecord = JsonTransformer.clone(actionMenuRecord) + + this.eventEmitter.emit({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + actionMenuRecord: clonedRecord, + previousState: previousState, + }, + }) + } +} diff --git a/packages/core/src/modules/action-menu/services/ActionMenuServiceOptions.ts b/packages/core/src/modules/action-menu/services/ActionMenuServiceOptions.ts new file mode 100644 index 0000000000..733a6d0c76 --- /dev/null +++ b/packages/core/src/modules/action-menu/services/ActionMenuServiceOptions.ts @@ -0,0 +1,29 @@ +import type { ConnectionRecord } from '../../connections' +import type { ActionMenuRole } from '../ActionMenuRole' +import type { ActionMenuSelection } from '../models' +import type { ActionMenu } from '../models/ActionMenu' +import type { ActionMenuRecord } from '../repository' + +export interface CreateRequestOptions { + connection: ConnectionRecord +} + +export interface CreateMenuOptions { + connection: ConnectionRecord + menu: ActionMenu +} + +export interface CreatePerformOptions { + actionMenuRecord: ActionMenuRecord + performedAction: ActionMenuSelection +} + +export interface ClearMenuOptions { + actionMenuRecord: ActionMenuRecord +} + +export interface FindMenuOptions { + connectionId: string + role: ActionMenuRole + threadId?: string +} diff --git a/packages/core/src/modules/action-menu/services/__tests__/ActionMenuService.test.ts b/packages/core/src/modules/action-menu/services/__tests__/ActionMenuService.test.ts new file mode 100644 index 0000000000..ab5b5cec85 --- /dev/null +++ b/packages/core/src/modules/action-menu/services/__tests__/ActionMenuService.test.ts @@ -0,0 +1,810 @@ +import type { AgentConfig } from '../../../../agent/AgentConfig' +import type { Repository } from '../../../../storage/Repository' +import type { ActionMenuStateChangedEvent } from '../../ActionMenuEvents' +import type { ActionMenuSelection } from '../../models' + +import { getAgentConfig, getMockConnection, mockFunction } from '../../../../../tests/helpers' +import { EventEmitter } from '../../../../agent/EventEmitter' +import { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import { DidExchangeState } from '../../../connections' +import { ActionMenuEventTypes } from '../../ActionMenuEvents' +import { ActionMenuRole } from '../../ActionMenuRole' +import { ActionMenuState } from '../../ActionMenuState' +import { ActionMenuProblemReportError } from '../../errors/ActionMenuProblemReportError' +import { ActionMenuProblemReportReason } from '../../errors/ActionMenuProblemReportReason' +import { MenuMessage, MenuRequestMessage, PerformMessage } from '../../messages' +import { ActionMenu } from '../../models' +import { ActionMenuRecord, ActionMenuRepository } from '../../repository' +import { ActionMenuService } from '../ActionMenuService' + +jest.mock('../../repository/ActionMenuRepository') +const ActionMenuRepositoryMock = ActionMenuRepository as jest.Mock + +describe('ActionMenuService', () => { + const mockConnectionRecord = getMockConnection({ + id: 'd3849ac3-c981-455b-a1aa-a10bea6cead8', + did: 'did:sov:C2SsBf5QUQpqSAQfhu3sd2', + state: DidExchangeState.Completed, + }) + + let actionMenuRepository: Repository + let actionMenuService: ActionMenuService + let eventEmitter: EventEmitter + let agentConfig: AgentConfig + + const mockActionMenuRecord = (options: { + connectionId: string + role: ActionMenuRole + state: ActionMenuState + threadId: string + menu?: ActionMenu + performedAction?: ActionMenuSelection + }) => { + return new ActionMenuRecord({ + connectionId: options.connectionId, + role: options.role, + state: options.state, + threadId: options.threadId, + menu: options.menu, + performedAction: options.performedAction, + }) + } + + beforeAll(async () => { + agentConfig = getAgentConfig('ActionMenuServiceTest') + }) + + beforeEach(async () => { + actionMenuRepository = new ActionMenuRepositoryMock() + eventEmitter = new EventEmitter(agentConfig) + actionMenuService = new ActionMenuService(actionMenuRepository, agentConfig, eventEmitter) + }) + + describe('createMenu', () => { + let testMenu: ActionMenu + + beforeAll(() => { + testMenu = new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [{ name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }], + }) + }) + + it(`throws an error when duplicated options are specified`, async () => { + expect( + actionMenuService.createMenu({ + connection: mockConnectionRecord, + menu: { + title: 'menu-title', + description: 'menu-description', + options: [ + { name: 'opt1', description: 'desc1', title: 'title1' }, + { name: 'opt2', description: 'desc2', title: 'title2' }, + { name: 'opt1', description: 'desc3', title: 'title3' }, + { name: 'opt4', description: 'desc4', title: 'title4' }, + ], + }, + }) + ).rejects.toThrowError('Action Menu contains duplicated options') + }) + + it(`no previous menu: emits a menu with title, description and options`, async () => { + // No previous menu + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(null)) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + await actionMenuService.createMenu({ + connection: mockConnectionRecord, + menu: testMenu, + }) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + previousState: null, + actionMenuRecord: expect.objectContaining({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.AwaitingSelection, + menu: expect.objectContaining({ + description: 'menu-description', + title: 'menu-title', + options: [{ name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }], + }), + }), + }, + }) + }) + + it(`existing menu: emits a menu with title, description, options and thread`, async () => { + // Previous menu is in Done state + const previousMenuDone = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.Done, + threadId: 'threadId-1', + }) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(previousMenuDone)) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + await actionMenuService.createMenu({ + connection: mockConnectionRecord, + menu: testMenu, + }) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + previousState: ActionMenuState.Done, + actionMenuRecord: expect.objectContaining({ + connectionId: mockConnectionRecord.id, + threadId: 'threadId-1', + role: ActionMenuRole.Responder, + state: ActionMenuState.AwaitingSelection, + menu: expect.objectContaining({ + description: 'menu-description', + title: 'menu-title', + options: [{ name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }], + }), + }), + }, + }) + }) + + it(`existing menu, cleared: emits a menu with title, description, options and new thread`, async () => { + // Previous menu is in Done state + const previousMenuClear = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.Null, + threadId: 'threadId-1', + }) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(previousMenuClear)) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + await actionMenuService.createMenu({ + connection: mockConnectionRecord, + menu: testMenu, + }) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + previousState: ActionMenuState.Null, + actionMenuRecord: expect.objectContaining({ + connectionId: mockConnectionRecord.id, + threadId: expect.not.stringMatching('threadId-1'), + role: ActionMenuRole.Responder, + state: ActionMenuState.AwaitingSelection, + menu: expect.objectContaining({ + description: 'menu-description', + title: 'menu-title', + options: [{ name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }], + }), + }), + }, + }) + }) + }) + + describe('createPerform', () => { + let mockRecord: ActionMenuRecord + + beforeEach(() => { + const testMenu = new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }) + + mockRecord = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: '123', + menu: testMenu, + }) + }) + + it(`throws an error when invalid selection is provided`, async () => { + expect( + actionMenuService.createPerform({ actionMenuRecord: mockRecord, performedAction: { name: 'fake' } }) + ).rejects.toThrowError('Selection does not match valid actions') + }) + + it(`throws an error when state is not preparing-selection`, async () => { + for (const state of Object.values(ActionMenuState).filter( + (state) => state !== ActionMenuState.PreparingSelection + )) { + mockRecord.state = state + expect( + actionMenuService.createPerform({ actionMenuRecord: mockRecord, performedAction: { name: 'opt1' } }) + ).rejects.toThrowError( + `Action Menu record is in invalid state ${state}. Valid states are: preparing-selection.` + ) + } + }) + + it(`emits a menu with a valid selection and action menu record`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + await actionMenuService.createPerform({ + actionMenuRecord: mockRecord, + performedAction: { name: 'opt2' }, + }) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + previousState: ActionMenuState.PreparingSelection, + actionMenuRecord: expect.objectContaining({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.Done, + performedAction: { name: 'opt2' }, + }), + }, + }) + }) + }) + + describe('createRequest', () => { + let mockRecord: ActionMenuRecord + + beforeEach(() => { + const testMenu = new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }) + + mockRecord = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: '123', + menu: testMenu, + }) + }) + + it(`no existing record: emits event and creates new request and record`, async () => { + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(null)) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + const { message, record } = await actionMenuService.createRequest({ + connection: mockConnectionRecord, + }) + + const expectedRecord = { + id: expect.any(String), + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + threadId: message.threadId, + state: ActionMenuState.AwaitingRootMenu, + menu: undefined, + performedAction: undefined, + } + expect(record).toMatchObject(expectedRecord) + + expect(actionMenuRepository.save).toHaveBeenCalledWith(expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.update).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + previousState: null, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + }) + + it(`already existing record: emits event, creates new request and updates record`, async () => { + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + const previousState = mockRecord.state + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + const { message, record } = await actionMenuService.createRequest({ + connection: mockConnectionRecord, + }) + + const expectedRecord = { + id: expect.any(String), + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + threadId: message.threadId, + state: ActionMenuState.AwaitingRootMenu, + menu: undefined, + performedAction: undefined, + } + expect(record).toMatchObject(expectedRecord) + + expect(actionMenuRepository.update).toHaveBeenCalledWith(expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.save).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + previousState, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + }) + }) + + describe('clearMenu', () => { + let mockRecord: ActionMenuRecord + + beforeEach(() => { + const testMenu = new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }) + + mockRecord = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: '123', + menu: testMenu, + performedAction: { name: 'opt1' }, + }) + }) + + it(`requester role: emits a cleared menu`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockRecord.role = ActionMenuRole.Requester + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + await actionMenuService.clearMenu({ + actionMenuRecord: mockRecord, + }) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + previousState: ActionMenuState.PreparingSelection, + actionMenuRecord: expect.objectContaining({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.Null, + menu: undefined, + performedAction: undefined, + }), + }, + }) + }) + + it(`responder role: emits a cleared menu`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockRecord.state = ActionMenuState.AwaitingSelection + mockRecord.role = ActionMenuRole.Responder + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + await actionMenuService.clearMenu({ + actionMenuRecord: mockRecord, + }) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + previousState: ActionMenuState.AwaitingSelection, + actionMenuRecord: expect.objectContaining({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.Null, + menu: undefined, + performedAction: undefined, + }), + }, + }) + }) + }) + + describe('processMenu', () => { + let mockRecord: ActionMenuRecord + let mockMenuMessage: MenuMessage + + beforeEach(() => { + mockRecord = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: '123', + menu: new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }), + performedAction: { name: 'opt1' }, + }) + + mockMenuMessage = new MenuMessage({ + title: 'incoming title', + description: 'incoming description', + options: [ + { + title: 'incoming option 1 title', + description: 'incoming option 1 description', + name: 'incoming option 1 name', + }, + ], + }) + }) + + it(`emits event and creates record when no previous record`, async () => { + const messageContext = new InboundMessageContext(mockMenuMessage, { connection: mockConnectionRecord }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(null)) + + await actionMenuService.processMenu(messageContext) + + const expectedRecord = { + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: messageContext.message.threadId, + menu: expect.objectContaining({ + title: 'incoming title', + description: 'incoming description', + options: [ + { + title: 'incoming option 1 title', + description: 'incoming option 1 description', + name: 'incoming option 1 name', + }, + ], + }), + performedAction: undefined, + } + + expect(actionMenuRepository.save).toHaveBeenCalledWith(expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.update).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + previousState: null, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + }) + + it(`emits event and updates record when existing record`, async () => { + const messageContext = new InboundMessageContext(mockMenuMessage, { connection: mockConnectionRecord }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + // It should accept any previous state + for (const state of Object.values(ActionMenuState)) { + mockRecord.state = state + const previousState = state + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + await actionMenuService.processMenu(messageContext) + + const expectedRecord = { + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: messageContext.message.threadId, + menu: expect.objectContaining({ + title: 'incoming title', + description: 'incoming description', + options: [ + { + title: 'incoming option 1 title', + description: 'incoming option 1 description', + name: 'incoming option 1 name', + }, + ], + }), + performedAction: undefined, + } + + expect(actionMenuRepository.update).toHaveBeenCalledWith(expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.save).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + previousState, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + } + }) + }) + + describe('processPerform', () => { + let mockRecord: ActionMenuRecord + + beforeEach(() => { + mockRecord = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.AwaitingSelection, + threadId: '123', + menu: new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }), + }) + }) + + it(`emits event and saves record when valid selection and thread Id`, async () => { + const mockPerformMessage = new PerformMessage({ + name: 'opt1', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(mockPerformMessage, { connection: mockConnectionRecord }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + await actionMenuService.processPerform(messageContext) + + const expectedRecord = { + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.Done, + threadId: messageContext.message.threadId, + menu: expect.objectContaining({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }), + performedAction: { name: 'opt1' }, + } + + expect(actionMenuRepository.findSingleByQuery).toHaveBeenCalledWith( + expect.objectContaining({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + threadId: messageContext.message.threadId, + }) + ) + expect(actionMenuRepository.update).toHaveBeenCalledWith(expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.save).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + previousState: ActionMenuState.AwaitingSelection, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + }) + + it(`throws error when invalid selection`, async () => { + const mockPerformMessage = new PerformMessage({ + name: 'fake', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(mockPerformMessage, { connection: mockConnectionRecord }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + expect(actionMenuService.processPerform(messageContext)).rejects.toThrowError( + 'Selection does not match valid actions' + ) + + expect(actionMenuRepository.update).not.toHaveBeenCalled() + expect(actionMenuRepository.save).not.toHaveBeenCalled() + expect(eventListenerMock).not.toHaveBeenCalled() + }) + + it(`throws error when record not found`, async () => { + const mockPerformMessage = new PerformMessage({ + name: 'opt1', + threadId: '122', + }) + + const messageContext = new InboundMessageContext(mockPerformMessage, { connection: mockConnectionRecord }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(null)) + + expect(actionMenuService.processPerform(messageContext)).rejects.toThrowError( + `No Action Menu found with thread id ${mockPerformMessage.threadId}` + ) + + expect(actionMenuRepository.update).not.toHaveBeenCalled() + expect(actionMenuRepository.save).not.toHaveBeenCalled() + expect(eventListenerMock).not.toHaveBeenCalled() + }) + + it(`throws error when invalid state`, async () => { + const mockPerformMessage = new PerformMessage({ + name: 'opt1', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(mockPerformMessage, { connection: mockConnectionRecord }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockRecord.state = ActionMenuState.Done + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + expect(actionMenuService.processPerform(messageContext)).rejects.toThrowError( + `Action Menu record is in invalid state ${mockRecord.state}. Valid states are: ${ActionMenuState.AwaitingSelection}.` + ) + + expect(actionMenuRepository.update).not.toHaveBeenCalled() + expect(actionMenuRepository.save).not.toHaveBeenCalled() + expect(eventListenerMock).not.toHaveBeenCalled() + }) + + it(`throws problem report error when menu has been cleared`, async () => { + const mockPerformMessage = new PerformMessage({ + name: 'opt1', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(mockPerformMessage, { connection: mockConnectionRecord }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockRecord.state = ActionMenuState.Null + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + expect(actionMenuService.processPerform(messageContext)).rejects.toThrowError( + new ActionMenuProblemReportError('Action Menu has been cleared by the responder', { + problemCode: ActionMenuProblemReportReason.Timeout, + }) + ) + + expect(actionMenuRepository.update).not.toHaveBeenCalled() + expect(actionMenuRepository.save).not.toHaveBeenCalled() + expect(eventListenerMock).not.toHaveBeenCalled() + }) + }) + + describe('processRequest', () => { + let mockRecord: ActionMenuRecord + let mockMenuRequestMessage: MenuRequestMessage + + beforeEach(() => { + mockRecord = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.PreparingRootMenu, + threadId: '123', + menu: new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }), + performedAction: { name: 'opt1' }, + }) + + mockMenuRequestMessage = new MenuRequestMessage({}) + }) + + it(`emits event and creates record when no previous record`, async () => { + const messageContext = new InboundMessageContext(mockMenuRequestMessage, { connection: mockConnectionRecord }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(null)) + + await actionMenuService.processRequest(messageContext) + + const expectedRecord = { + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.PreparingRootMenu, + threadId: messageContext.message.threadId, + menu: undefined, + performedAction: undefined, + } + + expect(actionMenuRepository.save).toHaveBeenCalledWith(expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.update).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + previousState: null, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + }) + + it(`emits event and updates record when existing record`, async () => { + const messageContext = new InboundMessageContext(mockMenuRequestMessage, { connection: mockConnectionRecord }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + // It should accept any previous state + for (const state of Object.values(ActionMenuState)) { + mockRecord.state = state + const previousState = state + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + await actionMenuService.processRequest(messageContext) + + const expectedRecord = { + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.PreparingRootMenu, + threadId: messageContext.message.threadId, + menu: undefined, + performedAction: undefined, + } + + expect(actionMenuRepository.update).toHaveBeenCalledWith(expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.save).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + previousState, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + } + }) + }) +}) diff --git a/packages/core/src/modules/action-menu/services/index.ts b/packages/core/src/modules/action-menu/services/index.ts new file mode 100644 index 0000000000..83362466e7 --- /dev/null +++ b/packages/core/src/modules/action-menu/services/index.ts @@ -0,0 +1,2 @@ +export * from './ActionMenuService' +export * from './ActionMenuServiceOptions' From f0ca8b6346385fc8c4811fbd531aa25a386fcf30 Mon Sep 17 00:00:00 2001 From: Niall Shaw <100220424+niallshaw-absa@users.noreply.github.com> Date: Tue, 6 Sep 2022 11:45:58 +0200 Subject: [PATCH 028/125] fix(ledger): remove poolConnected on pool close (#1011) Signed-off-by: Niall Shaw --- packages/core/src/modules/ledger/IndyPool.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/modules/ledger/IndyPool.ts b/packages/core/src/modules/ledger/IndyPool.ts index 19127dc946..27e87a2278 100644 --- a/packages/core/src/modules/ledger/IndyPool.ts +++ b/packages/core/src/modules/ledger/IndyPool.ts @@ -61,6 +61,7 @@ export class IndyPool { } this._poolHandle = undefined + this.poolConnected = undefined await this.indy.closePoolLedger(poolHandle) } From 4ca56f6b677f45aa96c91b5c5ee8df210722609e Mon Sep 17 00:00:00 2001 From: jakubkoci Date: Tue, 6 Sep 2022 14:50:05 +0200 Subject: [PATCH 029/125] fix(ledger): check taa version instad of aml version (#1013) Signed-off-by: Jakub Koci --- .../ledger/__tests__/IndyLedgerService.test.ts | 12 ++++++------ .../src/modules/ledger/services/IndyLedgerService.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts b/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts index 0929cad3dd..023cfe09d5 100644 --- a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts +++ b/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts @@ -21,7 +21,7 @@ const pools: IndyPoolConfig[] = [ id: 'sovrinMain', isProduction: true, genesisTransactions: 'xxx', - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + transactionAuthorAgreement: { version: '1.0', acceptanceMechanism: 'accept' }, }, ] @@ -67,7 +67,7 @@ describe('IndyLedgerService', () => { // @ts-ignore jest.spyOn(ledgerService, 'getTransactionAuthorAgreement').mockResolvedValue({ digest: 'abcde', - version: 'abdcg', + version: '2.0', text: 'jhsdhbv', ratification_ts: 12345678, acceptanceMechanisms: { @@ -84,7 +84,7 @@ describe('IndyLedgerService', () => { 'Heinz57' ) ).rejects.toThrowError( - 'Unable to satisfy matching TAA with mechanism "accept" and version "1" in pool.\n Found ["accept"] and version 3 in pool.' + 'Unable to satisfy matching TAA with mechanism "accept" and version "1.0" in pool.\n Found ["accept"] and version 2.0 in pool.' ) }) @@ -93,7 +93,7 @@ describe('IndyLedgerService', () => { // @ts-ignore jest.spyOn(ledgerService, 'getTransactionAuthorAgreement').mockResolvedValue({ digest: 'abcde', - version: 'abdcg', + version: '1.0', text: 'jhsdhbv', ratification_ts: 12345678, acceptanceMechanisms: { @@ -110,7 +110,7 @@ describe('IndyLedgerService', () => { 'Heinz57' ) ).rejects.toThrowError( - 'Unable to satisfy matching TAA with mechanism "accept" and version "1" in pool.\n Found ["decline"] and version 1 in pool.' + 'Unable to satisfy matching TAA with mechanism "accept" and version "1.0" in pool.\n Found ["decline"] and version 1.0 in pool.' ) }) @@ -123,7 +123,7 @@ describe('IndyLedgerService', () => { // @ts-ignore jest.spyOn(ledgerService, 'getTransactionAuthorAgreement').mockResolvedValue({ digest: 'abcde', - version: 'abdcg', + version: '1.0', text: 'jhsdhbv', ratification_ts: 12345678, acceptanceMechanisms: { diff --git a/packages/core/src/modules/ledger/services/IndyLedgerService.ts b/packages/core/src/modules/ledger/services/IndyLedgerService.ts index 60363f628e..72d7a36dc3 100644 --- a/packages/core/src/modules/ledger/services/IndyLedgerService.ts +++ b/packages/core/src/modules/ledger/services/IndyLedgerService.ts @@ -471,7 +471,7 @@ export class IndyLedgerService { // Throw an error if the pool doesn't have the specified version and acceptance mechanism if ( - authorAgreement.acceptanceMechanisms.version !== taa.version || + authorAgreement.version !== taa.version || !(taa.acceptanceMechanism in authorAgreement.acceptanceMechanisms.aml) ) { // Throw an error with a helpful message @@ -479,7 +479,7 @@ export class IndyLedgerService { taa.acceptanceMechanism )} and version ${JSON.stringify(taa.version)} in pool.\n Found ${JSON.stringify( Object.keys(authorAgreement.acceptanceMechanisms.aml) - )} and version ${authorAgreement.acceptanceMechanisms.version} in pool.` + )} and version ${authorAgreement.version} in pool.` throw new LedgerError(errMessage) } From 856f40de36ae223e2f5ec2b77d873276911541f8 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 7 Sep 2022 13:07:35 +0200 Subject: [PATCH 030/125] chore: add @janrtvld to maintainers (#1016) Signed-off-by: Timo Glastra --- MAINTAINERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 1d626e75a5..ff84db7f6e 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -10,3 +10,4 @@ | Karim Stekelenburg | [@karimStekelenburg](https://github.com/karimStekelenburg) | ssi_karim#3505 | | Timo Glastra | [@TimoGlastra](https://github.com/TimoGlastra) | TimoGlastra#2988 | | Ariel Gentile | [@genaris](https://github.com/genaris) | GenAris#4962 | +| Jan Rietveld | [@janrtvld](https://github.com/janrtvld) | janrtvld#3868 | From 543437cd94d3023139b259ee04d6ad51cf653794 Mon Sep 17 00:00:00 2001 From: Sergi Garreta Serra Date: Thu, 8 Sep 2022 07:55:20 -0700 Subject: [PATCH 031/125] feat(routing): add settings to control back off strategy on mediator reconnection (#1017) Signed-off-by: Sergi Garreta --- packages/core/src/agent/AgentConfig.ts | 8 +++++ .../src/modules/routing/RecipientModule.ts | 29 ++++++++++++++----- .../core/src/transport/TransportEventTypes.ts | 9 ++++++ .../core/src/transport/WsOutboundTransport.ts | 10 ++++++- packages/core/src/types.ts | 2 ++ 5 files changed, 50 insertions(+), 8 deletions(-) diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index bb8ca24b56..30766aa659 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -83,6 +83,14 @@ export class AgentConfig { return this.initConfig.maximumMessagePickup ?? 10 } + public get baseMediatorReconnectionIntervalMs() { + return this.initConfig.baseMediatorReconnectionIntervalMs ?? 100 + } + + public get maximumMediatorReconnectionIntervalMs() { + return this.initConfig.maximumMediatorReconnectionIntervalMs ?? Number.POSITIVE_INFINITY + } + public get endpoints(): [string, ...string[]] { // if endpoints is not set, return queue endpoint // https://github.com/hyperledger/aries-rfcs/issues/405#issuecomment-582612875 diff --git a/packages/core/src/modules/routing/RecipientModule.ts b/packages/core/src/modules/routing/RecipientModule.ts index c4b1f6aec2..2fbc66514f 100644 --- a/packages/core/src/modules/routing/RecipientModule.ts +++ b/packages/core/src/modules/routing/RecipientModule.ts @@ -1,6 +1,6 @@ import type { Logger } from '../../logger' import type { DependencyManager } from '../../plugins' -import type { OutboundWebSocketClosedEvent } from '../../transport' +import type { OutboundWebSocketClosedEvent, OutboundWebSocketOpenedEvent } from '../../transport' import type { OutboundMessage } from '../../types' import type { ConnectionRecord } from '../connections' import type { MediationStateChangedEvent } from './RoutingEvents' @@ -141,14 +141,27 @@ export class RecipientModule { } private async openWebSocketAndPickUp(mediator: MediationRecord, pickupStrategy: MediatorPickupStrategy) { - let interval = 50 + const { baseMediatorReconnectionIntervalMs, maximumMediatorReconnectionIntervalMs } = this.agentConfig + let interval = baseMediatorReconnectionIntervalMs + + const stopConditions$ = merge(this.agentConfig.stop$, this.stopMessagePickup$).pipe() + + // Reset back off interval when the websocket is successfully opened again + this.eventEmitter + .observable(TransportEventTypes.OutboundWebSocketOpenedEvent) + .pipe( + // Stop when the agent shuts down or stop message pickup signal is received + takeUntil(stopConditions$), + filter((e) => e.payload.connectionId === mediator.connectionId) + ) + .subscribe(() => { + interval = baseMediatorReconnectionIntervalMs + }) // Listens to Outbound websocket closed events and will reopen the websocket connection // in a recursive back off strategy if it matches the following criteria: // - Agent is not shutdown // - Socket was for current mediator connection id - - const stopConditions$ = merge(this.agentConfig.stop$, this.stopMessagePickup$).pipe() this.eventEmitter .observable(TransportEventTypes.OutboundWebSocketClosedEvent) .pipe( @@ -157,10 +170,12 @@ export class RecipientModule { filter((e) => e.payload.connectionId === mediator.connectionId), // Make sure we're not reconnecting multiple times throttleTime(interval), - // Increase the interval (recursive back-off) - tap(() => (interval *= 2)), // Wait for interval time before reconnecting - delayWhen(() => timer(interval)) + delayWhen(() => timer(interval)), + // Increase the interval (recursive back-off) + tap(() => { + interval = Math.min(interval * 2, maximumMediatorReconnectionIntervalMs) + }) ) .subscribe({ next: async () => { diff --git a/packages/core/src/transport/TransportEventTypes.ts b/packages/core/src/transport/TransportEventTypes.ts index b0777883e1..8916724e86 100644 --- a/packages/core/src/transport/TransportEventTypes.ts +++ b/packages/core/src/transport/TransportEventTypes.ts @@ -2,6 +2,7 @@ import type { BaseEvent } from '../agent/Events' export enum TransportEventTypes { OutboundWebSocketClosedEvent = 'OutboundWebSocketClosedEvent', + OutboundWebSocketOpenedEvent = 'OutboundWebSocketOpenedEvent', } export interface OutboundWebSocketClosedEvent extends BaseEvent { @@ -11,3 +12,11 @@ export interface OutboundWebSocketClosedEvent extends BaseEvent { connectionId?: string } } + +export interface OutboundWebSocketOpenedEvent extends BaseEvent { + type: TransportEventTypes.OutboundWebSocketOpenedEvent + payload: { + socketId: string + connectionId?: string + } +} diff --git a/packages/core/src/transport/WsOutboundTransport.ts b/packages/core/src/transport/WsOutboundTransport.ts index 98f351493b..d614ccf365 100644 --- a/packages/core/src/transport/WsOutboundTransport.ts +++ b/packages/core/src/transport/WsOutboundTransport.ts @@ -3,7 +3,7 @@ import type { AgentMessageReceivedEvent } from '../agent/Events' import type { Logger } from '../logger' import type { OutboundPackage } from '../types' import type { OutboundTransport } from './OutboundTransport' -import type { OutboundWebSocketClosedEvent } from './TransportEventTypes' +import type { OutboundWebSocketClosedEvent, OutboundWebSocketOpenedEvent } from './TransportEventTypes' import type WebSocket from 'ws' import { AgentConfig } from '../agent/AgentConfig' @@ -139,6 +139,14 @@ export class WsOutboundTransport implements OutboundTransport { socket.onopen = () => { this.logger.debug(`Successfully connected to WebSocket ${endpoint}`) resolve(socket) + + this.eventEmitter.emit({ + type: TransportEventTypes.OutboundWebSocketOpenedEvent, + payload: { + socketId, + connectionId: connectionId, + }, + }) } socket.onerror = (error) => { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 6b29421d2f..34aa8bc71c 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -75,6 +75,8 @@ export interface InitConfig { mediatorPollingInterval?: number mediatorPickupStrategy?: MediatorPickupStrategy maximumMessagePickup?: number + baseMediatorReconnectionIntervalMs?: number + maximumMediatorReconnectionIntervalMs?: number useLegacyDidSovPrefix?: boolean connectionImageUrl?: string From dba46c3bc3a1d6b5669f296f0c45cd03dc2294b1 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 9 Sep 2022 08:47:52 +0200 Subject: [PATCH 032/125] feat(proofs): delete associated didcomm messages (#1021) Signed-off-by: Timo Glastra --- .../credentials/CredentialServiceOptions.ts | 4 ++-- .../core/src/modules/proofs/ProofService.ts | 20 +++++++++++++++++++ packages/core/src/modules/proofs/ProofsApi.ts | 6 ++++-- .../proofs/models/ProofServiceOptions.ts | 4 ++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/core/src/modules/credentials/CredentialServiceOptions.ts b/packages/core/src/modules/credentials/CredentialServiceOptions.ts index e5326d1f72..49d8f623d8 100644 --- a/packages/core/src/modules/credentials/CredentialServiceOptions.ts +++ b/packages/core/src/modules/credentials/CredentialServiceOptions.ts @@ -128,6 +128,6 @@ export interface CredentialProtocolMsgReturnType { }) } + public async delete( + agentContext: AgentContext, + proofRecord: ProofRecord, + options?: DeleteProofOptions + ): Promise { + await this.proofRepository.delete(agentContext, proofRecord) + + const deleteAssociatedDidCommMessages = options?.deleteAssociatedDidCommMessages ?? true + + if (deleteAssociatedDidCommMessages) { + const didCommMessages = await this.didCommMessageRepository.findByQuery(agentContext, { + associatedRecordId: proofRecord.id, + }) + for (const didCommMessage of didCommMessages) { + await this.didCommMessageRepository.delete(agentContext, didCommMessage) + } + } + } + public abstract getRequestedCredentialsForProofRequest( agentContext: AgentContext, options: GetRequestedCredentialsForProofRequestOptions diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index 0bc08886b6..30ab0212cb 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -20,6 +20,7 @@ import type { CreateProofRequestFromProposalOptions, FormatRequestedCredentialReturn, FormatRetrievedCredentialOptions, + DeleteProofOptions, } from './models/ProofServiceOptions' import type { ProofRecord } from './repository/ProofRecord' @@ -510,9 +511,10 @@ export class ProofsApi< * * @param proofId the proof record id */ - public async deleteById(proofId: string) { + public async deleteById(proofId: string, options?: DeleteProofOptions) { const proofRecord = await this.getById(proofId) - return await this.proofRepository.delete(this.agentContext, proofRecord) + const service = this.getService(proofRecord.protocolVersion) + return service.delete(this.agentContext, proofRecord, options) } /** diff --git a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts index ed43d6babd..6500985e3e 100644 --- a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts +++ b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts @@ -71,3 +71,7 @@ export interface ProofRequestFromProposalOptions { proofRecord: ProofRecord proofFormats: ProofFormatPayload } + +export interface DeleteProofOptions { + deleteAssociatedDidCommMessages?: boolean +} From 2cfadd9167438a9446d26b933aa64521d8be75e7 Mon Sep 17 00:00:00 2001 From: Iskander508 Date: Fri, 9 Sep 2022 11:53:56 +0200 Subject: [PATCH 033/125] fix: avoid crash when an unexpected message arrives (#1019) Signed-off-by: Pavel Zarecky --- packages/core/src/agent/Agent.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 8063661f64..a77a074323 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -137,7 +137,13 @@ export class Agent { .observable(AgentEventTypes.AgentMessageReceived) .pipe( takeUntil(this.agentConfig.stop$), - concatMap((e) => this.messageReceiver.receiveMessage(e.payload.message, { connection: e.payload.connection })) + concatMap((e) => + this.messageReceiver + .receiveMessage(e.payload.message, { connection: e.payload.connection }) + .catch((error) => { + this.logger.error('Failed to process message', { error }) + }) + ) ) .subscribe() } From 5cdcfa203e6d457f74250028678dbc3393d8eb5c Mon Sep 17 00:00:00 2001 From: Karim Stekelenburg Date: Fri, 9 Sep 2022 16:55:51 +0200 Subject: [PATCH 034/125] fix: unable to resolve nodejs document loader in react native environment (#1003) Signed-off-by: Karim Stekelenburg --- .../core/src/modules/vc/W3cCredentialService.ts | 16 +++++----------- .../vc/libraries/documentLoader.native.ts | 8 ++++++++ .../src/modules/vc/libraries/documentLoader.ts | 8 ++++++++ packages/core/src/modules/vc/libraries/jsonld.ts | 11 ----------- packages/core/src/utils/environment.ts | 9 --------- 5 files changed, 21 insertions(+), 31 deletions(-) create mode 100644 packages/core/src/modules/vc/libraries/documentLoader.native.ts create mode 100644 packages/core/src/modules/vc/libraries/documentLoader.ts delete mode 100644 packages/core/src/utils/environment.ts diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 18fa986542..1b8e188c3f 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -18,13 +18,13 @@ import { createWalletKeyPairClass } from '../../crypto/WalletKeyPair' import { AriesFrameworkError } from '../../error' import { injectable } from '../../plugins' import { JsonTransformer } from '../../utils' -import { isNodeJS, isReactNative } from '../../utils/environment' import { DidResolverService, VerificationMethod } from '../dids' import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type' import { SignatureSuiteRegistry } from './SignatureSuiteRegistry' import { orArrayToArray, w3cDate } from './jsonldUtil' -import jsonld, { documentLoaderNode, documentLoaderXhr } from './libraries/jsonld' +import { getDocumentLoader } from './libraries/documentLoader' +import jsonld from './libraries/jsonld' import vc from './libraries/vc' import { W3cVerifiableCredential } from './models' import { W3cPresentation } from './models/presentation/W3Presentation' @@ -302,15 +302,9 @@ export class W3cCredentialService { } } - let loader - - if (isNodeJS()) { - loader = documentLoaderNode.apply(jsonld, []) - } else if (isReactNative()) { - loader = documentLoaderXhr.apply(jsonld, []) - } else { - throw new AriesFrameworkError('Unsupported environment') - } + // fetches the documentLoader from documentLoader.ts or documentLoader.native.ts depending on the platform at bundle time + const platformLoader = getDocumentLoader() + const loader = platformLoader.apply(jsonld, []) return await loader(url) } diff --git a/packages/core/src/modules/vc/libraries/documentLoader.native.ts b/packages/core/src/modules/vc/libraries/documentLoader.native.ts new file mode 100644 index 0000000000..11dcbd0826 --- /dev/null +++ b/packages/core/src/modules/vc/libraries/documentLoader.native.ts @@ -0,0 +1,8 @@ +import type { DocumentLoader } from './jsonld' + +export function getDocumentLoader(): () => DocumentLoader { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const loader = require('@digitalcredentials/jsonld/lib/documentLoaders/xhr') + + return loader as () => DocumentLoader +} diff --git a/packages/core/src/modules/vc/libraries/documentLoader.ts b/packages/core/src/modules/vc/libraries/documentLoader.ts new file mode 100644 index 0000000000..e7424258ee --- /dev/null +++ b/packages/core/src/modules/vc/libraries/documentLoader.ts @@ -0,0 +1,8 @@ +import type { DocumentLoader } from './jsonld' + +export function getDocumentLoader(): () => DocumentLoader { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const loader = require('@digitalcredentials/jsonld/lib/documentLoaders/node') + + return loader as () => DocumentLoader +} diff --git a/packages/core/src/modules/vc/libraries/jsonld.ts b/packages/core/src/modules/vc/libraries/jsonld.ts index 4e95767af4..aee4be8adf 100644 --- a/packages/core/src/modules/vc/libraries/jsonld.ts +++ b/packages/core/src/modules/vc/libraries/jsonld.ts @@ -4,14 +4,6 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore import jsonld from '@digitalcredentials/jsonld' -// No type definitions available for this library -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -//@ts-ignore -import nodeDocumentLoader from '@digitalcredentials/jsonld/lib/documentLoaders/node' -// No type definitions available for this library -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -//@ts-ignore -import xhrDocumentLoader from '@digitalcredentials/jsonld/lib/documentLoaders/xhr' interface JsonLd { compact(document: any, context: any, options?: any): any @@ -31,7 +23,4 @@ export interface DocumentLoaderResult { export type DocumentLoader = (url: string) => Promise -export const documentLoaderXhr = xhrDocumentLoader as () => DocumentLoader -export const documentLoaderNode = nodeDocumentLoader as () => DocumentLoader - export default jsonld as unknown as JsonLd diff --git a/packages/core/src/utils/environment.ts b/packages/core/src/utils/environment.ts deleted file mode 100644 index b2ebadf0dd..0000000000 --- a/packages/core/src/utils/environment.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function isNodeJS() { - return typeof process !== 'undefined' && process.release && process.release.name === 'node' -} - -export function isReactNative() { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return typeof navigator != 'undefined' && navigator.product == 'ReactNative' -} From 273e353f4b36ab5d2420356eb3a53dcfb1c59ec6 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Fri, 9 Sep 2022 18:13:08 -0300 Subject: [PATCH 035/125] feat!: Discover Features V2 (#991) * feat: expose featureRegistry & add some e2e tests * feat: add synchronous feature query Signed-off-by: Ariel Gentile BREAKING CHANGE: - `queryFeatures` method parameters have been unified to a single `QueryFeaturesOptions` object that requires specification of Discover Features protocol to be used. - `isProtocolSupported` has been replaced by the more general synchronous mode of `queryFeatures`, which works when `awaitDisclosures` in options is set. Instead of returning a boolean, it returns an object with matching features - Custom modules implementing protocols must register them in Feature Registry in order to let them be discovered by other agents (this can be done in module `register(dependencyManager, featureRegistry)` method) --- packages/core/src/agent/Agent.ts | 9 + packages/core/src/agent/BaseAgent.ts | 3 + packages/core/src/agent/FeatureRegistry.ts | 56 ++++ .../core/src/agent/__tests__/Agent.test.ts | 30 ++ .../core/src/agent/models/features/Feature.ts | 58 ++++ .../src/agent/models/features/FeatureQuery.ts | 23 ++ .../src/agent/models/features/GoalCode.ts | 13 + .../models/features/GovernanceFramework.ts | 13 + .../src/agent/models/features/Protocol.ts | 25 ++ .../core/src/agent/models/features/index.ts | 5 + packages/core/src/agent/models/index.ts | 2 + packages/core/src/index.ts | 4 +- .../basic-messages/BasicMessagesModule.ts | 14 +- .../__tests__/BasicMessagesModule.test.ts | 8 +- .../modules/connections/ConnectionsModule.ts | 18 +- .../__tests__/ConnectionsModule.test.ts | 8 +- .../modules/credentials/CredentialsModule.ts | 25 +- .../__tests__/CredentialsModule.test.ts | 8 +- .../discover-features/DiscoverFeaturesApi.ts | 166 ++++++---- .../DiscoverFeaturesApiOptions.ts | 45 +++ .../DiscoverFeaturesEvents.ts | 31 ++ .../DiscoverFeaturesModule.ts | 34 ++- .../DiscoverFeaturesModuleConfig.ts | 25 ++ .../DiscoverFeaturesServiceOptions.ts | 16 + .../__tests__/DiscoverFeaturesModule.test.ts | 27 +- .../__tests__/DiscoverFeaturesService.test.ts | 62 ---- .../__tests__/FeatureRegistry.test.ts | 53 ++++ .../discover-features/__tests__/helpers.ts | 41 +++ .../v1-discover-features.e2e.test.ts | 106 +++++++ .../v2-discover-features.e2e.test.ts | 234 ++++++++++++++ .../handlers/DiscloseMessageHandler.ts | 13 - .../handlers/QueryMessageHandler.ts | 22 -- .../discover-features/handlers/index.ts | 2 - .../src/modules/discover-features/index.ts | 4 +- .../discover-features/protocol/index.ts | 2 + .../protocol/v1/V1DiscoverFeaturesService.ts | 142 +++++++++ .../V1DiscoverFeaturesService.test.ts | 277 +++++++++++++++++ .../v1/handlers/V1DiscloseMessageHandler.ts | 17 ++ .../v1/handlers/V1QueryMessageHandler.ts | 24 ++ .../protocol/v1/handlers/index.ts | 2 + .../discover-features/protocol/v1/index.ts | 2 + .../v1}/messages/DiscloseMessage.ts | 10 +- .../v1}/messages/QueryMessage.ts | 10 +- .../{ => protocol/v1}/messages/index.ts | 0 .../protocol/v2/V2DiscoverFeaturesService.ts | 113 +++++++ .../V2DiscoverFeaturesService.test.ts | 288 ++++++++++++++++++ .../handlers/V2DisclosuresMessageHandler.ts | 17 ++ .../v2/handlers/V2QueriesMessageHandler.ts | 24 ++ .../protocol/v2/handlers/index.ts | 2 + .../discover-features/protocol/v2/index.ts | 2 + .../v2/messages/V2DisclosuresMessage.ts | 36 +++ .../protocol/v2/messages/V2QueriesMessage.ts | 34 +++ .../protocol/v2/messages/index.ts | 2 + .../services/DiscoverFeaturesService.ts | 76 ++--- packages/core/src/modules/oob/OutOfBandApi.ts | 5 + .../core/src/modules/oob/OutOfBandModule.ts | 13 +- .../oob/__tests__/OutOfBandModule.test.ts | 7 +- .../core/src/modules/proofs/ProofsModule.ts | 13 +- .../proofs/__tests__/ProofsModule.test.ts | 8 +- .../question-answer/QuestionAnswerModule.ts | 14 +- .../__tests__/QuestionAnswerModule.test.ts | 8 +- .../src/modules/routing/MediatorModule.ts | 22 +- .../core/src/modules/routing/RecipientApi.ts | 30 +- .../src/modules/routing/RecipientModule.ts | 14 +- .../routing/__tests__/MediatorModule.test.ts | 7 +- .../routing/__tests__/RecipientModule.test.ts | 8 +- .../core/src/plugins/DependencyManager.ts | 5 +- packages/core/src/plugins/Module.ts | 3 +- .../__tests__/DependencyManager.test.ts | 6 +- samples/extension-module/dummy/DummyModule.ts | 14 +- 70 files changed, 2213 insertions(+), 247 deletions(-) create mode 100644 packages/core/src/agent/FeatureRegistry.ts create mode 100644 packages/core/src/agent/models/features/Feature.ts create mode 100644 packages/core/src/agent/models/features/FeatureQuery.ts create mode 100644 packages/core/src/agent/models/features/GoalCode.ts create mode 100644 packages/core/src/agent/models/features/GovernanceFramework.ts create mode 100644 packages/core/src/agent/models/features/Protocol.ts create mode 100644 packages/core/src/agent/models/features/index.ts create mode 100644 packages/core/src/agent/models/index.ts create mode 100644 packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts create mode 100644 packages/core/src/modules/discover-features/DiscoverFeaturesEvents.ts create mode 100644 packages/core/src/modules/discover-features/DiscoverFeaturesModuleConfig.ts create mode 100644 packages/core/src/modules/discover-features/DiscoverFeaturesServiceOptions.ts delete mode 100644 packages/core/src/modules/discover-features/__tests__/DiscoverFeaturesService.test.ts create mode 100644 packages/core/src/modules/discover-features/__tests__/FeatureRegistry.test.ts create mode 100644 packages/core/src/modules/discover-features/__tests__/helpers.ts create mode 100644 packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts create mode 100644 packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts delete mode 100644 packages/core/src/modules/discover-features/handlers/DiscloseMessageHandler.ts delete mode 100644 packages/core/src/modules/discover-features/handlers/QueryMessageHandler.ts delete mode 100644 packages/core/src/modules/discover-features/handlers/index.ts create mode 100644 packages/core/src/modules/discover-features/protocol/index.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v1/V1DiscoverFeaturesService.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v1/__tests__/V1DiscoverFeaturesService.test.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v1/handlers/V1DiscloseMessageHandler.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v1/handlers/V1QueryMessageHandler.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v1/handlers/index.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v1/index.ts rename packages/core/src/modules/discover-features/{ => protocol/v1}/messages/DiscloseMessage.ts (79%) rename packages/core/src/modules/discover-features/{ => protocol/v1}/messages/QueryMessage.ts (65%) rename packages/core/src/modules/discover-features/{ => protocol/v1}/messages/index.ts (100%) create mode 100644 packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v2/__tests__/V2DiscoverFeaturesService.test.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v2/handlers/V2DisclosuresMessageHandler.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v2/handlers/index.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v2/index.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresMessage.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesMessage.ts create mode 100644 packages/core/src/modules/discover-features/protocol/v2/messages/index.ts diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index f0944078ff..50e00f6610 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -38,6 +38,7 @@ import { Dispatcher } from './Dispatcher' import { EnvelopeService } from './EnvelopeService' import { EventEmitter } from './EventEmitter' import { AgentEventTypes } from './Events' +import { FeatureRegistry } from './FeatureRegistry' import { MessageReceiver } from './MessageReceiver' import { MessageSender } from './MessageSender' import { TransportService } from './TransportService' @@ -92,6 +93,13 @@ export class Agent extends BaseAgent { return this.eventEmitter } + /** + * Agent's feature registry + */ + public get features() { + return this.featureRegistry + } + public async initialize() { await super.initialize() @@ -156,6 +164,7 @@ export class Agent extends BaseAgent { dependencyManager.registerSingleton(TransportService) dependencyManager.registerSingleton(Dispatcher) dependencyManager.registerSingleton(EnvelopeService) + dependencyManager.registerSingleton(FeatureRegistry) dependencyManager.registerSingleton(JwsService) dependencyManager.registerSingleton(CacheRepository) dependencyManager.registerSingleton(DidCommMessageRepository) diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index c18f937f25..ed407c881d 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -23,6 +23,7 @@ import { WalletApi } from '../wallet/WalletApi' import { WalletError } from '../wallet/error' import { EventEmitter } from './EventEmitter' +import { FeatureRegistry } from './FeatureRegistry' import { MessageReceiver } from './MessageReceiver' import { MessageSender } from './MessageSender' import { TransportService } from './TransportService' @@ -33,6 +34,7 @@ export abstract class BaseAgent { protected logger: Logger public readonly dependencyManager: DependencyManager protected eventEmitter: EventEmitter + protected featureRegistry: FeatureRegistry protected messageReceiver: MessageReceiver protected transportService: TransportService protected messageSender: MessageSender @@ -75,6 +77,7 @@ export abstract class BaseAgent { // Resolve instances after everything is registered this.eventEmitter = this.dependencyManager.resolve(EventEmitter) + this.featureRegistry = this.dependencyManager.resolve(FeatureRegistry) this.messageSender = this.dependencyManager.resolve(MessageSender) this.messageReceiver = this.dependencyManager.resolve(MessageReceiver) this.transportService = this.dependencyManager.resolve(TransportService) diff --git a/packages/core/src/agent/FeatureRegistry.ts b/packages/core/src/agent/FeatureRegistry.ts new file mode 100644 index 0000000000..bfcf3e5f8c --- /dev/null +++ b/packages/core/src/agent/FeatureRegistry.ts @@ -0,0 +1,56 @@ +import type { FeatureQuery, Feature } from './models' + +import { injectable } from 'tsyringe' + +@injectable() +class FeatureRegistry { + private features: Feature[] = [] + + /** + * Register a single or set of Features on the registry + * + * @param features set of {Feature} objects or any inherited class + */ + public register(...features: Feature[]) { + for (const feature of features) { + const index = this.features.findIndex((item) => item.type === feature.type && item.id === feature.id) + + if (index > -1) { + this.features[index] = this.features[index].combine(feature) + } else { + this.features.push(feature) + } + } + } + + /** + * Perform a set of queries in the registry, supporting wildcards (*) as + * expressed in Aries RFC 0557. + * + * @see https://github.com/hyperledger/aries-rfcs/blob/560ffd23361f16a01e34ccb7dcc908ec28c5ddb1/features/0557-discover-features-v2/README.md + * + * @param queries set of {FeatureQuery} objects to query features + * @returns array containing all matching features (can be empty) + */ + public query(...queries: FeatureQuery[]) { + const output = [] + for (const query of queries) { + const items = this.features.filter((item) => item.type === query.featureType) + // An * will return all features of a given type (e.g. all protocols, all goal codes, all AIP configs) + if (query.match === '*') { + output.push(...items) + // An string ending with * will return a family of features of a certain type + // (e.g. all versions of a given protocol, all subsets of an AIP, etc.) + } else if (query.match.endsWith('*')) { + const match = query.match.slice(0, -1) + output.push(...items.filter((m) => m.id.startsWith(match))) + // Exact matching (single feature) + } else { + output.push(...items.filter((m) => m.id === query.match)) + } + } + return output + } +} + +export { FeatureRegistry } diff --git a/packages/core/src/agent/__tests__/Agent.test.ts b/packages/core/src/agent/__tests__/Agent.test.ts index c890630ca9..8e33e303ff 100644 --- a/packages/core/src/agent/__tests__/Agent.test.ts +++ b/packages/core/src/agent/__tests__/Agent.test.ts @@ -27,6 +27,7 @@ import { WalletError } from '../../wallet/error' import { Agent } from '../Agent' import { Dispatcher } from '../Dispatcher' import { EnvelopeService } from '../EnvelopeService' +import { FeatureRegistry } from '../FeatureRegistry' import { MessageReceiver } from '../MessageReceiver' import { MessageSender } from '../MessageSender' @@ -194,7 +195,36 @@ describe('Agent', () => { expect(container.resolve(MessageSender)).toBe(container.resolve(MessageSender)) expect(container.resolve(MessageReceiver)).toBe(container.resolve(MessageReceiver)) expect(container.resolve(Dispatcher)).toBe(container.resolve(Dispatcher)) + expect(container.resolve(FeatureRegistry)).toBe(container.resolve(FeatureRegistry)) expect(container.resolve(EnvelopeService)).toBe(container.resolve(EnvelopeService)) }) }) + + it('all core features are properly registered', () => { + const agent = new Agent(config, dependencies) + const registry = agent.dependencyManager.resolve(FeatureRegistry) + + const protocols = registry.query({ featureType: 'protocol', match: '*' }).map((p) => p.id) + + expect(protocols).toEqual( + expect.arrayContaining([ + 'https://didcomm.org/basicmessage/1.0', + 'https://didcomm.org/connections/1.0', + 'https://didcomm.org/coordinate-mediation/1.0', + 'https://didcomm.org/didexchange/1.0', + 'https://didcomm.org/discover-features/1.0', + 'https://didcomm.org/discover-features/2.0', + 'https://didcomm.org/issue-credential/1.0', + 'https://didcomm.org/issue-credential/2.0', + 'https://didcomm.org/messagepickup/1.0', + 'https://didcomm.org/messagepickup/2.0', + 'https://didcomm.org/out-of-band/1.1', + 'https://didcomm.org/present-proof/1.0', + 'https://didcomm.org/revocation_notification/1.0', + 'https://didcomm.org/revocation_notification/2.0', + 'https://didcomm.org/questionanswer/1.0', + ]) + ) + expect(protocols.length).toEqual(15) + }) }) diff --git a/packages/core/src/agent/models/features/Feature.ts b/packages/core/src/agent/models/features/Feature.ts new file mode 100644 index 0000000000..1a5b3e461c --- /dev/null +++ b/packages/core/src/agent/models/features/Feature.ts @@ -0,0 +1,58 @@ +import { Expose } from 'class-transformer' +import { IsString } from 'class-validator' + +import { AriesFrameworkError } from '../../../error' +import { JsonTransformer } from '../../../utils/JsonTransformer' + +export interface FeatureOptions { + id: string + type: string +} + +export class Feature { + public id!: string + + public constructor(props: FeatureOptions) { + if (props) { + this.id = props.id + this.type = props.type + } + } + + @IsString() + @Expose({ name: 'feature-type' }) + public readonly type!: string + + /** + * Combine this feature with another one, provided both are from the same type + * and have the same id + * + * @param feature object to combine with this one + * @returns a new object resulting from the combination between this and feature + */ + public combine(feature: this) { + if (feature.id !== this.id) { + throw new AriesFrameworkError('Can only combine with a feature with the same id') + } + + const obj1 = JsonTransformer.toJSON(this) + const obj2 = JsonTransformer.toJSON(feature) + + for (const key in obj2) { + try { + if (Array.isArray(obj2[key])) { + obj1[key] = [...new Set([...obj1[key], ...obj2[key]])] + } else { + obj1[key] = obj2[key] + } + } catch (e) { + obj1[key] = obj2[key] + } + } + return JsonTransformer.fromJSON(obj1, Feature) + } + + public toJSON(): Record { + return JsonTransformer.toJSON(this) + } +} diff --git a/packages/core/src/agent/models/features/FeatureQuery.ts b/packages/core/src/agent/models/features/FeatureQuery.ts new file mode 100644 index 0000000000..aab269b5db --- /dev/null +++ b/packages/core/src/agent/models/features/FeatureQuery.ts @@ -0,0 +1,23 @@ +import { Expose } from 'class-transformer' +import { IsString } from 'class-validator' + +export interface FeatureQueryOptions { + featureType: string + match: string +} + +export class FeatureQuery { + public constructor(options: FeatureQueryOptions) { + if (options) { + this.featureType = options.featureType + this.match = options.match + } + } + + @Expose({ name: 'feature-type' }) + @IsString() + public featureType!: string + + @IsString() + public match!: string +} diff --git a/packages/core/src/agent/models/features/GoalCode.ts b/packages/core/src/agent/models/features/GoalCode.ts new file mode 100644 index 0000000000..71a0b9fdd3 --- /dev/null +++ b/packages/core/src/agent/models/features/GoalCode.ts @@ -0,0 +1,13 @@ +import type { FeatureOptions } from './Feature' + +import { Feature } from './Feature' + +export type GoalCodeOptions = Omit + +export class GoalCode extends Feature { + public constructor(props: GoalCodeOptions) { + super({ ...props, type: GoalCode.type }) + } + + public static readonly type = 'goal-code' +} diff --git a/packages/core/src/agent/models/features/GovernanceFramework.ts b/packages/core/src/agent/models/features/GovernanceFramework.ts new file mode 100644 index 0000000000..ce174e6ebd --- /dev/null +++ b/packages/core/src/agent/models/features/GovernanceFramework.ts @@ -0,0 +1,13 @@ +import type { FeatureOptions } from './Feature' + +import { Feature } from './Feature' + +export type GovernanceFrameworkOptions = Omit + +export class GovernanceFramework extends Feature { + public constructor(props: GovernanceFrameworkOptions) { + super({ ...props, type: GovernanceFramework.type }) + } + + public static readonly type = 'gov-fw' +} diff --git a/packages/core/src/agent/models/features/Protocol.ts b/packages/core/src/agent/models/features/Protocol.ts new file mode 100644 index 0000000000..ddfc63d384 --- /dev/null +++ b/packages/core/src/agent/models/features/Protocol.ts @@ -0,0 +1,25 @@ +import type { FeatureOptions } from './Feature' + +import { IsOptional, IsString } from 'class-validator' + +import { Feature } from './Feature' + +export interface ProtocolOptions extends Omit { + roles?: string[] +} + +export class Protocol extends Feature { + public constructor(props: ProtocolOptions) { + super({ ...props, type: Protocol.type }) + + if (props) { + this.roles = props.roles + } + } + + public static readonly type = 'protocol' + + @IsString({ each: true }) + @IsOptional() + public roles?: string[] +} diff --git a/packages/core/src/agent/models/features/index.ts b/packages/core/src/agent/models/features/index.ts new file mode 100644 index 0000000000..ad3b62896c --- /dev/null +++ b/packages/core/src/agent/models/features/index.ts @@ -0,0 +1,5 @@ +export * from './Feature' +export * from './FeatureQuery' +export * from './GoalCode' +export * from './GovernanceFramework' +export * from './Protocol' diff --git a/packages/core/src/agent/models/index.ts b/packages/core/src/agent/models/index.ts new file mode 100644 index 0000000000..3a9ffdf3ca --- /dev/null +++ b/packages/core/src/agent/models/index.ts @@ -0,0 +1,2 @@ +export * from './features' +export * from './InboundMessageContext' diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index afadf847a1..ef2d804359 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,8 +6,9 @@ export { Agent } from './agent/Agent' export { BaseAgent } from './agent/BaseAgent' export * from './agent' export { EventEmitter } from './agent/EventEmitter' +export { FeatureRegistry } from './agent/FeatureRegistry' export { Handler, HandlerInboundMessage } from './agent/Handler' -export { InboundMessageContext } from './agent/models/InboundMessageContext' +export * from './agent/models' export { AgentConfig } from './agent/AgentConfig' export { AgentMessage } from './agent/AgentMessage' export { Dispatcher } from './agent/Dispatcher' @@ -36,6 +37,7 @@ export * from './transport' export * from './modules/basic-messages' export * from './modules/common' export * from './modules/credentials' +export * from './modules/discover-features' export * from './modules/proofs' export * from './modules/connections' export * from './modules/ledger' diff --git a/packages/core/src/modules/basic-messages/BasicMessagesModule.ts b/packages/core/src/modules/basic-messages/BasicMessagesModule.ts index 03da109ff4..169e3afc48 100644 --- a/packages/core/src/modules/basic-messages/BasicMessagesModule.ts +++ b/packages/core/src/modules/basic-messages/BasicMessagesModule.ts @@ -1,5 +1,9 @@ +import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' +import { Protocol } from '../../agent/models' + +import { BasicMessageRole } from './BasicMessageRole' import { BasicMessagesApi } from './BasicMessagesApi' import { BasicMessageRepository } from './repository' import { BasicMessageService } from './services' @@ -8,7 +12,7 @@ export class BasicMessagesModule implements Module { /** * Registers the dependencies of the basic message module on the dependency manager. */ - public register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { // Api dependencyManager.registerContextScoped(BasicMessagesApi) @@ -17,5 +21,13 @@ export class BasicMessagesModule implements Module { // Repositories dependencyManager.registerSingleton(BasicMessageRepository) + + // Features + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/basicmessage/1.0', + roles: [BasicMessageRole.Sender, BasicMessageRole.Receiver], + }) + ) } } diff --git a/packages/core/src/modules/basic-messages/__tests__/BasicMessagesModule.test.ts b/packages/core/src/modules/basic-messages/__tests__/BasicMessagesModule.test.ts index caaaedae00..4a9f106810 100644 --- a/packages/core/src/modules/basic-messages/__tests__/BasicMessagesModule.test.ts +++ b/packages/core/src/modules/basic-messages/__tests__/BasicMessagesModule.test.ts @@ -1,3 +1,4 @@ +import { FeatureRegistry } from '../../../agent/FeatureRegistry' import { DependencyManager } from '../../../plugins/DependencyManager' import { BasicMessagesApi } from '../BasicMessagesApi' import { BasicMessagesModule } from '../BasicMessagesModule' @@ -9,9 +10,14 @@ const DependencyManagerMock = DependencyManager as jest.Mock const dependencyManager = new DependencyManagerMock() +jest.mock('../../../agent/FeatureRegistry') +const FeatureRegistryMock = FeatureRegistry as jest.Mock + +const featureRegistry = new FeatureRegistryMock() + describe('BasicMessagesModule', () => { test('registers dependencies on the dependency manager', () => { - new BasicMessagesModule().register(dependencyManager) + new BasicMessagesModule().register(dependencyManager, featureRegistry) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(BasicMessagesApi) diff --git a/packages/core/src/modules/connections/ConnectionsModule.ts b/packages/core/src/modules/connections/ConnectionsModule.ts index 5e6c98ee0a..a76d95ee71 100644 --- a/packages/core/src/modules/connections/ConnectionsModule.ts +++ b/packages/core/src/modules/connections/ConnectionsModule.ts @@ -1,9 +1,13 @@ +import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' import type { ConnectionsModuleConfigOptions } from './ConnectionsModuleConfig' +import { Protocol } from '../../agent/models' + import { ConnectionsApi } from './ConnectionsApi' import { ConnectionsModuleConfig } from './ConnectionsModuleConfig' import { DidExchangeProtocol } from './DidExchangeProtocol' +import { ConnectionRole, DidExchangeRole } from './models' import { ConnectionRepository } from './repository' import { ConnectionService, TrustPingService } from './services' @@ -17,7 +21,7 @@ export class ConnectionsModule implements Module { /** * Registers the dependencies of the connections module on the dependency manager. */ - public register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { // Api dependencyManager.registerContextScoped(ConnectionsApi) @@ -31,5 +35,17 @@ export class ConnectionsModule implements Module { // Repositories dependencyManager.registerSingleton(ConnectionRepository) + + // Features + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/connections/1.0', + roles: [ConnectionRole.Invitee, ConnectionRole.Inviter], + }), + new Protocol({ + id: 'https://didcomm.org/didexchange/1.0', + roles: [DidExchangeRole.Requester, DidExchangeRole.Responder], + }) + ) } } diff --git a/packages/core/src/modules/connections/__tests__/ConnectionsModule.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionsModule.test.ts index 2231f69b07..34ebb476f7 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionsModule.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionsModule.test.ts @@ -1,3 +1,4 @@ +import { FeatureRegistry } from '../../../agent/FeatureRegistry' import { DependencyManager } from '../../../plugins/DependencyManager' import { ConnectionsApi } from '../ConnectionsApi' import { ConnectionsModule } from '../ConnectionsModule' @@ -11,10 +12,15 @@ const DependencyManagerMock = DependencyManager as jest.Mock const dependencyManager = new DependencyManagerMock() +jest.mock('../../../agent/FeatureRegistry') +const FeatureRegistryMock = FeatureRegistry as jest.Mock + +const featureRegistry = new FeatureRegistryMock() + describe('ConnectionsModule', () => { test('registers dependencies on the dependency manager', () => { const connectionsModule = new ConnectionsModule() - connectionsModule.register(dependencyManager) + connectionsModule.register(dependencyManager, featureRegistry) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(ConnectionsApi) diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index 9f3456b4d2..475224b0f8 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -1,6 +1,9 @@ +import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' import type { CredentialsModuleConfigOptions } from './CredentialsModuleConfig' +import { Protocol } from '../../agent/models' + import { CredentialsApi } from './CredentialsApi' import { CredentialsModuleConfig } from './CredentialsModuleConfig' import { IndyCredentialFormatService } from './formats/indy' @@ -19,7 +22,7 @@ export class CredentialsModule implements Module { /** * Registers the dependencies of the credentials module on the dependency manager. */ - public register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { // Api dependencyManager.registerContextScoped(CredentialsApi) @@ -34,6 +37,26 @@ export class CredentialsModule implements Module { // Repositories dependencyManager.registerSingleton(CredentialRepository) + // Features + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/issue-credential/1.0', + roles: ['holder', 'issuer'], + }), + new Protocol({ + id: 'https://didcomm.org/issue-credential/2.0', + roles: ['holder', 'issuer'], + }), + new Protocol({ + id: 'https://didcomm.org/revocation_notification/1.0', + roles: ['holder'], + }), + new Protocol({ + id: 'https://didcomm.org/revocation_notification/2.0', + roles: ['holder'], + }) + ) + // Credential Formats dependencyManager.registerSingleton(IndyCredentialFormatService) } diff --git a/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts index b430d90d9c..cb76cc2840 100644 --- a/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts +++ b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts @@ -1,3 +1,4 @@ +import { FeatureRegistry } from '../../../agent/FeatureRegistry' import { DependencyManager } from '../../../plugins/DependencyManager' import { CredentialsApi } from '../CredentialsApi' import { CredentialsModule } from '../CredentialsModule' @@ -12,10 +13,15 @@ const DependencyManagerMock = DependencyManager as jest.Mock const dependencyManager = new DependencyManagerMock() +jest.mock('../../../agent/FeatureRegistry') +const FeatureRegistryMock = FeatureRegistry as jest.Mock + +const featureRegistry = new FeatureRegistryMock() + describe('CredentialsModule', () => { test('registers dependencies on the dependency manager', () => { const credentialsModule = new CredentialsModule() - credentialsModule.register(dependencyManager) + credentialsModule.register(dependencyManager, featureRegistry) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(CredentialsApi) diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts index 5ab8193324..1f49e65624 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts @@ -1,101 +1,153 @@ -import type { AgentMessageProcessedEvent } from '../../agent/Events' -import type { ParsedMessageType } from '../../utils/messageType' +import type { Feature } from '../../agent/models' +import type { DiscloseFeaturesOptions, QueryFeaturesOptions, ServiceMap } from './DiscoverFeaturesApiOptions' +import type { DiscoverFeaturesDisclosureReceivedEvent } from './DiscoverFeaturesEvents' +import type { DiscoverFeaturesService } from './services' import { firstValueFrom, of, ReplaySubject, Subject } from 'rxjs' import { catchError, filter, map, takeUntil, timeout } from 'rxjs/operators' import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' import { EventEmitter } from '../../agent/EventEmitter' -import { filterContextCorrelationId, AgentEventTypes } from '../../agent/Events' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' import { InjectionSymbols } from '../../constants' +import { AriesFrameworkError } from '../../error' import { inject, injectable } from '../../plugins' -import { canHandleMessageType, parseMessageType } from '../../utils/messageType' import { ConnectionService } from '../connections/services' -import { DiscloseMessageHandler, QueryMessageHandler } from './handlers' -import { DiscloseMessage } from './messages' -import { DiscoverFeaturesService } from './services' +import { DiscoverFeaturesEventTypes } from './DiscoverFeaturesEvents' +import { DiscoverFeaturesModuleConfig } from './DiscoverFeaturesModuleConfig' +import { V1DiscoverFeaturesService, V2DiscoverFeaturesService } from './protocol' +export interface QueryFeaturesReturnType { + features?: Feature[] +} + +export interface DiscoverFeaturesApi { + queryFeatures(options: QueryFeaturesOptions): Promise + discloseFeatures(options: DiscloseFeaturesOptions): Promise +} @injectable() -export class DiscoverFeaturesApi { +export class DiscoverFeaturesApi< + DFSs extends DiscoverFeaturesService[] = [V1DiscoverFeaturesService, V2DiscoverFeaturesService] +> implements DiscoverFeaturesApi +{ + /** + * Configuration for Discover Features module + */ + public readonly config: DiscoverFeaturesModuleConfig + private connectionService: ConnectionService private messageSender: MessageSender - private discoverFeaturesService: DiscoverFeaturesService private eventEmitter: EventEmitter private stop$: Subject private agentContext: AgentContext + private serviceMap: ServiceMap public constructor( - dispatcher: Dispatcher, connectionService: ConnectionService, messageSender: MessageSender, - discoverFeaturesService: DiscoverFeaturesService, + v1Service: V1DiscoverFeaturesService, + v2Service: V2DiscoverFeaturesService, eventEmitter: EventEmitter, @inject(InjectionSymbols.Stop$) stop$: Subject, - agentContext: AgentContext + agentContext: AgentContext, + config: DiscoverFeaturesModuleConfig ) { this.connectionService = connectionService this.messageSender = messageSender - this.discoverFeaturesService = discoverFeaturesService - this.registerHandlers(dispatcher) this.eventEmitter = eventEmitter this.stop$ = stop$ this.agentContext = agentContext + this.config = config + + // Dynamically build service map. This will be extracted once services are registered dynamically + this.serviceMap = [v1Service, v2Service].reduce( + (serviceMap, service) => ({ + ...serviceMap, + [service.version]: service, + }), + {} + ) as ServiceMap } - public async isProtocolSupported(connectionId: string, message: { type: ParsedMessageType }) { - const { protocolUri } = message.type - - // Listen for response to our feature query - const replaySubject = new ReplaySubject(1) - this.eventEmitter - .observable(AgentEventTypes.AgentMessageProcessed) - .pipe( - // Stop when the agent shuts down - takeUntil(this.stop$), - filterContextCorrelationId(this.agentContext.contextCorrelationId), - // filter by connection id and query disclose message type - filter( - (e) => - e.payload.connection?.id === connectionId && - canHandleMessageType(DiscloseMessage, parseMessageType(e.payload.message.type)) - ), - // Return whether the protocol is supported - map((e) => { - const message = e.payload.message as DiscloseMessage - return message.protocols.map((p) => p.protocolId).includes(protocolUri) - }), - // TODO: make configurable - // If we don't have an answer in 7 seconds (no response, not supported, etc...) error - timeout(7000), - // We want to return false if an error occurred - catchError(() => of(false)) - ) - .subscribe(replaySubject) - - await this.queryFeatures(connectionId, { - query: protocolUri, - comment: 'Detect if protocol is supported', - }) + public getService(protocolVersion: PVT): DiscoverFeaturesService { + if (!this.serviceMap[protocolVersion]) { + throw new AriesFrameworkError(`No discover features service registered for protocol version ${protocolVersion}`) + } - const isProtocolSupported = await firstValueFrom(replaySubject) - return isProtocolSupported + return this.serviceMap[protocolVersion] } - public async queryFeatures(connectionId: string, options: { query: string; comment?: string }) { - const connection = await this.connectionService.getById(this.agentContext, connectionId) + /** + * Send a query to an existing connection for discovering supported features of any kind. If desired, do the query synchronously, + * meaning that it will await the response (or timeout) before resolving. Otherwise, response can be hooked by subscribing to + * {DiscoverFeaturesDisclosureReceivedEvent}. + * + * Note: V1 protocol only supports a single query and is limited to protocols + * + * @param options feature queries to perform, protocol version and optional comment string (only used + * in V1 protocol). If awaitDisclosures is set, perform the query synchronously with awaitDisclosuresTimeoutMs timeout. + */ + public async queryFeatures(options: QueryFeaturesOptions) { + const service = this.getService(options.protocolVersion) - const queryMessage = await this.discoverFeaturesService.createQuery(options) + const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + + const { message: queryMessage } = await service.createQuery({ + queries: options.queries, + comment: options.comment, + }) const outbound = createOutboundMessage(connection, queryMessage) + + const replaySubject = new ReplaySubject(1) + if (options.awaitDisclosures) { + // Listen for response to our feature query + this.eventEmitter + .observable(DiscoverFeaturesEventTypes.DisclosureReceived) + .pipe( + // Stop when the agent shuts down + takeUntil(this.stop$), + // filter by connection id + filter((e) => e.payload.connection?.id === connection.id), + // Return disclosures + map((e) => e.payload.disclosures), + // If we don't have an answer in timeoutMs miliseconds (no response, not supported, etc...) error + timeout(options.awaitDisclosuresTimeoutMs ?? 7000), // TODO: Harmonize default timeouts across the framework + // We want to return false if an error occurred + catchError(() => of([])) + ) + .subscribe(replaySubject) + } + await this.messageSender.sendMessage(this.agentContext, outbound) + + return { features: options.awaitDisclosures ? await firstValueFrom(replaySubject) : undefined } } - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new DiscloseMessageHandler()) - dispatcher.registerHandler(new QueryMessageHandler(this.discoverFeaturesService)) + /** + * Disclose features to a connection, either proactively or as a response to a query. + * + * Features are disclosed based on queries that will be performed to Agent's Feature Registry, + * meaning that they should be registered prior to disclosure. When sending disclosure as response, + * these queries will usually match those from the corresponding Query or Queries message. + * + * Note: V1 protocol only supports sending disclosures as a response to a query. + * + * @param options multiple properties like protocol version to use, disclosure queries and thread id + * (in case of disclosure as response to query) + */ + public async discloseFeatures(options: DiscloseFeaturesOptions) { + const service = this.getService(options.protocolVersion) + + const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + const { message: disclosuresMessage } = await service.createDisclosure({ + disclosureQueries: options.disclosureQueries, + threadId: options.threadId, + }) + + const outbound = createOutboundMessage(connection, disclosuresMessage) + await this.messageSender.sendMessage(this.agentContext, outbound) } } diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts new file mode 100644 index 0000000000..5cd0b88d38 --- /dev/null +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts @@ -0,0 +1,45 @@ +import type { FeatureQueryOptions } from '../../agent/models' +import type { DiscoverFeaturesService } from './services' + +/** + * Get the supported protocol versions based on the provided discover features services. + */ +export type ProtocolVersionType = DFSs[number]['version'] + +/** + * Get the service map for usage in the discover features module. Will return a type mapping of protocol version to service. + * + * @example + * ``` + * type DiscoverFeaturesServiceMap = ServiceMap<[V1DiscoverFeaturesService,V2DiscoverFeaturesService]> + * + * // equal to + * type DiscoverFeaturesServiceMap = { + * v1: V1DiscoverFeatureService + * v2: V2DiscoverFeaturesService + * } + * ``` + */ +export type ServiceMap = { + [DFS in DFSs[number] as DFS['version']]: DiscoverFeaturesService +} + +interface BaseOptions { + connectionId: string +} + +export interface QueryFeaturesOptions + extends BaseOptions { + protocolVersion: ProtocolVersionType + queries: FeatureQueryOptions[] + awaitDisclosures?: boolean + awaitDisclosuresTimeoutMs?: number + comment?: string +} + +export interface DiscloseFeaturesOptions + extends BaseOptions { + protocolVersion: ProtocolVersionType + disclosureQueries: FeatureQueryOptions[] + threadId?: string +} diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesEvents.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesEvents.ts new file mode 100644 index 0000000000..2e4340ffbc --- /dev/null +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesEvents.ts @@ -0,0 +1,31 @@ +import type { AgentMessage } from '../../agent/AgentMessage' +import type { BaseEvent } from '../../agent/Events' +import type { Feature, FeatureQueryOptions } from '../../agent/models' +import type { ConnectionRecord } from '../connections' + +export enum DiscoverFeaturesEventTypes { + QueryReceived = 'QueryReceived', + DisclosureReceived = 'DisclosureReceived', +} + +export interface DiscoverFeaturesQueryReceivedEvent extends BaseEvent { + type: typeof DiscoverFeaturesEventTypes.QueryReceived + payload: { + message: AgentMessage + queries: FeatureQueryOptions[] + protocolVersion: string + connection: ConnectionRecord + threadId: string + } +} + +export interface DiscoverFeaturesDisclosureReceivedEvent extends BaseEvent { + type: typeof DiscoverFeaturesEventTypes.DisclosureReceived + payload: { + message: AgentMessage + disclosures: Feature[] + protocolVersion: string + connection: ConnectionRecord + threadId: string + } +} diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts index 8490fc88b7..d7be4b3732 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts @@ -1,17 +1,45 @@ +import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' +import type { DiscoverFeaturesModuleConfigOptions } from './DiscoverFeaturesModuleConfig' + +import { Protocol } from '../../agent/models' import { DiscoverFeaturesApi } from './DiscoverFeaturesApi' -import { DiscoverFeaturesService } from './services' +import { DiscoverFeaturesModuleConfig } from './DiscoverFeaturesModuleConfig' +import { V1DiscoverFeaturesService } from './protocol/v1' +import { V2DiscoverFeaturesService } from './protocol/v2' export class DiscoverFeaturesModule implements Module { + public readonly config: DiscoverFeaturesModuleConfig + + public constructor(config?: DiscoverFeaturesModuleConfigOptions) { + this.config = new DiscoverFeaturesModuleConfig(config) + } + /** * Registers the dependencies of the discover features module on the dependency manager. */ - public register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { // Api dependencyManager.registerContextScoped(DiscoverFeaturesApi) + // Config + dependencyManager.registerInstance(DiscoverFeaturesModuleConfig, this.config) + // Services - dependencyManager.registerSingleton(DiscoverFeaturesService) + dependencyManager.registerSingleton(V1DiscoverFeaturesService) + dependencyManager.registerSingleton(V2DiscoverFeaturesService) + + // Features + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/discover-features/1.0', + roles: ['requester', 'responder'], + }), + new Protocol({ + id: 'https://didcomm.org/discover-features/2.0', + roles: ['requester', 'responder'], + }) + ) } } diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesModuleConfig.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesModuleConfig.ts new file mode 100644 index 0000000000..9417b5c213 --- /dev/null +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesModuleConfig.ts @@ -0,0 +1,25 @@ +/** + * DiscoverFeaturesModuleConfigOptions defines the interface for the options of the DiscoverFeaturesModuleConfig class. + * This can contain optional parameters that have default values in the config class itself. + */ +export interface DiscoverFeaturesModuleConfigOptions { + /** + * Whether to automatically accept feature queries. Applies to all protocol versions. + * + * @default true + */ + autoAcceptQueries?: boolean +} + +export class DiscoverFeaturesModuleConfig { + private options: DiscoverFeaturesModuleConfigOptions + + public constructor(options?: DiscoverFeaturesModuleConfigOptions) { + this.options = options ?? {} + } + + /** {@inheritDoc DiscoverFeaturesModuleConfigOptions.autoAcceptQueries} */ + public get autoAcceptQueries() { + return this.options.autoAcceptQueries ?? true + } +} diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesServiceOptions.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesServiceOptions.ts new file mode 100644 index 0000000000..5dcbb04bdc --- /dev/null +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesServiceOptions.ts @@ -0,0 +1,16 @@ +import type { AgentMessage } from '../../agent/AgentMessage' +import type { FeatureQueryOptions } from '../../agent/models' + +export interface CreateQueryOptions { + queries: FeatureQueryOptions[] + comment?: string +} + +export interface CreateDisclosureOptions { + disclosureQueries: FeatureQueryOptions[] + threadId?: string +} + +export interface DiscoverFeaturesProtocolMsgReturnType { + message: MessageType +} diff --git a/packages/core/src/modules/discover-features/__tests__/DiscoverFeaturesModule.test.ts b/packages/core/src/modules/discover-features/__tests__/DiscoverFeaturesModule.test.ts index c47b85bb36..e4c259b69c 100644 --- a/packages/core/src/modules/discover-features/__tests__/DiscoverFeaturesModule.test.ts +++ b/packages/core/src/modules/discover-features/__tests__/DiscoverFeaturesModule.test.ts @@ -1,21 +1,40 @@ +import { FeatureRegistry } from '../../../agent/FeatureRegistry' +import { Protocol } from '../../../agent/models' import { DependencyManager } from '../../../plugins/DependencyManager' import { DiscoverFeaturesApi } from '../DiscoverFeaturesApi' import { DiscoverFeaturesModule } from '../DiscoverFeaturesModule' -import { DiscoverFeaturesService } from '../services' +import { V1DiscoverFeaturesService } from '../protocol/v1' +import { V2DiscoverFeaturesService } from '../protocol/v2' jest.mock('../../../plugins/DependencyManager') const DependencyManagerMock = DependencyManager as jest.Mock +jest.mock('../../../agent/FeatureRegistry') +const FeatureRegistryMock = FeatureRegistry as jest.Mock + const dependencyManager = new DependencyManagerMock() +const featureRegistry = new FeatureRegistryMock() describe('DiscoverFeaturesModule', () => { test('registers dependencies on the dependency manager', () => { - new DiscoverFeaturesModule().register(dependencyManager) + new DiscoverFeaturesModule().register(dependencyManager, featureRegistry) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(DiscoverFeaturesApi) - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(1) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DiscoverFeaturesService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(2) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V1DiscoverFeaturesService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V2DiscoverFeaturesService) + + expect(featureRegistry.register).toHaveBeenCalledWith( + new Protocol({ + id: 'https://didcomm.org/discover-features/1.0', + roles: ['requester', 'responder'], + }), + new Protocol({ + id: 'https://didcomm.org/discover-features/2.0', + roles: ['requester', 'responder'], + }) + ) }) }) diff --git a/packages/core/src/modules/discover-features/__tests__/DiscoverFeaturesService.test.ts b/packages/core/src/modules/discover-features/__tests__/DiscoverFeaturesService.test.ts deleted file mode 100644 index eb911df214..0000000000 --- a/packages/core/src/modules/discover-features/__tests__/DiscoverFeaturesService.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Dispatcher } from '../../../agent/Dispatcher' - -import { QueryMessage } from '../messages' -import { DiscoverFeaturesService } from '../services/DiscoverFeaturesService' - -const supportedProtocols = [ - 'https://didcomm.org/connections/1.0', - 'https://didcomm.org/notification/1.0', - 'https://didcomm.org/issue-credential/1.0', -] - -describe('DiscoverFeaturesService', () => { - const discoverFeaturesService = new DiscoverFeaturesService({ supportedProtocols } as Dispatcher) - - describe('createDisclose', () => { - it('should return all protocols when query is *', async () => { - const queryMessage = new QueryMessage({ - query: '*', - }) - - const message = await discoverFeaturesService.createDisclose(queryMessage) - - expect(message.protocols.map((p) => p.protocolId)).toStrictEqual([ - 'https://didcomm.org/connections/1.0', - 'https://didcomm.org/notification/1.0', - 'https://didcomm.org/issue-credential/1.0', - ]) - }) - - it('should return only one protocol if the query specifies a specific protocol', async () => { - const queryMessage = new QueryMessage({ - query: 'https://didcomm.org/connections/1.0', - }) - - const message = await discoverFeaturesService.createDisclose(queryMessage) - - expect(message.protocols.map((p) => p.protocolId)).toStrictEqual(['https://didcomm.org/connections/1.0']) - }) - - it('should respect a wild card at the end of the query', async () => { - const queryMessage = new QueryMessage({ - query: 'https://didcomm.org/connections/*', - }) - - const message = await discoverFeaturesService.createDisclose(queryMessage) - - expect(message.protocols.map((p) => p.protocolId)).toStrictEqual(['https://didcomm.org/connections/1.0']) - }) - }) - - describe('createQuery', () => { - it('should return a query message with the query and comment', async () => { - const message = await discoverFeaturesService.createQuery({ - query: '*', - comment: 'Hello', - }) - - expect(message.query).toBe('*') - expect(message.comment).toBe('Hello') - }) - }) -}) diff --git a/packages/core/src/modules/discover-features/__tests__/FeatureRegistry.test.ts b/packages/core/src/modules/discover-features/__tests__/FeatureRegistry.test.ts new file mode 100644 index 0000000000..f353b36752 --- /dev/null +++ b/packages/core/src/modules/discover-features/__tests__/FeatureRegistry.test.ts @@ -0,0 +1,53 @@ +import { FeatureRegistry } from '../../../agent/FeatureRegistry' +import { Feature, GoalCode, Protocol } from '../../../agent/models' +import { JsonTransformer } from '../../../utils/JsonTransformer' + +describe('Feature Registry', () => { + test('register goal codes', () => { + const featureRegistry = new FeatureRegistry() + + const goalCode = new GoalCode({ id: 'aries.vc.issue' }) + + expect(JsonTransformer.toJSON(goalCode)).toMatchObject({ id: 'aries.vc.issue', 'feature-type': 'goal-code' }) + + featureRegistry.register(goalCode) + const found = featureRegistry.query({ featureType: GoalCode.type, match: 'aries.*' }) + + expect(found.map((t) => t.toJSON())).toStrictEqual([{ id: 'aries.vc.issue', 'feature-type': 'goal-code' }]) + }) + + test('register generic feature', () => { + const featureRegistry = new FeatureRegistry() + + class GenericFeature extends Feature { + public customFieldString: string + public customFieldNumber: number + public constructor(id: string, customFieldString: string, customFieldNumber: number) { + super({ id, type: 'generic' }) + this.customFieldString = customFieldString + this.customFieldNumber = customFieldNumber + } + } + featureRegistry.register(new GenericFeature('myId', 'myString', 42)) + const found = featureRegistry.query({ featureType: 'generic', match: '*' }) + + expect(found.map((t) => t.toJSON())).toStrictEqual([ + { id: 'myId', 'feature-type': 'generic', customFieldString: 'myString', customFieldNumber: 42 }, + ]) + }) + + test('register combined features', () => { + const featureRegistry = new FeatureRegistry() + + featureRegistry.register( + new Protocol({ id: 'https://didcomm.org/dummy/1.0', roles: ['requester'] }), + new Protocol({ id: 'https://didcomm.org/dummy/1.0', roles: ['responder'] }), + new Protocol({ id: 'https://didcomm.org/dummy/1.0', roles: ['responder'] }) + ) + const found = featureRegistry.query({ featureType: Protocol.type, match: 'https://didcomm.org/dummy/1.0' }) + + expect(found.map((t) => t.toJSON())).toStrictEqual([ + { id: 'https://didcomm.org/dummy/1.0', 'feature-type': 'protocol', roles: ['requester', 'responder'] }, + ]) + }) +}) diff --git a/packages/core/src/modules/discover-features/__tests__/helpers.ts b/packages/core/src/modules/discover-features/__tests__/helpers.ts new file mode 100644 index 0000000000..209bf83b05 --- /dev/null +++ b/packages/core/src/modules/discover-features/__tests__/helpers.ts @@ -0,0 +1,41 @@ +import type { + DiscoverFeaturesDisclosureReceivedEvent, + DiscoverFeaturesQueryReceivedEvent, +} from '../DiscoverFeaturesEvents' +import type { Observable } from 'rxjs' + +import { map, catchError, timeout, firstValueFrom, ReplaySubject } from 'rxjs' + +export function waitForDisclosureSubject( + subject: ReplaySubject | Observable, + { timeoutMs = 10000 }: { timeoutMs: number } +) { + const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject + + return firstValueFrom( + observable.pipe( + timeout(timeoutMs), + catchError(() => { + throw new Error(`DiscoverFeaturesDisclosureReceivedEvent event not emitted within specified timeout`) + }), + map((e) => e.payload) + ) + ) +} + +export function waitForQuerySubject( + subject: ReplaySubject | Observable, + { timeoutMs = 10000 }: { timeoutMs: number } +) { + const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject + + return firstValueFrom( + observable.pipe( + timeout(timeoutMs), + catchError(() => { + throw new Error(`DiscoverFeaturesQueryReceivedEvent event not emitted within specified timeout`) + }), + map((e) => e.payload) + ) + ) +} diff --git a/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts b/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts new file mode 100644 index 0000000000..258eb4f543 --- /dev/null +++ b/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts @@ -0,0 +1,106 @@ +import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' +import type { ConnectionRecord } from '../../connections' +import type { + DiscoverFeaturesDisclosureReceivedEvent, + DiscoverFeaturesQueryReceivedEvent, +} from '../DiscoverFeaturesEvents' + +import { ReplaySubject, Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getBaseConfig, makeConnection } from '../../../../tests/helpers' +import { Agent } from '../../../agent/Agent' +import { DiscoverFeaturesEventTypes } from '../DiscoverFeaturesEvents' + +import { waitForDisclosureSubject, waitForQuerySubject } from './helpers' + +describe('v1 discover features', () => { + let faberAgent: Agent + let aliceAgent: Agent + let faberConnection: ConnectionRecord + + beforeAll(async () => { + const faberMessages = new Subject() + const aliceMessages = new Subject() + const subjectMap = { + 'rxjs:faber': faberMessages, + 'rxjs:alice': aliceMessages, + } + const faberConfig = getBaseConfig('Faber Discover Features V1 E2E', { + endpoints: ['rxjs:faber'], + }) + + const aliceConfig = getBaseConfig('Alice Discover Features V1 E2E', { + endpoints: ['rxjs:alice'], + }) + faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await faberAgent.initialize() + + aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + ;[faberConnection] = await makeConnection(faberAgent, aliceAgent) + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Faber asks Alice for issue credential protocol support', async () => { + const faberReplay = new ReplaySubject() + const aliceReplay = new ReplaySubject() + + faberAgent.events + .observable(DiscoverFeaturesEventTypes.DisclosureReceived) + .subscribe(faberReplay) + aliceAgent.events + .observable(DiscoverFeaturesEventTypes.QueryReceived) + .subscribe(aliceReplay) + + await faberAgent.discovery.queryFeatures({ + connectionId: faberConnection.id, + protocolVersion: 'v1', + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + }) + + const query = await waitForQuerySubject(aliceReplay, { timeoutMs: 10000 }) + + expect(query).toMatchObject({ + protocolVersion: 'v1', + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + }) + + const disclosure = await waitForDisclosureSubject(faberReplay, { timeoutMs: 10000 }) + + expect(disclosure).toMatchObject({ + protocolVersion: 'v1', + disclosures: [ + { type: 'protocol', id: 'https://didcomm.org/issue-credential/1.0', roles: ['holder', 'issuer'] }, + { type: 'protocol', id: 'https://didcomm.org/issue-credential/2.0', roles: ['holder', 'issuer'] }, + ], + }) + }) + + test('Faber asks Alice for issue credential protocol support synchronously', async () => { + const matchingFeatures = await faberAgent.discovery.queryFeatures({ + connectionId: faberConnection.id, + protocolVersion: 'v1', + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + awaitDisclosures: true, + }) + + expect(matchingFeatures).toMatchObject({ + features: [ + { type: 'protocol', id: 'https://didcomm.org/issue-credential/1.0', roles: ['holder', 'issuer'] }, + { type: 'protocol', id: 'https://didcomm.org/issue-credential/2.0', roles: ['holder', 'issuer'] }, + ], + }) + }) +}) diff --git a/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts b/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts new file mode 100644 index 0000000000..f014f5b6a6 --- /dev/null +++ b/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts @@ -0,0 +1,234 @@ +import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' +import type { ConnectionRecord } from '../../connections' +import type { + DiscoverFeaturesDisclosureReceivedEvent, + DiscoverFeaturesQueryReceivedEvent, +} from '../DiscoverFeaturesEvents' + +import { ReplaySubject, Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getBaseConfig, makeConnection } from '../../../../tests/helpers' +import { Agent } from '../../../agent/Agent' +import { GoalCode, Feature } from '../../../agent/models' +import { DiscoverFeaturesEventTypes } from '../DiscoverFeaturesEvents' + +import { waitForDisclosureSubject, waitForQuerySubject } from './helpers' + +describe('v2 discover features', () => { + let faberAgent: Agent + let aliceAgent: Agent + let aliceConnection: ConnectionRecord + let faberConnection: ConnectionRecord + + beforeAll(async () => { + const faberMessages = new Subject() + const aliceMessages = new Subject() + const subjectMap = { + 'rxjs:faber': faberMessages, + 'rxjs:alice': aliceMessages, + } + const faberConfig = getBaseConfig('Faber Discover Features V2 E2E', { + endpoints: ['rxjs:faber'], + }) + + const aliceConfig = getBaseConfig('Alice Discover Features V2 E2E', { + endpoints: ['rxjs:alice'], + }) + faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await faberAgent.initialize() + + aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + ;[faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Faber asks Alice for issue credential protocol support', async () => { + const faberReplay = new ReplaySubject() + const aliceReplay = new ReplaySubject() + + faberAgent.discovery.config.autoAcceptQueries + faberAgent.events + .observable(DiscoverFeaturesEventTypes.DisclosureReceived) + .subscribe(faberReplay) + aliceAgent.events + .observable(DiscoverFeaturesEventTypes.QueryReceived) + .subscribe(aliceReplay) + + await faberAgent.discovery.queryFeatures({ + connectionId: faberConnection.id, + protocolVersion: 'v2', + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + }) + + const query = await waitForQuerySubject(aliceReplay, { timeoutMs: 10000 }) + + expect(query).toMatchObject({ + protocolVersion: 'v2', + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + }) + + const disclosure = await waitForDisclosureSubject(faberReplay, { timeoutMs: 10000 }) + + expect(disclosure).toMatchObject({ + protocolVersion: 'v2', + disclosures: [ + { type: 'protocol', id: 'https://didcomm.org/issue-credential/1.0', roles: ['holder', 'issuer'] }, + { type: 'protocol', id: 'https://didcomm.org/issue-credential/2.0', roles: ['holder', 'issuer'] }, + ], + }) + }) + + test('Faber defines a supported goal code and Alice queries', async () => { + const faberReplay = new ReplaySubject() + const aliceReplay = new ReplaySubject() + + aliceAgent.events + .observable(DiscoverFeaturesEventTypes.DisclosureReceived) + .subscribe(aliceReplay) + faberAgent.events + .observable(DiscoverFeaturesEventTypes.QueryReceived) + .subscribe(faberReplay) + + // Register some goal codes + faberAgent.features.register(new GoalCode({ id: 'faber.vc.issuance' }), new GoalCode({ id: 'faber.vc.query' })) + + await aliceAgent.discovery.queryFeatures({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + queries: [{ featureType: 'goal-code', match: '*' }], + }) + + const query = await waitForQuerySubject(faberReplay, { timeoutMs: 10000 }) + + expect(query).toMatchObject({ + protocolVersion: 'v2', + queries: [{ featureType: 'goal-code', match: '*' }], + }) + + const disclosure = await waitForDisclosureSubject(aliceReplay, { timeoutMs: 10000 }) + + expect(disclosure).toMatchObject({ + protocolVersion: 'v2', + disclosures: [ + { type: 'goal-code', id: 'faber.vc.issuance' }, + { type: 'goal-code', id: 'faber.vc.query' }, + ], + }) + }) + + test('Faber defines a custom feature and Alice queries', async () => { + const faberReplay = new ReplaySubject() + const aliceReplay = new ReplaySubject() + + aliceAgent.events + .observable(DiscoverFeaturesEventTypes.DisclosureReceived) + .subscribe(aliceReplay) + faberAgent.events + .observable(DiscoverFeaturesEventTypes.QueryReceived) + .subscribe(faberReplay) + + // Define a custom feature type + class GenericFeature extends Feature { + public 'generic-field'!: string + + public constructor(options: { id: string; genericField: string }) { + super({ id: options.id, type: 'generic' }) + this['generic-field'] = options.genericField + } + } + + // Register a custom feature + faberAgent.features.register(new GenericFeature({ id: 'custom-feature', genericField: 'custom-field' })) + + await aliceAgent.discovery.queryFeatures({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + queries: [{ featureType: 'generic', match: 'custom-feature' }], + }) + + const query = await waitForQuerySubject(faberReplay, { timeoutMs: 10000 }) + + expect(query).toMatchObject({ + protocolVersion: 'v2', + queries: [{ featureType: 'generic', match: 'custom-feature' }], + }) + + const disclosure = await waitForDisclosureSubject(aliceReplay, { timeoutMs: 10000 }) + + expect(disclosure).toMatchObject({ + protocolVersion: 'v2', + disclosures: [ + { + type: 'generic', + id: 'custom-feature', + 'generic-field': 'custom-field', + }, + ], + }) + }) + + test('Faber proactively sends a set of features to Alice', async () => { + const faberReplay = new ReplaySubject() + const aliceReplay = new ReplaySubject() + + aliceAgent.events + .observable(DiscoverFeaturesEventTypes.DisclosureReceived) + .subscribe(aliceReplay) + faberAgent.events + .observable(DiscoverFeaturesEventTypes.QueryReceived) + .subscribe(faberReplay) + + // Register a custom feature + faberAgent.features.register( + new Feature({ id: 'AIP2.0', type: 'aip' }), + new Feature({ id: 'AIP2.0/INDYCRED', type: 'aip' }), + new Feature({ id: 'AIP2.0/MEDIATE', type: 'aip' }) + ) + + await faberAgent.discovery.discloseFeatures({ + connectionId: faberConnection.id, + protocolVersion: 'v2', + disclosureQueries: [{ featureType: 'aip', match: '*' }], + }) + + const disclosure = await waitForDisclosureSubject(aliceReplay, { timeoutMs: 10000 }) + + expect(disclosure).toMatchObject({ + protocolVersion: 'v2', + disclosures: [ + { type: 'aip', id: 'AIP2.0' }, + { type: 'aip', id: 'AIP2.0/INDYCRED' }, + { type: 'aip', id: 'AIP2.0/MEDIATE' }, + ], + }) + }) + + test('Faber asks Alice for issue credential protocol support synchronously', async () => { + const matchingFeatures = await faberAgent.discovery.queryFeatures({ + connectionId: faberConnection.id, + protocolVersion: 'v2', + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + awaitDisclosures: true, + }) + + expect(matchingFeatures).toMatchObject({ + features: [ + { type: 'protocol', id: 'https://didcomm.org/issue-credential/1.0', roles: ['holder', 'issuer'] }, + { type: 'protocol', id: 'https://didcomm.org/issue-credential/2.0', roles: ['holder', 'issuer'] }, + ], + }) + }) +}) diff --git a/packages/core/src/modules/discover-features/handlers/DiscloseMessageHandler.ts b/packages/core/src/modules/discover-features/handlers/DiscloseMessageHandler.ts deleted file mode 100644 index 2bf24a4822..0000000000 --- a/packages/core/src/modules/discover-features/handlers/DiscloseMessageHandler.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' - -import { DiscloseMessage } from '../messages' - -export class DiscloseMessageHandler implements Handler { - public supportedMessages = [DiscloseMessage] - - public async handle(inboundMessage: HandlerInboundMessage) { - // We don't really need to do anything with this at the moment - // The result can be hooked into through the generic message processed event - inboundMessage.assertReadyConnection() - } -} diff --git a/packages/core/src/modules/discover-features/handlers/QueryMessageHandler.ts b/packages/core/src/modules/discover-features/handlers/QueryMessageHandler.ts deleted file mode 100644 index 4e343b9b52..0000000000 --- a/packages/core/src/modules/discover-features/handlers/QueryMessageHandler.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' -import type { DiscoverFeaturesService } from '../services/DiscoverFeaturesService' - -import { createOutboundMessage } from '../../../agent/helpers' -import { QueryMessage } from '../messages' - -export class QueryMessageHandler implements Handler { - private discoverFeaturesService: DiscoverFeaturesService - public supportedMessages = [QueryMessage] - - public constructor(discoverFeaturesService: DiscoverFeaturesService) { - this.discoverFeaturesService = discoverFeaturesService - } - - public async handle(inboundMessage: HandlerInboundMessage) { - const connection = inboundMessage.assertReadyConnection() - - const discloseMessage = await this.discoverFeaturesService.createDisclose(inboundMessage.message) - - return createOutboundMessage(connection, discloseMessage) - } -} diff --git a/packages/core/src/modules/discover-features/handlers/index.ts b/packages/core/src/modules/discover-features/handlers/index.ts deleted file mode 100644 index 6ae21a8989..0000000000 --- a/packages/core/src/modules/discover-features/handlers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './DiscloseMessageHandler' -export * from './QueryMessageHandler' diff --git a/packages/core/src/modules/discover-features/index.ts b/packages/core/src/modules/discover-features/index.ts index f2ab347e4d..cfebfc79c6 100644 --- a/packages/core/src/modules/discover-features/index.ts +++ b/packages/core/src/modules/discover-features/index.ts @@ -1,5 +1,3 @@ export * from './DiscoverFeaturesApi' -export * from './handlers' -export * from './messages' -export * from './services' export * from './DiscoverFeaturesModule' +export * from './protocol' diff --git a/packages/core/src/modules/discover-features/protocol/index.ts b/packages/core/src/modules/discover-features/protocol/index.ts new file mode 100644 index 0000000000..4d9da63573 --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/index.ts @@ -0,0 +1,2 @@ +export * from './v1' +export * from './v2' diff --git a/packages/core/src/modules/discover-features/protocol/v1/V1DiscoverFeaturesService.ts b/packages/core/src/modules/discover-features/protocol/v1/V1DiscoverFeaturesService.ts new file mode 100644 index 0000000000..e60a9e7d0e --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v1/V1DiscoverFeaturesService.ts @@ -0,0 +1,142 @@ +import type { AgentMessage } from '../../../../agent/AgentMessage' +import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { + DiscoverFeaturesDisclosureReceivedEvent, + DiscoverFeaturesQueryReceivedEvent, +} from '../../DiscoverFeaturesEvents' +import type { + CreateDisclosureOptions, + CreateQueryOptions, + DiscoverFeaturesProtocolMsgReturnType, +} from '../../DiscoverFeaturesServiceOptions' + +import { Dispatcher } from '../../../../agent/Dispatcher' +import { EventEmitter } from '../../../../agent/EventEmitter' +import { FeatureRegistry } from '../../../../agent/FeatureRegistry' +import { Protocol } from '../../../../agent/models' +import { InjectionSymbols } from '../../../../constants' +import { AriesFrameworkError } from '../../../../error' +import { Logger } from '../../../../logger' +import { inject, injectable } from '../../../../plugins' +import { DiscoverFeaturesEventTypes } from '../../DiscoverFeaturesEvents' +import { DiscoverFeaturesModuleConfig } from '../../DiscoverFeaturesModuleConfig' +import { DiscoverFeaturesService } from '../../services' + +import { V1DiscloseMessageHandler, V1QueryMessageHandler } from './handlers' +import { V1QueryMessage, V1DiscloseMessage, DiscloseProtocol } from './messages' + +@injectable() +export class V1DiscoverFeaturesService extends DiscoverFeaturesService { + public constructor( + featureRegistry: FeatureRegistry, + eventEmitter: EventEmitter, + dispatcher: Dispatcher, + @inject(InjectionSymbols.Logger) logger: Logger, + discoverFeaturesConfig: DiscoverFeaturesModuleConfig + ) { + super(featureRegistry, eventEmitter, dispatcher, logger, discoverFeaturesConfig) + + this.registerHandlers(dispatcher) + } + + /** + * The version of the discover features protocol this service supports + */ + public readonly version = 'v1' + + private registerHandlers(dispatcher: Dispatcher) { + dispatcher.registerHandler(new V1DiscloseMessageHandler(this)) + dispatcher.registerHandler(new V1QueryMessageHandler(this)) + } + + public async createQuery( + options: CreateQueryOptions + ): Promise> { + if (options.queries.length > 1) { + throw new AriesFrameworkError('Discover Features V1 only supports a single query') + } + + if (options.queries[0].featureType !== 'protocol') { + throw new AriesFrameworkError('Discover Features V1 only supports querying for protocol support') + } + + const queryMessage = new V1QueryMessage({ + query: options.queries[0].match, + comment: options.comment, + }) + + return { message: queryMessage } + } + + public async processQuery( + messageContext: InboundMessageContext + ): Promise | void> { + const { query, threadId } = messageContext.message + + const connection = messageContext.assertReadyConnection() + + this.eventEmitter.emit(messageContext.agentContext, { + type: DiscoverFeaturesEventTypes.QueryReceived, + payload: { + message: messageContext.message, + connection, + queries: [{ featureType: 'protocol', match: query }], + protocolVersion: this.version, + threadId, + }, + }) + + // Process query and send responde automatically if configured to do so, otherwise + // just send the event and let controller decide + if (this.discoverFeaturesModuleConfig.autoAcceptQueries) { + return await this.createDisclosure({ + threadId, + disclosureQueries: [{ featureType: 'protocol', match: query }], + }) + } + } + + public async createDisclosure( + options: CreateDisclosureOptions + ): Promise> { + if (options.disclosureQueries.some((item) => item.featureType !== 'protocol')) { + throw new AriesFrameworkError('Discover Features V1 only supports protocols') + } + + if (!options.threadId) { + throw new AriesFrameworkError('Thread Id is required for Discover Features V1 disclosure') + } + + const matches = this.featureRegistry.query(...options.disclosureQueries) + + const discloseMessage = new V1DiscloseMessage({ + threadId: options.threadId, + protocols: matches.map( + (item) => + new DiscloseProtocol({ + protocolId: (item as Protocol).id, + roles: (item as Protocol).roles, + }) + ), + }) + + return { message: discloseMessage } + } + + public async processDisclosure(messageContext: InboundMessageContext): Promise { + const { protocols, threadId } = messageContext.message + + const connection = messageContext.assertReadyConnection() + + this.eventEmitter.emit(messageContext.agentContext, { + type: DiscoverFeaturesEventTypes.DisclosureReceived, + payload: { + message: messageContext.message, + connection, + disclosures: protocols.map((item) => new Protocol({ id: item.protocolId, roles: item.roles })), + protocolVersion: this.version, + threadId, + }, + }) + } +} diff --git a/packages/core/src/modules/discover-features/protocol/v1/__tests__/V1DiscoverFeaturesService.test.ts b/packages/core/src/modules/discover-features/protocol/v1/__tests__/V1DiscoverFeaturesService.test.ts new file mode 100644 index 0000000000..133a2b3442 --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v1/__tests__/V1DiscoverFeaturesService.test.ts @@ -0,0 +1,277 @@ +import type { + DiscoverFeaturesDisclosureReceivedEvent, + DiscoverFeaturesQueryReceivedEvent, +} from '../../../DiscoverFeaturesEvents' +import type { DiscoverFeaturesProtocolMsgReturnType } from '../../../DiscoverFeaturesServiceOptions' + +import { Subject } from 'rxjs' + +import { agentDependencies, getAgentContext, getMockConnection } from '../../../../../../tests/helpers' +import { Dispatcher } from '../../../../../agent/Dispatcher' +import { EventEmitter } from '../../../../../agent/EventEmitter' +import { FeatureRegistry } from '../../../../../agent/FeatureRegistry' +import { Protocol } from '../../../../../agent/models' +import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import { ConsoleLogger } from '../../../../../logger/ConsoleLogger' +import { DidExchangeState } from '../../../../../modules/connections' +import { DiscoverFeaturesEventTypes } from '../../../DiscoverFeaturesEvents' +import { DiscoverFeaturesModuleConfig } from '../../../DiscoverFeaturesModuleConfig' +import { V1DiscoverFeaturesService } from '../V1DiscoverFeaturesService' +import { V1DiscloseMessage, V1QueryMessage } from '../messages' + +jest.mock('../../../../../agent/Dispatcher') +const DispatcherMock = Dispatcher as jest.Mock +const eventEmitter = new EventEmitter(agentDependencies, new Subject()) +const featureRegistry = new FeatureRegistry() +featureRegistry.register(new Protocol({ id: 'https://didcomm.org/connections/1.0' })) +featureRegistry.register(new Protocol({ id: 'https://didcomm.org/notification/1.0', roles: ['role-1', 'role-2'] })) +featureRegistry.register(new Protocol({ id: 'https://didcomm.org/issue-credential/1.0' })) + +jest.mock('../../../../../logger/Logger') +const LoggerMock = ConsoleLogger as jest.Mock + +describe('V1DiscoverFeaturesService - auto accept queries', () => { + const discoverFeaturesModuleConfig = new DiscoverFeaturesModuleConfig({ autoAcceptQueries: true }) + + const discoverFeaturesService = new V1DiscoverFeaturesService( + featureRegistry, + eventEmitter, + new DispatcherMock(), + new LoggerMock(), + discoverFeaturesModuleConfig + ) + describe('createDisclosure', () => { + it('should return all protocols when query is *', async () => { + const queryMessage = new V1QueryMessage({ + query: '*', + }) + + const { message } = await discoverFeaturesService.createDisclosure({ + disclosureQueries: [{ featureType: 'protocol', match: queryMessage.query }], + threadId: queryMessage.threadId, + }) + + expect(message.protocols.map((p) => p.protocolId)).toStrictEqual([ + 'https://didcomm.org/connections/1.0', + 'https://didcomm.org/notification/1.0', + 'https://didcomm.org/issue-credential/1.0', + ]) + }) + + it('should return only one protocol if the query specifies a specific protocol', async () => { + const queryMessage = new V1QueryMessage({ + query: 'https://didcomm.org/connections/1.0', + }) + + const { message } = await discoverFeaturesService.createDisclosure({ + disclosureQueries: [{ featureType: 'protocol', match: queryMessage.query }], + threadId: queryMessage.threadId, + }) + + expect(message.protocols.map((p) => p.protocolId)).toStrictEqual(['https://didcomm.org/connections/1.0']) + }) + + it('should respect a wild card at the end of the query', async () => { + const queryMessage = new V1QueryMessage({ + query: 'https://didcomm.org/connections/*', + }) + + const { message } = await discoverFeaturesService.createDisclosure({ + disclosureQueries: [{ featureType: 'protocol', match: queryMessage.query }], + threadId: queryMessage.threadId, + }) + + expect(message.protocols.map((p) => p.protocolId)).toStrictEqual(['https://didcomm.org/connections/1.0']) + }) + + it('should send an empty array if no feature matches query', async () => { + const queryMessage = new V1QueryMessage({ + query: 'not-supported', + }) + + const { message } = await discoverFeaturesService.createDisclosure({ + disclosureQueries: [{ featureType: 'protocol', match: queryMessage.query }], + threadId: queryMessage.threadId, + }) + + expect(message.protocols.map((p) => p.protocolId)).toStrictEqual([]) + }) + + it('should throw error if features other than protocols are disclosed', async () => { + expect( + discoverFeaturesService.createDisclosure({ + disclosureQueries: [ + { featureType: 'protocol', match: '1' }, + { featureType: 'goal-code', match: '2' }, + ], + threadId: '1234', + }) + ).rejects.toThrow('Discover Features V1 only supports protocols') + }) + + it('should throw error if no thread id is provided', async () => { + expect( + discoverFeaturesService.createDisclosure({ + disclosureQueries: [{ featureType: 'protocol', match: '1' }], + }) + ).rejects.toThrow('Thread Id is required for Discover Features V1 disclosure') + }) + }) + + describe('createQuery', () => { + it('should return a query message with the query and comment', async () => { + const { message } = await discoverFeaturesService.createQuery({ + queries: [{ featureType: 'protocol', match: '*' }], + comment: 'Hello', + }) + + expect(message.query).toBe('*') + expect(message.comment).toBe('Hello') + }) + + it('should throw error if multiple features are queried', async () => { + expect( + discoverFeaturesService.createQuery({ + queries: [ + { featureType: 'protocol', match: '1' }, + { featureType: 'protocol', match: '2' }, + ], + }) + ).rejects.toThrow('Discover Features V1 only supports a single query') + }) + + it('should throw error if a feature other than protocol is queried', async () => { + expect( + discoverFeaturesService.createQuery({ + queries: [{ featureType: 'goal-code', match: '1' }], + }) + ).rejects.toThrow('Discover Features V1 only supports querying for protocol support') + }) + }) + + describe('processQuery', () => { + it('should emit event and create disclosure message', async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(DiscoverFeaturesEventTypes.QueryReceived, eventListenerMock) + + const queryMessage = new V1QueryMessage({ query: '*' }) + + const connection = getMockConnection({ state: DidExchangeState.Completed }) + const messageContext = new InboundMessageContext(queryMessage, { + agentContext: getAgentContext(), + connection, + }) + const outboundMessage = await discoverFeaturesService.processQuery(messageContext) + + eventEmitter.off(DiscoverFeaturesEventTypes.QueryReceived, eventListenerMock) + + expect(eventListenerMock).toHaveBeenCalledWith( + expect.objectContaining({ + type: DiscoverFeaturesEventTypes.QueryReceived, + payload: expect.objectContaining({ + connection, + protocolVersion: 'v1', + queries: [{ featureType: 'protocol', match: queryMessage.query }], + threadId: queryMessage.threadId, + }), + }) + ) + expect(outboundMessage).toBeDefined() + expect( + (outboundMessage as DiscoverFeaturesProtocolMsgReturnType).message.protocols.map( + (p) => p.protocolId + ) + ).toStrictEqual([ + 'https://didcomm.org/connections/1.0', + 'https://didcomm.org/notification/1.0', + 'https://didcomm.org/issue-credential/1.0', + ]) + }) + }) + + describe('processDisclosure', () => { + it('should emit event', async () => { + const eventListenerMock = jest.fn() + eventEmitter.on( + DiscoverFeaturesEventTypes.DisclosureReceived, + eventListenerMock + ) + + const discloseMessage = new V1DiscloseMessage({ + protocols: [{ protocolId: 'prot1', roles: ['role1', 'role2'] }, { protocolId: 'prot2' }], + threadId: '1234', + }) + + const connection = getMockConnection({ state: DidExchangeState.Completed }) + const messageContext = new InboundMessageContext(discloseMessage, { + agentContext: getAgentContext(), + connection, + }) + await discoverFeaturesService.processDisclosure(messageContext) + + eventEmitter.off( + DiscoverFeaturesEventTypes.DisclosureReceived, + eventListenerMock + ) + + expect(eventListenerMock).toHaveBeenCalledWith( + expect.objectContaining({ + type: DiscoverFeaturesEventTypes.DisclosureReceived, + payload: expect.objectContaining({ + connection, + protocolVersion: 'v1', + disclosures: [ + { type: 'protocol', id: 'prot1', roles: ['role1', 'role2'] }, + { type: 'protocol', id: 'prot2' }, + ], + + threadId: discloseMessage.threadId, + }), + }) + ) + }) + }) +}) + +describe('V1DiscoverFeaturesService - auto accept disabled', () => { + const discoverFeaturesModuleConfig = new DiscoverFeaturesModuleConfig({ autoAcceptQueries: false }) + + const discoverFeaturesService = new V1DiscoverFeaturesService( + featureRegistry, + eventEmitter, + new DispatcherMock(), + new LoggerMock(), + discoverFeaturesModuleConfig + ) + + describe('processQuery', () => { + it('should emit event and not send any message', async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(DiscoverFeaturesEventTypes.QueryReceived, eventListenerMock) + + const queryMessage = new V1QueryMessage({ query: '*' }) + + const connection = getMockConnection({ state: DidExchangeState.Completed }) + const messageContext = new InboundMessageContext(queryMessage, { + agentContext: getAgentContext(), + connection, + }) + const outboundMessage = await discoverFeaturesService.processQuery(messageContext) + + eventEmitter.off(DiscoverFeaturesEventTypes.QueryReceived, eventListenerMock) + + expect(eventListenerMock).toHaveBeenCalledWith( + expect.objectContaining({ + type: DiscoverFeaturesEventTypes.QueryReceived, + payload: expect.objectContaining({ + connection, + protocolVersion: 'v1', + queries: [{ featureType: 'protocol', match: queryMessage.query }], + threadId: queryMessage.threadId, + }), + }) + ) + expect(outboundMessage).toBeUndefined() + }) + }) +}) diff --git a/packages/core/src/modules/discover-features/protocol/v1/handlers/V1DiscloseMessageHandler.ts b/packages/core/src/modules/discover-features/protocol/v1/handlers/V1DiscloseMessageHandler.ts new file mode 100644 index 0000000000..e7a47da870 --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v1/handlers/V1DiscloseMessageHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { V1DiscoverFeaturesService } from '../V1DiscoverFeaturesService' + +import { V1DiscloseMessage } from '../messages' + +export class V1DiscloseMessageHandler implements Handler { + public supportedMessages = [V1DiscloseMessage] + private discoverFeaturesService: V1DiscoverFeaturesService + + public constructor(discoverFeaturesService: V1DiscoverFeaturesService) { + this.discoverFeaturesService = discoverFeaturesService + } + + public async handle(inboundMessage: HandlerInboundMessage) { + await this.discoverFeaturesService.processDisclosure(inboundMessage) + } +} diff --git a/packages/core/src/modules/discover-features/protocol/v1/handlers/V1QueryMessageHandler.ts b/packages/core/src/modules/discover-features/protocol/v1/handlers/V1QueryMessageHandler.ts new file mode 100644 index 0000000000..3ef5a66231 --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v1/handlers/V1QueryMessageHandler.ts @@ -0,0 +1,24 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { V1DiscoverFeaturesService } from '../V1DiscoverFeaturesService' + +import { createOutboundMessage } from '../../../../../agent/helpers' +import { V1QueryMessage } from '../messages' + +export class V1QueryMessageHandler implements Handler { + private discoverFeaturesService: V1DiscoverFeaturesService + public supportedMessages = [V1QueryMessage] + + public constructor(discoverFeaturesService: V1DiscoverFeaturesService) { + this.discoverFeaturesService = discoverFeaturesService + } + + public async handle(inboundMessage: HandlerInboundMessage) { + const connection = inboundMessage.assertReadyConnection() + + const discloseMessage = await this.discoverFeaturesService.processQuery(inboundMessage) + + if (discloseMessage) { + return createOutboundMessage(connection, discloseMessage.message) + } + } +} diff --git a/packages/core/src/modules/discover-features/protocol/v1/handlers/index.ts b/packages/core/src/modules/discover-features/protocol/v1/handlers/index.ts new file mode 100644 index 0000000000..73f3391154 --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v1/handlers/index.ts @@ -0,0 +1,2 @@ +export * from './V1DiscloseMessageHandler' +export * from './V1QueryMessageHandler' diff --git a/packages/core/src/modules/discover-features/protocol/v1/index.ts b/packages/core/src/modules/discover-features/protocol/v1/index.ts new file mode 100644 index 0000000000..e13fec27de --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v1/index.ts @@ -0,0 +1,2 @@ +export * from './V1DiscoverFeaturesService' +export * from './messages' diff --git a/packages/core/src/modules/discover-features/messages/DiscloseMessage.ts b/packages/core/src/modules/discover-features/protocol/v1/messages/DiscloseMessage.ts similarity index 79% rename from packages/core/src/modules/discover-features/messages/DiscloseMessage.ts rename to packages/core/src/modules/discover-features/protocol/v1/messages/DiscloseMessage.ts index 82dbe9451e..800900424b 100644 --- a/packages/core/src/modules/discover-features/messages/DiscloseMessage.ts +++ b/packages/core/src/modules/discover-features/protocol/v1/messages/DiscloseMessage.ts @@ -1,8 +1,8 @@ import { Expose, Type } from 'class-transformer' import { IsInstance, IsOptional, IsString } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' -import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' export interface DiscloseProtocolOptions { protocolId: string @@ -32,7 +32,7 @@ export interface DiscoverFeaturesDiscloseMessageOptions { protocols: DiscloseProtocolOptions[] } -export class DiscloseMessage extends AgentMessage { +export class V1DiscloseMessage extends AgentMessage { public constructor(options: DiscoverFeaturesDiscloseMessageOptions) { super() @@ -45,8 +45,8 @@ export class DiscloseMessage extends AgentMessage { } } - @IsValidMessageType(DiscloseMessage.type) - public readonly type = DiscloseMessage.type.messageTypeUri + @IsValidMessageType(V1DiscloseMessage.type) + public readonly type = V1DiscloseMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/discover-features/1.0/disclose') @IsInstance(DiscloseProtocol, { each: true }) diff --git a/packages/core/src/modules/discover-features/messages/QueryMessage.ts b/packages/core/src/modules/discover-features/protocol/v1/messages/QueryMessage.ts similarity index 65% rename from packages/core/src/modules/discover-features/messages/QueryMessage.ts rename to packages/core/src/modules/discover-features/protocol/v1/messages/QueryMessage.ts index 35f635ccd5..7b8d5e26b4 100644 --- a/packages/core/src/modules/discover-features/messages/QueryMessage.ts +++ b/packages/core/src/modules/discover-features/protocol/v1/messages/QueryMessage.ts @@ -1,7 +1,7 @@ import { IsOptional, IsString } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' -import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' export interface DiscoverFeaturesQueryMessageOptions { id?: string @@ -9,7 +9,7 @@ export interface DiscoverFeaturesQueryMessageOptions { comment?: string } -export class QueryMessage extends AgentMessage { +export class V1QueryMessage extends AgentMessage { public constructor(options: DiscoverFeaturesQueryMessageOptions) { super() @@ -20,8 +20,8 @@ export class QueryMessage extends AgentMessage { } } - @IsValidMessageType(QueryMessage.type) - public readonly type = QueryMessage.type.messageTypeUri + @IsValidMessageType(V1QueryMessage.type) + public readonly type = V1QueryMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/discover-features/1.0/query') @IsString() diff --git a/packages/core/src/modules/discover-features/messages/index.ts b/packages/core/src/modules/discover-features/protocol/v1/messages/index.ts similarity index 100% rename from packages/core/src/modules/discover-features/messages/index.ts rename to packages/core/src/modules/discover-features/protocol/v1/messages/index.ts diff --git a/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts b/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts new file mode 100644 index 0000000000..99ca5a948b --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts @@ -0,0 +1,113 @@ +import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { + DiscoverFeaturesDisclosureReceivedEvent, + DiscoverFeaturesQueryReceivedEvent, +} from '../../DiscoverFeaturesEvents' +import type { + CreateQueryOptions, + DiscoverFeaturesProtocolMsgReturnType, + CreateDisclosureOptions, +} from '../../DiscoverFeaturesServiceOptions' + +import { Dispatcher } from '../../../../agent/Dispatcher' +import { EventEmitter } from '../../../../agent/EventEmitter' +import { FeatureRegistry } from '../../../../agent/FeatureRegistry' +import { InjectionSymbols } from '../../../../constants' +import { Logger } from '../../../../logger' +import { inject, injectable } from '../../../../plugins' +import { DiscoverFeaturesEventTypes } from '../../DiscoverFeaturesEvents' +import { DiscoverFeaturesModuleConfig } from '../../DiscoverFeaturesModuleConfig' +import { DiscoverFeaturesService } from '../../services' + +import { V2DisclosuresMessageHandler, V2QueriesMessageHandler } from './handlers' +import { V2QueriesMessage, V2DisclosuresMessage } from './messages' + +@injectable() +export class V2DiscoverFeaturesService extends DiscoverFeaturesService { + public constructor( + featureRegistry: FeatureRegistry, + eventEmitter: EventEmitter, + dispatcher: Dispatcher, + @inject(InjectionSymbols.Logger) logger: Logger, + discoverFeaturesModuleConfig: DiscoverFeaturesModuleConfig + ) { + super(featureRegistry, eventEmitter, dispatcher, logger, discoverFeaturesModuleConfig) + this.registerHandlers(dispatcher) + } + + /** + * The version of the discover features protocol this service supports + */ + public readonly version = 'v2' + + private registerHandlers(dispatcher: Dispatcher) { + dispatcher.registerHandler(new V2DisclosuresMessageHandler(this)) + dispatcher.registerHandler(new V2QueriesMessageHandler(this)) + } + + public async createQuery( + options: CreateQueryOptions + ): Promise> { + const queryMessage = new V2QueriesMessage({ queries: options.queries }) + + return { message: queryMessage } + } + + public async processQuery( + messageContext: InboundMessageContext + ): Promise | void> { + const { queries, threadId } = messageContext.message + + const connection = messageContext.assertReadyConnection() + + this.eventEmitter.emit(messageContext.agentContext, { + type: DiscoverFeaturesEventTypes.QueryReceived, + payload: { + message: messageContext.message, + connection, + queries, + protocolVersion: this.version, + threadId, + }, + }) + + // Process query and send responde automatically if configured to do so, otherwise + // just send the event and let controller decide + if (this.discoverFeaturesModuleConfig.autoAcceptQueries) { + return await this.createDisclosure({ + threadId, + disclosureQueries: queries, + }) + } + } + + public async createDisclosure( + options: CreateDisclosureOptions + ): Promise> { + const matches = this.featureRegistry.query(...options.disclosureQueries) + + const discloseMessage = new V2DisclosuresMessage({ + threadId: options.threadId, + features: matches, + }) + + return { message: discloseMessage } + } + + public async processDisclosure(messageContext: InboundMessageContext): Promise { + const { disclosures, threadId } = messageContext.message + + const connection = messageContext.assertReadyConnection() + + this.eventEmitter.emit(messageContext.agentContext, { + type: DiscoverFeaturesEventTypes.DisclosureReceived, + payload: { + message: messageContext.message, + connection, + disclosures, + protocolVersion: this.version, + threadId, + }, + }) + } +} diff --git a/packages/core/src/modules/discover-features/protocol/v2/__tests__/V2DiscoverFeaturesService.test.ts b/packages/core/src/modules/discover-features/protocol/v2/__tests__/V2DiscoverFeaturesService.test.ts new file mode 100644 index 0000000000..9669c9a63f --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v2/__tests__/V2DiscoverFeaturesService.test.ts @@ -0,0 +1,288 @@ +import type { + DiscoverFeaturesDisclosureReceivedEvent, + DiscoverFeaturesQueryReceivedEvent, +} from '../../../DiscoverFeaturesEvents' +import type { DiscoverFeaturesProtocolMsgReturnType } from '../../../DiscoverFeaturesServiceOptions' + +import { Subject } from 'rxjs' + +import { agentDependencies, getAgentContext, getMockConnection } from '../../../../../../tests/helpers' +import { Dispatcher } from '../../../../../agent/Dispatcher' +import { EventEmitter } from '../../../../../agent/EventEmitter' +import { FeatureRegistry } from '../../../../../agent/FeatureRegistry' +import { InboundMessageContext, Protocol, GoalCode } from '../../../../../agent/models' +import { ConsoleLogger } from '../../../../../logger/ConsoleLogger' +import { DidExchangeState } from '../../../../connections' +import { DiscoverFeaturesEventTypes } from '../../../DiscoverFeaturesEvents' +import { DiscoverFeaturesModuleConfig } from '../../../DiscoverFeaturesModuleConfig' +import { V2DiscoverFeaturesService } from '../V2DiscoverFeaturesService' +import { V2DisclosuresMessage, V2QueriesMessage } from '../messages' + +jest.mock('../../../../../agent/Dispatcher') +const DispatcherMock = Dispatcher as jest.Mock +const eventEmitter = new EventEmitter(agentDependencies, new Subject()) +const featureRegistry = new FeatureRegistry() +featureRegistry.register(new Protocol({ id: 'https://didcomm.org/connections/1.0' })) +featureRegistry.register(new Protocol({ id: 'https://didcomm.org/notification/1.0', roles: ['role-1', 'role-2'] })) +featureRegistry.register(new Protocol({ id: 'https://didcomm.org/issue-credential/1.0' })) +featureRegistry.register(new GoalCode({ id: 'aries.vc.1' })) +featureRegistry.register(new GoalCode({ id: 'aries.vc.2' })) +featureRegistry.register(new GoalCode({ id: 'caries.vc.3' })) + +jest.mock('../../../../../logger/Logger') +const LoggerMock = ConsoleLogger as jest.Mock + +describe('V2DiscoverFeaturesService - auto accept queries', () => { + const discoverFeaturesModuleConfig = new DiscoverFeaturesModuleConfig({ autoAcceptQueries: true }) + + const discoverFeaturesService = new V2DiscoverFeaturesService( + featureRegistry, + eventEmitter, + new DispatcherMock(), + new LoggerMock(), + discoverFeaturesModuleConfig + ) + describe('createDisclosure', () => { + it('should return all items when query is *', async () => { + const queryMessage = new V2QueriesMessage({ + queries: [ + { featureType: Protocol.type, match: '*' }, + { featureType: GoalCode.type, match: '*' }, + ], + }) + + const { message } = await discoverFeaturesService.createDisclosure({ + disclosureQueries: queryMessage.queries, + threadId: queryMessage.threadId, + }) + + expect(message.disclosures.map((p) => p.id)).toStrictEqual([ + 'https://didcomm.org/connections/1.0', + 'https://didcomm.org/notification/1.0', + 'https://didcomm.org/issue-credential/1.0', + 'aries.vc.1', + 'aries.vc.2', + 'caries.vc.3', + ]) + }) + + it('should return only one protocol if the query specifies a specific protocol', async () => { + const queryMessage = new V2QueriesMessage({ + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/connections/1.0' }], + }) + + const { message } = await discoverFeaturesService.createDisclosure({ + disclosureQueries: queryMessage.queries, + threadId: queryMessage.threadId, + }) + + expect(message.disclosures).toEqual([{ type: 'protocol', id: 'https://didcomm.org/connections/1.0' }]) + }) + + it('should respect a wild card at the end of the query', async () => { + const queryMessage = new V2QueriesMessage({ + queries: [ + { featureType: 'protocol', match: 'https://didcomm.org/connections/*' }, + { featureType: 'goal-code', match: 'aries*' }, + ], + }) + + const { message } = await discoverFeaturesService.createDisclosure({ + disclosureQueries: queryMessage.queries, + threadId: queryMessage.threadId, + }) + + expect(message.disclosures.map((p) => p.id)).toStrictEqual([ + 'https://didcomm.org/connections/1.0', + 'aries.vc.1', + 'aries.vc.2', + ]) + }) + + it('should send an empty array if no feature matches query', async () => { + const queryMessage = new V2QueriesMessage({ + queries: [{ featureType: 'anything', match: 'not-supported' }], + }) + + const { message } = await discoverFeaturesService.createDisclosure({ + disclosureQueries: queryMessage.queries, + threadId: queryMessage.threadId, + }) + + expect(message.disclosures).toStrictEqual([]) + }) + + it('should accept an empty queries object', async () => { + const { message } = await discoverFeaturesService.createDisclosure({ + disclosureQueries: [], + threadId: '1234', + }) + + expect(message.disclosures).toStrictEqual([]) + }) + + it('should accept no thread Id', async () => { + const { message } = await discoverFeaturesService.createDisclosure({ + disclosureQueries: [{ featureType: 'goal-code', match: 'caries*' }], + }) + + expect(message.disclosures).toEqual([ + { + type: 'goal-code', + id: 'caries.vc.3', + }, + ]) + expect(message.threadId).toEqual(message.id) + }) + }) + + describe('createQuery', () => { + it('should return a queries message with the query and comment', async () => { + const { message } = await discoverFeaturesService.createQuery({ + queries: [{ featureType: 'protocol', match: '*' }], + }) + + expect(message.queries).toEqual([{ featureType: 'protocol', match: '*' }]) + }) + + it('should accept multiple features', async () => { + const { message } = await discoverFeaturesService.createQuery({ + queries: [ + { featureType: 'protocol', match: '1' }, + { featureType: 'anything', match: '2' }, + ], + }) + + expect(message.queries).toEqual([ + { featureType: 'protocol', match: '1' }, + { featureType: 'anything', match: '2' }, + ]) + }) + }) + + describe('processQuery', () => { + it('should emit event and create disclosure message', async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(DiscoverFeaturesEventTypes.QueryReceived, eventListenerMock) + + const queryMessage = new V2QueriesMessage({ queries: [{ featureType: 'protocol', match: '*' }] }) + + const connection = getMockConnection({ state: DidExchangeState.Completed }) + const messageContext = new InboundMessageContext(queryMessage, { + agentContext: getAgentContext(), + connection, + }) + const outboundMessage = await discoverFeaturesService.processQuery(messageContext) + + eventEmitter.off(DiscoverFeaturesEventTypes.QueryReceived, eventListenerMock) + + expect(eventListenerMock).toHaveBeenCalledWith( + expect.objectContaining({ + type: DiscoverFeaturesEventTypes.QueryReceived, + payload: expect.objectContaining({ + connection, + protocolVersion: 'v2', + queries: queryMessage.queries, + threadId: queryMessage.threadId, + }), + }) + ) + expect(outboundMessage).toBeDefined() + expect( + (outboundMessage as DiscoverFeaturesProtocolMsgReturnType).message.disclosures.map( + (p) => p.id + ) + ).toStrictEqual([ + 'https://didcomm.org/connections/1.0', + 'https://didcomm.org/notification/1.0', + 'https://didcomm.org/issue-credential/1.0', + ]) + }) + }) + + describe('processDisclosure', () => { + it('should emit event', async () => { + const eventListenerMock = jest.fn() + eventEmitter.on( + DiscoverFeaturesEventTypes.DisclosureReceived, + eventListenerMock + ) + + const discloseMessage = new V2DisclosuresMessage({ + features: [new Protocol({ id: 'prot1', roles: ['role1', 'role2'] }), new Protocol({ id: 'prot2' })], + threadId: '1234', + }) + + const connection = getMockConnection({ state: DidExchangeState.Completed }) + const messageContext = new InboundMessageContext(discloseMessage, { + agentContext: getAgentContext(), + connection, + }) + await discoverFeaturesService.processDisclosure(messageContext) + + eventEmitter.off( + DiscoverFeaturesEventTypes.DisclosureReceived, + eventListenerMock + ) + + expect(eventListenerMock).toHaveBeenCalledWith( + expect.objectContaining({ + type: DiscoverFeaturesEventTypes.DisclosureReceived, + payload: expect.objectContaining({ + connection, + protocolVersion: 'v2', + disclosures: [ + { type: 'protocol', id: 'prot1', roles: ['role1', 'role2'] }, + { type: 'protocol', id: 'prot2' }, + ], + + threadId: discloseMessage.threadId, + }), + }) + ) + }) + }) +}) + +describe('V2DiscoverFeaturesService - auto accept disabled', () => { + const discoverFeaturesModuleConfig = new DiscoverFeaturesModuleConfig({ autoAcceptQueries: false }) + + const discoverFeaturesService = new V2DiscoverFeaturesService( + featureRegistry, + eventEmitter, + new DispatcherMock(), + new LoggerMock(), + discoverFeaturesModuleConfig + ) + + describe('processQuery', () => { + it('should emit event and not send any message', async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(DiscoverFeaturesEventTypes.QueryReceived, eventListenerMock) + + const queryMessage = new V2QueriesMessage({ queries: [{ featureType: 'protocol', match: '*' }] }) + + const connection = getMockConnection({ state: DidExchangeState.Completed }) + const messageContext = new InboundMessageContext(queryMessage, { + agentContext: getAgentContext(), + connection, + }) + const outboundMessage = await discoverFeaturesService.processQuery(messageContext) + + eventEmitter.off(DiscoverFeaturesEventTypes.QueryReceived, eventListenerMock) + + expect(eventListenerMock).toHaveBeenCalledWith( + expect.objectContaining({ + type: DiscoverFeaturesEventTypes.QueryReceived, + payload: expect.objectContaining({ + connection, + protocolVersion: 'v2', + queries: queryMessage.queries, + threadId: queryMessage.threadId, + }), + }) + ) + expect(outboundMessage).toBeUndefined() + }) + }) +}) diff --git a/packages/core/src/modules/discover-features/protocol/v2/handlers/V2DisclosuresMessageHandler.ts b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2DisclosuresMessageHandler.ts new file mode 100644 index 0000000000..7bf631f92c --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2DisclosuresMessageHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { V2DiscoverFeaturesService } from '../V2DiscoverFeaturesService' + +import { V2DisclosuresMessage } from '../messages' + +export class V2DisclosuresMessageHandler implements Handler { + private discoverFeaturesService: V2DiscoverFeaturesService + public supportedMessages = [V2DisclosuresMessage] + + public constructor(discoverFeaturesService: V2DiscoverFeaturesService) { + this.discoverFeaturesService = discoverFeaturesService + } + + public async handle(inboundMessage: HandlerInboundMessage) { + await this.discoverFeaturesService.processDisclosure(inboundMessage) + } +} diff --git a/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts new file mode 100644 index 0000000000..d637bf2bc7 --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts @@ -0,0 +1,24 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { V2DiscoverFeaturesService } from '../V2DiscoverFeaturesService' + +import { createOutboundMessage } from '../../../../../agent/helpers' +import { V2QueriesMessage } from '../messages' + +export class V2QueriesMessageHandler implements Handler { + private discoverFeaturesService: V2DiscoverFeaturesService + public supportedMessages = [V2QueriesMessage] + + public constructor(discoverFeaturesService: V2DiscoverFeaturesService) { + this.discoverFeaturesService = discoverFeaturesService + } + + public async handle(inboundMessage: HandlerInboundMessage) { + const connection = inboundMessage.assertReadyConnection() + + const discloseMessage = await this.discoverFeaturesService.processQuery(inboundMessage) + + if (discloseMessage) { + return createOutboundMessage(connection, discloseMessage.message) + } + } +} diff --git a/packages/core/src/modules/discover-features/protocol/v2/handlers/index.ts b/packages/core/src/modules/discover-features/protocol/v2/handlers/index.ts new file mode 100644 index 0000000000..e4e6ce65a4 --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v2/handlers/index.ts @@ -0,0 +1,2 @@ +export * from './V2DisclosuresMessageHandler' +export * from './V2QueriesMessageHandler' diff --git a/packages/core/src/modules/discover-features/protocol/v2/index.ts b/packages/core/src/modules/discover-features/protocol/v2/index.ts new file mode 100644 index 0000000000..f3bc1281ae --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v2/index.ts @@ -0,0 +1,2 @@ +export * from './V2DiscoverFeaturesService' +export * from './messages' diff --git a/packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresMessage.ts b/packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresMessage.ts new file mode 100644 index 0000000000..de029d7b29 --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v2/messages/V2DisclosuresMessage.ts @@ -0,0 +1,36 @@ +import { Type } from 'class-transformer' +import { IsInstance } from 'class-validator' + +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Feature } from '../../../../../agent/models' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' + +export interface V2DisclosuresMessageOptions { + id?: string + threadId?: string + features?: Feature[] +} + +export class V2DisclosuresMessage extends AgentMessage { + public constructor(options: V2DisclosuresMessageOptions) { + super() + + if (options) { + this.id = options.id ?? this.generateId() + this.disclosures = options.features ?? [] + if (options.threadId) { + this.setThread({ + threadId: options.threadId, + }) + } + } + } + + @IsValidMessageType(V2DisclosuresMessage.type) + public readonly type = V2DisclosuresMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/discover-features/2.0/disclosures') + + @IsInstance(Feature, { each: true }) + @Type(() => Feature) + public disclosures!: Feature[] +} diff --git a/packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesMessage.ts b/packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesMessage.ts new file mode 100644 index 0000000000..b5de37fa20 --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v2/messages/V2QueriesMessage.ts @@ -0,0 +1,34 @@ +import type { FeatureQueryOptions } from '../../../../../agent/models' + +import { Type } from 'class-transformer' +import { ArrayNotEmpty, IsInstance } from 'class-validator' + +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { FeatureQuery } from '../../../../../agent/models' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' + +export interface V2DiscoverFeaturesQueriesMessageOptions { + id?: string + queries: FeatureQueryOptions[] + comment?: string +} + +export class V2QueriesMessage extends AgentMessage { + public constructor(options: V2DiscoverFeaturesQueriesMessageOptions) { + super() + + if (options) { + this.id = options.id ?? this.generateId() + this.queries = options.queries.map((q) => new FeatureQuery(q)) + } + } + + @IsValidMessageType(V2QueriesMessage.type) + public readonly type = V2QueriesMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/discover-features/2.0/queries') + + @IsInstance(FeatureQuery, { each: true }) + @Type(() => FeatureQuery) + @ArrayNotEmpty() + public queries!: FeatureQuery[] +} diff --git a/packages/core/src/modules/discover-features/protocol/v2/messages/index.ts b/packages/core/src/modules/discover-features/protocol/v2/messages/index.ts new file mode 100644 index 0000000000..ec88209bce --- /dev/null +++ b/packages/core/src/modules/discover-features/protocol/v2/messages/index.ts @@ -0,0 +1,2 @@ +export * from './V2DisclosuresMessage' +export * from './V2QueriesMessage' diff --git a/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts b/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts index 433792070c..0720c8747a 100644 --- a/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts +++ b/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts @@ -1,42 +1,46 @@ -import { Dispatcher } from '../../../agent/Dispatcher' -import { injectable } from '../../../plugins' -import { QueryMessage, DiscloseMessage } from '../messages' - -@injectable() -export class DiscoverFeaturesService { - private dispatcher: Dispatcher - - public constructor(dispatcher: Dispatcher) { +import type { AgentMessage } from '../../../agent/AgentMessage' +import type { Dispatcher } from '../../../agent/Dispatcher' +import type { EventEmitter } from '../../../agent/EventEmitter' +import type { FeatureRegistry } from '../../../agent/FeatureRegistry' +import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { Logger } from '../../../logger' +import type { DiscoverFeaturesModuleConfig } from '../DiscoverFeaturesModuleConfig' +import type { + CreateDisclosureOptions, + CreateQueryOptions, + DiscoverFeaturesProtocolMsgReturnType, +} from '../DiscoverFeaturesServiceOptions' + +export abstract class DiscoverFeaturesService { + protected featureRegistry: FeatureRegistry + protected eventEmitter: EventEmitter + protected dispatcher: Dispatcher + protected logger: Logger + protected discoverFeaturesModuleConfig: DiscoverFeaturesModuleConfig + + public constructor( + featureRegistry: FeatureRegistry, + eventEmitter: EventEmitter, + dispatcher: Dispatcher, + logger: Logger, + discoverFeaturesModuleConfig: DiscoverFeaturesModuleConfig + ) { + this.featureRegistry = featureRegistry + this.eventEmitter = eventEmitter this.dispatcher = dispatcher + this.logger = logger + this.discoverFeaturesModuleConfig = discoverFeaturesModuleConfig } - public async createQuery(options: { query: string; comment?: string }) { - const queryMessage = new QueryMessage(options) - - return queryMessage - } - - public async createDisclose(queryMessage: QueryMessage) { - const { query } = queryMessage + abstract readonly version: string - const messageFamilies = this.dispatcher.supportedProtocols + abstract createQuery(options: CreateQueryOptions): Promise> + abstract processQuery( + messageContext: InboundMessageContext + ): Promise | void> - let protocols: string[] = [] - - if (query === '*') { - protocols = messageFamilies - } else if (query.endsWith('*')) { - const match = query.slice(0, -1) - protocols = messageFamilies.filter((m) => m.startsWith(match)) - } else if (messageFamilies.includes(query)) { - protocols = [query] - } - - const discloseMessage = new DiscloseMessage({ - threadId: queryMessage.threadId, - protocols: protocols.map((protocolId) => ({ protocolId })), - }) - - return discloseMessage - } + abstract createDisclosure( + options: CreateDisclosureOptions + ): Promise> + abstract processDisclosure(messageContext: InboundMessageContext): Promise } diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts index a5edd589c0..7408417bd7 100644 --- a/packages/core/src/modules/oob/OutOfBandApi.ts +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -12,8 +12,10 @@ import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { EventEmitter } from '../../agent/EventEmitter' import { filterContextCorrelationId, AgentEventTypes } from '../../agent/Events' +import { FeatureRegistry } from '../../agent/FeatureRegistry' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' +import { FeatureQuery } from '../../agent/models' import { InjectionSymbols } from '../../constants' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' @@ -83,6 +85,7 @@ export class OutOfBandApi { private connectionsApi: ConnectionsApi private didCommMessageRepository: DidCommMessageRepository private dispatcher: Dispatcher + private featureRegistry: FeatureRegistry private messageSender: MessageSender private eventEmitter: EventEmitter private agentContext: AgentContext @@ -90,6 +93,7 @@ export class OutOfBandApi { public constructor( dispatcher: Dispatcher, + featureRegistry: FeatureRegistry, outOfBandService: OutOfBandService, routingService: RoutingService, connectionsApi: ConnectionsApi, @@ -100,6 +104,7 @@ export class OutOfBandApi { agentContext: AgentContext ) { this.dispatcher = dispatcher + this.featureRegistry = featureRegistry this.agentContext = agentContext this.logger = logger this.outOfBandService = outOfBandService diff --git a/packages/core/src/modules/oob/OutOfBandModule.ts b/packages/core/src/modules/oob/OutOfBandModule.ts index 3b31876a48..6cf1cebdd0 100644 --- a/packages/core/src/modules/oob/OutOfBandModule.ts +++ b/packages/core/src/modules/oob/OutOfBandModule.ts @@ -1,5 +1,8 @@ +import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' +import { Protocol } from '../../agent/models' + import { OutOfBandApi } from './OutOfBandApi' import { OutOfBandService } from './OutOfBandService' import { OutOfBandRepository } from './repository' @@ -8,7 +11,7 @@ export class OutOfBandModule implements Module { /** * Registers the dependencies of the ot of band module on the dependency manager. */ - public register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { // Api dependencyManager.registerContextScoped(OutOfBandApi) @@ -17,5 +20,13 @@ export class OutOfBandModule implements Module { // Repositories dependencyManager.registerSingleton(OutOfBandRepository) + + // Features + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/out-of-band/1.1', + roles: ['sender', 'receiver'], + }) + ) } } diff --git a/packages/core/src/modules/oob/__tests__/OutOfBandModule.test.ts b/packages/core/src/modules/oob/__tests__/OutOfBandModule.test.ts index 6613250092..b1c9337335 100644 --- a/packages/core/src/modules/oob/__tests__/OutOfBandModule.test.ts +++ b/packages/core/src/modules/oob/__tests__/OutOfBandModule.test.ts @@ -1,3 +1,4 @@ +import { FeatureRegistry } from '../../../agent/FeatureRegistry' import { DependencyManager } from '../../../plugins/DependencyManager' import { OutOfBandApi } from '../OutOfBandApi' import { OutOfBandModule } from '../OutOfBandModule' @@ -9,9 +10,13 @@ const DependencyManagerMock = DependencyManager as jest.Mock const dependencyManager = new DependencyManagerMock() +jest.mock('../../../agent/FeatureRegistry') +const FeatureRegistryMock = FeatureRegistry as jest.Mock + +const featureRegistry = new FeatureRegistryMock() describe('OutOfBandModule', () => { test('registers dependencies on the dependency manager', () => { - new OutOfBandModule().register(dependencyManager) + new OutOfBandModule().register(dependencyManager, featureRegistry) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(OutOfBandApi) diff --git a/packages/core/src/modules/proofs/ProofsModule.ts b/packages/core/src/modules/proofs/ProofsModule.ts index 329c1c17e4..81fe915fd8 100644 --- a/packages/core/src/modules/proofs/ProofsModule.ts +++ b/packages/core/src/modules/proofs/ProofsModule.ts @@ -1,6 +1,9 @@ +import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' import type { ProofsModuleConfigOptions } from './ProofsModuleConfig' +import { Protocol } from '../../agent/models' + import { ProofsApi } from './ProofsApi' import { ProofsModuleConfig } from './ProofsModuleConfig' import { IndyProofFormatService } from './formats/indy/IndyProofFormatService' @@ -18,7 +21,7 @@ export class ProofsModule implements Module { /** * Registers the dependencies of the proofs module on the dependency manager. */ - public register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { // Api dependencyManager.registerContextScoped(ProofsApi) @@ -34,5 +37,13 @@ export class ProofsModule implements Module { // Proof Formats dependencyManager.registerSingleton(IndyProofFormatService) + + // Features + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/present-proof/1.0', + roles: ['verifier', 'prover'], + }) + ) } } diff --git a/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts index 6bba3fd99e..8af9a5b2c2 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts +++ b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts @@ -1,3 +1,4 @@ +import { FeatureRegistry } from '../../../agent/FeatureRegistry' import { DependencyManager } from '../../../plugins/DependencyManager' import { ProofsApi } from '../ProofsApi' import { ProofsModule } from '../ProofsModule' @@ -11,9 +12,14 @@ const DependencyManagerMock = DependencyManager as jest.Mock const dependencyManager = new DependencyManagerMock() +jest.mock('../../../agent/FeatureRegistry') +const FeatureRegistryMock = FeatureRegistry as jest.Mock + +const featureRegistry = new FeatureRegistryMock() + describe('ProofsModule', () => { test('registers dependencies on the dependency manager', () => { - new ProofsModule().register(dependencyManager) + new ProofsModule().register(dependencyManager, featureRegistry) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(ProofsApi) diff --git a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts b/packages/core/src/modules/question-answer/QuestionAnswerModule.ts index 9fcea50803..0a353a4f1d 100644 --- a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts +++ b/packages/core/src/modules/question-answer/QuestionAnswerModule.ts @@ -1,6 +1,10 @@ +import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' +import { Protocol } from '../../agent/models' + import { QuestionAnswerApi } from './QuestionAnswerApi' +import { QuestionAnswerRole } from './QuestionAnswerRole' import { QuestionAnswerRepository } from './repository' import { QuestionAnswerService } from './services' @@ -8,7 +12,7 @@ export class QuestionAnswerModule implements Module { /** * Registers the dependencies of the question answer module on the dependency manager. */ - public register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { // Api dependencyManager.registerContextScoped(QuestionAnswerApi) @@ -17,5 +21,13 @@ export class QuestionAnswerModule implements Module { // Repositories dependencyManager.registerSingleton(QuestionAnswerRepository) + + // Feature Registry + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/questionanswer/1.0', + roles: [QuestionAnswerRole.Questioner, QuestionAnswerRole.Responder], + }) + ) } } diff --git a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerModule.test.ts b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerModule.test.ts index a285e5898a..19d46a9cb0 100644 --- a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerModule.test.ts +++ b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerModule.test.ts @@ -1,3 +1,4 @@ +import { FeatureRegistry } from '../../../agent/FeatureRegistry' import { DependencyManager } from '../../../plugins/DependencyManager' import { QuestionAnswerApi } from '../QuestionAnswerApi' import { QuestionAnswerModule } from '../QuestionAnswerModule' @@ -9,9 +10,14 @@ const DependencyManagerMock = DependencyManager as jest.Mock const dependencyManager = new DependencyManagerMock() +jest.mock('../../../agent/FeatureRegistry') +const FeatureRegistryMock = FeatureRegistry as jest.Mock + +const featureRegistry = new FeatureRegistryMock() + describe('QuestionAnswerModule', () => { test('registers dependencies on the dependency manager', () => { - new QuestionAnswerModule().register(dependencyManager) + new QuestionAnswerModule().register(dependencyManager, featureRegistry) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(QuestionAnswerApi) diff --git a/packages/core/src/modules/routing/MediatorModule.ts b/packages/core/src/modules/routing/MediatorModule.ts index 97aa521934..348e23aca4 100644 --- a/packages/core/src/modules/routing/MediatorModule.ts +++ b/packages/core/src/modules/routing/MediatorModule.ts @@ -1,8 +1,12 @@ +import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' import type { MediatorModuleConfigOptions } from './MediatorModuleConfig' +import { Protocol } from '../../agent/models' + import { MediatorApi } from './MediatorApi' import { MediatorModuleConfig } from './MediatorModuleConfig' +import { MediationRole } from './models' import { MessagePickupService, V2MessagePickupService } from './protocol' import { MediationRepository, MediatorRoutingRepository } from './repository' import { MediatorService } from './services' @@ -17,7 +21,7 @@ export class MediatorModule implements Module { /** * Registers the dependencies of the question answer module on the dependency manager. */ - public register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { // Api dependencyManager.registerContextScoped(MediatorApi) @@ -32,5 +36,21 @@ export class MediatorModule implements Module { // Repositories dependencyManager.registerSingleton(MediationRepository) dependencyManager.registerSingleton(MediatorRoutingRepository) + + // Features + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/coordinate-mediation/1.0', + roles: [MediationRole.Mediator], + }), + new Protocol({ + id: 'https://didcomm.org/messagepickup/1.0', + roles: ['message_holder', 'recipient', 'batch_sender', 'batch_recipient'], + }), + new Protocol({ + id: 'https://didcomm.org/messagepickup/2.0', + roles: ['mediator', 'recipient'], + }) + ) } } diff --git a/packages/core/src/modules/routing/RecipientApi.ts b/packages/core/src/modules/routing/RecipientApi.ts index 036ff2ed1d..293208a4a5 100644 --- a/packages/core/src/modules/routing/RecipientApi.ts +++ b/packages/core/src/modules/routing/RecipientApi.ts @@ -30,7 +30,7 @@ import { KeylistUpdateResponseHandler } from './handlers/KeylistUpdateResponseHa import { MediationDenyHandler } from './handlers/MediationDenyHandler' import { MediationGrantHandler } from './handlers/MediationGrantHandler' import { MediationState } from './models/MediationState' -import { StatusRequestMessage, BatchPickupMessage } from './protocol' +import { StatusRequestMessage, BatchPickupMessage, StatusMessage } from './protocol' import { StatusHandler, MessageDeliveryHandler } from './protocol/pickup/v2/handlers' import { MediationRepository } from './repository' import { MediationRecipientService } from './services/MediationRecipientService' @@ -248,20 +248,26 @@ export class RecipientApi { // If mediator pickup strategy is not configured we try to query if batch pickup // is supported through the discover features protocol if (!mediatorPickupStrategy) { - const isPickUpV2Supported = await this.discoverFeaturesApi.isProtocolSupported( - mediator.connectionId, - StatusRequestMessage - ) - if (isPickUpV2Supported) { + const discloseForPickupV2 = await this.discoverFeaturesApi.queryFeatures({ + connectionId: mediator.connectionId, + protocolVersion: 'v1', + queries: [{ featureType: 'protocol', match: StatusMessage.type.protocolUri }], + awaitDisclosures: true, + }) + + if (discloseForPickupV2.features?.find((item) => item.id === StatusMessage.type.protocolUri)) { mediatorPickupStrategy = MediatorPickupStrategy.PickUpV2 } else { - const isBatchPickupSupported = await this.discoverFeaturesApi.isProtocolSupported( - mediator.connectionId, - BatchPickupMessage - ) - + const discloseForPickupV1 = await this.discoverFeaturesApi.queryFeatures({ + connectionId: mediator.connectionId, + protocolVersion: 'v1', + queries: [{ featureType: 'protocol', match: BatchPickupMessage.type.protocolUri }], + awaitDisclosures: true, + }) // Use explicit pickup strategy - mediatorPickupStrategy = isBatchPickupSupported + mediatorPickupStrategy = discloseForPickupV1.features?.find( + (item) => item.id === BatchPickupMessage.type.protocolUri + ) ? MediatorPickupStrategy.PickUpV1 : MediatorPickupStrategy.Implicit } diff --git a/packages/core/src/modules/routing/RecipientModule.ts b/packages/core/src/modules/routing/RecipientModule.ts index 8233b2aacf..068f1f43af 100644 --- a/packages/core/src/modules/routing/RecipientModule.ts +++ b/packages/core/src/modules/routing/RecipientModule.ts @@ -1,8 +1,12 @@ +import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' import type { RecipientModuleConfigOptions } from './RecipientModuleConfig' +import { Protocol } from '../../agent/models' + import { RecipientApi } from './RecipientApi' import { RecipientModuleConfig } from './RecipientModuleConfig' +import { MediationRole } from './models' import { MediationRepository } from './repository' import { MediationRecipientService, RoutingService } from './services' @@ -16,7 +20,7 @@ export class RecipientModule implements Module { /** * Registers the dependencies of the mediator recipient module on the dependency manager. */ - public register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { // Api dependencyManager.registerContextScoped(RecipientApi) @@ -29,5 +33,13 @@ export class RecipientModule implements Module { // Repositories dependencyManager.registerSingleton(MediationRepository) + + // Features + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/coordinate-mediation/1.0', + roles: [MediationRole.Recipient], + }) + ) } } diff --git a/packages/core/src/modules/routing/__tests__/MediatorModule.test.ts b/packages/core/src/modules/routing/__tests__/MediatorModule.test.ts index 096e83cfad..5835103180 100644 --- a/packages/core/src/modules/routing/__tests__/MediatorModule.test.ts +++ b/packages/core/src/modules/routing/__tests__/MediatorModule.test.ts @@ -1,3 +1,4 @@ +import { FeatureRegistry } from '../../../agent/FeatureRegistry' import { DependencyManager } from '../../../plugins/DependencyManager' import { MediatorApi } from '../MediatorApi' import { MediatorModule } from '../MediatorModule' @@ -10,9 +11,13 @@ const DependencyManagerMock = DependencyManager as jest.Mock const dependencyManager = new DependencyManagerMock() +jest.mock('../../../agent/FeatureRegistry') +const FeatureRegistryMock = FeatureRegistry as jest.Mock + +const featureRegistry = new FeatureRegistryMock() describe('MediatorModule', () => { test('registers dependencies on the dependency manager', () => { - new MediatorModule().register(dependencyManager) + new MediatorModule().register(dependencyManager, featureRegistry) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(MediatorApi) diff --git a/packages/core/src/modules/routing/__tests__/RecipientModule.test.ts b/packages/core/src/modules/routing/__tests__/RecipientModule.test.ts index 916840344d..0008d36f8d 100644 --- a/packages/core/src/modules/routing/__tests__/RecipientModule.test.ts +++ b/packages/core/src/modules/routing/__tests__/RecipientModule.test.ts @@ -1,3 +1,4 @@ +import { FeatureRegistry } from '../../../agent/FeatureRegistry' import { DependencyManager } from '../../../plugins/DependencyManager' import { RecipientApi } from '../RecipientApi' import { RecipientModule } from '../RecipientModule' @@ -9,9 +10,14 @@ const DependencyManagerMock = DependencyManager as jest.Mock const dependencyManager = new DependencyManagerMock() +jest.mock('../../../agent/FeatureRegistry') +const FeatureRegistryMock = FeatureRegistry as jest.Mock + +const featureRegistry = new FeatureRegistryMock() + describe('RecipientModule', () => { test('registers dependencies on the dependency manager', () => { - new RecipientModule().register(dependencyManager) + new RecipientModule().register(dependencyManager, featureRegistry) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(RecipientApi) diff --git a/packages/core/src/plugins/DependencyManager.ts b/packages/core/src/plugins/DependencyManager.ts index a785ccf1e1..fe662670fc 100644 --- a/packages/core/src/plugins/DependencyManager.ts +++ b/packages/core/src/plugins/DependencyManager.ts @@ -4,6 +4,8 @@ import type { DependencyContainer } from 'tsyringe' import { container as rootContainer, InjectionToken, Lifecycle } from 'tsyringe' +import { FeatureRegistry } from '../agent/FeatureRegistry' + export { InjectionToken } export class DependencyManager { @@ -14,7 +16,8 @@ export class DependencyManager { } public registerModules(...modules: Module[]) { - modules.forEach((module) => module.register(this)) + const featureRegistry = this.resolve(FeatureRegistry) + modules.forEach((module) => module.register(this, featureRegistry)) } public registerSingleton(from: InjectionToken, to: InjectionToken): void diff --git a/packages/core/src/plugins/Module.ts b/packages/core/src/plugins/Module.ts index 5210e2d9c4..68fa680855 100644 --- a/packages/core/src/plugins/Module.ts +++ b/packages/core/src/plugins/Module.ts @@ -1,8 +1,9 @@ +import type { FeatureRegistry } from '../agent/FeatureRegistry' import type { Constructor } from '../utils/mixins' import type { DependencyManager } from './DependencyManager' export interface Module { - register(dependencyManager: DependencyManager): void + register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void } /** diff --git a/packages/core/src/plugins/__tests__/DependencyManager.test.ts b/packages/core/src/plugins/__tests__/DependencyManager.test.ts index 0991324abe..f576ad4811 100644 --- a/packages/core/src/plugins/__tests__/DependencyManager.test.ts +++ b/packages/core/src/plugins/__tests__/DependencyManager.test.ts @@ -2,6 +2,7 @@ import type { Module } from '../Module' import { container as rootContainer, injectable, Lifecycle } from 'tsyringe' +import { FeatureRegistry } from '../../agent/FeatureRegistry' import { DependencyManager } from '../DependencyManager' class Instance { @@ -11,6 +12,7 @@ const instance = new Instance() const container = rootContainer.createChildContainer() const dependencyManager = new DependencyManager(container) +const featureRegistry = container.resolve(FeatureRegistry) describe('DependencyManager', () => { afterEach(() => { @@ -35,10 +37,10 @@ describe('DependencyManager', () => { dependencyManager.registerModules(module1, module2) expect(module1.register).toHaveBeenCalledTimes(1) - expect(module1.register).toHaveBeenLastCalledWith(dependencyManager) + expect(module1.register).toHaveBeenLastCalledWith(dependencyManager, featureRegistry) expect(module2.register).toHaveBeenCalledTimes(1) - expect(module2.register).toHaveBeenLastCalledWith(dependencyManager) + expect(module2.register).toHaveBeenLastCalledWith(dependencyManager, featureRegistry) }) }) diff --git a/samples/extension-module/dummy/DummyModule.ts b/samples/extension-module/dummy/DummyModule.ts index 9f0f50f99a..44374596ba 100644 --- a/samples/extension-module/dummy/DummyModule.ts +++ b/samples/extension-module/dummy/DummyModule.ts @@ -1,14 +1,24 @@ -import type { DependencyManager, Module } from '@aries-framework/core' +import type { DependencyManager, FeatureRegistry, Module } from '@aries-framework/core' + +import { Protocol } from '@aries-framework/core' import { DummyRepository } from './repository' import { DummyService } from './services' export class DummyModule implements Module { - public register(dependencyManager: DependencyManager) { + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { // Api dependencyManager.registerContextScoped(DummyModule) dependencyManager.registerSingleton(DummyRepository) dependencyManager.registerSingleton(DummyService) + + // Features + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/dummy/1.0', + roles: ['requester', 'responder'], + }) + ) } } From 82a17a3a1eff61008b2e91695f6527501fe44237 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Sat, 10 Sep 2022 19:59:54 +0200 Subject: [PATCH 036/125] feat!: agent module registration api (#955) Signed-off-by: Timo Glastra BREAKING CHANGE: custom modules have been moved to the .modules namespace. In addition the agent constructor has been updated to a single options object that contains the `config` and `dependencies` properties. Instead of constructing the agent like this: ```ts const agent = new Agent({ /* config */ }, agentDependencies) ``` You should now construct it like this: ```ts const agent = new Agent({ config: { /* config */ }, dependencies: agentDependencies }) ``` This allows for the new custom modules to be defined in the agent constructor. --- demo/src/BaseAgent.ts | 2 +- packages/core/src/agent/Agent.ts | 173 ++++++-------- packages/core/src/agent/AgentModules.ts | 212 ++++++++++++++++++ packages/core/src/agent/BaseAgent.ts | 61 +++-- .../core/src/agent/__tests__/Agent.test.ts | 99 +++++--- .../src/agent/__tests__/AgentConfig.test.ts | 15 ++ .../src/agent/__tests__/AgentModules.test.ts | 137 +++++++++++ packages/core/src/index.ts | 2 + .../basic-messages/BasicMessagesModule.ts | 2 + .../modules/connections/ConnectionsModule.ts | 1 + .../modules/credentials/CredentialsModule.ts | 1 + .../v1-connectionless-credentials.e2e.test.ts | 10 +- .../v2-connectionless-credentials.e2e.test.ts | 10 +- .../v2/__tests__/v2-credentials.e2e.test.ts | 2 +- packages/core/src/modules/dids/DidsModule.ts | 2 + .../dids/__tests__/dids-registrar.e2e.test.ts | 6 +- .../dids/__tests__/dids-resolver.e2e.test.ts | 6 +- .../DiscoverFeaturesModule.ts | 1 + .../v1-discover-features.e2e.test.ts | 10 +- .../v2-discover-features.e2e.test.ts | 10 +- .../generic-records/GenericRecordsModule.ts | 2 + .../core/src/modules/ledger/LedgerModule.ts | 1 + packages/core/src/modules/oob/OutOfBandApi.ts | 1 - .../core/src/modules/oob/OutOfBandModule.ts | 2 + .../core/src/modules/proofs/ProofsModule.ts | 1 + .../question-answer/QuestionAnswerModule.ts | 2 + .../src/modules/routing/MediatorModule.ts | 1 + .../src/modules/routing/RecipientModule.ts | 1 + .../routing/__tests__/mediation.test.ts | 46 ++-- .../modules/routing/__tests__/pickup.test.ts | 14 +- .../core/src/plugins/DependencyManager.ts | 26 ++- packages/core/src/plugins/Module.ts | 1 + .../__tests__/DependencyManager.test.ts | 39 +++- .../storage/migration/__tests__/0.1.test.ts | 28 ++- .../__tests__/UpdateAssistant.test.ts | 6 +- .../migration/__tests__/backup.test.ts | 6 +- packages/core/src/wallet/WalletModule.ts | 2 + packages/core/tests/agents.test.ts | 10 +- packages/core/tests/connections.test.ts | 22 +- packages/core/tests/generic-records.test.ts | 6 +- packages/core/tests/helpers.ts | 33 +-- packages/core/tests/ledger.test.ts | 10 +- packages/core/tests/migration.test.ts | 10 +- .../core/tests/multi-protocol-version.test.ts | 10 +- .../tests/oob-mediation-provision.test.ts | 14 +- packages/core/tests/oob-mediation.test.ts | 14 +- packages/core/tests/oob.test.ts | 10 +- packages/core/tests/postgres.e2e.test.ts | 10 +- .../tests/v1-connectionless-proofs.test.ts | 14 +- .../tests/v2-connectionless-proofs.test.ts | 14 +- packages/core/tests/wallet.test.ts | 12 +- packages/module-tenants/src/TenantAgent.ts | 8 +- packages/module-tenants/src/TenantsApi.ts | 9 +- .../module-tenants/src/TenantsApiOptions.ts | 5 +- packages/module-tenants/src/TenantsModule.ts | 7 +- .../src/__tests__/TenantAgent.test.ts | 8 +- .../src/__tests__/TenantsApi.test.ts | 14 +- .../tests/tenant-sessions.e2e.test.ts | 28 +-- .../module-tenants/tests/tenants.e2e.test.ts | 75 ++++--- samples/extension-module/dummy/DummyModule.ts | 5 +- samples/extension-module/requester.ts | 21 +- samples/extension-module/responder.ts | 21 +- samples/mediator.ts | 2 +- tests/e2e-http.test.ts | 14 +- tests/e2e-subject.test.ts | 14 +- tests/e2e-ws-pickup-v2.test.ts | 14 +- tests/e2e-ws.test.ts | 14 +- yarn.lock | 2 +- 68 files changed, 918 insertions(+), 473 deletions(-) create mode 100644 packages/core/src/agent/AgentModules.ts create mode 100644 packages/core/src/agent/__tests__/AgentModules.test.ts diff --git a/demo/src/BaseAgent.ts b/demo/src/BaseAgent.ts index 3332af9ec4..efc4260103 100644 --- a/demo/src/BaseAgent.ts +++ b/demo/src/BaseAgent.ts @@ -42,7 +42,7 @@ export class BaseAgent { this.config = config - this.agent = new Agent(config, agentDependencies) + this.agent = new Agent({ config, dependencies: agentDependencies }) this.agent.registerInboundTransport(new HttpInboundTransport({ port })) this.agent.registerOutboundTransport(new HttpOutboundTransport()) } diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 50e00f6610..c1bdc5e009 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -2,6 +2,7 @@ import type { InboundTransport } from '../transport/InboundTransport' import type { OutboundTransport } from '../transport/OutboundTransport' import type { InitConfig } from '../types' import type { AgentDependencies } from './AgentDependencies' +import type { AgentModulesInput, ModulesMap } from './AgentModules' import type { AgentMessageReceivedEvent } from './Events' import type { Subscription } from 'rxjs' @@ -12,27 +13,14 @@ import { CacheRepository } from '../cache' import { InjectionSymbols } from '../constants' import { JwsService } from '../crypto/JwsService' import { AriesFrameworkError } from '../error' -import { BasicMessagesModule } from '../modules/basic-messages' -import { ConnectionsModule } from '../modules/connections' -import { CredentialsModule } from '../modules/credentials' -import { DidsModule } from '../modules/dids' -import { DiscoverFeaturesModule } from '../modules/discover-features' -import { GenericRecordsModule } from '../modules/generic-records' -import { IndyModule } from '../modules/indy' -import { LedgerModule } from '../modules/ledger' -import { OutOfBandModule } from '../modules/oob' -import { ProofsModule } from '../modules/proofs' -import { QuestionAnswerModule } from '../modules/question-answer' -import { MediatorModule, RecipientModule } from '../modules/routing' -import { W3cVcModule } from '../modules/vc' import { DependencyManager } from '../plugins' import { DidCommMessageRepository, StorageUpdateService, StorageVersionRepository } from '../storage' import { InMemoryMessageRepository } from '../storage/InMemoryMessageRepository' import { IndyStorageService } from '../storage/IndyStorageService' -import { WalletModule } from '../wallet' import { IndyWallet } from '../wallet/IndyWallet' import { AgentConfig } from './AgentConfig' +import { extendModulesWithDefaultModules } from './AgentModules' import { BaseAgent } from './BaseAgent' import { Dispatcher } from './Dispatcher' import { EnvelopeService } from './EnvelopeService' @@ -44,17 +32,71 @@ import { MessageSender } from './MessageSender' import { TransportService } from './TransportService' import { AgentContext, DefaultAgentContextProvider } from './context' -export class Agent extends BaseAgent { +interface AgentOptions { + config: InitConfig + modules?: AgentModules + dependencies: AgentDependencies +} + +export class Agent extends BaseAgent { public messageSubscription: Subscription - public constructor( - initialConfig: InitConfig, - dependencies: AgentDependencies, - dependencyManager?: DependencyManager - ) { - // NOTE: we can't create variables before calling super as TS will complain that the super call must be the - // the first statement in the constructor. - super(new AgentConfig(initialConfig, dependencies), dependencyManager ?? new DependencyManager()) + public constructor(options: AgentOptions, dependencyManager = new DependencyManager()) { + const agentConfig = new AgentConfig(options.config, options.dependencies) + const modulesWithDefaultModules = extendModulesWithDefaultModules(agentConfig, options.modules) + + // Register internal dependencies + dependencyManager.registerSingleton(EventEmitter) + dependencyManager.registerSingleton(MessageSender) + dependencyManager.registerSingleton(MessageReceiver) + dependencyManager.registerSingleton(TransportService) + dependencyManager.registerSingleton(Dispatcher) + dependencyManager.registerSingleton(EnvelopeService) + dependencyManager.registerSingleton(FeatureRegistry) + dependencyManager.registerSingleton(JwsService) + dependencyManager.registerSingleton(CacheRepository) + dependencyManager.registerSingleton(DidCommMessageRepository) + dependencyManager.registerSingleton(StorageVersionRepository) + dependencyManager.registerSingleton(StorageUpdateService) + + dependencyManager.registerInstance(AgentConfig, agentConfig) + dependencyManager.registerInstance(InjectionSymbols.AgentDependencies, agentConfig.agentDependencies) + dependencyManager.registerInstance(InjectionSymbols.Stop$, new Subject()) + dependencyManager.registerInstance(InjectionSymbols.FileSystem, new agentConfig.agentDependencies.FileSystem()) + + // Register possibly already defined services + if (!dependencyManager.isRegistered(InjectionSymbols.Wallet)) { + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndyWallet) + } + if (!dependencyManager.isRegistered(InjectionSymbols.Logger)) { + dependencyManager.registerInstance(InjectionSymbols.Logger, agentConfig.logger) + } + if (!dependencyManager.isRegistered(InjectionSymbols.StorageService)) { + dependencyManager.registerSingleton(InjectionSymbols.StorageService, IndyStorageService) + } + if (!dependencyManager.isRegistered(InjectionSymbols.MessageRepository)) { + dependencyManager.registerSingleton(InjectionSymbols.MessageRepository, InMemoryMessageRepository) + } + + // Register all modules. This will also include the default modules + dependencyManager.registerModules(modulesWithDefaultModules) + + // TODO: contextCorrelationId for base wallet + // Bind the default agent context to the container for use in modules etc. + dependencyManager.registerInstance( + AgentContext, + new AgentContext({ + dependencyManager, + contextCorrelationId: 'default', + }) + ) + + // If no agent context provider has been registered we use the default agent context provider. + if (!dependencyManager.isRegistered(InjectionSymbols.AgentContextProvider)) { + dependencyManager.registerSingleton(InjectionSymbols.AgentContextProvider, DefaultAgentContextProvider) + } + + super(agentConfig, dependencyManager) const stop$ = this.dependencyManager.resolve>(InjectionSymbols.Stop$) @@ -156,91 +198,6 @@ export class Agent extends BaseAgent { this._isInitialized = false } - protected registerDependencies(dependencyManager: DependencyManager) { - // Register internal dependencies - dependencyManager.registerSingleton(EventEmitter) - dependencyManager.registerSingleton(MessageSender) - dependencyManager.registerSingleton(MessageReceiver) - dependencyManager.registerSingleton(TransportService) - dependencyManager.registerSingleton(Dispatcher) - dependencyManager.registerSingleton(EnvelopeService) - dependencyManager.registerSingleton(FeatureRegistry) - dependencyManager.registerSingleton(JwsService) - dependencyManager.registerSingleton(CacheRepository) - dependencyManager.registerSingleton(DidCommMessageRepository) - dependencyManager.registerSingleton(StorageVersionRepository) - dependencyManager.registerSingleton(StorageUpdateService) - - dependencyManager.registerInstance(AgentConfig, this.agentConfig) - dependencyManager.registerInstance(InjectionSymbols.AgentDependencies, this.agentConfig.agentDependencies) - dependencyManager.registerInstance(InjectionSymbols.Stop$, new Subject()) - dependencyManager.registerInstance(InjectionSymbols.FileSystem, new this.agentConfig.agentDependencies.FileSystem()) - - // Register possibly already defined services - if (!dependencyManager.isRegistered(InjectionSymbols.Wallet)) { - dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndyWallet) - } - if (!dependencyManager.isRegistered(InjectionSymbols.Logger)) { - dependencyManager.registerInstance(InjectionSymbols.Logger, this.logger) - } - if (!dependencyManager.isRegistered(InjectionSymbols.StorageService)) { - dependencyManager.registerSingleton(InjectionSymbols.StorageService, IndyStorageService) - } - if (!dependencyManager.isRegistered(InjectionSymbols.MessageRepository)) { - dependencyManager.registerSingleton(InjectionSymbols.MessageRepository, InMemoryMessageRepository) - } - - // Register all modules - dependencyManager.registerModules( - new ConnectionsModule({ - autoAcceptConnections: this.agentConfig.autoAcceptConnections, - }), - new CredentialsModule({ - autoAcceptCredentials: this.agentConfig.autoAcceptCredentials, - }), - new ProofsModule({ - autoAcceptProofs: this.agentConfig.autoAcceptProofs, - }), - new MediatorModule({ - autoAcceptMediationRequests: this.agentConfig.autoAcceptMediationRequests, - }), - new RecipientModule({ - maximumMessagePickup: this.agentConfig.maximumMessagePickup, - mediatorInvitationUrl: this.agentConfig.mediatorConnectionsInvite, - mediatorPickupStrategy: this.agentConfig.mediatorPickupStrategy, - mediatorPollingInterval: this.agentConfig.mediatorPollingInterval, - }), - new BasicMessagesModule(), - new QuestionAnswerModule(), - new GenericRecordsModule(), - new LedgerModule({ - connectToIndyLedgersOnStartup: this.agentConfig.connectToIndyLedgersOnStartup, - indyLedgers: this.agentConfig.indyLedgers, - }), - new DiscoverFeaturesModule(), - new DidsModule(), - new WalletModule(), - new OutOfBandModule(), - new IndyModule(), - new W3cVcModule() - ) - - // TODO: contextCorrelationId for base wallet - // Bind the default agent context to the container for use in modules etc. - dependencyManager.registerInstance( - AgentContext, - new AgentContext({ - dependencyManager, - contextCorrelationId: 'default', - }) - ) - - // If no agent context provider has been registered we use the default agent context provider. - if (!this.dependencyManager.isRegistered(InjectionSymbols.AgentContextProvider)) { - this.dependencyManager.registerSingleton(InjectionSymbols.AgentContextProvider, DefaultAgentContextProvider) - } - } - protected async getMediationConnection(mediatorInvitationUrl: string) { const outOfBandInvitation = this.oob.parseInvitation(mediatorInvitationUrl) const outOfBandRecord = await this.oob.findByInvitationId(outOfBandInvitation.id) diff --git a/packages/core/src/agent/AgentModules.ts b/packages/core/src/agent/AgentModules.ts new file mode 100644 index 0000000000..958a90f47e --- /dev/null +++ b/packages/core/src/agent/AgentModules.ts @@ -0,0 +1,212 @@ +import type { Module, DependencyManager } from '../plugins' +import type { Constructor } from '../utils/mixins' +import type { AgentConfig } from './AgentConfig' + +import { BasicMessagesModule } from '../modules/basic-messages' +import { ConnectionsModule } from '../modules/connections' +import { CredentialsModule } from '../modules/credentials' +import { DidsModule } from '../modules/dids' +import { DiscoverFeaturesModule } from '../modules/discover-features' +import { GenericRecordsModule } from '../modules/generic-records' +import { IndyModule } from '../modules/indy' +import { LedgerModule } from '../modules/ledger' +import { OutOfBandModule } from '../modules/oob' +import { ProofsModule } from '../modules/proofs' +import { QuestionAnswerModule } from '../modules/question-answer' +import { MediatorModule, RecipientModule } from '../modules/routing' +import { W3cVcModule } from '../modules/vc' +import { WalletModule } from '../wallet' + +/** + * Simple utility type that represent a map of modules. This is used to map from moduleKey (api key) to the api in the framework. + */ +export type ModulesMap = { [key: string]: Module } + +// eslint-disable-next-line @typescript-eslint/ban-types +export type EmptyModuleMap = {} + +/** + * Default modules can be optionally defined to provide custom configuration. This type makes it so that it is not + * possible to use a different key for the default modules + */ +export type AgentModulesInput = Partial & ModulesMap + +/** + * Type that represents the default agent modules. This is the {@link ModulesMap} variant for the default modules in the framework. + * It uses the return type of the {@link getDefaultAgentModules} method to automatically infer which modules are always available on + * the agent and in the agent. namespace. + */ +export type DefaultAgentModules = { + [moduleKey in keyof ReturnType]: ReturnType< + ReturnType[moduleKey] + > +} + +export type WithoutDefaultModules = { + [moduleKey in Exclude]: Modules[moduleKey] +} + +/** + * Type that represents the api object of the agent (`agent.xxx`). It will extract all keys of the modules and map this to the + * registered {@link Module.api} class instance. If the module does not have an api class registered, the property will be removed + * and won't be available on the api object. + * + * @example + * If the following AgentModules type was passed: + * ```ts + * { + * connections: ConnectionsModule + * indy: IndyModule + * } + * ``` + * + * And we use the `AgentApi` type like this: + * ```ts + * type MyAgentApi = AgentApi<{ + * connections: ConnectionsModule + * indy: IndyModule + * }> + * ``` + * + * the resulting agent api will look like: + * + * ```ts + * { + * connections: ConnectionsApi + * } + * ``` + * + * The `indy` module has been ignored because it doesn't define an api class. + */ +export type AgentApi = { + [moduleKey in keyof Modules as Modules[moduleKey]['api'] extends Constructor + ? moduleKey + : never]: Modules[moduleKey]['api'] extends Constructor ? InstanceType : never +} + +/** + * Method to get the default agent modules to be registered on any agent instance. + * + * @note This implementation is quite ugly and is meant to be temporary. It extracts the module specific config from the agent config + * and will only construct the module if the method is called. This prevents the modules from being initialized if they are already configured by the end + * user using the `module` property in the agent constructor. + */ +function getDefaultAgentModules(agentConfig: AgentConfig) { + return { + connections: () => + new ConnectionsModule({ + autoAcceptConnections: agentConfig.autoAcceptConnections, + }), + credentials: () => + new CredentialsModule({ + autoAcceptCredentials: agentConfig.autoAcceptCredentials, + }), + proofs: () => + new ProofsModule({ + autoAcceptProofs: agentConfig.autoAcceptProofs, + }), + mediator: () => + new MediatorModule({ + autoAcceptMediationRequests: agentConfig.autoAcceptMediationRequests, + }), + mediationRecipient: () => + new RecipientModule({ + maximumMessagePickup: agentConfig.maximumMessagePickup, + mediatorInvitationUrl: agentConfig.mediatorConnectionsInvite, + mediatorPickupStrategy: agentConfig.mediatorPickupStrategy, + mediatorPollingInterval: agentConfig.mediatorPollingInterval, + }), + basicMessages: () => new BasicMessagesModule(), + questionAnswer: () => new QuestionAnswerModule(), + genericRecords: () => new GenericRecordsModule(), + ledger: () => + new LedgerModule({ + connectToIndyLedgersOnStartup: agentConfig.connectToIndyLedgersOnStartup, + indyLedgers: agentConfig.indyLedgers, + }), + discovery: () => new DiscoverFeaturesModule(), + dids: () => new DidsModule(), + wallet: () => new WalletModule(), + oob: () => new OutOfBandModule(), + indy: () => new IndyModule(), + w3cVc: () => new W3cVcModule(), + } as const +} + +/** + * Extend the provided modules object with the default agent modules. If the modules property already contains a module with the same + * name as a default module, the module won't be added to the extended module object. This allows users of the framework to override + * the modules with custom configuration. The agent constructor type ensures you can't provide a different module for a key that registered + * on the default agent. + */ +export function extendModulesWithDefaultModules( + agentConfig: AgentConfig, + modules?: AgentModules +): AgentModules & DefaultAgentModules { + const extendedModules: Record = { ...modules } + const defaultAgentModules = getDefaultAgentModules(agentConfig) + + // Register all default modules, if not registered yet + for (const [moduleKey, getConfiguredModule] of Object.entries(defaultAgentModules)) { + // Do not register if the module is already registered. + if (modules && modules[moduleKey]) continue + + extendedModules[moduleKey] = getConfiguredModule() + } + + return extendedModules as AgentModules & DefaultAgentModules +} + +/** + * Get the agent api object based on the modules registered in the dependency manager. For each registered module on the + * dependency manager, the method will extract the api class from the module, resolve it and assign it to the module key + * as provided in the agent constructor (or the {@link getDefaultAgentModules} method). + * + * Modules that don't have an api class defined ({@link Module.api} is undefined) will be ignored and won't be added to the + * api object. + * + * If the api of a module is passed in the `excluded` array, the api will not be added to the resulting api object. + * + * @example + * If the dependency manager has the following modules configured: + * ```ts + * { + * connections: ConnectionsModule + * indy: IndyModule + * } + * ``` + * + * And we call the `getAgentApi` method like this: + * ```ts + * const api = getAgentApi(dependencyManager) + * ``` + * + * the resulting agent api will look like: + * + * ```ts + * { + * connections: ConnectionsApi + * } + * ``` + * + * The `indy` module has been ignored because it doesn't define an api class. + */ +export function getAgentApi( + dependencyManager: DependencyManager, + excludedApis: unknown[] = [] +): AgentApi { + // Create the api object based on the `api` properties on the modules. If no `api` exists + // on the module it will be ignored. + const api = Object.entries(dependencyManager.registeredModules).reduce((api, [moduleKey, module]) => { + // Module has no api + if (!module.api) return api + + const apiInstance = dependencyManager.resolve(module.api) + + // Api is excluded + if (excludedApis.includes(apiInstance)) return api + return { ...api, [moduleKey]: apiInstance } + }, {}) as AgentApi + + return api +} diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index ed407c881d..1f33036f2c 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -1,27 +1,28 @@ import type { Logger } from '../logger' import type { DependencyManager } from '../plugins' import type { AgentConfig } from './AgentConfig' +import type { AgentApi, EmptyModuleMap, ModulesMap, WithoutDefaultModules } from './AgentModules' import type { TransportSession } from './TransportService' import { AriesFrameworkError } from '../error' -import { BasicMessagesApi } from '../modules/basic-messages/BasicMessagesApi' -import { ConnectionsApi } from '../modules/connections/ConnectionsApi' -import { CredentialsApi } from '../modules/credentials/CredentialsApi' -import { DidsApi } from '../modules/dids/DidsApi' +import { BasicMessagesApi } from '../modules/basic-messages' +import { ConnectionsApi } from '../modules/connections' +import { CredentialsApi } from '../modules/credentials' +import { DidsApi } from '../modules/dids' import { DiscoverFeaturesApi } from '../modules/discover-features' -import { GenericRecordsApi } from '../modules/generic-records/GenericRecordsApi' -import { LedgerApi } from '../modules/ledger/LedgerApi' -import { OutOfBandApi } from '../modules/oob/OutOfBandApi' +import { GenericRecordsApi } from '../modules/generic-records' +import { LedgerApi } from '../modules/ledger' +import { OutOfBandApi } from '../modules/oob' import { ProofsApi } from '../modules/proofs/ProofsApi' -import { QuestionAnswerApi } from '../modules/question-answer/QuestionAnswerApi' -import { MediatorApi } from '../modules/routing/MediatorApi' -import { RecipientApi } from '../modules/routing/RecipientApi' +import { QuestionAnswerApi } from '../modules/question-answer' +import { MediatorApi, RecipientApi } from '../modules/routing' import { StorageUpdateService } from '../storage' import { UpdateAssistant } from '../storage/migration/UpdateAssistant' import { DEFAULT_UPDATE_CONFIG } from '../storage/migration/updates' -import { WalletApi } from '../wallet/WalletApi' +import { WalletApi } from '../wallet' import { WalletError } from '../wallet/error' +import { getAgentApi } from './AgentModules' import { EventEmitter } from './EventEmitter' import { FeatureRegistry } from './FeatureRegistry' import { MessageReceiver } from './MessageReceiver' @@ -29,7 +30,7 @@ import { MessageSender } from './MessageSender' import { TransportService } from './TransportService' import { AgentContext } from './context' -export abstract class BaseAgent { +export abstract class BaseAgent { protected agentConfig: AgentConfig protected logger: Logger public readonly dependencyManager: DependencyManager @@ -42,19 +43,21 @@ export abstract class BaseAgent { protected agentContext: AgentContext public readonly connections: ConnectionsApi + public readonly credentials: CredentialsApi public readonly proofs: ProofsApi + public readonly mediator: MediatorApi + public readonly mediationRecipient: RecipientApi public readonly basicMessages: BasicMessagesApi + public readonly questionAnswer: QuestionAnswerApi public readonly genericRecords: GenericRecordsApi public readonly ledger: LedgerApi - public readonly questionAnswer!: QuestionAnswerApi - public readonly credentials: CredentialsApi - public readonly mediationRecipient: RecipientApi - public readonly mediator: MediatorApi public readonly discovery: DiscoverFeaturesApi public readonly dids: DidsApi public readonly wallet: WalletApi public readonly oob: OutOfBandApi + public readonly modules: AgentApi> + public constructor(agentConfig: AgentConfig, dependencyManager: DependencyManager) { this.dependencyManager = dependencyManager @@ -73,8 +76,6 @@ export abstract class BaseAgent { ) } - this.registerDependencies(this.dependencyManager) - // Resolve instances after everything is registered this.eventEmitter = this.dependencyManager.resolve(EventEmitter) this.featureRegistry = this.dependencyManager.resolve(FeatureRegistry) @@ -83,10 +84,9 @@ export abstract class BaseAgent { this.transportService = this.dependencyManager.resolve(TransportService) this.agentContext = this.dependencyManager.resolve(AgentContext) - // We set the modules in the constructor because that allows to set them as read-only this.connections = this.dependencyManager.resolve(ConnectionsApi) this.credentials = this.dependencyManager.resolve(CredentialsApi) as CredentialsApi - this.proofs = this.dependencyManager.resolve(ProofsApi) as ProofsApi + this.proofs = this.dependencyManager.resolve(ProofsApi) this.mediator = this.dependencyManager.resolve(MediatorApi) this.mediationRecipient = this.dependencyManager.resolve(RecipientApi) this.basicMessages = this.dependencyManager.resolve(BasicMessagesApi) @@ -97,6 +97,25 @@ export abstract class BaseAgent { this.dids = this.dependencyManager.resolve(DidsApi) this.wallet = this.dependencyManager.resolve(WalletApi) this.oob = this.dependencyManager.resolve(OutOfBandApi) + + const defaultApis = [ + this.connections, + this.credentials, + this.proofs, + this.mediator, + this.mediationRecipient, + this.basicMessages, + this.questionAnswer, + this.genericRecords, + this.ledger, + this.discovery, + this.dids, + this.wallet, + this.oob, + ] + + // Set the api of the registered modules on the agent, excluding the default apis + this.modules = getAgentApi(this.dependencyManager, defaultApis) } public get isInitialized() { @@ -180,6 +199,4 @@ export abstract class BaseAgent { public get context() { return this.agentContext } - - protected abstract registerDependencies(dependencyManager: DependencyManager): void } diff --git a/packages/core/src/agent/__tests__/Agent.test.ts b/packages/core/src/agent/__tests__/Agent.test.ts index 8e33e303ff..0a091e51d9 100644 --- a/packages/core/src/agent/__tests__/Agent.test.ts +++ b/packages/core/src/agent/__tests__/Agent.test.ts @@ -1,4 +1,8 @@ -import { getBaseConfig } from '../../../tests/helpers' +import type { DependencyManager, Module } from '../../plugins' + +import { injectable } from 'tsyringe' + +import { getAgentOptions } from '../../../tests/helpers' import { InjectionSymbols } from '../../constants' import { BasicMessageRepository, BasicMessageService } from '../../modules/basic-messages' import { BasicMessagesApi } from '../../modules/basic-messages/BasicMessagesApi' @@ -15,11 +19,12 @@ import { ProofsApi } from '../../modules/proofs/ProofsApi' import { V1ProofService } from '../../modules/proofs/protocol/v1' import { V2ProofService } from '../../modules/proofs/protocol/v2' import { - MediatorApi, - RecipientApi, + MediationRecipientService, MediationRepository, + MediatorApi, MediatorService, - MediationRecipientService, + RecipientApi, + RecipientModule, } from '../../modules/routing' import { InMemoryMessageRepository } from '../../storage/InMemoryMessageRepository' import { IndyStorageService } from '../../storage/IndyStorageService' @@ -31,9 +36,62 @@ import { FeatureRegistry } from '../FeatureRegistry' import { MessageReceiver } from '../MessageReceiver' import { MessageSender } from '../MessageSender' -const { config, agentDependencies: dependencies } = getBaseConfig('Agent Class Test') +const agentOptions = getAgentOptions('Agent Class Test') + +const myModuleMethod = jest.fn() +@injectable() +class MyApi { + public myModuleMethod = myModuleMethod +} + +class MyModule implements Module { + public api = MyApi + public register(dependencyManager: DependencyManager) { + dependencyManager.registerContextScoped(MyApi) + } +} describe('Agent', () => { + describe('Module registration', () => { + test('does not return default modules on modules key if no modules were provided', () => { + const agent = new Agent(agentOptions) + + expect(agent.modules).toEqual({}) + }) + + test('registers custom and default modules if custom modules are provided', () => { + const agent = new Agent({ + ...agentOptions, + modules: { + myModule: new MyModule(), + }, + }) + + expect(agent.modules.myModule.myModuleMethod).toBe(myModuleMethod) + expect(agent.modules).toEqual({ + myModule: expect.any(MyApi), + }) + }) + + test('override default module configuration', () => { + const agent = new Agent({ + ...agentOptions, + modules: { + myModule: new MyModule(), + mediationRecipient: new RecipientModule({ + maximumMessagePickup: 42, + }), + }, + }) + + // Should be custom module config property, not the default value + expect(agent.mediationRecipient.config.maximumMessagePickup).toBe(42) + expect(agent.modules).toEqual({ + myModule: expect.any(MyApi), + }) + }) + }) + describe('Initialization', () => { let agent: Agent @@ -48,7 +106,7 @@ describe('Agent', () => { it('isInitialized should only return true after initialization', async () => { expect.assertions(2) - agent = new Agent(config, dependencies) + agent = new Agent(agentOptions) expect(agent.isInitialized).toBe(false) await agent.initialize() @@ -58,7 +116,7 @@ describe('Agent', () => { it('wallet isInitialized should return true after agent initialization if wallet config is set in agent constructor', async () => { expect.assertions(4) - agent = new Agent(config, dependencies) + agent = new Agent(agentOptions) const wallet = agent.context.wallet expect(agent.isInitialized).toBe(false) @@ -71,8 +129,8 @@ describe('Agent', () => { it('wallet must be initialized if wallet config is not set before agent can be initialized', async () => { expect.assertions(9) - const { walletConfig, ...withoutWalletConfig } = config - agent = new Agent(withoutWalletConfig, dependencies) + const { walletConfig, ...withoutWalletConfig } = agentOptions.config + agent = new Agent({ ...agentOptions, config: withoutWalletConfig }) expect(agent.isInitialized).toBe(false) expect(agent.wallet.isInitialized).toBe(false) @@ -92,24 +150,9 @@ describe('Agent', () => { }) }) - describe('Change label', () => { - let agent: Agent - - it('should return new label after setter is called', async () => { - expect.assertions(2) - const newLabel = 'Agent: Agent Class Test 2' - - agent = new Agent(config, dependencies) - expect(agent.config.label).toBe(config.label) - - agent.config.label = newLabel - expect(agent.config.label).toBe(newLabel) - }) - }) - describe('Dependency Injection', () => { it('should be able to resolve registered instances', () => { - const agent = new Agent(config, dependencies) + const agent = new Agent(agentOptions) const container = agent.dependencyManager // Modules @@ -140,7 +183,7 @@ describe('Agent', () => { expect(container.resolve(IndyLedgerService)).toBeInstanceOf(IndyLedgerService) // Symbols, interface based - expect(container.resolve(InjectionSymbols.Logger)).toBe(config.logger) + expect(container.resolve(InjectionSymbols.Logger)).toBe(agentOptions.config.logger) expect(container.resolve(InjectionSymbols.MessageRepository)).toBeInstanceOf(InMemoryMessageRepository) expect(container.resolve(InjectionSymbols.StorageService)).toBeInstanceOf(IndyStorageService) @@ -152,7 +195,7 @@ describe('Agent', () => { }) it('should return the same instance for consequent resolves', () => { - const agent = new Agent(config, dependencies) + const agent = new Agent(agentOptions) const container = agent.dependencyManager // Modules @@ -201,7 +244,7 @@ describe('Agent', () => { }) it('all core features are properly registered', () => { - const agent = new Agent(config, dependencies) + const agent = new Agent(agentOptions) const registry = agent.dependencyManager.resolve(FeatureRegistry) const protocols = registry.query({ featureType: 'protocol', match: '*' }).map((p) => p.id) diff --git a/packages/core/src/agent/__tests__/AgentConfig.test.ts b/packages/core/src/agent/__tests__/AgentConfig.test.ts index dc4708ebc8..559a9880a3 100644 --- a/packages/core/src/agent/__tests__/AgentConfig.test.ts +++ b/packages/core/src/agent/__tests__/AgentConfig.test.ts @@ -20,6 +20,21 @@ describe('AgentConfig', () => { }) }) + describe('label', () => { + it('should return new label after setter is called', async () => { + expect.assertions(2) + const newLabel = 'Agent: Agent Class Test 2' + + const agentConfig = getAgentConfig('AgentConfig Test', { + label: 'Test', + }) + expect(agentConfig.label).toBe('Test') + + agentConfig.label = newLabel + expect(agentConfig.label).toBe(newLabel) + }) + }) + describe('extend()', () => { it('extends the existing AgentConfig', () => { const agentConfig = new AgentConfig( diff --git a/packages/core/src/agent/__tests__/AgentModules.test.ts b/packages/core/src/agent/__tests__/AgentModules.test.ts new file mode 100644 index 0000000000..2dc6eca2ae --- /dev/null +++ b/packages/core/src/agent/__tests__/AgentModules.test.ts @@ -0,0 +1,137 @@ +import type { Module } from '../../plugins' + +import { + ConnectionsModule, + CredentialsModule, + ProofsModule, + MediatorModule, + RecipientModule, + BasicMessagesModule, + QuestionAnswerModule, + LedgerModule, + DidsModule, + OutOfBandModule, +} from '../..' +import { getAgentConfig } from '../../../tests/helpers' +import { DiscoverFeaturesModule } from '../../modules/discover-features' +import { GenericRecordsModule } from '../../modules/generic-records' +import { IndyModule } from '../../modules/indy' +import { W3cVcModule } from '../../modules/vc' +import { DependencyManager, injectable } from '../../plugins' +import { WalletModule } from '../../wallet' +import { extendModulesWithDefaultModules, getAgentApi } from '../AgentModules' + +const agentConfig = getAgentConfig('AgentModules Test') + +@injectable() +class MyApi {} + +class MyModuleWithApi implements Module { + public api = MyApi + public register(dependencyManager: DependencyManager) { + dependencyManager.registerContextScoped(MyApi) + } +} + +class MyModuleWithoutApi implements Module { + public register() { + // nothing to register + } +} + +describe('AgentModules', () => { + describe('getAgentApi', () => { + test('returns object with all api instances for modules with public api in dependency manager', () => { + const dependencyManager = new DependencyManager() + + dependencyManager.registerModules({ + withApi: new MyModuleWithApi(), + withoutApi: new MyModuleWithoutApi(), + }) + + const api = getAgentApi(dependencyManager) + + expect(api).toEqual({ + withApi: expect.any(MyApi), + }) + }) + }) + + describe('extendModulesWithDefaultModules', () => { + test('returns default modules if no modules were provided', () => { + const extendedModules = extendModulesWithDefaultModules(agentConfig) + + expect(extendedModules).toEqual({ + connections: expect.any(ConnectionsModule), + credentials: expect.any(CredentialsModule), + proofs: expect.any(ProofsModule), + mediator: expect.any(MediatorModule), + mediationRecipient: expect.any(RecipientModule), + basicMessages: expect.any(BasicMessagesModule), + questionAnswer: expect.any(QuestionAnswerModule), + genericRecords: expect.any(GenericRecordsModule), + ledger: expect.any(LedgerModule), + discovery: expect.any(DiscoverFeaturesModule), + dids: expect.any(DidsModule), + wallet: expect.any(WalletModule), + oob: expect.any(OutOfBandModule), + indy: expect.any(IndyModule), + w3cVc: expect.any(W3cVcModule), + }) + }) + + test('returns custom and default modules if custom modules are provided', () => { + const myModule = new MyModuleWithApi() + const extendedModules = extendModulesWithDefaultModules(agentConfig, { + myModule, + }) + + expect(extendedModules).toEqual({ + connections: expect.any(ConnectionsModule), + credentials: expect.any(CredentialsModule), + proofs: expect.any(ProofsModule), + mediator: expect.any(MediatorModule), + mediationRecipient: expect.any(RecipientModule), + basicMessages: expect.any(BasicMessagesModule), + questionAnswer: expect.any(QuestionAnswerModule), + genericRecords: expect.any(GenericRecordsModule), + ledger: expect.any(LedgerModule), + discovery: expect.any(DiscoverFeaturesModule), + dids: expect.any(DidsModule), + wallet: expect.any(WalletModule), + oob: expect.any(OutOfBandModule), + indy: expect.any(IndyModule), + w3cVc: expect.any(W3cVcModule), + myModule, + }) + }) + + test('does not override default module if provided as custom module', () => { + const myModule = new MyModuleWithApi() + const connections = new ConnectionsModule() + const extendedModules = extendModulesWithDefaultModules(agentConfig, { + myModule, + connections, + }) + + expect(extendedModules).toEqual({ + connections: connections, + credentials: expect.any(CredentialsModule), + proofs: expect.any(ProofsModule), + mediator: expect.any(MediatorModule), + mediationRecipient: expect.any(RecipientModule), + basicMessages: expect.any(BasicMessagesModule), + questionAnswer: expect.any(QuestionAnswerModule), + genericRecords: expect.any(GenericRecordsModule), + ledger: expect.any(LedgerModule), + discovery: expect.any(DiscoverFeaturesModule), + dids: expect.any(DidsModule), + wallet: expect.any(WalletModule), + oob: expect.any(OutOfBandModule), + indy: expect.any(IndyModule), + w3cVc: expect.any(W3cVcModule), + myModule, + }) + }) + }) +}) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ef2d804359..89e06c665f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -5,6 +5,7 @@ export { MessageReceiver } from './agent/MessageReceiver' export { Agent } from './agent/Agent' export { BaseAgent } from './agent/BaseAgent' export * from './agent' +export type { ModulesMap, DefaultAgentModules, EmptyModuleMap } from './agent/AgentModules' export { EventEmitter } from './agent/EventEmitter' export { FeatureRegistry } from './agent/FeatureRegistry' export { Handler, HandlerInboundMessage } from './agent/Handler' @@ -52,6 +53,7 @@ export * from './error' export * from './wallet/error' export { Key, KeyType } from './crypto' export { parseMessageType, IsValidMessageType } from './utils/messageType' +export type { Constructor } from './utils/mixins' export * from './agent/Events' diff --git a/packages/core/src/modules/basic-messages/BasicMessagesModule.ts b/packages/core/src/modules/basic-messages/BasicMessagesModule.ts index 169e3afc48..fd1fd77f6c 100644 --- a/packages/core/src/modules/basic-messages/BasicMessagesModule.ts +++ b/packages/core/src/modules/basic-messages/BasicMessagesModule.ts @@ -9,6 +9,8 @@ import { BasicMessageRepository } from './repository' import { BasicMessageService } from './services' export class BasicMessagesModule implements Module { + public readonly api = BasicMessagesApi + /** * Registers the dependencies of the basic message module on the dependency manager. */ diff --git a/packages/core/src/modules/connections/ConnectionsModule.ts b/packages/core/src/modules/connections/ConnectionsModule.ts index a76d95ee71..b589f202ce 100644 --- a/packages/core/src/modules/connections/ConnectionsModule.ts +++ b/packages/core/src/modules/connections/ConnectionsModule.ts @@ -13,6 +13,7 @@ import { ConnectionService, TrustPingService } from './services' export class ConnectionsModule implements Module { public readonly config: ConnectionsModuleConfig + public readonly api = ConnectionsApi public constructor(config?: ConnectionsModuleConfigOptions) { this.config = new ConnectionsModuleConfig(config) diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index 475224b0f8..a54a20b1a7 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -14,6 +14,7 @@ import { CredentialRepository } from './repository' export class CredentialsModule implements Module { public readonly config: CredentialsModuleConfig + public readonly api = CredentialsApi public constructor(config?: CredentialsModuleConfigOptions) { this.config = new CredentialsModuleConfig(config) diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts index 3f8c508400..085142c8ea 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts @@ -6,7 +6,7 @@ import { ReplaySubject, Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' -import { prepareForIssuance, waitForCredentialRecordSubject, getBaseConfig } from '../../../../../../tests/helpers' +import { prepareForIssuance, waitForCredentialRecordSubject, getAgentOptions } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { Agent } from '../../../../../agent/Agent' import { CredentialEventTypes } from '../../../CredentialEvents' @@ -15,11 +15,11 @@ import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { V1CredentialPreview } from '../messages/V1CredentialPreview' -const faberConfig = getBaseConfig('Faber connection-less Credentials V1', { +const faberAgentOptions = getAgentOptions('Faber connection-less Credentials V1', { endpoints: ['rxjs:faber'], }) -const aliceConfig = getBaseConfig('Alice connection-less Credentials V1', { +const aliceAgentOptions = getAgentOptions('Alice connection-less Credentials V1', { endpoints: ['rxjs:alice'], }) @@ -43,12 +43,12 @@ describe('V1 Connectionless Credentials', () => { 'rxjs:faber': faberMessages, 'rxjs:alice': aliceMessages, } - faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent = new Agent(faberAgentOptions) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() - aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts index ced9f4f195..0382f05f1c 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts @@ -6,7 +6,7 @@ import { ReplaySubject, Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' -import { prepareForIssuance, waitForCredentialRecordSubject, getBaseConfig } from '../../../../../../tests/helpers' +import { prepareForIssuance, waitForCredentialRecordSubject, getAgentOptions } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { Agent } from '../../../../../agent/Agent' import { CredentialEventTypes } from '../../../CredentialEvents' @@ -15,11 +15,11 @@ import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { V2CredentialPreview } from '../messages' -const faberConfig = getBaseConfig('Faber connection-less Credentials V2', { +const faberAgentOptions = getAgentOptions('Faber connection-less Credentials V2', { endpoints: ['rxjs:faber'], }) -const aliceConfig = getBaseConfig('Alice connection-less Credentials V2', { +const aliceAgentOptions = getAgentOptions('Alice connection-less Credentials V2', { endpoints: ['rxjs:alice'], }) @@ -43,12 +43,12 @@ describe('V2 Connectionless Credentials', () => { 'rxjs:faber': faberMessages, 'rxjs:alice': aliceMessages, } - faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent = new Agent(faberAgentOptions) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() - aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts index afc83def5a..c3b73fd6ef 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts @@ -365,6 +365,7 @@ describe('v2 credentials', () => { state: CredentialState.CredentialReceived, }) + // testLogger.test('Alice sends credential ack to Faber') await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) testLogger.test('Faber waits for credential ack from Alice') @@ -427,7 +428,6 @@ describe('v2 credentials', () => { threadId: faberCredentialRecord.threadId, state: CredentialState.OfferReceived, }) - faberCredentialRecord = await faberAgent.credentials.negotiateProposal({ credentialRecordId: faberCredentialRecord.id, credentialFormats: { diff --git a/packages/core/src/modules/dids/DidsModule.ts b/packages/core/src/modules/dids/DidsModule.ts index 0a43f0a154..dbf46d7cad 100644 --- a/packages/core/src/modules/dids/DidsModule.ts +++ b/packages/core/src/modules/dids/DidsModule.ts @@ -14,6 +14,8 @@ export class DidsModule implements Module { this.config = new DidsModuleConfig(config) } + public readonly api = DidsApi + /** * Registers the dependencies of the dids module module on the dependency manager. */ diff --git a/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts index 264dffe6f9..a7cb1d869e 100644 --- a/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts +++ b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts @@ -5,7 +5,7 @@ import type { Wallet } from '@aries-framework/core' import { convertPublicKeyToX25519, generateKeyPairFromSeed } from '@stablelib/ed25519' -import { genesisPath, getBaseConfig } from '../../../../tests/helpers' +import { genesisPath, getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { KeyType } from '../../../crypto' import { TypedArrayEncoder } from '../../../utils' @@ -14,7 +14,7 @@ import { PeerDidNumAlgo } from '../methods/peer/didPeer' import { InjectionSymbols, JsonTransformer } from '@aries-framework/core' -const { config, agentDependencies } = getBaseConfig('Faber Dids Registrar', { +const agentOptions = getAgentOptions('Faber Dids Registrar', { indyLedgers: [ { id: `localhost`, @@ -29,7 +29,7 @@ describe('dids', () => { let agent: Agent beforeAll(async () => { - agent = new Agent(config, agentDependencies) + agent = new Agent(agentOptions) await agent.initialize() }) diff --git a/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts index 39e3710c19..861d7f0d1a 100644 --- a/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts +++ b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts @@ -2,18 +2,16 @@ import type { Wallet } from '../../../wallet' import { convertPublicKeyToX25519 } from '@stablelib/ed25519' -import { getBaseConfig } from '../../../../tests/helpers' +import { getAgentOptions } from '../../../../tests/helpers' import { sleep } from '../../../utils/sleep' import { InjectionSymbols, Key, KeyType, JsonTransformer, Agent } from '@aries-framework/core' -const { config, agentDependencies } = getBaseConfig('Faber Dids Resolver', {}) - describe('dids', () => { let agent: Agent beforeAll(async () => { - agent = new Agent(config, agentDependencies) + agent = new Agent(getAgentOptions('Faber Dids')) await agent.initialize() }) diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts index d7be4b3732..79caa885f9 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts @@ -10,6 +10,7 @@ import { V1DiscoverFeaturesService } from './protocol/v1' import { V2DiscoverFeaturesService } from './protocol/v2' export class DiscoverFeaturesModule implements Module { + public readonly api = DiscoverFeaturesApi public readonly config: DiscoverFeaturesModuleConfig public constructor(config?: DiscoverFeaturesModuleConfigOptions) { diff --git a/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts b/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts index 258eb4f543..19e48cd386 100644 --- a/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts +++ b/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts @@ -9,7 +9,7 @@ import { ReplaySubject, Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' -import { getBaseConfig, makeConnection } from '../../../../tests/helpers' +import { getAgentOptions, makeConnection } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { DiscoverFeaturesEventTypes } from '../DiscoverFeaturesEvents' @@ -27,19 +27,19 @@ describe('v1 discover features', () => { 'rxjs:faber': faberMessages, 'rxjs:alice': aliceMessages, } - const faberConfig = getBaseConfig('Faber Discover Features V1 E2E', { + const faberAgentOptions = getAgentOptions('Faber Discover Features V1 E2E', { endpoints: ['rxjs:faber'], }) - const aliceConfig = getBaseConfig('Alice Discover Features V1 E2E', { + const aliceAgentOptions = getAgentOptions('Alice Discover Features V1 E2E', { endpoints: ['rxjs:alice'], }) - faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent = new Agent(faberAgentOptions) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() - aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() diff --git a/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts b/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts index f014f5b6a6..20e2d72e2b 100644 --- a/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts +++ b/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts @@ -9,7 +9,7 @@ import { ReplaySubject, Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' -import { getBaseConfig, makeConnection } from '../../../../tests/helpers' +import { getAgentOptions, makeConnection } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { GoalCode, Feature } from '../../../agent/models' import { DiscoverFeaturesEventTypes } from '../DiscoverFeaturesEvents' @@ -29,19 +29,19 @@ describe('v2 discover features', () => { 'rxjs:faber': faberMessages, 'rxjs:alice': aliceMessages, } - const faberConfig = getBaseConfig('Faber Discover Features V2 E2E', { + const faberAgentOptions = getAgentOptions('Faber Discover Features V2 E2E', { endpoints: ['rxjs:faber'], }) - const aliceConfig = getBaseConfig('Alice Discover Features V2 E2E', { + const aliceAgentOptions = getAgentOptions('Alice Discover Features V2 E2E', { endpoints: ['rxjs:alice'], }) - faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent = new Agent(faberAgentOptions) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() - aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() diff --git a/packages/core/src/modules/generic-records/GenericRecordsModule.ts b/packages/core/src/modules/generic-records/GenericRecordsModule.ts index 0171374197..a302933083 100644 --- a/packages/core/src/modules/generic-records/GenericRecordsModule.ts +++ b/packages/core/src/modules/generic-records/GenericRecordsModule.ts @@ -5,6 +5,8 @@ import { GenericRecordsRepository } from './repository/GenericRecordsRepository' import { GenericRecordService } from './services/GenericRecordService' export class GenericRecordsModule implements Module { + public readonly api = GenericRecordsApi + /** * Registers the dependencies of the generic records module on the dependency manager. */ diff --git a/packages/core/src/modules/ledger/LedgerModule.ts b/packages/core/src/modules/ledger/LedgerModule.ts index eb501dac91..8bb9a3de82 100644 --- a/packages/core/src/modules/ledger/LedgerModule.ts +++ b/packages/core/src/modules/ledger/LedgerModule.ts @@ -10,6 +10,7 @@ import { IndyLedgerService, IndyPoolService } from './services' export class LedgerModule implements Module { public readonly config: LedgerModuleConfig + public readonly api = LedgerApi public constructor(config?: LedgerModuleConfigOptions) { this.config = new LedgerModuleConfig(config) diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts index 7408417bd7..e1561edfe2 100644 --- a/packages/core/src/modules/oob/OutOfBandApi.ts +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -15,7 +15,6 @@ import { filterContextCorrelationId, AgentEventTypes } from '../../agent/Events' import { FeatureRegistry } from '../../agent/FeatureRegistry' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' -import { FeatureQuery } from '../../agent/models' import { InjectionSymbols } from '../../constants' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' diff --git a/packages/core/src/modules/oob/OutOfBandModule.ts b/packages/core/src/modules/oob/OutOfBandModule.ts index 6cf1cebdd0..e79ab11ac8 100644 --- a/packages/core/src/modules/oob/OutOfBandModule.ts +++ b/packages/core/src/modules/oob/OutOfBandModule.ts @@ -8,6 +8,8 @@ import { OutOfBandService } from './OutOfBandService' import { OutOfBandRepository } from './repository' export class OutOfBandModule implements Module { + public readonly api = OutOfBandApi + /** * Registers the dependencies of the ot of band module on the dependency manager. */ diff --git a/packages/core/src/modules/proofs/ProofsModule.ts b/packages/core/src/modules/proofs/ProofsModule.ts index 81fe915fd8..05136c95a0 100644 --- a/packages/core/src/modules/proofs/ProofsModule.ts +++ b/packages/core/src/modules/proofs/ProofsModule.ts @@ -13,6 +13,7 @@ import { ProofRepository } from './repository' export class ProofsModule implements Module { public readonly config: ProofsModuleConfig + public readonly api = ProofsApi public constructor(config?: ProofsModuleConfigOptions) { this.config = new ProofsModuleConfig(config) diff --git a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts b/packages/core/src/modules/question-answer/QuestionAnswerModule.ts index 0a353a4f1d..3525d5b9ad 100644 --- a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts +++ b/packages/core/src/modules/question-answer/QuestionAnswerModule.ts @@ -9,6 +9,8 @@ import { QuestionAnswerRepository } from './repository' import { QuestionAnswerService } from './services' export class QuestionAnswerModule implements Module { + public readonly api = QuestionAnswerApi + /** * Registers the dependencies of the question answer module on the dependency manager. */ diff --git a/packages/core/src/modules/routing/MediatorModule.ts b/packages/core/src/modules/routing/MediatorModule.ts index 348e23aca4..db81da0f1a 100644 --- a/packages/core/src/modules/routing/MediatorModule.ts +++ b/packages/core/src/modules/routing/MediatorModule.ts @@ -13,6 +13,7 @@ import { MediatorService } from './services' export class MediatorModule implements Module { public readonly config: MediatorModuleConfig + public readonly api = MediatorApi public constructor(config?: MediatorModuleConfigOptions) { this.config = new MediatorModuleConfig(config) diff --git a/packages/core/src/modules/routing/RecipientModule.ts b/packages/core/src/modules/routing/RecipientModule.ts index 068f1f43af..7f160cdb4a 100644 --- a/packages/core/src/modules/routing/RecipientModule.ts +++ b/packages/core/src/modules/routing/RecipientModule.ts @@ -12,6 +12,7 @@ import { MediationRecipientService, RoutingService } from './services' export class RecipientModule implements Module { public readonly config: RecipientModuleConfig + public readonly api = RecipientApi public constructor(config?: RecipientModuleConfigOptions) { this.config = new RecipientModuleConfig(config) diff --git a/packages/core/src/modules/routing/__tests__/mediation.test.ts b/packages/core/src/modules/routing/__tests__/mediation.test.ts index c616bdd522..d3814e54b3 100644 --- a/packages/core/src/modules/routing/__tests__/mediation.test.ts +++ b/packages/core/src/modules/routing/__tests__/mediation.test.ts @@ -5,7 +5,7 @@ import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' -import { getBaseConfig, waitForBasicMessage } from '../../../../tests/helpers' +import { getAgentOptions, waitForBasicMessage } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { InjectionSymbols } from '../../../constants' import { sleep } from '../../../utils/sleep' @@ -13,16 +13,16 @@ import { ConnectionRecord, HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' import { MediationState } from '../models/MediationState' -const recipientConfig = getBaseConfig('Mediation: Recipient', { +const recipientAgentOptions = getAgentOptions('Mediation: Recipient', { indyLedgers: [], }) -const mediatorConfig = getBaseConfig('Mediation: Mediator', { +const mediatorAgentOptions = getAgentOptions('Mediation: Mediator', { autoAcceptMediationRequests: true, endpoints: ['rxjs:mediator'], indyLedgers: [], }) -const senderConfig = getBaseConfig('Mediation: Sender', { +const senderAgentOptions = getAgentOptions('Mediation: Sender', { endpoints: ['rxjs:sender'], indyLedgers: [], }) @@ -66,7 +66,7 @@ describe('mediator establishment', () => { } // Initialize mediatorReceived message - mediatorAgent = new Agent(mediatorConfig.config, recipientConfig.agentDependencies) + mediatorAgent = new Agent(mediatorAgentOptions) mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) await mediatorAgent.initialize() @@ -79,16 +79,16 @@ describe('mediator establishment', () => { }) // Initialize recipient with mediation connections invitation - recipientAgent = new Agent( - { - ...recipientConfig.config, + recipientAgent = new Agent({ + ...recipientAgentOptions, + config: { + ...recipientAgentOptions.config, mediatorConnectionsInvite: mediatorOutOfBandRecord.outOfBandInvitation.toUrl({ domain: 'https://example.com/ssi', }), mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - recipientConfig.agentDependencies - ) + }) recipientAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) recipientAgent.registerInboundTransport(new SubjectInboundTransport(recipientMessages)) await recipientAgent.initialize() @@ -111,7 +111,7 @@ describe('mediator establishment', () => { expect(recipientMediator?.state).toBe(MediationState.Granted) // Initialize sender agent - senderAgent = new Agent(senderConfig.config, senderConfig.agentDependencies) + senderAgent = new Agent(senderAgentOptions) senderAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) senderAgent.registerInboundTransport(new SubjectInboundTransport(senderMessages)) await senderAgent.initialize() @@ -158,7 +158,7 @@ describe('mediator establishment', () => { } // Initialize mediator - mediatorAgent = new Agent(mediatorConfig.config, recipientConfig.agentDependencies) + mediatorAgent = new Agent(mediatorAgentOptions) mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) await mediatorAgent.initialize() @@ -171,16 +171,16 @@ describe('mediator establishment', () => { }) // Initialize recipient with mediation connections invitation - recipientAgent = new Agent( - { - ...recipientConfig.config, + recipientAgent = new Agent({ + ...recipientAgentOptions, + config: { + ...recipientAgentOptions.config, mediatorConnectionsInvite: mediatorOutOfBandRecord.outOfBandInvitation.toUrl({ domain: 'https://example.com/ssi', }), mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - recipientConfig.agentDependencies - ) + }) recipientAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) recipientAgent.registerInboundTransport(new SubjectInboundTransport(recipientMessages)) await recipientAgent.initialize() @@ -202,22 +202,22 @@ describe('mediator establishment', () => { // Restart recipient agent await recipientAgent.shutdown() - recipientAgent = new Agent( - { - ...recipientConfig.config, + recipientAgent = new Agent({ + ...recipientAgentOptions, + config: { + ...recipientAgentOptions.config, mediatorConnectionsInvite: mediatorOutOfBandRecord.outOfBandInvitation.toUrl({ domain: 'https://example.com/ssi', }), mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - recipientConfig.agentDependencies - ) + }) recipientAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) recipientAgent.registerInboundTransport(new SubjectInboundTransport(recipientMessages)) await recipientAgent.initialize() // Initialize sender agent - senderAgent = new Agent(senderConfig.config, senderConfig.agentDependencies) + senderAgent = new Agent(senderAgentOptions) senderAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) senderAgent.registerInboundTransport(new SubjectInboundTransport(senderMessages)) await senderAgent.initialize() diff --git a/packages/core/src/modules/routing/__tests__/pickup.test.ts b/packages/core/src/modules/routing/__tests__/pickup.test.ts index 9320ea85da..69d43e8c46 100644 --- a/packages/core/src/modules/routing/__tests__/pickup.test.ts +++ b/packages/core/src/modules/routing/__tests__/pickup.test.ts @@ -5,16 +5,16 @@ import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' -import { getBaseConfig, waitForBasicMessage } from '../../../../tests/helpers' +import { getAgentOptions, waitForBasicMessage } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' -const recipientConfig = getBaseConfig('Mediation: Recipient Pickup', { +const recipientOptions = getAgentOptions('Mediation: Recipient Pickup', { autoAcceptConnections: true, indyLedgers: [], }) -const mediatorConfig = getBaseConfig('Mediation: Mediator Pickup', { +const mediatorOptions = getAgentOptions('Mediation: Mediator Pickup', { autoAcceptConnections: true, endpoints: ['rxjs:mediator'], indyLedgers: [], @@ -39,7 +39,7 @@ describe('E2E Pick Up protocol', () => { } // Initialize mediatorReceived message - mediatorAgent = new Agent(mediatorConfig.config, recipientConfig.agentDependencies) + mediatorAgent = new Agent(mediatorOptions) mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) await mediatorAgent.initialize() @@ -52,7 +52,7 @@ describe('E2E Pick Up protocol', () => { }) // Initialize recipient - recipientAgent = new Agent(recipientConfig.config, recipientConfig.agentDependencies) + recipientAgent = new Agent(recipientOptions) recipientAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await recipientAgent.initialize() @@ -91,7 +91,7 @@ describe('E2E Pick Up protocol', () => { } // Initialize mediatorReceived message - mediatorAgent = new Agent(mediatorConfig.config, recipientConfig.agentDependencies) + mediatorAgent = new Agent(mediatorOptions) mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) await mediatorAgent.initialize() @@ -104,7 +104,7 @@ describe('E2E Pick Up protocol', () => { }) // Initialize recipient - recipientAgent = new Agent(recipientConfig.config, recipientConfig.agentDependencies) + recipientAgent = new Agent(recipientOptions) recipientAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await recipientAgent.initialize() diff --git a/packages/core/src/plugins/DependencyManager.ts b/packages/core/src/plugins/DependencyManager.ts index fe662670fc..734a43ce81 100644 --- a/packages/core/src/plugins/DependencyManager.ts +++ b/packages/core/src/plugins/DependencyManager.ts @@ -1,23 +1,39 @@ +import type { ModulesMap } from '../agent/AgentModules' import type { Constructor } from '../utils/mixins' -import type { Module } from './Module' import type { DependencyContainer } from 'tsyringe' import { container as rootContainer, InjectionToken, Lifecycle } from 'tsyringe' import { FeatureRegistry } from '../agent/FeatureRegistry' +import { AriesFrameworkError } from '../error' export { InjectionToken } export class DependencyManager { public readonly container: DependencyContainer + public readonly registeredModules: ModulesMap - public constructor(container: DependencyContainer = rootContainer.createChildContainer()) { + public constructor( + container: DependencyContainer = rootContainer.createChildContainer(), + registeredModules: ModulesMap = {} + ) { this.container = container + this.registeredModules = registeredModules } - public registerModules(...modules: Module[]) { + public registerModules(modules: ModulesMap) { const featureRegistry = this.resolve(FeatureRegistry) - modules.forEach((module) => module.register(this, featureRegistry)) + + for (const [moduleKey, module] of Object.entries(modules)) { + if (this.registeredModules[moduleKey]) { + throw new AriesFrameworkError( + `Module with key ${moduleKey} has already been registered. Only a single module can be registered with the same key.` + ) + } + + this.registeredModules[moduleKey] = module + module.register(this, featureRegistry) + } } public registerSingleton(from: InjectionToken, to: InjectionToken): void @@ -60,6 +76,6 @@ export class DependencyManager { } public createChild() { - return new DependencyManager(this.container.createChildContainer()) + return new DependencyManager(this.container.createChildContainer(), this.registeredModules) } } diff --git a/packages/core/src/plugins/Module.ts b/packages/core/src/plugins/Module.ts index 68fa680855..f76b5329d1 100644 --- a/packages/core/src/plugins/Module.ts +++ b/packages/core/src/plugins/Module.ts @@ -3,6 +3,7 @@ import type { Constructor } from '../utils/mixins' import type { DependencyManager } from './DependencyManager' export interface Module { + api?: Constructor register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void } diff --git a/packages/core/src/plugins/__tests__/DependencyManager.test.ts b/packages/core/src/plugins/__tests__/DependencyManager.test.ts index f576ad4811..780bbaba67 100644 --- a/packages/core/src/plugins/__tests__/DependencyManager.test.ts +++ b/packages/core/src/plugins/__tests__/DependencyManager.test.ts @@ -1,4 +1,5 @@ import type { Module } from '../Module' +import type { DependencyContainer } from 'tsyringe' import { container as rootContainer, injectable, Lifecycle } from 'tsyringe' @@ -10,11 +11,15 @@ class Instance { } const instance = new Instance() -const container = rootContainer.createChildContainer() -const dependencyManager = new DependencyManager(container) -const featureRegistry = container.resolve(FeatureRegistry) - describe('DependencyManager', () => { + let container: DependencyContainer + let dependencyManager: DependencyManager + + beforeEach(() => { + container = rootContainer.createChildContainer() + dependencyManager = new DependencyManager(container) + }) + afterEach(() => { jest.resetAllMocks() container.reset() @@ -35,12 +40,19 @@ describe('DependencyManager', () => { const module1 = new Module1() const module2 = new Module2() - dependencyManager.registerModules(module1, module2) + const featureRegistry = container.resolve(FeatureRegistry) + + dependencyManager.registerModules({ module1, module2 }) expect(module1.register).toHaveBeenCalledTimes(1) expect(module1.register).toHaveBeenLastCalledWith(dependencyManager, featureRegistry) expect(module2.register).toHaveBeenCalledTimes(1) expect(module2.register).toHaveBeenLastCalledWith(dependencyManager, featureRegistry) + + expect(dependencyManager.registeredModules).toMatchObject({ + module1, + module2, + }) }) }) @@ -121,5 +133,22 @@ describe('DependencyManager', () => { expect(createChildSpy).toHaveBeenCalledTimes(1) expect(childDependencyManager.container).toBe(createChildSpy.mock.results[0].value) }) + + it('inherits the registeredModules from the parent dependency manager', () => { + const module = { + register: jest.fn(), + } + + dependencyManager.registerModules({ + module1: module, + module2: module, + }) + + const childDependencyManager = dependencyManager.createChild() + expect(childDependencyManager.registeredModules).toMatchObject({ + module1: module, + module2: module, + }) + }) }) }) diff --git a/packages/core/src/storage/migration/__tests__/0.1.test.ts b/packages/core/src/storage/migration/__tests__/0.1.test.ts index ecfca2ed69..fc2b3fb047 100644 --- a/packages/core/src/storage/migration/__tests__/0.1.test.ts +++ b/packages/core/src/storage/migration/__tests__/0.1.test.ts @@ -7,7 +7,7 @@ import path from 'path' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' import { Agent } from '../../../../src' -import { agentDependencies } from '../../../../tests/helpers' +import { agentDependencies as dependencies } from '../../../../tests/helpers' import { InjectionSymbols } from '../../../constants' import { DependencyManager } from '../../../plugins' import * as uuid from '../../../utils/uuid' @@ -57,10 +57,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const agent = new Agent( { - label: 'Test Agent', - walletConfig, + config: { label: 'Test Agent', walletConfig }, + dependencies, }, - agentDependencies, dependencyManager ) @@ -117,10 +116,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const agent = new Agent( { - label: 'Test Agent', - walletConfig, + config: { label: 'Test Agent', walletConfig }, + dependencies, }, - agentDependencies, dependencyManager ) @@ -179,11 +177,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const agent = new Agent( { - label: 'Test Agent', - walletConfig, - autoUpdateStorageOnStartup: true, + config: { label: 'Test Agent', walletConfig, autoUpdateStorageOnStartup: true }, + dependencies, }, - agentDependencies, dependencyManager ) @@ -229,11 +225,13 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const agent = new Agent( { - label: 'Test Agent', - walletConfig, - autoUpdateStorageOnStartup: true, + config: { + label: 'Test Agent', + walletConfig, + autoUpdateStorageOnStartup: true, + }, + dependencies, }, - agentDependencies, dependencyManager ) diff --git a/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts b/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts index 4cc59315e2..ec4545d05e 100644 --- a/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts +++ b/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts @@ -1,13 +1,13 @@ import type { BaseRecord } from '../../BaseRecord' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' -import { getBaseConfig } from '../../../../tests/helpers' +import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { InjectionSymbols } from '../../../constants' import { DependencyManager } from '../../../plugins' import { UpdateAssistant } from '../UpdateAssistant' -const { agentDependencies, config } = getBaseConfig('UpdateAssistant') +const agentOptions = getAgentOptions('UpdateAssistant') describe('UpdateAssistant', () => { let updateAssistant: UpdateAssistant @@ -19,7 +19,7 @@ describe('UpdateAssistant', () => { storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) - agent = new Agent(config, agentDependencies, dependencyManager) + agent = new Agent(agentOptions, dependencyManager) updateAssistant = new UpdateAssistant(agent, { v0_1ToV0_2: { diff --git a/packages/core/src/storage/migration/__tests__/backup.test.ts b/packages/core/src/storage/migration/__tests__/backup.test.ts index 557e521549..5bcf588afb 100644 --- a/packages/core/src/storage/migration/__tests__/backup.test.ts +++ b/packages/core/src/storage/migration/__tests__/backup.test.ts @@ -4,7 +4,7 @@ import type { StorageUpdateError } from '../error/StorageUpdateError' import { readFileSync, unlinkSync } from 'fs' import path from 'path' -import { getBaseConfig } from '../../../../tests/helpers' +import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { InjectionSymbols } from '../../../constants' import { AriesFrameworkError } from '../../../error' @@ -13,7 +13,7 @@ import { JsonTransformer } from '../../../utils' import { StorageUpdateService } from '../StorageUpdateService' import { UpdateAssistant } from '../UpdateAssistant' -const { agentDependencies, config } = getBaseConfig('UpdateAssistant | Backup') +const agentOptions = getAgentOptions('UpdateAssistant | Backup') const aliceCredentialRecordsString = readFileSync( path.join(__dirname, '__fixtures__/alice-4-credentials-0.1.json'), @@ -30,7 +30,7 @@ describe('UpdateAssistant | Backup', () => { let backupPath: string beforeEach(async () => { - agent = new Agent(config, agentDependencies) + agent = new Agent(agentOptions) const fileSystem = agent.dependencyManager.resolve(InjectionSymbols.FileSystem) backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` diff --git a/packages/core/src/wallet/WalletModule.ts b/packages/core/src/wallet/WalletModule.ts index 002dda6b2f..608285cd55 100644 --- a/packages/core/src/wallet/WalletModule.ts +++ b/packages/core/src/wallet/WalletModule.ts @@ -6,6 +6,8 @@ import { WalletApi } from './WalletApi' // TODO: this should be moved into the modules directory export class WalletModule implements Module { + public readonly api = WalletApi + /** * Registers the dependencies of the wallet module on the injection dependencyManager. */ diff --git a/packages/core/tests/agents.test.ts b/packages/core/tests/agents.test.ts index 484d98aa4c..47393e371b 100644 --- a/packages/core/tests/agents.test.ts +++ b/packages/core/tests/agents.test.ts @@ -9,12 +9,12 @@ import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutbou import { Agent } from '../src/agent/Agent' import { HandshakeProtocol } from '../src/modules/connections' -import { waitForBasicMessage, getBaseConfig } from './helpers' +import { waitForBasicMessage, getAgentOptions } from './helpers' -const aliceConfig = getBaseConfig('Agents Alice', { +const aliceAgentOptions = getAgentOptions('Agents Alice', { endpoints: ['rxjs:alice'], }) -const bobConfig = getBaseConfig('Agents Bob', { +const bobAgentOptions = getAgentOptions('Agents Bob', { endpoints: ['rxjs:bob'], }) @@ -40,12 +40,12 @@ describe('agents', () => { 'rxjs:bob': bobMessages, } - aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - bobAgent = new Agent(bobConfig.config, bobConfig.agentDependencies) + bobAgent = new Agent(bobAgentOptions) bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await bobAgent.initialize() diff --git a/packages/core/tests/connections.test.ts b/packages/core/tests/connections.test.ts index e9cbe9906d..01461162e2 100644 --- a/packages/core/tests/connections.test.ts +++ b/packages/core/tests/connections.test.ts @@ -9,7 +9,7 @@ import { DidExchangeState, HandshakeProtocol } from '../src' import { Agent } from '../src/agent/Agent' import { OutOfBandState } from '../src/modules/oob/domain/OutOfBandState' -import { getBaseConfig } from './helpers' +import { getAgentOptions } from './helpers' describe('connections', () => { let faberAgent: Agent @@ -26,13 +26,13 @@ describe('connections', () => { }) it('one should be able to make multiple connections using a multi use invite', async () => { - const faberConfig = getBaseConfig('Faber Agent Connections', { + const faberAgentOptions = getAgentOptions('Faber Agent Connections', { endpoints: ['rxjs:faber'], }) - const aliceConfig = getBaseConfig('Alice Agent Connections', { + const aliceAgentOptions = getAgentOptions('Alice Agent Connections', { endpoints: ['rxjs:alice'], }) - const acmeConfig = getBaseConfig('Acme Agent Connections', { + const acmeAgentOptions = getAgentOptions('Acme Agent Connections', { endpoints: ['rxjs:acme'], }) @@ -45,17 +45,17 @@ describe('connections', () => { 'rxjs:acme': acmeMessages, } - faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent = new Agent(faberAgentOptions) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() - aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - acmeAgent = new Agent(acmeConfig.config, acmeConfig.agentDependencies) + acmeAgent = new Agent(acmeAgentOptions) acmeAgent.registerInboundTransport(new SubjectInboundTransport(acmeMessages)) acmeAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await acmeAgent.initialize() @@ -100,19 +100,19 @@ describe('connections', () => { 'rxjs:faber': faberMessages, } - const faberConfig = getBaseConfig('Faber Agent Connections 2', { + const faberAgentOptions = getAgentOptions('Faber Agent Connections 2', { endpoints: ['rxjs:faber'], }) - const aliceConfig = getBaseConfig('Alice Agent Connections 2') + const aliceAgentOptions = getAgentOptions('Alice Agent Connections 2') // Faber defines both inbound and outbound transports - faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent = new Agent(faberAgentOptions) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() // Alice only has outbound transport - aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() diff --git a/packages/core/tests/generic-records.test.ts b/packages/core/tests/generic-records.test.ts index a206a5e71c..efe7455e2f 100644 --- a/packages/core/tests/generic-records.test.ts +++ b/packages/core/tests/generic-records.test.ts @@ -3,9 +3,9 @@ import type { GenericRecord } from '../src/modules/generic-records/repository/Ge import { Agent } from '../src/agent/Agent' import { RecordNotFoundError } from '../src/error' -import { getBaseConfig } from './helpers' +import { getAgentOptions } from './helpers' -const aliceConfig = getBaseConfig('Generic Records Alice', { +const aliceAgentOptions = getAgentOptions('Generic Records Alice', { endpoints: ['rxjs:alice'], }) @@ -24,7 +24,7 @@ describe('genericRecords', () => { }) test('store generic-record record', async () => { - aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent = new Agent(aliceAgentOptions) await aliceAgent.initialize() // Save genericRecord message (Minimal) diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index d98a2160d2..f23e24dbc5 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -21,7 +21,7 @@ import type { Observable } from 'rxjs' import path from 'path' import { firstValueFrom, ReplaySubject, Subject } from 'rxjs' -import { catchError, filter, map, tap, timeout } from 'rxjs/operators' +import { catchError, filter, map, timeout } from 'rxjs/operators' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' @@ -47,7 +47,8 @@ import { Key, KeyType } from '../src/crypto' import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment' import { AutoAcceptCredential } from '../src/modules/credentials/models/CredentialAutoAcceptType' import { V1CredentialPreview } from '../src/modules/credentials/protocol/v1/messages/V1CredentialPreview' -import { DidCommV1Service, DidKey } from '../src/modules/dids' +import { DidCommV1Service } from '../src/modules/dids' +import { DidKey } from '../src/modules/dids/methods/key' import { OutOfBandRole } from '../src/modules/oob/domain/OutOfBandRole' import { OutOfBandState } from '../src/modules/oob/domain/OutOfBandState' import { OutOfBandInvitation } from '../src/modules/oob/messages' @@ -72,7 +73,7 @@ export const genesisPath = process.env.GENESIS_TXN_PATH export const publicDidSeed = process.env.TEST_AGENT_PUBLIC_DID_SEED ?? '000000000000000000000000Trustee9' export { agentDependencies } -export function getBaseConfig(name: string, extraConfig: Partial = {}) { +export function getAgentOptions(name: string, extraConfig: Partial = {}) { const config: InitConfig = { label: `Agent: ${name}`, walletConfig: { @@ -96,10 +97,10 @@ export function getBaseConfig(name: string, extraConfig: Partial = { ...extraConfig, } - return { config, agentDependencies } as const + return { config, dependencies: agentDependencies } as const } -export function getBasePostgresConfig(name: string, extraConfig: Partial = {}) { +export function getPostgresAgentOptions(name: string, extraConfig: Partial = {}) { const config: InitConfig = { label: `Agent: ${name}`, walletConfig: { @@ -133,12 +134,12 @@ export function getBasePostgresConfig(name: string, extraConfig: Partial = {}) { - const { config, agentDependencies } = getBaseConfig(name, extraConfig) - return new AgentConfig(config, agentDependencies) + const { config, dependencies } = getAgentOptions(name, extraConfig) + return new AgentConfig(config, dependencies) } export function getAgentContext({ @@ -659,21 +660,21 @@ export async function setupCredentialTests( 'rxjs:faber': faberMessages, 'rxjs:alice': aliceMessages, } - const faberConfig = getBaseConfig(faberName, { + const faberAgentOptions = getAgentOptions(faberName, { endpoints: ['rxjs:faber'], autoAcceptCredentials, }) - const aliceConfig = getBaseConfig(aliceName, { + const aliceAgentOptions = getAgentOptions(aliceName, { endpoints: ['rxjs:alice'], autoAcceptCredentials, }) - const faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + const faberAgent = new Agent(faberAgentOptions) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() - const aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + const aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() @@ -706,12 +707,12 @@ export async function setupProofsTest(faberName: string, aliceName: string, auto const unique = uuid().substring(0, 4) - const faberConfig = getBaseConfig(`${faberName}-${unique}`, { + const faberAgentOptions = getAgentOptions(`${faberName}-${unique}`, { autoAcceptProofs, endpoints: ['rxjs:faber'], }) - const aliceConfig = getBaseConfig(`${aliceName}-${unique}`, { + const aliceAgentOptions = getAgentOptions(`${aliceName}-${unique}`, { autoAcceptProofs, endpoints: ['rxjs:alice'], }) @@ -723,12 +724,12 @@ export async function setupProofsTest(faberName: string, aliceName: string, auto 'rxjs:faber': faberMessages, 'rxjs:alice': aliceMessages, } - const faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + const faberAgent = new Agent(faberAgentOptions) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() - const aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + const aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() diff --git a/packages/core/tests/ledger.test.ts b/packages/core/tests/ledger.test.ts index ce0802353d..969bd32c0c 100644 --- a/packages/core/tests/ledger.test.ts +++ b/packages/core/tests/ledger.test.ts @@ -5,17 +5,15 @@ import { Agent } from '../src/agent/Agent' import { DID_IDENTIFIER_REGEX, isAbbreviatedVerkey, isFullVerkey, VERKEY_REGEX } from '../src/utils/did' import { sleep } from '../src/utils/sleep' -import { genesisPath, getBaseConfig } from './helpers' +import { genesisPath, getAgentOptions } from './helpers' import testLogger from './logger' -const { config: faberConfig, agentDependencies: faberDependencies } = getBaseConfig('Faber Ledger') - describe('ledger', () => { let faberAgent: Agent let schemaId: indy.SchemaId beforeAll(async () => { - faberAgent = new Agent(faberConfig, faberDependencies) + faberAgent = new Agent(getAgentOptions('Faber Ledger')) await faberAgent.initialize() }) @@ -141,7 +139,7 @@ describe('ledger', () => { it('should correctly store the genesis file if genesis transactions is passed', async () => { const genesisTransactions = await promises.readFile(genesisPath, { encoding: 'utf-8' }) - const { config, agentDependencies: dependencies } = getBaseConfig('Faber Ledger Genesis Transactions', { + const agentOptions = getAgentOptions('Faber Ledger Genesis Transactions', { indyLedgers: [ { id: 'pool-Faber Ledger Genesis Transactions', @@ -150,7 +148,7 @@ describe('ledger', () => { }, ], }) - const agent = new Agent(config, dependencies) + const agent = new Agent(agentOptions) await agent.initialize() if (!faberAgent.publicDid?.did) { diff --git a/packages/core/tests/migration.test.ts b/packages/core/tests/migration.test.ts index ef1590e69e..0dbf6b02dc 100644 --- a/packages/core/tests/migration.test.ts +++ b/packages/core/tests/migration.test.ts @@ -3,13 +3,13 @@ import type { VersionString } from '../src/utils/version' import { Agent } from '../src/agent/Agent' import { UpdateAssistant } from '../src/storage/migration/UpdateAssistant' -import { getBaseConfig } from './helpers' +import { getAgentOptions } from './helpers' -const { config, agentDependencies } = getBaseConfig('Migration', { publicDidSeed: undefined, indyLedgers: [] }) +const agentOptions = getAgentOptions('Migration', { publicDidSeed: undefined, indyLedgers: [] }) describe('migration', () => { test('manually initiating the update assistant to perform an update', async () => { - const agent = new Agent(config, agentDependencies) + const agent = new Agent(agentOptions) const updateAssistant = new UpdateAssistant(agent, { v0_1ToV0_2: { mediationRoleUpdateStrategy: 'allMediator' }, @@ -30,7 +30,7 @@ describe('migration', () => { // The storage version will normally be stored in e.g. persistent storage on a mobile device let currentStorageVersion: VersionString = '0.1' - const agent = new Agent(config, agentDependencies) + const agent = new Agent(agentOptions) if (currentStorageVersion !== UpdateAssistant.frameworkStorageVersion) { const updateAssistant = new UpdateAssistant(agent, { @@ -51,7 +51,7 @@ describe('migration', () => { }) test('Automatic update on agent startup', async () => { - const agent = new Agent({ ...config, autoUpdateStorageOnStartup: true }, agentDependencies) + const agent = new Agent({ ...agentOptions, config: { ...agentOptions.config, autoUpdateStorageOnStartup: true } }) await agent.initialize() await agent.shutdown() diff --git a/packages/core/tests/multi-protocol-version.test.ts b/packages/core/tests/multi-protocol-version.test.ts index 0a2f86aa93..83dc39986a 100644 --- a/packages/core/tests/multi-protocol-version.test.ts +++ b/packages/core/tests/multi-protocol-version.test.ts @@ -10,12 +10,12 @@ import { Agent } from '../src/agent/Agent' import { AgentEventTypes } from '../src/agent/Events' import { createOutboundMessage } from '../src/agent/helpers' -import { getBaseConfig } from './helpers' +import { getAgentOptions } from './helpers' -const aliceConfig = getBaseConfig('Multi Protocol Versions - Alice', { +const aliceAgentOptions = getAgentOptions('Multi Protocol Versions - Alice', { endpoints: ['rxjs:alice'], }) -const bobConfig = getBaseConfig('Multi Protocol Versions - Bob', { +const bobAgentOptions = getAgentOptions('Multi Protocol Versions - Bob', { endpoints: ['rxjs:bob'], }) @@ -41,7 +41,7 @@ describe('multi version protocols', () => { const mockHandle = jest.fn() - aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) @@ -51,7 +51,7 @@ describe('multi version protocols', () => { await aliceAgent.initialize() - bobAgent = new Agent(bobConfig.config, bobConfig.agentDependencies) + bobAgent = new Agent(bobAgentOptions) bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await bobAgent.initialize() diff --git a/packages/core/tests/oob-mediation-provision.test.ts b/packages/core/tests/oob-mediation-provision.test.ts index 448b68e3bb..3a95ee0c85 100644 --- a/packages/core/tests/oob-mediation-provision.test.ts +++ b/packages/core/tests/oob-mediation-provision.test.ts @@ -10,16 +10,16 @@ import { Agent } from '../src/agent/Agent' import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' import { MediationState, MediatorPickupStrategy } from '../src/modules/routing' -import { getBaseConfig, waitForBasicMessage } from './helpers' +import { getAgentOptions, waitForBasicMessage } from './helpers' -const faberConfig = getBaseConfig('OOB mediation provision - Faber Agent', { +const faberAgentOptions = getAgentOptions('OOB mediation provision - Faber Agent', { endpoints: ['rxjs:faber'], }) -const aliceConfig = getBaseConfig('OOB mediation provision - Alice Recipient Agent', { +const aliceAgentOptions = getAgentOptions('OOB mediation provision - Alice Recipient Agent', { endpoints: ['rxjs:alice'], mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }) -const mediatorConfig = getBaseConfig('OOB mediation provision - Mediator Agent', { +const mediatorAgentOptions = getAgentOptions('OOB mediation provision - Mediator Agent', { endpoints: ['rxjs:mediator'], autoAcceptMediationRequests: true, }) @@ -49,17 +49,17 @@ describe('out of band with mediation set up with provision method', () => { 'rxjs:mediator': mediatorMessages, } - mediatorAgent = new Agent(mediatorConfig.config, mediatorConfig.agentDependencies) + mediatorAgent = new Agent(mediatorAgentOptions) mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await mediatorAgent.initialize() - faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent = new Agent(faberAgentOptions) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() - aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) const mediationOutOfBandRecord = await mediatorAgent.oob.createInvitation(makeConnectionConfig) diff --git a/packages/core/tests/oob-mediation.test.ts b/packages/core/tests/oob-mediation.test.ts index 34ff80b35d..091c62b1ed 100644 --- a/packages/core/tests/oob-mediation.test.ts +++ b/packages/core/tests/oob-mediation.test.ts @@ -9,18 +9,18 @@ import { Agent } from '../src/agent/Agent' import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' import { MediationState, MediatorPickupStrategy } from '../src/modules/routing' -import { getBaseConfig, waitForBasicMessage } from './helpers' +import { getAgentOptions, waitForBasicMessage } from './helpers' -const faberConfig = getBaseConfig('OOB mediation - Faber Agent', { +const faberAgentOptions = getAgentOptions('OOB mediation - Faber Agent', { endpoints: ['rxjs:faber'], }) -const aliceConfig = getBaseConfig('OOB mediation - Alice Recipient Agent', { +const aliceAgentOptions = getAgentOptions('OOB mediation - Alice Recipient Agent', { endpoints: ['rxjs:alice'], // FIXME: discover features returns that we support this protocol, but we don't support all roles // we should return that we only support the mediator role so we don't have to explicitly declare this mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }) -const mediatorConfig = getBaseConfig('OOB mediation - Mediator Agent', { +const mediatorAgentOptions = getAgentOptions('OOB mediation - Mediator Agent', { endpoints: ['rxjs:mediator'], autoAcceptMediationRequests: true, }) @@ -48,17 +48,17 @@ describe('out of band with mediation', () => { 'rxjs:mediator': mediatorMessages, } - faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent = new Agent(faberAgentOptions) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() - aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - mediatorAgent = new Agent(mediatorConfig.config, mediatorConfig.agentDependencies) + mediatorAgent = new Agent(mediatorAgentOptions) mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await mediatorAgent.initialize() diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index 416522376a..9172496c09 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -20,7 +20,7 @@ import { DidCommMessageRepository, DidCommMessageRole } from '../src/storage' import { JsonEncoder } from '../src/utils' import { TestMessage } from './TestMessage' -import { getBaseConfig, prepareForIssuance, waitForCredentialRecord } from './helpers' +import { getAgentOptions, prepareForIssuance, waitForCredentialRecord } from './helpers' import { AgentEventTypes, @@ -30,10 +30,10 @@ import { V1CredentialPreview, } from '@aries-framework/core' -const faberConfig = getBaseConfig('Faber Agent OOB', { +const faberAgentOptions = getAgentOptions('Faber Agent OOB', { endpoints: ['rxjs:faber'], }) -const aliceConfig = getBaseConfig('Alice Agent OOB', { +const aliceAgentOptions = getAgentOptions('Alice Agent OOB', { endpoints: ['rxjs:alice'], }) @@ -67,12 +67,12 @@ describe('out of band', () => { 'rxjs:alice': aliceMessages, } - faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent = new Agent(faberAgentOptions) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() - aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() diff --git a/packages/core/tests/postgres.e2e.test.ts b/packages/core/tests/postgres.e2e.test.ts index 3a92c8ef46..eedffe43b5 100644 --- a/packages/core/tests/postgres.e2e.test.ts +++ b/packages/core/tests/postgres.e2e.test.ts @@ -11,12 +11,12 @@ import { loadPostgresPlugin, WalletScheme } from '../../node/src' import { Agent } from '../src/agent/Agent' import { HandshakeProtocol } from '../src/modules/connections' -import { waitForBasicMessage, getBasePostgresConfig } from './helpers' +import { waitForBasicMessage, getPostgresAgentOptions } from './helpers' -const alicePostgresConfig = getBasePostgresConfig('AgentsAlice', { +const alicePostgresAgentOptions = getPostgresAgentOptions('AgentsAlice', { endpoints: ['rxjs:alice'], }) -const bobPostgresConfig = getBasePostgresConfig('AgentsBob', { +const bobPostgresAgentOptions = getPostgresAgentOptions('AgentsBob', { endpoints: ['rxjs:bob'], }) @@ -59,12 +59,12 @@ describe('postgres agents', () => { // loading the postgres wallet plugin loadPostgresPlugin(storageConfig.config, storageConfig.credentials) - aliceAgent = new Agent(alicePostgresConfig.config, alicePostgresConfig.agentDependencies) + aliceAgent = new Agent(alicePostgresAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - bobAgent = new Agent(bobPostgresConfig.config, bobPostgresConfig.agentDependencies) + bobAgent = new Agent(bobPostgresAgentOptions) bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await bobAgent.initialize() diff --git a/packages/core/tests/v1-connectionless-proofs.test.ts b/packages/core/tests/v1-connectionless-proofs.test.ts index 68b4a4c5ac..684584a97a 100644 --- a/packages/core/tests/v1-connectionless-proofs.test.ts +++ b/packages/core/tests/v1-connectionless-proofs.test.ts @@ -29,7 +29,7 @@ import { sleep } from '../src/utils/sleep' import { uuid } from '../src/utils/uuid' import { - getBaseConfig, + getAgentOptions, issueCredential, makeConnection, prepareForIssuance, @@ -221,7 +221,7 @@ describe('Present Proof', () => { const unique = uuid().substring(0, 4) - const mediatorConfig = getBaseConfig(`Connectionless proofs with mediator Mediator-${unique}`, { + const mediatorAgentOptions = getAgentOptions(`Connectionless proofs with mediator Mediator-${unique}`, { autoAcceptMediationRequests: true, endpoints: ['rxjs:mediator'], }) @@ -235,7 +235,7 @@ describe('Present Proof', () => { } // Initialize mediator - const mediatorAgent = new Agent(mediatorConfig.config, mediatorConfig.agentDependencies) + const mediatorAgent = new Agent(mediatorAgentOptions) mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) await mediatorAgent.initialize() @@ -250,7 +250,7 @@ describe('Present Proof', () => { handshakeProtocols: [HandshakeProtocol.Connections], }) - const faberConfig = getBaseConfig(`Connectionless proofs with mediator Faber-${unique}`, { + const faberAgentOptions = getAgentOptions(`Connectionless proofs with mediator Faber-${unique}`, { autoAcceptProofs: AutoAcceptProof.Always, mediatorConnectionsInvite: faberMediationOutOfBandRecord.outOfBandInvitation.toUrl({ domain: 'https://example.com', @@ -258,7 +258,7 @@ describe('Present Proof', () => { mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }) - const aliceConfig = getBaseConfig(`Connectionless proofs with mediator Alice-${unique}`, { + const aliceAgentOptions = getAgentOptions(`Connectionless proofs with mediator Alice-${unique}`, { autoAcceptProofs: AutoAcceptProof.Always, mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation.toUrl({ domain: 'https://example.com', @@ -266,12 +266,12 @@ describe('Present Proof', () => { mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }) - const faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + const faberAgent = new Agent(faberAgentOptions) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) await faberAgent.initialize() - const aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + const aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) await aliceAgent.initialize() diff --git a/packages/core/tests/v2-connectionless-proofs.test.ts b/packages/core/tests/v2-connectionless-proofs.test.ts index b426713e3e..0acb20850f 100644 --- a/packages/core/tests/v2-connectionless-proofs.test.ts +++ b/packages/core/tests/v2-connectionless-proofs.test.ts @@ -27,7 +27,7 @@ import { LinkedAttachment } from '../src/utils/LinkedAttachment' import { uuid } from '../src/utils/uuid' import { - getBaseConfig, + getAgentOptions, issueCredential, makeConnection, prepareForIssuance, @@ -220,7 +220,7 @@ describe('Present Proof', () => { const unique = uuid().substring(0, 4) - const mediatorConfig = getBaseConfig(`Connectionless proofs with mediator Mediator-${unique}`, { + const mediatorOptions = getAgentOptions(`Connectionless proofs with mediator Mediator-${unique}`, { autoAcceptMediationRequests: true, endpoints: ['rxjs:mediator'], }) @@ -234,7 +234,7 @@ describe('Present Proof', () => { } // Initialize mediator - const mediatorAgent = new Agent(mediatorConfig.config, mediatorConfig.agentDependencies) + const mediatorAgent = new Agent(mediatorOptions) mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) await mediatorAgent.initialize() @@ -249,7 +249,7 @@ describe('Present Proof', () => { handshakeProtocols: [HandshakeProtocol.Connections], }) - const faberConfig = getBaseConfig(`Connectionless proofs with mediator Faber-${unique}`, { + const faberOptions = getAgentOptions(`Connectionless proofs with mediator Faber-${unique}`, { autoAcceptProofs: AutoAcceptProof.Always, mediatorConnectionsInvite: faberMediationOutOfBandRecord.outOfBandInvitation.toUrl({ domain: 'https://example.com', @@ -257,7 +257,7 @@ describe('Present Proof', () => { mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }) - const aliceConfig = getBaseConfig(`Connectionless proofs with mediator Alice-${unique}`, { + const aliceOptions = getAgentOptions(`Connectionless proofs with mediator Alice-${unique}`, { autoAcceptProofs: AutoAcceptProof.Always, // logger: new TestLogger(LogLevel.test), mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation.toUrl({ @@ -266,12 +266,12 @@ describe('Present Proof', () => { mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }) - const faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + const faberAgent = new Agent(faberOptions) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) await faberAgent.initialize() - const aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + const aliceAgent = new Agent(aliceOptions) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) await aliceAgent.initialize() diff --git a/packages/core/tests/wallet.test.ts b/packages/core/tests/wallet.test.ts index 2d6d718d0c..3362741146 100644 --- a/packages/core/tests/wallet.test.ts +++ b/packages/core/tests/wallet.test.ts @@ -9,18 +9,18 @@ import { WalletInvalidKeyError } from '../src/wallet/error' import { WalletDuplicateError } from '../src/wallet/error/WalletDuplicateError' import { WalletNotFoundError } from '../src/wallet/error/WalletNotFoundError' -import { getBaseConfig } from './helpers' +import { getAgentOptions } from './helpers' -const aliceConfig = getBaseConfig('wallet-tests-Alice') -const bobConfig = getBaseConfig('wallet-tests-Bob') +const aliceAgentOptions = getAgentOptions('wallet-tests-Alice') +const bobAgentOptions = getAgentOptions('wallet-tests-Bob') describe('wallet', () => { let aliceAgent: Agent let bobAgent: Agent beforeEach(async () => { - aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) - bobAgent = new Agent(bobConfig.config, bobConfig.agentDependencies) + aliceAgent = new Agent(aliceAgentOptions) + bobAgent = new Agent(bobAgentOptions) }) afterEach(async () => { @@ -141,7 +141,7 @@ describe('wallet', () => { // Initialize the wallet again and assert record does not exist // This should create a new wallet // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await bobAgent.wallet.initialize(bobConfig.config.walletConfig!) + await bobAgent.wallet.initialize(bobAgentOptions.config.walletConfig!) expect(await bobBasicMessageRepository.findById(bobAgent.context, basicMessageRecord.id)).toBeNull() await bobAgent.wallet.delete() diff --git a/packages/module-tenants/src/TenantAgent.ts b/packages/module-tenants/src/TenantAgent.ts index 20d0e61eb7..cf0deb7931 100644 --- a/packages/module-tenants/src/TenantAgent.ts +++ b/packages/module-tenants/src/TenantAgent.ts @@ -1,8 +1,8 @@ -import type { AgentContext } from '@aries-framework/core' +import type { AgentContext, DefaultAgentModules, ModulesMap } from '@aries-framework/core' import { AriesFrameworkError, BaseAgent } from '@aries-framework/core' -export class TenantAgent extends BaseAgent { +export class TenantAgent extends BaseAgent { private sessionHasEnded = false public constructor(agentContext: AgentContext) { @@ -26,8 +26,4 @@ export class TenantAgent extends BaseAgent { this._isInitialized = false this.sessionHasEnded = true } - - protected registerDependencies() { - // Nothing to do here - } } diff --git a/packages/module-tenants/src/TenantsApi.ts b/packages/module-tenants/src/TenantsApi.ts index 901c21e35f..430bd2634f 100644 --- a/packages/module-tenants/src/TenantsApi.ts +++ b/packages/module-tenants/src/TenantsApi.ts @@ -1,4 +1,5 @@ import type { CreateTenantOptions, GetTenantAgentOptions, WithTenantAgentCallback } from './TenantsApiOptions' +import type { EmptyModuleMap, ModulesMap } from '@aries-framework/core' import { AgentContext, inject, InjectionSymbols, AgentContextProvider, injectable, Logger } from '@aries-framework/core' @@ -6,7 +7,7 @@ import { TenantAgent } from './TenantAgent' import { TenantRecordService } from './services' @injectable() -export class TenantsApi { +export class TenantsApi { private agentContext: AgentContext private tenantRecordService: TenantRecordService private agentContextProvider: AgentContextProvider @@ -24,12 +25,12 @@ export class TenantsApi { this.logger = logger } - public async getTenantAgent({ tenantId }: GetTenantAgentOptions): Promise { + public async getTenantAgent({ tenantId }: GetTenantAgentOptions): Promise> { this.logger.debug(`Getting tenant agent for tenant '${tenantId}'`) const tenantContext = await this.agentContextProvider.getAgentContextForContextCorrelationId(tenantId) this.logger.trace(`Got tenant context for tenant '${tenantId}'`) - const tenantAgent = new TenantAgent(tenantContext) + const tenantAgent = new TenantAgent(tenantContext) await tenantAgent.initialize() this.logger.trace(`Initializing tenant agent for tenant '${tenantId}'`) @@ -38,7 +39,7 @@ export class TenantsApi { public async withTenantAgent( options: GetTenantAgentOptions, - withTenantAgentCallback: WithTenantAgentCallback + withTenantAgentCallback: WithTenantAgentCallback ): Promise { this.logger.debug(`Getting tenant agent for tenant '${options.tenantId}' in with tenant agent callback`) const tenantAgent = await this.getTenantAgent(options) diff --git a/packages/module-tenants/src/TenantsApiOptions.ts b/packages/module-tenants/src/TenantsApiOptions.ts index 12b01a16b8..def445b627 100644 --- a/packages/module-tenants/src/TenantsApiOptions.ts +++ b/packages/module-tenants/src/TenantsApiOptions.ts @@ -1,11 +1,14 @@ import type { TenantAgent } from './TenantAgent' import type { TenantConfig } from './models/TenantConfig' +import type { ModulesMap } from '@aries-framework/core' export interface GetTenantAgentOptions { tenantId: string } -export type WithTenantAgentCallback = (tenantAgent: TenantAgent) => Promise +export type WithTenantAgentCallback = ( + tenantAgent: TenantAgent +) => Promise export interface CreateTenantOptions { config: Omit diff --git a/packages/module-tenants/src/TenantsModule.ts b/packages/module-tenants/src/TenantsModule.ts index 190f165c3e..0d4880d61a 100644 --- a/packages/module-tenants/src/TenantsModule.ts +++ b/packages/module-tenants/src/TenantsModule.ts @@ -1,5 +1,6 @@ import type { TenantsModuleConfigOptions } from './TenantsModuleConfig' -import type { DependencyManager, Module } from '@aries-framework/core' +import type { ModulesMap, DependencyManager, Module, EmptyModuleMap } from '@aries-framework/core' +import type { Constructor } from '@aries-framework/core' import { InjectionSymbols } from '@aries-framework/core' @@ -10,9 +11,11 @@ import { TenantSessionCoordinator } from './context/TenantSessionCoordinator' import { TenantRepository, TenantRoutingRepository } from './repository' import { TenantRecordService } from './services' -export class TenantsModule implements Module { +export class TenantsModule implements Module { public readonly config: TenantsModuleConfig + public readonly api: Constructor> = TenantsApi + public constructor(config?: TenantsModuleConfigOptions) { this.config = new TenantsModuleConfig(config) } diff --git a/packages/module-tenants/src/__tests__/TenantAgent.test.ts b/packages/module-tenants/src/__tests__/TenantAgent.test.ts index 901afd77a0..ce599ef4bf 100644 --- a/packages/module-tenants/src/__tests__/TenantAgent.test.ts +++ b/packages/module-tenants/src/__tests__/TenantAgent.test.ts @@ -5,16 +5,16 @@ import { TenantAgent } from '../TenantAgent' describe('TenantAgent', () => { test('possible to construct a TenantAgent instance', () => { - const agent = new Agent( - { + const agent = new Agent({ + config: { label: 'test', walletConfig: { id: 'Wallet: TenantAgentRoot', key: 'Wallet: TenantAgentRoot', }, }, - agentDependencies - ) + dependencies: agentDependencies, + }) const tenantDependencyManager = agent.dependencyManager.createChild() diff --git a/packages/module-tenants/src/__tests__/TenantsApi.test.ts b/packages/module-tenants/src/__tests__/TenantsApi.test.ts index 3650c64509..e2c5c28fed 100644 --- a/packages/module-tenants/src/__tests__/TenantsApi.test.ts +++ b/packages/module-tenants/src/__tests__/TenantsApi.test.ts @@ -1,6 +1,6 @@ import { Agent, AgentContext, InjectionSymbols } from '@aries-framework/core' -import { agentDependencies, getAgentConfig, getAgentContext, mockFunction } from '../../../core/tests/helpers' +import { getAgentContext, getAgentOptions, mockFunction } from '../../../core/tests/helpers' import { TenantAgent } from '../TenantAgent' import { TenantsApi } from '../TenantsApi' import { TenantAgentContextProvider } from '../context/TenantAgentContextProvider' @@ -15,11 +15,11 @@ const AgentContextProviderMock = TenantAgentContextProvider as jest.Mock { describe('getTenantAgent', () => { @@ -28,7 +28,7 @@ describe('TenantsApi', () => { const tenantAgentContext = getAgentContext({ contextCorrelationId: 'tenant-id', dependencyManager: tenantDependencyManager, - agentConfig: agentConfig.extend({ + agentConfig: rootAgent.config.extend({ label: 'tenant-agent', walletConfig: { id: 'Wallet: TenantsApi: tenant-id', @@ -65,7 +65,7 @@ describe('TenantsApi', () => { const tenantAgentContext = getAgentContext({ contextCorrelationId: 'tenant-id', dependencyManager: tenantDependencyManager, - agentConfig: agentConfig.extend({ + agentConfig: rootAgent.config.extend({ label: 'tenant-agent', walletConfig: { id: 'Wallet: TenantsApi: tenant-id', @@ -103,7 +103,7 @@ describe('TenantsApi', () => { const tenantAgentContext = getAgentContext({ contextCorrelationId: 'tenant-id', dependencyManager: tenantDependencyManager, - agentConfig: agentConfig.extend({ + agentConfig: rootAgent.config.extend({ label: 'tenant-agent', walletConfig: { id: 'Wallet: TenantsApi: tenant-id', diff --git a/packages/module-tenants/tests/tenant-sessions.e2e.test.ts b/packages/module-tenants/tests/tenant-sessions.e2e.test.ts index 9043a5a5e4..61da36bd79 100644 --- a/packages/module-tenants/tests/tenant-sessions.e2e.test.ts +++ b/packages/module-tenants/tests/tenant-sessions.e2e.test.ts @@ -1,10 +1,11 @@ import type { InitConfig } from '@aries-framework/core' -import { Agent, DependencyManager } from '@aries-framework/core' +import { Agent } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/node' import testLogger from '../../core/tests/logger' -import { TenantsApi, TenantsModule } from '../src' + +import { TenantsModule } from '@aries-framework/module-tenants' jest.setTimeout(2000000) @@ -19,15 +20,14 @@ const agentConfig: InitConfig = { autoAcceptConnections: true, } -// Register tenant module. For now we need to create a custom dependency manager -// and register all plugins before initializing the agent. Later, we can add the module registration -// to the agent constructor. -const dependencyManager = new DependencyManager() -dependencyManager.registerModules(new TenantsModule()) - // Create multi-tenant agent -const agent = new Agent(agentConfig, agentDependencies, dependencyManager) -const agentTenantsApi = agent.dependencyManager.resolve(TenantsApi) +const agent = new Agent({ + config: agentConfig, + dependencies: agentDependencies, + modules: { + tenants: new TenantsModule(), + }, +}) describe('Tenants Sessions E2E', () => { beforeAll(async () => { @@ -42,7 +42,7 @@ describe('Tenants Sessions E2E', () => { test('create 100 sessions in parallel for the same tenant and close them', async () => { const numberOfSessions = 100 - const tenantRecord = await agentTenantsApi.createTenant({ + const tenantRecord = await agent.modules.tenants.createTenant({ config: { label: 'Agent 1 Tenant 1', }, @@ -51,7 +51,7 @@ describe('Tenants Sessions E2E', () => { const tenantAgentPromises = [] for (let session = 0; session < numberOfSessions; session++) { - tenantAgentPromises.push(agentTenantsApi.getTenantAgent({ tenantId: tenantRecord.id })) + tenantAgentPromises.push(agent.modules.tenants.getTenantAgent({ tenantId: tenantRecord.id })) } const tenantAgents = await Promise.all(tenantAgentPromises) @@ -65,7 +65,7 @@ describe('Tenants Sessions E2E', () => { const tenantRecordPromises = [] for (let tenantNo = 0; tenantNo < numberOfTenants; tenantNo++) { - const tenantRecord = agentTenantsApi.createTenant({ + const tenantRecord = agent.modules.tenants.createTenant({ config: { label: 'Agent 1 Tenant 1', }, @@ -79,7 +79,7 @@ describe('Tenants Sessions E2E', () => { const tenantAgentPromises = [] for (const tenantRecord of tenantRecords) { for (let session = 0; session < numberOfSessions; session++) { - tenantAgentPromises.push(agentTenantsApi.getTenantAgent({ tenantId: tenantRecord.id })) + tenantAgentPromises.push(agent.modules.tenants.getTenantAgent({ tenantId: tenantRecord.id })) } } diff --git a/packages/module-tenants/tests/tenants.e2e.test.ts b/packages/module-tenants/tests/tenants.e2e.test.ts index a8b24a88f1..5c2a616e57 100644 --- a/packages/module-tenants/tests/tenants.e2e.test.ts +++ b/packages/module-tenants/tests/tenants.e2e.test.ts @@ -1,12 +1,13 @@ import type { InitConfig } from '@aries-framework/core' -import { OutOfBandRecord, Agent, DependencyManager } from '@aries-framework/core' +import { OutOfBandRecord, Agent } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/node' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' import testLogger from '../../core/tests/logger' -import { TenantsApi, TenantsModule } from '../src' + +import { TenantsModule } from '@aries-framework/module-tenants' jest.setTimeout(2000000) @@ -32,18 +33,22 @@ const agent2Config: InitConfig = { autoAcceptConnections: true, } -// Register tenant module. For now we need to create a custom dependency manager -// and register all plugins before initializing the agent. Later, we can add the module registration -// to the agent constructor. -const agent1DependencyManager = new DependencyManager() -agent1DependencyManager.registerModules(new TenantsModule()) - -const agent2DependencyManager = new DependencyManager() -agent2DependencyManager.registerModules(new TenantsModule()) - // Create multi-tenant agents -const agent1 = new Agent(agent1Config, agentDependencies, agent1DependencyManager) -const agent2 = new Agent(agent2Config, agentDependencies, agent2DependencyManager) +const agent1 = new Agent({ + config: agent1Config, + modules: { + tenants: new TenantsModule(), + }, + dependencies: agentDependencies, +}) + +const agent2 = new Agent({ + config: agent2Config, + modules: { + tenants: new TenantsModule(), + }, + dependencies: agentDependencies, +}) // Register inbound and outbound transports (so we can communicate with ourselves) const agent1InboundTransport = new SubjectInboundTransport() @@ -65,9 +70,6 @@ agent2.registerOutboundTransport( }) ) -const agent1TenantsApi = agent1.dependencyManager.resolve(TenantsApi) -const agent2TenantsApi = agent2.dependencyManager.resolve(TenantsApi) - describe('Tenants E2E', () => { beforeAll(async () => { await agent1.initialize() @@ -83,47 +85,48 @@ describe('Tenants E2E', () => { test('create get and delete a tenant', async () => { // Create tenant - let tenantRecord1 = await agent1TenantsApi.createTenant({ + let tenantRecord1 = await agent1.modules.tenants.createTenant({ config: { label: 'Tenant 1', }, }) // Retrieve tenant record from storage - tenantRecord1 = await agent1TenantsApi.getTenantById(tenantRecord1.id) + tenantRecord1 = await agent1.modules.tenants.getTenantById(tenantRecord1.id) // Get tenant agent - const tenantAgent = await agent1TenantsApi.getTenantAgent({ + const tenantAgent = await agent1.modules.tenants.getTenantAgent({ tenantId: tenantRecord1.id, }) await tenantAgent.endSession() // Delete tenant agent - await agent1TenantsApi.deleteTenantById(tenantRecord1.id) + await agent1.modules.tenants.deleteTenantById(tenantRecord1.id) // Can not get tenant agent again - await expect(agent1TenantsApi.getTenantAgent({ tenantId: tenantRecord1.id })).rejects.toThrow( + await expect(agent1.modules.tenants.getTenantAgent({ tenantId: tenantRecord1.id })).rejects.toThrow( `TenantRecord: record with id ${tenantRecord1.id} not found.` ) }) test('create a connection between two tenants within the same agent', async () => { // Create tenants - const tenantRecord1 = await agent1TenantsApi.createTenant({ + const tenantRecord1 = await agent1.modules.tenants.createTenant({ config: { label: 'Tenant 1', }, }) - const tenantRecord2 = await agent1TenantsApi.createTenant({ + const tenantRecord2 = await agent1.modules.tenants.createTenant({ config: { label: 'Tenant 2', }, }) - const tenantAgent1 = await agent1TenantsApi.getTenantAgent({ + const tenantAgent1 = await agent1.modules.tenants.getTenantAgent({ tenantId: tenantRecord1.id, }) - const tenantAgent2 = await agent1TenantsApi.getTenantAgent({ + + const tenantAgent2 = await agent1.modules.tenants.getTenantAgent({ tenantId: tenantRecord2.id, }) @@ -154,27 +157,27 @@ describe('Tenants E2E', () => { await tenantAgent2.endSession() // Delete tenants (will also delete wallets) - await agent1TenantsApi.deleteTenantById(tenantAgent1.context.contextCorrelationId) - await agent1TenantsApi.deleteTenantById(tenantAgent2.context.contextCorrelationId) + await agent1.modules.tenants.deleteTenantById(tenantAgent1.context.contextCorrelationId) + await agent1.modules.tenants.deleteTenantById(tenantAgent2.context.contextCorrelationId) }) test('create a connection between two tenants within different agents', async () => { // Create tenants - const tenantRecord1 = await agent1TenantsApi.createTenant({ + const tenantRecord1 = await agent1.modules.tenants.createTenant({ config: { label: 'Agent 1 Tenant 1', }, }) - const tenantAgent1 = await agent1TenantsApi.getTenantAgent({ + const tenantAgent1 = await agent1.modules.tenants.getTenantAgent({ tenantId: tenantRecord1.id, }) - const tenantRecord2 = await agent2TenantsApi.createTenant({ + const tenantRecord2 = await agent2.modules.tenants.createTenant({ config: { label: 'Agent 2 Tenant 1', }, }) - const tenantAgent2 = await agent2TenantsApi.getTenantAgent({ + const tenantAgent2 = await agent2.modules.tenants.getTenantAgent({ tenantId: tenantRecord2.id, }) @@ -195,18 +198,18 @@ describe('Tenants E2E', () => { await tenantAgent2.endSession() // Delete tenants (will also delete wallets) - await agent1TenantsApi.deleteTenantById(tenantRecord1.id) - await agent2TenantsApi.deleteTenantById(tenantRecord2.id) + await agent1.modules.tenants.deleteTenantById(tenantRecord1.id) + await agent2.modules.tenants.deleteTenantById(tenantRecord2.id) }) test('perform actions within the callback of withTenantAgent', async () => { - const tenantRecord = await agent1TenantsApi.createTenant({ + const tenantRecord = await agent1.modules.tenants.createTenant({ config: { label: 'Agent 1 Tenant 1', }, }) - await agent1TenantsApi.withTenantAgent({ tenantId: tenantRecord.id }, async (tenantAgent) => { + await agent1.modules.tenants.withTenantAgent({ tenantId: tenantRecord.id }, async (tenantAgent) => { const outOfBandRecord = await tenantAgent.oob.createInvitation() expect(outOfBandRecord).toBeInstanceOf(OutOfBandRecord) @@ -214,6 +217,6 @@ describe('Tenants E2E', () => { expect(tenantAgent.config.label).toBe('Agent 1 Tenant 1') }) - await agent1TenantsApi.deleteTenantById(tenantRecord.id) + await agent1.modules.tenants.deleteTenantById(tenantRecord.id) }) }) diff --git a/samples/extension-module/dummy/DummyModule.ts b/samples/extension-module/dummy/DummyModule.ts index 44374596ba..7aab0695c1 100644 --- a/samples/extension-module/dummy/DummyModule.ts +++ b/samples/extension-module/dummy/DummyModule.ts @@ -2,13 +2,16 @@ import type { DependencyManager, FeatureRegistry, Module } from '@aries-framewor import { Protocol } from '@aries-framework/core' +import { DummyApi } from './DummyApi' import { DummyRepository } from './repository' import { DummyService } from './services' export class DummyModule implements Module { + public api = DummyApi + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { // Api - dependencyManager.registerContextScoped(DummyModule) + dependencyManager.registerContextScoped(DummyApi) dependencyManager.registerSingleton(DummyRepository) dependencyManager.registerSingleton(DummyService) diff --git a/samples/extension-module/requester.ts b/samples/extension-module/requester.ts index 4eaf2c9e7b..3a4a7726f3 100644 --- a/samples/extension-module/requester.ts +++ b/samples/extension-module/requester.ts @@ -4,7 +4,7 @@ import { Agent, AriesFrameworkError, ConsoleLogger, LogLevel, WsOutboundTranspor import { agentDependencies } from '@aries-framework/node' import { filter, first, firstValueFrom, map, ReplaySubject, timeout } from 'rxjs' -import { DummyEventTypes, DummyApi, DummyState, DummyModule } from './dummy' +import { DummyEventTypes, DummyState, DummyModule } from './dummy' const run = async () => { // Create transports @@ -12,8 +12,8 @@ const run = async () => { const wsOutboundTransport = new WsOutboundTransport() // Setup the agent - const agent = new Agent( - { + const agent = new Agent({ + config: { label: 'Dummy-powered agent - requester', walletConfig: { id: 'requester', @@ -22,18 +22,15 @@ const run = async () => { logger: new ConsoleLogger(LogLevel.test), autoAcceptConnections: true, }, - agentDependencies - ) - - // Register the DummyModule - agent.dependencyManager.registerModules(new DummyModule()) + modules: { + dummy: new DummyModule(), + }, + dependencies: agentDependencies, + }) // Register transports agent.registerOutboundTransport(wsOutboundTransport) - // Inject DummyApi - const dummyApi = agent.dependencyManager.resolve(DummyApi) - // Now agent will handle messages and events from Dummy protocol //Initialize the agent @@ -61,7 +58,7 @@ const run = async () => { .subscribe(subject) // Send a dummy request and wait for response - const record = await dummyApi.request(connectionRecord.id) + const record = await agent.modules.dummy.request(connectionRecord.id) agent.config.logger.info(`Request received for Dummy Record: ${record.id}`) const dummyRecord = await firstValueFrom(subject) diff --git a/samples/extension-module/responder.ts b/samples/extension-module/responder.ts index 8d09540a3e..9235db89c7 100644 --- a/samples/extension-module/responder.ts +++ b/samples/extension-module/responder.ts @@ -6,7 +6,7 @@ import { agentDependencies, HttpInboundTransport, WsInboundTransport } from '@ar import express from 'express' import { Server } from 'ws' -import { DummyModule, DummyEventTypes, DummyApi, DummyState } from './dummy' +import { DummyModule, DummyEventTypes, DummyState } from './dummy' const run = async () => { // Create transports @@ -19,8 +19,8 @@ const run = async () => { const wsOutboundTransport = new WsOutboundTransport() // Setup the agent - const agent = new Agent( - { + const agent = new Agent({ + config: { label: 'Dummy-powered agent - responder', endpoints: [`ws://localhost:${port}`], walletConfig: { @@ -30,11 +30,11 @@ const run = async () => { logger: new ConsoleLogger(LogLevel.test), autoAcceptConnections: true, }, - agentDependencies - ) - - // Register the DummyModule - agent.dependencyManager.registerModules(new DummyModule()) + modules: { + dummy: new DummyModule(), + }, + dependencies: agentDependencies, + }) // Register transports agent.registerInboundTransport(httpInboundTransport) @@ -47,9 +47,6 @@ const run = async () => { res.send(outOfBandInvitation.toUrl({ domain: `http://localhost:${port}/invitation` })) }) - // Inject DummyApi - const dummyApi = agent.dependencyManager.resolve(DummyApi) - // Now agent will handle messages and events from Dummy protocol //Initialize the agent @@ -64,7 +61,7 @@ const run = async () => { // Subscribe to dummy record events agent.events.on(DummyEventTypes.StateChanged, async (event: DummyStateChangedEvent) => { if (event.payload.dummyRecord.state === DummyState.RequestReceived) { - await dummyApi.respond(event.payload.dummyRecord.id) + await agent.modules.dummy.respond(event.payload.dummyRecord.id) } }) diff --git a/samples/mediator.ts b/samples/mediator.ts index da4dd15293..f62d1d00b8 100644 --- a/samples/mediator.ts +++ b/samples/mediator.ts @@ -53,7 +53,7 @@ const agentConfig: InitConfig = { } // Set up agent -const agent = new Agent(agentConfig, agentDependencies) +const agent = new Agent({ config: agentConfig, dependencies: agentDependencies }) const config = agent.config // Create all transports diff --git a/tests/e2e-http.test.ts b/tests/e2e-http.test.ts index fa140f4220..4bd2396d41 100644 --- a/tests/e2e-http.test.ts +++ b/tests/e2e-http.test.ts @@ -1,23 +1,23 @@ -import { getBaseConfig } from '../packages/core/tests/helpers' +import { getAgentOptions } from '../packages/core/tests/helpers' import { e2eTest } from './e2e-test' import { HttpOutboundTransport, Agent, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { HttpInboundTransport } from '@aries-framework/node' -const recipientConfig = getBaseConfig('E2E HTTP Recipient', { +const recipientAgentOptions = getAgentOptions('E2E HTTP Recipient', { autoAcceptCredentials: AutoAcceptCredential.ContentApproved, mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }) const mediatorPort = 3000 -const mediatorConfig = getBaseConfig('E2E HTTP Mediator', { +const mediatorAgentOptions = getAgentOptions('E2E HTTP Mediator', { endpoints: [`http://localhost:${mediatorPort}`], autoAcceptMediationRequests: true, }) const senderPort = 3001 -const senderConfig = getBaseConfig('E2E HTTP Sender', { +const senderAgentOptions = getAgentOptions('E2E HTTP Sender', { endpoints: [`http://localhost:${senderPort}`], mediatorPollingInterval: 1000, autoAcceptCredentials: AutoAcceptCredential.ContentApproved, @@ -30,9 +30,9 @@ describe('E2E HTTP tests', () => { let senderAgent: Agent beforeEach(async () => { - recipientAgent = new Agent(recipientConfig.config, recipientConfig.agentDependencies) - mediatorAgent = new Agent(mediatorConfig.config, mediatorConfig.agentDependencies) - senderAgent = new Agent(senderConfig.config, senderConfig.agentDependencies) + recipientAgent = new Agent(recipientAgentOptions) + mediatorAgent = new Agent(mediatorAgentOptions) + senderAgent = new Agent(senderAgentOptions) }) afterEach(async () => { diff --git a/tests/e2e-subject.test.ts b/tests/e2e-subject.test.ts index 51945cf1ed..1b6cdb09cc 100644 --- a/tests/e2e-subject.test.ts +++ b/tests/e2e-subject.test.ts @@ -2,7 +2,7 @@ import type { SubjectMessage } from './transport/SubjectInboundTransport' import { Subject } from 'rxjs' -import { getBaseConfig } from '../packages/core/tests/helpers' +import { getAgentOptions } from '../packages/core/tests/helpers' import { e2eTest } from './e2e-test' import { SubjectInboundTransport } from './transport/SubjectInboundTransport' @@ -10,15 +10,15 @@ import { SubjectOutboundTransport } from './transport/SubjectOutboundTransport' import { Agent, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' -const recipientConfig = getBaseConfig('E2E Subject Recipient', { +const recipientAgentOptions = getAgentOptions('E2E Subject Recipient', { autoAcceptCredentials: AutoAcceptCredential.ContentApproved, mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }) -const mediatorConfig = getBaseConfig('E2E Subject Mediator', { +const mediatorAgentOptions = getAgentOptions('E2E Subject Mediator', { endpoints: ['rxjs:mediator'], autoAcceptMediationRequests: true, }) -const senderConfig = getBaseConfig('E2E Subject Sender', { +const senderAgentOptions = getAgentOptions('E2E Subject Sender', { endpoints: ['rxjs:sender'], mediatorPollingInterval: 1000, autoAcceptCredentials: AutoAcceptCredential.ContentApproved, @@ -31,9 +31,9 @@ describe('E2E Subject tests', () => { let senderAgent: Agent beforeEach(async () => { - recipientAgent = new Agent(recipientConfig.config, recipientConfig.agentDependencies) - mediatorAgent = new Agent(mediatorConfig.config, mediatorConfig.agentDependencies) - senderAgent = new Agent(senderConfig.config, senderConfig.agentDependencies) + recipientAgent = new Agent(recipientAgentOptions) + mediatorAgent = new Agent(mediatorAgentOptions) + senderAgent = new Agent(senderAgentOptions) }) afterEach(async () => { diff --git a/tests/e2e-ws-pickup-v2.test.ts b/tests/e2e-ws-pickup-v2.test.ts index 8acb7bb96b..45c641c7d7 100644 --- a/tests/e2e-ws-pickup-v2.test.ts +++ b/tests/e2e-ws-pickup-v2.test.ts @@ -1,24 +1,24 @@ -import { getBaseConfig } from '../packages/core/tests/helpers' +import { getAgentOptions } from '../packages/core/tests/helpers' import { e2eTest } from './e2e-test' import { Agent, WsOutboundTransport, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { WsInboundTransport } from '@aries-framework/node' -const recipientConfig = getBaseConfig('E2E WS Pickup V2 Recipient ', { +const recipientOptions = getAgentOptions('E2E WS Pickup V2 Recipient ', { autoAcceptCredentials: AutoAcceptCredential.ContentApproved, mediatorPickupStrategy: MediatorPickupStrategy.PickUpV2, }) // FIXME: port numbers should not depend on availability from other test suites that use web sockets const mediatorPort = 4100 -const mediatorConfig = getBaseConfig('E2E WS Pickup V2 Mediator', { +const mediatorOptions = getAgentOptions('E2E WS Pickup V2 Mediator', { endpoints: [`ws://localhost:${mediatorPort}`], autoAcceptMediationRequests: true, }) const senderPort = 4101 -const senderConfig = getBaseConfig('E2E WS Pickup V2 Sender', { +const senderOptions = getAgentOptions('E2E WS Pickup V2 Sender', { endpoints: [`ws://localhost:${senderPort}`], mediatorPollingInterval: 1000, autoAcceptCredentials: AutoAcceptCredential.ContentApproved, @@ -31,9 +31,9 @@ describe('E2E WS Pickup V2 tests', () => { let senderAgent: Agent beforeEach(async () => { - recipientAgent = new Agent(recipientConfig.config, recipientConfig.agentDependencies) - mediatorAgent = new Agent(mediatorConfig.config, mediatorConfig.agentDependencies) - senderAgent = new Agent(senderConfig.config, senderConfig.agentDependencies) + recipientAgent = new Agent(recipientOptions) + mediatorAgent = new Agent(mediatorOptions) + senderAgent = new Agent(senderOptions) }) afterEach(async () => { diff --git a/tests/e2e-ws.test.ts b/tests/e2e-ws.test.ts index f8452eb484..e0bd5f27ab 100644 --- a/tests/e2e-ws.test.ts +++ b/tests/e2e-ws.test.ts @@ -1,23 +1,23 @@ -import { getBaseConfig } from '../packages/core/tests/helpers' +import { getAgentOptions } from '../packages/core/tests/helpers' import { e2eTest } from './e2e-test' import { Agent, WsOutboundTransport, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { WsInboundTransport } from '@aries-framework/node' -const recipientConfig = getBaseConfig('E2E WS Recipient ', { +const recipientAgentOptions = getAgentOptions('E2E WS Recipient ', { autoAcceptCredentials: AutoAcceptCredential.ContentApproved, mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }) const mediatorPort = 4000 -const mediatorConfig = getBaseConfig('E2E WS Mediator', { +const mediatorAgentOptions = getAgentOptions('E2E WS Mediator', { endpoints: [`ws://localhost:${mediatorPort}`], autoAcceptMediationRequests: true, }) const senderPort = 4001 -const senderConfig = getBaseConfig('E2E WS Sender', { +const senderAgentOptions = getAgentOptions('E2E WS Sender', { endpoints: [`ws://localhost:${senderPort}`], mediatorPollingInterval: 1000, autoAcceptCredentials: AutoAcceptCredential.ContentApproved, @@ -30,9 +30,9 @@ describe('E2E WS tests', () => { let senderAgent: Agent beforeEach(async () => { - recipientAgent = new Agent(recipientConfig.config, recipientConfig.agentDependencies) - mediatorAgent = new Agent(mediatorConfig.config, mediatorConfig.agentDependencies) - senderAgent = new Agent(senderConfig.config, senderConfig.agentDependencies) + recipientAgent = new Agent(recipientAgentOptions) + mediatorAgent = new Agent(mediatorAgentOptions) + senderAgent = new Agent(senderAgentOptions) }) afterEach(async () => { diff --git a/yarn.lock b/yarn.lock index dd7f0e014b..1fdd754f6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8791,7 +8791,7 @@ parse-json@^5.0.0, parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse-path@^4.0.4: +parse-path@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.4.tgz#4bf424e6b743fb080831f03b536af9fc43f0ffea" integrity sha512-Z2lWUis7jlmXC1jeOG9giRO2+FsuyNipeQ43HAjqAZjwSe3SEf+q/84FGPHoso3kyntbxa4c4i77t3m6fGf8cw== From c789081f932b28d08dc1cce147558149cb7abb55 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 Sep 2022 09:50:58 -0300 Subject: [PATCH 037/125] chore(release): v0.2.4 (#1024) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 14 ++++++++++++++ lerna.json | 2 +- packages/core/CHANGELOG.md | 14 ++++++++++++++ packages/core/package.json | 2 +- packages/node/CHANGELOG.md | 4 ++++ packages/node/package.json | 4 ++-- packages/react-native/CHANGELOG.md | 4 ++++ packages/react-native/package.json | 4 ++-- 8 files changed, 42 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4c354c278..f28623e4f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) + +### Bug Fixes + +- avoid crash when an unexpected message arrives ([#1019](https://github.com/hyperledger/aries-framework-javascript/issues/1019)) ([2cfadd9](https://github.com/hyperledger/aries-framework-javascript/commit/2cfadd9167438a9446d26b933aa64521d8be75e7)) +- **ledger:** check taa version instad of aml version ([#1013](https://github.com/hyperledger/aries-framework-javascript/issues/1013)) ([4ca56f6](https://github.com/hyperledger/aries-framework-javascript/commit/4ca56f6b677f45aa96c91b5c5ee8df210722609e)) +- **ledger:** remove poolConnected on pool close ([#1011](https://github.com/hyperledger/aries-framework-javascript/issues/1011)) ([f0ca8b6](https://github.com/hyperledger/aries-framework-javascript/commit/f0ca8b6346385fc8c4811fbd531aa25a386fcf30)) +- **question-answer:** question answer protocol state/role check ([#1001](https://github.com/hyperledger/aries-framework-javascript/issues/1001)) ([4b90e87](https://github.com/hyperledger/aries-framework-javascript/commit/4b90e876cc8377e7518e05445beb1a6b524840c4)) + +### Features + +- Action Menu protocol (Aries RFC 0509) implementation ([#974](https://github.com/hyperledger/aries-framework-javascript/issues/974)) ([60a8091](https://github.com/hyperledger/aries-framework-javascript/commit/60a8091d6431c98f764b2b94bff13ee97187b915)) +- **routing:** add settings to control back off strategy on mediator reconnection ([#1017](https://github.com/hyperledger/aries-framework-javascript/issues/1017)) ([543437c](https://github.com/hyperledger/aries-framework-javascript/commit/543437cd94d3023139b259ee04d6ad51cf653794)) + ## [0.2.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.2...v0.2.3) (2022-08-30) ### Bug Fixes diff --git a/lerna.json b/lerna.json index cfe7bf4adb..86f806459b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.2.3", + "version": "0.2.4", "useWorkspaces": true, "npmClient": "yarn", "command": { diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 6c088a2e42..20c7b345be 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) + +### Bug Fixes + +- avoid crash when an unexpected message arrives ([#1019](https://github.com/hyperledger/aries-framework-javascript/issues/1019)) ([2cfadd9](https://github.com/hyperledger/aries-framework-javascript/commit/2cfadd9167438a9446d26b933aa64521d8be75e7)) +- **ledger:** check taa version instad of aml version ([#1013](https://github.com/hyperledger/aries-framework-javascript/issues/1013)) ([4ca56f6](https://github.com/hyperledger/aries-framework-javascript/commit/4ca56f6b677f45aa96c91b5c5ee8df210722609e)) +- **ledger:** remove poolConnected on pool close ([#1011](https://github.com/hyperledger/aries-framework-javascript/issues/1011)) ([f0ca8b6](https://github.com/hyperledger/aries-framework-javascript/commit/f0ca8b6346385fc8c4811fbd531aa25a386fcf30)) +- **question-answer:** question answer protocol state/role check ([#1001](https://github.com/hyperledger/aries-framework-javascript/issues/1001)) ([4b90e87](https://github.com/hyperledger/aries-framework-javascript/commit/4b90e876cc8377e7518e05445beb1a6b524840c4)) + +### Features + +- Action Menu protocol (Aries RFC 0509) implementation ([#974](https://github.com/hyperledger/aries-framework-javascript/issues/974)) ([60a8091](https://github.com/hyperledger/aries-framework-javascript/commit/60a8091d6431c98f764b2b94bff13ee97187b915)) +- **routing:** add settings to control back off strategy on mediator reconnection ([#1017](https://github.com/hyperledger/aries-framework-javascript/issues/1017)) ([543437c](https://github.com/hyperledger/aries-framework-javascript/commit/543437cd94d3023139b259ee04d6ad51cf653794)) + ## [0.2.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.2...v0.2.3) (2022-08-30) ### Bug Fixes diff --git a/packages/core/package.json b/packages/core/package.json index 7ba5851665..eeb795b516 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/core", "main": "build/index", "types": "build/index", - "version": "0.2.3", + "version": "0.2.4", "files": [ "build" ], diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index 1ec6c5e21e..1a01719007 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) + +**Note:** Version bump only for package @aries-framework/node + ## [0.2.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.2...v0.2.3) (2022-08-30) **Note:** Version bump only for package @aries-framework/node diff --git a/packages/node/package.json b/packages/node/package.json index edc898820e..e4faa84365 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/node", "main": "build/index", "types": "build/index", - "version": "0.2.3", + "version": "0.2.4", "files": [ "build", "bin" @@ -28,7 +28,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.3", + "@aries-framework/core": "0.2.4", "express": "^4.17.1", "ffi-napi": "^4.0.3", "indy-sdk": "^1.16.0-dev-1636", diff --git a/packages/react-native/CHANGELOG.md b/packages/react-native/CHANGELOG.md index 73dbc1dee3..d693eee5df 100644 --- a/packages/react-native/CHANGELOG.md +++ b/packages/react-native/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) + +**Note:** Version bump only for package @aries-framework/react-native + ## [0.2.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.2...v0.2.3) (2022-08-30) **Note:** Version bump only for package @aries-framework/react-native diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 351320472c..628c06a80d 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/react-native", "main": "build/index", "types": "build/index", - "version": "0.2.3", + "version": "0.2.4", "files": [ "build" ], @@ -24,7 +24,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.3", + "@aries-framework/core": "0.2.4", "@azure/core-asynciterator-polyfill": "^1.0.0", "events": "^3.3.0" }, From 8efade5b2a885f0767ac8b10cba8582fe9ff486a Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Tue, 20 Sep 2022 17:15:19 -0300 Subject: [PATCH 038/125] feat: use did:key flag (#1029) Signed-off-by: Ariel Gentile --- packages/core/src/agent/AgentConfig.ts | 10 ++ .../repository/ConnectionMetadataTypes.ts | 9 + .../repository/ConnectionRecord.ts | 3 +- packages/core/src/modules/dids/helpers.ts | 8 +- .../routing/__tests__/mediation.test.ts | 44 +++-- .../routing/messages/KeylistUpdateMessage.ts | 5 +- .../messages/KeylistUpdateResponseMessage.ts | 5 +- .../services/MediationRecipientService.ts | 42 ++++- .../routing/services/MediatorService.ts | 29 +++- .../MediationRecipientService.test.ts | 70 +++++++- .../__tests__/MediatorService.test.ts | 160 ++++++++++++++---- packages/core/src/types.ts | 1 + 12 files changed, 318 insertions(+), 68 deletions(-) create mode 100644 packages/core/src/modules/connections/repository/ConnectionMetadataTypes.ts diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index 30766aa659..cb8ac3f680 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -91,6 +91,16 @@ export class AgentConfig { return this.initConfig.maximumMediatorReconnectionIntervalMs ?? Number.POSITIVE_INFINITY } + /** + * Encode keys in did:key format instead of 'naked' keys, as stated in Aries RFC 0360. + * + * This setting will not be taken into account if the other party has previously used naked keys + * in a given protocol (i.e. it does not support Aries RFC 0360). + */ + public get useDidKeyInProtocols() { + return this.initConfig.useDidKeyInProtocols ?? false + } + public get endpoints(): [string, ...string[]] { // if endpoints is not set, return queue endpoint // https://github.com/hyperledger/aries-rfcs/issues/405#issuecomment-582612875 diff --git a/packages/core/src/modules/connections/repository/ConnectionMetadataTypes.ts b/packages/core/src/modules/connections/repository/ConnectionMetadataTypes.ts new file mode 100644 index 0000000000..9609097515 --- /dev/null +++ b/packages/core/src/modules/connections/repository/ConnectionMetadataTypes.ts @@ -0,0 +1,9 @@ +export enum ConnectionMetadataKeys { + UseDidKeysForProtocol = '_internal/useDidKeysForProtocol', +} + +export type ConnectionMetadata = { + [ConnectionMetadataKeys.UseDidKeysForProtocol]: { + [protocolUri: string]: boolean + } +} diff --git a/packages/core/src/modules/connections/repository/ConnectionRecord.ts b/packages/core/src/modules/connections/repository/ConnectionRecord.ts index dca03cd576..b4d36e2dee 100644 --- a/packages/core/src/modules/connections/repository/ConnectionRecord.ts +++ b/packages/core/src/modules/connections/repository/ConnectionRecord.ts @@ -1,5 +1,6 @@ import type { TagsBase } from '../../../storage/BaseRecord' import type { HandshakeProtocol } from '../models' +import type { ConnectionMetadata } from './ConnectionMetadataTypes' import { AriesFrameworkError } from '../../../error' import { BaseRecord } from '../../../storage/BaseRecord' @@ -39,7 +40,7 @@ export type DefaultConnectionTags = { } export class ConnectionRecord - extends BaseRecord + extends BaseRecord implements ConnectionRecordProps { public state!: DidExchangeState diff --git a/packages/core/src/modules/dids/helpers.ts b/packages/core/src/modules/dids/helpers.ts index 2a8316a59f..27a87eee80 100644 --- a/packages/core/src/modules/dids/helpers.ts +++ b/packages/core/src/modules/dids/helpers.ts @@ -3,8 +3,12 @@ import { KeyType } from '../../crypto' import { Key } from './domain/Key' import { DidKey } from './methods/key' +export function isDidKey(key: string) { + return key.startsWith('did:key') +} + export function didKeyToVerkey(key: string) { - if (key.startsWith('did:key')) { + if (isDidKey(key)) { const publicKeyBase58 = DidKey.fromDid(key).key.publicKeyBase58 return publicKeyBase58 } @@ -12,7 +16,7 @@ export function didKeyToVerkey(key: string) { } export function verkeyToDidKey(key: string) { - if (key.startsWith('did:key')) { + if (isDidKey(key)) { return key } const publicKeyBase58 = key diff --git a/packages/core/src/modules/routing/__tests__/mediation.test.ts b/packages/core/src/modules/routing/__tests__/mediation.test.ts index 7c77711ed0..98709f5bdb 100644 --- a/packages/core/src/modules/routing/__tests__/mediation.test.ts +++ b/packages/core/src/modules/routing/__tests__/mediation.test.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' +import type { InitConfig } from '../../../types' import { Subject } from 'rxjs' @@ -39,14 +40,7 @@ describe('mediator establishment', () => { await senderAgent?.wallet.delete() }) - test(`Mediation end-to-end flow - 1. Start mediator agent and create invitation - 2. Start recipient agent with mediatorConnectionsInvite from mediator - 3. Assert mediator and recipient are connected and mediation state is Granted - 4. Start sender agent and create connection with recipient - 5. Assert endpoint in recipient invitation for sender is mediator endpoint - 6. Send basic message from sender to recipient and assert it is received on the recipient side -`, async () => { + const e2eMediationTest = async (mediatorAgentConfig: InitConfig, recipientAgentConfig: InitConfig) => { const mediatorMessages = new Subject() const recipientMessages = new Subject() const senderMessages = new Subject() @@ -56,8 +50,8 @@ describe('mediator establishment', () => { 'rxjs:sender': senderMessages, } - // Initialize mediatorReceived message - mediatorAgent = new Agent(mediatorConfig.config, recipientConfig.agentDependencies) + // Initialize mediator + mediatorAgent = new Agent(mediatorAgentConfig, mediatorConfig.agentDependencies) mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) await mediatorAgent.initialize() @@ -66,17 +60,16 @@ describe('mediator establishment', () => { const mediatorOutOfBandRecord = await mediatorAgent.oob.createInvitation({ label: 'mediator invitation', handshake: true, - handshakeProtocols: [HandshakeProtocol.DidExchange], + handshakeProtocols: [HandshakeProtocol.Connections], }) // Initialize recipient with mediation connections invitation recipientAgent = new Agent( { - ...recipientConfig.config, + ...recipientAgentConfig, mediatorConnectionsInvite: mediatorOutOfBandRecord.outOfBandInvitation.toUrl({ domain: 'https://example.com/ssi', }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, recipientConfig.agentDependencies ) @@ -136,6 +129,31 @@ describe('mediator establishment', () => { }) expect(basicMessage.content).toBe(message) + } + + test(`Mediation end-to-end flow + 1. Start mediator agent and create invitation + 2. Start recipient agent with mediatorConnectionsInvite from mediator + 3. Assert mediator and recipient are connected and mediation state is Granted + 4. Start sender agent and create connection with recipient + 5. Assert endpoint in recipient invitation for sender is mediator endpoint + 6. Send basic message from sender to recipient and assert it is received on the recipient side +`, async () => { + await e2eMediationTest(mediatorConfig.config, { + ...recipientConfig.config, + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }) + }) + + test('Mediation end-to-end flow (use did:key in both sides)', async () => { + await e2eMediationTest( + { ...mediatorConfig.config, useDidKeyInProtocols: true }, + { + ...recipientConfig.config, + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + useDidKeyInProtocols: true, + } + ) }) test('restart recipient agent and create connection through mediator after recipient agent is restarted', async () => { diff --git a/packages/core/src/modules/routing/messages/KeylistUpdateMessage.ts b/packages/core/src/modules/routing/messages/KeylistUpdateMessage.ts index e17a9edf79..b8d493881e 100644 --- a/packages/core/src/modules/routing/messages/KeylistUpdateMessage.ts +++ b/packages/core/src/modules/routing/messages/KeylistUpdateMessage.ts @@ -1,6 +1,5 @@ import { Expose, Type } from 'class-transformer' import { IsArray, ValidateNested, IsString, IsEnum, IsInstance } from 'class-validator' -import { Verkey } from 'indy-sdk' import { AgentMessage } from '../../../agent/AgentMessage' import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' @@ -11,7 +10,7 @@ export enum KeylistUpdateAction { } export class KeylistUpdate { - public constructor(options: { recipientKey: Verkey; action: KeylistUpdateAction }) { + public constructor(options: { recipientKey: string; action: KeylistUpdateAction }) { if (options) { this.recipientKey = options.recipientKey this.action = options.action @@ -20,7 +19,7 @@ export class KeylistUpdate { @IsString() @Expose({ name: 'recipient_key' }) - public recipientKey!: Verkey + public recipientKey!: string @IsEnum(KeylistUpdateAction) public action!: KeylistUpdateAction diff --git a/packages/core/src/modules/routing/messages/KeylistUpdateResponseMessage.ts b/packages/core/src/modules/routing/messages/KeylistUpdateResponseMessage.ts index 7367184e7a..88b75c694c 100644 --- a/packages/core/src/modules/routing/messages/KeylistUpdateResponseMessage.ts +++ b/packages/core/src/modules/routing/messages/KeylistUpdateResponseMessage.ts @@ -1,6 +1,5 @@ import { Expose, Type } from 'class-transformer' import { IsArray, IsEnum, IsInstance, IsString, ValidateNested } from 'class-validator' -import { Verkey } from 'indy-sdk' import { AgentMessage } from '../../../agent/AgentMessage' import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' @@ -15,7 +14,7 @@ export enum KeylistUpdateResult { } export class KeylistUpdated { - public constructor(options: { recipientKey: Verkey; action: KeylistUpdateAction; result: KeylistUpdateResult }) { + public constructor(options: { recipientKey: string; action: KeylistUpdateAction; result: KeylistUpdateResult }) { if (options) { this.recipientKey = options.recipientKey this.action = options.action @@ -25,7 +24,7 @@ export class KeylistUpdated { @IsString() @Expose({ name: 'recipient_key' }) - public recipientKey!: Verkey + public recipientKey!: string @IsEnum(KeylistUpdateAction) public action!: KeylistUpdateAction diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index 5d5073689e..7f9bb35769 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -5,7 +5,7 @@ import type { EncryptedMessage } from '../../../types' import type { ConnectionRecord } from '../../connections' import type { Routing } from '../../connections/services/ConnectionService' import type { MediationStateChangedEvent, KeylistUpdatedEvent } from '../RoutingEvents' -import type { KeylistUpdateResponseMessage, MediationDenyMessage, MediationGrantMessage } from '../messages' +import type { MediationDenyMessage } from '../messages' import type { StatusMessage, MessageDeliveryMessage } from '../protocol' import type { GetRoutingOptions } from './RoutingService' @@ -21,13 +21,19 @@ import { KeyType } from '../../../crypto' import { AriesFrameworkError } from '../../../error' import { injectable } from '../../../plugins' import { JsonTransformer } from '../../../utils' +import { ConnectionMetadataKeys } from '../../connections/repository/ConnectionMetadataTypes' import { ConnectionService } from '../../connections/services/ConnectionService' import { Key } from '../../dids' -import { didKeyToVerkey } from '../../dids/helpers' +import { didKeyToVerkey, isDidKey, verkeyToDidKey } from '../../dids/helpers' import { ProblemReportError } from '../../problem-reports' import { RoutingEventTypes } from '../RoutingEvents' import { RoutingProblemReportReason } from '../error' -import { KeylistUpdateAction, MediationRequestMessage } from '../messages' +import { + KeylistUpdateAction, + KeylistUpdateResponseMessage, + MediationRequestMessage, + MediationGrantMessage, +} from '../messages' import { KeylistUpdate, KeylistUpdateMessage } from '../messages/KeylistUpdateMessage' import { MediationRole, MediationState } from '../models' import { DeliveryRequestMessage, MessagesReceivedMessage, StatusRequestMessage } from '../protocol/pickup/v2/messages' @@ -104,6 +110,10 @@ export class MediationRecipientService { // Update record mediationRecord.endpoint = messageContext.message.endpoint + // Update connection metadata to use their key format in further protocol messages + const connectionUsesDidKey = messageContext.message.routingKeys.some(isDidKey) + await this.updateUseDidKeysFlag(connection, MediationGrantMessage.type.protocolUri, connectionUsesDidKey) + // According to RFC 0211 keys should be a did key, but base58 encoded verkey was used before // RFC was accepted. This converts the key to a public key base58 if it is a did key. mediationRecord.routingKeys = messageContext.message.routingKeys.map(didKeyToVerkey) @@ -122,12 +132,16 @@ export class MediationRecipientService { const keylist = messageContext.message.updated + // Update connection metadata to use their key format in further protocol messages + const connectionUsesDidKey = keylist.some((key) => isDidKey(key.recipientKey)) + await this.updateUseDidKeysFlag(connection, KeylistUpdateResponseMessage.type.protocolUri, connectionUsesDidKey) + // update keylist in mediationRecord for (const update of keylist) { if (update.action === KeylistUpdateAction.add) { - mediationRecord.addRecipientKey(update.recipientKey) + mediationRecord.addRecipientKey(didKeyToVerkey(update.recipientKey)) } else if (update.action === KeylistUpdateAction.remove) { - mediationRecord.removeRecipientKey(update.recipientKey) + mediationRecord.removeRecipientKey(didKeyToVerkey(update.recipientKey)) } } @@ -146,9 +160,18 @@ export class MediationRecipientService { verKey: string, timeoutMs = 15000 // TODO: this should be a configurable value in agent config ): Promise { - const message = this.createKeylistUpdateMessage(verKey) const connection = await this.connectionService.getById(mediationRecord.connectionId) + // Use our useDidKey configuration unless we know the key formatting other party is using + let useDidKey = this.config.useDidKeyInProtocols + + const useDidKeysConnectionMetadata = connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol) + if (useDidKeysConnectionMetadata) { + useDidKey = useDidKeysConnectionMetadata[KeylistUpdateMessage.type.protocolUri] ?? useDidKey + } + + const message = this.createKeylistUpdateMessage(useDidKey ? verkeyToDidKey(verKey) : verKey) + mediationRecord.assertReady() mediationRecord.assertRole(MediationRole.Recipient) @@ -381,6 +404,13 @@ export class MediationRecipientService { await this.mediationRepository.update(mediationRecord) } } + + private async updateUseDidKeysFlag(connection: ConnectionRecord, protocolUri: string, connectionUsesDidKey: boolean) { + const useDidKeysForProtocol = connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol) ?? {} + useDidKeysForProtocol[protocolUri] = connectionUsesDidKey + connection.metadata.set(ConnectionMetadataKeys.UseDidKeysForProtocol, useDidKeysForProtocol) + await this.connectionService.update(connection) + } } export interface MediationProtocolMsgReturnType { diff --git a/packages/core/src/modules/routing/services/MediatorService.ts b/packages/core/src/modules/routing/services/MediatorService.ts index 8e3e76db95..4633555081 100644 --- a/packages/core/src/modules/routing/services/MediatorService.ts +++ b/packages/core/src/modules/routing/services/MediatorService.ts @@ -1,7 +1,8 @@ import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { EncryptedMessage } from '../../../types' +import type { ConnectionRecord } from '../../connections' import type { MediationStateChangedEvent } from '../RoutingEvents' -import type { ForwardMessage, KeylistUpdateMessage, MediationRequestMessage } from '../messages' +import type { ForwardMessage, MediationRequestMessage } from '../messages' import { AgentConfig } from '../../../agent/AgentConfig' import { EventEmitter } from '../../../agent/EventEmitter' @@ -10,9 +11,12 @@ import { AriesFrameworkError } from '../../../error' import { inject, injectable } from '../../../plugins' import { JsonTransformer } from '../../../utils/JsonTransformer' import { Wallet } from '../../../wallet/Wallet' -import { didKeyToVerkey } from '../../dids/helpers' +import { ConnectionService } from '../../connections' +import { ConnectionMetadataKeys } from '../../connections/repository/ConnectionMetadataTypes' +import { didKeyToVerkey, isDidKey, verkeyToDidKey } from '../../dids/helpers' import { RoutingEventTypes } from '../RoutingEvents' import { + KeylistUpdateMessage, KeylistUpdateAction, KeylistUpdateResult, KeylistUpdated, @@ -33,6 +37,7 @@ export class MediatorService { private mediatorRoutingRepository: MediatorRoutingRepository private wallet: Wallet private eventEmitter: EventEmitter + private connectionService: ConnectionService private _mediatorRoutingRecord?: MediatorRoutingRecord public constructor( @@ -40,13 +45,15 @@ export class MediatorService { mediatorRoutingRepository: MediatorRoutingRepository, agentConfig: AgentConfig, @inject(InjectionSymbols.Wallet) wallet: Wallet, - eventEmitter: EventEmitter + eventEmitter: EventEmitter, + connectionService: ConnectionService ) { this.mediationRepository = mediationRepository this.mediatorRoutingRepository = mediatorRoutingRepository this.agentConfig = agentConfig this.wallet = wallet this.eventEmitter = eventEmitter + this.connectionService = connectionService } public async initialize() { @@ -114,6 +121,10 @@ export class MediatorService { mediationRecord.assertReady() mediationRecord.assertRole(MediationRole.Mediator) + // Update connection metadata to use their key format in further protocol messages + const connectionUsesDidKey = message.updates.some((update) => isDidKey(update.recipientKey)) + await this.updateUseDidKeysFlag(connection, KeylistUpdateMessage.type.protocolUri, connectionUsesDidKey) + for (const update of message.updates) { const updated = new KeylistUpdated({ action: update.action, @@ -149,9 +160,12 @@ export class MediatorService { await this.updateState(mediationRecord, MediationState.Granted) + // Use our useDidKey configuration, as this is the first interaction for this protocol + const useDidKey = this.agentConfig.useDidKeyInProtocols + const message = new MediationGrantMessage({ endpoint: this.agentConfig.endpoints[0], - routingKeys: this.getRoutingKeys(), + routingKeys: useDidKey ? this.getRoutingKeys().map(verkeyToDidKey) : this.getRoutingKeys(), threadId: mediationRecord.threadId, }) @@ -207,4 +221,11 @@ export class MediatorService { }, }) } + + private async updateUseDidKeysFlag(connection: ConnectionRecord, protocolUri: string, connectionUsesDidKey: boolean) { + const useDidKeysForProtocol = connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol) ?? {} + useDidKeysForProtocol[protocolUri] = connectionUsesDidKey + connection.metadata.set(ConnectionMetadataKeys.UseDidKeysForProtocol, useDidKeysForProtocol) + await this.connectionService.update(connection) + } } diff --git a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts index 4f1253a5c2..829cda5735 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts @@ -11,11 +11,18 @@ import { AriesFrameworkError } from '../../../../error' import { uuid } from '../../../../utils/uuid' import { IndyWallet } from '../../../../wallet/IndyWallet' import { DidExchangeState } from '../../../connections' +import { ConnectionMetadataKeys } from '../../../connections/repository/ConnectionMetadataTypes' import { ConnectionRepository } from '../../../connections/repository/ConnectionRepository' import { ConnectionService } from '../../../connections/services/ConnectionService' import { Key } from '../../../dids' import { DidRepository } from '../../../dids/repository/DidRepository' -import { MediationGrantMessage } from '../../messages' +import { RoutingEventTypes } from '../../RoutingEvents' +import { + KeylistUpdateAction, + KeylistUpdateResponseMessage, + KeylistUpdateResult, + MediationGrantMessage, +} from '../../messages' import { MediationRole, MediationState } from '../../models' import { DeliveryRequestMessage, MessageDeliveryMessage, MessagesReceivedMessage, StatusMessage } from '../../protocol' import { MediationRecord } from '../../repository/MediationRecord' @@ -104,10 +111,17 @@ describe('MediationRecipientService', () => { threadId: 'threadId', }) - const messageContext = new InboundMessageContext(mediationGrant, { connection: mockConnection }) + const connection = getMockConnection({ + state: DidExchangeState.Completed, + }) + + const messageContext = new InboundMessageContext(mediationGrant, { connection }) await mediationRecipientService.processMediationGrant(messageContext) + expect(connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol)).toEqual({ + 'https://didcomm.org/coordinate-mediation/1.0': false, + }) expect(mediationRecord.routingKeys).toEqual(['79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ']) }) @@ -119,10 +133,17 @@ describe('MediationRecipientService', () => { threadId: 'threadId', }) - const messageContext = new InboundMessageContext(mediationGrant, { connection: mockConnection }) + const connection = getMockConnection({ + state: DidExchangeState.Completed, + }) + + const messageContext = new InboundMessageContext(mediationGrant, { connection }) await mediationRecipientService.processMediationGrant(messageContext) + expect(connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol)).toEqual({ + 'https://didcomm.org/coordinate-mediation/1.0': true, + }) expect(mediationRecord.routingKeys).toEqual(['8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K']) }) }) @@ -154,6 +175,49 @@ describe('MediationRecipientService', () => { }) }) + describe('processKeylistUpdateResults', () => { + it('it stores did:key-encoded keys in base58 format', async () => { + const spyAddRecipientKey = jest.spyOn(mediationRecord, 'addRecipientKey') + + const connection = getMockConnection({ + state: DidExchangeState.Completed, + }) + + const keylist = [ + { + result: KeylistUpdateResult.Success, + recipientKey: 'did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th', + action: KeylistUpdateAction.add, + }, + ] + + const keyListUpdateResponse = new KeylistUpdateResponseMessage({ + threadId: uuid(), + keylist, + }) + + const messageContext = new InboundMessageContext(keyListUpdateResponse, { connection }) + + expect(connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol)).toBeNull() + + await mediationRecipientService.processKeylistUpdateResults(messageContext) + + expect(connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol)).toEqual({ + 'https://didcomm.org/coordinate-mediation/1.0': true, + }) + + expect(eventEmitter.emit).toHaveBeenCalledWith({ + type: RoutingEventTypes.RecipientKeylistUpdated, + payload: { + mediationRecord, + keylist, + }, + }) + expect(spyAddRecipientKey).toHaveBeenCalledWith('8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K') + spyAddRecipientKey.mockClear() + }) + }) + describe('processStatus', () => { it('if status request has a message count of zero returns nothing', async () => { const status = new StatusMessage({ diff --git a/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts index bd3d315294..85f2d68164 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts @@ -2,43 +2,74 @@ import { getAgentConfig, getMockConnection, mockFunction } from '../../../../../ import { EventEmitter } from '../../../../agent/EventEmitter' import { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' import { IndyWallet } from '../../../../wallet/IndyWallet' -import { DidExchangeState } from '../../../connections' -import { KeylistUpdateAction, KeylistUpdateMessage } from '../../messages' +import { ConnectionService, DidExchangeState } from '../../../connections' +import { isDidKey } from '../../../dids/helpers' +import { KeylistUpdateAction, KeylistUpdateMessage, KeylistUpdateResult } from '../../messages' import { MediationRole, MediationState } from '../../models' -import { MediationRecord } from '../../repository' +import { MediationRecord, MediatorRoutingRecord } from '../../repository' import { MediationRepository } from '../../repository/MediationRepository' import { MediatorRoutingRepository } from '../../repository/MediatorRoutingRepository' import { MediatorService } from '../MediatorService' -const agentConfig = getAgentConfig('MediatorService') - jest.mock('../../repository/MediationRepository') const MediationRepositoryMock = MediationRepository as jest.Mock jest.mock('../../repository/MediatorRoutingRepository') const MediatorRoutingRepositoryMock = MediatorRoutingRepository as jest.Mock +jest.mock('../../../connections/services/ConnectionService') +const ConnectionServiceMock = ConnectionService as jest.Mock + jest.mock('../../../../wallet/IndyWallet') const WalletMock = IndyWallet as jest.Mock const mediationRepository = new MediationRepositoryMock() const mediatorRoutingRepository = new MediatorRoutingRepositoryMock() - +const connectionService = new ConnectionServiceMock() const wallet = new WalletMock() -const mediatorService = new MediatorService( - mediationRepository, - mediatorRoutingRepository, - agentConfig, - wallet, - new EventEmitter(agentConfig) -) - const mockConnection = getMockConnection({ state: DidExchangeState.Completed, }) -describe('MediatorService', () => { +describe('MediatorService - default config', () => { + const agentConfig = getAgentConfig('MediatorService') + + const mediatorService = new MediatorService( + mediationRepository, + mediatorRoutingRepository, + agentConfig, + wallet, + new EventEmitter(agentConfig), + connectionService + ) + + describe('createGrantMediationMessage', () => { + test('sends base58 encoded recipient keys by default', async () => { + const mediationRecord = new MediationRecord({ + connectionId: 'connectionId', + role: MediationRole.Mediator, + state: MediationState.Requested, + threadId: 'threadId', + }) + + mockFunction(mediationRepository.getByConnectionId).mockResolvedValue(mediationRecord) + + mockFunction(mediatorRoutingRepository.findById).mockResolvedValue( + new MediatorRoutingRecord({ + routingKeys: ['8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K'], + }) + ) + + await mediatorService.initialize() + + const { message } = await mediatorService.createGrantMediationMessage(mediationRecord) + + expect(message.routingKeys.length).toBe(1) + expect(isDidKey(message.routingKeys[0])).toBeFalsy() + }) + }) + describe('processKeylistUpdateRequest', () => { test('processes base58 encoded recipient keys', async () => { const mediationRecord = new MediationRecord({ @@ -65,39 +96,102 @@ describe('MediatorService', () => { }) const messageContext = new InboundMessageContext(keyListUpdate, { connection: mockConnection }) - await mediatorService.processKeylistUpdateRequest(messageContext) + const response = await mediatorService.processKeylistUpdateRequest(messageContext) expect(mediationRecord.recipientKeys).toEqual(['79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ']) + expect(response.updated).toEqual([ + { + action: KeylistUpdateAction.add, + recipientKey: '79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ', + result: KeylistUpdateResult.Success, + }, + { + action: KeylistUpdateAction.remove, + recipientKey: '8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K', + result: KeylistUpdateResult.Success, + }, + ]) + }) + }) + + test('processes did:key encoded recipient keys', async () => { + const mediationRecord = new MediationRecord({ + connectionId: 'connectionId', + role: MediationRole.Mediator, + state: MediationState.Granted, + threadId: 'threadId', + recipientKeys: ['8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K'], }) - test('processes did:key encoded recipient keys', async () => { + mockFunction(mediationRepository.getByConnectionId).mockResolvedValue(mediationRecord) + + const keyListUpdate = new KeylistUpdateMessage({ + updates: [ + { + action: KeylistUpdateAction.add, + recipientKey: 'did:key:z6MkkbTaLstV4fwr1rNf5CSxdS2rGbwxi3V5y6NnVFTZ2V1w', + }, + { + action: KeylistUpdateAction.remove, + recipientKey: 'did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th', + }, + ], + }) + + const messageContext = new InboundMessageContext(keyListUpdate, { connection: mockConnection }) + const response = await mediatorService.processKeylistUpdateRequest(messageContext) + + expect(mediationRecord.recipientKeys).toEqual(['79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ']) + expect(response.updated).toEqual([ + { + action: KeylistUpdateAction.add, + recipientKey: 'did:key:z6MkkbTaLstV4fwr1rNf5CSxdS2rGbwxi3V5y6NnVFTZ2V1w', + result: KeylistUpdateResult.Success, + }, + { + action: KeylistUpdateAction.remove, + recipientKey: 'did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th', + result: KeylistUpdateResult.Success, + }, + ]) + }) +}) + +describe('MediatorService - useDidKeyInProtocols set to true', () => { + const agentConfig = getAgentConfig('MediatorService', { useDidKeyInProtocols: true }) + + const mediatorService = new MediatorService( + mediationRepository, + mediatorRoutingRepository, + agentConfig, + wallet, + new EventEmitter(agentConfig), + connectionService + ) + + describe('createGrantMediationMessage', () => { + test('sends did:key encoded recipient keys when config is set', async () => { const mediationRecord = new MediationRecord({ connectionId: 'connectionId', role: MediationRole.Mediator, - state: MediationState.Granted, + state: MediationState.Requested, threadId: 'threadId', - recipientKeys: ['8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K'], }) mockFunction(mediationRepository.getByConnectionId).mockResolvedValue(mediationRecord) - const keyListUpdate = new KeylistUpdateMessage({ - updates: [ - { - action: KeylistUpdateAction.add, - recipientKey: 'did:key:z6MkkbTaLstV4fwr1rNf5CSxdS2rGbwxi3V5y6NnVFTZ2V1w', - }, - { - action: KeylistUpdateAction.remove, - recipientKey: 'did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th', - }, - ], + const routingRecord = new MediatorRoutingRecord({ + routingKeys: ['8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K'], }) - const messageContext = new InboundMessageContext(keyListUpdate, { connection: mockConnection }) - await mediatorService.processKeylistUpdateRequest(messageContext) + mockFunction(mediatorRoutingRepository.findById).mockResolvedValue(routingRecord) - expect(mediationRecord.recipientKeys).toEqual(['79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ']) + await mediatorService.initialize() + + const { message } = await mediatorService.createGrantMediationMessage(mediationRecord) + + expect(message.routingKeys.length).toBe(1) + expect(isDidKey(message.routingKeys[0])).toBeTruthy() }) }) }) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 34aa8bc71c..abe5571c30 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -77,6 +77,7 @@ export interface InitConfig { maximumMessagePickup?: number baseMediatorReconnectionIntervalMs?: number maximumMediatorReconnectionIntervalMs?: number + useDidKeyInProtocols?: boolean useLegacyDidSovPrefix?: boolean connectionImageUrl?: string From 26bb9c9989a97bf22859a7eccbeabc632521a6c2 Mon Sep 17 00:00:00 2001 From: NB-MikeRichardson <93971245+NB-MikeRichardson@users.noreply.github.com> Date: Thu, 22 Sep 2022 10:35:09 +0300 Subject: [PATCH 039/125] feat(proofs): add getRequestedCredentialsForProofRequest (#1028) Signed-off-by: Mike Richardson --- packages/core/src/modules/proofs/ProofsApi.ts | 30 +- .../modules/proofs/models/ModuleOptions.ts | 2 + packages/core/tests/v2-indy-proofs.test.ts | 74 +++ yarn.lock | 588 +++++++++--------- 4 files changed, 413 insertions(+), 281 deletions(-) diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index 30ab0212cb..528a12e584 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -10,7 +10,10 @@ import type { } from './ProofsApiOptions' import type { ProofFormat } from './formats/ProofFormat' import type { IndyProofFormat } from './formats/indy/IndyProofFormat' -import type { AutoSelectCredentialsForProofRequestOptions } from './models/ModuleOptions' +import type { + AutoSelectCredentialsForProofRequestOptions, + GetRequestedCredentialsForProofRequest, +} from './models/ModuleOptions' import type { CreateOutOfBandRequestOptions, CreatePresentationOptions, @@ -70,6 +73,11 @@ export interface ProofsApi> + // Get Requested Credentials + getRequestedCredentialsForProofRequest( + options: AutoSelectCredentialsForProofRequestOptions + ): Promise> + sendProblemReport(proofRecordId: string, message: string): Promise // Record Methods @@ -446,6 +454,26 @@ export class ProofsApi< return await service.autoSelectCredentialsForProofRequest(retrievedCredentials) } + /** + * Create a {@link RetrievedCredentials} object. Given input proof request and presentation proposal, + * use credentials in the wallet to build indy requested credentials object for input to proof creation. + * If restrictions allow, self attested attributes will be used. + * + * @param options multiple properties like proof record id and optional configuration + * @returns RetrievedCredentials object + */ + public async getRequestedCredentialsForProofRequest( + options: GetRequestedCredentialsForProofRequest + ): Promise> { + const record = await this.getById(options.proofRecordId) + const service = this.getService(record.protocolVersion) + + return await service.getRequestedCredentialsForProofRequest(this.agentContext, { + proofRecord: record, + config: options.config, + }) + } + /** * Send problem report message for a proof record * diff --git a/packages/core/src/modules/proofs/models/ModuleOptions.ts b/packages/core/src/modules/proofs/models/ModuleOptions.ts index f60e9d853b..e471a243db 100644 --- a/packages/core/src/modules/proofs/models/ModuleOptions.ts +++ b/packages/core/src/modules/proofs/models/ModuleOptions.ts @@ -18,3 +18,5 @@ export interface AutoSelectCredentialsForProofRequestOptions { proofRecordId: string config?: GetRequestedCredentialsConfig } + +export type GetRequestedCredentialsForProofRequest = AutoSelectCredentialsForProofRequestOptions diff --git a/packages/core/tests/v2-indy-proofs.test.ts b/packages/core/tests/v2-indy-proofs.test.ts index 4bfac719ec..71d0b04096 100644 --- a/packages/core/tests/v2-indy-proofs.test.ts +++ b/packages/core/tests/v2-indy-proofs.test.ts @@ -428,6 +428,80 @@ describe('Present Proof', () => { }) }) + test('Alice provides credentials via call to getRequestedCredentials', async () => { + const attributes = { + name: new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + image_0: new ProofAttributeInfo({ + name: 'image_0', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + // Sample predicates + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const requestProofsOptions: RequestProofOptions = { + protocolVersion: ProofProtocolVersion.V2, + connectionId: faberConnection.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324864', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } + + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + // Faber sends a presentation request to Alice + testLogger.test('Faber sends a presentation request to Alice') + faberProofRecord = await faberAgent.proofs.requestProof(requestProofsOptions) + + // Alice waits for presentation request from Faber + testLogger.test('Alice waits for presentation request from Faber') + aliceProofRecord = await aliceProofRecordPromise + + const retrievedCredentials = await faberAgent.proofs.getRequestedCredentialsForProofRequest({ + proofRecordId: faberProofRecord.id, + config: {}, + }) + + if (retrievedCredentials.proofFormats.indy) { + const keys = Object.keys(retrievedCredentials.proofFormats.indy?.requestedAttributes) + expect(keys).toContain('name') + expect(keys).toContain('image_0') + } else { + fail() + } + }) + test('Faber starts with proof request to Alice but gets Problem Reported', async () => { const attributes = { name: new ProofAttributeInfo({ diff --git a/yarn.lock b/yarn.lock index 1fdd754f6e..9187c4a305 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,38 +34,38 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d" - integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.0.tgz#2a592fd89bacb1fcde68de31bee4f2f2dacb0e86" + integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.9.tgz#805461f967c77ff46c74ca0460ccf4fe933ddd59" - integrity sha512-1LIb1eL8APMy91/IMW+31ckrfBM4yCoLaVzoDhZUKSM4cu1L1nIidyxkCgzPAgrC5WEz36IPEr/eSeSF9pIn+g== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.0.tgz#d2f5f4f2033c00de8096be3c9f45772563e150c3" + integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.18.9" - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-module-transforms" "^7.18.9" - "@babel/helpers" "^7.18.9" - "@babel/parser" "^7.18.9" - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/generator" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helpers" "^7.19.0" + "@babel/parser" "^7.19.0" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.18.9", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.9.tgz#68337e9ea8044d6ddc690fb29acae39359cca0a5" - integrity sha512-wt5Naw6lJrL1/SGkipMiFxJjtyczUWTP38deiP1PO60HsBjDeKk08CGC3S8iVuvf0FmTdgKwU1KIXzSKL1G0Ug== +"@babel/generator@^7.19.0", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.0.tgz#785596c06425e59334df2ccee63ab166b738419a" + integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== dependencies: - "@babel/types" "^7.18.9" + "@babel/types" "^7.19.0" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" @@ -84,41 +84,41 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf" - integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz#537ec8339d53e806ed422f1e06c8f17d55b96bb0" + integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA== dependencies: - "@babel/compat-data" "^7.18.8" + "@babel/compat-data" "^7.19.0" "@babel/helper-validator-option" "^7.18.6" browserslist "^4.20.2" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.18.6": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz#d802ee16a64a9e824fcbf0a2ffc92f19d58550ce" - integrity sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b" + integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" "@babel/helper-member-expression-to-functions" "^7.18.9" "@babel/helper-optimise-call-expression" "^7.18.6" "@babel/helper-replace-supers" "^7.18.9" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-create-regexp-features-plugin@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz#3e35f4e04acbbf25f1b3534a657610a000543d3c" - integrity sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" + integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.1.0" -"@babel/helper-define-polyfill-provider@^0.3.1", "@babel/helper-define-polyfill-provider@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz#bd10d0aca18e8ce012755395b05a79f45eca5073" - integrity sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg== +"@babel/helper-define-polyfill-provider@^0.3.2", "@babel/helper-define-polyfill-provider@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" + integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== dependencies: "@babel/helper-compilation-targets" "^7.17.7" "@babel/helper-plugin-utils" "^7.16.7" @@ -139,13 +139,13 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-function-name@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz#940e6084a55dee867d33b4e487da2676365e86b0" - integrity sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A== +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== dependencies: - "@babel/template" "^7.18.6" - "@babel/types" "^7.18.9" + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" @@ -168,19 +168,19 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz#5a1079c005135ed627442df31a42887e80fcb712" - integrity sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g== +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30" + integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" "@babel/helper-simple-access" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.18.6" - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -189,10 +189,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.8.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz#4b8aea3b069d8cb8a72cdfe28ddf5ceca695ef2f" - integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== "@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": version "7.18.9" @@ -226,6 +226,11 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-string-parser@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" + integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== + "@babel/helper-validator-identifier@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" @@ -236,14 +241,14 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== -"@babel/helpers@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.9.tgz#4bef3b893f253a1eced04516824ede94dcfe7ff9" - integrity sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ== +"@babel/helpers@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18" + integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== dependencies: - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" "@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": version "7.18.6" @@ -254,10 +259,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.18.6", "@babel/parser@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539" - integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c" + integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== "@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.1.0": version "7.18.6" @@ -268,9 +273,9 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-proposal-export-default-from@^7.0.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.9.tgz#9dfad26452e53cae8f045c6153e82dc50e9bee89" - integrity sha512-1qtsLNCDm5awHLIt+2qAFDi31XC94r4QepMQcOosC7FpY6O+Bgay5f2IyAQt2wvm1TARumpFprnQt5pTIJ9nUg== + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz#091f4794dbce4027c03cf4ebc64d3fb96b75c206" + integrity sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow== dependencies: "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-syntax-export-default-from" "^7.18.6" @@ -452,15 +457,16 @@ "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-classes@^7.0.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz#90818efc5b9746879b869d5ce83eb2aa48bbc3da" - integrity sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz#0e61ec257fba409c41372175e7c1e606dc79bb20" + integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-compilation-targets" "^7.19.0" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-replace-supers" "^7.18.9" "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" @@ -473,9 +479,9 @@ "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-destructuring@^7.0.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz#68906549c021cb231bee1db21d3b5b095f8ee292" - integrity sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA== + version "7.18.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz#9e03bc4a94475d62b7f4114938e6c5c33372cbf5" + integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow== dependencies: "@babel/helper-plugin-utils" "^7.18.9" @@ -488,11 +494,11 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-flow-strip-types@^7.0.0", "@babel/plugin-transform-flow-strip-types@^7.18.6": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.18.9.tgz#5b4cc521426263b5ce08893a2db41097ceba35bf" - integrity sha512-+G6rp2zRuOAInY5wcggsx4+QVao1qPM0osC9fTUVlAV3zOrzTCnrMAFVnR6+a3T8wz1wFIH7KhYMcMB3u1n80A== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz#e9e8606633287488216028719638cbbb2f2dde8f" + integrity sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-syntax-flow" "^7.18.6" "@babel/plugin-transform-for-of@^7.0.0": @@ -586,15 +592,15 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-react-jsx@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.18.6.tgz#2721e96d31df96e3b7ad48ff446995d26bc028ff" - integrity sha512-Mz7xMPxoy9kPS/JScj6fJs03TZ/fZ1dJPlMjRAgTaxaS0fUBk8FV/A2rRgfPsVCZqALNwMexD+0Uaf5zlcKPpw== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz#b3cbb7c3a00b92ec8ae1027910e331ba5c500eb9" + integrity sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-syntax-jsx" "^7.18.6" - "@babel/types" "^7.18.6" + "@babel/types" "^7.19.0" "@babel/plugin-transform-regenerator@^7.0.0": version "7.18.6" @@ -605,15 +611,15 @@ regenerator-transform "^0.15.0" "@babel/plugin-transform-runtime@^7.0.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.9.tgz#d9e4b1b25719307bfafbf43065ed7fb3a83adb8f" - integrity sha512-wS8uJwBt7/b/mzE13ktsJdmS4JP/j7PQSaADtnb4I2wL0zK51MQ0pmF8/Jy0wUIS96fr+fXT6S/ifiPXnvrlSg== + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz#37d14d1fa810a368fd635d4d1476c0154144a96f" + integrity sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ== dependencies: "@babel/helper-module-imports" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.9" - babel-plugin-polyfill-corejs2 "^0.3.1" - babel-plugin-polyfill-corejs3 "^0.5.2" - babel-plugin-polyfill-regenerator "^0.3.1" + babel-plugin-polyfill-corejs2 "^0.3.2" + babel-plugin-polyfill-corejs3 "^0.5.3" + babel-plugin-polyfill-regenerator "^0.4.0" semver "^6.3.0" "@babel/plugin-transform-shorthand-properties@^7.0.0": @@ -624,11 +630,11 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-spread@^7.0.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz#6ea7a6297740f381c540ac56caf75b05b74fb664" - integrity sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" + integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-transform-sticky-regex@^7.0.0": @@ -646,12 +652,12 @@ "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-typescript@^7.18.6", "@babel/plugin-transform-typescript@^7.5.0": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.8.tgz#303feb7a920e650f2213ef37b36bbf327e6fa5a0" - integrity sha512-p2xM8HI83UObjsZGofMV/EdYjamsDm6MoN3hXPYIT0+gxIoopE+B7rPYKAxfrz9K9PK7JafTTjqYC6qipLExYA== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.0.tgz#50c3a68ec8efd5e040bde2cd764e8e16bc0cbeaf" + integrity sha512-DOOIywxPpkQHXijXv+s9MDAyZcLp12oYRl3CMWZ6u7TjSoCBq/KqHR/nNFR3+i2xqheZxoF0H2XyL7B6xeSRuA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-syntax-typescript" "^7.18.6" "@babel/plugin-transform-unicode-regex@^7.0.0": @@ -692,42 +698,43 @@ source-map-support "^0.5.16" "@babel/runtime@^7.8.4": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" - integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" + integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.0.0", "@babel/template@^7.18.6", "@babel/template@^7.3.3": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" - integrity sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw== +"@babel/template@^7.0.0", "@babel/template@^7.18.10", "@babel/template@^7.3.3": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.6" - "@babel/types" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.18.9", "@babel/traverse@^7.7.2": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.9.tgz#deeff3e8f1bad9786874cb2feda7a2d77a904f98" - integrity sha512-LcPAnujXGwBgv3/WHv01pHtb2tihcyW1XuL9wd7jqh1Z8AQkTd+QVjMrMijrln0T7ED3UXLIy36P9Ao7W75rYg== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.18.9", "@babel/traverse@^7.19.0", "@babel/traverse@^7.7.2": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.0.tgz#eb9c561c7360005c592cc645abafe0c3c4548eed" + integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.18.9" + "@babel/generator" "^7.19.0" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/parser" "^7.19.0" + "@babel/types" "^7.19.0" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.9.tgz#7148d64ba133d8d73a41b3172ac4b83a1452205f" - integrity sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg== +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600" + integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== dependencies: + "@babel/helper-string-parser" "^7.18.10" "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" @@ -1097,9 +1104,9 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping@^0.3.9": - version "0.3.14" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" - integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + version "0.3.15" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" + integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== dependencies: "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" @@ -2031,9 +2038,9 @@ "@octokit/openapi-types" "^12.11.0" "@peculiar/asn1-schema@^2.1.6": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.2.0.tgz#d8a54527685c8dee518e6448137349444310ad64" - integrity sha512-1ENEJNY7Lwlua/1wvzpYP194WtjQBfFxvde2FlzfBFh/ln6wvChrtxlORhbKEnYswzn6fOC4c7HdC5izLPMTJg== + version "2.3.0" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.0.tgz#5368416eb336138770c692ffc2bab119ee3ae917" + integrity sha512-DtNLAG4vmDrdSJFPe7rypkcj597chNQL7u+2dBtYo5mh7VW2+im6ke+O0NVr8W1f4re4C3F71LhoMb0Yxqa48Q== dependencies: asn1js "^3.0.5" pvtsutils "^1.3.2" @@ -2240,11 +2247,11 @@ "@stablelib/int" "^1.0.1" "@stablelib/ed25519@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@stablelib/ed25519/-/ed25519-1.0.2.tgz#937a88a2f73a71d9bdc3ea276efe8954776ae0f4" - integrity sha512-FtnvUwvKbp6l1dNcg4CswMAVFVu/nzLK3oC7/PRtjYyHbWsIkD8j+5cjXHmwcCpdCpRCaTGACkEhhMQ1RcdSOQ== + version "1.0.3" + resolved "https://registry.yarnpkg.com/@stablelib/ed25519/-/ed25519-1.0.3.tgz#f8fdeb6f77114897c887bb6a3138d659d3f35996" + integrity sha512-puIMWaX9QlRsbhxfDc5i+mNPMY+0TmQEskunY1rZEBPi1acBCVQAhnsk/1Hk50DGPtVsZtAWQg4NHGlVaO9Hqg== dependencies: - "@stablelib/random" "^1.0.1" + "@stablelib/random" "^1.0.2" "@stablelib/sha512" "^1.0.1" "@stablelib/wipe" "^1.0.1" @@ -2266,10 +2273,10 @@ "@stablelib/binary" "^1.0.0" "@stablelib/wipe" "^1.0.0" -"@stablelib/random@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@stablelib/random/-/random-1.0.1.tgz#4357a00cb1249d484a9a71e6054bc7b8324a7009" - integrity sha512-zOh+JHX3XG9MSfIB0LZl/YwPP9w3o6WBiJkZvjPoKKu5LKFW4OLV71vMxWp9qG5T43NaWyn0QQTWgqCdO+yOBQ== +"@stablelib/random@^1.0.1", "@stablelib/random@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@stablelib/random/-/random-1.0.2.tgz#2dece393636489bf7e19c51229dd7900eddf742c" + integrity sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w== dependencies: "@stablelib/binary" "^1.0.1" "@stablelib/wipe" "^1.0.1" @@ -2349,16 +2356,16 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.17.1" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" - integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== + version "7.18.1" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.1.tgz#ce5e2c8c272b99b7a9fd69fa39f0b4cd85028bd9" + integrity sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA== dependencies: "@babel/types" "^7.3.0" "@types/bn.js@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68" - integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA== + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" + integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g== dependencies: "@types/node" "*" @@ -2429,9 +2436,9 @@ "@types/ref-struct-di" "*" "@types/figlet@^1.5.4": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@types/figlet/-/figlet-1.5.4.tgz#54a426d63e921a9bca44102c5b1b1f206fa56d93" - integrity sha512-cskPTju7glYgzvkJy/hftqw7Fen3fsd0yrPOqcbBLJu+YdDQuA438akS1g+2XVKGzsQOnXGV2I9ePv6xUBnKMQ== + version "1.5.5" + resolved "https://registry.yarnpkg.com/@types/figlet/-/figlet-1.5.5.tgz#da93169178f0187da288c313ab98ab02fb1e8b8c" + integrity sha512-0sMBeFoqdGgdXoR/hgKYSWMpFufSpToosNsI2VgmkPqZJgeEXsXNu2hGr0FN401dBro2tNO5y2D6uw3UxVaxbg== "@types/graceful-fs@^4.1.2": version "4.1.5" @@ -2448,12 +2455,11 @@ buffer "^6.0.0" "@types/inquirer@^8.1.3": - version "8.2.1" - resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.1.tgz#28a139be3105a1175e205537e8ac10830e38dbf4" - integrity sha512-wKW3SKIUMmltbykg4I5JzCVzUhkuD9trD6efAmYgN2MrSntY0SMRQzEnD3mkyJ/rv9NLbTC7g3hKKE86YwEDLw== + version "8.2.3" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.3.tgz#985515d04879a0d0c1f5f49ec375767410ba9dab" + integrity sha512-ZlBqD+8WIVNy3KIVkl+Qne6bGLW2erwN0GJXY9Ri/9EMbyupee3xw3H0Mmv5kJoLyNpfd/oHlwKxO0DUDH7yWA== dependencies: "@types/through" "*" - rxjs "^7.2.0" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" @@ -2498,9 +2504,9 @@ integrity sha512-cPiXpOvPFDr2edMnOXlz3UBDApwUfR+cpizvxCy0n3vp9bz/qe8BWzHPIEFcy+ogUOyjKuCISgyq77ELZPmkkg== "@types/mime@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.0.tgz#e9a9903894405c6a6551f1774df4e64d9804d69c" - integrity sha512-fccbsHKqFDXClBZTDLA43zl0+TbxyIwyzIzwwhvoJvhNjOErCdeX2xJbURimv2EbSVUGav001PaCJg4mZxMl4w== + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== "@types/minimatch@^3.0.3": version "3.0.5" @@ -2541,9 +2547,9 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prettier@^2.1.5": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.4.tgz#ad899dad022bab6b5a9f0a0fe67c2f7a4a8950ed" - integrity sha512-fOwvpvQYStpb/zHMx0Cauwywu9yLDmzWiiQBC7gJyq5tYLUXFZvDG7VK1B7WBxxjBJNKFOZ0zLoOQn8vmATbhw== + version "2.7.0" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.0.tgz#ea03e9f0376a4446f44797ca19d9c46c36e352dc" + integrity sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A== "@types/prop-types@*": version "15.7.5" @@ -2561,16 +2567,16 @@ integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== "@types/react-native@^0.64.10": - version "0.64.26" - resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.64.26.tgz#f19fdfe7f0d8a36f6864409b28e69c5f7d602cf3" - integrity sha512-L1U0+wM7GSq1uGu6d/Z9wKB705xyr7Pg47JiXjp9P8DZAFpqP4xsEM/PQ8hcCopEupo6ltQ8ipqoDJ4Nb9Lw5Q== + version "0.64.27" + resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.64.27.tgz#f16e0b713c733c2476e7b16c92bf767f0675c560" + integrity sha512-vOEGMQGKNp6B1UfofKvCit2AxwByI6cbSa71E2uuxuvFr7FATVlykDbUS/Yht1HJhnbP5qlNOYw4ocUvDGjwbA== dependencies: "@types/react" "^17" "@types/react@^17": - version "17.0.48" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.48.tgz#a4532a8b91d7b27b8768b6fc0c3bccb760d15a6c" - integrity sha512-zJ6IYlJ8cYYxiJfUaZOQee4lh99mFihBoqkOSEGV+dFi9leROW6+PgstzQ+w3gWTnUfskALtQPGHK6dYmPj+2A== + version "17.0.49" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.49.tgz#df87ba4ca8b7942209c3dc655846724539dc1049" + integrity sha512-CCBPMZaPhcKkYUTqFs/hOWqKjPxhTEmnZWjlHHgIMop67DsXywf9B5Os9Hz8KSacjNOgIdnZVJamwl232uxoPg== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -2621,9 +2627,9 @@ integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/validator@^13.1.3": - version "13.7.4" - resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.4.tgz#33cc949ee87dd47c63e35ba4ad94f6888852be04" - integrity sha512-uAaSWegu2lymY18l+s5nmcXu3sFeeTOl1zhSGoYzcr6T3wz1M+3OcW4UjfPhIhHGd13tIMRDsEpR+d8w/MexwQ== + version "13.7.6" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.6.tgz#631f1acd15cbac9cb0a114da7e87575f1c95b46a" + integrity sha512-uBsnWETsUagQ0n6G2wcXNIufpTNJir0zqzG4p62fhnwzs48d/iuOWEEo0d3iUxN7D+9R/8CSvWGKS+KmaD0mWA== "@types/varint@^6.0.0": version "6.0.0" @@ -3255,16 +3261,16 @@ babel-plugin-jest-hoist@^27.5.1: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" -babel-plugin-polyfill-corejs2@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz#e4c31d4c89b56f3cf85b92558954c66b54bd972d" - integrity sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q== +babel-plugin-polyfill-corejs2@^0.3.2: + version "0.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" + integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== dependencies: "@babel/compat-data" "^7.17.7" - "@babel/helper-define-polyfill-provider" "^0.3.2" + "@babel/helper-define-polyfill-provider" "^0.3.3" semver "^6.1.1" -babel-plugin-polyfill-corejs3@^0.5.2: +babel-plugin-polyfill-corejs3@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7" integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw== @@ -3272,12 +3278,12 @@ babel-plugin-polyfill-corejs3@^0.5.2: "@babel/helper-define-polyfill-provider" "^0.3.2" core-js-compat "^3.21.0" -babel-plugin-polyfill-regenerator@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990" - integrity sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A== +babel-plugin-polyfill-regenerator@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" + integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.1" + "@babel/helper-define-polyfill-provider" "^0.3.3" babel-plugin-syntax-trailing-function-commas@^7.0.0-beta.0: version "7.0.0-beta.0" @@ -3396,9 +3402,9 @@ big-integer@1.6.x: integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== bignumber.js@^9.0.0: - version "9.0.2" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.2.tgz#71c6c6bed38de64e24a65ebe16cfcf23ae693673" - integrity sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw== + version "9.1.0" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.0.tgz#8d340146107fe3a6cb8d40699643c302e8773b62" + integrity sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A== bindings@^1.3.1: version "1.5.0" @@ -3653,9 +3659,9 @@ camelcase@^6.0.0, camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001370: - version "1.0.30001373" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001373.tgz#2dc3bc3bfcb5d5a929bec11300883040d7b4b4be" - integrity sha512-pJYArGHrPp3TUqQzFYRmP/lwJlj8RCbVe3Gd3eJQkAV8SAC6b19XS9BjMvRdvaS8RMkaTN8ZhoHP6S1y8zzwEQ== + version "1.0.30001399" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001399.tgz#1bf994ca375d7f33f8d01ce03b7d5139e8587873" + integrity sha512-4vQ90tMKS+FkvuVWS5/QY1+d805ODxZiKFzsU8o/RsVJz49ZSRR8EjykLJbqhzdPgadbX6wB538wOzle3JniRA== canonicalize@^1.0.1: version "1.0.8" @@ -3725,9 +3731,9 @@ ci-info@^2.0.0: integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== ci-info@^3.2.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.2.tgz#6d2967ffa407466481c6c90b6e16b3098f080128" - integrity sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg== + version "3.4.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.4.0.tgz#b28484fd436cbc267900364f096c9dc185efb251" + integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== cjs-module-lexer@^1.0.0: version "1.2.2" @@ -4167,12 +4173,11 @@ copy-descriptor@^0.1.0: integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== core-js-compat@^3.21.0: - version "3.24.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.24.1.tgz#d1af84a17e18dfdd401ee39da9996f9a7ba887de" - integrity sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw== + version "3.25.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.1.tgz#6f13a90de52f89bbe6267e5620a412c7f7ff7e42" + integrity sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw== dependencies: browserslist "^4.21.3" - semver "7.0.0" core-util-is@1.0.2: version "1.0.2" @@ -4304,9 +4309,9 @@ dateformat@^3.0.0: integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== dayjs@^1.8.15: - version "1.11.4" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.4.tgz#3b3c10ca378140d8917e06ebc13a4922af4f433e" - integrity sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g== + version "1.11.5" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93" + integrity sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA== debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" @@ -4348,9 +4353,9 @@ decamelize@^1.1.0, decamelize@^1.2.0: integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== decimal.js@^10.2.1: - version "10.3.1" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" - integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + version "10.4.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.0.tgz#97a7448873b01e92e5ff9117d89a7bca8e63e0fe" + integrity sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg== decode-uri-component@^0.2.0: version "0.2.0" @@ -4487,11 +4492,16 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" -did-resolver@^3.1.3, did-resolver@^3.2.2: +did-resolver@^3.1.3: version "3.2.2" resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-3.2.2.tgz#6f4e252a810f785d1b28a10265fad6dffee25158" integrity sha512-Eeo2F524VM5N3W4GwglZrnul2y6TLTwMQP3In62JdG34NZoqihYyOZLk+5wUW8sSgvIYIcJM8Dlt3xsdKZZ3tg== +did-resolver@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-4.0.0.tgz#fc8f657b4cd7f44c2921051fb046599fbe7d4b31" + integrity sha512-/roxrDr9EnAmLs+s9T+8+gcpilMo+IkeytcsGO7dcxvTmVJ+0Rt60HtV8o0UXHhGBo0Q+paMH/0ffXz1rqGFYg== + diff-sequences@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" @@ -4573,9 +4583,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.4.202: - version "1.4.206" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.206.tgz#580ff85b54d7ec0c05f20b1e37ea0becdd7b0ee4" - integrity sha512-h+Fadt1gIaQ06JaIiyqPsBjJ08fV5Q7md+V8bUvQW/9OvXfL2LRICTz2EcnnCP7QzrFTS6/27MRV6Bl9Yn97zA== + version "1.4.248" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.248.tgz#dd2dab68277e91e8452536ee9265f484066f94ad" + integrity sha512-qShjzEYpa57NnhbW2K+g+Fl+eNoDvQ7I+2MRwWnU6Z6F0HhXekzsECCLv+y2OJUsRodjqoSfwHkIX42VUFtUzg== emittery@^0.8.1: version "0.8.1" @@ -4651,15 +4661,15 @@ errorhandler@^1.5.0: escape-html "~1.0.3" es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814" - integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== + version "1.20.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.2.tgz#8495a07bc56d342a3b8ea3ab01bd986700c2ccb3" + integrity sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" function.prototype.name "^1.1.5" - get-intrinsic "^1.1.1" + get-intrinsic "^1.1.2" get-symbol-description "^1.0.0" has "^1.0.3" has-property-descriptors "^1.0.0" @@ -4671,9 +4681,9 @@ es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19 is-shared-array-buffer "^1.0.2" is-string "^1.0.7" is-weakref "^1.0.2" - object-inspect "^1.12.0" + object-inspect "^1.12.2" object-keys "^1.1.1" - object.assign "^4.1.2" + object.assign "^4.1.4" regexp.prototype.flags "^1.4.3" string.prototype.trimend "^1.0.5" string.prototype.trimstart "^1.0.5" @@ -4762,12 +4772,11 @@ eslint-import-resolver-typescript@^2.4.0: tsconfig-paths "^3.14.1" eslint-module-utils@^2.7.3: - version "2.7.3" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" - integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ== + version "2.7.4" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" + integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== dependencies: debug "^3.2.7" - find-up "^2.1.0" eslint-plugin-import@^2.23.4: version "2.26.0" @@ -5009,9 +5018,9 @@ expo-modules-autolinking@^0.0.3: fs-extra "^9.1.0" expo-random@*: - version "12.2.0" - resolved "https://registry.yarnpkg.com/expo-random/-/expo-random-12.2.0.tgz#a3c8a9ce84ef2c85900131d96eea6c7123285482" - integrity sha512-SihCGLmDyDOALzBN8XXpz2hCw0RSx9c4/rvjcS4Bfqhw6luHjL2rHNTLrFYrPrPRmG1jHM6dXXJe/Zm8jdu+2g== + version "12.3.0" + resolved "https://registry.yarnpkg.com/expo-random/-/expo-random-12.3.0.tgz#4a45bcb14e285a4a9161e4a5dc82ff6c3fc2ac0c" + integrity sha512-q+AsTfGNT+Q+fb2sRrYtRkI3g5tV4H0kuYXM186aueILGO/vLn/YYFa7xFZj1IZ8LJZg2h96JDPDpsqHfRG2mQ== dependencies: base64-js "^1.3.0" @@ -5121,9 +5130,9 @@ fast-diff@^1.1.2: integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== fast-glob@^3.2.5, fast-glob@^3.2.9: - version "3.2.11" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" - integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + version "3.2.12" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -5142,9 +5151,9 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-text-encoding@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz#bf1898ad800282a4e53c0ea9690704dd26e4298e" - integrity sha512-x6lDDm/tBAzX9kmsPcZsNbvDs3Zey3+scsxaZElS8xWLgUMAg/oFLeewfUz0mu1CblHhhsu15jGkraldkFh8KQ== + version "1.0.6" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867" + integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w== fastq@^1.6.0: version "1.13.0" @@ -5265,7 +5274,7 @@ find-replace@^3.0.0: dependencies: array-back "^3.0.1" -find-up@^2.0.0, find-up@^2.1.0: +find-up@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== @@ -5304,14 +5313,14 @@ flat-cache@^3.0.4: rimraf "^3.0.2" flatted@^3.1.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.6.tgz#022e9218c637f9f3fc9c35ab9c9193f05add60b2" - integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ== + version "3.2.7" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== flow-parser@0.*: - version "0.183.1" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.183.1.tgz#633387855028cbeb38d65ed0a0d64729e1599a3b" - integrity sha512-xBnvBk8D7aBY7gAilyjjGaNJe+9PGU6I/D2g6lGkkKyl4dW8nzn2eAc7Sc7RNRRr2NNYwpgHOOxBTjJKdKOXcA== + version "0.186.0" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.186.0.tgz#ef6f4c7a3d8eb29fdd96e1d1f651b7ccb210f8e9" + integrity sha512-QaPJczRxNc/yvp3pawws439VZ/vHGq+i1/mZ3bEdSaRy8scPgZgiWklSB6jN7y5NR9sfgL4GGIiBcMXTj3Opqg== flow-parser@^0.121.0: version "0.121.0" @@ -5493,10 +5502,10 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" - integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== dependencies: function-bind "^1.1.1" has "^1.0.3" @@ -5722,7 +5731,7 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.1.1" -has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: +has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== @@ -6099,9 +6108,9 @@ is-buffer@^1.1.5: integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-callable@^1.1.4, is-callable@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" - integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + version "1.2.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.5.tgz#6123e0b1fef5d7591514b371bb018204892f1a2b" + integrity sha512-ZIWRujF6MvYGkEuHMYtFRkL2wAtFw89EHfKlXrkPkjQZZRWeh9L1q3SV13NIfHnqxugjLvAOkEHx9mb1zcMnEw== is-ci@^2.0.0: version "2.0.0" @@ -6111,9 +6120,9 @@ is-ci@^2.0.0: ci-info "^2.0.0" is-core-module@^2.5.0, is-core-module@^2.8.1, is-core-module@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" - integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== + version "2.10.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" + integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== dependencies: has "^1.0.3" @@ -7242,9 +7251,9 @@ libnpmpublish@^4.0.0: ssri "^8.0.1" libphonenumber-js@^1.9.7: - version "1.10.11" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.11.tgz#0078756857bcc5c9dfe097123c6e04ea930e309b" - integrity sha512-ehoihx4HpRXO6FH/uJ0EnaEV4dVU+FDny+jv0S6k9JPyPsAIr0eXDAFvGRMBKE1daCtyHAaFSKCiuCxrOjVAzQ== + version "1.10.13" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.13.tgz#0b5833c7fdbf671140530d83531c6753f7e0ea3c" + integrity sha512-b74iyWmwb4GprAUPjPkJ11GTC7KX4Pd3onpJfKxYyY8y9Rbb4ERY47LvCMEDM09WD3thiLDMXtkfDK/AX+zT7Q== lines-and-columns@^1.1.6: version "1.2.4" @@ -8446,9 +8455,9 @@ number-is-nan@^1.0.0: integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== nwsapi@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.1.tgz#10a9f268fbf4c461249ebcfe38e359aa36e2577c" - integrity sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg== + version "2.2.2" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" + integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== oauth-sign@~0.9.0: version "0.9.0" @@ -8474,7 +8483,7 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.10.3, object-inspect@^1.12.0, object-inspect@^1.9.0: +object-inspect@^1.10.3, object-inspect@^1.12.2, object-inspect@^1.9.0: version "1.12.2" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== @@ -8491,14 +8500,14 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.0, object.assign@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== +object.assign@^4.1.0, object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" object-keys "^1.1.1" object.getownpropertydescriptors@^2.0.3: @@ -9005,9 +9014,9 @@ promise-retry@^2.0.1: retry "^0.12.0" promise@^8.0.3: - version "8.1.0" - resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" - integrity sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q== + version "8.2.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.2.0.tgz#a1f6280ab67457fbfc8aad2b198c9497e9e5c806" + integrity sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg== dependencies: asap "~2.0.6" @@ -9132,6 +9141,11 @@ query-string@^7.0.1: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -9572,6 +9586,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -9768,11 +9787,6 @@ scheduler@^0.20.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - semver@7.x, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: version "7.3.7" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" @@ -10093,9 +10107,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.11" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" - integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== + version "3.0.12" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz#69077835abe2710b65f03969898b6637b505a779" + integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA== split-on-first@^1.0.0: version "1.1.0" @@ -10354,9 +10368,9 @@ supports-color@^8.0.0: has-flag "^4.0.0" supports-hyperlinks@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" - integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + version "2.3.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== dependencies: has-flag "^4.0.0" supports-color "^7.0.0" @@ -10565,13 +10579,14 @@ toml@^3.0.0: integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== tough-cookie@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" - integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== + version "4.1.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" + integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== dependencies: psl "^1.1.33" punycode "^2.1.1" - universalify "^0.1.2" + universalify "^0.2.0" + url-parse "^1.5.3" tough-cookie@~2.5.0: version "2.5.0" @@ -10657,9 +10672,9 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0: integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== tslog@^3.2.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/tslog/-/tslog-3.3.3.tgz#751a469e0d36841bd7e03676c27e53e7ffe9bc3d" - integrity sha512-lGrkndwpAohZ9ntQpT+xtUw5k9YFV1DjsksiWQlBSf82TTqsSAWBARPRD9juI730r8o3Awpkjp2aXy9k+6vr+g== + version "3.3.4" + resolved "https://registry.yarnpkg.com/tslog/-/tslog-3.3.4.tgz#083197a908c97b3b714a0576b9dac293f223f368" + integrity sha512-N0HHuHE0e/o75ALfkioFObknHR5dVchUad4F0XyFf3gXJYB++DewEzwGI/uIOM216E5a43ovnRNEeQIq9qgm4Q== dependencies: source-map-support "^0.5.21" @@ -10787,9 +10802,9 @@ uglify-es@^3.1.9: source-map "~0.6.1" uglify-js@^3.1.4: - version "3.16.3" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.16.3.tgz#94c7a63337ee31227a18d03b8a3041c210fd1f1d" - integrity sha512-uVbFqx9vvLhQg0iBaau9Z75AxWJ8tqM9AV890dIZCLApF4rTcyHwmAvLeEdYRs+BzYWu8Iw81F79ah0EfTXbaw== + version "3.17.0" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.0.tgz#55bd6e9d19ce5eef0d5ad17cd1f587d85b180a85" + integrity sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg== uid-number@0.0.6: version "0.0.6" @@ -10868,11 +10883,16 @@ universal-user-agent@^6.0.0: resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== -universalify@^0.1.0, universalify@^0.1.2: +universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -10897,9 +10917,9 @@ upath@^2.0.1: integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== update-browserslist-db@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38" - integrity sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q== + version "1.0.8" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.8.tgz#2f0b711327668eee01bbecddcf4a7c7954a7f8e2" + integrity sha512-GHg7C4M7oJSJYW/ED/5QOJ7nL/E0lwTOBGsOorA7jqHr8ExUhPfwAotIAmdSw/LWv3SMLSNpzTAgeLG9zaZKTA== dependencies: escalade "^3.1.1" picocolors "^1.0.0" @@ -10916,6 +10936,14 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + use-subscription@^1.0.0: version "1.8.0" resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.8.0.tgz#f118938c29d263c2bce12fc5585d3fe694d4dbce" @@ -11057,12 +11085,12 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: defaults "^1.0.3" web-did-resolver@^2.0.8: - version "2.0.19" - resolved "https://registry.yarnpkg.com/web-did-resolver/-/web-did-resolver-2.0.19.tgz#25f11fd89f510b2650ce77f50baae496ae20d104" - integrity sha512-KRnLWTOApVAVvx20k5Fn2e4Fwhbo7cZbALruOv/lcW3Fr/1UTfGXFg0hnFYcscxk/hBrT+wBORoJf/VeQIOMSQ== + version "2.0.20" + resolved "https://registry.yarnpkg.com/web-did-resolver/-/web-did-resolver-2.0.20.tgz#22e053b0f8bc1f4ab03da05989ce934852b7623f" + integrity sha512-qGcrm01B+ytCZUYhxH0mGOk0Ldf67kXUXLsNth6F3sx3fhUKNSIE8D+MnMFRugQm7j87mDHqUTDLmW9c90g3nw== dependencies: cross-fetch "^3.1.5" - did-resolver "^3.2.2" + did-resolver "^4.0.0" webcrypto-core@^1.7.4: version "1.7.5" From 5a286b7ce68942eb754c47daebd630a8eb70e36f Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Thu, 22 Sep 2022 06:37:17 -0300 Subject: [PATCH 040/125] refactor(proofs)!: createRequest for connectionless proof request (#1034) Signed-off-by: Ariel Gentile --- packages/core/src/modules/proofs/ProofsApi.ts | 32 +++------------ .../src/modules/proofs/ProofsApiOptions.ts | 5 ++- .../proofs/models/ProofServiceOptions.ts | 5 --- .../tests/v1-connectionless-proofs.test.ts | 41 +++++++++++++------ .../tests/v2-connectionless-proofs.test.ts | 41 +++++++++++++------ 5 files changed, 65 insertions(+), 59 deletions(-) diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index 528a12e584..ff5fddaff7 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -3,7 +3,7 @@ import type { ProofService } from './ProofService' import type { AcceptPresentationOptions, AcceptProposalOptions, - OutOfBandRequestOptions, + CreateProofRequestOptions, ProposeProofOptions, RequestProofOptions, ServiceMap, @@ -15,7 +15,6 @@ import type { GetRequestedCredentialsForProofRequest, } from './models/ModuleOptions' import type { - CreateOutOfBandRequestOptions, CreatePresentationOptions, CreateProposalOptions, CreateRequestOptions, @@ -60,7 +59,7 @@ export interface ProofsApi // out of band - createOutOfBandRequest(options: OutOfBandRequestOptions): Promise<{ + createRequest(options: CreateProofRequestOptions): Promise<{ message: AgentMessage proofRecord: ProofRecord }> @@ -333,39 +332,20 @@ export class ProofsApi< } } - public async createOutOfBandRequest(options: OutOfBandRequestOptions): Promise<{ + public async createRequest(options: CreateProofRequestOptions): Promise<{ message: AgentMessage proofRecord: ProofRecord }> { const service = this.getService(options.protocolVersion) - const createProofRequest: CreateOutOfBandRequestOptions = { + const createProofRequest: CreateRequestOptions = { proofFormats: options.proofFormats, autoAcceptProof: options.autoAcceptProof, comment: options.comment, + parentThreadId: options.parentThreadId, } - const { message, proofRecord } = await service.createRequest(this.agentContext, createProofRequest) - - // Create and set ~service decorator - - const routing = await this.routingService.getRouting(this.agentContext) - message.service = new ServiceDecorator({ - serviceEndpoint: routing.endpoints[0], - recipientKeys: [routing.recipientKey.publicKeyBase58], - routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), - }) - // Save ~service decorator to record (to remember our verkey) - - await service.saveOrUpdatePresentationMessage(this.agentContext, { - message, - proofRecord: proofRecord, - role: DidCommMessageRole.Sender, - }) - - await service.update(this.agentContext, proofRecord) - - return { proofRecord, message } + return await service.createRequest(this.agentContext, createProofRequest) } public async declineRequest(proofRecordId: string): Promise { diff --git a/packages/core/src/modules/proofs/ProofsApiOptions.ts b/packages/core/src/modules/proofs/ProofsApiOptions.ts index 798a56c30c..1e820b75d7 100644 --- a/packages/core/src/modules/proofs/ProofsApiOptions.ts +++ b/packages/core/src/modules/proofs/ProofsApiOptions.ts @@ -63,12 +63,13 @@ export interface RequestProofOptions< parentThreadId?: string } -export interface OutOfBandRequestOptions< +export interface CreateProofRequestOptions< PFs extends ProofFormat[] = ProofFormat[], PSs extends ProofService[] = ProofService[] > { protocolVersion: ProtocolVersionType - proofFormats: ProofFormatPayload + proofFormats: ProofFormatPayload comment?: string autoAcceptProof?: AutoAcceptProof + parentThreadId?: string } diff --git a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts index 6500985e3e..b151253d93 100644 --- a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts +++ b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts @@ -28,11 +28,6 @@ export interface CreateRequestAsResponseOptions exten proofFormats: ProofFormatPayload } -// ----- Out Of Band Proof ----- // -export interface CreateOutOfBandRequestOptions extends BaseOptions { - proofFormats: ProofFormatPayload -} - export interface CreateRequestOptions extends BaseOptions { connectionRecord?: ConnectionRecord proofFormats: ProofFormatPayload diff --git a/packages/core/tests/v1-connectionless-proofs.test.ts b/packages/core/tests/v1-connectionless-proofs.test.ts index 684584a97a..447304229f 100644 --- a/packages/core/tests/v1-connectionless-proofs.test.ts +++ b/packages/core/tests/v1-connectionless-proofs.test.ts @@ -1,6 +1,6 @@ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { ProofStateChangedEvent } from '../src/modules/proofs' -import type { OutOfBandRequestOptions } from '../src/modules/proofs/ProofsApiOptions' +import type { CreateProofRequestOptions } from '../src/modules/proofs/ProofsApiOptions' import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' import type { V1ProofService } from '../src/modules/proofs/protocol/v1' @@ -81,7 +81,7 @@ describe('Present Proof', () => { }), } - const outOfBandRequestOptions: OutOfBandRequestOptions<[IndyProofFormat], [V1ProofService]> = { + const outOfBandRequestOptions: CreateProofRequestOptions<[IndyProofFormat], [V1ProofService]> = { protocolVersion: ProofProtocolVersion.V1, proofFormats: { indy: { @@ -99,11 +99,14 @@ describe('Present Proof', () => { }) // eslint-disable-next-line prefer-const - let { proofRecord: faberProofRecord, message } = await faberAgent.proofs.createOutOfBandRequest( - outOfBandRequestOptions - ) + let { proofRecord: faberProofRecord, message } = await faberAgent.proofs.createRequest(outOfBandRequestOptions) - await aliceAgent.receiveMessage(message.toJSON()) + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofRecord.id, + message, + domain: 'https://a-domain.com', + }) + await aliceAgent.receiveMessage(requestMessage.toJSON()) testLogger.test('Alice waits for presentation request from Faber') let aliceProofRecord = await aliceProofRecordPromise @@ -179,7 +182,7 @@ describe('Present Proof', () => { }), } - const outOfBandRequestOptions: OutOfBandRequestOptions<[IndyProofFormat], [V1ProofService]> = { + const outOfBandRequestOptions: CreateProofRequestOptions<[IndyProofFormat], [V1ProofService]> = { protocolVersion: ProofProtocolVersion.V1, proofFormats: { indy: { @@ -202,9 +205,15 @@ describe('Present Proof', () => { }) // eslint-disable-next-line prefer-const - let { message } = await faberAgent.proofs.createOutOfBandRequest(outOfBandRequestOptions) + let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest(outOfBandRequestOptions) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofRecord.id, + message, + domain: 'https://a-domain.com', + }) - await aliceAgent.receiveMessage(message.toJSON()) + await aliceAgent.receiveMessage(requestMessage.toJSON()) await aliceProofRecordPromise @@ -339,7 +348,7 @@ describe('Present Proof', () => { }), } - const outOfBandRequestOptions: OutOfBandRequestOptions<[IndyProofFormat], [V1ProofService]> = { + const outOfBandRequestOptions: CreateProofRequestOptions<[IndyProofFormat], [V1ProofService]> = { protocolVersion: ProofProtocolVersion.V1, proofFormats: { indy: { @@ -362,14 +371,20 @@ describe('Present Proof', () => { }) // eslint-disable-next-line prefer-const - let { message } = await faberAgent.proofs.createOutOfBandRequest(outOfBandRequestOptions) + let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest(outOfBandRequestOptions) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofRecord.id, + message, + domain: 'https://a-domain.com', + }) const mediationRecord = await faberAgent.mediationRecipient.findDefaultMediator() if (!mediationRecord) { throw new Error('Faber agent has no default mediator') } - expect(message).toMatchObject({ + expect(requestMessage).toMatchObject({ service: { recipientKeys: [expect.any(String)], routingKeys: mediationRecord.routingKeys, @@ -377,7 +392,7 @@ describe('Present Proof', () => { }, }) - await aliceAgent.receiveMessage(message.toJSON()) + await aliceAgent.receiveMessage(requestMessage.toJSON()) await aliceProofRecordPromise diff --git a/packages/core/tests/v2-connectionless-proofs.test.ts b/packages/core/tests/v2-connectionless-proofs.test.ts index 0acb20850f..39cd103bd9 100644 --- a/packages/core/tests/v2-connectionless-proofs.test.ts +++ b/packages/core/tests/v2-connectionless-proofs.test.ts @@ -1,6 +1,6 @@ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { ProofStateChangedEvent } from '../src/modules/proofs' -import type { OutOfBandRequestOptions } from '../src/modules/proofs/ProofsApiOptions' +import type { CreateProofRequestOptions } from '../src/modules/proofs/ProofsApiOptions' import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' import type { V2ProofService } from '../src/modules/proofs/protocol/v2' @@ -79,7 +79,7 @@ describe('Present Proof', () => { }), } - const outOfBandRequestOptions: OutOfBandRequestOptions<[IndyProofFormat], [V2ProofService]> = { + const outOfBandRequestOptions: CreateProofRequestOptions<[IndyProofFormat], [V2ProofService]> = { protocolVersion: ProofProtocolVersion.V2, proofFormats: { indy: { @@ -97,11 +97,15 @@ describe('Present Proof', () => { }) // eslint-disable-next-line prefer-const - let { proofRecord: faberProofRecord, message } = await faberAgent.proofs.createOutOfBandRequest( - outOfBandRequestOptions - ) + let { proofRecord: faberProofRecord, message } = await faberAgent.proofs.createRequest(outOfBandRequestOptions) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofRecord.id, + message, + domain: 'https://a-domain.com', + }) - await aliceAgent.receiveMessage(message.toJSON()) + await aliceAgent.receiveMessage(requestMessage.toJSON()) testLogger.test('Alice waits for presentation request from Faber') let aliceProofRecord = await aliceProofRecordPromise @@ -178,7 +182,7 @@ describe('Present Proof', () => { }), } - const outOfBandRequestOptions: OutOfBandRequestOptions<[IndyProofFormat], [V2ProofService]> = { + const outOfBandRequestOptions: CreateProofRequestOptions<[IndyProofFormat], [V2ProofService]> = { protocolVersion: ProofProtocolVersion.V2, proofFormats: { indy: { @@ -201,9 +205,14 @@ describe('Present Proof', () => { }) // eslint-disable-next-line prefer-const - let { message } = await faberAgent.proofs.createOutOfBandRequest(outOfBandRequestOptions) + let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest(outOfBandRequestOptions) - await aliceAgent.receiveMessage(message.toJSON()) + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofRecord.id, + message, + domain: 'https://a-domain.com', + }) + await aliceAgent.receiveMessage(requestMessage.toJSON()) await aliceProofRecordPromise @@ -339,7 +348,7 @@ describe('Present Proof', () => { }), } - const outOfBandRequestOptions: OutOfBandRequestOptions<[IndyProofFormat], [V2ProofService]> = { + const outOfBandRequestOptions: CreateProofRequestOptions<[IndyProofFormat], [V2ProofService]> = { protocolVersion: ProofProtocolVersion.V2, proofFormats: { indy: { @@ -362,14 +371,20 @@ describe('Present Proof', () => { }) // eslint-disable-next-line prefer-const - let { message } = await faberAgent.proofs.createOutOfBandRequest(outOfBandRequestOptions) + let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest(outOfBandRequestOptions) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofRecord.id, + message, + domain: 'https://a-domain.com', + }) const mediationRecord = await faberAgent.mediationRecipient.findDefaultMediator() if (!mediationRecord) { throw new Error('Faber agent has no default mediator') } - expect(message).toMatchObject({ + expect(requestMessage).toMatchObject({ service: { recipientKeys: [expect.any(String)], routingKeys: mediationRecord.routingKeys, @@ -377,7 +392,7 @@ describe('Present Proof', () => { }, }) - await aliceAgent.receiveMessage(message.toJSON()) + await aliceAgent.receiveMessage(requestMessage.toJSON()) await aliceProofRecordPromise From 5e9e0fcc7f13b8a27e35761464c8fd970c17d28c Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 22 Sep 2022 12:07:29 +0200 Subject: [PATCH 041/125] feat(proofs): proofs module migration script for 0.3.0 (#1020) Signed-off-by: Timo Glastra --- .../modules/proofs/repository/ProofRecord.ts | 5 +- .../storage/migration/StorageUpdateService.ts | 14 +- .../src/storage/migration/UpdateAssistant.ts | 58 +- .../storage/migration/__tests__/0.1.test.ts | 95 +- .../storage/migration/__tests__/0.2.test.ts | 144 +++ .../__tests__/UpdateAssistant.test.ts | 22 +- .../__fixtures__/alice-4-proofs-0.2.json | 235 +++++ .../__tests__/__snapshots__/0.1.test.ts.snap | 28 - .../__tests__/__snapshots__/0.2.test.ts.snap | 953 ++++++++++++++++++ .../core/src/storage/migration/updates.ts | 18 +- .../updates/0.2-0.3/__tests__/proof.test.ts | 310 ++++++ .../migration/updates/0.2-0.3/index.ts | 7 + .../migration/updates/0.2-0.3/proof.ts | 162 +++ .../core/src/utils/__tests__/version.test.ts | 29 +- packages/core/src/utils/version.ts | 4 + 15 files changed, 1996 insertions(+), 88 deletions(-) create mode 100644 packages/core/src/storage/migration/__tests__/0.2.test.ts create mode 100644 packages/core/src/storage/migration/__tests__/__fixtures__/alice-4-proofs-0.2.json create mode 100644 packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap create mode 100644 packages/core/src/storage/migration/updates/0.2-0.3/__tests__/proof.test.ts create mode 100644 packages/core/src/storage/migration/updates/0.2-0.3/index.ts create mode 100644 packages/core/src/storage/migration/updates/0.2-0.3/proof.ts diff --git a/packages/core/src/modules/proofs/repository/ProofRecord.ts b/packages/core/src/modules/proofs/repository/ProofRecord.ts index bf03ad2bf6..54cb12538e 100644 --- a/packages/core/src/modules/proofs/repository/ProofRecord.ts +++ b/packages/core/src/modules/proofs/repository/ProofRecord.ts @@ -1,6 +1,5 @@ import type { TagsBase } from '../../../storage/BaseRecord' import type { AutoAcceptProof } from '../models/ProofAutoAcceptType' -import type { ProofProtocolVersion } from '../models/ProofProtocolVersion' import type { ProofState } from '../models/ProofState' import { AriesFrameworkError } from '../../../error' @@ -10,7 +9,7 @@ import { uuid } from '../../../utils/uuid' export interface ProofRecordProps { id?: string createdAt?: Date - protocolVersion: ProofProtocolVersion + protocolVersion: string isVerified?: boolean state: ProofState connectionId?: string @@ -34,7 +33,7 @@ export type DefaultProofTags = { export class ProofRecord extends BaseRecord { public connectionId?: string public threadId!: string - public protocolVersion!: ProofProtocolVersion + public protocolVersion!: string public parentThreadId?: string public isVerified?: boolean public state!: ProofState diff --git a/packages/core/src/storage/migration/StorageUpdateService.ts b/packages/core/src/storage/migration/StorageUpdateService.ts index 2c8991d319..eef16af7ff 100644 --- a/packages/core/src/storage/migration/StorageUpdateService.ts +++ b/packages/core/src/storage/migration/StorageUpdateService.ts @@ -1,9 +1,11 @@ import type { AgentContext } from '../../agent' import type { VersionString } from '../../utils/version' +import type { UpdateToVersion } from './updates' import { InjectionSymbols } from '../../constants' import { Logger } from '../../logger' import { injectable, inject } from '../../plugins' +import { isFirstVersionEqualToSecond, isFirstVersionHigherThanSecond, parseVersionString } from '../../utils/version' import { StorageVersionRecord } from './repository/StorageVersionRecord' import { StorageVersionRepository } from './repository/StorageVersionRepository' @@ -24,10 +26,14 @@ export class StorageUpdateService { this.storageVersionRepository = storageVersionRepository } - public async isUpToDate(agentContext: AgentContext) { - const currentStorageVersion = await this.getCurrentStorageVersion(agentContext) + public async isUpToDate(agentContext: AgentContext, updateToVersion?: UpdateToVersion) { + const currentStorageVersion = parseVersionString(await this.getCurrentStorageVersion(agentContext)) - const isUpToDate = CURRENT_FRAMEWORK_STORAGE_VERSION === currentStorageVersion + const compareToVersion = parseVersionString(updateToVersion ?? CURRENT_FRAMEWORK_STORAGE_VERSION) + + const isUpToDate = + isFirstVersionEqualToSecond(currentStorageVersion, compareToVersion) || + isFirstVersionHigherThanSecond(currentStorageVersion, compareToVersion) return isUpToDate } @@ -65,7 +71,7 @@ export class StorageUpdateService { * Retrieve the update record, creating it if it doesn't exist already. * * The storageVersion will be set to the INITIAL_STORAGE_VERSION if it doesn't exist yet, - * as we can assume the wallet was created before the udpate record existed + * as we can assume the wallet was created before the update record existed */ public async getStorageVersionRecord(agentContext: AgentContext) { let storageVersionRecord = await this.storageVersionRepository.findById( diff --git a/packages/core/src/storage/migration/UpdateAssistant.ts b/packages/core/src/storage/migration/UpdateAssistant.ts index 8cf30b461b..b5ce6f4e89 100644 --- a/packages/core/src/storage/migration/UpdateAssistant.ts +++ b/packages/core/src/storage/migration/UpdateAssistant.ts @@ -1,11 +1,11 @@ import type { BaseAgent } from '../../agent/BaseAgent' import type { FileSystem } from '../FileSystem' -import type { UpdateConfig } from './updates' +import type { UpdateConfig, UpdateToVersion } from './updates' import { InjectionSymbols } from '../../constants' import { AriesFrameworkError } from '../../error' import { isIndyError } from '../../utils/indyError' -import { isFirstVersionHigherThanSecond, parseVersionString } from '../../utils/version' +import { isFirstVersionEqualToSecond, isFirstVersionHigherThanSecond, parseVersionString } from '../../utils/version' import { WalletError } from '../../wallet/error/WalletError' import { StorageUpdateService } from './StorageUpdateService' @@ -43,8 +43,8 @@ export class UpdateAssistant { } } - public async isUpToDate() { - return this.storageUpdateService.isUpToDate(this.agent.context) + public async isUpToDate(updateToVersion?: UpdateToVersion) { + return this.storageUpdateService.isUpToDate(this.agent.context, updateToVersion) } public async getCurrentAgentStorageVersion() { @@ -55,18 +55,34 @@ export class UpdateAssistant { return CURRENT_FRAMEWORK_STORAGE_VERSION } - public async getNeededUpdates() { + public async getNeededUpdates(toVersion?: UpdateToVersion) { const currentStorageVersion = parseVersionString( await this.storageUpdateService.getCurrentStorageVersion(this.agent.context) ) + const parsedToVersion = toVersion ? parseVersionString(toVersion) : undefined + + // If the current storage version is higher or equal to the toVersion, we can't update, so return empty array + if ( + parsedToVersion && + (isFirstVersionHigherThanSecond(currentStorageVersion, parsedToVersion) || + isFirstVersionEqualToSecond(currentStorageVersion, parsedToVersion)) + ) { + return [] + } + // Filter updates. We don't want older updates we already applied // or aren't needed because the wallet was created after the update script was made const neededUpdates = supportedUpdates.filter((update) => { - const toVersion = parseVersionString(update.toVersion) + const updateToVersion = parseVersionString(update.toVersion) + + // If the update toVersion is higher than the wanted toVersion, we skip the update + if (parsedToVersion && isFirstVersionHigherThanSecond(updateToVersion, parsedToVersion)) { + return false + } // if an update toVersion is higher than currentStorageVersion we want to to include the update - return isFirstVersionHigherThanSecond(toVersion, currentStorageVersion) + return isFirstVersionHigherThanSecond(updateToVersion, currentStorageVersion) }) // The current storage version is too old to update @@ -79,15 +95,38 @@ export class UpdateAssistant { ) } + const lastUpdateToVersion = neededUpdates.length > 0 ? neededUpdates[neededUpdates.length - 1].toVersion : undefined + if (toVersion && lastUpdateToVersion && lastUpdateToVersion !== toVersion) { + throw new AriesFrameworkError( + `No update found for toVersion ${toVersion}. Make sure the toVersion is a valid version you can update to` + ) + } + return neededUpdates } - public async update() { + public async update(updateToVersion?: UpdateToVersion) { const updateIdentifier = Date.now().toString() try { this.agent.config.logger.info(`Starting update of agent storage with updateIdentifier ${updateIdentifier}`) - const neededUpdates = await this.getNeededUpdates() + const neededUpdates = await this.getNeededUpdates(updateToVersion) + + const currentStorageVersion = parseVersionString( + await this.storageUpdateService.getCurrentStorageVersion(this.agent.context) + ) + const parsedToVersion = updateToVersion ? parseVersionString(updateToVersion) : undefined + + // If the current storage version is higher or equal to the toVersion, we can't update. + if ( + parsedToVersion && + (isFirstVersionHigherThanSecond(currentStorageVersion, parsedToVersion) || + isFirstVersionEqualToSecond(currentStorageVersion, parsedToVersion)) + ) { + throw new StorageUpdateError( + `Can't update to version ${updateToVersion} because it is lower or equal to the current agent storage version ${currentStorageVersion[0]}.${currentStorageVersion[1]}}` + ) + } if (neededUpdates.length == 0) { this.agent.config.logger.info('No update needed. Agent storage is up to date.') @@ -96,6 +135,7 @@ export class UpdateAssistant { const fromVersion = neededUpdates[0].fromVersion const toVersion = neededUpdates[neededUpdates.length - 1].toVersion + this.agent.config.logger.info( `Starting update process. Total of ${neededUpdates.length} update(s) will be applied to update the agent storage from version ${fromVersion} to version ${toVersion}` ) diff --git a/packages/core/src/storage/migration/__tests__/0.1.test.ts b/packages/core/src/storage/migration/__tests__/0.1.test.ts index fc2b3fb047..139b73024a 100644 --- a/packages/core/src/storage/migration/__tests__/0.1.test.ts +++ b/packages/core/src/storage/migration/__tests__/0.1.test.ts @@ -1,5 +1,4 @@ import type { FileSystem } from '../../../../src' -import type { DidInfo, DidConfig } from '../../../wallet' import type { V0_1ToV0_2UpdateConfig } from '../updates/0.1-0.2' import { unlinkSync, readFileSync } from 'fs' @@ -11,7 +10,6 @@ import { agentDependencies as dependencies } from '../../../../tests/helpers' import { InjectionSymbols } from '../../../constants' import { DependencyManager } from '../../../plugins' import * as uuid from '../../../utils/uuid' -import { IndyWallet } from '../../../wallet/IndyWallet' import { UpdateAssistant } from '../UpdateAssistant' const backupDate = new Date('2022-01-21T22:50:20.522Z') @@ -31,19 +29,6 @@ const mediationRoleUpdateStrategies: V0_1ToV0_2UpdateConfig['mediationRoleUpdate ] describe('UpdateAssistant | v0.1 - v0.2', () => { - let createDidSpy: jest.SpyInstance, [didConfig?: DidConfig | undefined]> - - beforeAll(async () => { - // We need to mock did generation to create a consistent mediator routing record across sessions - createDidSpy = jest - .spyOn(IndyWallet.prototype, 'createDid') - .mockImplementation(async () => ({ did: 'mock-did', verkey: 'ocxwFbXouLkzuTCyyjFg1bPGK3nM6aPv1pZ6fn5RNgD' })) - }) - - afterAll(async () => { - createDidSpy.mockReset() - }) - it(`should correctly update the role in the mediation record`, async () => { const aliceMediationRecordsString = readFileSync( path.join(__dirname, '__fixtures__/alice-4-mediators-0.1.json'), @@ -77,7 +62,7 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { // is opened as an existing wallet instead of a new wallet storageService.records = JSON.parse(aliceMediationRecordsString) - expect(await updateAssistant.getNeededUpdates()).toEqual([ + expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([ { fromVersion: '0.1', toVersion: '0.2', @@ -85,10 +70,13 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { }, ]) - await updateAssistant.update() + await updateAssistant.update('0.2') - expect(await updateAssistant.isUpToDate()).toBe(true) - expect(await updateAssistant.getNeededUpdates()).toEqual([]) + expect(await updateAssistant.isUpToDate('0.2')).toBe(true) + expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([]) + + // MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here + delete storageService.records.MEDIATOR_ROUTING_RECORD expect(storageService.records).toMatchSnapshot(mediationRoleUpdateStrategy) // Need to remove backupFiles after each run so we don't get IOErrors @@ -136,8 +124,8 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { // is opened as an existing wallet instead of a new wallet storageService.records = JSON.parse(aliceCredentialRecordsString) - expect(await updateAssistant.isUpToDate()).toBe(false) - expect(await updateAssistant.getNeededUpdates()).toEqual([ + expect(await updateAssistant.isUpToDate('0.2')).toBe(false) + expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([ { fromVersion: '0.1', toVersion: '0.2', @@ -145,10 +133,13 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { }, ]) - await updateAssistant.update() + await updateAssistant.update('0.2') + + expect(await updateAssistant.isUpToDate('0.2')).toBe(true) + expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([]) - expect(await updateAssistant.isUpToDate()).toBe(true) - expect(await updateAssistant.getNeededUpdates()).toEqual([]) + // MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here + delete storageService.records.MEDIATOR_ROUTING_RECORD expect(storageService.records).toMatchSnapshot() // Need to remove backupFiles after each run so we don't get IOErrors @@ -185,18 +176,34 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) - // We need to manually initialize the wallet as we're using the in memory wallet service - // When we call agent.initialize() it will create the wallet and store the current framework - // version in the in memory storage service. We need to manually set the records between initializing - // the wallet and calling agent.initialize() - await agent.wallet.initialize(walletConfig) + const updateAssistant = new UpdateAssistant(agent, { + v0_1ToV0_2: { + mediationRoleUpdateStrategy: 'doNotChange', + }, + }) + + await updateAssistant.initialize() // Set storage after initialization. This mimics as if this wallet // is opened as an existing wallet instead of a new wallet storageService.records = JSON.parse(aliceCredentialRecordsString) - await agent.initialize() + expect(await updateAssistant.isUpToDate('0.2')).toBe(false) + expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([ + { + fromVersion: '0.1', + toVersion: '0.2', + doUpdate: expect.any(Function), + }, + ]) + + await updateAssistant.update('0.2') + + expect(await updateAssistant.isUpToDate('0.2')).toBe(true) + expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([]) + // MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here + delete storageService.records.MEDIATOR_ROUTING_RECORD expect(storageService.records).toMatchSnapshot() // Need to remove backupFiles after each run so we don't get IOErrors @@ -237,18 +244,34 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) - // We need to manually initialize the wallet as we're using the in memory wallet service - // When we call agent.initialize() it will create the wallet and store the current framework - // version in the in memory storage service. We need to manually set the records between initializing - // the wallet and calling agent.initialize() - await agent.wallet.initialize(walletConfig) + const updateAssistant = new UpdateAssistant(agent, { + v0_1ToV0_2: { + mediationRoleUpdateStrategy: 'doNotChange', + }, + }) + + await updateAssistant.initialize() // Set storage after initialization. This mimics as if this wallet // is opened as an existing wallet instead of a new wallet storageService.records = JSON.parse(aliceConnectionRecordsString) - await agent.initialize() + expect(await updateAssistant.isUpToDate('0.2')).toBe(false) + expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([ + { + fromVersion: '0.1', + toVersion: '0.2', + doUpdate: expect.any(Function), + }, + ]) + + await updateAssistant.update('0.2') + + expect(await updateAssistant.isUpToDate('0.2')).toBe(true) + expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([]) + // MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here + delete storageService.records.MEDIATOR_ROUTING_RECORD expect(storageService.records).toMatchSnapshot() // Need to remove backupFiles after each run so we don't get IOErrors diff --git a/packages/core/src/storage/migration/__tests__/0.2.test.ts b/packages/core/src/storage/migration/__tests__/0.2.test.ts new file mode 100644 index 0000000000..3003a203ab --- /dev/null +++ b/packages/core/src/storage/migration/__tests__/0.2.test.ts @@ -0,0 +1,144 @@ +import type { FileSystem } from '../../../storage/FileSystem' + +import { unlinkSync, readFileSync } from 'fs' +import path from 'path' + +import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' +import { Agent } from '../../../../src' +import { agentDependencies } from '../../../../tests/helpers' +import { InjectionSymbols } from '../../../constants' +import { DependencyManager } from '../../../plugins' +import * as uuid from '../../../utils/uuid' +import { UpdateAssistant } from '../UpdateAssistant' + +const backupDate = new Date('2022-01-21T22:50:20.522Z') +jest.useFakeTimers().setSystemTime(backupDate) +const backupIdentifier = backupDate.getTime() + +const walletConfig = { + id: `Wallet: 0.2 Update`, + key: `Key: 0.2 Update`, +} + +describe('UpdateAssistant | v0.2 - v0.3', () => { + it(`should correctly update proof records and create didcomm records`, async () => { + // We need to mock the uuid generation to make sure we generate consistent uuids for the new records created. + let uuidCounter = 1 + const uuidSpy = jest.spyOn(uuid, 'uuid').mockImplementation(() => `${uuidCounter++}-4e4f-41d9-94c4-f49351b811f1`) + + const aliceCredentialRecordsString = readFileSync( + path.join(__dirname, '__fixtures__/alice-4-proofs-0.2.json'), + 'utf8' + ) + + const dependencyManager = new DependencyManager() + const storageService = new InMemoryStorageService() + dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + + const agent = new Agent( + { + config: { + label: 'Test Agent', + walletConfig, + }, + dependencies: agentDependencies, + }, + dependencyManager + ) + + const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) + + const updateAssistant = new UpdateAssistant(agent, { + v0_1ToV0_2: { + mediationRoleUpdateStrategy: 'doNotChange', + }, + }) + + await updateAssistant.initialize() + + // Set storage after initialization. This mimics as if this wallet + // is opened as an existing wallet instead of a new wallet + storageService.records = JSON.parse(aliceCredentialRecordsString) + + expect(await updateAssistant.isUpToDate()).toBe(false) + expect(await updateAssistant.getNeededUpdates()).toEqual([ + { + fromVersion: '0.2', + toVersion: '0.3', + doUpdate: expect.any(Function), + }, + ]) + + await updateAssistant.update() + + expect(await updateAssistant.isUpToDate()).toBe(true) + expect(await updateAssistant.getNeededUpdates()).toEqual([]) + + // MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here + delete storageService.records.MEDIATOR_ROUTING_RECORD + expect(storageService.records).toMatchSnapshot() + + // Need to remove backupFiles after each run so we don't get IOErrors + const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` + unlinkSync(backupPath) + + await agent.shutdown() + await agent.wallet.delete() + + uuidSpy.mockReset() + }) + + it(`should correctly update the proofs records and create didcomm records with auto update`, async () => { + // We need to mock the uuid generation to make sure we generate consistent uuids for the new records created. + let uuidCounter = 1 + const uuidSpy = jest.spyOn(uuid, 'uuid').mockImplementation(() => `${uuidCounter++}-4e4f-41d9-94c4-f49351b811f1`) + + const aliceCredentialRecordsString = readFileSync( + path.join(__dirname, '__fixtures__/alice-4-proofs-0.2.json'), + 'utf8' + ) + + const dependencyManager = new DependencyManager() + const storageService = new InMemoryStorageService() + dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + + const agent = new Agent( + { + config: { + label: 'Test Agent', + walletConfig, + autoUpdateStorageOnStartup: true, + }, + dependencies: agentDependencies, + }, + dependencyManager + ) + + const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) + + // We need to manually initialize the wallet as we're using the in memory wallet service + // When we call agent.initialize() it will create the wallet and store the current framework + // version in the in memory storage service. We need to manually set the records between initializing + // the wallet and calling agent.initialize() + await agent.wallet.initialize(walletConfig) + + // Set storage after initialization. This mimics as if this wallet + // is opened as an existing wallet instead of a new wallet + storageService.records = JSON.parse(aliceCredentialRecordsString) + + await agent.initialize() + + // MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here + delete storageService.records.MEDIATOR_ROUTING_RECORD + expect(storageService.records).toMatchSnapshot() + + // Need to remove backupFiles after each run so we don't get IOErrors + const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` + unlinkSync(backupPath) + + await agent.shutdown() + await agent.wallet.delete() + + uuidSpy.mockReset() + }) +}) diff --git a/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts b/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts index ec4545d05e..d1677a5648 100644 --- a/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts +++ b/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts @@ -6,6 +6,7 @@ import { Agent } from '../../../agent/Agent' import { InjectionSymbols } from '../../../constants' import { DependencyManager } from '../../../plugins' import { UpdateAssistant } from '../UpdateAssistant' +import { CURRENT_FRAMEWORK_STORAGE_VERSION } from '../updates' const agentOptions = getAgentOptions('UpdateAssistant') @@ -54,17 +55,30 @@ describe('UpdateAssistant', () => { it('should return true when a new wallet is created', async () => { expect(await updateAssistant.isUpToDate()).toBe(true) }) + + it('should return true for a lower version than current storage', async () => { + expect(await updateAssistant.isUpToDate('0.2')).toBe(true) + }) + + it('should return true for current agent storage version', async () => { + expect(await updateAssistant.isUpToDate('0.3')).toBe(true) + }) + + it('should return false for a higher version than current storage', async () => { + // @ts-expect-error isUpToDate only allows existing versions to be passed, 100.100 is not a valid version (yet) + expect(await updateAssistant.isUpToDate('100.100')).toBe(false) + }) }) describe('UpdateAssistant.frameworkStorageVersion', () => { - it('should return 0.2', async () => { - expect(UpdateAssistant.frameworkStorageVersion).toBe('0.2') + it(`should return ${CURRENT_FRAMEWORK_STORAGE_VERSION}`, async () => { + expect(UpdateAssistant.frameworkStorageVersion).toBe(CURRENT_FRAMEWORK_STORAGE_VERSION) }) }) describe('getCurrentAgentStorageVersion()', () => { - it('should return 0.2 when a new wallet is created', async () => { - expect(await updateAssistant.getCurrentAgentStorageVersion()).toBe('0.2') + it(`should return ${CURRENT_FRAMEWORK_STORAGE_VERSION} when a new wallet is created`, async () => { + expect(await updateAssistant.getCurrentAgentStorageVersion()).toBe(CURRENT_FRAMEWORK_STORAGE_VERSION) }) }) }) diff --git a/packages/core/src/storage/migration/__tests__/__fixtures__/alice-4-proofs-0.2.json b/packages/core/src/storage/migration/__tests__/__fixtures__/alice-4-proofs-0.2.json new file mode 100644 index 0000000000..3a479abd31 --- /dev/null +++ b/packages/core/src/storage/migration/__tests__/__fixtures__/alice-4-proofs-0.2.json @@ -0,0 +1,235 @@ +{ + "STORAGE_VERSION_RECORD_ID": { + "value": { + "metadata": {}, + "id": "STORAGE_VERSION_RECORD_ID", + "createdAt": "2022-09-08T19:35:53.872Z", + "storageVersion": "0.2" + }, + "id": "STORAGE_VERSION_RECORD_ID", + "type": "StorageVersionRecord", + "tags": {} + }, + "72c96cd1-1f26-4bf3-8a00-5c00926859a8": { + "value": { + "metadata": {}, + "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "createdAt": "2022-09-08T19:36:06.208Z", + "proposalMessage": { + "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", + "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "presentation_proposal": { + "@type": "https://didcomm.org/present-proof/1.0/presentation-preview", + "attributes": [ + { + "name": "name", + "cred_def_id": "7yW6SoTjHNhD3zYgm4PbK8:3:CL:472319:TAG", + "value": "Alice" + } + ], + "predicates": [] + } + }, + "isVerified": true, + "requestMessage": { + "@type": "https://didcomm.org/present-proof/1.0/request-presentation", + "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", + "request_presentations~attach": [ + { + "@id": "libindy-request-presentation-0", + "mime-type": "application/json", + "data": { + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjQwMzU1MDc0MDYxMTU0MzEwMzA5NzMyMiIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7IjBmZWExODk3LTQzMTEtNDhmMi1hMmMyLTM4NzRlOTkzYWVmMCI6eyJuYW1lIjoibmFtZSIsInJlc3RyaWN0aW9ucyI6W3siY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyJ9XX19LCJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6e319" + } + } + ], + "~thread": { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5" + } + }, + "presentationMessage": { + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "@id": "4185f336-f307-4022-a27d-78d1271586f6", + "presentations~attach": [ + { + "@id": "libindy-presentation-0", + "mime-type": "application/json", + "data": { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19" + } + } + ], + "~thread": { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5" + } + }, + "state": "done", + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5" + }, + "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "type": "ProofRecord", + "tags": { + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "state": "done" + } + }, + "ea840186-3c77-45f4-a2e6-349811ad8994": { + "value": { + "metadata": {}, + "isVerified": true, + "id": "ea840186-3c77-45f4-a2e6-349811ad8994", + "createdAt": "2022-09-08T19:36:06.261Z", + "requestMessage": { + "@type": "https://didcomm.org/present-proof/1.0/request-presentation", + "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "request_presentations~attach": [ + { + "@id": "libindy-request-presentation-0", + "mime-type": "application/json", + "data": { + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjUyODExNDc1NTIxNzg3NzExMjI1Mzc0NSIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7Im5hbWUiOnsibmFtZSI6Im5hbWUiLCJyZXN0cmljdGlvbnMiOlt7ImNyZWRfZGVmX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODozOkNMOjQ3MjMxOTpUQUcifV19fSwicmVxdWVzdGVkX3ByZWRpY2F0ZXMiOnt9fQ==" + } + } + ] + }, + "presentationMessage": { + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", + "presentations~attach": [ + { + "@id": "libindy-presentation-0", + "mime-type": "application/json", + "data": { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==" + } + } + ], + "~thread": { + "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82" + } + }, + "state": "done", + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82" + }, + "id": "ea840186-3c77-45f4-a2e6-349811ad8994", + "type": "ProofRecord", + "tags": { + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "state": "done" + } + }, + "ec02ba64-63e3-46bc-b2a4-9d549d642d30": { + "value": { + "metadata": {}, + "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "createdAt": "2022-09-08T19:36:06.208Z", + "proposalMessage": { + "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", + "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "presentation_proposal": { + "@type": "https://didcomm.org/present-proof/1.0/presentation-preview", + "attributes": [ + { + "name": "name", + "cred_def_id": "7yW6SoTjHNhD3zYgm4PbK8:3:CL:472319:TAG", + "value": "Alice" + } + ], + "predicates": [] + } + }, + "requestMessage": { + "@type": "https://didcomm.org/present-proof/1.0/request-presentation", + "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", + "request_presentations~attach": [ + { + "@id": "libindy-request-presentation-0", + "mime-type": "application/json", + "data": { + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjQwMzU1MDc0MDYxMTU0MzEwMzA5NzMyMiIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7IjBmZWExODk3LTQzMTEtNDhmMi1hMmMyLTM4NzRlOTkzYWVmMCI6eyJuYW1lIjoibmFtZSIsInJlc3RyaWN0aW9ucyI6W3siY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyJ9XX19LCJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6e319" + } + } + ], + "~thread": { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5" + } + }, + "presentationMessage": { + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "@id": "4185f336-f307-4022-a27d-78d1271586f6", + "presentations~attach": [ + { + "@id": "libindy-presentation-0", + "mime-type": "application/json", + "data": { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19" + } + } + ], + "~thread": { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5" + } + }, + "state": "done", + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5" + }, + "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "type": "ProofRecord", + "tags": { + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "state": "done" + } + }, + "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e": { + "value": { + "metadata": {}, + "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "createdAt": "2022-09-08T19:36:06.261Z", + "requestMessage": { + "@type": "https://didcomm.org/present-proof/1.0/request-presentation", + "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "request_presentations~attach": [ + { + "@id": "libindy-request-presentation-0", + "mime-type": "application/json", + "data": { + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjUyODExNDc1NTIxNzg3NzExMjI1Mzc0NSIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7Im5hbWUiOnsibmFtZSI6Im5hbWUiLCJyZXN0cmljdGlvbnMiOlt7ImNyZWRfZGVmX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODozOkNMOjQ3MjMxOTpUQUcifV19fSwicmVxdWVzdGVkX3ByZWRpY2F0ZXMiOnt9fQ==" + } + } + ] + }, + "presentationMessage": { + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", + "presentations~attach": [ + { + "@id": "libindy-presentation-0", + "mime-type": "application/json", + "data": { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==" + } + } + ], + "~thread": { + "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82" + } + }, + "state": "done", + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82" + }, + "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "type": "ProofRecord", + "tags": { + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "state": "done" + } + } +} diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap index 838bdd61b0..927b02b255 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap @@ -1273,20 +1273,6 @@ Object { "threadId": "0b2f1133-ced9-49f1-83a1-eb6ba1c24cdf", }, }, - "MEDIATOR_ROUTING_RECORD": Object { - "id": "MEDIATOR_ROUTING_RECORD", - "tags": Object {}, - "type": "MediatorRoutingRecord", - "value": Object { - "_tags": Object {}, - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "MEDIATOR_ROUTING_RECORD", - "metadata": Object {}, - "routingKeys": Array [ - "ocxwFbXouLkzuTCyyjFg1bPGK3nM6aPv1pZ6fn5RNgD", - ], - }, - }, "STORAGE_VERSION_RECORD_ID": Object { "id": "STORAGE_VERSION_RECORD_ID", "tags": Object {}, @@ -2975,20 +2961,6 @@ Object { "role": "receiver", }, }, - "MEDIATOR_ROUTING_RECORD": Object { - "id": "MEDIATOR_ROUTING_RECORD", - "tags": Object {}, - "type": "MediatorRoutingRecord", - "value": Object { - "_tags": Object {}, - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "MEDIATOR_ROUTING_RECORD", - "metadata": Object {}, - "routingKeys": Array [ - "ocxwFbXouLkzuTCyyjFg1bPGK3nM6aPv1pZ6fn5RNgD", - ], - }, - }, "STORAGE_VERSION_RECORD_ID": Object { "id": "STORAGE_VERSION_RECORD_ID", "tags": Object {}, diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap new file mode 100644 index 0000000000..47b6988932 --- /dev/null +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap @@ -0,0 +1,953 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UpdateAssistant | v0.2 - v0.3 should correctly update proof records and create didcomm records 1`] = ` +Object { + "1-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "1-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "messageId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "messageName": "propose-presentation", + "messageType": "https://didcomm.org/present-proof/1.0/propose-presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "receiver", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "1-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", + "presentation_proposal": Object { + "@type": "https://didcomm.org/present-proof/1.0/presentation-preview", + "attributes": Array [ + Object { + "cred_def_id": "7yW6SoTjHNhD3zYgm4PbK8:3:CL:472319:TAG", + "name": "name", + "value": "Alice", + }, + ], + "predicates": Array [], + }, + }, + "metadata": Object {}, + "role": "receiver", + }, + }, + "10-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "10-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "messageId": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", + "messageName": "presentation", + "messageType": "https://didcomm.org/present-proof/1.0/presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "sender", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "10-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "presentations~attach": Array [ + Object { + "@id": "libindy-presentation-0", + "data": Object { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + }, + "metadata": Object {}, + "role": "sender", + }, + }, + "2-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "2-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "messageId": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", + "messageName": "request-presentation", + "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "sender", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "2-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", + "@type": "https://didcomm.org/present-proof/1.0/request-presentation", + "request_presentations~attach": Array [ + Object { + "@id": "libindy-request-presentation-0", + "data": Object { + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjQwMzU1MDc0MDYxMTU0MzEwMzA5NzMyMiIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7IjBmZWExODk3LTQzMTEtNDhmMi1hMmMyLTM4NzRlOTkzYWVmMCI6eyJuYW1lIjoibmFtZSIsInJlc3RyaWN0aW9ucyI6W3siY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyJ9XX19LCJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6e319", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "metadata": Object {}, + "role": "sender", + }, + }, + "3-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "3-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "messageId": "4185f336-f307-4022-a27d-78d1271586f6", + "messageName": "presentation", + "messageType": "https://didcomm.org/present-proof/1.0/presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "receiver", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "3-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "4185f336-f307-4022-a27d-78d1271586f6", + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "presentations~attach": Array [ + Object { + "@id": "libindy-presentation-0", + "data": Object { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "metadata": Object {}, + "role": "receiver", + }, + }, + "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e": Object { + "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "tags": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "parentThreadId": undefined, + "state": "done", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + "type": "ProofRecord", + "value": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "createdAt": "2022-09-08T19:36:06.261Z", + "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "metadata": Object {}, + "protocolVersion": "v1", + "state": "done", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + }, + "4-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "4-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", + "messageId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "messageName": "request-presentation", + "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "sender", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "4-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "@type": "https://didcomm.org/present-proof/1.0/request-presentation", + "request_presentations~attach": Array [ + Object { + "@id": "libindy-request-presentation-0", + "data": Object { + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjUyODExNDc1NTIxNzg3NzExMjI1Mzc0NSIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7Im5hbWUiOnsibmFtZSI6Im5hbWUiLCJyZXN0cmljdGlvbnMiOlt7ImNyZWRfZGVmX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODozOkNMOjQ3MjMxOTpUQUcifV19fSwicmVxdWVzdGVkX3ByZWRpY2F0ZXMiOnt9fQ==", + }, + "mime-type": "application/json", + }, + ], + }, + "metadata": Object {}, + "role": "sender", + }, + }, + "5-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "5-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", + "messageId": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", + "messageName": "presentation", + "messageType": "https://didcomm.org/present-proof/1.0/presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "receiver", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "5-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "presentations~attach": Array [ + Object { + "@id": "libindy-presentation-0", + "data": Object { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + }, + "metadata": Object {}, + "role": "receiver", + }, + }, + "6-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "6-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "messageId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "messageName": "propose-presentation", + "messageType": "https://didcomm.org/present-proof/1.0/propose-presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "sender", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "6-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", + "presentation_proposal": Object { + "@type": "https://didcomm.org/present-proof/1.0/presentation-preview", + "attributes": Array [ + Object { + "cred_def_id": "7yW6SoTjHNhD3zYgm4PbK8:3:CL:472319:TAG", + "name": "name", + "value": "Alice", + }, + ], + "predicates": Array [], + }, + }, + "metadata": Object {}, + "role": "sender", + }, + }, + "7-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "7-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "messageId": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", + "messageName": "request-presentation", + "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "receiver", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "7-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", + "@type": "https://didcomm.org/present-proof/1.0/request-presentation", + "request_presentations~attach": Array [ + Object { + "@id": "libindy-request-presentation-0", + "data": Object { + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjQwMzU1MDc0MDYxMTU0MzEwMzA5NzMyMiIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7IjBmZWExODk3LTQzMTEtNDhmMi1hMmMyLTM4NzRlOTkzYWVmMCI6eyJuYW1lIjoibmFtZSIsInJlc3RyaWN0aW9ucyI6W3siY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyJ9XX19LCJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6e319", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "metadata": Object {}, + "role": "receiver", + }, + }, + "72c96cd1-1f26-4bf3-8a00-5c00926859a8": Object { + "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "tags": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "parentThreadId": undefined, + "state": "done", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "ProofRecord", + "value": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "createdAt": "2022-09-08T19:36:06.208Z", + "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "isVerified": true, + "metadata": Object {}, + "protocolVersion": "v1", + "state": "done", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "8-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "8-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "messageId": "4185f336-f307-4022-a27d-78d1271586f6", + "messageName": "presentation", + "messageType": "https://didcomm.org/present-proof/1.0/presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "sender", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "8-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "4185f336-f307-4022-a27d-78d1271586f6", + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "presentations~attach": Array [ + Object { + "@id": "libindy-presentation-0", + "data": Object { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "metadata": Object {}, + "role": "sender", + }, + }, + "9-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "9-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "messageId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "messageName": "request-presentation", + "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "receiver", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "9-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "@type": "https://didcomm.org/present-proof/1.0/request-presentation", + "request_presentations~attach": Array [ + Object { + "@id": "libindy-request-presentation-0", + "data": Object { + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjUyODExNDc1NTIxNzg3NzExMjI1Mzc0NSIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7Im5hbWUiOnsibmFtZSI6Im5hbWUiLCJyZXN0cmljdGlvbnMiOlt7ImNyZWRfZGVmX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODozOkNMOjQ3MjMxOTpUQUcifV19fSwicmVxdWVzdGVkX3ByZWRpY2F0ZXMiOnt9fQ==", + }, + "mime-type": "application/json", + }, + ], + }, + "metadata": Object {}, + "role": "receiver", + }, + }, + "STORAGE_VERSION_RECORD_ID": Object { + "id": "STORAGE_VERSION_RECORD_ID", + "tags": Object {}, + "type": "StorageVersionRecord", + "value": Object { + "createdAt": "2022-09-08T19:35:53.872Z", + "id": "STORAGE_VERSION_RECORD_ID", + "metadata": Object {}, + "storageVersion": "0.3", + }, + }, + "ea840186-3c77-45f4-a2e6-349811ad8994": Object { + "id": "ea840186-3c77-45f4-a2e6-349811ad8994", + "tags": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "parentThreadId": undefined, + "state": "done", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + "type": "ProofRecord", + "value": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "createdAt": "2022-09-08T19:36:06.261Z", + "id": "ea840186-3c77-45f4-a2e6-349811ad8994", + "isVerified": true, + "metadata": Object {}, + "protocolVersion": "v1", + "state": "done", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + }, + "ec02ba64-63e3-46bc-b2a4-9d549d642d30": Object { + "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "tags": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "parentThreadId": undefined, + "state": "done", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "ProofRecord", + "value": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "createdAt": "2022-09-08T19:36:06.208Z", + "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "metadata": Object {}, + "protocolVersion": "v1", + "state": "done", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, +} +`; + +exports[`UpdateAssistant | v0.2 - v0.3 should correctly update the proofs records and create didcomm records with auto update 1`] = ` +Object { + "1-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "1-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "messageId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "messageName": "propose-presentation", + "messageType": "https://didcomm.org/present-proof/1.0/propose-presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "receiver", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "1-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", + "presentation_proposal": Object { + "@type": "https://didcomm.org/present-proof/1.0/presentation-preview", + "attributes": Array [ + Object { + "cred_def_id": "7yW6SoTjHNhD3zYgm4PbK8:3:CL:472319:TAG", + "name": "name", + "value": "Alice", + }, + ], + "predicates": Array [], + }, + }, + "metadata": Object {}, + "role": "receiver", + }, + }, + "10-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "10-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "messageId": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", + "messageName": "presentation", + "messageType": "https://didcomm.org/present-proof/1.0/presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "sender", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "10-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "presentations~attach": Array [ + Object { + "@id": "libindy-presentation-0", + "data": Object { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + }, + "metadata": Object {}, + "role": "sender", + }, + }, + "2-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "2-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "messageId": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", + "messageName": "request-presentation", + "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "sender", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "2-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", + "@type": "https://didcomm.org/present-proof/1.0/request-presentation", + "request_presentations~attach": Array [ + Object { + "@id": "libindy-request-presentation-0", + "data": Object { + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjQwMzU1MDc0MDYxMTU0MzEwMzA5NzMyMiIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7IjBmZWExODk3LTQzMTEtNDhmMi1hMmMyLTM4NzRlOTkzYWVmMCI6eyJuYW1lIjoibmFtZSIsInJlc3RyaWN0aW9ucyI6W3siY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyJ9XX19LCJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6e319", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "metadata": Object {}, + "role": "sender", + }, + }, + "3-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "3-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "messageId": "4185f336-f307-4022-a27d-78d1271586f6", + "messageName": "presentation", + "messageType": "https://didcomm.org/present-proof/1.0/presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "receiver", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "3-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "4185f336-f307-4022-a27d-78d1271586f6", + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "presentations~attach": Array [ + Object { + "@id": "libindy-presentation-0", + "data": Object { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "metadata": Object {}, + "role": "receiver", + }, + }, + "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e": Object { + "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "tags": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "parentThreadId": undefined, + "state": "done", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + "type": "ProofRecord", + "value": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "createdAt": "2022-09-08T19:36:06.261Z", + "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "metadata": Object {}, + "protocolVersion": "v1", + "state": "done", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + }, + "4-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "4-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", + "messageId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "messageName": "request-presentation", + "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "sender", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "4-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "@type": "https://didcomm.org/present-proof/1.0/request-presentation", + "request_presentations~attach": Array [ + Object { + "@id": "libindy-request-presentation-0", + "data": Object { + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjUyODExNDc1NTIxNzg3NzExMjI1Mzc0NSIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7Im5hbWUiOnsibmFtZSI6Im5hbWUiLCJyZXN0cmljdGlvbnMiOlt7ImNyZWRfZGVmX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODozOkNMOjQ3MjMxOTpUQUcifV19fSwicmVxdWVzdGVkX3ByZWRpY2F0ZXMiOnt9fQ==", + }, + "mime-type": "application/json", + }, + ], + }, + "metadata": Object {}, + "role": "sender", + }, + }, + "5-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "5-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", + "messageId": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", + "messageName": "presentation", + "messageType": "https://didcomm.org/present-proof/1.0/presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "receiver", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "5-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "presentations~attach": Array [ + Object { + "@id": "libindy-presentation-0", + "data": Object { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + }, + "metadata": Object {}, + "role": "receiver", + }, + }, + "6-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "6-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "messageId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "messageName": "propose-presentation", + "messageType": "https://didcomm.org/present-proof/1.0/propose-presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "sender", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "6-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", + "presentation_proposal": Object { + "@type": "https://didcomm.org/present-proof/1.0/presentation-preview", + "attributes": Array [ + Object { + "cred_def_id": "7yW6SoTjHNhD3zYgm4PbK8:3:CL:472319:TAG", + "name": "name", + "value": "Alice", + }, + ], + "predicates": Array [], + }, + }, + "metadata": Object {}, + "role": "sender", + }, + }, + "7-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "7-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "messageId": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", + "messageName": "request-presentation", + "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "receiver", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "7-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", + "@type": "https://didcomm.org/present-proof/1.0/request-presentation", + "request_presentations~attach": Array [ + Object { + "@id": "libindy-request-presentation-0", + "data": Object { + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjQwMzU1MDc0MDYxMTU0MzEwMzA5NzMyMiIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7IjBmZWExODk3LTQzMTEtNDhmMi1hMmMyLTM4NzRlOTkzYWVmMCI6eyJuYW1lIjoibmFtZSIsInJlc3RyaWN0aW9ucyI6W3siY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyJ9XX19LCJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6e319", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "metadata": Object {}, + "role": "receiver", + }, + }, + "72c96cd1-1f26-4bf3-8a00-5c00926859a8": Object { + "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "tags": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "parentThreadId": undefined, + "state": "done", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "ProofRecord", + "value": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "createdAt": "2022-09-08T19:36:06.208Z", + "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "isVerified": true, + "metadata": Object {}, + "protocolVersion": "v1", + "state": "done", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "8-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "8-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "messageId": "4185f336-f307-4022-a27d-78d1271586f6", + "messageName": "presentation", + "messageType": "https://didcomm.org/present-proof/1.0/presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "sender", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "8-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "4185f336-f307-4022-a27d-78d1271586f6", + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "presentations~attach": Array [ + Object { + "@id": "libindy-presentation-0", + "data": Object { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "metadata": Object {}, + "role": "sender", + }, + }, + "9-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "9-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "messageId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "messageName": "request-presentation", + "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "present-proof", + "role": "receiver", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + "type": "DidCommMessageRecord", + "value": Object { + "_tags": Object {}, + "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "9-4e4f-41d9-94c4-f49351b811f1", + "message": Object { + "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "@type": "https://didcomm.org/present-proof/1.0/request-presentation", + "request_presentations~attach": Array [ + Object { + "@id": "libindy-request-presentation-0", + "data": Object { + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjUyODExNDc1NTIxNzg3NzExMjI1Mzc0NSIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7Im5hbWUiOnsibmFtZSI6Im5hbWUiLCJyZXN0cmljdGlvbnMiOlt7ImNyZWRfZGVmX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODozOkNMOjQ3MjMxOTpUQUcifV19fSwicmVxdWVzdGVkX3ByZWRpY2F0ZXMiOnt9fQ==", + }, + "mime-type": "application/json", + }, + ], + }, + "metadata": Object {}, + "role": "receiver", + }, + }, + "STORAGE_VERSION_RECORD_ID": Object { + "id": "STORAGE_VERSION_RECORD_ID", + "tags": Object {}, + "type": "StorageVersionRecord", + "value": Object { + "createdAt": "2022-09-08T19:35:53.872Z", + "id": "STORAGE_VERSION_RECORD_ID", + "metadata": Object {}, + "storageVersion": "0.3", + }, + }, + "ea840186-3c77-45f4-a2e6-349811ad8994": Object { + "id": "ea840186-3c77-45f4-a2e6-349811ad8994", + "tags": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "parentThreadId": undefined, + "state": "done", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + "type": "ProofRecord", + "value": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "createdAt": "2022-09-08T19:36:06.261Z", + "id": "ea840186-3c77-45f4-a2e6-349811ad8994", + "isVerified": true, + "metadata": Object {}, + "protocolVersion": "v1", + "state": "done", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + }, + }, + "ec02ba64-63e3-46bc-b2a4-9d549d642d30": Object { + "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "tags": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "parentThreadId": undefined, + "state": "done", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + "type": "ProofRecord", + "value": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "createdAt": "2022-09-08T19:36:06.208Z", + "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", + "metadata": Object {}, + "protocolVersion": "v1", + "state": "done", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, +} +`; diff --git a/packages/core/src/storage/migration/updates.ts b/packages/core/src/storage/migration/updates.ts index 86d9ef5b71..d079287e61 100644 --- a/packages/core/src/storage/migration/updates.ts +++ b/packages/core/src/storage/migration/updates.ts @@ -3,6 +3,7 @@ import type { VersionString } from '../../utils/version' import type { V0_1ToV0_2UpdateConfig } from './updates/0.1-0.2' import { updateV0_1ToV0_2 } from './updates/0.1-0.2' +import { updateV0_2ToV0_3 } from './updates/0.2-0.3' export const INITIAL_STORAGE_VERSION = '0.1' @@ -22,13 +23,24 @@ export const DEFAULT_UPDATE_CONFIG: UpdateConfig = { }, } -export const supportedUpdates: Update[] = [ +export const supportedUpdates = [ { fromVersion: '0.1', toVersion: '0.2', doUpdate: updateV0_1ToV0_2, }, -] + { + fromVersion: '0.2', + toVersion: '0.3', + doUpdate: updateV0_2ToV0_3, + }, +] as const // Current version is last toVersion from the supported updates -export const CURRENT_FRAMEWORK_STORAGE_VERSION = supportedUpdates[supportedUpdates.length - 1].toVersion +export const CURRENT_FRAMEWORK_STORAGE_VERSION = supportedUpdates[supportedUpdates.length - 1].toVersion as LastItem< + typeof supportedUpdates +>['toVersion'] + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type LastItem = T extends readonly [...infer _, infer U] ? U : T[0] | undefined +export type UpdateToVersion = typeof supportedUpdates[number]['toVersion'] diff --git a/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/proof.test.ts b/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/proof.test.ts new file mode 100644 index 0000000000..b3026f8bf2 --- /dev/null +++ b/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/proof.test.ts @@ -0,0 +1,310 @@ +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../../tests/helpers' +import { Agent } from '../../../../../agent/Agent' +import { ProofRecord, ProofState } from '../../../../../modules/proofs' +import { ProofRepository } from '../../../../../modules/proofs/repository/ProofRepository' +import { JsonTransformer } from '../../../../../utils' +import { DidCommMessageRole } from '../../../../didcomm' +import { DidCommMessageRepository } from '../../../../didcomm/DidCommMessageRepository' +import * as testModule from '../proof' + +const agentConfig = getAgentConfig('Migration ProofRecord 0.2-0.3') +const agentContext = getAgentContext() + +jest.mock('../../../../../modules/proofs/repository/ProofRepository') +const ProofRepositoryMock = ProofRepository as jest.Mock +const proofRepository = new ProofRepositoryMock() + +jest.mock('../../../../didcomm/DidCommMessageRepository') +const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock +const didCommMessageRepository = new DidCommMessageRepositoryMock() + +jest.mock('../../../../../agent/Agent', () => { + return { + Agent: jest.fn(() => ({ + config: agentConfig, + context: agentContext, + dependencyManager: { + resolve: jest.fn((token) => (token === ProofRepositoryMock ? proofRepository : didCommMessageRepository)), + }, + })), + } +}) + +// Mock typed object +const AgentMock = Agent as jest.Mock + +describe('0.2-0.3 | Proof', () => { + let agent: Agent + + beforeEach(() => { + agent = new AgentMock() + }) + + afterEach(() => { + mockFunction(didCommMessageRepository.save).mockReset() + }) + + describe('migrateProofRecordToV0_3()', () => { + it('should fetch all records and apply the needed updates ', async () => { + const records: ProofRecord[] = [getProof({})] + + mockFunction(proofRepository.getAll).mockResolvedValue(records) + + await testModule.migrateProofRecordToV0_3(agent) + + expect(proofRepository.getAll).toHaveBeenCalledTimes(1) + expect(proofRepository.update).toHaveBeenCalledTimes(records.length) + + const updatedRecord = mockFunction(proofRepository.update).mock.calls[0][1] + + // Check first object is transformed correctly + expect(updatedRecord.toJSON()).toMatchObject({ + protocolVersion: 'v1', + }) + }) + }) + + describe('migrateInternalProofRecordProperties()', () => { + it('should set the protocol version to v1 if not set on the record', async () => { + const proofRecord = getProof({}) + + await testModule.migrateInternalProofRecordProperties(agent, proofRecord) + + expect(proofRecord).toMatchObject({ + protocolVersion: 'v1', + }) + }) + + it('should not set the protocol version if a value is already set', async () => { + const proofRecord = getProof({ + protocolVersion: 'v2', + }) + + await testModule.migrateInternalProofRecordProperties(agent, proofRecord) + + expect(proofRecord).toMatchObject({ + protocolVersion: 'v2', + }) + }) + }) + + describe('moveDidCommMessages()', () => { + it('should move the proposalMessage, requestMessage and presentationMessage to the didCommMessageRepository', async () => { + const proposalMessage = { '@type': 'ProposalMessage' } + const requestMessage = { '@type': 'RequestMessage' } + const presentationMessage = { '@type': 'ProofMessage' } + + const proofRecord = getProof({ + id: 'theProofId', + state: ProofState.Done, + proposalMessage, + requestMessage, + presentationMessage, + }) + + await testModule.moveDidCommMessages(agent, proofRecord) + + expect(didCommMessageRepository.save).toHaveBeenCalledTimes(3) + const [[, proposalMessageRecord], [, requestMessageRecord], [, presentationMessageRecord]] = mockFunction( + didCommMessageRepository.save + ).mock.calls + + expect(proposalMessageRecord).toMatchObject({ + role: DidCommMessageRole.Sender, + associatedRecordId: 'theProofId', + message: proposalMessage, + }) + + expect(requestMessageRecord).toMatchObject({ + role: DidCommMessageRole.Receiver, + associatedRecordId: 'theProofId', + message: requestMessage, + }) + + expect(presentationMessageRecord).toMatchObject({ + role: DidCommMessageRole.Sender, + associatedRecordId: 'theProofId', + message: presentationMessage, + }) + + expect(proofRecord.toJSON()).toEqual({ + _tags: {}, + protocolVersion: undefined, + id: 'theProofId', + state: ProofState.Done, + metadata: {}, + isVerified: undefined, + }) + }) + + it('should only move the messages which exist in the record', async () => { + const proposalMessage = { '@type': 'ProposalMessage' } + + const proofRecord = getProof({ + id: 'theProofId', + state: ProofState.Done, + proposalMessage, + isVerified: true, + }) + + await testModule.moveDidCommMessages(agent, proofRecord) + + expect(didCommMessageRepository.save).toHaveBeenCalledTimes(1) + const [[, proposalMessageRecord]] = mockFunction(didCommMessageRepository.save).mock.calls + + expect(proposalMessageRecord).toMatchObject({ + role: DidCommMessageRole.Receiver, + associatedRecordId: 'theProofId', + message: proposalMessage, + }) + + expect(proofRecord.toJSON()).toEqual({ + _tags: {}, + protocolVersion: undefined, + id: 'theProofId', + state: ProofState.Done, + metadata: {}, + isVerified: true, + presentationMessage: undefined, + requestMessage: undefined, + }) + }) + + it('should determine the correct DidCommMessageRole for each message', async () => { + const proposalMessage = { '@type': 'ProposalMessage' } + const requestMessage = { '@type': 'RequestMessage' } + const presentationMessage = { '@type': 'ProofMessage' } + + const proofRecord = getProof({ + id: 'theProofId', + state: ProofState.Done, + proposalMessage, + requestMessage, + presentationMessage, + }) + + await testModule.moveDidCommMessages(agent, proofRecord) + + expect(didCommMessageRepository.save).toHaveBeenCalledTimes(3) + const [[, proposalMessageRecord], [, requestMessageRecord], [, presentationMessageRecord]] = mockFunction( + didCommMessageRepository.save + ).mock.calls + + expect(proposalMessageRecord).toMatchObject({ + role: DidCommMessageRole.Sender, + associatedRecordId: 'theProofId', + message: proposalMessage, + }) + + expect(requestMessageRecord).toMatchObject({ + role: DidCommMessageRole.Receiver, + associatedRecordId: 'theProofId', + message: requestMessage, + }) + + expect(presentationMessageRecord).toMatchObject({ + role: DidCommMessageRole.Sender, + associatedRecordId: 'theProofId', + message: presentationMessage, + }) + + expect(proofRecord.toJSON()).toEqual({ + _tags: {}, + metadata: {}, + protocolVersion: undefined, + id: 'theProofId', + state: ProofState.Done, + }) + }) + }) + + describe('getProofRole', () => { + it('should return ProofRole.Verifier if isVerified is set', () => { + expect( + testModule.getProofRole( + getProof({ + isVerified: true, + }) + ) + ).toBe(testModule.ProofRole.Verifier) + + expect( + testModule.getProofRole( + getProof({ + isVerified: false, + }) + ) + ).toBe(testModule.ProofRole.Verifier) + }) + + it('should return ProofRole.Prover if state is Done and isVerified is not set', () => { + const proofRecord = getProof({ + state: ProofState.Done, + }) + + expect(testModule.getProofRole(proofRecord)).toBe(testModule.ProofRole.Prover) + }) + + it('should return ProofRole.Prover if the value is a prover state', () => { + const holderStates = [ + ProofState.Declined, + ProofState.ProposalSent, + ProofState.RequestReceived, + ProofState.PresentationSent, + ] + + for (const holderState of holderStates) { + expect( + testModule.getProofRole( + getProof({ + state: holderState, + }) + ) + ).toBe(testModule.ProofRole.Prover) + } + }) + + it('should return ProofRole.Verifier if the state is not a prover state, isVerified is not set and the state is not Done', () => { + expect( + testModule.getProofRole( + getProof({ + state: ProofState.PresentationReceived, + }) + ) + ).toBe(testModule.ProofRole.Verifier) + }) + }) +}) + +function getProof({ + protocolVersion, + proposalMessage, + requestMessage, + presentationMessage, + state, + isVerified, + id, +}: { + protocolVersion?: string + /* eslint-disable @typescript-eslint/no-explicit-any */ + proposalMessage?: any + requestMessage?: any + presentationMessage?: any + /* eslint-enable @typescript-eslint/no-explicit-any */ + state?: ProofState + isVerified?: boolean + id?: string +}) { + return JsonTransformer.fromJSON( + { + protocolVersion, + proposalMessage, + requestMessage, + presentationMessage, + state, + isVerified, + id, + }, + ProofRecord + ) +} diff --git a/packages/core/src/storage/migration/updates/0.2-0.3/index.ts b/packages/core/src/storage/migration/updates/0.2-0.3/index.ts new file mode 100644 index 0000000000..562ddba1db --- /dev/null +++ b/packages/core/src/storage/migration/updates/0.2-0.3/index.ts @@ -0,0 +1,7 @@ +import type { BaseAgent } from '../../../../agent/BaseAgent' + +import { migrateProofRecordToV0_3 } from './proof' + +export async function updateV0_2ToV0_3(agent: Agent): Promise { + await migrateProofRecordToV0_3(agent) +} diff --git a/packages/core/src/storage/migration/updates/0.2-0.3/proof.ts b/packages/core/src/storage/migration/updates/0.2-0.3/proof.ts new file mode 100644 index 0000000000..71a9f10cc3 --- /dev/null +++ b/packages/core/src/storage/migration/updates/0.2-0.3/proof.ts @@ -0,0 +1,162 @@ +import type { BaseAgent } from '../../../../agent/BaseAgent' +import type { ProofRecord } from '../../../../modules/proofs' +import type { JsonObject } from '../../../../types' + +import { ProofState } from '../../../../modules/proofs/models' +import { ProofRepository } from '../../../../modules/proofs/repository/ProofRepository' +import { DidCommMessageRepository, DidCommMessageRecord, DidCommMessageRole } from '../../../didcomm' + +/** + * Migrates the {@link ProofRecord} to 0.3 compatible format. It fetches all records from storage + * and applies the needed updates to the records. After a record has been transformed, it is updated + * in storage and the next record will be transformed. + * + * The following transformations are applied: + * - {@link migrateInternalProofRecordProperties} + * - {@link moveDidCommMessages} + */ +export async function migrateProofRecordToV0_3(agent: Agent) { + agent.config.logger.info('Migrating proof records to storage version 0.3') + const proofRepository = agent.dependencyManager.resolve(ProofRepository) + + agent.config.logger.debug(`Fetching all proof records from storage`) + const allProofs = await proofRepository.getAll(agent.context) + + agent.config.logger.debug(`Found a total of ${allProofs.length} proof records to update.`) + for (const proofRecord of allProofs) { + agent.config.logger.debug(`Migrating proof record with id ${proofRecord.id} to storage version 0.3`) + + await migrateInternalProofRecordProperties(agent, proofRecord) + await moveDidCommMessages(agent, proofRecord) + + await proofRepository.update(agent.context, proofRecord) + + agent.config.logger.debug(`Successfully migrated proof record with id ${proofRecord.id} to storage version 0.3`) + } +} + +export enum ProofRole { + Verifier, + Prover, +} + +const proverProofStates = [ + ProofState.Declined, + ProofState.ProposalSent, + ProofState.RequestReceived, + ProofState.PresentationSent, + ProofState.Done, +] + +const didCommMessageRoleMapping = { + [ProofRole.Verifier]: { + proposalMessage: DidCommMessageRole.Receiver, + requestMessage: DidCommMessageRole.Sender, + presentationMessage: DidCommMessageRole.Receiver, + }, + [ProofRole.Prover]: { + proposalMessage: DidCommMessageRole.Sender, + requestMessage: DidCommMessageRole.Receiver, + presentationMessage: DidCommMessageRole.Sender, + }, +} + +const proofRecordMessageKeys = ['proposalMessage', 'requestMessage', 'presentationMessage'] as const + +export function getProofRole(proofRecord: ProofRecord) { + // Proofs will only have an isVerified value when a presentation is verified, meaning we're the verifier + if (proofRecord.isVerified !== undefined) { + return ProofRole.Verifier + } + // If proofRecord.isVerified doesn't have any value, and we're also not in state done it means we're the prover. + else if (proofRecord.state === ProofState.Done) { + return ProofRole.Prover + } + // For these states we know for certain that we're the prover + else if (proverProofStates.includes(proofRecord.state)) { + return ProofRole.Prover + } + + // For all other states we can be certain we're the verifier + return ProofRole.Verifier +} + +/** + * With the addition of support for different protocol versions the proof record now stores the protocol version. + * + * The following 0.2.0 proof record structure (unrelated keys omitted): + * + * ```json + * { + * } + * ``` + * + * Will be transformed into the following 0.3.0 structure (unrelated keys omitted): + * + * ```json + * { + * "protocolVersion: "v1" + * } + * ``` + */ +export async function migrateInternalProofRecordProperties( + agent: Agent, + proofRecord: ProofRecord +) { + agent.config.logger.debug(`Migrating internal proof record ${proofRecord.id} properties to storage version 0.3`) + + if (!proofRecord.protocolVersion) { + agent.config.logger.debug(`Setting protocolVersion to v1`) + proofRecord.protocolVersion = 'v1' + } + + agent.config.logger.debug( + `Successfully migrated internal proof record ${proofRecord.id} properties to storage version 0.3` + ) +} + +/** + * In 0.3.0 the v1 didcomm messages have been moved out of the proof record into separate record using the DidCommMessageRepository. + * This migration scripts extracts all message (proposalMessage, requestMessage, presentationMessage) and moves + * them into the DidCommMessageRepository. + */ +export async function moveDidCommMessages(agent: Agent, proofRecord: ProofRecord) { + agent.config.logger.debug( + `Moving didcomm messages from proof record with id ${proofRecord.id} to DidCommMessageRecord` + ) + const didCommMessageRepository = agent.dependencyManager.resolve(DidCommMessageRepository) + + for (const messageKey of proofRecordMessageKeys) { + agent.config.logger.debug( + `Starting move of ${messageKey} from proof record with id ${proofRecord.id} to DIDCommMessageRecord` + ) + const proofRecordJson = proofRecord as unknown as JsonObject + const message = proofRecordJson[messageKey] as JsonObject | undefined + + if (message) { + const proofRole = getProofRole(proofRecord) + const didCommMessageRole = didCommMessageRoleMapping[proofRole][messageKey] + + const didCommMessageRecord = new DidCommMessageRecord({ + role: didCommMessageRole, + associatedRecordId: proofRecord.id, + message, + }) + await didCommMessageRepository.save(agent.context, didCommMessageRecord) + + agent.config.logger.debug( + `Successfully moved ${messageKey} from proof record with id ${proofRecord.id} to DIDCommMessageRecord` + ) + + delete proofRecordJson[messageKey] + } else { + agent.config.logger.debug( + `Proof record with id ${proofRecord.id} does not have a ${messageKey}. Not creating a DIDCommMessageRecord` + ) + } + } + + agent.config.logger.debug( + `Successfully moved didcomm messages from proof record with id ${proofRecord.id} to DIDCommMessageRecord` + ) +} diff --git a/packages/core/src/utils/__tests__/version.test.ts b/packages/core/src/utils/__tests__/version.test.ts index 408bca1ee4..d9fdcdedb8 100644 --- a/packages/core/src/utils/__tests__/version.test.ts +++ b/packages/core/src/utils/__tests__/version.test.ts @@ -1,4 +1,4 @@ -import { isFirstVersionHigherThanSecond, parseVersionString } from '../version' +import { isFirstVersionEqualToSecond, isFirstVersionHigherThanSecond, parseVersionString } from '../version' describe('version', () => { describe('parseVersionString()', () => { @@ -35,4 +35,31 @@ describe('version', () => { expect(isFirstVersionHigherThanSecond([2, 10], [2, 10])).toBe(false) }) }) + + describe('isFirstVersionEqualToSecond()', () => { + it('returns false if the major version digit of the first version is lower than the second', () => { + expect(isFirstVersionEqualToSecond([2, 0], [1, 0])).toBe(false) + expect(isFirstVersionEqualToSecond([2, 1], [1, 10])).toBe(false) + }) + + it('returns false if the major version digit of the first version is higher than the second', () => { + expect(isFirstVersionEqualToSecond([1, 0], [2, 0])).toBe(false) + expect(isFirstVersionEqualToSecond([1, 10], [2, 1])).toBe(false) + }) + + it('returns false if the major version digit of both versions are equal, but the minor version of the first version is lower', () => { + expect(isFirstVersionEqualToSecond([1, 10], [1, 0])).toBe(false) + expect(isFirstVersionEqualToSecond([2, 11], [2, 10])).toBe(false) + }) + + it('returns false if the major version digit of both versions are equal, but the minor version of the second version is lower', () => { + expect(isFirstVersionEqualToSecond([1, 0], [1, 10])).toBe(false) + expect(isFirstVersionEqualToSecond([2, 10], [2, 11])).toBe(false) + }) + + it('returns true if the major and minor version digit of both versions are equal', () => { + expect(isFirstVersionEqualToSecond([1, 0], [1, 0])).toBe(true) + expect(isFirstVersionEqualToSecond([2, 10], [2, 10])).toBe(true) + }) + }) }) diff --git a/packages/core/src/utils/version.ts b/packages/core/src/utils/version.ts index 58a3f10a77..33ae345f99 100644 --- a/packages/core/src/utils/version.ts +++ b/packages/core/src/utils/version.ts @@ -8,6 +8,10 @@ export function isFirstVersionHigherThanSecond(first: Version, second: Version) return first[0] > second[0] || (first[0] == second[0] && first[1] > second[1]) } +export function isFirstVersionEqualToSecond(first: Version, second: Version) { + return first[0] === second[0] && first[1] === second[1] +} + export type VersionString = `${number}.${number}` export type MajorVersion = number export type MinorVersion = number From 34db14bb3ba62cb4e51e5b4f0d45b3cbd61ea68e Mon Sep 17 00:00:00 2001 From: Sai Ranjit Tummalapalli <34263716+sairanjit@users.noreply.github.com> Date: Sat, 24 Sep 2022 15:11:18 +0530 Subject: [PATCH 042/125] ci: set default rust version (#1036) Signed-off-by: Sai Ranjit Tummalapalli --- .github/actions/setup-postgres-wallet-plugin/action.yml | 4 ++++ Dockerfile | 3 +++ 2 files changed, 7 insertions(+) diff --git a/.github/actions/setup-postgres-wallet-plugin/action.yml b/.github/actions/setup-postgres-wallet-plugin/action.yml index 7ac41af866..a03b2f3fde 100644 --- a/.github/actions/setup-postgres-wallet-plugin/action.yml +++ b/.github/actions/setup-postgres-wallet-plugin/action.yml @@ -5,11 +5,15 @@ author: 'sairanjit.tummalapalli@ayanworks.com' runs: using: composite steps: + # cargo build failing on latest release of rust due to + # socket2 dependency in the plugin https://users.rust-lang.org/t/build-broken-with-parse-quote-spanned-is-ambiguous/80280/2 + # so pointing rust version to 1.63.0 - name: Setup Postgres wallet plugin run: | sudo apt-get install -y libzmq3-dev libsodium-dev pkg-config libssl-dev curl https://sh.rustup.rs -sSf | bash -s -- -y export PATH="/root/.cargo/bin:${PATH}" + rustup default 1.63.0 cd ../ git clone https://github.com/hyperledger/indy-sdk.git cd indy-sdk/experimental/plugins/postgres_storage/ diff --git a/Dockerfile b/Dockerfile index fa261eea80..91ccda0363 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,9 @@ RUN apt-get install -y --no-install-recommends yarn RUN curl https://sh.rustup.rs -sSf | bash -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" +# cargo build failing on latest release of rust due to socket2 dependency in the plugin https://users.rust-lang.org/t/build-broken-with-parse-quote-spanned-is-ambiguous/80280/2 so pointing rust version to 1.63.0 +RUN rustup default 1.63.0 + # clone indy-sdk and build postgres plugin RUN git clone https://github.com/hyperledger/indy-sdk.git WORKDIR /indy-sdk/experimental/plugins/postgres_storage/ From e1d6592b818bc4348078ca6593eea4641caafae5 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Mon, 26 Sep 2022 17:36:12 +0200 Subject: [PATCH 043/125] fix(oob): allow encoding in content type header (#1037) Signed-off-by: Timo Glastra --- .../src/utils/__tests__/shortenedUrl.test.ts | 21 ++++++++++++++----- packages/core/src/utils/parseInvitation.ts | 6 +++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/core/src/utils/__tests__/shortenedUrl.test.ts b/packages/core/src/utils/__tests__/shortenedUrl.test.ts index a6e2364f97..5e79621e96 100644 --- a/packages/core/src/utils/__tests__/shortenedUrl.test.ts +++ b/packages/core/src/utils/__tests__/shortenedUrl.test.ts @@ -7,7 +7,7 @@ import { OutOfBandInvitation } from '../../modules/oob' import { convertToNewInvitation } from '../../modules/oob/helpers' import { JsonTransformer } from '../JsonTransformer' import { MessageValidator } from '../MessageValidator' -import { oobInvitationfromShortUrl } from '../parseInvitation' +import { oobInvitationFromShortUrl } from '../parseInvitation' const mockOobInvite = { '@type': 'did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/out-of-band/1.0/invitation', @@ -89,21 +89,32 @@ beforeAll(async () => { describe('shortened urls resolving to oob invitations', () => { test('Resolve a mocked response in the form of a oob invitation as a json object', async () => { - const short = await oobInvitationfromShortUrl(mockedResponseOobJson) + const short = await oobInvitationFromShortUrl(mockedResponseOobJson) expect(short).toEqual(outOfBandInvitationMock) }) test('Resolve a mocked response in the form of a oob invitation encoded in an url', async () => { - const short = await oobInvitationfromShortUrl(mockedResponseOobUrl) + const short = await oobInvitationFromShortUrl(mockedResponseOobUrl) + expect(short).toEqual(outOfBandInvitationMock) + }) + + test("Resolve a mocked response in the form of a oob invitation as a json object with header 'application/json; charset=utf-8'", async () => { + const short = await oobInvitationFromShortUrl({ + ...mockedResponseOobJson, + headers: new Headers({ + 'content-type': 'application/json; charset=utf-8', + }), + } as Response) expect(short).toEqual(outOfBandInvitationMock) }) }) + describe('shortened urls resolving to connection invitations', () => { test('Resolve a mocked response in the form of a connection invitation as a json object', async () => { - const short = await oobInvitationfromShortUrl(mockedResponseConnectionJson) + const short = await oobInvitationFromShortUrl(mockedResponseConnectionJson) expect(short).toEqual(connectionInvitationToNew) }) test('Resolve a mocked Response in the form of a connection invitation encoded in an url', async () => { - const short = await oobInvitationfromShortUrl(mockedResponseConnectionUrl) + const short = await oobInvitationFromShortUrl(mockedResponseConnectionUrl) expect(short).toEqual(connectionInvitationToNew) }) }) diff --git a/packages/core/src/utils/parseInvitation.ts b/packages/core/src/utils/parseInvitation.ts index 713360512e..6f3c9e8f3b 100644 --- a/packages/core/src/utils/parseInvitation.ts +++ b/packages/core/src/utils/parseInvitation.ts @@ -54,9 +54,9 @@ export const parseInvitationUrl = (invitationUrl: string): OutOfBandInvitation = } //This currently does not follow the RFC because of issues with fetch, currently uses a janky work around -export const oobInvitationfromShortUrl = async (response: Response): Promise => { +export const oobInvitationFromShortUrl = async (response: Response): Promise => { if (response) { - if (response.headers.get('Content-Type') === 'application/json' && response.ok) { + if (response.headers.get('Content-Type')?.startsWith('application/json') && response.ok) { const invitationJson = await response.json() const parsedMessageType = parseMessageType(invitationJson['@type']) if (supportsIncomingMessageType(parsedMessageType, OutOfBandInvitation.type)) { @@ -107,7 +107,7 @@ export const parseInvitationShortUrl = async ( return convertToNewInvitation(invitation) } else { try { - return oobInvitationfromShortUrl(await fetchShortUrl(invitationUrl, dependencies)) + return oobInvitationFromShortUrl(await fetchShortUrl(invitationUrl, dependencies)) } catch (error) { throw new AriesFrameworkError( 'InvitationUrl is invalid. It needs to contain one, and only one, of the following parameters: `oob`, `c_i` or `d_m`, or be valid shortened URL' From 0d14a7157e2118592829109dbc5c793faee1e201 Mon Sep 17 00:00:00 2001 From: KolbyRKunz Date: Thu, 29 Sep 2022 00:55:43 -0600 Subject: [PATCH 044/125] feat: connection type (#994) Signed-off-by: KolbyRKunz --- .../modules/connections/ConnectionsModule.ts | 54 +++++++++++++++++++ .../connections/models/ConnectionType.ts | 3 ++ .../src/modules/connections/models/index.ts | 1 + .../repository/ConnectionRecord.ts | 2 + .../connections/services/ConnectionService.ts | 5 ++ .../services/MediationRecipientService.ts | 4 ++ packages/core/tests/oob-mediation.test.ts | 11 +++- 7 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/modules/connections/models/ConnectionType.ts diff --git a/packages/core/src/modules/connections/ConnectionsModule.ts b/packages/core/src/modules/connections/ConnectionsModule.ts index 697b4492de..476288a96b 100644 --- a/packages/core/src/modules/connections/ConnectionsModule.ts +++ b/packages/core/src/modules/connections/ConnectionsModule.ts @@ -1,6 +1,7 @@ import type { DependencyManager } from '../../plugins' import type { Key } from '../dids' import type { OutOfBandRecord } from '../oob/repository' +import type { ConnectionType } from './models' import type { ConnectionRecord } from './repository/ConnectionRecord' import type { Routing } from './services' @@ -197,6 +198,59 @@ export class ConnectionsModule { return this.connectionService.getAll() } + /** + * Allows for the addition of connectionType to the record. + * Either updates or creates an array of string conection types + * @param connectionId + * @param type + * @throws {RecordNotFoundError} If no record is found + */ + public async addConnectionType(connectionId: string, type: ConnectionType | string) { + const record = await this.getById(connectionId) + + const tags = (record.getTag('connectionType') as string[]) || ([] as string[]) + record.setTag('connectionType', [type, ...tags]) + await this.connectionService.update(record) + } + /** + * Removes the given tag from the given record found by connectionId, if the tag exists otherwise does nothing + * @param connectionId + * @param type + * @throws {RecordNotFoundError} If no record is found + */ + public async removeConnectionType(connectionId: string, type: ConnectionType | string) { + const record = await this.getById(connectionId) + + const tags = (record.getTag('connectionType') as string[]) || ([] as string[]) + + const newTags = tags.filter((value: string) => { + if (value != type) return value + }) + record.setTag('connectionType', [...newTags]) + + await this.connectionService.update(record) + } + /** + * Gets the known connection types for the record matching the given connectionId + * @param connectionId + * @returns An array of known connection types or null if none exist + * @throws {RecordNotFoundError} If no record is found + */ + public async getConnectionTypes(connectionId: string) { + const record = await this.getById(connectionId) + const tags = record.getTag('connectionType') as string[] + return tags || null + } + + /** + * + * @param connectionTypes An array of connection types to query for a match for + * @returns a promise of ab array of connection records + */ + public async findAllByConnectionType(connectionTypes: [ConnectionType | string]) { + return this.connectionService.findAllByConnectionType(connectionTypes) + } + /** * Retrieve a connection record by id * diff --git a/packages/core/src/modules/connections/models/ConnectionType.ts b/packages/core/src/modules/connections/models/ConnectionType.ts new file mode 100644 index 0000000000..85e6a5dbf9 --- /dev/null +++ b/packages/core/src/modules/connections/models/ConnectionType.ts @@ -0,0 +1,3 @@ +export enum ConnectionType { + Mediator = 'mediator', +} diff --git a/packages/core/src/modules/connections/models/index.ts b/packages/core/src/modules/connections/models/index.ts index 0c8dd1b360..69752df9c7 100644 --- a/packages/core/src/modules/connections/models/index.ts +++ b/packages/core/src/modules/connections/models/index.ts @@ -5,3 +5,4 @@ export * from './DidExchangeState' export * from './DidExchangeRole' export * from './HandshakeProtocol' export * from './did' +export * from './ConnectionType' diff --git a/packages/core/src/modules/connections/repository/ConnectionRecord.ts b/packages/core/src/modules/connections/repository/ConnectionRecord.ts index b4d36e2dee..db9512e5fc 100644 --- a/packages/core/src/modules/connections/repository/ConnectionRecord.ts +++ b/packages/core/src/modules/connections/repository/ConnectionRecord.ts @@ -1,5 +1,6 @@ import type { TagsBase } from '../../../storage/BaseRecord' import type { HandshakeProtocol } from '../models' +import type { ConnectionType } from '../models/ConnectionType' import type { ConnectionMetadata } from './ConnectionMetadataTypes' import { AriesFrameworkError } from '../../../error' @@ -37,6 +38,7 @@ export type DefaultConnectionTags = { theirDid?: string outOfBandId?: string invitationDid?: string + connectionType?: [ConnectionType | string] } export class ConnectionRecord diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index 1ca5adf5ce..d7fc004881 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -6,6 +6,7 @@ import type { OutOfBandDidCommService } from '../../oob/domain/OutOfBandDidCommS import type { OutOfBandRecord } from '../../oob/repository' import type { ConnectionStateChangedEvent } from '../ConnectionEvents' import type { ConnectionProblemReportMessage } from '../messages' +import type { ConnectionType } from '../models' import type { ConnectionRecordProps } from '../repository/ConnectionRecord' import { firstValueFrom, ReplaySubject } from 'rxjs' @@ -583,6 +584,10 @@ export class ConnectionService { return this.connectionRepository.findByQuery({ outOfBandId }) } + public async findAllByConnectionType(connectionType: [ConnectionType | string]) { + return this.connectionRepository.findByQuery({ connectionType }) + } + public async findByInvitationDid(invitationDid: string) { return this.connectionRepository.findByQuery({ invitationDid }) } diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index 7f9bb35769..715cee4e9e 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -21,6 +21,7 @@ import { KeyType } from '../../../crypto' import { AriesFrameworkError } from '../../../error' import { injectable } from '../../../plugins' import { JsonTransformer } from '../../../utils' +import { ConnectionType } from '../../connections/models/ConnectionType' import { ConnectionMetadataKeys } from '../../connections/repository/ConnectionMetadataTypes' import { ConnectionService } from '../../connections/services/ConnectionService' import { Key } from '../../dids' @@ -90,6 +91,9 @@ export class MediationRecipientService { role: MediationRole.Recipient, connectionId: connection.id, }) + connection.setTag('connectionType', [ConnectionType.Mediator]) + await this.connectionService.update(connection) + await this.mediationRepository.save(mediationRecord) this.emitStateChangedEvent(mediationRecord, null) diff --git a/packages/core/tests/oob-mediation.test.ts b/packages/core/tests/oob-mediation.test.ts index 34ff80b35d..18be1d65ed 100644 --- a/packages/core/tests/oob-mediation.test.ts +++ b/packages/core/tests/oob-mediation.test.ts @@ -7,6 +7,7 @@ import { SubjectInboundTransport } from '../../../tests/transport/SubjectInbound import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' import { Agent } from '../src/agent/Agent' import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' +import { ConnectionType } from '../src/modules/connections/models/ConnectionType' import { MediationState, MediatorPickupStrategy } from '../src/modules/routing' import { getBaseConfig, waitForBasicMessage } from './helpers' @@ -90,8 +91,16 @@ describe('out of band with mediation', () => { mediatorAliceConnection = await mediatorAgent.connections.returnWhenIsConnected(mediatorAliceConnection!.id) expect(mediatorAliceConnection.state).toBe(DidExchangeState.Completed) - // ========== Set meadiation between Alice and Mediator agents ========== + // ========== Set mediation between Alice and Mediator agents ========== const mediationRecord = await aliceAgent.mediationRecipient.requestAndAwaitGrant(aliceMediatorConnection) + const connectonTypes = await aliceAgent.connections.getConnectionTypes(mediationRecord.connectionId) + expect(connectonTypes).toContain(ConnectionType.Mediator) + await aliceAgent.connections.addConnectionType(mediationRecord.connectionId, 'test') + expect(await aliceAgent.connections.getConnectionTypes(mediationRecord.connectionId)).toContain('test') + await aliceAgent.connections.removeConnectionType(mediationRecord.connectionId, 'test') + expect(await aliceAgent.connections.getConnectionTypes(mediationRecord.connectionId)).toEqual([ + ConnectionType.Mediator, + ]) expect(mediationRecord.state).toBe(MediationState.Granted) await aliceAgent.mediationRecipient.setDefaultMediator(mediationRecord) From a230841aa99102bcc8b60aa2a23040f13a929a6c Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Thu, 6 Oct 2022 15:34:32 -0500 Subject: [PATCH 045/125] feat: improve sending error handling (#1045) Signed-off-by: Ariel Gentile --- packages/core/src/agent/MessageSender.ts | 12 +- .../core/src/error/MessageSendingError.ts | 11 ++ packages/core/src/error/index.ts | 1 + .../basic-messages/BasicMessagesModule.ts | 45 ++++++- .../__tests__/basic-messages.e2e.test.ts | 110 ++++++++++++++++++ .../services/BasicMessageService.ts | 11 +- packages/core/src/types.ts | 2 + 7 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 packages/core/src/error/MessageSendingError.ts create mode 100644 packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts diff --git a/packages/core/src/agent/MessageSender.ts b/packages/core/src/agent/MessageSender.ts index e8a284e450..b85883b9c4 100644 --- a/packages/core/src/agent/MessageSender.ts +++ b/packages/core/src/agent/MessageSender.ts @@ -10,7 +10,7 @@ import type { TransportSession } from './TransportService' import { DID_COMM_TRANSPORT_QUEUE, InjectionSymbols } from '../constants' import { ReturnRouteTypes } from '../decorators/transport/TransportDecorator' -import { AriesFrameworkError } from '../error' +import { AriesFrameworkError, MessageSendingError } from '../error' import { Logger } from '../logger' import { DidCommDocumentService } from '../modules/didcomm' import { getKeyDidMappingByVerificationMethod } from '../modules/dids/domain/key-type' @@ -209,8 +209,9 @@ export class MessageSender { if (!connection.did) { this.logger.error(`Unable to send message using connection '${connection.id}' that doesn't have a did`) - throw new AriesFrameworkError( - `Unable to send message using connection '${connection.id}' that doesn't have a did` + throw new MessageSendingError( + `Unable to send message using connection '${connection.id}' that doesn't have a did`, + { outboundMessage } ) } @@ -277,7 +278,10 @@ export class MessageSender { errors, connection, }) - throw new AriesFrameworkError(`Message is undeliverable to connection ${connection.id} (${connection.theirLabel})`) + throw new MessageSendingError( + `Message is undeliverable to connection ${connection.id} (${connection.theirLabel})`, + { outboundMessage } + ) } public async sendMessageToService({ diff --git a/packages/core/src/error/MessageSendingError.ts b/packages/core/src/error/MessageSendingError.ts new file mode 100644 index 0000000000..6ebc95a23d --- /dev/null +++ b/packages/core/src/error/MessageSendingError.ts @@ -0,0 +1,11 @@ +import type { OutboundMessage } from '../types' + +import { AriesFrameworkError } from './AriesFrameworkError' + +export class MessageSendingError extends AriesFrameworkError { + public outboundMessage: OutboundMessage + public constructor(message: string, { outboundMessage, cause }: { outboundMessage: OutboundMessage; cause?: Error }) { + super(message, { cause }) + this.outboundMessage = outboundMessage + } +} diff --git a/packages/core/src/error/index.ts b/packages/core/src/error/index.ts index 5098161d50..7122734300 100644 --- a/packages/core/src/error/index.ts +++ b/packages/core/src/error/index.ts @@ -3,3 +3,4 @@ export * from './RecordNotFoundError' export * from './RecordDuplicateError' export * from './IndySdkError' export * from './ClassValidationError' +export * from './MessageSendingError' diff --git a/packages/core/src/modules/basic-messages/BasicMessagesModule.ts b/packages/core/src/modules/basic-messages/BasicMessagesModule.ts index 8d38643c4b..26d958cc97 100644 --- a/packages/core/src/modules/basic-messages/BasicMessagesModule.ts +++ b/packages/core/src/modules/basic-messages/BasicMessagesModule.ts @@ -30,18 +30,61 @@ export class BasicMessagesModule { this.registerHandlers(dispatcher) } + /** + * Send a message to an active connection + * + * @param connectionId Connection Id + * @param message Message contents + * @throws {RecordNotFoundError} If connection is not found + * @throws {MessageSendingError} If message is undeliverable + * @returns the created record + */ public async sendMessage(connectionId: string, message: string) { const connection = await this.connectionService.getById(connectionId) - const basicMessage = await this.basicMessageService.createMessage(message, connection) + const { message: basicMessage, record: basicMessageRecord } = await this.basicMessageService.createMessage( + message, + connection + ) const outboundMessage = createOutboundMessage(connection, basicMessage) + outboundMessage.associatedRecord = basicMessageRecord + await this.messageSender.sendMessage(outboundMessage) + return basicMessageRecord } + /** + * Retrieve all basic messages matching a given query + * + * @param query The query + * @returns array containing all matching records + */ public async findAllByQuery(query: Partial) { return this.basicMessageService.findAllByQuery(query) } + /** + * Retrieve a basic message record by id + * + * @param basicMessageRecordId The basic message record id + * @throws {RecordNotFoundError} If no record is found + * @return The basic message record + * + */ + public async getById(basicMessageRecordId: string) { + return this.basicMessageService.getById(basicMessageRecordId) + } + + /** + * Delete a basic message record by id + * + * @param connectionId the basic message record id + * @throws {RecordNotFoundError} If no record is found + */ + public async deleteById(basicMessageRecordId: string) { + await this.basicMessageService.deleteById(basicMessageRecordId) + } + private registerHandlers(dispatcher: Dispatcher) { dispatcher.registerHandler(new BasicMessageHandler(this.basicMessageService)) } diff --git a/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts new file mode 100644 index 0000000000..ac8b0458d2 --- /dev/null +++ b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts @@ -0,0 +1,110 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' +import type { ConnectionRecord } from '../../../modules/connections' + +import { Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getBaseConfig, makeConnection, waitForBasicMessage } from '../../../../tests/helpers' +import testLogger from '../../../../tests/logger' +import { Agent } from '../../../agent/Agent' +import { MessageSendingError, RecordNotFoundError } from '../../../error' +import { BasicMessage } from '../messages' +import { BasicMessageRecord } from '../repository' + +const faberConfig = getBaseConfig('Faber Basic Messages', { + endpoints: ['rxjs:faber'], +}) + +const aliceConfig = getBaseConfig('Alice Basic Messages', { + endpoints: ['rxjs:alice'], +}) + +describe('Basic Messages E2E', () => { + let faberAgent: Agent + let aliceAgent: Agent + let faberConnection: ConnectionRecord + let aliceConnection: ConnectionRecord + + beforeEach(async () => { + const faberMessages = new Subject() + const aliceMessages = new Subject() + const subjectMap = { + 'rxjs:faber': faberMessages, + 'rxjs:alice': aliceMessages, + } + + faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await faberAgent.initialize() + + aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + ;[aliceConnection, faberConnection] = await makeConnection(aliceAgent, faberAgent) + }) + + afterEach(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice and Faber exchange messages', async () => { + testLogger.test('Alice sends message to Faber') + const helloRecord = await aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello') + + expect(helloRecord.content).toBe('Hello') + + testLogger.test('Faber waits for message from Alice') + await waitForBasicMessage(faberAgent, { + content: 'Hello', + }) + + testLogger.test('Faber sends message to Alice') + const replyRecord = await faberAgent.basicMessages.sendMessage(faberConnection.id, 'How are you?') + expect(replyRecord.content).toBe('How are you?') + + testLogger.test('Alice waits until she receives message from faber') + await waitForBasicMessage(aliceAgent, { + content: 'How are you?', + }) + }) + + test('Alice is unable to send a message', async () => { + testLogger.test('Alice sends message to Faber that is undeliverable') + + const spy = jest.spyOn(aliceAgent.outboundTransports[0], 'sendMessage').mockRejectedValue(new Error('any error')) + + await expect(aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello')).rejects.toThrowError( + MessageSendingError + ) + try { + await aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello undeliverable') + } catch (error) { + const thrownError = error as MessageSendingError + expect(thrownError.message).toEqual( + `Message is undeliverable to connection ${aliceConnection.id} (${aliceConnection.theirLabel})` + ) + testLogger.test('Error thrown includes the outbound message and recently created record id') + expect(thrownError.outboundMessage.associatedRecord).toBeInstanceOf(BasicMessageRecord) + expect(thrownError.outboundMessage.payload).toBeInstanceOf(BasicMessage) + expect((thrownError.outboundMessage.payload as BasicMessage).content).toBe('Hello undeliverable') + + testLogger.test('Created record can be found and deleted by id') + const storedRecord = await aliceAgent.basicMessages.getById(thrownError.outboundMessage.associatedRecord!.id) + expect(storedRecord).toBeInstanceOf(BasicMessageRecord) + expect(storedRecord.content).toBe('Hello undeliverable') + + await aliceAgent.basicMessages.deleteById(storedRecord.id) + await expect( + aliceAgent.basicMessages.getById(thrownError.outboundMessage.associatedRecord!.id) + ).rejects.toThrowError(RecordNotFoundError) + } + spy.mockClear() + }) +}) diff --git a/packages/core/src/modules/basic-messages/services/BasicMessageService.ts b/packages/core/src/modules/basic-messages/services/BasicMessageService.ts index 749258deda..7388e74e82 100644 --- a/packages/core/src/modules/basic-messages/services/BasicMessageService.ts +++ b/packages/core/src/modules/basic-messages/services/BasicMessageService.ts @@ -35,7 +35,7 @@ export class BasicMessageService { await this.basicMessageRepository.save(basicMessageRecord) this.emitStateChangedEvent(basicMessageRecord, basicMessage) - return basicMessage + return { message: basicMessage, record: basicMessageRecord } } /** @@ -64,4 +64,13 @@ export class BasicMessageService { public async findAllByQuery(query: Partial) { return this.basicMessageRepository.findByQuery(query) } + + public async getById(basicMessageRecordId: string) { + return this.basicMessageRepository.getById(basicMessageRecordId) + } + + public async deleteById(basicMessageRecordId: string) { + const basicMessageRecord = await this.getById(basicMessageRecordId) + return this.basicMessageRepository.delete(basicMessageRecord) + } } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index abe5571c30..75f00cde88 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -8,6 +8,7 @@ import type { IndyPoolConfig } from './modules/ledger/IndyPool' import type { OutOfBandRecord } from './modules/oob/repository' import type { AutoAcceptProof } from './modules/proofs' import type { MediatorPickupStrategy } from './modules/routing' +import type { BaseRecord } from './storage/BaseRecord' export enum KeyDerivationMethod { /** default value in indy-sdk. Will be used when no value is provided */ @@ -96,6 +97,7 @@ export interface OutboundMessage { connection: ConnectionRecord sessionId?: string outOfBand?: OutOfBandRecord + associatedRecord?: BaseRecord } export interface OutboundServiceMessage { From 9dd95e81770d3140558196d2b5b508723f918f04 Mon Sep 17 00:00:00 2001 From: Jim Ezesinachi Date: Fri, 7 Oct 2022 00:58:55 +0100 Subject: [PATCH 046/125] feat: expose findAllByQuery method in modules and services (#1044) Signed-off-by: Jim Ezesinachi --- packages/core/src/index.ts | 2 +- .../modules/action-menu/services/ActionMenuService.ts | 5 +++++ .../src/modules/basic-messages/BasicMessagesModule.ts | 5 +++-- .../basic-messages/services/BasicMessageService.ts | 4 ++-- .../core/src/modules/connections/ConnectionsModule.ts | 10 ++++++++++ .../connections/__tests__/ConnectionService.test.ts | 10 ++++++++++ .../modules/connections/services/ConnectionService.ts | 5 +++++ .../core/src/modules/credentials/CredentialsModule.ts | 11 +++++++++++ .../v1/__tests__/V1CredentialServiceCred.test.ts | 10 ++++++++++ .../v2/__tests__/V2CredentialServiceCred.test.ts | 10 ++++++++++ .../modules/credentials/services/CredentialService.ts | 5 +++++ .../modules/generic-records/GenericRecordsModule.ts | 5 +++-- .../generic-records/service/GenericRecordService.ts | 5 +++-- packages/core/src/modules/oob/OutOfBandModule.ts | 10 ++++++++++ packages/core/src/modules/oob/OutOfBandService.ts | 5 +++++ .../modules/oob/__tests__/OutOfBandService.test.ts | 10 ++++++++++ packages/core/src/modules/proofs/ProofsModule.ts | 10 ++++++++++ .../core/src/modules/proofs/services/ProofService.ts | 10 ++++++++++ .../modules/question-answer/QuestionAnswerModule.ts | 11 +++++++++++ .../question-answer/services/QuestionAnswerService.ts | 4 ++-- .../routing/services/MediationRecipientService.ts | 5 +++++ .../src/modules/routing/services/MediatorService.ts | 5 +++++ packages/core/src/storage/StorageService.ts | 4 ++-- samples/extension-module/dummy/DummyApi.ts | 10 ++++++++++ .../extension-module/dummy/services/DummyService.ts | 11 ++++++++++- 25 files changed, 168 insertions(+), 14 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c5b562eb37..19c61439ab 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -17,7 +17,7 @@ export * from './storage/BaseRecord' export { InMemoryMessageRepository } from './storage/InMemoryMessageRepository' export { Repository } from './storage/Repository' export * from './storage/RepositoryEvents' -export { StorageService } from './storage/StorageService' +export { StorageService, Query } from './storage/StorageService' export { getDirFromFilePath } from './utils/path' export { InjectionSymbols } from './constants' export type { Wallet } from './wallet/Wallet' diff --git a/packages/core/src/modules/action-menu/services/ActionMenuService.ts b/packages/core/src/modules/action-menu/services/ActionMenuService.ts index f96387fa8e..f1c821c917 100644 --- a/packages/core/src/modules/action-menu/services/ActionMenuService.ts +++ b/packages/core/src/modules/action-menu/services/ActionMenuService.ts @@ -1,5 +1,6 @@ import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { Logger } from '../../../logger' +import type { Query } from '../../../storage/StorageService' import type { ActionMenuStateChangedEvent } from '../ActionMenuEvents' import type { ActionMenuProblemReportMessage } from '../messages' import type { @@ -346,6 +347,10 @@ export class ActionMenuService { }) } + public async findAllByQuery(options: Query) { + return await this.actionMenuRepository.findByQuery(options) + } + private emitStateChangedEvent(actionMenuRecord: ActionMenuRecord, previousState: ActionMenuState | null) { const clonedRecord = JsonTransformer.clone(actionMenuRecord) diff --git a/packages/core/src/modules/basic-messages/BasicMessagesModule.ts b/packages/core/src/modules/basic-messages/BasicMessagesModule.ts index 26d958cc97..39b8cfc263 100644 --- a/packages/core/src/modules/basic-messages/BasicMessagesModule.ts +++ b/packages/core/src/modules/basic-messages/BasicMessagesModule.ts @@ -1,5 +1,6 @@ import type { DependencyManager } from '../../plugins' -import type { BasicMessageTags } from './repository/BasicMessageRecord' +import type { Query } from '../../storage/StorageService' +import type { BasicMessageRecord } from './repository/BasicMessageRecord' import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' @@ -59,7 +60,7 @@ export class BasicMessagesModule { * @param query The query * @returns array containing all matching records */ - public async findAllByQuery(query: Partial) { + public async findAllByQuery(query: Query) { return this.basicMessageService.findAllByQuery(query) } diff --git a/packages/core/src/modules/basic-messages/services/BasicMessageService.ts b/packages/core/src/modules/basic-messages/services/BasicMessageService.ts index 7388e74e82..116376de25 100644 --- a/packages/core/src/modules/basic-messages/services/BasicMessageService.ts +++ b/packages/core/src/modules/basic-messages/services/BasicMessageService.ts @@ -1,7 +1,7 @@ import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { Query } from '../../../storage/StorageService' import type { ConnectionRecord } from '../../connections/repository/ConnectionRecord' import type { BasicMessageStateChangedEvent } from '../BasicMessageEvents' -import type { BasicMessageTags } from '../repository' import { EventEmitter } from '../../../agent/EventEmitter' import { injectable } from '../../../plugins' @@ -61,7 +61,7 @@ export class BasicMessageService { }) } - public async findAllByQuery(query: Partial) { + public async findAllByQuery(query: Query) { return this.basicMessageRepository.findByQuery(query) } diff --git a/packages/core/src/modules/connections/ConnectionsModule.ts b/packages/core/src/modules/connections/ConnectionsModule.ts index 476288a96b..7195d83ca3 100644 --- a/packages/core/src/modules/connections/ConnectionsModule.ts +++ b/packages/core/src/modules/connections/ConnectionsModule.ts @@ -1,4 +1,5 @@ import type { DependencyManager } from '../../plugins' +import type { Query } from '../../storage/StorageService' import type { Key } from '../dids' import type { OutOfBandRecord } from '../oob/repository' import type { ConnectionType } from './models' @@ -198,6 +199,15 @@ export class ConnectionsModule { return this.connectionService.getAll() } + /** + * Retrieve all connections records by specified query params + * + * @returns List containing all connection records matching specified query paramaters + */ + public findAllByQuery(query: Query) { + return this.connectionService.findAllByQuery(query) + } + /** * Allows for the addition of connectionType to the record. * Either updates or creates an array of string conection types diff --git a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index 97ef3fbd3d..c21e90e226 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -897,5 +897,15 @@ describe('ConnectionService', () => { expect(result).toEqual(expect.arrayContaining(expected)) }) + + it('findAllByQuery should return value from connectionRepository.findByQuery', async () => { + const expected = [getMockConnection(), getMockConnection()] + + mockFunction(connectionRepository.findByQuery).mockReturnValue(Promise.resolve(expected)) + const result = await connectionService.findAllByQuery({ state: DidExchangeState.InvitationReceived }) + expect(connectionRepository.findByQuery).toBeCalledWith({ state: DidExchangeState.InvitationReceived }) + + expect(result).toEqual(expect.arrayContaining(expected)) + }) }) }) diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index d7fc004881..5b7de49125 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -1,6 +1,7 @@ import type { AgentMessage } from '../../../agent/AgentMessage' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { Logger } from '../../../logger' +import type { Query } from '../../../storage/StorageService' import type { AckMessage } from '../../common' import type { OutOfBandDidCommService } from '../../oob/domain/OutOfBandDidCommService' import type { OutOfBandRecord } from '../../oob/repository' @@ -592,6 +593,10 @@ export class ConnectionService { return this.connectionRepository.findByQuery({ invitationDid }) } + public async findAllByQuery(query: Query): Promise { + return this.connectionRepository.findByQuery(query) + } + public async createConnection(options: ConnectionRecordProps): Promise { const connectionRecord = new ConnectionRecord(options) await this.connectionRepository.save(connectionRecord) diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index 6c74598b0b..f82018bf95 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -1,6 +1,7 @@ import type { AgentMessage } from '../../agent/AgentMessage' import type { Logger } from '../../logger' import type { DependencyManager } from '../../plugins' +import type { Query } from '../../storage/StorageService' import type { DeleteCredentialOptions } from './CredentialServiceOptions' import type { AcceptCredentialOptions, @@ -73,6 +74,7 @@ export interface CredentialsModule + findAllByQuery(query: Query): Promise getById(credentialRecordId: string): Promise findById(credentialRecordId: string): Promise deleteById(credentialRecordId: string, options?: DeleteCredentialOptions): Promise @@ -573,6 +575,15 @@ export class CredentialsModule< return this.credentialRepository.getAll() } + /** + * Retrieve all credential records by specified query params + * + * @returns List containing all credential records matching specified query paramaters + */ + public findAllByQuery(query: Query) { + return this.credentialRepository.findByQuery(query) + } + /** * Find a credential record by id * diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts index e0188dd352..4dff8e928a 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts @@ -811,6 +811,16 @@ describe('V1CredentialService', () => { expect(result).toEqual(expect.arrayContaining(expected)) }) + + it('findAllByQuery should return value from credentialRepository.findByQuery', async () => { + const expected = [mockCredentialRecord(), mockCredentialRecord()] + + mockFunction(credentialRepository.findByQuery).mockReturnValue(Promise.resolve(expected)) + const result = await credentialService.findAllByQuery({ state: CredentialState.OfferSent }) + expect(credentialRepository.findByQuery).toBeCalledWith({ state: CredentialState.OfferSent }) + + expect(result).toEqual(expect.arrayContaining(expected)) + }) }) describe('deleteCredential', () => { diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts index a3dbe65866..2ac5b3580c 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts @@ -755,6 +755,16 @@ describe('CredentialService', () => { expect(result).toEqual(expect.arrayContaining(expected)) }) + + it('findAllByQuery should return value from credentialRepository.findByQuery', async () => { + const expected = [mockCredentialRecord(), mockCredentialRecord()] + + mockFunction(credentialRepository.findByQuery).mockReturnValue(Promise.resolve(expected)) + const result = await credentialService.findAllByQuery({ state: CredentialState.OfferSent }) + expect(credentialRepository.findByQuery).toBeCalledWith({ state: CredentialState.OfferSent }) + + expect(result).toEqual(expect.arrayContaining(expected)) + }) }) describe('deleteCredential', () => { diff --git a/packages/core/src/modules/credentials/services/CredentialService.ts b/packages/core/src/modules/credentials/services/CredentialService.ts index 2e305864ef..a0be67c7bf 100644 --- a/packages/core/src/modules/credentials/services/CredentialService.ts +++ b/packages/core/src/modules/credentials/services/CredentialService.ts @@ -5,6 +5,7 @@ import type { EventEmitter } from '../../../agent/EventEmitter' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { Logger } from '../../../logger' import type { DidCommMessageRepository } from '../../../storage' +import type { Query } from '../../../storage/StorageService' import type { ProblemReportMessage } from '../../problem-reports' import type { CredentialStateChangedEvent } from '../CredentialEvents' import type { @@ -185,6 +186,10 @@ export abstract class CredentialService): Promise { + return this.credentialRepository.findByQuery(query) + } + /** * Find a credential record by id * diff --git a/packages/core/src/modules/generic-records/GenericRecordsModule.ts b/packages/core/src/modules/generic-records/GenericRecordsModule.ts index 579536848f..c52f9daf30 100644 --- a/packages/core/src/modules/generic-records/GenericRecordsModule.ts +++ b/packages/core/src/modules/generic-records/GenericRecordsModule.ts @@ -1,6 +1,7 @@ import type { Logger } from '../../logger' import type { DependencyManager } from '../../plugins' -import type { GenericRecord, GenericRecordTags, SaveGenericRecordOption } from './repository/GenericRecord' +import type { Query } from '../../storage/StorageService' +import type { GenericRecord, SaveGenericRecordOption } from './repository/GenericRecord' import { AgentConfig } from '../../agent/AgentConfig' import { injectable, module } from '../../plugins' @@ -74,7 +75,7 @@ export class GenericRecordsModule { return this.genericRecordsService.findById(id) } - public async findAllByQuery(query: Partial): Promise { + public async findAllByQuery(query: Query): Promise { return this.genericRecordsService.findAllByQuery(query) } diff --git a/packages/core/src/modules/generic-records/service/GenericRecordService.ts b/packages/core/src/modules/generic-records/service/GenericRecordService.ts index e27f818e86..397f0b44f0 100644 --- a/packages/core/src/modules/generic-records/service/GenericRecordService.ts +++ b/packages/core/src/modules/generic-records/service/GenericRecordService.ts @@ -1,4 +1,5 @@ -import type { GenericRecordTags, SaveGenericRecordOption } from '../repository/GenericRecord' +import type { Query } from '../../../storage/StorageService' +import type { SaveGenericRecordOption } from '../repository/GenericRecord' import { AriesFrameworkError } from '../../../error' import { injectable } from '../../../plugins' @@ -50,7 +51,7 @@ export class GenericRecordService { } } - public async findAllByQuery(query: Partial) { + public async findAllByQuery(query: Query) { return this.genericRecordsRepository.findByQuery(query) } diff --git a/packages/core/src/modules/oob/OutOfBandModule.ts b/packages/core/src/modules/oob/OutOfBandModule.ts index a513fea180..cdd722aefc 100644 --- a/packages/core/src/modules/oob/OutOfBandModule.ts +++ b/packages/core/src/modules/oob/OutOfBandModule.ts @@ -4,6 +4,7 @@ import type { Attachment } from '../../decorators/attachment/Attachment' import type { Logger } from '../../logger' import type { ConnectionRecord, Routing, ConnectionInvitationMessage } from '../../modules/connections' import type { DependencyManager } from '../../plugins' +import type { Query } from '../../storage/StorageService' import type { PlaintextMessage } from '../../types' import type { Key } from '../dids' import type { HandshakeReusedEvent } from './domain/OutOfBandEvents' @@ -533,6 +534,15 @@ export class OutOfBandModule { return this.outOfBandService.getAll() } + /** + * Retrieve all out of bands records by specified query param + * + * @returns List containing all out of band records matching specified query params + */ + public findAllByQuery(query: Query) { + return this.outOfBandService.findAllByQuery(query) + } + /** * Retrieve a out of band record by id * diff --git a/packages/core/src/modules/oob/OutOfBandService.ts b/packages/core/src/modules/oob/OutOfBandService.ts index b84d332e0f..85a2c64a49 100644 --- a/packages/core/src/modules/oob/OutOfBandService.ts +++ b/packages/core/src/modules/oob/OutOfBandService.ts @@ -1,4 +1,5 @@ import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' +import type { Query } from '../../storage/StorageService' import type { ConnectionRecord } from '../connections' import type { Key } from '../dids/domain/Key' import type { HandshakeReusedEvent, OutOfBandStateChangedEvent } from './domain/OutOfBandEvents' @@ -167,6 +168,10 @@ export class OutOfBandService { return this.outOfBandRepository.getAll() } + public async findAllByQuery(query: Query) { + return this.outOfBandRepository.findByQuery(query) + } + public async deleteById(outOfBandId: string) { const outOfBandRecord = await this.getById(outOfBandId) return this.outOfBandRepository.delete(outOfBandRecord) diff --git a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts index 9bfd317ddb..9f1dfca323 100644 --- a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts +++ b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts @@ -495,5 +495,15 @@ describe('OutOfBandService', () => { expect(result).toEqual(expect.arrayContaining(expected)) }) + + it('findAllByQuery should return value from outOfBandRepository.findByQuery', async () => { + const expected = [getMockOutOfBand(), getMockOutOfBand()] + + mockFunction(outOfBandRepository.findByQuery).mockReturnValue(Promise.resolve(expected)) + const result = await outOfBandService.findAllByQuery({ state: OutOfBandState.Initial }) + expect(outOfBandRepository.findByQuery).toBeCalledWith({ state: OutOfBandState.Initial }) + + expect(result).toEqual(expect.arrayContaining(expected)) + }) }) }) diff --git a/packages/core/src/modules/proofs/ProofsModule.ts b/packages/core/src/modules/proofs/ProofsModule.ts index 613a4928ca..bf14e6f2e9 100644 --- a/packages/core/src/modules/proofs/ProofsModule.ts +++ b/packages/core/src/modules/proofs/ProofsModule.ts @@ -1,4 +1,5 @@ import type { DependencyManager } from '../../plugins' +import type { Query } from '../../storage/StorageService' import type { AutoAcceptProof } from './ProofAutoAcceptType' import type { PresentationPreview, RequestPresentationMessage } from './messages' import type { RequestedCredentials, RetrievedCredentials } from './models' @@ -414,6 +415,15 @@ export class ProofsModule { return this.proofService.getAll() } + /** + * Retrieve all proof records by specified query params + * + * @returns List containing all proof records matching specified params + */ + public findAllByQuery(query: Query): Promise { + return this.proofService.findAllByQuery(query) + } + /** * Retrieve a proof record by id * diff --git a/packages/core/src/modules/proofs/services/ProofService.ts b/packages/core/src/modules/proofs/services/ProofService.ts index 0f1a721c6a..94b5b935ea 100644 --- a/packages/core/src/modules/proofs/services/ProofService.ts +++ b/packages/core/src/modules/proofs/services/ProofService.ts @@ -1,6 +1,7 @@ import type { AgentMessage } from '../../../agent/AgentMessage' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { Logger } from '../../../logger' +import type { Query } from '../../../storage/StorageService' import type { ConnectionRecord } from '../../connections' import type { AutoAcceptProof } from '../ProofAutoAcceptType' import type { ProofStateChangedEvent } from '../ProofEvents' @@ -938,6 +939,15 @@ export class ProofService { return this.proofRepository.getAll() } + /** + * Retrieve all proof records + * + * @returns List containing all proof records + */ + public async findAllByQuery(query: Query): Promise { + return this.proofRepository.findByQuery(query) + } + /** * Retrieve a proof record by id * diff --git a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts b/packages/core/src/modules/question-answer/QuestionAnswerModule.ts index 59350a2334..103df18952 100644 --- a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts +++ b/packages/core/src/modules/question-answer/QuestionAnswerModule.ts @@ -1,4 +1,6 @@ import type { DependencyManager } from '../../plugins' +import type { Query } from '../../storage/StorageService' +import type { QuestionAnswerRecord } from './repository' import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' @@ -92,6 +94,15 @@ export class QuestionAnswerModule { return this.questionAnswerService.getAll() } + /** + * Get all QuestionAnswer records by specified query params + * + * @returns list containing all QuestionAnswer records matching specified query params + */ + public findAllByQuery(query: Query) { + return this.questionAnswerService.findAllByQuery(query) + } + /** * Retrieve a question answer record by id * diff --git a/packages/core/src/modules/question-answer/services/QuestionAnswerService.ts b/packages/core/src/modules/question-answer/services/QuestionAnswerService.ts index 15495a5c89..6217fe2faf 100644 --- a/packages/core/src/modules/question-answer/services/QuestionAnswerService.ts +++ b/packages/core/src/modules/question-answer/services/QuestionAnswerService.ts @@ -1,8 +1,8 @@ import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { Logger } from '../../../logger' +import type { Query } from '../../../storage/StorageService' import type { QuestionAnswerStateChangedEvent } from '../QuestionAnswerEvents' import type { ValidResponse } from '../models' -import type { QuestionAnswerTags } from '../repository' import { AgentConfig } from '../../../agent/AgentConfig' import { EventEmitter } from '../../../agent/EventEmitter' @@ -269,7 +269,7 @@ export class QuestionAnswerService { return this.questionAnswerRepository.getAll() } - public async findAllByQuery(query: Partial) { + public async findAllByQuery(query: Query) { return this.questionAnswerRepository.findByQuery(query) } } diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index 715cee4e9e..0aed806a5e 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -1,6 +1,7 @@ import type { AgentMessage } from '../../../agent/AgentMessage' import type { AgentMessageReceivedEvent } from '../../../agent/Events' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { Query } from '../../../storage/StorageService' import type { EncryptedMessage } from '../../../types' import type { ConnectionRecord } from '../../connections' import type { Routing } from '../../connections/services/ConnectionService' @@ -365,6 +366,10 @@ export class MediationRecipientService { return this.mediationRepository.getAll() } + public async findAllMediatorsByQuery(query: Query): Promise { + return await this.mediationRepository.findByQuery(query) + } + public async findDefaultMediator(): Promise { return this.mediationRepository.findSingleByQuery({ default: true }) } diff --git a/packages/core/src/modules/routing/services/MediatorService.ts b/packages/core/src/modules/routing/services/MediatorService.ts index 4633555081..e5e319304d 100644 --- a/packages/core/src/modules/routing/services/MediatorService.ts +++ b/packages/core/src/modules/routing/services/MediatorService.ts @@ -1,4 +1,5 @@ import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { Query } from '../../../storage/StorageService' import type { EncryptedMessage } from '../../../types' import type { ConnectionRecord } from '../../connections' import type { MediationStateChangedEvent } from '../RoutingEvents' @@ -201,6 +202,10 @@ export class MediatorService { return await this.mediationRepository.getAll() } + public async findAllByQuery(query: Query): Promise { + return await this.mediationRepository.findByQuery(query) + } + private async updateState(mediationRecord: MediationRecord, newState: MediationState) { const previousState = mediationRecord.state diff --git a/packages/core/src/storage/StorageService.ts b/packages/core/src/storage/StorageService.ts index 87360ab330..3e8b99a090 100644 --- a/packages/core/src/storage/StorageService.ts +++ b/packages/core/src/storage/StorageService.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { Constructor } from '../utils/mixins' import type { BaseRecord, TagsBase } from './BaseRecord' @@ -10,13 +11,12 @@ interface AdvancedQuery { $not?: Query } -export type Query = AdvancedQuery | SimpleQuery +export type Query> = AdvancedQuery | SimpleQuery export interface BaseRecordConstructor extends Constructor { type: string } -// eslint-disable-next-line @typescript-eslint/no-explicit-any export interface StorageService> { /** * Save record in storage diff --git a/samples/extension-module/dummy/DummyApi.ts b/samples/extension-module/dummy/DummyApi.ts index b15735148a..93d716e130 100644 --- a/samples/extension-module/dummy/DummyApi.ts +++ b/samples/extension-module/dummy/DummyApi.ts @@ -1,4 +1,5 @@ import type { DummyRecord } from './repository/DummyRecord' +import type { Query } from '@aries-framework/core' import { injectable, ConnectionService, Dispatcher, MessageSender } from '@aries-framework/core' @@ -69,6 +70,15 @@ export class DummyApi { return this.dummyService.getAll() } + /** + * Retrieve all dummy records + * + * @returns List containing all records + */ + public findAllByQuery(query: Query): Promise { + return this.dummyService.findAllByQuery(query) + } + private registerHandlers(dispatcher: Dispatcher) { dispatcher.registerHandler(new DummyRequestHandler(this.dummyService)) dispatcher.registerHandler(new DummyResponseHandler(this.dummyService)) diff --git a/samples/extension-module/dummy/services/DummyService.ts b/samples/extension-module/dummy/services/DummyService.ts index 3cc73eba9f..96f0eb4edb 100644 --- a/samples/extension-module/dummy/services/DummyService.ts +++ b/samples/extension-module/dummy/services/DummyService.ts @@ -1,5 +1,5 @@ import type { DummyStateChangedEvent } from './DummyEvents' -import type { ConnectionRecord, InboundMessageContext } from '@aries-framework/core' +import type { Query, ConnectionRecord, InboundMessageContext } from '@aries-framework/core' import { injectable, JsonTransformer, EventEmitter } from '@aries-framework/core' @@ -119,6 +119,15 @@ export class DummyService { return this.dummyRepository.getAll() } + /** + * Retrieve dummy records by query + * + * @returns List containing all dummy records matching query + */ + public findAllByQuery(query: Query): Promise { + return this.dummyRepository.findByQuery(query) + } + /** * Retrieve a dummy record by id * From 8a89ad2624922e5e5455f8881d1ccc656d6b33ec Mon Sep 17 00:00:00 2001 From: an-uhryn <55444541+an-uhryn@users.noreply.github.com> Date: Fri, 7 Oct 2022 02:49:37 +0200 Subject: [PATCH 047/125] feat: possibility to set masterSecretId inside of WalletConfig (#1043) Signed-off-by: Andrii Uhryn --- packages/core/src/types.ts | 1 + packages/core/src/wallet/IndyWallet.ts | 6 ++-- packages/core/src/wallet/Wallet.test.ts | 38 +++++++++++++++++++++++-- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 75f00cde88..f1cdd9b0e0 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -27,6 +27,7 @@ export interface WalletConfig { type: string [key: string]: unknown } + masterSecretId?: string } export interface WalletConfigRekey { diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts index 5411041ae2..9b200eb67c 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/core/src/wallet/IndyWallet.ts @@ -56,13 +56,13 @@ export class IndyWallet implements Wallet { } public get masterSecretId() { - if (!this.isInitialized || !this.walletConfig?.id) { + if (!this.isInitialized || !(this.walletConfig?.id || this.walletConfig?.masterSecretId)) { throw new AriesFrameworkError( 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' ) } - return this.walletConfig.id + return this.walletConfig?.masterSecretId ?? this.walletConfig.id } private walletStorageConfig(walletConfig: WalletConfig): Indy.WalletConfig { @@ -124,7 +124,7 @@ export class IndyWallet implements Wallet { await this.open(walletConfig) // We need to open wallet before creating master secret because we need wallet handle here. - await this.createMasterSecret(this.handle, walletConfig.id) + await this.createMasterSecret(this.handle, this.masterSecretId) } catch (error) { // If an error ocurred while creating the master secret, we should close the wallet if (this.isInitialized) await this.close() diff --git a/packages/core/src/wallet/Wallet.test.ts b/packages/core/src/wallet/Wallet.test.ts index 6d6a85da7d..49753b8791 100644 --- a/packages/core/src/wallet/Wallet.test.ts +++ b/packages/core/src/wallet/Wallet.test.ts @@ -1,13 +1,29 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { AgentConfig } from '../../src' + import { getAgentConfig } from '../../tests/helpers' import { IndyWallet } from './IndyWallet' describe('Wallet', () => { - const config = getAgentConfig('WalletTest') - const wallet = new IndyWallet(config) + let wallet: IndyWallet + let config: AgentConfig + let configWithMasterSecretId: AgentConfig + + beforeEach(() => { + config = getAgentConfig('WalletTest') + configWithMasterSecretId = getAgentConfig('WalletTestWithMasterSecretId', { + walletConfig: { + id: `Wallet: WalletTestWithMasterSecretId`, + key: `Key: WalletTestWithMasterSecretId`, + masterSecretId: 'customMasterSecretId', + }, + }) + }) test('initialize public did', async () => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + wallet = new IndyWallet(config) + await wallet.createAndOpen(config.walletConfig!) await wallet.initPublicDid({ seed: '00000000000000000000000Forward01' }) @@ -18,6 +34,22 @@ describe('Wallet', () => { }) }) + test('masterSecretId is equal to wallet ID by default', async () => { + wallet = new IndyWallet(config) + + await wallet.createAndOpen(config.walletConfig!) + + expect(wallet.masterSecretId).toEqual(config.walletConfig!.id) + }) + + test('masterSecretId is set by config', async () => { + wallet = new IndyWallet(configWithMasterSecretId) + + await wallet.createAndOpen(configWithMasterSecretId.walletConfig!) + + expect(wallet.masterSecretId).toEqual(configWithMasterSecretId.walletConfig!.masterSecretId) + }) + afterEach(async () => { await wallet.delete() }) From df3777ee394211a401940bf27b3e5a9e1688f6b2 Mon Sep 17 00:00:00 2001 From: Mo <10432473+morrieinmaas@users.noreply.github.com> Date: Fri, 7 Oct 2022 12:43:08 +0200 Subject: [PATCH 048/125] feat: add indynamespace for ledger id for anoncreds (#965) Signed-off-by: Moriarty --- .../setup-postgres-wallet-plugin/action.yml | 4 ++ Dockerfile | 3 + demo/src/BaseAgent.ts | 1 + docs/getting-started/ledger.md | 2 + .../indy/IndyCredentialFormatService.ts | 1 + .../dids/__tests__/dids-registrar.e2e.test.ts | 3 +- .../dids/methods/sov/SovDidRegistrar.ts | 7 +- .../sov/__tests__/SovDidRegistrar.test.ts | 6 +- .../AnonCredsCredentialDefinitionRecord.ts | 16 ++--- .../indy/repository/AnonCredsSchemaRecord.ts | 4 ++ packages/core/src/modules/ledger/IndyPool.ts | 6 ++ packages/core/src/modules/ledger/LedgerApi.ts | 67 ++++++++++++++++--- .../__tests__/IndyLedgerService.test.ts | 3 +- .../ledger/__tests__/IndyPoolService.test.ts | 8 ++- .../ledger/__tests__/LedgerApi.test.ts | 67 +++++++++++++------ .../ledger/__tests__/ledgerUtils.test.ts | 16 ----- .../core/src/modules/ledger/ledgerUtil.ts | 8 --- .../ledger/services/IndyLedgerService.ts | 4 ++ .../utils/__tests__/indyIdentifiers.test.ts | 62 +++++++++++++++++ packages/core/src/utils/index.ts | 1 + packages/core/src/utils/indyIdentifiers.ts | 53 +++++++++++++++ packages/core/tests/helpers.ts | 2 + packages/core/tests/ledger.test.ts | 1 + packages/module-tenants/src/TenantsModule.ts | 3 +- 24 files changed, 269 insertions(+), 79 deletions(-) create mode 100644 packages/core/src/utils/__tests__/indyIdentifiers.test.ts create mode 100644 packages/core/src/utils/indyIdentifiers.ts diff --git a/.github/actions/setup-postgres-wallet-plugin/action.yml b/.github/actions/setup-postgres-wallet-plugin/action.yml index 7ac41af866..a03b2f3fde 100644 --- a/.github/actions/setup-postgres-wallet-plugin/action.yml +++ b/.github/actions/setup-postgres-wallet-plugin/action.yml @@ -5,11 +5,15 @@ author: 'sairanjit.tummalapalli@ayanworks.com' runs: using: composite steps: + # cargo build failing on latest release of rust due to + # socket2 dependency in the plugin https://users.rust-lang.org/t/build-broken-with-parse-quote-spanned-is-ambiguous/80280/2 + # so pointing rust version to 1.63.0 - name: Setup Postgres wallet plugin run: | sudo apt-get install -y libzmq3-dev libsodium-dev pkg-config libssl-dev curl https://sh.rustup.rs -sSf | bash -s -- -y export PATH="/root/.cargo/bin:${PATH}" + rustup default 1.63.0 cd ../ git clone https://github.com/hyperledger/indy-sdk.git cd indy-sdk/experimental/plugins/postgres_storage/ diff --git a/Dockerfile b/Dockerfile index fa261eea80..91ccda0363 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,9 @@ RUN apt-get install -y --no-install-recommends yarn RUN curl https://sh.rustup.rs -sSf | bash -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" +# cargo build failing on latest release of rust due to socket2 dependency in the plugin https://users.rust-lang.org/t/build-broken-with-parse-quote-spanned-is-ambiguous/80280/2 so pointing rust version to 1.63.0 +RUN rustup default 1.63.0 + # clone indy-sdk and build postgres plugin RUN git clone https://github.com/hyperledger/indy-sdk.git WORKDIR /indy-sdk/experimental/plugins/postgres_storage/ diff --git a/demo/src/BaseAgent.ts b/demo/src/BaseAgent.ts index efc4260103..abf507014e 100644 --- a/demo/src/BaseAgent.ts +++ b/demo/src/BaseAgent.ts @@ -31,6 +31,7 @@ export class BaseAgent { { genesisTransactions: bcovrin, id: 'greenlights' + name, + indyNamespace: 'greenlights' + name, isProduction: false, }, ], diff --git a/docs/getting-started/ledger.md b/docs/getting-started/ledger.md index 2cc976c083..a79308d53b 100644 --- a/docs/getting-started/ledger.md +++ b/docs/getting-started/ledger.md @@ -18,11 +18,13 @@ const agentConfig: InitConfig = { indyLedgers: [ { id: 'sovrin-main', + didIndyNamespace: 'sovrin', isProduction: true, genesisPath: './genesis/sovrin-main.txn', }, { id: 'bcovrin-test', + didIndyNamespace: 'bcovrin:test', isProduction: false, genesisTransactions: 'XXXX', }, diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts index 491cde0be0..2a29055c1d 100644 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts @@ -232,6 +232,7 @@ export class IndyCredentialFormatService extends CredentialFormatService { qualifiedIndyDid: `did:indy:localhost:${indyDid}`, }, didRegistrationMetadata: { - indyNamespace: 'localhost', + didIndyNamespace: 'localhost', }, didState: { state: 'finished', diff --git a/packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts b/packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts index cf5350ccbd..a1d83176b1 100644 --- a/packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts +++ b/packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts @@ -102,9 +102,8 @@ export class SovDidRegistrar implements DidRegistrar { // Build did document. const didDocument = didDocumentBuilder.build() - // FIXME: we need to update this to the `indyNamespace` once https://github.com/hyperledger/aries-framework-javascript/issues/944 has been resolved - const indyNamespace = this.indyPoolService.ledgerWritePool.config.id - const qualifiedIndyDid = `did:indy:${indyNamespace}:${unqualifiedIndyDid}` + const didIndyNamespace = this.indyPoolService.ledgerWritePool.config.indyNamespace + const qualifiedIndyDid = `did:indy:${didIndyNamespace}:${unqualifiedIndyDid}` // Save the did so we know we created it and can issue with it const didRecord = new DidRecord({ @@ -122,7 +121,7 @@ export class SovDidRegistrar implements DidRegistrar { qualifiedIndyDid, }, didRegistrationMetadata: { - indyNamespace, + didIndyNamespace, }, didState: { state: 'finished', diff --git a/packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts index e2a3041652..073b67a3e8 100644 --- a/packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts +++ b/packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts @@ -21,7 +21,7 @@ const IndyLedgerServiceMock = IndyLedgerService as jest.Mock jest.mock('../../../../ledger/services/IndyPoolService') const IndyPoolServiceMock = IndyPoolService as jest.Mock const indyPoolServiceMock = new IndyPoolServiceMock() -mockProperty(indyPoolServiceMock, 'ledgerWritePool', { config: { id: 'pool1' } } as IndyPool) +mockProperty(indyPoolServiceMock, 'ledgerWritePool', { config: { id: 'pool1', indyNamespace: 'pool1' } } as IndyPool) const agentConfig = getAgentConfig('SovDidRegistrar') @@ -148,7 +148,7 @@ describe('DidRegistrar', () => { qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', }, didRegistrationMetadata: { - indyNamespace: 'pool1', + didIndyNamespace: 'pool1', }, didState: { state: 'finished', @@ -222,7 +222,7 @@ describe('DidRegistrar', () => { qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', }, didRegistrationMetadata: { - indyNamespace: 'pool1', + didIndyNamespace: 'pool1', }, didState: { state: 'finished', diff --git a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts index 730262447f..699abb6148 100644 --- a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts +++ b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts @@ -1,28 +1,23 @@ import type { CredDef } from 'indy-sdk' import { BaseRecord } from '../../../storage/BaseRecord' -import { didFromCredentialDefinitionId } from '../../../utils/did' +import { uuid } from '../../../utils/uuid' export interface AnonCredsCredentialDefinitionRecordProps { credentialDefinition: CredDef } -export type DefaultAnonCredsCredentialDefinitionTags = { - credentialDefinitionId: string - issuerDid: string - schemaId: string - tag: string -} - -export class AnonCredsCredentialDefinitionRecord extends BaseRecord { +export class AnonCredsCredentialDefinitionRecord extends BaseRecord { public static readonly type = 'AnonCredsCredentialDefinitionRecord' public readonly type = AnonCredsCredentialDefinitionRecord.type + public readonly credentialDefinition!: CredDef public constructor(props: AnonCredsCredentialDefinitionRecordProps) { super() if (props) { + this.id = uuid() this.credentialDefinition = props.credentialDefinition } } @@ -31,9 +26,6 @@ export class AnonCredsCredentialDefinitionRecord extends BaseRecord { public static readonly type = 'AnonCredsSchemaRecord' public readonly type = AnonCredsSchemaRecord.type + public readonly schema!: Schema public constructor(props: AnonCredsSchemaRecordProps) { super() if (props) { + this.id = props.id ?? uuid() this.schema = props.schema } } diff --git a/packages/core/src/modules/ledger/IndyPool.ts b/packages/core/src/modules/ledger/IndyPool.ts index a6bd99c6e0..853b253b0e 100644 --- a/packages/core/src/modules/ledger/IndyPool.ts +++ b/packages/core/src/modules/ledger/IndyPool.ts @@ -1,6 +1,7 @@ import type { AgentDependencies } from '../../agent/AgentDependencies' import type { Logger } from '../../logger' import type { FileSystem } from '../../storage/FileSystem' +import type { DidIndyNamespace } from '../../utils/indyIdentifiers' import type * as Indy from 'indy-sdk' import type { Subject } from 'rxjs' @@ -20,6 +21,7 @@ export interface IndyPoolConfig { genesisTransactions?: string id: string isProduction: boolean + indyNamespace: DidIndyNamespace transactionAuthorAgreement?: TransactionAuthorAgreement } @@ -52,6 +54,10 @@ export class IndyPool { }) } + public get didIndyNamespace(): string { + return this.didIndyNamespace + } + public get id() { return this.poolConfig.id } diff --git a/packages/core/src/modules/ledger/LedgerApi.ts b/packages/core/src/modules/ledger/LedgerApi.ts index 07a535ff47..1cf2deef4f 100644 --- a/packages/core/src/modules/ledger/LedgerApi.ts +++ b/packages/core/src/modules/ledger/LedgerApi.ts @@ -7,11 +7,18 @@ import { AriesFrameworkError } from '../../error' import { IndySdkError } from '../../error/IndySdkError' import { injectable } from '../../plugins' import { isIndyError } from '../../utils/indyError' +import { + getLegacyCredentialDefinitionId, + getLegacySchemaId, + getQualifiedIndyCredentialDefinitionId, + getQualifiedIndySchemaId, +} from '../../utils/indyIdentifiers' +import { AnonCredsCredentialDefinitionRecord } from '../indy/repository/AnonCredsCredentialDefinitionRecord' import { AnonCredsCredentialDefinitionRepository } from '../indy/repository/AnonCredsCredentialDefinitionRepository' +import { AnonCredsSchemaRecord } from '../indy/repository/AnonCredsSchemaRecord' import { AnonCredsSchemaRepository } from '../indy/repository/AnonCredsSchemaRepository' import { LedgerModuleConfig } from './LedgerModuleConfig' -import { generateCredentialDefinitionId, generateSchemaId } from './ledgerUtil' import { IndyLedgerService } from './services' @injectable() @@ -73,16 +80,33 @@ export class LedgerApi { throw new AriesFrameworkError('Agent has no public DID.') } - const schemaId = generateSchemaId(did, schema.name, schema.version) + const schemaId = getLegacySchemaId(did, schema.name, schema.version) + + // Generate the qualified ID + const qualifiedIdentifier = getQualifiedIndySchemaId(this.ledgerService.getDidIndyWriteNamespace(), schemaId) // Try find the schema in the wallet - const schemaRecord = await this.anonCredsSchemaRepository.findBySchemaId(this.agentContext, schemaId) - // Schema in wallet - if (schemaRecord) return schemaRecord.schema + const schemaRecord = await this.anonCredsSchemaRepository.findById(this.agentContext, qualifiedIdentifier) + // Schema in wallet + if (schemaRecord) { + // Transform qualified to unqualified + return { + ...schemaRecord.schema, + id: schemaId, + } + } const schemaFromLedger = await this.findBySchemaIdOnLedger(schemaId) + if (schemaFromLedger) return schemaFromLedger - return this.ledgerService.registerSchema(this.agentContext, did, schema) + const createdSchema = await this.ledgerService.registerSchema(this.agentContext, did, schema) + + const anonCredsSchema = new AnonCredsSchemaRecord({ + schema: { ...createdSchema, id: qualifiedIdentifier }, + }) + await this.anonCredsSchemaRepository.save(this.agentContext, anonCredsSchema) + + return createdSchema } private async findBySchemaIdOnLedger(schemaId: string) { @@ -115,18 +139,32 @@ export class LedgerApi { } // Construct credential definition ID - const credentialDefinitionId = generateCredentialDefinitionId( + const credentialDefinitionId = getLegacyCredentialDefinitionId( did, credentialDefinitionTemplate.schema.seqNo, credentialDefinitionTemplate.tag ) + // Construct qualified identifier + const qualifiedIdentifier = getQualifiedIndyCredentialDefinitionId( + this.ledgerService.getDidIndyWriteNamespace(), + credentialDefinitionId + ) + // Check if the credential exists in wallet. If so, return it - const credentialDefinitionRecord = await this.anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId( + const credentialDefinitionRecord = await this.anonCredsCredentialDefinitionRepository.findById( this.agentContext, - credentialDefinitionId + qualifiedIdentifier ) - if (credentialDefinitionRecord) return credentialDefinitionRecord.credentialDefinition + + // Credential Definition in wallet + if (credentialDefinitionRecord) { + // Transform qualified to unqualified + return { + ...credentialDefinitionRecord.credentialDefinition, + id: credentialDefinitionId, + } + } // Check for the credential on the ledger. const credentialDefinitionOnLedger = await this.findByCredentialDefinitionIdOnLedger(credentialDefinitionId) @@ -137,10 +175,17 @@ export class LedgerApi { } // Register the credential - return await this.ledgerService.registerCredentialDefinition(this.agentContext, did, { + const registeredDefinition = await this.ledgerService.registerCredentialDefinition(this.agentContext, did, { ...credentialDefinitionTemplate, signatureType: 'CL', }) + // Replace the unqualified with qualified Identifier in anonCred + const anonCredCredential = new AnonCredsCredentialDefinitionRecord({ + credentialDefinition: { ...registeredDefinition, id: qualifiedIdentifier }, + }) + await this.anonCredsCredentialDefinitionRepository.save(this.agentContext, anonCredCredential) + + return registeredDefinition } public async getCredentialDefinition(id: string) { diff --git a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts b/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts index 74636089b3..84563e5344 100644 --- a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts +++ b/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts @@ -23,7 +23,8 @@ const CacheRepositoryMock = CacheRepository as jest.Mock const pools: IndyPoolConfig[] = [ { - id: 'sovrinMain', + id: 'sovrin', + indyNamespace: 'sovrin', isProduction: true, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, diff --git a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts b/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts index 3e6a65b96b..1cdae2af73 100644 --- a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts +++ b/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts @@ -24,30 +24,35 @@ const CacheRepositoryMock = CacheRepository as jest.Mock const pools: IndyPoolConfig[] = [ { id: 'sovrinMain', + indyNamespace: 'sovrin', isProduction: true, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { id: 'sovrinBuilder', + indyNamespace: 'sovrin:builder', isProduction: false, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { - id: 'sovrinStaging', + id: 'sovringStaging', + indyNamespace: 'sovrin:staging', isProduction: false, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { id: 'indicioMain', + indyNamespace: 'indicio', isProduction: true, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { id: 'bcovrinTest', + indyNamespace: 'bcovrin:test', isProduction: false, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, @@ -280,6 +285,7 @@ describe('IndyPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) expect(pool.config.id).toBe('sovrinBuilder') + expect(pool.config.indyNamespace).toBe('sovrin:builder') const cacheRecord = spy.mock.calls[0][1] expect(cacheRecord.entries.length).toBe(1) diff --git a/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts b/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts index 1df7a5c120..5ca80f5fcd 100644 --- a/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts +++ b/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts @@ -6,6 +6,7 @@ import type * as Indy from 'indy-sdk' import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../tests/helpers' import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' +import { getLegacySchemaId, getLegacyCredentialDefinitionId } from '../../../utils' import { IndyWallet } from '../../../wallet/IndyWallet' import { AnonCredsCredentialDefinitionRecord } from '../../indy/repository/AnonCredsCredentialDefinitionRecord' import { AnonCredsCredentialDefinitionRepository } from '../../indy/repository/AnonCredsCredentialDefinitionRepository' @@ -13,7 +14,6 @@ import { AnonCredsSchemaRecord } from '../../indy/repository/AnonCredsSchemaReco import { AnonCredsSchemaRepository } from '../../indy/repository/AnonCredsSchemaRepository' import { LedgerApi } from '../LedgerApi' import { LedgerModuleConfig } from '../LedgerModuleConfig' -import { generateCredentialDefinitionId, generateSchemaId } from '../ledgerUtil' import { IndyLedgerService } from '../services/IndyLedgerService' jest.mock('../services/IndyLedgerService') @@ -27,7 +27,7 @@ const AnonCredsSchemaRepositoryMock = AnonCredsSchemaRepository as jest.Mock = { - schema: schema, + schema: { ...schema, id: schemaIdQualified }, tag: 'someTag', supportRevocation: true, } @@ -78,9 +82,7 @@ const revocRegDef: Indy.RevocRegDef = { ver: 'abcde', } -const schemaIdGenerated = generateSchemaId(did, schema.name, schema.version) - -const credentialDefinitionId = generateCredentialDefinitionId( +const credentialDefinitionId = getLegacyCredentialDefinitionId( did, credentialDefinitionTemplate.schema.seqNo, credentialDefinitionTemplate.tag @@ -88,7 +90,8 @@ const credentialDefinitionId = generateCredentialDefinitionId( const pools: IndyPoolConfig[] = [ { - id: 'sovrinMain', + id: '7Tqg6BwSSWapxgUDm9KKgg', + indyNamespace: 'sovrin', isProduction: true, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, @@ -210,11 +213,17 @@ describe('LedgerApi', () => { it('should return the schema from anonCreds when it already exists', async () => { mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(anonCredsSchemaRepository.findBySchemaId).mockResolvedValueOnce( - new AnonCredsSchemaRecord({ schema: schema }) + mockFunction(anonCredsSchemaRepository.findById).mockResolvedValueOnce( + new AnonCredsSchemaRecord({ schema: { ...schema, id: schemaIdQualified } }) ) - await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual(schema) - expect(anonCredsSchemaRepository.findBySchemaId).toHaveBeenCalledWith(agentContext, schemaIdGenerated) + mockFunction(ledgerService.getDidIndyWriteNamespace).mockReturnValueOnce(pools[0].indyNamespace) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { id, ...schemaWithoutId } = schema + await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toMatchObject({ + ...schema, + id: schema.id, + }) + expect(anonCredsSchemaRepository.findById).toHaveBeenCalledWith(agentContext, schemaIdQualified) }) it('should return the schema from the ledger when it already exists', async () => { @@ -223,9 +232,13 @@ describe('LedgerApi', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any .spyOn(LedgerApi.prototype as any, 'findBySchemaIdOnLedger') .mockResolvedValueOnce(new AnonCredsSchemaRecord({ schema: schema })) + mockProperty(ledgerApi, 'config', { + connectToIndyLedgersOnStartup: true, + indyLedgers: pools, + } as LedgerModuleConfig) await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toHaveProperty( 'schema', - schema + { ...schema } ) // eslint-disable-next-line @typescript-eslint/no-explicit-any expect(jest.spyOn(LedgerApi.prototype as any, 'findBySchemaIdOnLedger')).toHaveBeenCalledWith(schemaIdGenerated) @@ -234,6 +247,10 @@ describe('LedgerApi', () => { it('should return the schema after registering it', async () => { mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) mockFunction(ledgerService.registerSchema).mockResolvedValueOnce(schema) + mockProperty(ledgerApi, 'config', { + connectToIndyLedgersOnStartup: true, + indyLedgers: pools, + } as LedgerModuleConfig) await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual(schema) expect(ledgerService.registerSchema).toHaveBeenCalledWith(agentContext, did, { ...schema, @@ -256,17 +273,19 @@ describe('LedgerApi', () => { new AnonCredsCredentialDefinitionRecord({ credentialDefinition: credDef, }) - mockFunction(anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId).mockResolvedValueOnce( + mockFunction(anonCredsCredentialDefinitionRepository.findById).mockResolvedValueOnce( anonCredsCredentialDefinitionRecord ) + mockProperty(ledgerApi, 'config', { + connectToIndyLedgersOnStartup: true, + indyLedgers: pools, + } as LedgerModuleConfig) + mockFunction(ledgerService.getDidIndyWriteNamespace).mockReturnValueOnce(pools[0].indyNamespace) await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toHaveProperty( 'value.primary', credentialDefinition ) - expect(anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId).toHaveBeenCalledWith( - agentContext, - credentialDefinitionId - ) + expect(anonCredsCredentialDefinitionRepository.findById).toHaveBeenCalledWith(agentContext, qualifiedDidCred) }) it('should throw an exception if the definition already exists on the ledger', async () => { @@ -275,6 +294,10 @@ describe('LedgerApi', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any .spyOn(LedgerApi.prototype as any, 'findByCredentialDefinitionIdOnLedger') .mockResolvedValueOnce({ credentialDefinition: credentialDefinition }) + mockProperty(ledgerApi, 'config', { + connectToIndyLedgersOnStartup: true, + indyLedgers: pools, + } as LedgerModuleConfig) await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( AriesFrameworkError ) @@ -287,6 +310,10 @@ describe('LedgerApi', () => { it('should register the credential successfully if it is neither in the wallet and neither on the ledger', async () => { mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) mockFunction(ledgerService.registerCredentialDefinition).mockResolvedValueOnce(credDef) + mockProperty(ledgerApi, 'config', { + connectToIndyLedgersOnStartup: true, + indyLedgers: pools, + } as LedgerModuleConfig) await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toEqual(credDef) expect(ledgerService.registerCredentialDefinition).toHaveBeenCalledWith(agentContext, did, { ...credentialDefinitionTemplate, diff --git a/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts b/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts index a27e788ed2..ec33976a63 100644 --- a/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts +++ b/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts @@ -42,20 +42,4 @@ describe('LedgerUtils', () => { } expect(LedgerUtil.isLedgerReqnackResponse(ledgerResponse)).toEqual(false) }) - - // generateSchemaId - it('Should return a valid schema ID given did name and version', () => { - const did = '12345', - name = 'backbench', - version = '420' - expect(LedgerUtil.generateSchemaId(did, name, version)).toEqual('12345:2:backbench:420') - }) - - // generateCredentialDefinitionId - it('Should return a valid schema ID given did name and version', () => { - const did = '12345', - seqNo = 420, - tag = 'someTag' - expect(LedgerUtil.generateCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') - }) }) diff --git a/packages/core/src/modules/ledger/ledgerUtil.ts b/packages/core/src/modules/ledger/ledgerUtil.ts index 6c9cfb1cf8..62e75f1e72 100644 --- a/packages/core/src/modules/ledger/ledgerUtil.ts +++ b/packages/core/src/modules/ledger/ledgerUtil.ts @@ -7,11 +7,3 @@ export function isLedgerRejectResponse(response: Indy.LedgerResponse): response export function isLedgerReqnackResponse(response: Indy.LedgerResponse): response is Indy.LedgerReqnackResponse { return response.op === 'REQNACK' } - -export function generateSchemaId(did: string, name: string, version: string) { - return `${did}:2:${name}:${version}` -} - -export function generateCredentialDefinitionId(did: string, seqNo: number, tag: string) { - return `${did}:3:CL:${seqNo}:${tag}` -} diff --git a/packages/core/src/modules/ledger/services/IndyLedgerService.ts b/packages/core/src/modules/ledger/services/IndyLedgerService.ts index 1e7916a84a..c4c3f4a0f6 100644 --- a/packages/core/src/modules/ledger/services/IndyLedgerService.ts +++ b/packages/core/src/modules/ledger/services/IndyLedgerService.ts @@ -51,6 +51,10 @@ export class IndyLedgerService { return this.indyPoolService.setPools(poolConfigs) } + public getDidIndyWriteNamespace(): string { + return this.indyPoolService.ledgerWritePool.config.indyNamespace + } + public async connectToPools() { return this.indyPoolService.connectToPools() } diff --git a/packages/core/src/utils/__tests__/indyIdentifiers.test.ts b/packages/core/src/utils/__tests__/indyIdentifiers.test.ts new file mode 100644 index 0000000000..8da274a789 --- /dev/null +++ b/packages/core/src/utils/__tests__/indyIdentifiers.test.ts @@ -0,0 +1,62 @@ +import { + isQualifiedIndyIdentifier, + getQualifiedIndyCredentialDefinitionId, + getQualifiedIndySchemaId, + getLegacyCredentialDefinitionId, + getLegacySchemaId, +} from '../indyIdentifiers' + +const indyNamespace = 'some:staging' +const did = 'q7ATwTYbQDgiigVijUAej' +const qualifiedSchemaId = `did:indy:${indyNamespace}:${did}/anoncreds/v0/SCHEMA/awesomeSchema/4.2.0` +const qualifiedCredentialDefinitionId = `did:indy:${indyNamespace}:${did}/anoncreds/v0/CLAIM_DEF/99/sth` +const unqualifiedSchemaId = `${did}:2:awesomeSchema:4.2.0` +const unqualifiedCredentialDefinitionId = `${did}:3:CL:99:sth` + +describe('Mangle indy identifiers', () => { + test('is a qualified identifier', async () => { + expect(isQualifiedIndyIdentifier(qualifiedSchemaId)).toBe(true) + }) + + test('is NOT a qualified identifier', async () => { + expect(isQualifiedIndyIdentifier(did)).toBe(false) + }) + + describe('get the qualified identifier', () => { + it('should return the qualified identifier if the identifier is already qualified', () => { + expect(getQualifiedIndyCredentialDefinitionId(indyNamespace, qualifiedCredentialDefinitionId)).toBe( + qualifiedCredentialDefinitionId + ) + }) + + it('should return the qualified identifier for a credential definition', () => { + expect(getQualifiedIndyCredentialDefinitionId(indyNamespace, unqualifiedCredentialDefinitionId)).toBe( + qualifiedCredentialDefinitionId + ) + }) + + it('should return the qualified identifier for a schema', () => { + expect(getQualifiedIndySchemaId(indyNamespace, qualifiedSchemaId)).toBe(qualifiedSchemaId) + }) + + it('should return the qualified identifier for a schema', () => { + expect(getQualifiedIndySchemaId(indyNamespace, unqualifiedSchemaId)).toBe(qualifiedSchemaId) + }) + }) + + // generateSchemaId + it('Should return a valid schema ID given did name and version', () => { + const did = '12345', + name = 'backbench', + version = '420' + expect(getLegacySchemaId(did, name, version)).toEqual('12345:2:backbench:420') + }) + + // generateCredentialDefinitionId + it('Should return a valid schema ID given did name and version', () => { + const did = '12345', + seqNo = 420, + tag = 'someTag' + expect(getLegacyCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') + }) +}) diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 95ebc0b554..cb4f5d92e7 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -11,3 +11,4 @@ export * from './VarintEncoder' export * from './Hasher' export * from './validators' export * from './type' +export * from './indyIdentifiers' diff --git a/packages/core/src/utils/indyIdentifiers.ts b/packages/core/src/utils/indyIdentifiers.ts new file mode 100644 index 0000000000..0d6343a3e7 --- /dev/null +++ b/packages/core/src/utils/indyIdentifiers.ts @@ -0,0 +1,53 @@ +/** + * + * @see For the definitions below see also: https://hyperledger.github.io/indy-did-method/#indy-did-method-identifiers + * + */ +export type Did = 'did' +export type DidIndyMethod = 'indy' +// Maybe this can be typed more strictly than string. Choosing string for now as this can be eg just `sovrin` or eg `sovrin:staging` +export type DidIndyNamespace = string +// NOTE: because of the ambiguous nature - whether there is a colon or not within DidIndyNamespace this is the substring after the ***last*** colon +export type NamespaceIdentifier = string + +// TODO: This template literal type can possibly be improved. This version leaves the substrings as potentially undefined +export type IndyNamespace = `${Did}:${DidIndyMethod}:${DidIndyNamespace}:${NamespaceIdentifier}` + +export function isQualifiedIndyIdentifier(identifier: string | undefined): identifier is IndyNamespace { + if (!identifier || identifier === '') return false + return identifier.startsWith('did:indy:') +} + +export function getQualifiedIndyCredentialDefinitionId( + indyNamespace: string, + unqualifiedCredentialDefinitionId: string +): IndyNamespace { + if (isQualifiedIndyIdentifier(unqualifiedCredentialDefinitionId)) return unqualifiedCredentialDefinitionId + + // 5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npbd + const [did, , , seqNo, tag] = unqualifiedCredentialDefinitionId.split(':') + + return `did:indy:${indyNamespace}:${did}/anoncreds/v0/CLAIM_DEF/${seqNo}/${tag}` +} + +/** + * + * @see https://hyperledger.github.io/indy-did-method/#schema + * + */ +export function getQualifiedIndySchemaId(indyNamespace: string, schemaId: string): IndyNamespace { + if (isQualifiedIndyIdentifier(schemaId)) return schemaId + + // F72i3Y3Q4i466efjYJYCHM:2:npdb:4.3.4 + const [did, , schemaName, schemaVersion] = schemaId.split(':') + + return `did:indy:${indyNamespace}:${did}/anoncreds/v0/SCHEMA/${schemaName}/${schemaVersion}` +} + +export function getLegacySchemaId(unqualifiedDid: string, name: string, version: string) { + return `${unqualifiedDid}:2:${name}:${version}` +} + +export function getLegacyCredentialDefinitionId(unqualifiedDid: string, seqNo: number, tag: string) { + return `${unqualifiedDid}:3:CL:${seqNo}:${tag}` +} diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index f23e24dbc5..6378f64600 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -88,6 +88,7 @@ export function getAgentOptions(name: string, extraConfig: Partial = id: `pool-${name}`, isProduction: false, genesisPath, + indyNamespace: `pool:localtest`, transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, ], @@ -126,6 +127,7 @@ export function getPostgresAgentOptions(name: string, extraConfig: Partial { indyLedgers: [ { id: 'pool-Faber Ledger Genesis Transactions', + indyNamespace: 'pool-faber-ledger-genesis-transactions', isProduction: false, genesisTransactions, }, diff --git a/packages/module-tenants/src/TenantsModule.ts b/packages/module-tenants/src/TenantsModule.ts index 0d4880d61a..c96dbf92db 100644 --- a/packages/module-tenants/src/TenantsModule.ts +++ b/packages/module-tenants/src/TenantsModule.ts @@ -1,6 +1,5 @@ import type { TenantsModuleConfigOptions } from './TenantsModuleConfig' -import type { ModulesMap, DependencyManager, Module, EmptyModuleMap } from '@aries-framework/core' -import type { Constructor } from '@aries-framework/core' +import type { ModulesMap, DependencyManager, Module, EmptyModuleMap, Constructor } from '@aries-framework/core' import { InjectionSymbols } from '@aries-framework/core' From 991151bfff829fa11cd98a1951be9b54a77385a8 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 7 Oct 2022 13:54:49 +0200 Subject: [PATCH 049/125] feat(bbs): extract bbs logic into separate module (#1035) Signed-off-by: Timo Glastra --- packages/core/package.json | 2 - packages/core/src/agent/Agent.ts | 8 + packages/core/src/crypto/WalletKeyPair.ts | 7 +- packages/core/src/crypto/index.ts | 1 + .../SigningProviderRegistry.ts | 9 +- .../core/src/crypto/signing-provider/index.ts | 1 - packages/core/src/index.ts | 18 +- .../core/src/modules/dids/domain/index.ts | 1 + .../src/modules/dids/domain/key-type/index.ts | 6 + .../src/modules/vc/W3cCredentialService.ts | 5 +- packages/core/src/modules/vc/W3cVcModule.ts | 16 -- .../vc/__tests__/W3cCredentialService.test.ts | 184 +----------- .../modules/vc/__tests__/W3cVcModule.test.ts | 17 +- .../core/src/modules/vc/__tests__/fixtures.ts | 211 +------------- .../{signature-suites/bbs => }/deriveProof.ts | 16 +- packages/core/src/modules/vc/index.ts | 8 + packages/core/src/modules/vc/jsonldUtil.ts | 2 +- .../core/src/modules/vc/libraries/index.ts | 20 ++ .../modules/vc/libraries/jsonld-signatures.ts | 4 +- .../core/src/modules/vc/libraries/jsonld.ts | 2 +- packages/core/src/modules/vc/libraries/vc.ts | 2 +- .../bbs/types => models}/GetProofsOptions.ts | 4 +- .../bbs/types => models}/GetProofsResult.ts | 2 +- .../bbs/types => models}/GetTypeOptions.ts | 4 +- .../vc/models}/LdKeyPair.ts | 2 +- .../vc/models/W3cCredentialServiceOptions.ts | 2 +- packages/core/src/modules/vc/models/index.ts | 8 + .../{W3Presentation.ts => W3cPresentation.ts} | 0 .../presentation/W3cVerifiablePresentation.ts | 4 +- .../src/modules/vc/proof-purposes/index.ts | 2 + .../vc/repository/W3cCredentialRecord.ts | 4 +- .../JwsLinkedDataSignature.ts | 2 +- packages/core/src/wallet/IndyWallet.test.ts | 39 +-- packages/core/src/wallet/IndyWallet.ts | 12 +- packages/core/src/wallet/Wallet.ts | 12 +- packages/core/src/wallet/WalletModule.ts | 5 - packages/core/src/wallet/index.ts | 1 + packages/core/tests/mocks/MockWallet.ts | 12 +- packages/module-bbs/README.md | 31 ++ packages/module-bbs/jest.config.ts | 14 + packages/module-bbs/package.json | 41 +++ packages/module-bbs/src/BbsModule.ts | 35 +++ .../src}/Bls12381g2SigningProvider.ts | 10 +- .../src/__tests__/BbsModule.test.ts | 41 +++ packages/module-bbs/src/index.ts | 4 + .../signature-suites}/BbsBlsSignature2020.ts | 30 +- .../BbsBlsSignatureProof2020.ts | 20 +- .../src/signature-suites}/index.ts | 3 - .../src}/types/CanonizeOptions.ts | 2 +- .../src}/types/CreateProofOptions.ts | 4 +- .../src}/types/CreateVerifyDataOptions.ts | 3 +- .../src}/types/DeriveProofOptions.ts | 3 +- .../src}/types/DidDocumentPublicKey.ts | 0 .../src}/types/JsonWebKey.ts | 0 .../src}/types/KeyPairOptions.ts | 0 .../src}/types/KeyPairSigner.ts | 0 .../src}/types/KeyPairVerifier.ts | 0 .../src}/types/SignatureSuiteOptions.ts | 3 +- .../src}/types/SuiteSignOptions.ts | 3 +- .../src}/types/VerifyProofOptions.ts | 4 +- .../src}/types/VerifyProofResult.ts | 0 .../src}/types/VerifySignatureOptions.ts | 3 +- .../bbs => module-bbs/src}/types/index.ts | 3 - .../tests/bbs-signatures.e2e.test.ts | 267 ++++++++++++++++++ .../tests/bbs-signing-provider.e2e.test.ts | 74 +++++ packages/module-bbs/tests/fixtures.ts | 210 ++++++++++++++ packages/module-bbs/tests/setup.ts | 3 + packages/module-bbs/tests/util.ts | 19 ++ packages/module-bbs/tsconfig.build.json | 9 + packages/module-bbs/tsconfig.json | 6 + yarn.lock | 8 + 71 files changed, 928 insertions(+), 580 deletions(-) rename packages/core/src/modules/vc/{signature-suites/bbs => }/deriveProof.ts (91%) create mode 100644 packages/core/src/modules/vc/libraries/index.ts rename packages/core/src/modules/vc/{signature-suites/bbs/types => models}/GetProofsOptions.ts (91%) rename packages/core/src/modules/vc/{signature-suites/bbs/types => models}/GetProofsResult.ts (93%) rename packages/core/src/modules/vc/{signature-suites/bbs/types => models}/GetTypeOptions.ts (87%) rename packages/core/src/{crypto => modules/vc/models}/LdKeyPair.ts (95%) rename packages/core/src/modules/vc/models/presentation/{W3Presentation.ts => W3cPresentation.ts} (100%) create mode 100644 packages/core/src/modules/vc/proof-purposes/index.ts create mode 100644 packages/module-bbs/README.md create mode 100644 packages/module-bbs/jest.config.ts create mode 100644 packages/module-bbs/package.json create mode 100644 packages/module-bbs/src/BbsModule.ts rename packages/{core/src/crypto/signing-provider => module-bbs/src}/Bls12381g2SigningProvider.ts (92%) create mode 100644 packages/module-bbs/src/__tests__/BbsModule.test.ts create mode 100644 packages/module-bbs/src/index.ts rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src/signature-suites}/BbsBlsSignature2020.ts (95%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src/signature-suites}/BbsBlsSignatureProof2020.ts (95%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src/signature-suites}/index.ts (91%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/CanonizeOptions.ts (94%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/CreateProofOptions.ts (85%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/CreateVerifyDataOptions.ts (90%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/DeriveProofOptions.ts (91%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/DidDocumentPublicKey.ts (100%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/JsonWebKey.ts (100%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/KeyPairOptions.ts (100%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/KeyPairSigner.ts (100%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/KeyPairVerifier.ts (100%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/SignatureSuiteOptions.ts (92%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/SuiteSignOptions.ts (90%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/VerifyProofOptions.ts (84%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/VerifyProofResult.ts (100%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/VerifySignatureOptions.ts (89%) rename packages/{core/src/modules/vc/signature-suites/bbs => module-bbs/src}/types/index.ts (89%) create mode 100644 packages/module-bbs/tests/bbs-signatures.e2e.test.ts create mode 100644 packages/module-bbs/tests/bbs-signing-provider.e2e.test.ts create mode 100644 packages/module-bbs/tests/fixtures.ts create mode 100644 packages/module-bbs/tests/setup.ts create mode 100644 packages/module-bbs/tests/util.ts create mode 100644 packages/module-bbs/tsconfig.build.json create mode 100644 packages/module-bbs/tsconfig.json diff --git a/packages/core/package.json b/packages/core/package.json index 360da5bd98..3858bbcac0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -26,8 +26,6 @@ "@digitalcredentials/jsonld": "^5.2.1", "@digitalcredentials/jsonld-signatures": "^9.3.1", "@digitalcredentials/vc": "^1.1.2", - "@mattrglobal/bbs-signatures": "^1.0.0", - "@mattrglobal/bls12381-key-pair": "^1.0.0", "@multiformats/base-x": "^4.0.1", "@stablelib/ed25519": "^1.0.2", "@stablelib/random": "^1.0.1", diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index c1bdc5e009..09c2e6f89f 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -11,6 +11,7 @@ import { concatMap, takeUntil } from 'rxjs/operators' import { CacheRepository } from '../cache' import { InjectionSymbols } from '../constants' +import { SigningProviderToken } from '../crypto' import { JwsService } from '../crypto/JwsService' import { AriesFrameworkError } from '../error' import { DependencyManager } from '../plugins' @@ -59,6 +60,13 @@ export class Agent extends dependencyManager.registerSingleton(StorageVersionRepository) dependencyManager.registerSingleton(StorageUpdateService) + // This is a really ugly hack to make tsyringe work without any SigningProviders registered + // It is currently impossible to use @injectAll if there are no instances registered for the + // token. We register a value of `default` by default and will filter that out in the registry. + // Once we have a signing provider that should always be registered we can remove this. We can make an ed25519 + // signer using the @stablelib/ed25519 library. + dependencyManager.registerInstance(SigningProviderToken, 'default') + dependencyManager.registerInstance(AgentConfig, agentConfig) dependencyManager.registerInstance(InjectionSymbols.AgentDependencies, agentConfig.agentDependencies) dependencyManager.registerInstance(InjectionSymbols.Stop$, new Subject()) diff --git a/packages/core/src/crypto/WalletKeyPair.ts b/packages/core/src/crypto/WalletKeyPair.ts index a251d5dfdd..f5008112db 100644 --- a/packages/core/src/crypto/WalletKeyPair.ts +++ b/packages/core/src/crypto/WalletKeyPair.ts @@ -1,15 +1,14 @@ -import type { Wallet } from '..' +import type { LdKeyPairOptions } from '../modules/vc/models/LdKeyPair' +import type { Wallet } from '../wallet' import type { Key } from './Key' -import type { LdKeyPairOptions } from './LdKeyPair' import { VerificationMethod } from '../modules/dids' import { getKeyDidMappingByVerificationMethod } from '../modules/dids/domain/key-type/keyDidMapping' +import { LdKeyPair } from '../modules/vc/models/LdKeyPair' import { JsonTransformer } from '../utils' import { MessageValidator } from '../utils/MessageValidator' import { Buffer } from '../utils/buffer' -import { LdKeyPair } from './LdKeyPair' - interface WalletKeyPairOptions extends LdKeyPairOptions { wallet: Wallet key: Key diff --git a/packages/core/src/crypto/index.ts b/packages/core/src/crypto/index.ts index 4c598a5a2a..49b83878ad 100644 --- a/packages/core/src/crypto/index.ts +++ b/packages/core/src/crypto/index.ts @@ -1,2 +1,3 @@ export { KeyType } from './KeyType' export { Key } from './Key' +export * from './signing-provider' diff --git a/packages/core/src/crypto/signing-provider/SigningProviderRegistry.ts b/packages/core/src/crypto/signing-provider/SigningProviderRegistry.ts index 993ae09547..8a33483d2d 100644 --- a/packages/core/src/crypto/signing-provider/SigningProviderRegistry.ts +++ b/packages/core/src/crypto/signing-provider/SigningProviderRegistry.ts @@ -10,8 +10,13 @@ export const SigningProviderToken = Symbol('SigningProviderToken') export class SigningProviderRegistry { private signingKeyProviders: SigningProvider[] - public constructor(@injectAll(SigningProviderToken) signingKeyProviders: SigningProvider[]) { - this.signingKeyProviders = signingKeyProviders + public constructor(@injectAll(SigningProviderToken) signingKeyProviders: Array<'default' | SigningProvider>) { + // This is a really ugly hack to make tsyringe work without any SigningProviders registered + // It is currently impossible to use @injectAll if there are no instances registered for the + // token. We register a value of `default` by default and will filter that out in the registry. + // Once we have a signing provider that should always be registered we can remove this. We can make an ed25519 + // signer using the @stablelib/ed25519 library. + this.signingKeyProviders = signingKeyProviders.filter((provider) => provider !== 'default') as SigningProvider[] } public hasProviderForKeyType(keyType: KeyType): boolean { diff --git a/packages/core/src/crypto/signing-provider/index.ts b/packages/core/src/crypto/signing-provider/index.ts index 165ca2ba92..e1ee8e8fe0 100644 --- a/packages/core/src/crypto/signing-provider/index.ts +++ b/packages/core/src/crypto/signing-provider/index.ts @@ -1,4 +1,3 @@ -export * from './Bls12381g2SigningProvider' export * from './SigningProvider' export * from './SigningProviderRegistry' export * from './SigningProviderError' diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 89e06c665f..c3e3e4a087 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -15,7 +15,15 @@ export { AgentMessage } from './agent/AgentMessage' export { Dispatcher } from './agent/Dispatcher' export { MessageSender } from './agent/MessageSender' export type { AgentDependencies } from './agent/AgentDependencies' -export type { InitConfig, OutboundPackage, EncryptedMessage, WalletConfig } from './types' +export type { + InitConfig, + OutboundPackage, + EncryptedMessage, + WalletConfig, + JsonArray, + JsonObject, + JsonValue, +} from './types' export { DidCommMimeType, KeyDerivationMethod } from './types' export type { FileSystem } from './storage/FileSystem' export * from './storage/BaseRecord' @@ -25,7 +33,7 @@ export * from './storage/RepositoryEvents' export { StorageService } from './storage/StorageService' export { getDirFromFilePath } from './utils/path' export { InjectionSymbols } from './constants' -export type { Wallet } from './wallet/Wallet' +export * from './wallet' export type { TransportSession } from './agent/TransportService' export { TransportService } from './agent/TransportService' export { Attachment } from './decorators/attachment/Attachment' @@ -45,13 +53,13 @@ export * from './modules/ledger' export * from './modules/routing' export * from './modules/question-answer' export * from './modules/oob' -export * from './wallet/WalletApi' export * from './modules/dids' -export { JsonEncoder, JsonTransformer, isJsonObject, isValidJweStructure } from './utils' +export * from './modules/vc' +export { JsonEncoder, JsonTransformer, isJsonObject, isValidJweStructure, TypedArrayEncoder, Buffer } from './utils' export * from './logger' export * from './error' export * from './wallet/error' -export { Key, KeyType } from './crypto' +export * from './crypto' export { parseMessageType, IsValidMessageType } from './utils/messageType' export type { Constructor } from './utils/mixins' diff --git a/packages/core/src/modules/dids/domain/index.ts b/packages/core/src/modules/dids/domain/index.ts index cae70d066f..6cd7cf22e8 100644 --- a/packages/core/src/modules/dids/domain/index.ts +++ b/packages/core/src/modules/dids/domain/index.ts @@ -5,3 +5,4 @@ export * from './DidDocumentBuilder' export * from './DidDocumentRole' export * from './DidRegistrar' export * from './DidResolver' +export * from './key-type' diff --git a/packages/core/src/modules/dids/domain/key-type/index.ts b/packages/core/src/modules/dids/domain/key-type/index.ts index 8e0d752102..edb319be90 100644 --- a/packages/core/src/modules/dids/domain/key-type/index.ts +++ b/packages/core/src/modules/dids/domain/key-type/index.ts @@ -1 +1,7 @@ export { getKeyDidMappingByKeyType, getKeyDidMappingByVerificationMethod } from './keyDidMapping' + +export * from './bls12381g2' +export * from './bls12381g1' +export * from './bls12381g1g2' +export * from './ed25519' +export * from './x25519' diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 1b8e188c3f..039e3544c5 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -22,15 +22,15 @@ import { DidResolverService, VerificationMethod } from '../dids' import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type' import { SignatureSuiteRegistry } from './SignatureSuiteRegistry' +import { deriveProof } from './deriveProof' import { orArrayToArray, w3cDate } from './jsonldUtil' import { getDocumentLoader } from './libraries/documentLoader' import jsonld from './libraries/jsonld' import vc from './libraries/vc' import { W3cVerifiableCredential } from './models' -import { W3cPresentation } from './models/presentation/W3Presentation' +import { W3cPresentation } from './models/presentation/W3cPresentation' import { W3cVerifiablePresentation } from './models/presentation/W3cVerifiablePresentation' import { W3cCredentialRecord, W3cCredentialRepository } from './repository' -import { deriveProof } from './signature-suites/bbs' @injectable() export class W3cCredentialService { @@ -267,6 +267,7 @@ export class W3cCredentialService { } public async deriveProof(agentContext: AgentContext, options: DeriveProofOptions): Promise { + // TODO: make suite dynamic const suiteInfo = this.suiteRegistry.getByProofType('BbsBlsSignatureProof2020') const SuiteClass = suiteInfo.suiteClass diff --git a/packages/core/src/modules/vc/W3cVcModule.ts b/packages/core/src/modules/vc/W3cVcModule.ts index 64a8782501..b8191cc70c 100644 --- a/packages/core/src/modules/vc/W3cVcModule.ts +++ b/packages/core/src/modules/vc/W3cVcModule.ts @@ -1,7 +1,6 @@ import type { DependencyManager, Module } from '../../plugins' import { KeyType } from '../../crypto' -import { VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020 } from '../dids/domain/key-type/bls12381g2' import { VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020, @@ -11,7 +10,6 @@ import { SignatureSuiteRegistry, SignatureSuiteToken } from './SignatureSuiteReg import { W3cCredentialService } from './W3cCredentialService' import { W3cCredentialRepository } from './repository/W3cCredentialRepository' import { Ed25519Signature2018 } from './signature-suites' -import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from './signature-suites/bbs' export class W3cVcModule implements Module { public register(dependencyManager: DependencyManager) { @@ -30,19 +28,5 @@ export class W3cVcModule implements Module { ], keyTypes: [KeyType.Ed25519], }) - - // This will be moved out of core into the bbs module - dependencyManager.registerInstance(SignatureSuiteToken, { - suiteClass: BbsBlsSignature2020, - proofType: 'BbsBlsSignature2020', - verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], - keyTypes: [KeyType.Bls12381g2], - }) - dependencyManager.registerInstance(SignatureSuiteToken, { - suiteClass: BbsBlsSignatureProof2020, - proofType: 'BbsBlsSignatureProof2020', - verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], - keyTypes: [KeyType.Bls12381g2], - }) } } diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index 6f81ce43c7..9971c97c3f 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -3,12 +3,11 @@ import type { AgentContext } from '../../../agent' import { getAgentConfig, getAgentContext, mockFunction } from '../../../../tests/helpers' import { KeyType } from '../../../crypto' import { Key } from '../../../crypto/Key' -import { Bls12381g2SigningProvider, SigningProviderRegistry } from '../../../crypto/signing-provider' +import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { JsonTransformer } from '../../../utils/JsonTransformer' import { IndyWallet } from '../../../wallet/IndyWallet' import { WalletError } from '../../../wallet/error' import { DidKey, DidResolverService } from '../../dids' -import { VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020 } from '../../dids/domain/key-type/bls12381g2' import { VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020, @@ -17,18 +16,16 @@ import { SignatureSuiteRegistry } from '../SignatureSuiteRegistry' import { W3cCredentialService } from '../W3cCredentialService' import { orArrayToArray } from '../jsonldUtil' import jsonld from '../libraries/jsonld' -import { purposes } from '../libraries/jsonld-signatures' import { W3cCredential, W3cVerifiableCredential } from '../models' import { LinkedDataProof } from '../models/LinkedDataProof' -import { W3cPresentation } from '../models/presentation/W3Presentation' +import { W3cPresentation } from '../models/presentation/W3cPresentation' import { W3cVerifiablePresentation } from '../models/presentation/W3cVerifiablePresentation' import { CredentialIssuancePurpose } from '../proof-purposes/CredentialIssuancePurpose' import { W3cCredentialRecord, W3cCredentialRepository } from '../repository' import { Ed25519Signature2018 } from '../signature-suites' -import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from '../signature-suites/bbs' import { customDocumentLoader } from './documentLoader' -import { BbsBlsSignature2020Fixtures, Ed25519Signature2018Fixtures } from './fixtures' +import { Ed25519Signature2018Fixtures } from './fixtures' const signatureSuiteRegistry = new SignatureSuiteRegistry([ { @@ -41,21 +38,9 @@ const signatureSuiteRegistry = new SignatureSuiteRegistry([ ], keyTypes: [KeyType.Ed25519], }, - { - suiteClass: BbsBlsSignature2020, - proofType: 'BbsBlsSignature2020', - verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], - keyTypes: [KeyType.Bls12381g2], - }, - { - suiteClass: BbsBlsSignatureProof2020, - proofType: 'BbsBlsSignatureProof2020', - verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], - keyTypes: [KeyType.Bls12381g2], - }, ]) -const signingProviderRegistry = new SigningProviderRegistry([new Bls12381g2SigningProvider()]) +const signingProviderRegistry = new SigningProviderRegistry([]) jest.mock('../../ledger/services/IndyLedgerService') @@ -109,14 +94,6 @@ describe('W3cCredentialService', () => { const keyTypes = w3cCredentialService.getKeyTypesByProofType('Ed25519Signature2018') expect(keyTypes).toEqual([KeyType.Ed25519]) }) - it('should return the correct key types for BbsBlsSignature2020 proof type', async () => { - const keyTypes = w3cCredentialService.getKeyTypesByProofType('BbsBlsSignature2020') - expect(keyTypes).toEqual([KeyType.Bls12381g2]) - }) - it('should return the correct key types for BbsBlsSignatureProof2020 proof type', async () => { - const keyTypes = w3cCredentialService.getKeyTypesByProofType('BbsBlsSignatureProof2020') - expect(keyTypes).toEqual([KeyType.Bls12381g2]) - }) }) describe('getVerificationMethodTypesByProofType', () => { @@ -128,16 +105,6 @@ describe('W3cCredentialService', () => { VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020, ]) }) - it('should return the correct key types for BbsBlsSignature2020 proof type', async () => { - const verificationMethodTypes = - w3cCredentialService.getVerificationMethodTypesByProofType('BbsBlsSignature2020') - expect(verificationMethodTypes).toEqual([VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020]) - }) - it('should return the correct key types for BbsBlsSignatureProof2020 proof type', async () => { - const verificationMethodTypes = - w3cCredentialService.getVerificationMethodTypesByProofType('BbsBlsSignatureProof2020') - expect(verificationMethodTypes).toEqual([VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020]) - }) }) }) @@ -339,149 +306,6 @@ describe('W3cCredentialService', () => { }) }) - describe('BbsBlsSignature2020', () => { - let issuerDidKey: DidKey - let verificationMethod: string - beforeAll(async () => { - const key = await wallet.createKey({ keyType: KeyType.Bls12381g2, seed }) - issuerDidKey = new DidKey(key) - verificationMethod = `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}` - }) - describe('signCredential', () => { - it('should return a successfully signed credential bbs', async () => { - const credentialJson = BbsBlsSignature2020Fixtures.TEST_LD_DOCUMENT - credentialJson.issuer = issuerDidKey.did - - const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) - - const vc = await w3cCredentialService.signCredential(agentContext, { - credential, - proofType: 'BbsBlsSignature2020', - verificationMethod: verificationMethod, - }) - - expect(vc).toBeInstanceOf(W3cVerifiableCredential) - expect(vc.issuer).toEqual(issuerDidKey.did) - expect(Array.isArray(vc.proof)).toBe(false) - expect(vc.proof).toBeInstanceOf(LinkedDataProof) - - vc.proof = vc.proof as LinkedDataProof - expect(vc.proof.verificationMethod).toEqual(verificationMethod) - }) - }) - describe('verifyCredential', () => { - it('should verify the credential successfully', async () => { - const result = await w3cCredentialService.verifyCredential(agentContext, { - credential: JsonTransformer.fromJSON( - BbsBlsSignature2020Fixtures.TEST_LD_DOCUMENT_SIGNED, - W3cVerifiableCredential - ), - proofPurpose: new purposes.AssertionProofPurpose(), - }) - - expect(result.verified).toEqual(true) - }) - }) - describe('deriveProof', () => { - it('should derive proof successfully', async () => { - const credentialJson = BbsBlsSignature2020Fixtures.TEST_LD_DOCUMENT_SIGNED - - const vc = JsonTransformer.fromJSON(credentialJson, W3cVerifiableCredential) - - const revealDocument = { - '@context': [ - 'https://www.w3.org/2018/credentials/v1', - 'https://w3id.org/citizenship/v1', - 'https://w3id.org/security/bbs/v1', - ], - type: ['VerifiableCredential', 'PermanentResidentCard'], - credentialSubject: { - '@explicit': true, - type: ['PermanentResident', 'Person'], - givenName: {}, - familyName: {}, - gender: {}, - }, - } - - const result = await w3cCredentialService.deriveProof(agentContext, { - credential: vc, - revealDocument: revealDocument, - verificationMethod: verificationMethod, - }) - - // result.proof = result.proof as LinkedDataProof - expect(orArrayToArray(result.proof)[0].verificationMethod).toBe( - 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN' - ) - }) - }) - describe('verifyDerived', () => { - it('should verify the derived proof successfully', async () => { - const result = await w3cCredentialService.verifyCredential(agentContext, { - credential: JsonTransformer.fromJSON(BbsBlsSignature2020Fixtures.TEST_VALID_DERIVED, W3cVerifiableCredential), - proofPurpose: new purposes.AssertionProofPurpose(), - }) - expect(result.verified).toEqual(true) - }) - }) - describe('createPresentation', () => { - it('should create a presentation successfully', async () => { - const vc = JsonTransformer.fromJSON(BbsBlsSignature2020Fixtures.TEST_VALID_DERIVED, W3cVerifiableCredential) - const result = await w3cCredentialService.createPresentation({ credentials: vc }) - - expect(result).toBeInstanceOf(W3cPresentation) - - expect(result.type).toEqual(expect.arrayContaining(['VerifiablePresentation'])) - - expect(result.verifiableCredential).toHaveLength(1) - expect(result.verifiableCredential).toEqual(expect.arrayContaining([vc])) - }) - }) - describe('signPresentation', () => { - it('should sign the presentation successfully', async () => { - const signingKey = Key.fromPublicKeyBase58((await wallet.createDid({ seed })).verkey, KeyType.Ed25519) - const signingDidKey = new DidKey(signingKey) - const verificationMethod = `${signingDidKey.did}#${signingDidKey.key.fingerprint}` - const presentation = JsonTransformer.fromJSON(BbsBlsSignature2020Fixtures.TEST_VP_DOCUMENT, W3cPresentation) - - const purpose = new CredentialIssuancePurpose({ - controller: { - id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - }, - date: new Date().toISOString(), - }) - - const verifiablePresentation = await w3cCredentialService.signPresentation(agentContext, { - presentation: presentation, - purpose: purpose, - signatureType: 'Ed25519Signature2018', - challenge: 'e950bfe5-d7ec-4303-ad61-6983fb976ac9', - verificationMethod: verificationMethod, - }) - - expect(verifiablePresentation).toBeInstanceOf(W3cVerifiablePresentation) - }) - }) - describe('verifyPresentation', () => { - it('should successfully verify a presentation containing a single verifiable credential bbs', async () => { - const vp = JsonTransformer.fromJSON( - BbsBlsSignature2020Fixtures.TEST_VP_DOCUMENT_SIGNED, - W3cVerifiablePresentation - ) - - const result = await w3cCredentialService.verifyPresentation(agentContext, { - presentation: vp, - proofType: 'Ed25519Signature2018', - challenge: 'e950bfe5-d7ec-4303-ad61-6983fb976ac9', - verificationMethod: - 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - }) - - expect(result.verified).toBe(true) - }) - }) - }) describe('Credential Storage', () => { let w3cCredentialRecord: W3cCredentialRecord let w3cCredentialRepositoryDeleteMock: jest.MockedFunction diff --git a/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts b/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts index d48d1672e6..2e98f11f63 100644 --- a/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts @@ -5,7 +5,6 @@ import { W3cCredentialService } from '../W3cCredentialService' import { W3cVcModule } from '../W3cVcModule' import { W3cCredentialRepository } from '../repository' import { Ed25519Signature2018 } from '../signature-suites' -import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from '../signature-suites/bbs' jest.mock('../../../plugins/DependencyManager') const DependencyManagerMock = DependencyManager as jest.Mock @@ -21,26 +20,12 @@ describe('W3cVcModule', () => { expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(W3cCredentialRepository) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(SignatureSuiteRegistry) - expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(3) + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) expect(dependencyManager.registerInstance).toHaveBeenCalledWith(SignatureSuiteToken, { suiteClass: Ed25519Signature2018, verificationMethodTypes: ['Ed25519VerificationKey2018', 'Ed25519VerificationKey2020'], proofType: 'Ed25519Signature2018', keyTypes: [KeyType.Ed25519], }) - - expect(dependencyManager.registerInstance).toHaveBeenCalledWith(SignatureSuiteToken, { - suiteClass: BbsBlsSignature2020, - verificationMethodTypes: ['Bls12381G2Key2020'], - proofType: 'BbsBlsSignature2020', - keyTypes: [KeyType.Bls12381g2], - }) - - expect(dependencyManager.registerInstance).toHaveBeenCalledWith(SignatureSuiteToken, { - suiteClass: BbsBlsSignatureProof2020, - proofType: 'BbsBlsSignatureProof2020', - verificationMethodTypes: ['Bls12381G2Key2020'], - keyTypes: [KeyType.Bls12381g2], - }) }) }) diff --git a/packages/core/src/modules/vc/__tests__/fixtures.ts b/packages/core/src/modules/vc/__tests__/fixtures.ts index a40b8499c1..491a388f98 100644 --- a/packages/core/src/modules/vc/__tests__/fixtures.ts +++ b/packages/core/src/modules/vc/__tests__/fixtures.ts @@ -1,4 +1,4 @@ -import { CREDENTIALS_CONTEXT_V1_URL, SECURITY_CONTEXT_BBS_URL } from '../constants' +import { CREDENTIALS_CONTEXT_V1_URL } from '../constants' export const Ed25519Signature2018Fixtures = { TEST_LD_DOCUMENT: { @@ -115,212 +115,3 @@ export const Ed25519Signature2018Fixtures = { }, }, } - -export const BbsBlsSignature2020Fixtures = { - TEST_LD_DOCUMENT: { - '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], - id: 'https://issuer.oidp.uscis.gov/credentials/83627465', - type: ['VerifiableCredential', 'PermanentResidentCard'], - issuer: '', - identifier: '83627465', - name: 'Permanent Resident Card', - description: 'Government of Example Permanent Resident Card.', - issuanceDate: '2019-12-03T12:19:52Z', - expirationDate: '2029-12-03T12:19:52Z', - credentialSubject: { - id: 'did:example:b34ca6cd37bbf23', - type: ['PermanentResident', 'Person'], - givenName: 'JOHN', - familyName: 'SMITH', - gender: 'Male', - image: 'data:image/png;base64,iVBORw0KGgokJggg==', - residentSince: '2015-01-01', - lprCategory: 'C09', - lprNumber: '999-999-999', - commuterClassification: 'C1', - birthCountry: 'Bahamas', - birthDate: '1958-07-17', - }, - }, - - TEST_LD_DOCUMENT_SIGNED: { - '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], - id: 'https://issuer.oidp.uscis.gov/credentials/83627465', - type: ['VerifiableCredential', 'PermanentResidentCard'], - issuer: - 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', - identifier: '83627465', - name: 'Permanent Resident Card', - description: 'Government of Example Permanent Resident Card.', - issuanceDate: '2019-12-03T12:19:52Z', - expirationDate: '2029-12-03T12:19:52Z', - credentialSubject: { - id: 'did:example:b34ca6cd37bbf23', - type: ['PermanentResident', 'Person'], - givenName: 'JOHN', - familyName: 'SMITH', - gender: 'Male', - image: 'data:image/png;base64,iVBORw0KGgokJggg==', - residentSince: '2015-01-01', - lprCategory: 'C09', - lprNumber: '999-999-999', - commuterClassification: 'C1', - birthCountry: 'Bahamas', - birthDate: '1958-07-17', - }, - proof: { - type: 'BbsBlsSignature2020', - created: '2022-04-13T13:47:47Z', - verificationMethod: - 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', - proofPurpose: 'assertionMethod', - proofValue: - 'hoNNnnRIoEoaY9Fvg3pGVG2eWTAHnR1kIM01nObEL2FdI2IkkpM3246jn3VBD8KBYUHlKfzccE4m7waZyoLEkBLFiK2g54Q2i+CdtYBgDdkUDsoULSBMcH1MwGHwdjfXpldFNFrHFx/IAvLVniyeMQ==', - }, - }, - TEST_LD_DOCUMENT_BAD_SIGNED: { - '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], - id: 'https://issuer.oidp.uscis.gov/credentials/83627465', - type: ['VerifiableCredential', 'PermanentResidentCard'], - issuer: - 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', - identifier: '83627465', - name: 'Permanent Resident Card', - description: 'Government of Example Permanent Resident Card.', - issuanceDate: '2019-12-03T12:19:52Z', - expirationDate: '2029-12-03T12:19:52Z', - credentialSubject: { - id: 'did:example:b34ca6cd37bbf23', - type: ['PermanentResident', 'Person'], - givenName: 'JOHN', - familyName: 'SMITH', - gender: 'Male', - image: 'data:image/png;base64,iVBORw0KGgokJggg==', - residentSince: '2015-01-01', - lprCategory: 'C09', - lprNumber: '999-999-999', - commuterClassification: 'C1', - birthCountry: 'Bahamas', - birthDate: '1958-07-17', - }, - proof: { - type: 'BbsBlsSignature2020', - created: '2022-04-13T13:47:47Z', - verificationMethod: - 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', - proofPurpose: 'assertionMethod', - proofValue: - 'gU44r/fmvGpkOyMRZX4nwRB6IsbrL7zbVTs+yu6bZGeCNJuiJqS5U6fCPuvGQ+iNYUHlKfzccE4m7waZyoLEkBLFiK2g54Q2i+CdtYBgDdkUDsoULSBMcH1MwGHwdjfXpldFNFrHFx/IAvLVniyeMQ==', - }, - }, - - TEST_VALID_DERIVED: { - '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], - id: 'https://issuer.oidp.uscis.gov/credentials/83627465', - type: ['PermanentResidentCard', 'VerifiableCredential'], - description: 'Government of Example Permanent Resident Card.', - identifier: '83627465', - name: 'Permanent Resident Card', - credentialSubject: { - id: 'did:example:b34ca6cd37bbf23', - type: ['Person', 'PermanentResident'], - familyName: 'SMITH', - gender: 'Male', - givenName: 'JOHN', - }, - expirationDate: '2029-12-03T12:19:52Z', - issuanceDate: '2019-12-03T12:19:52Z', - issuer: - 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', - proof: { - type: 'BbsBlsSignatureProof2020', - created: '2022-04-13T13:47:47Z', - nonce: 'GfuRhH8hSAcWm5RWgUQYNQNWjQBsWuVgMCJrhTCD3kSpnHmQOkHcnNAoBsgyMAT4UUI=', - proofPurpose: 'assertionMethod', - proofValue: - 'ABkB/wbvkcCcbPRE5vrXc++orru4MsgrS4ESsZ30RNCs3noqLwm94/RZNp62I6Hyf0Kmht0Vog70HDtnNzbnMAj/zD9oT/N53pOADrtn5v+xZgP3cK4N2d6amg6h3LXem29gidW9hMrROPLit5cWEIL4/HOzxPxQQGYiwEXdW++Aja5ZuwJoMsIx7ysn4C4ekN7JXZtnAAAAdJR/oeDShxRdSBlnCSUHkE4Ol+Z3AhXBKkxb4AxiMKOiNmBreMTjJUGwNAPNU2aKnAAAAAIBUuKV0W0YBQZY/mwLmwCcyOWMiaEpjnVhYip4jhBBZw1aPBe8GzsG9zv3Sf9XAyGEAvVFe3OvwvMwYY5nZYdYoLSR4TLl1aLw0oChiPm2zb6ApXypCEEVd8KhJMATyssTlY48bEljDNixAD2rVDaoAAAACWjyrWp3b62M5Onuwo9EItCrBjPD68xC12q1agqgwFTnOI0+MfEwVGNZsA0IqkCGrZmo3AyRpcRm51IYDWYorM4hued5EcVHeCGd6NrnLSxTFPEu8lnmCoMXcxBWDCZFRGb//M5WlncbsYiz01itHbSs1nmpj3o+DYlF2ZyOYphvLo5A9T4rWVwHRK1+LeCDEawOnI03DWLyN8U4ZpbpcdZNK421IwNjseYY+ptvvL3juZ2uQR84maAZYy/OjMuHNyzqHPXNgsLLqtrvPo0kncefp+x1jgA0J/b5xfT72+vhKZAN1R48/uPf+DySC3avwD3T+YHjePn1bBOidhCWMjwzI9LYO8VvhcWXzH7nBWh5MeUch+Wkl777KrsLhrXnCg==', - verificationMethod: - 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', - }, - }, - - TEST_VP_DOCUMENT: { - '@context': [CREDENTIALS_CONTEXT_V1_URL], - type: ['VerifiablePresentation'], - verifiableCredential: [ - { - '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], - id: 'https://issuer.oidp.uscis.gov/credentials/83627465', - type: ['PermanentResidentCard', 'VerifiableCredential'], - description: 'Government of Example Permanent Resident Card.', - identifier: '83627465', - name: 'Permanent Resident Card', - credentialSubject: { - id: 'did:example:b34ca6cd37bbf23', - type: ['Person', 'PermanentResident'], - familyName: 'SMITH', - gender: 'Male', - givenName: 'JOHN', - }, - expirationDate: '2029-12-03T12:19:52Z', - issuanceDate: '2019-12-03T12:19:52Z', - issuer: - 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', - proof: { - type: 'BbsBlsSignatureProof2020', - created: '2022-04-13T13:47:47Z', - nonce: 'GfuRhH8hSAcWm5RWgUQYNQNWjQBsWuVgMCJrhTCD3kSpnHmQOkHcnNAoBsgyMAT4UUI=', - proofPurpose: 'assertionMethod', - proofValue: - 'ABkB/wbvkcCcbPRE5vrXc++orru4MsgrS4ESsZ30RNCs3noqLwm94/RZNp62I6Hyf0Kmht0Vog70HDtnNzbnMAj/zD9oT/N53pOADrtn5v+xZgP3cK4N2d6amg6h3LXem29gidW9hMrROPLit5cWEIL4/HOzxPxQQGYiwEXdW++Aja5ZuwJoMsIx7ysn4C4ekN7JXZtnAAAAdJR/oeDShxRdSBlnCSUHkE4Ol+Z3AhXBKkxb4AxiMKOiNmBreMTjJUGwNAPNU2aKnAAAAAIBUuKV0W0YBQZY/mwLmwCcyOWMiaEpjnVhYip4jhBBZw1aPBe8GzsG9zv3Sf9XAyGEAvVFe3OvwvMwYY5nZYdYoLSR4TLl1aLw0oChiPm2zb6ApXypCEEVd8KhJMATyssTlY48bEljDNixAD2rVDaoAAAACWjyrWp3b62M5Onuwo9EItCrBjPD68xC12q1agqgwFTnOI0+MfEwVGNZsA0IqkCGrZmo3AyRpcRm51IYDWYorM4hued5EcVHeCGd6NrnLSxTFPEu8lnmCoMXcxBWDCZFRGb//M5WlncbsYiz01itHbSs1nmpj3o+DYlF2ZyOYphvLo5A9T4rWVwHRK1+LeCDEawOnI03DWLyN8U4ZpbpcdZNK421IwNjseYY+ptvvL3juZ2uQR84maAZYy/OjMuHNyzqHPXNgsLLqtrvPo0kncefp+x1jgA0J/b5xfT72+vhKZAN1R48/uPf+DySC3avwD3T+YHjePn1bBOidhCWMjwzI9LYO8VvhcWXzH7nBWh5MeUch+Wkl777KrsLhrXnCg==', - verificationMethod: - 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', - }, - }, - ], - }, - TEST_VP_DOCUMENT_SIGNED: { - '@context': [CREDENTIALS_CONTEXT_V1_URL], - type: ['VerifiablePresentation'], - verifiableCredential: [ - { - '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], - id: 'https://issuer.oidp.uscis.gov/credentials/83627465', - type: ['PermanentResidentCard', 'VerifiableCredential'], - description: 'Government of Example Permanent Resident Card.', - identifier: '83627465', - name: 'Permanent Resident Card', - credentialSubject: { - id: 'did:example:b34ca6cd37bbf23', - type: ['Person', 'PermanentResident'], - familyName: 'SMITH', - gender: 'Male', - givenName: 'JOHN', - }, - expirationDate: '2029-12-03T12:19:52Z', - issuanceDate: '2019-12-03T12:19:52Z', - issuer: - 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', - proof: { - type: 'BbsBlsSignatureProof2020', - created: '2022-04-13T13:47:47Z', - nonce: 'GfuRhH8hSAcWm5RWgUQYNQNWjQBsWuVgMCJrhTCD3kSpnHmQOkHcnNAoBsgyMAT4UUI=', - proofPurpose: 'assertionMethod', - proofValue: - 'ABkB/wbvkcCcbPRE5vrXc++orru4MsgrS4ESsZ30RNCs3noqLwm94/RZNp62I6Hyf0Kmht0Vog70HDtnNzbnMAj/zD9oT/N53pOADrtn5v+xZgP3cK4N2d6amg6h3LXem29gidW9hMrROPLit5cWEIL4/HOzxPxQQGYiwEXdW++Aja5ZuwJoMsIx7ysn4C4ekN7JXZtnAAAAdJR/oeDShxRdSBlnCSUHkE4Ol+Z3AhXBKkxb4AxiMKOiNmBreMTjJUGwNAPNU2aKnAAAAAIBUuKV0W0YBQZY/mwLmwCcyOWMiaEpjnVhYip4jhBBZw1aPBe8GzsG9zv3Sf9XAyGEAvVFe3OvwvMwYY5nZYdYoLSR4TLl1aLw0oChiPm2zb6ApXypCEEVd8KhJMATyssTlY48bEljDNixAD2rVDaoAAAACWjyrWp3b62M5Onuwo9EItCrBjPD68xC12q1agqgwFTnOI0+MfEwVGNZsA0IqkCGrZmo3AyRpcRm51IYDWYorM4hued5EcVHeCGd6NrnLSxTFPEu8lnmCoMXcxBWDCZFRGb//M5WlncbsYiz01itHbSs1nmpj3o+DYlF2ZyOYphvLo5A9T4rWVwHRK1+LeCDEawOnI03DWLyN8U4ZpbpcdZNK421IwNjseYY+ptvvL3juZ2uQR84maAZYy/OjMuHNyzqHPXNgsLLqtrvPo0kncefp+x1jgA0J/b5xfT72+vhKZAN1R48/uPf+DySC3avwD3T+YHjePn1bBOidhCWMjwzI9LYO8VvhcWXzH7nBWh5MeUch+Wkl777KrsLhrXnCg==', - verificationMethod: - 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', - }, - }, - ], - proof: { - verificationMethod: - 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - type: 'Ed25519Signature2018', - created: '2022-04-21T10:15:38Z', - proofPurpose: 'authentication', - challenge: 'e950bfe5-d7ec-4303-ad61-6983fb976ac9', - jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..wGtR9yuTRfhrsvCthUOn-fg_lK0mZIe2IOO2Lv21aOXo5YUAbk50qMBLk4C1iqoOx-Jz6R0g4aa4cuqpdXzkBw', - }, - }, -} diff --git a/packages/core/src/modules/vc/signature-suites/bbs/deriveProof.ts b/packages/core/src/modules/vc/deriveProof.ts similarity index 91% rename from packages/core/src/modules/vc/signature-suites/bbs/deriveProof.ts rename to packages/core/src/modules/vc/deriveProof.ts index de0ac5a67a..ff4ae9ecaf 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/deriveProof.ts +++ b/packages/core/src/modules/vc/deriveProof.ts @@ -11,13 +11,14 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../types' +import type { JsonObject } from '../../types' -import { JsonTransformer } from '../../../../utils' -import { SECURITY_PROOF_URL } from '../../constants' -import { getProofs, getTypeInfo } from '../../jsonldUtil' -import jsonld from '../../libraries/jsonld' -import { W3cVerifiableCredential } from '../../models' +import { JsonTransformer } from '../../utils' + +import { SECURITY_PROOF_URL } from './constants' +import { getProofs, getTypeInfo } from './jsonldUtil' +import jsonld from './libraries/jsonld' +import { W3cVerifiableCredential } from './models' /** * Derives a proof from a document featuring a supported linked data proof @@ -51,9 +52,8 @@ export const deriveProof = async ( if (proofs.length === 0) { throw new Error(`There were not any proofs provided that can be used to derive a proof with this suite.`) } - let derivedProof - derivedProof = await suite.deriveProof({ + let derivedProof = await suite.deriveProof({ document, proof: proofs[0], revealDocument, diff --git a/packages/core/src/modules/vc/index.ts b/packages/core/src/modules/vc/index.ts index 8a4149599f..289e8fbcd9 100644 --- a/packages/core/src/modules/vc/index.ts +++ b/packages/core/src/modules/vc/index.ts @@ -1,3 +1,11 @@ export * from './W3cCredentialService' export * from './repository/W3cCredentialRecord' export * from './W3cVcModule' +export * from './models' +export type { DocumentLoader, Proof } from './jsonldUtil' +export { w3cDate, orArrayToArray } from './jsonldUtil' +export * from './proof-purposes' +export * from './constants' +export * from './libraries' +export { SuiteInfo, SignatureSuiteToken } from './SignatureSuiteRegistry' +export * from './signature-suites' diff --git a/packages/core/src/modules/vc/jsonldUtil.ts b/packages/core/src/modules/vc/jsonldUtil.ts index 1581291abf..761e22726f 100644 --- a/packages/core/src/modules/vc/jsonldUtil.ts +++ b/packages/core/src/modules/vc/jsonldUtil.ts @@ -1,6 +1,6 @@ import type { JsonObject, JsonValue } from '../../types' import type { SingleOrArray } from '../../utils/type' -import type { GetProofsOptions, GetProofsResult, GetTypeOptions } from './signature-suites/bbs' +import type { GetProofsOptions, GetProofsResult, GetTypeOptions } from './models' import { SECURITY_CONTEXT_URL } from './constants' import jsonld from './libraries/jsonld' diff --git a/packages/core/src/modules/vc/libraries/index.ts b/packages/core/src/modules/vc/libraries/index.ts new file mode 100644 index 0000000000..2a721334a1 --- /dev/null +++ b/packages/core/src/modules/vc/libraries/index.ts @@ -0,0 +1,20 @@ +import * as jsonld from './jsonld' +import * as jsonldSignatures from './jsonld-signatures' +import * as vc from './vc' + +// Temporary re-export of vc libraries. As the libraries don't +// have types, it's inconvenient to import them from non-core packages +// as we would have to re-add the types. We re-export these libraries, +// so they can be imported by other packages. In the future we should look +// at proper types for these libraries so we don't have to re-export them. +export const vcLibraries = { + jsonldSignatures, + jsonld: { + ...jsonld, + ...jsonld.default, + }, + vc: { + ...vc, + ...vc.default, + }, +} diff --git a/packages/core/src/modules/vc/libraries/jsonld-signatures.ts b/packages/core/src/modules/vc/libraries/jsonld-signatures.ts index 6257b8e2db..2ef49a3aa9 100644 --- a/packages/core/src/modules/vc/libraries/jsonld-signatures.ts +++ b/packages/core/src/modules/vc/libraries/jsonld-signatures.ts @@ -9,12 +9,12 @@ import { //@ts-ignore } from '@digitalcredentials/jsonld-signatures' -interface Suites { +export interface Suites { LinkedDataSignature: any LinkedDataProof: any } -interface Purposes { +export interface Purposes { AssertionProofPurpose: any } diff --git a/packages/core/src/modules/vc/libraries/jsonld.ts b/packages/core/src/modules/vc/libraries/jsonld.ts index aee4be8adf..a00f793f07 100644 --- a/packages/core/src/modules/vc/libraries/jsonld.ts +++ b/packages/core/src/modules/vc/libraries/jsonld.ts @@ -5,7 +5,7 @@ //@ts-ignore import jsonld from '@digitalcredentials/jsonld' -interface JsonLd { +export interface JsonLd { compact(document: any, context: any, options?: any): any fromRDF(document: any): any frame(document: any, revealDocument: any, options?: any): any diff --git a/packages/core/src/modules/vc/libraries/vc.ts b/packages/core/src/modules/vc/libraries/vc.ts index 21c4a38df4..4e37531518 100644 --- a/packages/core/src/modules/vc/libraries/vc.ts +++ b/packages/core/src/modules/vc/libraries/vc.ts @@ -5,7 +5,7 @@ // @ts-ignore import vc from '@digitalcredentials/vc' -interface VC { +export interface VC { issue(options: any): Promise> verifyCredential(options: any): Promise> createPresentation(options: any): Promise> diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/GetProofsOptions.ts b/packages/core/src/modules/vc/models/GetProofsOptions.ts similarity index 91% rename from packages/core/src/modules/vc/signature-suites/bbs/types/GetProofsOptions.ts rename to packages/core/src/modules/vc/models/GetProofsOptions.ts index 41b9fa935f..33f5abb3b2 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/types/GetProofsOptions.ts +++ b/packages/core/src/modules/vc/models/GetProofsOptions.ts @@ -11,8 +11,8 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../../types' -import type { DocumentLoader } from '../../../jsonldUtil' +import type { JsonObject } from '../../../types' +import type { DocumentLoader } from '../jsonldUtil' /** * Options for getting a proof from a JSON-LD document diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/GetProofsResult.ts b/packages/core/src/modules/vc/models/GetProofsResult.ts similarity index 93% rename from packages/core/src/modules/vc/signature-suites/bbs/types/GetProofsResult.ts rename to packages/core/src/modules/vc/models/GetProofsResult.ts index 6e24011b74..5483acbe04 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/types/GetProofsResult.ts +++ b/packages/core/src/modules/vc/models/GetProofsResult.ts @@ -11,7 +11,7 @@ * limitations under the License. */ -import type { JsonArray, JsonObject } from '../../../../../types' +import type { JsonObject, JsonArray } from '../../../types' /** * Result for getting proofs from a JSON-LD document diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/GetTypeOptions.ts b/packages/core/src/modules/vc/models/GetTypeOptions.ts similarity index 87% rename from packages/core/src/modules/vc/signature-suites/bbs/types/GetTypeOptions.ts rename to packages/core/src/modules/vc/models/GetTypeOptions.ts index 0dd40cb546..f39854f02b 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/types/GetTypeOptions.ts +++ b/packages/core/src/modules/vc/models/GetTypeOptions.ts @@ -11,7 +11,7 @@ * limitations under the License. */ -import type { DocumentLoader } from '../../../jsonldUtil' +import type { DocumentLoader } from '../jsonldUtil' /** * Options for getting the type from a JSON-LD document @@ -20,11 +20,9 @@ export interface GetTypeOptions { /** * Optional custom document loader */ - // eslint-disable-next-line documentLoader?: DocumentLoader /** * Optional expansion map */ - // eslint-disable-next-line expansionMap?: () => void } diff --git a/packages/core/src/crypto/LdKeyPair.ts b/packages/core/src/modules/vc/models/LdKeyPair.ts similarity index 95% rename from packages/core/src/crypto/LdKeyPair.ts rename to packages/core/src/modules/vc/models/LdKeyPair.ts index 3a46c47c1a..0982f1a153 100644 --- a/packages/core/src/crypto/LdKeyPair.ts +++ b/packages/core/src/modules/vc/models/LdKeyPair.ts @@ -1,4 +1,4 @@ -import type { VerificationMethod } from '../modules/dids' +import type { VerificationMethod } from '../../dids' export interface LdKeyPairOptions { id: string diff --git a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts index 88206fa4bd..66d12e5d64 100644 --- a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts +++ b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts @@ -3,7 +3,7 @@ import type { SingleOrArray } from '../../../utils/type' import type { ProofPurpose } from '../proof-purposes/ProofPurpose' import type { W3cCredential } from './credential/W3cCredential' import type { W3cVerifiableCredential } from './credential/W3cVerifiableCredential' -import type { W3cPresentation } from './presentation/W3Presentation' +import type { W3cPresentation } from './presentation/W3cPresentation' import type { W3cVerifiablePresentation } from './presentation/W3cVerifiablePresentation' export interface SignCredentialOptions { diff --git a/packages/core/src/modules/vc/models/index.ts b/packages/core/src/modules/vc/models/index.ts index 37c71ef25d..45d0a281f7 100644 --- a/packages/core/src/modules/vc/models/index.ts +++ b/packages/core/src/modules/vc/models/index.ts @@ -1,3 +1,11 @@ export * from './credential/W3cCredential' export * from './credential/W3cVerifiableCredential' export * from './credential/W3cVerifyCredentialResult' +export * from './presentation/VerifyPresentationResult' +export * from './presentation/W3cPresentation' +export * from './presentation/W3cVerifiablePresentation' +export * from './GetProofsOptions' +export * from './GetProofsResult' +export * from './GetTypeOptions' +export * from './LdKeyPair' +export * from './LinkedDataProof' diff --git a/packages/core/src/modules/vc/models/presentation/W3Presentation.ts b/packages/core/src/modules/vc/models/presentation/W3cPresentation.ts similarity index 100% rename from packages/core/src/modules/vc/models/presentation/W3Presentation.ts rename to packages/core/src/modules/vc/models/presentation/W3cPresentation.ts diff --git a/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts b/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts index 2645fb9cc5..fa1fd6001a 100644 --- a/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts +++ b/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts @@ -1,11 +1,11 @@ import type { LinkedDataProofOptions } from '../LinkedDataProof' -import type { W3cPresentationOptions } from './W3Presentation' +import type { W3cPresentationOptions } from './W3cPresentation' import { SingleOrArray } from '../../../../utils/type' import { IsInstanceOrArrayOfInstances } from '../../../../utils/validators' import { LinkedDataProof, LinkedDataProofTransformer } from '../LinkedDataProof' -import { W3cPresentation } from './W3Presentation' +import { W3cPresentation } from './W3cPresentation' export interface W3cVerifiablePresentationOptions extends W3cPresentationOptions { proof: LinkedDataProofOptions diff --git a/packages/core/src/modules/vc/proof-purposes/index.ts b/packages/core/src/modules/vc/proof-purposes/index.ts new file mode 100644 index 0000000000..d224af2583 --- /dev/null +++ b/packages/core/src/modules/vc/proof-purposes/index.ts @@ -0,0 +1,2 @@ +export * from './CredentialIssuancePurpose' +export * from './ProofPurpose' diff --git a/packages/core/src/modules/vc/repository/W3cCredentialRecord.ts b/packages/core/src/modules/vc/repository/W3cCredentialRecord.ts index b324b6ee26..3c10234210 100644 --- a/packages/core/src/modules/vc/repository/W3cCredentialRecord.ts +++ b/packages/core/src/modules/vc/repository/W3cCredentialRecord.ts @@ -17,7 +17,7 @@ export type CustomW3cCredentialTags = TagsBase & { expandedTypes?: Array } -export type DefaultCredentialTags = { +export type DefaultW3cCredentialTags = { issuerId: string subjectIds: Array schemaIds: Array @@ -26,7 +26,7 @@ export type DefaultCredentialTags = { givenId?: string } -export class W3cCredentialRecord extends BaseRecord { +export class W3cCredentialRecord extends BaseRecord { public static readonly type = 'W3cCredentialRecord' public readonly type = W3cCredentialRecord.type diff --git a/packages/core/src/modules/vc/signature-suites/JwsLinkedDataSignature.ts b/packages/core/src/modules/vc/signature-suites/JwsLinkedDataSignature.ts index 226c0e5ecc..66e5811b32 100644 --- a/packages/core/src/modules/vc/signature-suites/JwsLinkedDataSignature.ts +++ b/packages/core/src/modules/vc/signature-suites/JwsLinkedDataSignature.ts @@ -1,8 +1,8 @@ /*! * Copyright (c) 2020-2021 Digital Bazaar, Inc. All rights reserved. */ -import type { LdKeyPair } from '../../../crypto/LdKeyPair' import type { DocumentLoader, Proof, VerificationMethod } from '../jsonldUtil' +import type { LdKeyPair } from '../models/LdKeyPair' import { AriesFrameworkError } from '../../../error' import { TypedArrayEncoder, JsonEncoder } from '../../../utils' diff --git a/packages/core/src/wallet/IndyWallet.test.ts b/packages/core/src/wallet/IndyWallet.test.ts index c2ab276035..04176f9b55 100644 --- a/packages/core/src/wallet/IndyWallet.test.ts +++ b/packages/core/src/wallet/IndyWallet.test.ts @@ -1,12 +1,11 @@ import type { WalletConfig } from '../types' -import { BBS_SIGNATURE_LENGTH } from '@mattrglobal/bbs-signatures' import { SIGNATURE_LENGTH as ED25519_SIGNATURE_LENGTH } from '@stablelib/ed25519' import { agentDependencies } from '../../tests/helpers' import testLogger from '../../tests/logger' import { KeyType } from '../crypto' -import { Bls12381g2SigningProvider, SigningProviderRegistry } from '../crypto/signing-provider' +import { SigningProviderRegistry } from '../crypto/signing-provider' import { KeyDerivationMethod } from '../types' import { TypedArrayEncoder } from '../utils' @@ -27,11 +26,7 @@ describe('IndyWallet', () => { const message = TypedArrayEncoder.fromString('sample-message') beforeEach(async () => { - indyWallet = new IndyWallet( - agentDependencies, - testLogger, - new SigningProviderRegistry([new Bls12381g2SigningProvider()]) - ) + indyWallet = new IndyWallet(agentDependencies, testLogger, new SigningProviderRegistry([])) await indyWallet.createAndOpen(walletConfig) }) @@ -84,18 +79,6 @@ describe('IndyWallet', () => { }) }) - test('Create bls12381g2 keypair', async () => { - await expect(indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 })).resolves.toMatchObject({ - publicKeyBase58: - 't54oLBmhhRcDLUyWTvfYRWw8VRXRy1p43pVm62hrpShrYPuHe9WNAgS33DPfeTK6xK7iPrtJDwCHZjYgbFYDVTJHxXex9xt2XEGF8D356jBT1HtqNeucv3YsPLfTWcLcpFA', - keyType: KeyType.Bls12381g2, - }) - }) - - test('Fail to create bls12381g1g2 keypair', async () => { - await expect(indyWallet.createKey({ seed, keyType: KeyType.Bls12381g1g2 })).rejects.toThrowError(WalletError) - }) - test('Fail to create x25519 keypair', async () => { await expect(indyWallet.createKey({ seed, keyType: KeyType.X25519 })).rejects.toThrowError(WalletError) }) @@ -109,15 +92,6 @@ describe('IndyWallet', () => { expect(signature.length).toStrictEqual(ED25519_SIGNATURE_LENGTH) }) - test('Create a signature with a bls12381g2 keypair', async () => { - const bls12381g2Key = await indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) - const signature = await indyWallet.sign({ - data: message, - key: bls12381g2Key, - }) - expect(signature.length).toStrictEqual(BBS_SIGNATURE_LENGTH) - }) - test('Verify a signed message with a ed25519 publicKey', async () => { const ed25519Key = await indyWallet.createKey({ keyType: KeyType.Ed25519 }) const signature = await indyWallet.sign({ @@ -126,13 +100,4 @@ describe('IndyWallet', () => { }) await expect(indyWallet.verify({ key: ed25519Key, data: message, signature })).resolves.toStrictEqual(true) }) - - test('Verify a signed message with a bls12381g2 publicKey', async () => { - const bls12381g2Key = await indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) - const signature = await indyWallet.sign({ - data: message, - key: bls12381g2Key, - }) - await expect(indyWallet.verify({ key: bls12381g2Key, data: message, signature })).resolves.toStrictEqual(true) - }) }) diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts index caced3613d..ccf614351d 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/core/src/wallet/IndyWallet.ts @@ -8,12 +8,12 @@ import type { } from '../types' import type { Buffer } from '../utils/buffer' import type { - CreateKeyOptions, + WalletCreateKeyOptions, DidConfig, DidInfo, - SignOptions, + WalletSignOptions, UnpackedMessageContext, - VerifyOptions, + WalletVerifyOptions, Wallet, } from './Wallet' import type { default as Indy, WalletStorageConfig } from 'indy-sdk' @@ -470,7 +470,7 @@ export class IndyWallet implements Wallet { * @throws {WalletError} When an unsupported keytype is requested * @throws {WalletError} When the key could not be created */ - public async createKey({ seed, keyType }: CreateKeyOptions): Promise { + public async createKey({ seed, keyType }: WalletCreateKeyOptions): Promise { try { // Ed25519 is supported natively in Indy wallet if (keyType === KeyType.Ed25519) { @@ -508,7 +508,7 @@ export class IndyWallet implements Wallet { * * @returns A signature for the data */ - public async sign({ data, key }: SignOptions): Promise { + public async sign({ data, key }: WalletSignOptions): Promise { try { // Ed25519 is supported natively in Indy wallet if (key.keyType === KeyType.Ed25519) { @@ -555,7 +555,7 @@ export class IndyWallet implements Wallet { * @throws {WalletError} When it could not do the verification * @throws {WalletError} When an unsupported keytype is used */ - public async verify({ data, key, signature }: VerifyOptions): Promise { + public async verify({ data, key, signature }: WalletVerifyOptions): Promise { try { // Ed25519 is supported natively in Indy wallet if (key.keyType === KeyType.Ed25519) { diff --git a/packages/core/src/wallet/Wallet.ts b/packages/core/src/wallet/Wallet.ts index 20218f3928..9e942eff56 100644 --- a/packages/core/src/wallet/Wallet.ts +++ b/packages/core/src/wallet/Wallet.ts @@ -23,9 +23,9 @@ export interface Wallet extends Disposable { export(exportConfig: WalletExportImportConfig): Promise import(walletConfig: WalletConfig, importConfig: WalletExportImportConfig): Promise - createKey(options: CreateKeyOptions): Promise - sign(options: SignOptions): Promise - verify(options: VerifyOptions): Promise + createKey(options: WalletCreateKeyOptions): Promise + sign(options: WalletSignOptions): Promise + verify(options: WalletVerifyOptions): Promise initPublicDid(didConfig: DidConfig): Promise createDid(didConfig?: DidConfig): Promise @@ -40,17 +40,17 @@ export interface DidInfo { verkey: string } -export interface CreateKeyOptions { +export interface WalletCreateKeyOptions { keyType: KeyType seed?: string } -export interface SignOptions { +export interface WalletSignOptions { data: Buffer | Buffer[] key: Key } -export interface VerifyOptions { +export interface WalletVerifyOptions { data: Buffer | Buffer[] key: Key signature: Buffer diff --git a/packages/core/src/wallet/WalletModule.ts b/packages/core/src/wallet/WalletModule.ts index 608285cd55..b8dfc25372 100644 --- a/packages/core/src/wallet/WalletModule.ts +++ b/packages/core/src/wallet/WalletModule.ts @@ -1,7 +1,5 @@ import type { DependencyManager, Module } from '../plugins' -import { SigningProviderToken, Bls12381g2SigningProvider } from '../crypto/signing-provider' - import { WalletApi } from './WalletApi' // TODO: this should be moved into the modules directory @@ -14,8 +12,5 @@ export class WalletModule implements Module { public register(dependencyManager: DependencyManager) { // Api dependencyManager.registerContextScoped(WalletApi) - - // Signing providers. - dependencyManager.registerSingleton(SigningProviderToken, Bls12381g2SigningProvider) } } diff --git a/packages/core/src/wallet/index.ts b/packages/core/src/wallet/index.ts index e60dcfdb68..9259969b47 100644 --- a/packages/core/src/wallet/index.ts +++ b/packages/core/src/wallet/index.ts @@ -1,3 +1,4 @@ export * from './Wallet' export * from './WalletApi' export * from './WalletModule' +export * from './IndyWallet' diff --git a/packages/core/tests/mocks/MockWallet.ts b/packages/core/tests/mocks/MockWallet.ts index 0063fdef9d..caf24a990c 100644 --- a/packages/core/tests/mocks/MockWallet.ts +++ b/packages/core/tests/mocks/MockWallet.ts @@ -7,9 +7,9 @@ import type { DidInfo, UnpackedMessageContext, DidConfig, - CreateKeyOptions, - SignOptions, - VerifyOptions, + WalletCreateKeyOptions, + WalletSignOptions, + WalletVerifyOptions, } from '../../src/wallet' export class MockWallet implements Wallet { @@ -57,14 +57,14 @@ export class MockWallet implements Wallet { public unpack(encryptedMessage: EncryptedMessage): Promise { throw new Error('Method not implemented.') } - public sign(options: SignOptions): Promise { + public sign(options: WalletSignOptions): Promise { throw new Error('Method not implemented.') } - public verify(options: VerifyOptions): Promise { + public verify(options: WalletVerifyOptions): Promise { throw new Error('Method not implemented.') } - public createKey(options: CreateKeyOptions): Promise { + public createKey(options: WalletCreateKeyOptions): Promise { throw new Error('Method not implemented.') } diff --git a/packages/module-bbs/README.md b/packages/module-bbs/README.md new file mode 100644 index 0000000000..0bf81ab439 --- /dev/null +++ b/packages/module-bbs/README.md @@ -0,0 +1,31 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript - BBS Module

+

+ License + typescript + @aries-framework/bbs-signatures version + +

+
+ +Aries Framework JavaScript BBS Module provides an optional addon to Aries Framework JavaScript to use BBS signatures in W3C VC exchange. diff --git a/packages/module-bbs/jest.config.ts b/packages/module-bbs/jest.config.ts new file mode 100644 index 0000000000..55c67d70a6 --- /dev/null +++ b/packages/module-bbs/jest.config.ts @@ -0,0 +1,14 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + name: packageJson.name, + displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/packages/module-bbs/package.json b/packages/module-bbs/package.json new file mode 100644 index 0000000000..a80985b584 --- /dev/null +++ b/packages/module-bbs/package.json @@ -0,0 +1,41 @@ +{ + "name": "@aries-framework/bbs-signatures", + "main": "build/index", + "types": "build/index", + "version": "0.2.2", + "private": true, + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/module-bbs", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/module-bbs" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf -rf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@mattrglobal/bbs-signatures": "^1.0.0", + "@mattrglobal/bls12381-key-pair": "^1.0.0", + "@stablelib/random": "^1.0.2" + }, + "peerDependencies": { + "@aries-framework/core": "0.2.2" + }, + "devDependencies": { + "@aries-framework/node": "0.2.2", + "reflect-metadata": "^0.1.13", + "rimraf": "~3.0.2", + "typescript": "~4.3.0" + } +} diff --git a/packages/module-bbs/src/BbsModule.ts b/packages/module-bbs/src/BbsModule.ts new file mode 100644 index 0000000000..81531db352 --- /dev/null +++ b/packages/module-bbs/src/BbsModule.ts @@ -0,0 +1,35 @@ +import type { DependencyManager, Module } from '@aries-framework/core' + +import { + KeyType, + SigningProviderToken, + VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020, + SignatureSuiteToken, +} from '@aries-framework/core' + +import { Bls12381g2SigningProvider } from './Bls12381g2SigningProvider' +import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from './signature-suites' + +export class BbsModule implements Module { + /** + * Registers the dependencies of the bbs module on the dependency manager. + */ + public register(dependencyManager: DependencyManager) { + // Signing providers. + dependencyManager.registerSingleton(SigningProviderToken, Bls12381g2SigningProvider) + + // Signature suites. + dependencyManager.registerInstance(SignatureSuiteToken, { + suiteClass: BbsBlsSignature2020, + proofType: 'BbsBlsSignature2020', + verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], + keyTypes: [KeyType.Bls12381g2], + }) + dependencyManager.registerInstance(SignatureSuiteToken, { + suiteClass: BbsBlsSignatureProof2020, + proofType: 'BbsBlsSignatureProof2020', + verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], + keyTypes: [KeyType.Bls12381g2], + }) + } +} diff --git a/packages/core/src/crypto/signing-provider/Bls12381g2SigningProvider.ts b/packages/module-bbs/src/Bls12381g2SigningProvider.ts similarity index 92% rename from packages/core/src/crypto/signing-provider/Bls12381g2SigningProvider.ts rename to packages/module-bbs/src/Bls12381g2SigningProvider.ts index 02998c007f..fd7c1de9cf 100644 --- a/packages/core/src/crypto/signing-provider/Bls12381g2SigningProvider.ts +++ b/packages/module-bbs/src/Bls12381g2SigningProvider.ts @@ -1,14 +1,8 @@ -import type { SigningProvider, CreateKeyPairOptions, SignOptions, VerifyOptions, KeyPair } from './SigningProvider' +import type { SigningProvider, CreateKeyPairOptions, KeyPair, SignOptions, VerifyOptions } from '@aries-framework/core' +import { KeyType, injectable, TypedArrayEncoder, SigningProviderError, Buffer } from '@aries-framework/core' import { bls12381toBbs, verify, sign, generateBls12381G2KeyPair } from '@mattrglobal/bbs-signatures' -import { injectable } from '../../plugins' -import { TypedArrayEncoder } from '../../utils' -import { Buffer } from '../../utils/buffer' -import { KeyType } from '../KeyType' - -import { SigningProviderError } from './SigningProviderError' - /** * This will be extracted to the bbs package. */ diff --git a/packages/module-bbs/src/__tests__/BbsModule.test.ts b/packages/module-bbs/src/__tests__/BbsModule.test.ts new file mode 100644 index 0000000000..90bd7c4727 --- /dev/null +++ b/packages/module-bbs/src/__tests__/BbsModule.test.ts @@ -0,0 +1,41 @@ +import type { DependencyManager } from '@aries-framework/core' + +import { + KeyType, + SigningProviderToken, + VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020, + SignatureSuiteToken, +} from '@aries-framework/core' + +import { BbsModule } from '../BbsModule' +import { Bls12381g2SigningProvider } from '../Bls12381g2SigningProvider' +import { BbsBlsSignature2020, BbsBlsSignatureProof2020 } from '../signature-suites' + +const dependencyManager = { + registerInstance: jest.fn(), + registerSingleton: jest.fn(), +} as unknown as DependencyManager + +describe('BbsModule', () => { + test('registers dependencies on the dependency manager', () => { + const bbsModule = new BbsModule() + bbsModule.register(dependencyManager) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(SigningProviderToken, Bls12381g2SigningProvider) + + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(2) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(SignatureSuiteToken, { + suiteClass: BbsBlsSignature2020, + proofType: 'BbsBlsSignature2020', + verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], + keyTypes: [KeyType.Bls12381g2], + }) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(SignatureSuiteToken, { + suiteClass: BbsBlsSignatureProof2020, + proofType: 'BbsBlsSignatureProof2020', + verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], + keyTypes: [KeyType.Bls12381g2], + }) + }) +}) diff --git a/packages/module-bbs/src/index.ts b/packages/module-bbs/src/index.ts new file mode 100644 index 0000000000..0b218fb3d0 --- /dev/null +++ b/packages/module-bbs/src/index.ts @@ -0,0 +1,4 @@ +export * from './signature-suites' +export * from './BbsModule' +export * from './Bls12381g2SigningProvider' +export * from './types' diff --git a/packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignature2020.ts b/packages/module-bbs/src/signature-suites/BbsBlsSignature2020.ts similarity index 95% rename from packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignature2020.ts rename to packages/module-bbs/src/signature-suites/BbsBlsSignature2020.ts index 789260e8c6..f1a4239007 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignature2020.ts +++ b/packages/module-bbs/src/signature-suites/BbsBlsSignature2020.ts @@ -11,29 +11,33 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../types' -import type { DocumentLoader, Proof, VerificationMethod } from '../../jsonldUtil' import type { SignatureSuiteOptions, CreateProofOptions, + VerifyProofOptions, CanonizeOptions, CreateVerifyDataOptions, - VerifyProofOptions, - VerifySignatureOptions, SuiteSignOptions, -} from './types' + VerifySignatureOptions, +} from '../types' +import type { VerificationMethod, JsonObject, DocumentLoader, Proof } from '@aries-framework/core' + +import { + AriesFrameworkError, + TypedArrayEncoder, + SECURITY_CONTEXT_BBS_URL, + SECURITY_CONTEXT_URL, + w3cDate, + vcLibraries, +} from '@aries-framework/core' -import { AriesFrameworkError } from '../../../../error' -import { TypedArrayEncoder } from '../../../../utils' -import { SECURITY_CONTEXT_BBS_URL, SECURITY_CONTEXT_URL } from '../../constants' -import { w3cDate } from '../../jsonldUtil' -import jsonld from '../../libraries/jsonld' -import { suites } from '../../libraries/jsonld-signatures' +const { jsonld, jsonldSignatures } = vcLibraries +const LinkedDataProof = jsonldSignatures.suites.LinkedDataProof /** * A BBS+ signature suite for use with BLS12-381 key pairs */ -export class BbsBlsSignature2020 extends suites.LinkedDataProof { +export class BbsBlsSignature2020 extends LinkedDataProof { private proof: Record /** * Default constructor @@ -361,7 +365,7 @@ export class BbsBlsSignature2020 extends suites.LinkedDataProof { throw new Error('The verification method has been revoked.') } - return document as VerificationMethod + return document as unknown as VerificationMethod } /** diff --git a/packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignatureProof2020.ts b/packages/module-bbs/src/signature-suites/BbsBlsSignatureProof2020.ts similarity index 95% rename from packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignatureProof2020.ts rename to packages/module-bbs/src/signature-suites/BbsBlsSignatureProof2020.ts index 3dbd76f466..ddf5b5d119 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/BbsBlsSignatureProof2020.ts +++ b/packages/module-bbs/src/signature-suites/BbsBlsSignatureProof2020.ts @@ -11,25 +11,21 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../types' -import type { Proof } from '../../jsonldUtil' -import type { DocumentLoader } from '../../libraries/jsonld' -import type { DeriveProofOptions, VerifyProofOptions, CreateVerifyDataOptions, CanonizeOptions } from './types' -import type { VerifyProofResult } from './types/VerifyProofResult' +import type { DeriveProofOptions, VerifyProofOptions, CreateVerifyDataOptions, CanonizeOptions } from '../types' +import type { VerifyProofResult } from '../types/VerifyProofResult' +import type { JsonObject, DocumentLoader, Proof } from '@aries-framework/core/src' +import { AriesFrameworkError, TypedArrayEncoder, SECURITY_CONTEXT_URL, vcLibraries } from '@aries-framework/core' import { blsCreateProof, blsVerifyProof } from '@mattrglobal/bbs-signatures' import { Bls12381G2KeyPair } from '@mattrglobal/bls12381-key-pair' import { randomBytes } from '@stablelib/random' -import { AriesFrameworkError } from '../../../../error' -import { TypedArrayEncoder } from '../../../../utils' -import { SECURITY_CONTEXT_URL } from '../../constants' -import jsonld from '../../libraries/jsonld' -import { suites } from '../../libraries/jsonld-signatures' - import { BbsBlsSignature2020 } from './BbsBlsSignature2020' -export class BbsBlsSignatureProof2020 extends suites.LinkedDataProof { +const { jsonld, jsonldSignatures } = vcLibraries +const LinkedDataProof = jsonldSignatures.suites.LinkedDataProof + +export class BbsBlsSignatureProof2020 extends LinkedDataProof { public constructor({ useNativeCanonize, key, LDKeyClass }: Record = {}) { super({ type: 'BbsBlsSignatureProof2020', diff --git a/packages/core/src/modules/vc/signature-suites/bbs/index.ts b/packages/module-bbs/src/signature-suites/index.ts similarity index 91% rename from packages/core/src/modules/vc/signature-suites/bbs/index.ts rename to packages/module-bbs/src/signature-suites/index.ts index 47275d0d9a..932af48e2f 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/index.ts +++ b/packages/module-bbs/src/signature-suites/index.ts @@ -14,6 +14,3 @@ export { Bls12381G2KeyPair } from '@mattrglobal/bls12381-key-pair' export { BbsBlsSignature2020 } from './BbsBlsSignature2020' export { BbsBlsSignatureProof2020 } from './BbsBlsSignatureProof2020' -export * from './types' - -export { deriveProof } from './deriveProof' diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/CanonizeOptions.ts b/packages/module-bbs/src/types/CanonizeOptions.ts similarity index 94% rename from packages/core/src/modules/vc/signature-suites/bbs/types/CanonizeOptions.ts rename to packages/module-bbs/src/types/CanonizeOptions.ts index 551dc0f777..e2a4af60c8 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/types/CanonizeOptions.ts +++ b/packages/module-bbs/src/types/CanonizeOptions.ts @@ -11,7 +11,7 @@ * limitations under the License. */ -import type { DocumentLoader } from '../../../jsonldUtil' +import type { DocumentLoader } from '@aries-framework/core' /** * Options for canonizing a document diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/CreateProofOptions.ts b/packages/module-bbs/src/types/CreateProofOptions.ts similarity index 85% rename from packages/core/src/modules/vc/signature-suites/bbs/types/CreateProofOptions.ts rename to packages/module-bbs/src/types/CreateProofOptions.ts index 38f54dbceb..d4acbbe0ba 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/types/CreateProofOptions.ts +++ b/packages/module-bbs/src/types/CreateProofOptions.ts @@ -11,9 +11,7 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../../types' -import type { DocumentLoader } from '../../../jsonldUtil' -import type { ProofPurpose } from '../../../proof-purposes/ProofPurpose' +import type { DocumentLoader, ProofPurpose, JsonObject } from '@aries-framework/core' /** * Options for creating a proof diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/CreateVerifyDataOptions.ts b/packages/module-bbs/src/types/CreateVerifyDataOptions.ts similarity index 90% rename from packages/core/src/modules/vc/signature-suites/bbs/types/CreateVerifyDataOptions.ts rename to packages/module-bbs/src/types/CreateVerifyDataOptions.ts index 204dafd5e0..c163eca5c6 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/types/CreateVerifyDataOptions.ts +++ b/packages/module-bbs/src/types/CreateVerifyDataOptions.ts @@ -11,8 +11,7 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../../types' -import type { DocumentLoader } from '../../../jsonldUtil' +import type { JsonObject, DocumentLoader } from '@aries-framework/core' /** * Options for creating a proof diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/DeriveProofOptions.ts b/packages/module-bbs/src/types/DeriveProofOptions.ts similarity index 91% rename from packages/core/src/modules/vc/signature-suites/bbs/types/DeriveProofOptions.ts rename to packages/module-bbs/src/types/DeriveProofOptions.ts index c726180f9d..23fe427798 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/types/DeriveProofOptions.ts +++ b/packages/module-bbs/src/types/DeriveProofOptions.ts @@ -11,8 +11,7 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../../types' -import type { DocumentLoader, Proof } from '../../../jsonldUtil' +import type { JsonObject, DocumentLoader, Proof } from '@aries-framework/core' /** * Options for creating a proof diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/DidDocumentPublicKey.ts b/packages/module-bbs/src/types/DidDocumentPublicKey.ts similarity index 100% rename from packages/core/src/modules/vc/signature-suites/bbs/types/DidDocumentPublicKey.ts rename to packages/module-bbs/src/types/DidDocumentPublicKey.ts diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/JsonWebKey.ts b/packages/module-bbs/src/types/JsonWebKey.ts similarity index 100% rename from packages/core/src/modules/vc/signature-suites/bbs/types/JsonWebKey.ts rename to packages/module-bbs/src/types/JsonWebKey.ts diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/KeyPairOptions.ts b/packages/module-bbs/src/types/KeyPairOptions.ts similarity index 100% rename from packages/core/src/modules/vc/signature-suites/bbs/types/KeyPairOptions.ts rename to packages/module-bbs/src/types/KeyPairOptions.ts diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/KeyPairSigner.ts b/packages/module-bbs/src/types/KeyPairSigner.ts similarity index 100% rename from packages/core/src/modules/vc/signature-suites/bbs/types/KeyPairSigner.ts rename to packages/module-bbs/src/types/KeyPairSigner.ts diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/KeyPairVerifier.ts b/packages/module-bbs/src/types/KeyPairVerifier.ts similarity index 100% rename from packages/core/src/modules/vc/signature-suites/bbs/types/KeyPairVerifier.ts rename to packages/module-bbs/src/types/KeyPairVerifier.ts diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/SignatureSuiteOptions.ts b/packages/module-bbs/src/types/SignatureSuiteOptions.ts similarity index 92% rename from packages/core/src/modules/vc/signature-suites/bbs/types/SignatureSuiteOptions.ts rename to packages/module-bbs/src/types/SignatureSuiteOptions.ts index 34209afdda..55218e5b11 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/types/SignatureSuiteOptions.ts +++ b/packages/module-bbs/src/types/SignatureSuiteOptions.ts @@ -11,9 +11,8 @@ * limitations under the License. */ -import type { LdKeyPair } from '../../../../../crypto/LdKeyPair' -import type { JsonArray } from '../../../../../types' import type { KeyPairSigner } from './KeyPairSigner' +import type { JsonArray, LdKeyPair } from '@aries-framework/core' import type { Bls12381G2KeyPair } from '@mattrglobal/bls12381-key-pair' /** diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/SuiteSignOptions.ts b/packages/module-bbs/src/types/SuiteSignOptions.ts similarity index 90% rename from packages/core/src/modules/vc/signature-suites/bbs/types/SuiteSignOptions.ts rename to packages/module-bbs/src/types/SuiteSignOptions.ts index a754325972..850587dc60 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/types/SuiteSignOptions.ts +++ b/packages/module-bbs/src/types/SuiteSignOptions.ts @@ -11,8 +11,7 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../../types' -import type { DocumentLoader } from '../../../jsonldUtil' +import type { JsonObject, DocumentLoader } from '@aries-framework/core' /** * Options for signing using a signature suite diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/VerifyProofOptions.ts b/packages/module-bbs/src/types/VerifyProofOptions.ts similarity index 84% rename from packages/core/src/modules/vc/signature-suites/bbs/types/VerifyProofOptions.ts rename to packages/module-bbs/src/types/VerifyProofOptions.ts index 4bf5f0c953..9aa2a60ff4 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/types/VerifyProofOptions.ts +++ b/packages/module-bbs/src/types/VerifyProofOptions.ts @@ -11,9 +11,7 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../../types' -import type { DocumentLoader, Proof } from '../../../jsonldUtil' -import type { ProofPurpose } from '../../../proof-purposes/ProofPurpose' +import type { Proof, JsonObject, ProofPurpose, DocumentLoader } from '@aries-framework/core' /** * Options for verifying a proof diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/VerifyProofResult.ts b/packages/module-bbs/src/types/VerifyProofResult.ts similarity index 100% rename from packages/core/src/modules/vc/signature-suites/bbs/types/VerifyProofResult.ts rename to packages/module-bbs/src/types/VerifyProofResult.ts diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/VerifySignatureOptions.ts b/packages/module-bbs/src/types/VerifySignatureOptions.ts similarity index 89% rename from packages/core/src/modules/vc/signature-suites/bbs/types/VerifySignatureOptions.ts rename to packages/module-bbs/src/types/VerifySignatureOptions.ts index a1597b59e8..07ea80c5b8 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/types/VerifySignatureOptions.ts +++ b/packages/module-bbs/src/types/VerifySignatureOptions.ts @@ -11,8 +11,7 @@ * limitations under the License. */ -import type { JsonObject } from '../../../../../types' -import type { DocumentLoader, Proof, VerificationMethod } from '../../../jsonldUtil' +import type { VerificationMethod, JsonObject, Proof, DocumentLoader } from '@aries-framework/core' /** * Options for verifying a signature diff --git a/packages/core/src/modules/vc/signature-suites/bbs/types/index.ts b/packages/module-bbs/src/types/index.ts similarity index 89% rename from packages/core/src/modules/vc/signature-suites/bbs/types/index.ts rename to packages/module-bbs/src/types/index.ts index f3436316be..60575814bb 100644 --- a/packages/core/src/modules/vc/signature-suites/bbs/types/index.ts +++ b/packages/module-bbs/src/types/index.ts @@ -23,6 +23,3 @@ export { VerifySignatureOptions } from './VerifySignatureOptions' export { SuiteSignOptions } from './SuiteSignOptions' export { DeriveProofOptions } from './DeriveProofOptions' export { DidDocumentPublicKey } from './DidDocumentPublicKey' -export { GetProofsOptions } from './GetProofsOptions' -export { GetProofsResult } from './GetProofsResult' -export { GetTypeOptions } from './GetTypeOptions' diff --git a/packages/module-bbs/tests/bbs-signatures.e2e.test.ts b/packages/module-bbs/tests/bbs-signatures.e2e.test.ts new file mode 100644 index 0000000000..72a76fa517 --- /dev/null +++ b/packages/module-bbs/tests/bbs-signatures.e2e.test.ts @@ -0,0 +1,267 @@ +import type { W3cCredentialRepository } from '../../core/src/modules/vc/repository' +import type { AgentContext } from '@aries-framework/core' + +import { + VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, + KeyType, + JsonTransformer, + DidResolverService, + DidKey, + Key, + SigningProviderRegistry, + W3cVerifiableCredential, + W3cCredentialService, + W3cCredential, + CredentialIssuancePurpose, + VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020, + orArrayToArray, + vcLibraries, + LinkedDataProof, + W3cPresentation, + W3cVerifiablePresentation, + IndyWallet, + Ed25519Signature2018, +} from '@aries-framework/core' + +import { SignatureSuiteRegistry } from '../../core/src/modules/vc/SignatureSuiteRegistry' +import { customDocumentLoader } from '../../core/src/modules/vc/__tests__/documentLoader' +import { getAgentConfig, getAgentContext } from '../../core/tests/helpers' +import { BbsBlsSignature2020, BbsBlsSignatureProof2020, Bls12381g2SigningProvider } from '../src' + +import { BbsBlsSignature2020Fixtures } from './fixtures' +import { describeSkipNode17And18 } from './util' + +const { jsonldSignatures } = vcLibraries +const { purposes } = jsonldSignatures + +const signatureSuiteRegistry = new SignatureSuiteRegistry([ + { + suiteClass: BbsBlsSignature2020, + proofType: 'BbsBlsSignature2020', + verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], + keyTypes: [KeyType.Bls12381g2], + }, + { + suiteClass: BbsBlsSignatureProof2020, + proofType: 'BbsBlsSignatureProof2020', + verificationMethodTypes: [VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020], + keyTypes: [KeyType.Bls12381g2], + }, + { + suiteClass: Ed25519Signature2018, + proofType: 'Ed25519Signature2018', + verificationMethodTypes: [VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018], + keyTypes: [KeyType.Ed25519], + }, +]) + +const signingProviderRegistry = new SigningProviderRegistry([new Bls12381g2SigningProvider()]) + +const agentConfig = getAgentConfig('BbsSignaturesE2eTest') + +describeSkipNode17And18('BBS W3cCredentialService', () => { + let wallet: IndyWallet + let agentContext: AgentContext + let didResolverService: DidResolverService + let w3cCredentialService: W3cCredentialService + const seed = 'testseed000000000000000000000001' + + beforeAll(async () => { + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, signingProviderRegistry) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await wallet.createAndOpen(agentConfig.walletConfig!) + agentContext = getAgentContext({ + agentConfig, + wallet, + }) + didResolverService = new DidResolverService(agentConfig.logger, []) + w3cCredentialService = new W3cCredentialService( + {} as unknown as W3cCredentialRepository, + didResolverService, + signatureSuiteRegistry + ) + w3cCredentialService.documentLoaderWithContext = () => customDocumentLoader + }) + + afterAll(async () => { + await wallet.delete() + }) + + describe('Utility methods', () => { + describe('getKeyTypesByProofType', () => { + it('should return the correct key types for BbsBlsSignature2020 proof type', async () => { + const keyTypes = w3cCredentialService.getKeyTypesByProofType('BbsBlsSignature2020') + expect(keyTypes).toEqual([KeyType.Bls12381g2]) + }) + it('should return the correct key types for BbsBlsSignatureProof2020 proof type', async () => { + const keyTypes = w3cCredentialService.getKeyTypesByProofType('BbsBlsSignatureProof2020') + expect(keyTypes).toEqual([KeyType.Bls12381g2]) + }) + }) + + describe('getVerificationMethodTypesByProofType', () => { + it('should return the correct key types for BbsBlsSignature2020 proof type', async () => { + const verificationMethodTypes = + w3cCredentialService.getVerificationMethodTypesByProofType('BbsBlsSignature2020') + expect(verificationMethodTypes).toEqual([VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020]) + }) + it('should return the correct key types for BbsBlsSignatureProof2020 proof type', async () => { + const verificationMethodTypes = + w3cCredentialService.getVerificationMethodTypesByProofType('BbsBlsSignatureProof2020') + expect(verificationMethodTypes).toEqual([VERIFICATION_METHOD_TYPE_BLS12381G2_KEY_2020]) + }) + }) + }) + + describe('BbsBlsSignature2020', () => { + let issuerDidKey: DidKey + let verificationMethod: string + + beforeAll(async () => { + const key = await wallet.createKey({ keyType: KeyType.Bls12381g2, seed }) + issuerDidKey = new DidKey(key) + verificationMethod = `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}` + }) + + describe('signCredential', () => { + it('should return a successfully signed credential bbs', async () => { + const credentialJson = BbsBlsSignature2020Fixtures.TEST_LD_DOCUMENT + credentialJson.issuer = issuerDidKey.did + + const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) + + const vc = await w3cCredentialService.signCredential(agentContext, { + credential, + proofType: 'BbsBlsSignature2020', + verificationMethod: verificationMethod, + }) + + expect(vc).toBeInstanceOf(W3cVerifiableCredential) + expect(vc.issuer).toEqual(issuerDidKey.did) + expect(Array.isArray(vc.proof)).toBe(false) + expect(vc.proof).toBeInstanceOf(LinkedDataProof) + + vc.proof = vc.proof as LinkedDataProof + expect(vc.proof.verificationMethod).toEqual(verificationMethod) + }) + }) + + describe('verifyCredential', () => { + it('should verify the credential successfully', async () => { + const result = await w3cCredentialService.verifyCredential(agentContext, { + credential: JsonTransformer.fromJSON( + BbsBlsSignature2020Fixtures.TEST_LD_DOCUMENT_SIGNED, + W3cVerifiableCredential + ), + proofPurpose: new purposes.AssertionProofPurpose(), + }) + + expect(result.verified).toEqual(true) + }) + }) + + describe('deriveProof', () => { + it('should derive proof successfully', async () => { + const credentialJson = BbsBlsSignature2020Fixtures.TEST_LD_DOCUMENT_SIGNED + + const vc = JsonTransformer.fromJSON(credentialJson, W3cVerifiableCredential) + + const revealDocument = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + type: ['VerifiableCredential', 'PermanentResidentCard'], + credentialSubject: { + '@explicit': true, + type: ['PermanentResident', 'Person'], + givenName: {}, + familyName: {}, + gender: {}, + }, + } + + const result = await w3cCredentialService.deriveProof(agentContext, { + credential: vc, + revealDocument: revealDocument, + verificationMethod: verificationMethod, + }) + + // result.proof = result.proof as LinkedDataProof + expect(orArrayToArray(result.proof)[0].verificationMethod).toBe( + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN' + ) + }) + }) + + describe('verifyDerived', () => { + it('should verify the derived proof successfully', async () => { + const result = await w3cCredentialService.verifyCredential(agentContext, { + credential: JsonTransformer.fromJSON(BbsBlsSignature2020Fixtures.TEST_VALID_DERIVED, W3cVerifiableCredential), + proofPurpose: new purposes.AssertionProofPurpose(), + }) + expect(result.verified).toEqual(true) + }) + }) + + describe('createPresentation', () => { + it('should create a presentation successfully', async () => { + const vc = JsonTransformer.fromJSON(BbsBlsSignature2020Fixtures.TEST_VALID_DERIVED, W3cVerifiableCredential) + const result = await w3cCredentialService.createPresentation({ credentials: vc }) + + expect(result).toBeInstanceOf(W3cPresentation) + + expect(result.type).toEqual(expect.arrayContaining(['VerifiablePresentation'])) + + expect(result.verifiableCredential).toHaveLength(1) + expect(result.verifiableCredential).toEqual(expect.arrayContaining([vc])) + }) + }) + + describe('signPresentation', () => { + it('should sign the presentation successfully', async () => { + const signingKey = Key.fromPublicKeyBase58((await wallet.createDid({ seed })).verkey, KeyType.Ed25519) + const signingDidKey = new DidKey(signingKey) + const verificationMethod = `${signingDidKey.did}#${signingDidKey.key.fingerprint}` + const presentation = JsonTransformer.fromJSON(BbsBlsSignature2020Fixtures.TEST_VP_DOCUMENT, W3cPresentation) + + const purpose = new CredentialIssuancePurpose({ + controller: { + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + }, + date: new Date().toISOString(), + }) + + const verifiablePresentation = await w3cCredentialService.signPresentation(agentContext, { + presentation: presentation, + purpose: purpose, + signatureType: 'Ed25519Signature2018', + challenge: 'e950bfe5-d7ec-4303-ad61-6983fb976ac9', + verificationMethod: verificationMethod, + }) + + expect(verifiablePresentation).toBeInstanceOf(W3cVerifiablePresentation) + }) + }) + + describe('verifyPresentation', () => { + it('should successfully verify a presentation containing a single verifiable credential bbs', async () => { + const vp = JsonTransformer.fromJSON( + BbsBlsSignature2020Fixtures.TEST_VP_DOCUMENT_SIGNED, + W3cVerifiablePresentation + ) + + const result = await w3cCredentialService.verifyPresentation(agentContext, { + presentation: vp, + proofType: 'Ed25519Signature2018', + challenge: 'e950bfe5-d7ec-4303-ad61-6983fb976ac9', + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + }) + + expect(result.verified).toBe(true) + }) + }) + }) +}) diff --git a/packages/module-bbs/tests/bbs-signing-provider.e2e.test.ts b/packages/module-bbs/tests/bbs-signing-provider.e2e.test.ts new file mode 100644 index 0000000000..b5a90765af --- /dev/null +++ b/packages/module-bbs/tests/bbs-signing-provider.e2e.test.ts @@ -0,0 +1,74 @@ +import type { WalletConfig } from '@aries-framework/core' + +import { + KeyDerivationMethod, + KeyType, + WalletError, + TypedArrayEncoder, + SigningProviderRegistry, + IndyWallet, +} from '@aries-framework/core' +import { agentDependencies } from '@aries-framework/node' +import { BBS_SIGNATURE_LENGTH } from '@mattrglobal/bbs-signatures' + +import testLogger from '../../core/tests/logger' +import { Bls12381g2SigningProvider } from '../src' + +import { describeSkipNode17And18 } from './util' + +// use raw key derivation method to speed up wallet creating / opening / closing between tests +const walletConfig: WalletConfig = { + id: 'Wallet: IndyWalletTest', + // generated using indy.generateWalletKey + key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', + keyDerivationMethod: KeyDerivationMethod.Raw, +} + +describeSkipNode17And18('BBS Signing Provider', () => { + let indyWallet: IndyWallet + const seed = 'sample-seed' + const message = TypedArrayEncoder.fromString('sample-message') + + beforeEach(async () => { + indyWallet = new IndyWallet( + agentDependencies, + testLogger, + new SigningProviderRegistry([new Bls12381g2SigningProvider()]) + ) + await indyWallet.createAndOpen(walletConfig) + }) + + afterEach(async () => { + await indyWallet.delete() + }) + + test('Create bls12381g2 keypair', async () => { + await expect(indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 })).resolves.toMatchObject({ + publicKeyBase58: + 't54oLBmhhRcDLUyWTvfYRWw8VRXRy1p43pVm62hrpShrYPuHe9WNAgS33DPfeTK6xK7iPrtJDwCHZjYgbFYDVTJHxXex9xt2XEGF8D356jBT1HtqNeucv3YsPLfTWcLcpFA', + keyType: KeyType.Bls12381g2, + }) + }) + + test('Fail to create bls12381g1g2 keypair', async () => { + await expect(indyWallet.createKey({ seed, keyType: KeyType.Bls12381g1g2 })).rejects.toThrowError(WalletError) + }) + + test('Create a signature with a bls12381g2 keypair', async () => { + const bls12381g2Key = await indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) + const signature = await indyWallet.sign({ + data: message, + key: bls12381g2Key, + }) + expect(signature.length).toStrictEqual(BBS_SIGNATURE_LENGTH) + }) + + test('Verify a signed message with a bls12381g2 publicKey', async () => { + const bls12381g2Key = await indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) + const signature = await indyWallet.sign({ + data: message, + key: bls12381g2Key, + }) + await expect(indyWallet.verify({ key: bls12381g2Key, data: message, signature })).resolves.toStrictEqual(true) + }) +}) diff --git a/packages/module-bbs/tests/fixtures.ts b/packages/module-bbs/tests/fixtures.ts new file mode 100644 index 0000000000..d8946c97c6 --- /dev/null +++ b/packages/module-bbs/tests/fixtures.ts @@ -0,0 +1,210 @@ +import { CREDENTIALS_CONTEXT_V1_URL, SECURITY_CONTEXT_BBS_URL } from '@aries-framework/core' + +export const BbsBlsSignature2020Fixtures = { + TEST_LD_DOCUMENT: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: '', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + }, + + TEST_LD_DOCUMENT_SIGNED: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + proof: { + type: 'BbsBlsSignature2020', + created: '2022-04-13T13:47:47Z', + verificationMethod: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + proofPurpose: 'assertionMethod', + proofValue: + 'hoNNnnRIoEoaY9Fvg3pGVG2eWTAHnR1kIM01nObEL2FdI2IkkpM3246jn3VBD8KBYUHlKfzccE4m7waZyoLEkBLFiK2g54Q2i+CdtYBgDdkUDsoULSBMcH1MwGHwdjfXpldFNFrHFx/IAvLVniyeMQ==', + }, + }, + TEST_LD_DOCUMENT_BAD_SIGNED: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + proof: { + type: 'BbsBlsSignature2020', + created: '2022-04-13T13:47:47Z', + verificationMethod: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + proofPurpose: 'assertionMethod', + proofValue: + 'gU44r/fmvGpkOyMRZX4nwRB6IsbrL7zbVTs+yu6bZGeCNJuiJqS5U6fCPuvGQ+iNYUHlKfzccE4m7waZyoLEkBLFiK2g54Q2i+CdtYBgDdkUDsoULSBMcH1MwGHwdjfXpldFNFrHFx/IAvLVniyeMQ==', + }, + }, + + TEST_VALID_DERIVED: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['PermanentResidentCard', 'VerifiableCredential'], + description: 'Government of Example Permanent Resident Card.', + identifier: '83627465', + name: 'Permanent Resident Card', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['Person', 'PermanentResident'], + familyName: 'SMITH', + gender: 'Male', + givenName: 'JOHN', + }, + expirationDate: '2029-12-03T12:19:52Z', + issuanceDate: '2019-12-03T12:19:52Z', + issuer: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + proof: { + type: 'BbsBlsSignatureProof2020', + created: '2022-04-13T13:47:47Z', + nonce: 'GfuRhH8hSAcWm5RWgUQYNQNWjQBsWuVgMCJrhTCD3kSpnHmQOkHcnNAoBsgyMAT4UUI=', + proofPurpose: 'assertionMethod', + proofValue: + 'ABkB/wbvkcCcbPRE5vrXc++orru4MsgrS4ESsZ30RNCs3noqLwm94/RZNp62I6Hyf0Kmht0Vog70HDtnNzbnMAj/zD9oT/N53pOADrtn5v+xZgP3cK4N2d6amg6h3LXem29gidW9hMrROPLit5cWEIL4/HOzxPxQQGYiwEXdW++Aja5ZuwJoMsIx7ysn4C4ekN7JXZtnAAAAdJR/oeDShxRdSBlnCSUHkE4Ol+Z3AhXBKkxb4AxiMKOiNmBreMTjJUGwNAPNU2aKnAAAAAIBUuKV0W0YBQZY/mwLmwCcyOWMiaEpjnVhYip4jhBBZw1aPBe8GzsG9zv3Sf9XAyGEAvVFe3OvwvMwYY5nZYdYoLSR4TLl1aLw0oChiPm2zb6ApXypCEEVd8KhJMATyssTlY48bEljDNixAD2rVDaoAAAACWjyrWp3b62M5Onuwo9EItCrBjPD68xC12q1agqgwFTnOI0+MfEwVGNZsA0IqkCGrZmo3AyRpcRm51IYDWYorM4hued5EcVHeCGd6NrnLSxTFPEu8lnmCoMXcxBWDCZFRGb//M5WlncbsYiz01itHbSs1nmpj3o+DYlF2ZyOYphvLo5A9T4rWVwHRK1+LeCDEawOnI03DWLyN8U4ZpbpcdZNK421IwNjseYY+ptvvL3juZ2uQR84maAZYy/OjMuHNyzqHPXNgsLLqtrvPo0kncefp+x1jgA0J/b5xfT72+vhKZAN1R48/uPf+DySC3avwD3T+YHjePn1bBOidhCWMjwzI9LYO8VvhcWXzH7nBWh5MeUch+Wkl777KrsLhrXnCg==', + verificationMethod: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + }, + }, + + TEST_VP_DOCUMENT: { + '@context': [CREDENTIALS_CONTEXT_V1_URL], + type: ['VerifiablePresentation'], + verifiableCredential: [ + { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['PermanentResidentCard', 'VerifiableCredential'], + description: 'Government of Example Permanent Resident Card.', + identifier: '83627465', + name: 'Permanent Resident Card', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['Person', 'PermanentResident'], + familyName: 'SMITH', + gender: 'Male', + givenName: 'JOHN', + }, + expirationDate: '2029-12-03T12:19:52Z', + issuanceDate: '2019-12-03T12:19:52Z', + issuer: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + proof: { + type: 'BbsBlsSignatureProof2020', + created: '2022-04-13T13:47:47Z', + nonce: 'GfuRhH8hSAcWm5RWgUQYNQNWjQBsWuVgMCJrhTCD3kSpnHmQOkHcnNAoBsgyMAT4UUI=', + proofPurpose: 'assertionMethod', + proofValue: + 'ABkB/wbvkcCcbPRE5vrXc++orru4MsgrS4ESsZ30RNCs3noqLwm94/RZNp62I6Hyf0Kmht0Vog70HDtnNzbnMAj/zD9oT/N53pOADrtn5v+xZgP3cK4N2d6amg6h3LXem29gidW9hMrROPLit5cWEIL4/HOzxPxQQGYiwEXdW++Aja5ZuwJoMsIx7ysn4C4ekN7JXZtnAAAAdJR/oeDShxRdSBlnCSUHkE4Ol+Z3AhXBKkxb4AxiMKOiNmBreMTjJUGwNAPNU2aKnAAAAAIBUuKV0W0YBQZY/mwLmwCcyOWMiaEpjnVhYip4jhBBZw1aPBe8GzsG9zv3Sf9XAyGEAvVFe3OvwvMwYY5nZYdYoLSR4TLl1aLw0oChiPm2zb6ApXypCEEVd8KhJMATyssTlY48bEljDNixAD2rVDaoAAAACWjyrWp3b62M5Onuwo9EItCrBjPD68xC12q1agqgwFTnOI0+MfEwVGNZsA0IqkCGrZmo3AyRpcRm51IYDWYorM4hued5EcVHeCGd6NrnLSxTFPEu8lnmCoMXcxBWDCZFRGb//M5WlncbsYiz01itHbSs1nmpj3o+DYlF2ZyOYphvLo5A9T4rWVwHRK1+LeCDEawOnI03DWLyN8U4ZpbpcdZNK421IwNjseYY+ptvvL3juZ2uQR84maAZYy/OjMuHNyzqHPXNgsLLqtrvPo0kncefp+x1jgA0J/b5xfT72+vhKZAN1R48/uPf+DySC3avwD3T+YHjePn1bBOidhCWMjwzI9LYO8VvhcWXzH7nBWh5MeUch+Wkl777KrsLhrXnCg==', + verificationMethod: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + }, + }, + ], + }, + TEST_VP_DOCUMENT_SIGNED: { + '@context': [CREDENTIALS_CONTEXT_V1_URL], + type: ['VerifiablePresentation'], + verifiableCredential: [ + { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['PermanentResidentCard', 'VerifiableCredential'], + description: 'Government of Example Permanent Resident Card.', + identifier: '83627465', + name: 'Permanent Resident Card', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['Person', 'PermanentResident'], + familyName: 'SMITH', + gender: 'Male', + givenName: 'JOHN', + }, + expirationDate: '2029-12-03T12:19:52Z', + issuanceDate: '2019-12-03T12:19:52Z', + issuer: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + proof: { + type: 'BbsBlsSignatureProof2020', + created: '2022-04-13T13:47:47Z', + nonce: 'GfuRhH8hSAcWm5RWgUQYNQNWjQBsWuVgMCJrhTCD3kSpnHmQOkHcnNAoBsgyMAT4UUI=', + proofPurpose: 'assertionMethod', + proofValue: + 'ABkB/wbvkcCcbPRE5vrXc++orru4MsgrS4ESsZ30RNCs3noqLwm94/RZNp62I6Hyf0Kmht0Vog70HDtnNzbnMAj/zD9oT/N53pOADrtn5v+xZgP3cK4N2d6amg6h3LXem29gidW9hMrROPLit5cWEIL4/HOzxPxQQGYiwEXdW++Aja5ZuwJoMsIx7ysn4C4ekN7JXZtnAAAAdJR/oeDShxRdSBlnCSUHkE4Ol+Z3AhXBKkxb4AxiMKOiNmBreMTjJUGwNAPNU2aKnAAAAAIBUuKV0W0YBQZY/mwLmwCcyOWMiaEpjnVhYip4jhBBZw1aPBe8GzsG9zv3Sf9XAyGEAvVFe3OvwvMwYY5nZYdYoLSR4TLl1aLw0oChiPm2zb6ApXypCEEVd8KhJMATyssTlY48bEljDNixAD2rVDaoAAAACWjyrWp3b62M5Onuwo9EItCrBjPD68xC12q1agqgwFTnOI0+MfEwVGNZsA0IqkCGrZmo3AyRpcRm51IYDWYorM4hued5EcVHeCGd6NrnLSxTFPEu8lnmCoMXcxBWDCZFRGb//M5WlncbsYiz01itHbSs1nmpj3o+DYlF2ZyOYphvLo5A9T4rWVwHRK1+LeCDEawOnI03DWLyN8U4ZpbpcdZNK421IwNjseYY+ptvvL3juZ2uQR84maAZYy/OjMuHNyzqHPXNgsLLqtrvPo0kncefp+x1jgA0J/b5xfT72+vhKZAN1R48/uPf+DySC3avwD3T+YHjePn1bBOidhCWMjwzI9LYO8VvhcWXzH7nBWh5MeUch+Wkl777KrsLhrXnCg==', + verificationMethod: + 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN#zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN', + }, + }, + ], + proof: { + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + type: 'Ed25519Signature2018', + created: '2022-04-21T10:15:38Z', + proofPurpose: 'authentication', + challenge: 'e950bfe5-d7ec-4303-ad61-6983fb976ac9', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..wGtR9yuTRfhrsvCthUOn-fg_lK0mZIe2IOO2Lv21aOXo5YUAbk50qMBLk4C1iqoOx-Jz6R0g4aa4cuqpdXzkBw', + }, + }, +} diff --git a/packages/module-bbs/tests/setup.ts b/packages/module-bbs/tests/setup.ts new file mode 100644 index 0000000000..00b77cc0fe --- /dev/null +++ b/packages/module-bbs/tests/setup.ts @@ -0,0 +1,3 @@ +import 'reflect-metadata' + +jest.setTimeout(30000) diff --git a/packages/module-bbs/tests/util.ts b/packages/module-bbs/tests/util.ts new file mode 100644 index 0000000000..5d73cbe64b --- /dev/null +++ b/packages/module-bbs/tests/util.ts @@ -0,0 +1,19 @@ +export function testSkipNode17And18(...parameters: Parameters) { + const version = process.version + + if (version.startsWith('v17.') || version.startsWith('v18.')) { + test.skip(...parameters) + } else { + test(...parameters) + } +} + +export function describeSkipNode17And18(...parameters: Parameters) { + const version = process.version + + if (version.startsWith('v17.') || version.startsWith('v18.')) { + describe.skip(...parameters) + } else { + describe(...parameters) + } +} diff --git a/packages/module-bbs/tsconfig.build.json b/packages/module-bbs/tsconfig.build.json new file mode 100644 index 0000000000..9c30e30bd2 --- /dev/null +++ b/packages/module-bbs/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.build.json", + + "compilerOptions": { + "outDir": "./build" + }, + + "include": ["src/**/*"] +} diff --git a/packages/module-bbs/tsconfig.json b/packages/module-bbs/tsconfig.json new file mode 100644 index 0000000000..46efe6f721 --- /dev/null +++ b/packages/module-bbs/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + } +} diff --git a/yarn.lock b/yarn.lock index 9187c4a305..700134c89b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2281,6 +2281,14 @@ "@stablelib/binary" "^1.0.1" "@stablelib/wipe" "^1.0.1" +"@stablelib/random@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@stablelib/random/-/random-1.0.2.tgz#2dece393636489bf7e19c51229dd7900eddf742c" + integrity sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + "@stablelib/sha256@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@stablelib/sha256/-/sha256-1.0.1.tgz#77b6675b67f9b0ea081d2e31bda4866297a3ae4f" From 7be979a74b86c606db403c8df04cfc8be2aae249 Mon Sep 17 00:00:00 2001 From: jakubkoci Date: Sat, 8 Oct 2022 10:22:06 +0200 Subject: [PATCH 050/125] fix(oob): set connection alias when creating invitation (#1047) Signed-off-by: Jakub Koci --- .../src/modules/connections/DidExchangeProtocol.ts | 1 + .../modules/connections/services/ConnectionService.ts | 1 + packages/core/src/modules/oob/OutOfBandModule.ts | 5 +++-- .../core/src/modules/oob/repository/OutOfBandRecord.ts | 3 +++ .../__fixtures__/alice-8-connections-0.1.json | 1 + .../migration/__tests__/__snapshots__/0.1.test.ts.snap | 8 ++++++++ .../storage/migration/updates/0.1-0.2/connection.ts | 1 + packages/core/tests/oob.test.ts | 10 +++++++--- 8 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/core/src/modules/connections/DidExchangeProtocol.ts b/packages/core/src/modules/connections/DidExchangeProtocol.ts index bc2a4e939e..53490db0a3 100644 --- a/packages/core/src/modules/connections/DidExchangeProtocol.ts +++ b/packages/core/src/modules/connections/DidExchangeProtocol.ts @@ -189,6 +189,7 @@ export class DidExchangeProtocol { protocol: HandshakeProtocol.DidExchange, role: DidExchangeRole.Responder, state: DidExchangeState.RequestReceived, + alias: outOfBandRecord.alias, theirDid: message.did, theirLabel: message.label, threadId: message.threadId, diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index 5b7de49125..719e80429f 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -173,6 +173,7 @@ export class ConnectionService { protocol: HandshakeProtocol.Connections, role: DidExchangeRole.Responder, state: DidExchangeState.RequestReceived, + alias: outOfBandRecord.alias, theirLabel: message.label, imageUrl: message.imageUrl, outOfBandId: outOfBandRecord.id, diff --git a/packages/core/src/modules/oob/OutOfBandModule.ts b/packages/core/src/modules/oob/OutOfBandModule.ts index cdd722aefc..463f2f1a13 100644 --- a/packages/core/src/modules/oob/OutOfBandModule.ts +++ b/packages/core/src/modules/oob/OutOfBandModule.ts @@ -46,7 +46,7 @@ const didCommProfiles = ['didcomm/aip1', 'didcomm/aip2;env=rfc19'] export interface CreateOutOfBandInvitationConfig { label?: string - alias?: string + alias?: string // alias for a connection record to be created imageUrl?: string goalCode?: string goal?: string @@ -61,7 +61,7 @@ export interface CreateOutOfBandInvitationConfig { export interface CreateLegacyInvitationConfig { label?: string - alias?: string + alias?: string // alias for a connection record to be created imageUrl?: string multiUseInvitation?: boolean autoAcceptConnection?: boolean @@ -208,6 +208,7 @@ export class OutOfBandModule { mediatorId: routing.mediatorId, role: OutOfBandRole.Sender, state: OutOfBandState.AwaitResponse, + alias: config.alias, outOfBandInvitation: outOfBandInvitation, reusable: multiUseInvitation, autoAcceptConnection, diff --git a/packages/core/src/modules/oob/repository/OutOfBandRecord.ts b/packages/core/src/modules/oob/repository/OutOfBandRecord.ts index 3a67aa4aa7..ec291225c2 100644 --- a/packages/core/src/modules/oob/repository/OutOfBandRecord.ts +++ b/packages/core/src/modules/oob/repository/OutOfBandRecord.ts @@ -27,6 +27,7 @@ export interface OutOfBandRecordProps { outOfBandInvitation: OutOfBandInvitation role: OutOfBandRole state: OutOfBandState + alias?: string autoAcceptConnection?: boolean reusable?: boolean mediatorId?: string @@ -38,6 +39,7 @@ export class OutOfBandRecord extends BaseRecord { goal: 'To make a connection', goalCode: 'p2p-messaging', label: 'Faber College', + alias: `Faber's connection with Alice`, } const issueCredentialConfig = { @@ -158,10 +159,11 @@ describe('out of band', () => { expect(outOfBandRecord.autoAcceptConnection).toBe(true) expect(outOfBandRecord.role).toBe(OutOfBandRole.Sender) expect(outOfBandRecord.state).toBe(OutOfBandState.AwaitResponse) + expect(outOfBandRecord.alias).toBe(makeConnectionConfig.alias) expect(outOfBandRecord.reusable).toBe(false) - expect(outOfBandRecord.outOfBandInvitation.goal).toBe('To make a connection') - expect(outOfBandRecord.outOfBandInvitation.goalCode).toBe('p2p-messaging') - expect(outOfBandRecord.outOfBandInvitation.label).toBe('Faber College') + expect(outOfBandRecord.outOfBandInvitation.goal).toBe(makeConnectionConfig.goal) + expect(outOfBandRecord.outOfBandInvitation.goalCode).toBe(makeConnectionConfig.goalCode) + expect(outOfBandRecord.outOfBandInvitation.label).toBe(makeConnectionConfig.label) }) test('create OOB message only with handshake', async () => { @@ -290,6 +292,7 @@ describe('out of band', () => { expect(aliceFaberConnection).toBeConnectedWith(faberAliceConnection!) expect(faberAliceConnection).toBeConnectedWith(aliceFaberConnection) + expect(faberAliceConnection.alias).toBe(makeConnectionConfig.alias) }) test(`make a connection with ${HandshakeProtocol.Connections} based on OOB invitation encoded in URL`, async () => { @@ -311,6 +314,7 @@ describe('out of band', () => { expect(aliceFaberConnection).toBeConnectedWith(faberAliceConnection) expect(faberAliceConnection).toBeConnectedWith(aliceFaberConnection) + expect(faberAliceConnection.alias).toBe(makeConnectionConfig.alias) }) test('make a connection based on old connection invitation encoded in URL', async () => { From 34658b07cd509774f58f435f93582413dab59f60 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Tue, 11 Oct 2022 08:26:42 -0300 Subject: [PATCH 051/125] chore: merge branch 'main' into 0.3.0-pre (#1030) * feat: OOB public did (#930) Signed-off-by: Pavel Zarecky * feat(routing): manual mediator pickup lifecycle management (#989) Signed-off-by: Ariel Gentile * docs(demo): faber creates invitation (#995) Signed-off-by: conanoc * chore(release): v0.2.3 (#999) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix(question-answer): question answer protocol state/role check (#1001) Signed-off-by: Ariel Gentile * feat: Action Menu protocol (Aries RFC 0509) implementation (#974) Signed-off-by: Ariel Gentile * fix(ledger): remove poolConnected on pool close (#1011) Signed-off-by: Niall Shaw * fix(ledger): check taa version instad of aml version (#1013) Signed-off-by: Jakub Koci * chore: add @janrtvld to maintainers (#1016) Signed-off-by: Timo Glastra * feat(routing): add settings to control back off strategy on mediator reconnection (#1017) Signed-off-by: Sergi Garreta * fix: avoid crash when an unexpected message arrives (#1019) Signed-off-by: Pavel Zarecky * chore(release): v0.2.4 (#1024) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * style: fix some lint errors Signed-off-by: Ariel Gentile * feat: use did:key flag (#1029) Signed-off-by: Ariel Gentile * ci: set default rust version (#1036) Signed-off-by: Sai Ranjit Tummalapalli * fix(oob): allow encoding in content type header (#1037) Signed-off-by: Timo Glastra * feat: connection type (#994) Signed-off-by: KolbyRKunz * chore(module-tenants): match package versions Signed-off-by: Ariel Gentile * feat: improve sending error handling (#1045) Signed-off-by: Ariel Gentile * feat: expose findAllByQuery method in modules and services (#1044) Signed-off-by: Jim Ezesinachi * feat: possibility to set masterSecretId inside of WalletConfig (#1043) Signed-off-by: Andrii Uhryn * fix(oob): set connection alias when creating invitation (#1047) Signed-off-by: Jakub Koci * build: fix missing parameter Signed-off-by: Ariel Gentile Signed-off-by: Pavel Zarecky Signed-off-by: Ariel Gentile Signed-off-by: conanoc Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Signed-off-by: Niall Shaw Signed-off-by: Jakub Koci Signed-off-by: Timo Glastra Signed-off-by: Sergi Garreta Signed-off-by: Sai Ranjit Tummalapalli Signed-off-by: KolbyRKunz Signed-off-by: Jim Ezesinachi Signed-off-by: Andrii Uhryn Co-authored-by: Iskander508 Co-authored-by: conanoc Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Niall Shaw <100220424+niallshaw-absa@users.noreply.github.com> Co-authored-by: jakubkoci Co-authored-by: Timo Glastra Co-authored-by: Sergi Garreta Serra Co-authored-by: Sai Ranjit Tummalapalli <34263716+sairanjit@users.noreply.github.com> Co-authored-by: KolbyRKunz Co-authored-by: Jim Ezesinachi Co-authored-by: an-uhryn <55444541+an-uhryn@users.noreply.github.com> --- CHANGELOG.md | 33 + MAINTAINERS.md | 1 + demo/src/Alice.ts | 85 +- demo/src/AliceInquirer.ts | 12 +- demo/src/Faber.ts | 77 +- demo/src/FaberInquirer.ts | 14 +- demo/src/OutputClass.ts | 2 +- lerna.json | 2 +- packages/core/CHANGELOG.md | 33 + packages/core/package.json | 2 +- packages/core/src/agent/Agent.ts | 12 +- packages/core/src/agent/AgentConfig.ts | 18 + packages/core/src/agent/AgentModules.ts | 2 + packages/core/src/agent/BaseAgent.ts | 4 + packages/core/src/agent/MessageSender.ts | 86 +- .../core/src/agent/__tests__/Agent.test.ts | 3 +- .../src/agent/__tests__/AgentModules.test.ts | 4 + .../src/agent/__tests__/MessageSender.test.ts | 45 +- packages/core/src/agent/helpers.ts | 2 +- .../decorators/service/ServiceDecorator.ts | 2 +- .../core/src/error/MessageSendingError.ts | 11 + packages/core/src/error/index.ts | 1 + packages/core/src/index.ts | 3 +- .../src/modules/action-menu/ActionMenuApi.ts | 150 +++ .../action-menu/ActionMenuApiOptions.ts | 27 + .../modules/action-menu/ActionMenuEvents.ts | 14 + .../modules/action-menu/ActionMenuModule.ts | 35 + .../src/modules/action-menu/ActionMenuRole.ts | 9 + .../modules/action-menu/ActionMenuState.ts | 13 + .../__tests__/action-menu.e2e.test.ts | 334 +++++++ .../modules/action-menu/__tests__/helpers.ts | 62 ++ .../errors/ActionMenuProblemReportError.ts | 22 + .../errors/ActionMenuProblemReportReason.ts | 8 + .../ActionMenuProblemReportHandler.ts | 17 + .../handlers/MenuMessageHandler.ts | 19 + .../handlers/MenuRequestMessageHandler.ts | 19 + .../handlers/PerformMessageHandler.ts | 19 + .../src/modules/action-menu/handlers/index.ts | 4 + .../core/src/modules/action-menu/index.ts | 10 + .../ActionMenuProblemReportMessage.ts | 23 + .../action-menu/messages/MenuMessage.ts | 55 ++ .../messages/MenuRequestMessage.ts | 20 + .../action-menu/messages/PerformMessage.ts | 37 + .../src/modules/action-menu/messages/index.ts | 4 + .../modules/action-menu/models/ActionMenu.ts | 32 + .../action-menu/models/ActionMenuOption.ts | 46 + .../models/ActionMenuOptionForm.ts | 33 + .../models/ActionMenuOptionFormParameter.ts | 48 + .../action-menu/models/ActionMenuSelection.ts | 22 + .../src/modules/action-menu/models/index.ts | 5 + .../repository/ActionMenuRecord.ts | 92 ++ .../repository/ActionMenuRepository.ts | 17 + .../modules/action-menu/repository/index.ts | 2 + .../action-menu/services/ActionMenuService.ts | 370 ++++++++ .../services/ActionMenuServiceOptions.ts | 29 + .../__tests__/ActionMenuService.test.ts | 894 ++++++++++++++++++ .../src/modules/action-menu/services/index.ts | 2 + .../basic-messages/BasicMessagesApi.ts | 51 +- .../__tests__/BasicMessageService.test.ts | 2 +- .../__tests__/basic-messages.e2e.test.ts | 110 +++ .../services/BasicMessageService.ts | 15 +- .../src/modules/connections/ConnectionsApi.ts | 64 ++ .../connections/DidExchangeProtocol.ts | 13 +- .../__tests__/ConnectionService.test.ts | 14 + .../handlers/ConnectionResponseHandler.ts | 1 - .../connections/models/ConnectionType.ts | 3 + .../src/modules/connections/models/index.ts | 1 + .../repository/ConnectionMetadataTypes.ts | 9 + .../repository/ConnectionRecord.ts | 5 +- .../connections/services/ConnectionService.ts | 118 +-- .../src/modules/credentials/CredentialsApi.ts | 11 + .../__tests__/V1CredentialServiceCred.test.ts | 10 + .../__tests__/V2CredentialServiceCred.test.ts | 10 + .../credentials/services/CredentialService.ts | 8 + packages/core/src/modules/didcomm/index.ts | 2 + .../services/DidCommDocumentService.ts | 72 ++ .../__tests__/DidCommDocumentService.test.ts | 122 +++ .../src/modules/didcomm/services/index.ts | 1 + packages/core/src/modules/didcomm/types.ts | 8 + .../util/__tests__/matchingEd25519Key.test.ts | 84 ++ .../didcomm/util/matchingEd25519Key.ts | 32 + packages/core/src/modules/dids/helpers.ts | 8 +- .../peer/createPeerDidDocumentFromServices.ts | 2 +- .../generic-records/GenericRecordsApi.ts | 5 +- .../services/GenericRecordService.ts | 5 +- packages/core/src/modules/ledger/IndyPool.ts | 1 + .../__tests__/IndyLedgerService.test.ts | 12 +- .../ledger/services/IndyLedgerService.ts | 4 +- packages/core/src/modules/oob/OutOfBandApi.ts | 102 +- .../core/src/modules/oob/OutOfBandService.ts | 5 + .../oob/__tests__/OutOfBandService.test.ts | 10 + packages/core/src/modules/oob/helpers.ts | 2 +- .../oob/messages/OutOfBandInvitation.ts | 35 +- .../modules/oob/repository/OutOfBandRecord.ts | 27 +- .../__tests__/OutOfBandRecord.test.ts | 3 + packages/core/src/modules/proofs/ProofsApi.ts | 11 + .../question-answer/QuestionAnswerApi.ts | 26 +- .../__tests__/QuestionAnswerService.test.ts | 168 +++- .../question-answer/__tests__/helpers.ts | 64 ++ .../__tests__/question-answer.e2e.test.ts | 92 ++ .../repository/QuestionAnswerRecord.ts | 6 + .../services/QuestionAnswerService.ts | 70 +- .../core/src/modules/routing/RecipientApi.ts | 116 ++- .../routing/__tests__/mediation.test.ts | 66 +- .../routing/messages/KeylistUpdateMessage.ts | 5 +- .../messages/KeylistUpdateResponseMessage.ts | 5 +- .../services/MediationRecipientService.ts | 69 +- .../routing/services/MediatorService.ts | 47 +- .../MediationRecipientService.test.ts | 84 +- .../__tests__/MediatorService.test.ts | 166 +++- packages/core/src/storage/StorageService.ts | 4 +- .../__fixtures__/alice-8-connections-0.1.json | 1 + .../__tests__/__snapshots__/0.1.test.ts.snap | 44 +- .../0.1-0.2/__tests__/connection.test.ts | 2 +- .../migration/updates/0.1-0.2/connection.ts | 11 +- .../core/src/transport/TransportEventTypes.ts | 9 + .../core/src/transport/WsOutboundTransport.ts | 10 +- packages/core/src/types.ts | 8 +- .../src/utils/__tests__/shortenedUrl.test.ts | 21 +- packages/core/src/utils/parseInvitation.ts | 6 +- packages/core/src/utils/validators.ts | 8 +- packages/core/src/wallet/IndyWallet.test.ts | 30 + packages/core/src/wallet/IndyWallet.ts | 6 +- packages/core/tests/helpers.ts | 11 +- packages/core/tests/oob-mediation.test.ts | 11 +- packages/core/tests/oob.test.ts | 33 +- .../tests/v1-connectionless-proofs.test.ts | 12 +- packages/module-bbs/package.json | 4 +- packages/module-tenants/package.json | 4 +- packages/module-tenants/src/TenantsModule.ts | 2 +- packages/node/CHANGELOG.md | 8 + packages/node/package.json | 4 +- packages/react-native/CHANGELOG.md | 8 + packages/react-native/package.json | 4 +- samples/extension-module/dummy/DummyApi.ts | 10 + .../dummy/services/DummyService.ts | 11 +- tests/e2e-test.ts | 7 +- yarn.lock | 8 - 138 files changed, 4673 insertions(+), 553 deletions(-) create mode 100644 packages/core/src/error/MessageSendingError.ts create mode 100644 packages/core/src/modules/action-menu/ActionMenuApi.ts create mode 100644 packages/core/src/modules/action-menu/ActionMenuApiOptions.ts create mode 100644 packages/core/src/modules/action-menu/ActionMenuEvents.ts create mode 100644 packages/core/src/modules/action-menu/ActionMenuModule.ts create mode 100644 packages/core/src/modules/action-menu/ActionMenuRole.ts create mode 100644 packages/core/src/modules/action-menu/ActionMenuState.ts create mode 100644 packages/core/src/modules/action-menu/__tests__/action-menu.e2e.test.ts create mode 100644 packages/core/src/modules/action-menu/__tests__/helpers.ts create mode 100644 packages/core/src/modules/action-menu/errors/ActionMenuProblemReportError.ts create mode 100644 packages/core/src/modules/action-menu/errors/ActionMenuProblemReportReason.ts create mode 100644 packages/core/src/modules/action-menu/handlers/ActionMenuProblemReportHandler.ts create mode 100644 packages/core/src/modules/action-menu/handlers/MenuMessageHandler.ts create mode 100644 packages/core/src/modules/action-menu/handlers/MenuRequestMessageHandler.ts create mode 100644 packages/core/src/modules/action-menu/handlers/PerformMessageHandler.ts create mode 100644 packages/core/src/modules/action-menu/handlers/index.ts create mode 100644 packages/core/src/modules/action-menu/index.ts create mode 100644 packages/core/src/modules/action-menu/messages/ActionMenuProblemReportMessage.ts create mode 100644 packages/core/src/modules/action-menu/messages/MenuMessage.ts create mode 100644 packages/core/src/modules/action-menu/messages/MenuRequestMessage.ts create mode 100644 packages/core/src/modules/action-menu/messages/PerformMessage.ts create mode 100644 packages/core/src/modules/action-menu/messages/index.ts create mode 100644 packages/core/src/modules/action-menu/models/ActionMenu.ts create mode 100644 packages/core/src/modules/action-menu/models/ActionMenuOption.ts create mode 100644 packages/core/src/modules/action-menu/models/ActionMenuOptionForm.ts create mode 100644 packages/core/src/modules/action-menu/models/ActionMenuOptionFormParameter.ts create mode 100644 packages/core/src/modules/action-menu/models/ActionMenuSelection.ts create mode 100644 packages/core/src/modules/action-menu/models/index.ts create mode 100644 packages/core/src/modules/action-menu/repository/ActionMenuRecord.ts create mode 100644 packages/core/src/modules/action-menu/repository/ActionMenuRepository.ts create mode 100644 packages/core/src/modules/action-menu/repository/index.ts create mode 100644 packages/core/src/modules/action-menu/services/ActionMenuService.ts create mode 100644 packages/core/src/modules/action-menu/services/ActionMenuServiceOptions.ts create mode 100644 packages/core/src/modules/action-menu/services/__tests__/ActionMenuService.test.ts create mode 100644 packages/core/src/modules/action-menu/services/index.ts create mode 100644 packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts create mode 100644 packages/core/src/modules/connections/models/ConnectionType.ts create mode 100644 packages/core/src/modules/connections/repository/ConnectionMetadataTypes.ts create mode 100644 packages/core/src/modules/didcomm/index.ts create mode 100644 packages/core/src/modules/didcomm/services/DidCommDocumentService.ts create mode 100644 packages/core/src/modules/didcomm/services/__tests__/DidCommDocumentService.test.ts create mode 100644 packages/core/src/modules/didcomm/services/index.ts create mode 100644 packages/core/src/modules/didcomm/types.ts create mode 100644 packages/core/src/modules/didcomm/util/__tests__/matchingEd25519Key.test.ts create mode 100644 packages/core/src/modules/didcomm/util/matchingEd25519Key.ts create mode 100644 packages/core/src/modules/question-answer/__tests__/helpers.ts create mode 100644 packages/core/src/modules/question-answer/__tests__/question-answer.e2e.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 02b95e4682..f28623e4f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,39 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) + +### Bug Fixes + +- avoid crash when an unexpected message arrives ([#1019](https://github.com/hyperledger/aries-framework-javascript/issues/1019)) ([2cfadd9](https://github.com/hyperledger/aries-framework-javascript/commit/2cfadd9167438a9446d26b933aa64521d8be75e7)) +- **ledger:** check taa version instad of aml version ([#1013](https://github.com/hyperledger/aries-framework-javascript/issues/1013)) ([4ca56f6](https://github.com/hyperledger/aries-framework-javascript/commit/4ca56f6b677f45aa96c91b5c5ee8df210722609e)) +- **ledger:** remove poolConnected on pool close ([#1011](https://github.com/hyperledger/aries-framework-javascript/issues/1011)) ([f0ca8b6](https://github.com/hyperledger/aries-framework-javascript/commit/f0ca8b6346385fc8c4811fbd531aa25a386fcf30)) +- **question-answer:** question answer protocol state/role check ([#1001](https://github.com/hyperledger/aries-framework-javascript/issues/1001)) ([4b90e87](https://github.com/hyperledger/aries-framework-javascript/commit/4b90e876cc8377e7518e05445beb1a6b524840c4)) + +### Features + +- Action Menu protocol (Aries RFC 0509) implementation ([#974](https://github.com/hyperledger/aries-framework-javascript/issues/974)) ([60a8091](https://github.com/hyperledger/aries-framework-javascript/commit/60a8091d6431c98f764b2b94bff13ee97187b915)) +- **routing:** add settings to control back off strategy on mediator reconnection ([#1017](https://github.com/hyperledger/aries-framework-javascript/issues/1017)) ([543437c](https://github.com/hyperledger/aries-framework-javascript/commit/543437cd94d3023139b259ee04d6ad51cf653794)) + +## [0.2.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.2...v0.2.3) (2022-08-30) + +### Bug Fixes + +- export the KeyDerivationMethod ([#958](https://github.com/hyperledger/aries-framework-javascript/issues/958)) ([04ab1cc](https://github.com/hyperledger/aries-framework-javascript/commit/04ab1cca853284d144fd64d35e26e9dfe77d4a1b)) +- expose oob domain ([#990](https://github.com/hyperledger/aries-framework-javascript/issues/990)) ([dad975d](https://github.com/hyperledger/aries-framework-javascript/commit/dad975d9d9b658c6b37749ece2a91381e2a314c9)) +- **generic-records:** support custom id property ([#964](https://github.com/hyperledger/aries-framework-javascript/issues/964)) ([0f690a0](https://github.com/hyperledger/aries-framework-javascript/commit/0f690a0564a25204cacfae7cd958f660f777567e)) + +### Features + +- always initialize mediator ([#985](https://github.com/hyperledger/aries-framework-javascript/issues/985)) ([b699977](https://github.com/hyperledger/aries-framework-javascript/commit/b69997744ac9e30ffba22daac7789216d2683e36)) +- delete by record id ([#983](https://github.com/hyperledger/aries-framework-javascript/issues/983)) ([d8a30d9](https://github.com/hyperledger/aries-framework-javascript/commit/d8a30d94d336cf3417c2cd00a8110185dde6a106)) +- **ledger:** handle REQNACK response for write request ([#967](https://github.com/hyperledger/aries-framework-javascript/issues/967)) ([6468a93](https://github.com/hyperledger/aries-framework-javascript/commit/6468a9311c8458615871e1e85ba3f3b560453715)) +- OOB public did ([#930](https://github.com/hyperledger/aries-framework-javascript/issues/930)) ([c99f3c9](https://github.com/hyperledger/aries-framework-javascript/commit/c99f3c9152a79ca6a0a24fdc93e7f3bebbb9d084)) +- **proofs:** present proof as nested protocol ([#972](https://github.com/hyperledger/aries-framework-javascript/issues/972)) ([52247d9](https://github.com/hyperledger/aries-framework-javascript/commit/52247d997c5910924d3099c736dd2e20ec86a214)) +- **routing:** manual mediator pickup lifecycle management ([#989](https://github.com/hyperledger/aries-framework-javascript/issues/989)) ([69d4906](https://github.com/hyperledger/aries-framework-javascript/commit/69d4906a0ceb8a311ca6bdad5ed6d2048335109a)) +- **routing:** pickup v2 mediator role basic implementation ([#975](https://github.com/hyperledger/aries-framework-javascript/issues/975)) ([a989556](https://github.com/hyperledger/aries-framework-javascript/commit/a98955666853471d504f8a5c8c4623e18ba8c8ed)) +- **routing:** support promise in message repo ([#959](https://github.com/hyperledger/aries-framework-javascript/issues/959)) ([79c5d8d](https://github.com/hyperledger/aries-framework-javascript/commit/79c5d8d76512b641167bce46e82f34cf22bc285e)) + ## [0.2.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.1...v0.2.2) (2022-07-15) ### Bug Fixes diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 1d626e75a5..ff84db7f6e 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -10,3 +10,4 @@ | Karim Stekelenburg | [@karimStekelenburg](https://github.com/karimStekelenburg) | ssi_karim#3505 | | Timo Glastra | [@TimoGlastra](https://github.com/TimoGlastra) | TimoGlastra#2988 | | Ariel Gentile | [@genaris](https://github.com/genaris) | GenAris#4962 | +| Jan Rietveld | [@janrtvld](https://github.com/janrtvld) | janrtvld#3868 | diff --git a/demo/src/Alice.ts b/demo/src/Alice.ts index 54eae3b019..67b100f8c6 100644 --- a/demo/src/Alice.ts +++ b/demo/src/Alice.ts @@ -1,18 +1,11 @@ -import type { - ConnectionRecord, - ConnectionStateChangedEvent, - CredentialExchangeRecord, - ProofRecord, -} from '@aries-framework/core' - -import { ConnectionEventTypes } from '@aries-framework/core' +import type { ConnectionRecord, CredentialExchangeRecord, ProofRecord } from '@aries-framework/core' import { BaseAgent } from './BaseAgent' import { greenText, Output, redText } from './OutputClass' export class Alice extends BaseAgent { - public outOfBandId?: string public connected: boolean + public connectionRecordFaberId?: string public constructor(port: number, name: string) { super(port, name) @@ -26,74 +19,30 @@ export class Alice extends BaseAgent { } private async getConnectionRecord() { - if (!this.outOfBandId) { - throw Error(redText(Output.MissingConnectionRecord)) - } - - const [connection] = await this.agent.connections.findAllByOutOfBandId(this.outOfBandId) - - if (!connection) { + if (!this.connectionRecordFaberId) { throw Error(redText(Output.MissingConnectionRecord)) } - - return connection + return await this.agent.connections.getById(this.connectionRecordFaberId) } - private async printConnectionInvite() { - const outOfBand = await this.agent.oob.createInvitation() - this.outOfBandId = outOfBand.id - - console.log( - Output.ConnectionLink, - outOfBand.outOfBandInvitation.toUrl({ domain: `http://localhost:${this.port}` }), - '\n' - ) - } - - private async waitForConnection() { - if (!this.outOfBandId) { - throw new Error(redText(Output.MissingConnectionRecord)) + private async receiveConnectionRequest(invitationUrl: string) { + const { connectionRecord } = await this.agent.oob.receiveInvitationFromUrl(invitationUrl) + if (!connectionRecord) { + throw new Error(redText(Output.NoConnectionRecordFromOutOfBand)) } + return connectionRecord + } - console.log('Waiting for Faber to finish connection...') - - const getConnectionRecord = (outOfBandId: string) => - new Promise((resolve, reject) => { - // Timeout of 20 seconds - const timeoutId = setTimeout(() => reject(new Error(redText(Output.MissingConnectionRecord))), 20000) - - // Start listener - this.agent.events.on(ConnectionEventTypes.ConnectionStateChanged, (e) => { - if (e.payload.connectionRecord.outOfBandId !== outOfBandId) return - - clearTimeout(timeoutId) - resolve(e.payload.connectionRecord) - }) - - // Also retrieve the connection record by invitation if the event has already fired - void this.agent.connections.findAllByOutOfBandId(outOfBandId).then(([connectionRecord]) => { - if (connectionRecord) { - clearTimeout(timeoutId) - resolve(connectionRecord) - } - }) - }) - - const connectionRecord = await getConnectionRecord(this.outOfBandId) - - try { - await this.agent.connections.returnWhenIsConnected(connectionRecord.id) - } catch (e) { - console.log(redText(`\nTimeout of 20 seconds reached.. Returning to home screen.\n`)) - return - } - console.log(greenText(Output.ConnectionEstablished)) + private async waitForConnection(connectionRecord: ConnectionRecord) { + connectionRecord = await this.agent.connections.returnWhenIsConnected(connectionRecord.id) this.connected = true + console.log(greenText(Output.ConnectionEstablished)) + return connectionRecord.id } - public async setupConnection() { - await this.printConnectionInvite() - await this.waitForConnection() + public async acceptConnection(invitation_url: string) { + const connectionRecord = await this.receiveConnectionRequest(invitation_url) + this.connectionRecordFaberId = await this.waitForConnection(connectionRecord) } public async acceptCredentialOffer(credentialRecord: CredentialExchangeRecord) { diff --git a/demo/src/AliceInquirer.ts b/demo/src/AliceInquirer.ts index 9f82717246..457d33b528 100644 --- a/demo/src/AliceInquirer.ts +++ b/demo/src/AliceInquirer.ts @@ -17,7 +17,7 @@ export const runAlice = async () => { } enum PromptOptions { - CreateConnection = 'Create connection invitation', + ReceiveConnectionUrl = 'Receive connection invitation', SendMessage = 'Send message', Exit = 'Exit', Restart = 'Restart', @@ -42,9 +42,9 @@ export class AliceInquirer extends BaseInquirer { } private async getPromptChoice() { - if (this.alice.outOfBandId) return inquirer.prompt([this.inquireOptions(this.promptOptionsString)]) + if (this.alice.connectionRecordFaberId) return inquirer.prompt([this.inquireOptions(this.promptOptionsString)]) - const reducedOption = [PromptOptions.CreateConnection, PromptOptions.Exit, PromptOptions.Restart] + const reducedOption = [PromptOptions.ReceiveConnectionUrl, PromptOptions.Exit, PromptOptions.Restart] return inquirer.prompt([this.inquireOptions(reducedOption)]) } @@ -53,7 +53,7 @@ export class AliceInquirer extends BaseInquirer { if (this.listener.on) return switch (choice.options) { - case PromptOptions.CreateConnection: + case PromptOptions.ReceiveConnectionUrl: await this.connection() break case PromptOptions.SendMessage: @@ -88,7 +88,9 @@ export class AliceInquirer extends BaseInquirer { } public async connection() { - await this.alice.setupConnection() + const title = Title.InvitationTitle + const getUrl = await inquirer.prompt([this.inquireInput(title)]) + await this.alice.acceptConnection(getUrl.input) if (!this.alice.connected) return this.listener.credentialOfferListener(this.alice, this) diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index e94b3a922b..8d127c1a43 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -1,4 +1,4 @@ -import type { ConnectionRecord } from '@aries-framework/core' +import type { ConnectionRecord, ConnectionStateChangedEvent } from '@aries-framework/core' import type { CredDef, Schema } from 'indy-sdk' import type BottomBar from 'inquirer/lib/ui/bottom-bar' @@ -8,6 +8,7 @@ import { ProofProtocolVersion, utils, V1CredentialPreview, + ConnectionEventTypes, } from '@aries-framework/core' import { ui } from 'inquirer' @@ -15,7 +16,7 @@ import { BaseAgent } from './BaseAgent' import { Color, greenText, Output, purpleText, redText } from './OutputClass' export class Faber extends BaseAgent { - public connectionRecordAliceId?: string + public outOfBandId?: string public credentialDefinition?: CredDef public ui: BottomBar @@ -31,29 +32,73 @@ export class Faber extends BaseAgent { } private async getConnectionRecord() { - if (!this.connectionRecordAliceId) { + if (!this.outOfBandId) { throw Error(redText(Output.MissingConnectionRecord)) } - return await this.agent.connections.getById(this.connectionRecordAliceId) - } - private async receiveConnectionRequest(invitationUrl: string) { - const { connectionRecord } = await this.agent.oob.receiveInvitationFromUrl(invitationUrl) - if (!connectionRecord) { - throw new Error(redText(Output.NoConnectionRecordFromOutOfBand)) + const [connection] = await this.agent.connections.findAllByOutOfBandId(this.outOfBandId) + + if (!connection) { + throw Error(redText(Output.MissingConnectionRecord)) } - return connectionRecord + + return connection } - private async waitForConnection(connectionRecord: ConnectionRecord) { - connectionRecord = await this.agent.connections.returnWhenIsConnected(connectionRecord.id) + private async printConnectionInvite() { + const outOfBand = await this.agent.oob.createInvitation() + this.outOfBandId = outOfBand.id + + console.log( + Output.ConnectionLink, + outOfBand.outOfBandInvitation.toUrl({ domain: `http://localhost:${this.port}` }), + '\n' + ) + } + + private async waitForConnection() { + if (!this.outOfBandId) { + throw new Error(redText(Output.MissingConnectionRecord)) + } + + console.log('Waiting for Alice to finish connection...') + + const getConnectionRecord = (outOfBandId: string) => + new Promise((resolve, reject) => { + // Timeout of 20 seconds + const timeoutId = setTimeout(() => reject(new Error(redText(Output.MissingConnectionRecord))), 20000) + + // Start listener + this.agent.events.on(ConnectionEventTypes.ConnectionStateChanged, (e) => { + if (e.payload.connectionRecord.outOfBandId !== outOfBandId) return + + clearTimeout(timeoutId) + resolve(e.payload.connectionRecord) + }) + + // Also retrieve the connection record by invitation if the event has already fired + void this.agent.connections.findAllByOutOfBandId(outOfBandId).then(([connectionRecord]) => { + if (connectionRecord) { + clearTimeout(timeoutId) + resolve(connectionRecord) + } + }) + }) + + const connectionRecord = await getConnectionRecord(this.outOfBandId) + + try { + await this.agent.connections.returnWhenIsConnected(connectionRecord.id) + } catch (e) { + console.log(redText(`\nTimeout of 20 seconds reached.. Returning to home screen.\n`)) + return + } console.log(greenText(Output.ConnectionEstablished)) - return connectionRecord.id } - public async acceptConnection(invitation_url: string) { - const connectionRecord = await this.receiveConnectionRequest(invitation_url) - this.connectionRecordAliceId = await this.waitForConnection(connectionRecord) + public async setupConnection() { + await this.printConnectionInvite() + await this.waitForConnection() } private printSchema(name: string, version: string, attributes: string[]) { diff --git a/demo/src/FaberInquirer.ts b/demo/src/FaberInquirer.ts index a61ec60175..98c1ccabb6 100644 --- a/demo/src/FaberInquirer.ts +++ b/demo/src/FaberInquirer.ts @@ -15,7 +15,7 @@ export const runFaber = async () => { } enum PromptOptions { - ReceiveConnectionUrl = 'Receive connection invitation', + CreateConnection = 'Create connection invitation', OfferCredential = 'Offer credential', RequestProof = 'Request proof', SendMessage = 'Send message', @@ -42,9 +42,9 @@ export class FaberInquirer extends BaseInquirer { } private async getPromptChoice() { - if (this.faber.connectionRecordAliceId) return inquirer.prompt([this.inquireOptions(this.promptOptionsString)]) + if (this.faber.outOfBandId) return inquirer.prompt([this.inquireOptions(this.promptOptionsString)]) - const reducedOption = [PromptOptions.ReceiveConnectionUrl, PromptOptions.Exit, PromptOptions.Restart] + const reducedOption = [PromptOptions.CreateConnection, PromptOptions.Exit, PromptOptions.Restart] return inquirer.prompt([this.inquireOptions(reducedOption)]) } @@ -53,7 +53,7 @@ export class FaberInquirer extends BaseInquirer { if (this.listener.on) return switch (choice.options) { - case PromptOptions.ReceiveConnectionUrl: + case PromptOptions.CreateConnection: await this.connection() break case PromptOptions.OfferCredential: @@ -76,9 +76,7 @@ export class FaberInquirer extends BaseInquirer { } public async connection() { - const title = Title.InvitationTitle - const getUrl = await inquirer.prompt([this.inquireInput(title)]) - await this.faber.acceptConnection(getUrl.input) + await this.faber.setupConnection() } public async exitUseCase(title: string) { @@ -104,7 +102,7 @@ export class FaberInquirer extends BaseInquirer { public async message() { const message = await this.inquireMessage() - if (message) return + if (!message) return await this.faber.sendMessage(message) } diff --git a/demo/src/OutputClass.ts b/demo/src/OutputClass.ts index 3d7b9ebff3..b9e69c72f0 100644 --- a/demo/src/OutputClass.ts +++ b/demo/src/OutputClass.ts @@ -9,7 +9,7 @@ export enum Output { NoConnectionRecordFromOutOfBand = `\nNo connectionRecord has been created from invitation\n`, ConnectionEstablished = `\nConnection established!`, MissingConnectionRecord = `\nNo connectionRecord ID has been set yet\n`, - ConnectionLink = `\nRun 'Receive connection invitation' in Faber and paste this invitation link:\n\n`, + ConnectionLink = `\nRun 'Receive connection invitation' in Alice and paste this invitation link:\n\n`, Exit = 'Shutting down agent...\nExiting...', } diff --git a/lerna.json b/lerna.json index 6e0c665e15..86f806459b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.2.2", + "version": "0.2.4", "useWorkspaces": true, "npmClient": "yarn", "command": { diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index dc994f8cc0..20c7b345be 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,39 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) + +### Bug Fixes + +- avoid crash when an unexpected message arrives ([#1019](https://github.com/hyperledger/aries-framework-javascript/issues/1019)) ([2cfadd9](https://github.com/hyperledger/aries-framework-javascript/commit/2cfadd9167438a9446d26b933aa64521d8be75e7)) +- **ledger:** check taa version instad of aml version ([#1013](https://github.com/hyperledger/aries-framework-javascript/issues/1013)) ([4ca56f6](https://github.com/hyperledger/aries-framework-javascript/commit/4ca56f6b677f45aa96c91b5c5ee8df210722609e)) +- **ledger:** remove poolConnected on pool close ([#1011](https://github.com/hyperledger/aries-framework-javascript/issues/1011)) ([f0ca8b6](https://github.com/hyperledger/aries-framework-javascript/commit/f0ca8b6346385fc8c4811fbd531aa25a386fcf30)) +- **question-answer:** question answer protocol state/role check ([#1001](https://github.com/hyperledger/aries-framework-javascript/issues/1001)) ([4b90e87](https://github.com/hyperledger/aries-framework-javascript/commit/4b90e876cc8377e7518e05445beb1a6b524840c4)) + +### Features + +- Action Menu protocol (Aries RFC 0509) implementation ([#974](https://github.com/hyperledger/aries-framework-javascript/issues/974)) ([60a8091](https://github.com/hyperledger/aries-framework-javascript/commit/60a8091d6431c98f764b2b94bff13ee97187b915)) +- **routing:** add settings to control back off strategy on mediator reconnection ([#1017](https://github.com/hyperledger/aries-framework-javascript/issues/1017)) ([543437c](https://github.com/hyperledger/aries-framework-javascript/commit/543437cd94d3023139b259ee04d6ad51cf653794)) + +## [0.2.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.2...v0.2.3) (2022-08-30) + +### Bug Fixes + +- export the KeyDerivationMethod ([#958](https://github.com/hyperledger/aries-framework-javascript/issues/958)) ([04ab1cc](https://github.com/hyperledger/aries-framework-javascript/commit/04ab1cca853284d144fd64d35e26e9dfe77d4a1b)) +- expose oob domain ([#990](https://github.com/hyperledger/aries-framework-javascript/issues/990)) ([dad975d](https://github.com/hyperledger/aries-framework-javascript/commit/dad975d9d9b658c6b37749ece2a91381e2a314c9)) +- **generic-records:** support custom id property ([#964](https://github.com/hyperledger/aries-framework-javascript/issues/964)) ([0f690a0](https://github.com/hyperledger/aries-framework-javascript/commit/0f690a0564a25204cacfae7cd958f660f777567e)) + +### Features + +- always initialize mediator ([#985](https://github.com/hyperledger/aries-framework-javascript/issues/985)) ([b699977](https://github.com/hyperledger/aries-framework-javascript/commit/b69997744ac9e30ffba22daac7789216d2683e36)) +- delete by record id ([#983](https://github.com/hyperledger/aries-framework-javascript/issues/983)) ([d8a30d9](https://github.com/hyperledger/aries-framework-javascript/commit/d8a30d94d336cf3417c2cd00a8110185dde6a106)) +- **ledger:** handle REQNACK response for write request ([#967](https://github.com/hyperledger/aries-framework-javascript/issues/967)) ([6468a93](https://github.com/hyperledger/aries-framework-javascript/commit/6468a9311c8458615871e1e85ba3f3b560453715)) +- OOB public did ([#930](https://github.com/hyperledger/aries-framework-javascript/issues/930)) ([c99f3c9](https://github.com/hyperledger/aries-framework-javascript/commit/c99f3c9152a79ca6a0a24fdc93e7f3bebbb9d084)) +- **proofs:** present proof as nested protocol ([#972](https://github.com/hyperledger/aries-framework-javascript/issues/972)) ([52247d9](https://github.com/hyperledger/aries-framework-javascript/commit/52247d997c5910924d3099c736dd2e20ec86a214)) +- **routing:** manual mediator pickup lifecycle management ([#989](https://github.com/hyperledger/aries-framework-javascript/issues/989)) ([69d4906](https://github.com/hyperledger/aries-framework-javascript/commit/69d4906a0ceb8a311ca6bdad5ed6d2048335109a)) +- **routing:** pickup v2 mediator role basic implementation ([#975](https://github.com/hyperledger/aries-framework-javascript/issues/975)) ([a989556](https://github.com/hyperledger/aries-framework-javascript/commit/a98955666853471d504f8a5c8c4623e18ba8c8ed)) +- **routing:** support promise in message repo ([#959](https://github.com/hyperledger/aries-framework-javascript/issues/959)) ([79c5d8d](https://github.com/hyperledger/aries-framework-javascript/commit/79c5d8d76512b641167bce46e82f34cf22bc285e)) + ## [0.2.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.1...v0.2.2) (2022-07-15) ### Bug Fixes diff --git a/packages/core/package.json b/packages/core/package.json index 3858bbcac0..6d6add7046 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/core", "main": "build/index", "types": "build/index", - "version": "0.2.2", + "version": "0.2.4", "files": [ "build" ], diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 09c2e6f89f..1d57029d15 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -114,10 +114,14 @@ export class Agent extends .pipe( takeUntil(stop$), concatMap((e) => - this.messageReceiver.receiveMessage(e.payload.message, { - connection: e.payload.connection, - contextCorrelationId: e.payload.contextCorrelationId, - }) + this.messageReceiver + .receiveMessage(e.payload.message, { + connection: e.payload.connection, + contextCorrelationId: e.payload.contextCorrelationId, + }) + .catch((error) => { + this.logger.error('Failed to process message', { error }) + }) ) ) .subscribe() diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index be90bdf17b..10d56e61da 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -105,6 +105,24 @@ export class AgentConfig { return this.initConfig.maximumMessagePickup ?? 10 } + public get baseMediatorReconnectionIntervalMs() { + return this.initConfig.baseMediatorReconnectionIntervalMs ?? 100 + } + + public get maximumMediatorReconnectionIntervalMs() { + return this.initConfig.maximumMediatorReconnectionIntervalMs ?? Number.POSITIVE_INFINITY + } + + /** + * Encode keys in did:key format instead of 'naked' keys, as stated in Aries RFC 0360. + * + * This setting will not be taken into account if the other party has previously used naked keys + * in a given protocol (i.e. it does not support Aries RFC 0360). + */ + public get useDidKeyInProtocols() { + return this.initConfig.useDidKeyInProtocols ?? false + } + public get endpoints(): [string, ...string[]] { // if endpoints is not set, return queue endpoint // https://github.com/hyperledger/aries-rfcs/issues/405#issuecomment-582612875 diff --git a/packages/core/src/agent/AgentModules.ts b/packages/core/src/agent/AgentModules.ts index 958a90f47e..9a750a4593 100644 --- a/packages/core/src/agent/AgentModules.ts +++ b/packages/core/src/agent/AgentModules.ts @@ -2,6 +2,7 @@ import type { Module, DependencyManager } from '../plugins' import type { Constructor } from '../utils/mixins' import type { AgentConfig } from './AgentConfig' +import { ActionMenuModule } from '../modules/action-menu' import { BasicMessagesModule } from '../modules/basic-messages' import { ConnectionsModule } from '../modules/connections' import { CredentialsModule } from '../modules/credentials' @@ -118,6 +119,7 @@ function getDefaultAgentModules(agentConfig: AgentConfig) { }), basicMessages: () => new BasicMessagesModule(), questionAnswer: () => new QuestionAnswerModule(), + actionMenu: () => new ActionMenuModule(), genericRecords: () => new GenericRecordsModule(), ledger: () => new LedgerModule({ diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index 1f33036f2c..380bdecd42 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -5,6 +5,7 @@ import type { AgentApi, EmptyModuleMap, ModulesMap, WithoutDefaultModules } from import type { TransportSession } from './TransportService' import { AriesFrameworkError } from '../error' +import { ActionMenuApi } from '../modules/action-menu' import { BasicMessagesApi } from '../modules/basic-messages' import { ConnectionsApi } from '../modules/connections' import { CredentialsApi } from '../modules/credentials' @@ -48,6 +49,7 @@ export abstract class BaseAgent - keyReferenceToKey(didDocument, recipientKey) - ) - - // DidCommV1Service has keys encoded as key references - didCommServices.push({ - id: didCommService.id, - recipientKeys, - routingKeys, - serviceEndpoint: didCommService.serviceEndpoint, - }) - } - } - - return didCommServices - } - private async retrieveServicesByConnection( agentContext: AgentContext, connection: ConnectionRecord, @@ -417,14 +378,15 @@ export class MessageSender { if (connection.theirDid) { this.logger.debug(`Resolving services for connection theirDid ${connection.theirDid}.`) - didCommServices = await this.retrieveServicesFromDid(agentContext, connection.theirDid) + didCommServices = await this.didCommDocumentService.resolveServicesFromDid(agentContext, connection.theirDid) } else if (outOfBand) { - this.logger.debug(`Resolving services from out-of-band record ${outOfBand?.id}.`) + this.logger.debug(`Resolving services from out-of-band record ${outOfBand.id}.`) if (connection.isRequester) { - for (const service of outOfBand.outOfBandInvitation.services) { + for (const service of outOfBand.outOfBandInvitation.getServices()) { // Resolve dids to DIDDocs to retrieve services if (typeof service === 'string') { - didCommServices = await this.retrieveServicesFromDid(agentContext, service) + this.logger.debug(`Resolving services for did ${service}.`) + didCommServices.push(...(await this.didCommDocumentService.resolveServicesFromDid(agentContext, service))) } else { // Out of band inline service contains keys encoded as did:key references didCommServices.push({ diff --git a/packages/core/src/agent/__tests__/Agent.test.ts b/packages/core/src/agent/__tests__/Agent.test.ts index 0a091e51d9..f3878de46c 100644 --- a/packages/core/src/agent/__tests__/Agent.test.ts +++ b/packages/core/src/agent/__tests__/Agent.test.ts @@ -251,6 +251,7 @@ describe('Agent', () => { expect(protocols).toEqual( expect.arrayContaining([ + 'https://didcomm.org/action-menu/1.0', 'https://didcomm.org/basicmessage/1.0', 'https://didcomm.org/connections/1.0', 'https://didcomm.org/coordinate-mediation/1.0', @@ -268,6 +269,6 @@ describe('Agent', () => { 'https://didcomm.org/questionanswer/1.0', ]) ) - expect(protocols.length).toEqual(15) + expect(protocols.length).toEqual(16) }) }) diff --git a/packages/core/src/agent/__tests__/AgentModules.test.ts b/packages/core/src/agent/__tests__/AgentModules.test.ts index 2dc6eca2ae..f22bcff195 100644 --- a/packages/core/src/agent/__tests__/AgentModules.test.ts +++ b/packages/core/src/agent/__tests__/AgentModules.test.ts @@ -1,6 +1,7 @@ import type { Module } from '../../plugins' import { + ActionMenuModule, ConnectionsModule, CredentialsModule, ProofsModule, @@ -68,6 +69,7 @@ describe('AgentModules', () => { mediator: expect.any(MediatorModule), mediationRecipient: expect.any(RecipientModule), basicMessages: expect.any(BasicMessagesModule), + actionMenu: expect.any(ActionMenuModule), questionAnswer: expect.any(QuestionAnswerModule), genericRecords: expect.any(GenericRecordsModule), ledger: expect.any(LedgerModule), @@ -93,6 +95,7 @@ describe('AgentModules', () => { mediator: expect.any(MediatorModule), mediationRecipient: expect.any(RecipientModule), basicMessages: expect.any(BasicMessagesModule), + actionMenu: expect.any(ActionMenuModule), questionAnswer: expect.any(QuestionAnswerModule), genericRecords: expect.any(GenericRecordsModule), ledger: expect.any(LedgerModule), @@ -121,6 +124,7 @@ describe('AgentModules', () => { mediator: expect.any(MediatorModule), mediationRecipient: expect.any(RecipientModule), basicMessages: expect.any(BasicMessagesModule), + actionMenu: expect.any(ActionMenuModule), questionAnswer: expect.any(QuestionAnswerModule), genericRecords: expect.any(GenericRecordsModule), ledger: expect.any(LedgerModule), diff --git a/packages/core/src/agent/__tests__/MessageSender.test.ts b/packages/core/src/agent/__tests__/MessageSender.test.ts index 96adad3bdd..7776df1ea8 100644 --- a/packages/core/src/agent/__tests__/MessageSender.test.ts +++ b/packages/core/src/agent/__tests__/MessageSender.test.ts @@ -1,18 +1,20 @@ import type { ConnectionRecord } from '../../modules/connections' +import type { ResolvedDidCommService } from '../../modules/didcomm' import type { DidDocumentService } from '../../modules/dids' import type { MessageRepository } from '../../storage/MessageRepository' import type { OutboundTransport } from '../../transport' import type { OutboundMessage, EncryptedMessage } from '../../types' -import type { ResolvedDidCommService } from '../MessageSender' import { TestMessage } from '../../../tests/TestMessage' import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../tests/helpers' import testLogger from '../../../tests/logger' import { Key, KeyType } from '../../crypto' import { ReturnRouteTypes } from '../../decorators/transport/TransportDecorator' -import { DidDocument, VerificationMethod } from '../../modules/dids' +import { DidCommDocumentService } from '../../modules/didcomm' +import { DidResolverService, DidDocument, VerificationMethod } from '../../modules/dids' import { DidCommV1Service } from '../../modules/dids/domain/service/DidCommV1Service' -import { DidResolverService } from '../../modules/dids/services/DidResolverService' +import { verkeyToInstanceOfKey } from '../../modules/dids/helpers' +import { OutOfBandRepository } from '../../modules/oob' import { InMemoryMessageRepository } from '../../storage/InMemoryMessageRepository' import { EnvelopeService as EnvelopeServiceImpl } from '../EnvelopeService' import { MessageSender } from '../MessageSender' @@ -24,11 +26,15 @@ import { DummyTransportSession } from './stubs' jest.mock('../TransportService') jest.mock('../EnvelopeService') jest.mock('../../modules/dids/services/DidResolverService') +jest.mock('../../modules/didcomm/services/DidCommDocumentService') +jest.mock('../../modules/oob/repository/OutOfBandRepository') const logger = testLogger const TransportServiceMock = TransportService as jest.MockedClass const DidResolverServiceMock = DidResolverService as jest.Mock +const DidCommDocumentServiceMock = DidCommDocumentService as jest.Mock +const OutOfBandRepositoryMock = OutOfBandRepository as jest.Mock class DummyHttpOutboundTransport implements OutboundTransport { public start(): Promise { @@ -76,7 +82,10 @@ describe('MessageSender', () => { const envelopeServicePackMessageMock = mockFunction(enveloperService.packMessage) const didResolverService = new DidResolverServiceMock() + const didCommDocumentService = new DidCommDocumentServiceMock() + const outOfBandRepository = new OutOfBandRepositoryMock() const didResolverServiceResolveMock = mockFunction(didResolverService.resolveDidDocument) + const didResolverServiceResolveDidServicesMock = mockFunction(didCommDocumentService.resolveServicesFromDid) const inboundMessage = new TestMessage() inboundMessage.setReturnRouting(ReturnRouteTypes.all) @@ -132,7 +141,9 @@ describe('MessageSender', () => { transportService, messageRepository, logger, - didResolverService + didResolverService, + didCommDocumentService, + outOfBandRepository ) connection = getMockConnection({ id: 'test-123', @@ -149,6 +160,10 @@ describe('MessageSender', () => { service: [firstDidCommService, secondDidCommService], }) didResolverServiceResolveMock.mockResolvedValue(didDocumentInstance) + didResolverServiceResolveDidServicesMock.mockResolvedValue([ + getMockResolvedDidService(firstDidCommService), + getMockResolvedDidService(secondDidCommService), + ]) }) afterEach(() => { @@ -165,6 +180,7 @@ describe('MessageSender', () => { messageSender.registerOutboundTransport(outboundTransport) didResolverServiceResolveMock.mockResolvedValue(getMockDidDocument({ service: [] })) + didResolverServiceResolveDidServicesMock.mockResolvedValue([]) await expect(messageSender.sendMessage(agentContext, outboundMessage)).rejects.toThrow( `Message is undeliverable to connection test-123 (Test 123)` @@ -190,14 +206,14 @@ describe('MessageSender', () => { expect(sendMessageSpy).toHaveBeenCalledTimes(1) }) - test("resolves the did document using the did resolver if connection.theirDid starts with 'did:'", async () => { + test("resolves the did service using the did resolver if connection.theirDid starts with 'did:'", async () => { messageSender.registerOutboundTransport(outboundTransport) const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') await messageSender.sendMessage(agentContext, outboundMessage) - expect(didResolverServiceResolveMock).toHaveBeenCalledWith(agentContext, connection.theirDid) + expect(didResolverServiceResolveDidServicesMock).toHaveBeenCalledWith(agentContext, connection.theirDid) expect(sendMessageSpy).toHaveBeenCalledWith({ connectionId: 'test-123', payload: encryptedMessage, @@ -332,7 +348,9 @@ describe('MessageSender', () => { transportService, new InMemoryMessageRepository(agentConfig.logger), logger, - didResolverService + didResolverService, + didCommDocumentService, + outOfBandRepository ) envelopeServicePackMessageMock.mockReturnValue(Promise.resolve(encryptedMessage)) @@ -412,7 +430,9 @@ describe('MessageSender', () => { transportService, messageRepository, logger, - didResolverService + didResolverService, + didCommDocumentService, + outOfBandRepository ) connection = getMockConnection() @@ -460,3 +480,12 @@ function getMockDidDocument({ service }: { service: DidDocumentService[] }) { ], }) } + +function getMockResolvedDidService(service: DidDocumentService): ResolvedDidCommService { + return { + id: service.id, + serviceEndpoint: service.serviceEndpoint, + recipientKeys: [verkeyToInstanceOfKey('EoGusetSxDJktp493VCyh981nUnzMamTRjvBaHZAy68d')], + routingKeys: [], + } +} diff --git a/packages/core/src/agent/helpers.ts b/packages/core/src/agent/helpers.ts index 8bce437d96..fcfb906220 100644 --- a/packages/core/src/agent/helpers.ts +++ b/packages/core/src/agent/helpers.ts @@ -1,9 +1,9 @@ import type { Key } from '../crypto' import type { ConnectionRecord } from '../modules/connections' +import type { ResolvedDidCommService } from '../modules/didcomm' import type { OutOfBandRecord } from '../modules/oob/repository' import type { OutboundMessage, OutboundServiceMessage } from '../types' import type { AgentMessage } from './AgentMessage' -import type { ResolvedDidCommService } from './MessageSender' export function createOutboundMessage( connection: ConnectionRecord, diff --git a/packages/core/src/decorators/service/ServiceDecorator.ts b/packages/core/src/decorators/service/ServiceDecorator.ts index 72ee1226fe..0a105c4831 100644 --- a/packages/core/src/decorators/service/ServiceDecorator.ts +++ b/packages/core/src/decorators/service/ServiceDecorator.ts @@ -1,4 +1,4 @@ -import type { ResolvedDidCommService } from '../../agent/MessageSender' +import type { ResolvedDidCommService } from '../../modules/didcomm' import { IsArray, IsOptional, IsString } from 'class-validator' diff --git a/packages/core/src/error/MessageSendingError.ts b/packages/core/src/error/MessageSendingError.ts new file mode 100644 index 0000000000..6ebc95a23d --- /dev/null +++ b/packages/core/src/error/MessageSendingError.ts @@ -0,0 +1,11 @@ +import type { OutboundMessage } from '../types' + +import { AriesFrameworkError } from './AriesFrameworkError' + +export class MessageSendingError extends AriesFrameworkError { + public outboundMessage: OutboundMessage + public constructor(message: string, { outboundMessage, cause }: { outboundMessage: OutboundMessage; cause?: Error }) { + super(message, { cause }) + this.outboundMessage = outboundMessage + } +} diff --git a/packages/core/src/error/index.ts b/packages/core/src/error/index.ts index 5098161d50..7122734300 100644 --- a/packages/core/src/error/index.ts +++ b/packages/core/src/error/index.ts @@ -3,3 +3,4 @@ export * from './RecordNotFoundError' export * from './RecordDuplicateError' export * from './IndySdkError' export * from './ClassValidationError' +export * from './MessageSendingError' diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c3e3e4a087..f604e793bb 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -30,7 +30,7 @@ export * from './storage/BaseRecord' export { InMemoryMessageRepository } from './storage/InMemoryMessageRepository' export { Repository } from './storage/Repository' export * from './storage/RepositoryEvents' -export { StorageService } from './storage/StorageService' +export { StorageService, Query } from './storage/StorageService' export { getDirFromFilePath } from './utils/path' export { InjectionSymbols } from './constants' export * from './wallet' @@ -43,6 +43,7 @@ import { uuid } from './utils/uuid' export * from './plugins' export * from './transport' +export * from './modules/action-menu' export * from './modules/basic-messages' export * from './modules/common' export * from './modules/credentials' diff --git a/packages/core/src/modules/action-menu/ActionMenuApi.ts b/packages/core/src/modules/action-menu/ActionMenuApi.ts new file mode 100644 index 0000000000..54ff506c56 --- /dev/null +++ b/packages/core/src/modules/action-menu/ActionMenuApi.ts @@ -0,0 +1,150 @@ +import type { + ClearActiveMenuOptions, + FindActiveMenuOptions, + PerformActionOptions, + RequestMenuOptions, + SendMenuOptions, +} from './ActionMenuApiOptions' + +import { AgentContext } from '../../agent' +import { Dispatcher } from '../../agent/Dispatcher' +import { MessageSender } from '../../agent/MessageSender' +import { createOutboundMessage } from '../../agent/helpers' +import { AriesFrameworkError } from '../../error' +import { injectable } from '../../plugins' +import { ConnectionService } from '../connections/services' + +import { ActionMenuRole } from './ActionMenuRole' +import { + ActionMenuProblemReportHandler, + MenuMessageHandler, + MenuRequestMessageHandler, + PerformMessageHandler, +} from './handlers' +import { ActionMenuService } from './services' + +@injectable() +export class ActionMenuApi { + private connectionService: ConnectionService + private messageSender: MessageSender + private actionMenuService: ActionMenuService + private agentContext: AgentContext + + public constructor( + dispatcher: Dispatcher, + connectionService: ConnectionService, + messageSender: MessageSender, + actionMenuService: ActionMenuService, + agentContext: AgentContext + ) { + this.connectionService = connectionService + this.messageSender = messageSender + this.actionMenuService = actionMenuService + this.agentContext = agentContext + this.registerHandlers(dispatcher) + } + + /** + * Start Action Menu protocol as requester, asking for root menu. Any active menu will be cleared. + * + * @param options options for requesting menu + * @returns Action Menu record associated to this new request + */ + public async requestMenu(options: RequestMenuOptions) { + const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + + const { message, record } = await this.actionMenuService.createRequest(this.agentContext, { + connection, + }) + + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return record + } + + /** + * Send a new Action Menu as responder. This menu will be sent as response if there is an + * existing menu thread. + * + * @param options options for sending menu + * @returns Action Menu record associated to this action + */ + public async sendMenu(options: SendMenuOptions) { + const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + + const { message, record } = await this.actionMenuService.createMenu(this.agentContext, { + connection, + menu: options.menu, + }) + + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return record + } + + /** + * Perform action in active Action Menu, as a requester. The related + * menu will be closed. + * + * @param options options for requesting menu + * @returns Action Menu record associated to this selection + */ + public async performAction(options: PerformActionOptions) { + const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + + const actionMenuRecord = await this.actionMenuService.find(this.agentContext, { + connectionId: connection.id, + role: ActionMenuRole.Requester, + }) + if (!actionMenuRecord) { + throw new AriesFrameworkError(`No active menu found for connection id ${options.connectionId}`) + } + + const { message, record } = await this.actionMenuService.createPerform(this.agentContext, { + actionMenuRecord, + performedAction: options.performedAction, + }) + + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + + return record + } + + /** + * Find the current active menu for a given connection and the specified role. + * + * @param options options for requesting active menu + * @returns Active Action Menu record, or null if no active menu found + */ + public async findActiveMenu(options: FindActiveMenuOptions) { + return this.actionMenuService.find(this.agentContext, { + connectionId: options.connectionId, + role: options.role, + }) + } + + /** + * Clears the current active menu for a given connection and the specified role. + * + * @param options options for clearing active menu + * @returns Active Action Menu record, or null if no active menu record found + */ + public async clearActiveMenu(options: ClearActiveMenuOptions) { + const actionMenuRecord = await this.actionMenuService.find(this.agentContext, { + connectionId: options.connectionId, + role: options.role, + }) + + return actionMenuRecord ? await this.actionMenuService.clearMenu(this.agentContext, { actionMenuRecord }) : null + } + + private registerHandlers(dispatcher: Dispatcher) { + dispatcher.registerHandler(new ActionMenuProblemReportHandler(this.actionMenuService)) + dispatcher.registerHandler(new MenuMessageHandler(this.actionMenuService)) + dispatcher.registerHandler(new MenuRequestMessageHandler(this.actionMenuService)) + dispatcher.registerHandler(new PerformMessageHandler(this.actionMenuService)) + } +} diff --git a/packages/core/src/modules/action-menu/ActionMenuApiOptions.ts b/packages/core/src/modules/action-menu/ActionMenuApiOptions.ts new file mode 100644 index 0000000000..2ad9fcdd54 --- /dev/null +++ b/packages/core/src/modules/action-menu/ActionMenuApiOptions.ts @@ -0,0 +1,27 @@ +import type { ActionMenuRole } from './ActionMenuRole' +import type { ActionMenu } from './models/ActionMenu' +import type { ActionMenuSelection } from './models/ActionMenuSelection' + +export interface FindActiveMenuOptions { + connectionId: string + role: ActionMenuRole +} + +export interface ClearActiveMenuOptions { + connectionId: string + role: ActionMenuRole +} + +export interface RequestMenuOptions { + connectionId: string +} + +export interface SendMenuOptions { + connectionId: string + menu: ActionMenu +} + +export interface PerformActionOptions { + connectionId: string + performedAction: ActionMenuSelection +} diff --git a/packages/core/src/modules/action-menu/ActionMenuEvents.ts b/packages/core/src/modules/action-menu/ActionMenuEvents.ts new file mode 100644 index 0000000000..78733fafb7 --- /dev/null +++ b/packages/core/src/modules/action-menu/ActionMenuEvents.ts @@ -0,0 +1,14 @@ +import type { BaseEvent } from '../../agent/Events' +import type { ActionMenuState } from './ActionMenuState' +import type { ActionMenuRecord } from './repository' + +export enum ActionMenuEventTypes { + ActionMenuStateChanged = 'ActionMenuStateChanged', +} +export interface ActionMenuStateChangedEvent extends BaseEvent { + type: typeof ActionMenuEventTypes.ActionMenuStateChanged + payload: { + actionMenuRecord: ActionMenuRecord + previousState: ActionMenuState | null + } +} diff --git a/packages/core/src/modules/action-menu/ActionMenuModule.ts b/packages/core/src/modules/action-menu/ActionMenuModule.ts new file mode 100644 index 0000000000..330d87afd1 --- /dev/null +++ b/packages/core/src/modules/action-menu/ActionMenuModule.ts @@ -0,0 +1,35 @@ +import type { FeatureRegistry } from '../../agent/FeatureRegistry' +import type { DependencyManager, Module } from '../../plugins' + +import { Protocol } from '../../agent/models' + +import { ActionMenuApi } from './ActionMenuApi' +import { ActionMenuRole } from './ActionMenuRole' +import { ActionMenuRepository } from './repository' +import { ActionMenuService } from './services' + +export class ActionMenuModule implements Module { + public readonly api = ActionMenuApi + + /** + * Registers the dependencies of the question answer module on the dependency manager. + */ + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { + // Api + dependencyManager.registerContextScoped(ActionMenuApi) + + // Services + dependencyManager.registerSingleton(ActionMenuService) + + // Repositories + dependencyManager.registerSingleton(ActionMenuRepository) + + // Feature Registry + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/action-menu/1.0', + roles: [ActionMenuRole.Requester, ActionMenuRole.Responder], + }) + ) + } +} diff --git a/packages/core/src/modules/action-menu/ActionMenuRole.ts b/packages/core/src/modules/action-menu/ActionMenuRole.ts new file mode 100644 index 0000000000..f4ef73f56c --- /dev/null +++ b/packages/core/src/modules/action-menu/ActionMenuRole.ts @@ -0,0 +1,9 @@ +/** + * Action Menu roles based on the flow defined in RFC 0509. + * + * @see https://github.com/hyperledger/aries-rfcs/tree/main/features/0509-action-menu#roles + */ +export enum ActionMenuRole { + Requester = 'requester', + Responder = 'responder', +} diff --git a/packages/core/src/modules/action-menu/ActionMenuState.ts b/packages/core/src/modules/action-menu/ActionMenuState.ts new file mode 100644 index 0000000000..bf158c9b26 --- /dev/null +++ b/packages/core/src/modules/action-menu/ActionMenuState.ts @@ -0,0 +1,13 @@ +/** + * Action Menu states based on the flow defined in RFC 0509. + * + * @see https://github.com/hyperledger/aries-rfcs/tree/main/features/0509-action-menu#states + */ +export enum ActionMenuState { + Null = 'null', + AwaitingRootMenu = 'awaiting-root-menu', + PreparingRootMenu = 'preparing-root-menu', + PreparingSelection = 'preparing-selection', + AwaitingSelection = 'awaiting-selection', + Done = 'done', +} diff --git a/packages/core/src/modules/action-menu/__tests__/action-menu.e2e.test.ts b/packages/core/src/modules/action-menu/__tests__/action-menu.e2e.test.ts new file mode 100644 index 0000000000..7003f5cc3e --- /dev/null +++ b/packages/core/src/modules/action-menu/__tests__/action-menu.e2e.test.ts @@ -0,0 +1,334 @@ +import type { ConnectionRecord } from '../../..' +import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' + +import { Subject } from 'rxjs' + +import { Agent } from '../../..' +import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getAgentOptions, makeConnection } from '../../../../tests/helpers' +import testLogger from '../../../../tests/logger' +import { ActionMenuRole } from '../ActionMenuRole' +import { ActionMenuState } from '../ActionMenuState' +import { ActionMenu } from '../models' +import { ActionMenuRecord } from '../repository' + +import { waitForActionMenuRecord } from './helpers' + +const faberAgentOptions = getAgentOptions('Faber Action Menu', { + endpoints: ['rxjs:faber'], +}) + +const aliceAgentOptions = getAgentOptions('Alice Action Menu', { + endpoints: ['rxjs:alice'], +}) + +describe('Action Menu', () => { + let faberAgent: Agent + let aliceAgent: Agent + let faberConnection: ConnectionRecord + let aliceConnection: ConnectionRecord + + const rootMenu = new ActionMenu({ + title: 'Welcome', + description: 'This is the root menu', + options: [ + { + name: 'option-1', + description: 'Option 1 description', + title: 'Option 1', + }, + { + name: 'option-2', + description: 'Option 2 description', + title: 'Option 2', + }, + ], + }) + + const submenu1 = new ActionMenu({ + title: 'Menu 1', + description: 'This is first submenu', + options: [ + { + name: 'option-1-1', + description: '1-1 desc', + title: '1-1 title', + }, + { + name: 'option-1-2', + description: '1-1 desc', + title: '1-1 title', + }, + ], + }) + + beforeEach(async () => { + const faberMessages = new Subject() + const aliceMessages = new Subject() + const subjectMap = { + 'rxjs:faber': faberMessages, + 'rxjs:alice': aliceMessages, + } + + faberAgent = new Agent(faberAgentOptions) + faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await faberAgent.initialize() + + aliceAgent = new Agent(aliceAgentOptions) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + ;[aliceConnection, faberConnection] = await makeConnection(aliceAgent, faberAgent) + }) + + afterEach(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice requests menu to Faber and selects an option once received', async () => { + testLogger.test('Alice sends menu request to Faber') + let aliceActionMenuRecord = await aliceAgent.actionMenu.requestMenu({ connectionId: aliceConnection.id }) + + testLogger.test('Faber waits for menu request from Alice') + await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.PreparingRootMenu, + }) + + testLogger.test('Faber sends root menu to Alice') + await faberAgent.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) + + testLogger.test('Alice waits until she receives menu') + aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.PreparingSelection, + }) + + expect(aliceActionMenuRecord.menu).toEqual(rootMenu) + const faberActiveMenu = await faberAgent.actionMenu.findActiveMenu({ + connectionId: faberConnection.id, + role: ActionMenuRole.Responder, + }) + expect(faberActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(faberActiveMenu?.state).toBe(ActionMenuState.AwaitingSelection) + + testLogger.test('Alice selects menu item') + await aliceAgent.actionMenu.performAction({ + connectionId: aliceConnection.id, + performedAction: { name: 'option-1' }, + }) + + testLogger.test('Faber waits for menu selection from Alice') + await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.Done, + }) + + // As Alice has responded, menu should be closed (done state) + const aliceActiveMenu = await aliceAgent.actionMenu.findActiveMenu({ + connectionId: aliceConnection.id, + role: ActionMenuRole.Requester, + }) + expect(aliceActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(aliceActiveMenu?.state).toBe(ActionMenuState.Done) + }) + + test('Faber sends root menu and Alice selects an option', async () => { + testLogger.test('Faber sends root menu to Alice') + await faberAgent.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) + + testLogger.test('Alice waits until she receives menu') + const aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.PreparingSelection, + }) + + expect(aliceActionMenuRecord.menu).toEqual(rootMenu) + const faberActiveMenu = await faberAgent.actionMenu.findActiveMenu({ + connectionId: faberConnection.id, + role: ActionMenuRole.Responder, + }) + expect(faberActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(faberActiveMenu?.state).toBe(ActionMenuState.AwaitingSelection) + + testLogger.test('Alice selects menu item') + await aliceAgent.actionMenu.performAction({ + connectionId: aliceConnection.id, + performedAction: { name: 'option-1' }, + }) + + testLogger.test('Faber waits for menu selection from Alice') + await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.Done, + }) + + // As Alice has responded, menu should be closed (done state) + const aliceActiveMenu = await aliceAgent.actionMenu.findActiveMenu({ + connectionId: aliceConnection.id, + role: ActionMenuRole.Requester, + }) + expect(aliceActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(aliceActiveMenu?.state).toBe(ActionMenuState.Done) + }) + + test('Menu navigation', async () => { + testLogger.test('Faber sends root menu ') + let faberActionMenuRecord = await faberAgent.actionMenu.sendMenu({ + connectionId: faberConnection.id, + menu: rootMenu, + }) + + const rootThreadId = faberActionMenuRecord.threadId + + testLogger.test('Alice waits until she receives menu') + let aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.PreparingSelection, + }) + + expect(aliceActionMenuRecord.menu).toEqual(rootMenu) + expect(aliceActionMenuRecord.threadId).toEqual(rootThreadId) + + testLogger.test('Alice selects menu item 1') + await aliceAgent.actionMenu.performAction({ + connectionId: aliceConnection.id, + performedAction: { name: 'option-1' }, + }) + + testLogger.test('Faber waits for menu selection from Alice') + faberActionMenuRecord = await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.Done, + }) + + // As Alice has responded, menu should be closed (done state) + let aliceActiveMenu = await aliceAgent.actionMenu.findActiveMenu({ + connectionId: aliceConnection.id, + role: ActionMenuRole.Requester, + }) + expect(aliceActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(aliceActiveMenu?.state).toBe(ActionMenuState.Done) + expect(aliceActiveMenu?.threadId).toEqual(rootThreadId) + + testLogger.test('Faber sends submenu to Alice') + faberActionMenuRecord = await faberAgent.actionMenu.sendMenu({ + connectionId: faberConnection.id, + menu: submenu1, + }) + + testLogger.test('Alice waits until she receives submenu') + aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.PreparingSelection, + }) + + expect(aliceActionMenuRecord.menu).toEqual(submenu1) + expect(aliceActionMenuRecord.threadId).toEqual(rootThreadId) + + testLogger.test('Alice selects menu item 1-1') + await aliceAgent.actionMenu.performAction({ + connectionId: aliceConnection.id, + performedAction: { name: 'option-1-1' }, + }) + + testLogger.test('Faber waits for menu selection from Alice') + faberActionMenuRecord = await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.Done, + }) + + // As Alice has responded, menu should be closed (done state) + aliceActiveMenu = await aliceAgent.actionMenu.findActiveMenu({ + connectionId: aliceConnection.id, + role: ActionMenuRole.Requester, + }) + expect(aliceActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(aliceActiveMenu?.state).toBe(ActionMenuState.Done) + expect(aliceActiveMenu?.threadId).toEqual(rootThreadId) + + testLogger.test('Alice sends menu request to Faber') + aliceActionMenuRecord = await aliceAgent.actionMenu.requestMenu({ connectionId: aliceConnection.id }) + + testLogger.test('Faber waits for menu request from Alice') + faberActionMenuRecord = await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.PreparingRootMenu, + }) + + testLogger.test('This new menu request must have a different thread Id') + expect(faberActionMenuRecord.menu).toBeUndefined() + expect(aliceActionMenuRecord.threadId).not.toEqual(rootThreadId) + expect(faberActionMenuRecord.threadId).toEqual(aliceActionMenuRecord.threadId) + }) + + test('Menu clearing', async () => { + testLogger.test('Faber sends root menu to Alice') + await faberAgent.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) + + testLogger.test('Alice waits until she receives menu') + let aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.PreparingSelection, + }) + + expect(aliceActionMenuRecord.menu).toEqual(rootMenu) + let faberActiveMenu = await faberAgent.actionMenu.findActiveMenu({ + connectionId: faberConnection.id, + role: ActionMenuRole.Responder, + }) + expect(faberActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(faberActiveMenu?.state).toBe(ActionMenuState.AwaitingSelection) + + await faberAgent.actionMenu.clearActiveMenu({ + connectionId: faberConnection.id, + role: ActionMenuRole.Responder, + }) + + testLogger.test('Alice selects menu item') + await aliceAgent.actionMenu.performAction({ + connectionId: aliceConnection.id, + performedAction: { name: 'option-1' }, + }) + + // Exception + + testLogger.test('Faber rejects selection, as menu has been cleared') + // Faber sends error report to Alice, meaning that her Menu flow will be cleared + aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.Null, + role: ActionMenuRole.Requester, + }) + + testLogger.test('Alice request a new menu') + await aliceAgent.actionMenu.requestMenu({ + connectionId: aliceConnection.id, + }) + + testLogger.test('Faber waits for menu request from Alice') + await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.PreparingRootMenu, + }) + + testLogger.test('Faber sends root menu to Alice') + await faberAgent.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) + + testLogger.test('Alice waits until she receives menu') + aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { + state: ActionMenuState.PreparingSelection, + }) + + expect(aliceActionMenuRecord.menu).toEqual(rootMenu) + faberActiveMenu = await faberAgent.actionMenu.findActiveMenu({ + connectionId: faberConnection.id, + role: ActionMenuRole.Responder, + }) + expect(faberActiveMenu).toBeInstanceOf(ActionMenuRecord) + expect(faberActiveMenu?.state).toBe(ActionMenuState.AwaitingSelection) + + /*testLogger.test('Alice selects menu item') + await aliceAgent.actionMenu.performAction({ + connectionId: aliceConnection.id, + performedAction: { name: 'option-1' }, + }) + + testLogger.test('Faber waits for menu selection from Alice') + await waitForActionMenuRecord(faberAgent, { + state: ActionMenuState.Done, + })*/ + }) +}) diff --git a/packages/core/src/modules/action-menu/__tests__/helpers.ts b/packages/core/src/modules/action-menu/__tests__/helpers.ts new file mode 100644 index 0000000000..8d0c6c48d6 --- /dev/null +++ b/packages/core/src/modules/action-menu/__tests__/helpers.ts @@ -0,0 +1,62 @@ +import type { Agent } from '../../../agent/Agent' +import type { ActionMenuStateChangedEvent } from '../ActionMenuEvents' +import type { ActionMenuRole } from '../ActionMenuRole' +import type { ActionMenuState } from '../ActionMenuState' +import type { Observable } from 'rxjs' + +import { catchError, filter, firstValueFrom, map, ReplaySubject, timeout } from 'rxjs' + +import { ActionMenuEventTypes } from '../ActionMenuEvents' + +export async function waitForActionMenuRecord( + agent: Agent, + options: { + threadId?: string + role?: ActionMenuRole + state?: ActionMenuState + previousState?: ActionMenuState | null + timeoutMs?: number + } +) { + const observable = agent.events.observable(ActionMenuEventTypes.ActionMenuStateChanged) + + return waitForActionMenuRecordSubject(observable, options) +} + +export function waitForActionMenuRecordSubject( + subject: ReplaySubject | Observable, + { + threadId, + role, + state, + previousState, + timeoutMs = 10000, + }: { + threadId?: string + role?: ActionMenuRole + state?: ActionMenuState + previousState?: ActionMenuState | null + timeoutMs?: number + } +) { + const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject + return firstValueFrom( + observable.pipe( + filter((e) => previousState === undefined || e.payload.previousState === previousState), + filter((e) => threadId === undefined || e.payload.actionMenuRecord.threadId === threadId), + filter((e) => role === undefined || e.payload.actionMenuRecord.role === role), + filter((e) => state === undefined || e.payload.actionMenuRecord.state === state), + timeout(timeoutMs), + catchError(() => { + throw new Error( + `ActionMenuStateChangedEvent event not emitted within specified timeout: { + previousState: ${previousState}, + threadId: ${threadId}, + state: ${state} + }` + ) + }), + map((e) => e.payload.actionMenuRecord) + ) + ) +} diff --git a/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportError.ts b/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportError.ts new file mode 100644 index 0000000000..2dcd8162e7 --- /dev/null +++ b/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportError.ts @@ -0,0 +1,22 @@ +import type { ProblemReportErrorOptions } from '../../problem-reports' +import type { ActionMenuProblemReportReason } from './ActionMenuProblemReportReason' + +import { ProblemReportError } from '../../problem-reports' +import { ActionMenuProblemReportMessage } from '../messages' + +interface ActionMenuProblemReportErrorOptions extends ProblemReportErrorOptions { + problemCode: ActionMenuProblemReportReason +} +export class ActionMenuProblemReportError extends ProblemReportError { + public problemReport: ActionMenuProblemReportMessage + + public constructor(public message: string, { problemCode }: ActionMenuProblemReportErrorOptions) { + super(message, { problemCode }) + this.problemReport = new ActionMenuProblemReportMessage({ + description: { + en: message, + code: problemCode, + }, + }) + } +} diff --git a/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportReason.ts b/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportReason.ts new file mode 100644 index 0000000000..97e18b9245 --- /dev/null +++ b/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportReason.ts @@ -0,0 +1,8 @@ +/** + * Action Menu errors discussed in RFC 0509. + * + * @see https://github.com/hyperledger/aries-rfcs/tree/main/features/0509-action-menu#unresolved-questions + */ +export enum ActionMenuProblemReportReason { + Timeout = 'timeout', +} diff --git a/packages/core/src/modules/action-menu/handlers/ActionMenuProblemReportHandler.ts b/packages/core/src/modules/action-menu/handlers/ActionMenuProblemReportHandler.ts new file mode 100644 index 0000000000..023ffc5cc1 --- /dev/null +++ b/packages/core/src/modules/action-menu/handlers/ActionMenuProblemReportHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { ActionMenuService } from '../services' + +import { ActionMenuProblemReportMessage } from '../messages' + +export class ActionMenuProblemReportHandler implements Handler { + private actionMenuService: ActionMenuService + public supportedMessages = [ActionMenuProblemReportMessage] + + public constructor(actionMenuService: ActionMenuService) { + this.actionMenuService = actionMenuService + } + + public async handle(messageContext: HandlerInboundMessage) { + await this.actionMenuService.processProblemReport(messageContext) + } +} diff --git a/packages/core/src/modules/action-menu/handlers/MenuMessageHandler.ts b/packages/core/src/modules/action-menu/handlers/MenuMessageHandler.ts new file mode 100644 index 0000000000..0e81788525 --- /dev/null +++ b/packages/core/src/modules/action-menu/handlers/MenuMessageHandler.ts @@ -0,0 +1,19 @@ +import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { ActionMenuService } from '../services' + +import { MenuMessage } from '../messages' + +export class MenuMessageHandler implements Handler { + private actionMenuService: ActionMenuService + public supportedMessages = [MenuMessage] + + public constructor(actionMenuService: ActionMenuService) { + this.actionMenuService = actionMenuService + } + + public async handle(inboundMessage: HandlerInboundMessage) { + inboundMessage.assertReadyConnection() + + await this.actionMenuService.processMenu(inboundMessage) + } +} diff --git a/packages/core/src/modules/action-menu/handlers/MenuRequestMessageHandler.ts b/packages/core/src/modules/action-menu/handlers/MenuRequestMessageHandler.ts new file mode 100644 index 0000000000..33277d2510 --- /dev/null +++ b/packages/core/src/modules/action-menu/handlers/MenuRequestMessageHandler.ts @@ -0,0 +1,19 @@ +import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { ActionMenuService } from '../services' + +import { MenuRequestMessage } from '../messages' + +export class MenuRequestMessageHandler implements Handler { + private actionMenuService: ActionMenuService + public supportedMessages = [MenuRequestMessage] + + public constructor(actionMenuService: ActionMenuService) { + this.actionMenuService = actionMenuService + } + + public async handle(inboundMessage: HandlerInboundMessage) { + inboundMessage.assertReadyConnection() + + await this.actionMenuService.processRequest(inboundMessage) + } +} diff --git a/packages/core/src/modules/action-menu/handlers/PerformMessageHandler.ts b/packages/core/src/modules/action-menu/handlers/PerformMessageHandler.ts new file mode 100644 index 0000000000..65de15dcb0 --- /dev/null +++ b/packages/core/src/modules/action-menu/handlers/PerformMessageHandler.ts @@ -0,0 +1,19 @@ +import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { ActionMenuService } from '../services' + +import { PerformMessage } from '../messages' + +export class PerformMessageHandler implements Handler { + private actionMenuService: ActionMenuService + public supportedMessages = [PerformMessage] + + public constructor(actionMenuService: ActionMenuService) { + this.actionMenuService = actionMenuService + } + + public async handle(inboundMessage: HandlerInboundMessage) { + inboundMessage.assertReadyConnection() + + await this.actionMenuService.processPerform(inboundMessage) + } +} diff --git a/packages/core/src/modules/action-menu/handlers/index.ts b/packages/core/src/modules/action-menu/handlers/index.ts new file mode 100644 index 0000000000..b7ba3b7117 --- /dev/null +++ b/packages/core/src/modules/action-menu/handlers/index.ts @@ -0,0 +1,4 @@ +export * from './ActionMenuProblemReportHandler' +export * from './MenuMessageHandler' +export * from './MenuRequestMessageHandler' +export * from './PerformMessageHandler' diff --git a/packages/core/src/modules/action-menu/index.ts b/packages/core/src/modules/action-menu/index.ts new file mode 100644 index 0000000000..3183ffd412 --- /dev/null +++ b/packages/core/src/modules/action-menu/index.ts @@ -0,0 +1,10 @@ +export * from './ActionMenuApi' +export * from './ActionMenuApiOptions' +export * from './ActionMenuModule' +export * from './ActionMenuEvents' +export * from './ActionMenuRole' +export * from './ActionMenuState' +export * from './messages' +export * from './models' +export * from './repository' +export * from './services' diff --git a/packages/core/src/modules/action-menu/messages/ActionMenuProblemReportMessage.ts b/packages/core/src/modules/action-menu/messages/ActionMenuProblemReportMessage.ts new file mode 100644 index 0000000000..cfff53ca65 --- /dev/null +++ b/packages/core/src/modules/action-menu/messages/ActionMenuProblemReportMessage.ts @@ -0,0 +1,23 @@ +import type { ProblemReportMessageOptions } from '../../problem-reports/messages/ProblemReportMessage' + +import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' +import { ProblemReportMessage } from '../../problem-reports/messages/ProblemReportMessage' + +export type ActionMenuProblemReportMessageOptions = ProblemReportMessageOptions + +/** + * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md + */ +export class ActionMenuProblemReportMessage extends ProblemReportMessage { + /** + * Create new ConnectionProblemReportMessage instance. + * @param options + */ + public constructor(options: ActionMenuProblemReportMessageOptions) { + super(options) + } + + @IsValidMessageType(ActionMenuProblemReportMessage.type) + public readonly type = ActionMenuProblemReportMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/action-menu/1.0/problem-report') +} diff --git a/packages/core/src/modules/action-menu/messages/MenuMessage.ts b/packages/core/src/modules/action-menu/messages/MenuMessage.ts new file mode 100644 index 0000000000..d1c87dcebe --- /dev/null +++ b/packages/core/src/modules/action-menu/messages/MenuMessage.ts @@ -0,0 +1,55 @@ +import type { ActionMenuOptionOptions } from '../models' + +import { Expose, Type } from 'class-transformer' +import { IsInstance, IsOptional, IsString } from 'class-validator' + +import { AgentMessage } from '../../../agent/AgentMessage' +import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' +import { ActionMenuOption } from '../models' + +export interface MenuMessageOptions { + id?: string + title: string + description: string + errorMessage?: string + options: ActionMenuOptionOptions[] + threadId?: string +} + +export class MenuMessage extends AgentMessage { + public constructor(options: MenuMessageOptions) { + super() + + if (options) { + this.id = options.id ?? this.generateId() + this.title = options.title + this.description = options.description + this.errorMessage = options.errorMessage + this.options = options.options.map((p) => new ActionMenuOption(p)) + if (options.threadId) { + this.setThread({ + threadId: options.threadId, + }) + } + } + } + + @IsValidMessageType(MenuMessage.type) + public readonly type = MenuMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/action-menu/1.0/menu') + + @IsString() + public title!: string + + @IsString() + public description!: string + + @Expose({ name: 'errormsg' }) + @IsString() + @IsOptional() + public errorMessage?: string + + @IsInstance(ActionMenuOption, { each: true }) + @Type(() => ActionMenuOption) + public options!: ActionMenuOption[] +} diff --git a/packages/core/src/modules/action-menu/messages/MenuRequestMessage.ts b/packages/core/src/modules/action-menu/messages/MenuRequestMessage.ts new file mode 100644 index 0000000000..d4961553c6 --- /dev/null +++ b/packages/core/src/modules/action-menu/messages/MenuRequestMessage.ts @@ -0,0 +1,20 @@ +import { AgentMessage } from '../../../agent/AgentMessage' +import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' + +export interface MenuRequestMessageOptions { + id?: string +} + +export class MenuRequestMessage extends AgentMessage { + public constructor(options: MenuRequestMessageOptions) { + super() + + if (options) { + this.id = options.id ?? this.generateId() + } + } + + @IsValidMessageType(MenuRequestMessage.type) + public readonly type = MenuRequestMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/action-menu/1.0/menu-request') +} diff --git a/packages/core/src/modules/action-menu/messages/PerformMessage.ts b/packages/core/src/modules/action-menu/messages/PerformMessage.ts new file mode 100644 index 0000000000..75f03f02f7 --- /dev/null +++ b/packages/core/src/modules/action-menu/messages/PerformMessage.ts @@ -0,0 +1,37 @@ +import { IsOptional, IsString } from 'class-validator' + +import { AgentMessage } from '../../../agent/AgentMessage' +import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' + +export interface PerformMessageOptions { + id?: string + name: string + params?: Record + threadId: string +} + +export class PerformMessage extends AgentMessage { + public constructor(options: PerformMessageOptions) { + super() + + if (options) { + this.id = options.id ?? this.generateId() + this.name = options.name + this.params = options.params + this.setThread({ + threadId: options.threadId, + }) + } + } + + @IsValidMessageType(PerformMessage.type) + public readonly type = PerformMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/action-menu/1.0/perform') + + @IsString() + public name!: string + + @IsString({ each: true }) + @IsOptional() + public params?: Record +} diff --git a/packages/core/src/modules/action-menu/messages/index.ts b/packages/core/src/modules/action-menu/messages/index.ts new file mode 100644 index 0000000000..ecf085a0cb --- /dev/null +++ b/packages/core/src/modules/action-menu/messages/index.ts @@ -0,0 +1,4 @@ +export * from './ActionMenuProblemReportMessage' +export * from './MenuMessage' +export * from './MenuRequestMessage' +export * from './PerformMessage' diff --git a/packages/core/src/modules/action-menu/models/ActionMenu.ts b/packages/core/src/modules/action-menu/models/ActionMenu.ts new file mode 100644 index 0000000000..1123394796 --- /dev/null +++ b/packages/core/src/modules/action-menu/models/ActionMenu.ts @@ -0,0 +1,32 @@ +import type { ActionMenuOptionOptions } from './ActionMenuOption' + +import { Type } from 'class-transformer' +import { IsInstance, IsString } from 'class-validator' + +import { ActionMenuOption } from './ActionMenuOption' + +export interface ActionMenuOptions { + title: string + description: string + options: ActionMenuOptionOptions[] +} + +export class ActionMenu { + public constructor(options: ActionMenuOptions) { + if (options) { + this.title = options.title + this.description = options.description + this.options = options.options.map((p) => new ActionMenuOption(p)) + } + } + + @IsString() + public title!: string + + @IsString() + public description!: string + + @IsInstance(ActionMenuOption, { each: true }) + @Type(() => ActionMenuOption) + public options!: ActionMenuOption[] +} diff --git a/packages/core/src/modules/action-menu/models/ActionMenuOption.ts b/packages/core/src/modules/action-menu/models/ActionMenuOption.ts new file mode 100644 index 0000000000..1418c61e6c --- /dev/null +++ b/packages/core/src/modules/action-menu/models/ActionMenuOption.ts @@ -0,0 +1,46 @@ +import type { ActionMenuFormOptions } from './ActionMenuOptionForm' + +import { Type } from 'class-transformer' +import { IsBoolean, IsInstance, IsOptional, IsString } from 'class-validator' + +import { ActionMenuForm } from './ActionMenuOptionForm' + +export interface ActionMenuOptionOptions { + name: string + title: string + description: string + disabled?: boolean + form?: ActionMenuFormOptions +} + +export class ActionMenuOption { + public constructor(options: ActionMenuOptionOptions) { + if (options) { + this.name = options.name + this.title = options.title + this.description = options.description + this.disabled = options.disabled + if (options.form) { + this.form = new ActionMenuForm(options.form) + } + } + } + + @IsString() + public name!: string + + @IsString() + public title!: string + + @IsString() + public description!: string + + @IsBoolean() + @IsOptional() + public disabled?: boolean + + @IsInstance(ActionMenuForm) + @Type(() => ActionMenuForm) + @IsOptional() + public form?: ActionMenuForm +} diff --git a/packages/core/src/modules/action-menu/models/ActionMenuOptionForm.ts b/packages/core/src/modules/action-menu/models/ActionMenuOptionForm.ts new file mode 100644 index 0000000000..07a027a0a1 --- /dev/null +++ b/packages/core/src/modules/action-menu/models/ActionMenuOptionForm.ts @@ -0,0 +1,33 @@ +import type { ActionMenuFormParameterOptions } from './ActionMenuOptionFormParameter' + +import { Expose, Type } from 'class-transformer' +import { IsInstance, IsString } from 'class-validator' + +import { ActionMenuFormParameter } from './ActionMenuOptionFormParameter' + +export interface ActionMenuFormOptions { + description: string + params: ActionMenuFormParameterOptions[] + submitLabel: string +} + +export class ActionMenuForm { + public constructor(options: ActionMenuFormOptions) { + if (options) { + this.description = options.description + this.params = options.params.map((p) => new ActionMenuFormParameter(p)) + this.submitLabel = options.submitLabel + } + } + + @IsString() + public description!: string + + @Expose({ name: 'submit-label' }) + @IsString() + public submitLabel!: string + + @IsInstance(ActionMenuFormParameter, { each: true }) + @Type(() => ActionMenuFormParameter) + public params!: ActionMenuFormParameter[] +} diff --git a/packages/core/src/modules/action-menu/models/ActionMenuOptionFormParameter.ts b/packages/core/src/modules/action-menu/models/ActionMenuOptionFormParameter.ts new file mode 100644 index 0000000000..2c66ac39dc --- /dev/null +++ b/packages/core/src/modules/action-menu/models/ActionMenuOptionFormParameter.ts @@ -0,0 +1,48 @@ +import { IsBoolean, IsEnum, IsOptional, IsString } from 'class-validator' + +export enum ActionMenuFormInputType { + Text = 'text', +} + +export interface ActionMenuFormParameterOptions { + name: string + title: string + default?: string + description: string + required?: boolean + type?: ActionMenuFormInputType +} + +export class ActionMenuFormParameter { + public constructor(options: ActionMenuFormParameterOptions) { + if (options) { + this.name = options.name + this.title = options.title + this.default = options.default + this.description = options.description + this.required = options.required + this.type = options.type + } + } + + @IsString() + public name!: string + + @IsString() + public title!: string + + @IsString() + @IsOptional() + public default?: string + + @IsString() + public description!: string + + @IsBoolean() + @IsOptional() + public required?: boolean + + @IsEnum(ActionMenuFormInputType) + @IsOptional() + public type?: ActionMenuFormInputType +} diff --git a/packages/core/src/modules/action-menu/models/ActionMenuSelection.ts b/packages/core/src/modules/action-menu/models/ActionMenuSelection.ts new file mode 100644 index 0000000000..ff4299da6d --- /dev/null +++ b/packages/core/src/modules/action-menu/models/ActionMenuSelection.ts @@ -0,0 +1,22 @@ +import { IsOptional, IsString } from 'class-validator' + +export interface ActionMenuSelectionOptions { + name: string + params?: Record +} + +export class ActionMenuSelection { + public constructor(options: ActionMenuSelectionOptions) { + if (options) { + this.name = options.name + this.params = options.params + } + } + + @IsString() + public name!: string + + @IsString({ each: true }) + @IsOptional() + public params?: Record +} diff --git a/packages/core/src/modules/action-menu/models/index.ts b/packages/core/src/modules/action-menu/models/index.ts new file mode 100644 index 0000000000..15c8673f52 --- /dev/null +++ b/packages/core/src/modules/action-menu/models/index.ts @@ -0,0 +1,5 @@ +export * from './ActionMenu' +export * from './ActionMenuOption' +export * from './ActionMenuOptionForm' +export * from './ActionMenuOptionFormParameter' +export * from './ActionMenuSelection' diff --git a/packages/core/src/modules/action-menu/repository/ActionMenuRecord.ts b/packages/core/src/modules/action-menu/repository/ActionMenuRecord.ts new file mode 100644 index 0000000000..a5eb125fc0 --- /dev/null +++ b/packages/core/src/modules/action-menu/repository/ActionMenuRecord.ts @@ -0,0 +1,92 @@ +import type { TagsBase } from '../../../storage/BaseRecord' +import type { ActionMenuRole } from '../ActionMenuRole' +import type { ActionMenuState } from '../ActionMenuState' + +import { Type } from 'class-transformer' + +import { AriesFrameworkError } from '../../../error' +import { BaseRecord } from '../../../storage/BaseRecord' +import { uuid } from '../../../utils/uuid' +import { ActionMenuSelection, ActionMenu } from '../models' + +export interface ActionMenuRecordProps { + id?: string + state: ActionMenuState + role: ActionMenuRole + createdAt?: Date + connectionId: string + threadId: string + menu?: ActionMenu + performedAction?: ActionMenuSelection + tags?: CustomActionMenuTags +} + +export type CustomActionMenuTags = TagsBase + +export type DefaultActionMenuTags = { + role: ActionMenuRole + connectionId: string + threadId: string +} + +export class ActionMenuRecord + extends BaseRecord + implements ActionMenuRecordProps +{ + public state!: ActionMenuState + public role!: ActionMenuRole + public connectionId!: string + public threadId!: string + + @Type(() => ActionMenu) + public menu?: ActionMenu + + @Type(() => ActionMenuSelection) + public performedAction?: ActionMenuSelection + + public static readonly type = 'ActionMenuRecord' + public readonly type = ActionMenuRecord.type + + public constructor(props: ActionMenuRecordProps) { + super() + + if (props) { + this.id = props.id ?? uuid() + this.createdAt = props.createdAt ?? new Date() + this.connectionId = props.connectionId + this.threadId = props.threadId + this.state = props.state + this.role = props.role + this.menu = props.menu + this.performedAction = props.performedAction + this._tags = props.tags ?? {} + } + } + + public getTags() { + return { + ...this._tags, + role: this.role, + connectionId: this.connectionId, + threadId: this.threadId, + } + } + + public assertState(expectedStates: ActionMenuState | ActionMenuState[]) { + if (!Array.isArray(expectedStates)) { + expectedStates = [expectedStates] + } + + if (!expectedStates.includes(this.state)) { + throw new AriesFrameworkError( + `Action Menu record is in invalid state ${this.state}. Valid states are: ${expectedStates.join(', ')}.` + ) + } + } + + public assertRole(expectedRole: ActionMenuRole) { + if (this.role !== expectedRole) { + throw new AriesFrameworkError(`Action Menu record has invalid role ${this.role}. Expected role ${expectedRole}.`) + } + } +} diff --git a/packages/core/src/modules/action-menu/repository/ActionMenuRepository.ts b/packages/core/src/modules/action-menu/repository/ActionMenuRepository.ts new file mode 100644 index 0000000000..e22f014ec7 --- /dev/null +++ b/packages/core/src/modules/action-menu/repository/ActionMenuRepository.ts @@ -0,0 +1,17 @@ +import { EventEmitter } from '../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../constants' +import { inject, injectable } from '../../../plugins' +import { Repository } from '../../../storage/Repository' +import { StorageService } from '../../../storage/StorageService' + +import { ActionMenuRecord } from './ActionMenuRecord' + +@injectable() +export class ActionMenuRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(ActionMenuRecord, storageService, eventEmitter) + } +} diff --git a/packages/core/src/modules/action-menu/repository/index.ts b/packages/core/src/modules/action-menu/repository/index.ts new file mode 100644 index 0000000000..2c34741daf --- /dev/null +++ b/packages/core/src/modules/action-menu/repository/index.ts @@ -0,0 +1,2 @@ +export * from './ActionMenuRepository' +export * from './ActionMenuRecord' diff --git a/packages/core/src/modules/action-menu/services/ActionMenuService.ts b/packages/core/src/modules/action-menu/services/ActionMenuService.ts new file mode 100644 index 0000000000..2db0d2a1db --- /dev/null +++ b/packages/core/src/modules/action-menu/services/ActionMenuService.ts @@ -0,0 +1,370 @@ +import type { AgentContext } from '../../../agent' +import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { Logger } from '../../../logger' +import type { Query } from '../../../storage/StorageService' +import type { ActionMenuStateChangedEvent } from '../ActionMenuEvents' +import type { ActionMenuProblemReportMessage } from '../messages' +import type { + ClearMenuOptions, + CreateMenuOptions, + CreatePerformOptions, + CreateRequestOptions, + FindMenuOptions, +} from './ActionMenuServiceOptions' + +import { AgentConfig } from '../../../agent/AgentConfig' +import { EventEmitter } from '../../../agent/EventEmitter' +import { AriesFrameworkError } from '../../../error' +import { injectable } from '../../../plugins' +import { JsonTransformer } from '../../../utils' +import { ActionMenuEventTypes } from '../ActionMenuEvents' +import { ActionMenuRole } from '../ActionMenuRole' +import { ActionMenuState } from '../ActionMenuState' +import { ActionMenuProblemReportError } from '../errors/ActionMenuProblemReportError' +import { ActionMenuProblemReportReason } from '../errors/ActionMenuProblemReportReason' +import { PerformMessage, MenuMessage, MenuRequestMessage } from '../messages' +import { ActionMenuSelection, ActionMenu } from '../models' +import { ActionMenuRepository, ActionMenuRecord } from '../repository' + +@injectable() +export class ActionMenuService { + private actionMenuRepository: ActionMenuRepository + private eventEmitter: EventEmitter + private logger: Logger + + public constructor(actionMenuRepository: ActionMenuRepository, agentConfig: AgentConfig, eventEmitter: EventEmitter) { + this.actionMenuRepository = actionMenuRepository + this.eventEmitter = eventEmitter + this.logger = agentConfig.logger + } + + public async createRequest(agentContext: AgentContext, options: CreateRequestOptions) { + // Assert + options.connection.assertReady() + + // Create message + const menuRequestMessage = new MenuRequestMessage({}) + + // Create record if not existant for connection/role + let actionMenuRecord = await this.find(agentContext, { + connectionId: options.connection.id, + role: ActionMenuRole.Requester, + }) + + if (actionMenuRecord) { + // Protocol will be restarted and menu cleared + const previousState = actionMenuRecord.state + actionMenuRecord.state = ActionMenuState.AwaitingRootMenu + actionMenuRecord.threadId = menuRequestMessage.id + actionMenuRecord.menu = undefined + actionMenuRecord.performedAction = undefined + + await this.actionMenuRepository.update(agentContext, actionMenuRecord) + this.emitStateChangedEvent(agentContext, actionMenuRecord, previousState) + } else { + actionMenuRecord = new ActionMenuRecord({ + connectionId: options.connection.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.AwaitingRootMenu, + threadId: menuRequestMessage.id, + }) + + await this.actionMenuRepository.save(agentContext, actionMenuRecord) + this.emitStateChangedEvent(agentContext, actionMenuRecord, null) + } + + return { message: menuRequestMessage, record: actionMenuRecord } + } + + public async processRequest(messageContext: InboundMessageContext) { + const { message: menuRequestMessage, agentContext } = messageContext + + this.logger.debug(`Processing menu request with id ${menuRequestMessage.id}`) + + // Assert + const connection = messageContext.assertReadyConnection() + + let actionMenuRecord = await this.find(agentContext, { + connectionId: connection.id, + role: ActionMenuRole.Responder, + }) + + if (actionMenuRecord) { + // Protocol will be restarted and menu cleared + const previousState = actionMenuRecord.state + actionMenuRecord.state = ActionMenuState.PreparingRootMenu + actionMenuRecord.threadId = menuRequestMessage.id + actionMenuRecord.menu = undefined + actionMenuRecord.performedAction = undefined + + await this.actionMenuRepository.update(agentContext, actionMenuRecord) + this.emitStateChangedEvent(agentContext, actionMenuRecord, previousState) + } else { + // Create record + actionMenuRecord = new ActionMenuRecord({ + connectionId: connection.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.PreparingRootMenu, + threadId: menuRequestMessage.id, + }) + + await this.actionMenuRepository.save(agentContext, actionMenuRecord) + this.emitStateChangedEvent(agentContext, actionMenuRecord, null) + } + + return actionMenuRecord + } + + public async createMenu(agentContext: AgentContext, options: CreateMenuOptions) { + // Assert connection ready + options.connection.assertReady() + + const uniqueNames = new Set(options.menu.options.map((v) => v.name)) + if (uniqueNames.size < options.menu.options.length) { + throw new AriesFrameworkError('Action Menu contains duplicated options') + } + + // Create message + const menuMessage = new MenuMessage({ + title: options.menu.title, + description: options.menu.description, + options: options.menu.options, + }) + + // Check if there is an existing menu for this connection and role + let actionMenuRecord = await this.find(agentContext, { + connectionId: options.connection.id, + role: ActionMenuRole.Responder, + }) + + // If so, continue existing flow + if (actionMenuRecord) { + actionMenuRecord.assertState([ActionMenuState.Null, ActionMenuState.PreparingRootMenu, ActionMenuState.Done]) + // The new menu will be bound to the existing thread + // unless it is in null state (protocol reset) + if (actionMenuRecord.state !== ActionMenuState.Null) { + menuMessage.setThread({ threadId: actionMenuRecord.threadId }) + } + + const previousState = actionMenuRecord.state + actionMenuRecord.menu = options.menu + actionMenuRecord.state = ActionMenuState.AwaitingSelection + actionMenuRecord.threadId = menuMessage.threadId + + await this.actionMenuRepository.update(agentContext, actionMenuRecord) + this.emitStateChangedEvent(agentContext, actionMenuRecord, previousState) + } else { + // Create record + actionMenuRecord = new ActionMenuRecord({ + connectionId: options.connection.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.AwaitingSelection, + menu: options.menu, + threadId: menuMessage.id, + }) + + await this.actionMenuRepository.save(agentContext, actionMenuRecord) + this.emitStateChangedEvent(agentContext, actionMenuRecord, null) + } + + return { message: menuMessage, record: actionMenuRecord } + } + + public async processMenu(messageContext: InboundMessageContext) { + const { message: menuMessage, agentContext } = messageContext + + this.logger.debug(`Processing action menu with id ${menuMessage.id}`) + + // Assert + const connection = messageContext.assertReadyConnection() + + // Check if there is an existing menu for this connection and role + const record = await this.find(agentContext, { + connectionId: connection.id, + role: ActionMenuRole.Requester, + }) + + if (record) { + // Record found: update with menu details + const previousState = record.state + + record.state = ActionMenuState.PreparingSelection + record.menu = new ActionMenu({ + title: menuMessage.title, + description: menuMessage.description, + options: menuMessage.options, + }) + record.threadId = menuMessage.threadId + record.performedAction = undefined + + await this.actionMenuRepository.update(agentContext, record) + + this.emitStateChangedEvent(agentContext, record, previousState) + } else { + // Record not found: create it + const actionMenuRecord = new ActionMenuRecord({ + connectionId: connection.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: menuMessage.id, + menu: new ActionMenu({ + title: menuMessage.title, + description: menuMessage.description, + options: menuMessage.options, + }), + }) + + await this.actionMenuRepository.save(agentContext, actionMenuRecord) + + this.emitStateChangedEvent(agentContext, actionMenuRecord, null) + } + } + + public async createPerform(agentContext: AgentContext, options: CreatePerformOptions) { + const { actionMenuRecord: record, performedAction: performedSelection } = options + + // Assert + record.assertRole(ActionMenuRole.Requester) + record.assertState([ActionMenuState.PreparingSelection]) + + const validSelection = record.menu?.options.some((item) => item.name === performedSelection.name) + if (!validSelection) { + throw new AriesFrameworkError('Selection does not match valid actions') + } + + const previousState = record.state + + // Create message + const menuMessage = new PerformMessage({ + name: performedSelection.name, + params: performedSelection.params, + threadId: record.threadId, + }) + + // Update record + record.performedAction = options.performedAction + record.state = ActionMenuState.Done + + await this.actionMenuRepository.update(agentContext, record) + + this.emitStateChangedEvent(agentContext, record, previousState) + + return { message: menuMessage, record } + } + + public async processPerform(messageContext: InboundMessageContext) { + const { message: performMessage, agentContext } = messageContext + + this.logger.debug(`Processing action menu perform with id ${performMessage.id}`) + + const connection = messageContext.assertReadyConnection() + + // Check if there is an existing menu for this connection and role + const record = await this.find(agentContext, { + connectionId: connection.id, + role: ActionMenuRole.Responder, + threadId: performMessage.threadId, + }) + + if (record) { + // Record found: check state and update with menu details + + // A Null state means that menu has been cleared by the responder. + // Requester should be informed in order to request another menu + if (record.state === ActionMenuState.Null) { + throw new ActionMenuProblemReportError('Action Menu has been cleared by the responder', { + problemCode: ActionMenuProblemReportReason.Timeout, + }) + } + record.assertState([ActionMenuState.AwaitingSelection]) + + const validSelection = record.menu?.options.some((item) => item.name === performMessage.name) + if (!validSelection) { + throw new AriesFrameworkError('Selection does not match valid actions') + } + + const previousState = record.state + + record.state = ActionMenuState.Done + record.performedAction = new ActionMenuSelection({ name: performMessage.name, params: performMessage.params }) + + await this.actionMenuRepository.update(agentContext, record) + + this.emitStateChangedEvent(agentContext, record, previousState) + } else { + throw new AriesFrameworkError(`No Action Menu found with thread id ${messageContext.message.threadId}`) + } + } + + public async clearMenu(agentContext: AgentContext, options: ClearMenuOptions) { + const { actionMenuRecord: record } = options + + const previousState = record.state + + // Update record + record.state = ActionMenuState.Null + record.menu = undefined + record.performedAction = undefined + + await this.actionMenuRepository.update(agentContext, record) + + this.emitStateChangedEvent(agentContext, record, previousState) + + return record + } + + public async processProblemReport( + messageContext: InboundMessageContext + ): Promise { + const { message: actionMenuProblemReportMessage, agentContext } = messageContext + + const connection = messageContext.assertReadyConnection() + + this.logger.debug(`Processing problem report with id ${actionMenuProblemReportMessage.id}`) + + const actionMenuRecord = await this.find(agentContext, { + role: ActionMenuRole.Requester, + connectionId: connection.id, + }) + + if (!actionMenuRecord) { + throw new AriesFrameworkError( + `Unable to process action menu problem: record not found for connection id ${connection.id}` + ) + } + // Clear menu to restart flow + return await this.clearMenu(agentContext, { actionMenuRecord }) + } + + public async findById(agentContext: AgentContext, actionMenuRecordId: string) { + return await this.actionMenuRepository.findById(agentContext, actionMenuRecordId) + } + + public async find(agentContext: AgentContext, options: FindMenuOptions) { + return await this.actionMenuRepository.findSingleByQuery(agentContext, { + connectionId: options.connectionId, + role: options.role, + threadId: options.threadId, + }) + } + + public async findAllByQuery(agentContext: AgentContext, options: Query) { + return await this.actionMenuRepository.findByQuery(agentContext, options) + } + + private emitStateChangedEvent( + agentContext: AgentContext, + actionMenuRecord: ActionMenuRecord, + previousState: ActionMenuState | null + ) { + const clonedRecord = JsonTransformer.clone(actionMenuRecord) + + this.eventEmitter.emit(agentContext, { + type: ActionMenuEventTypes.ActionMenuStateChanged, + payload: { + actionMenuRecord: clonedRecord, + previousState: previousState, + }, + }) + } +} diff --git a/packages/core/src/modules/action-menu/services/ActionMenuServiceOptions.ts b/packages/core/src/modules/action-menu/services/ActionMenuServiceOptions.ts new file mode 100644 index 0000000000..733a6d0c76 --- /dev/null +++ b/packages/core/src/modules/action-menu/services/ActionMenuServiceOptions.ts @@ -0,0 +1,29 @@ +import type { ConnectionRecord } from '../../connections' +import type { ActionMenuRole } from '../ActionMenuRole' +import type { ActionMenuSelection } from '../models' +import type { ActionMenu } from '../models/ActionMenu' +import type { ActionMenuRecord } from '../repository' + +export interface CreateRequestOptions { + connection: ConnectionRecord +} + +export interface CreateMenuOptions { + connection: ConnectionRecord + menu: ActionMenu +} + +export interface CreatePerformOptions { + actionMenuRecord: ActionMenuRecord + performedAction: ActionMenuSelection +} + +export interface ClearMenuOptions { + actionMenuRecord: ActionMenuRecord +} + +export interface FindMenuOptions { + connectionId: string + role: ActionMenuRole + threadId?: string +} diff --git a/packages/core/src/modules/action-menu/services/__tests__/ActionMenuService.test.ts b/packages/core/src/modules/action-menu/services/__tests__/ActionMenuService.test.ts new file mode 100644 index 0000000000..d8ade2a3ee --- /dev/null +++ b/packages/core/src/modules/action-menu/services/__tests__/ActionMenuService.test.ts @@ -0,0 +1,894 @@ +import type { AgentContext } from '../../../../agent' +import type { AgentConfig } from '../../../../agent/AgentConfig' +import type { Repository } from '../../../../storage/Repository' +import type { ActionMenuStateChangedEvent } from '../../ActionMenuEvents' +import type { ActionMenuSelection } from '../../models' + +import { Subject } from 'rxjs' + +import { + agentDependencies, + getAgentConfig, + getAgentContext, + getMockConnection, + mockFunction, +} from '../../../../../tests/helpers' +import { EventEmitter } from '../../../../agent/EventEmitter' +import { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import { DidExchangeState } from '../../../connections' +import { ActionMenuEventTypes } from '../../ActionMenuEvents' +import { ActionMenuRole } from '../../ActionMenuRole' +import { ActionMenuState } from '../../ActionMenuState' +import { ActionMenuProblemReportError } from '../../errors/ActionMenuProblemReportError' +import { ActionMenuProblemReportReason } from '../../errors/ActionMenuProblemReportReason' +import { MenuMessage, MenuRequestMessage, PerformMessage } from '../../messages' +import { ActionMenu } from '../../models' +import { ActionMenuRecord, ActionMenuRepository } from '../../repository' +import { ActionMenuService } from '../ActionMenuService' + +jest.mock('../../repository/ActionMenuRepository') +const ActionMenuRepositoryMock = ActionMenuRepository as jest.Mock + +describe('ActionMenuService', () => { + const mockConnectionRecord = getMockConnection({ + id: 'd3849ac3-c981-455b-a1aa-a10bea6cead8', + did: 'did:sov:C2SsBf5QUQpqSAQfhu3sd2', + state: DidExchangeState.Completed, + }) + + let actionMenuRepository: Repository + let actionMenuService: ActionMenuService + let eventEmitter: EventEmitter + let agentConfig: AgentConfig + let agentContext: AgentContext + + const mockActionMenuRecord = (options: { + connectionId: string + role: ActionMenuRole + state: ActionMenuState + threadId: string + menu?: ActionMenu + performedAction?: ActionMenuSelection + }) => { + return new ActionMenuRecord({ + connectionId: options.connectionId, + role: options.role, + state: options.state, + threadId: options.threadId, + menu: options.menu, + performedAction: options.performedAction, + }) + } + + beforeAll(async () => { + agentConfig = getAgentConfig('ActionMenuServiceTest') + agentContext = getAgentContext() + }) + + beforeEach(async () => { + actionMenuRepository = new ActionMenuRepositoryMock() + eventEmitter = new EventEmitter(agentDependencies, new Subject()) + actionMenuService = new ActionMenuService(actionMenuRepository, agentConfig, eventEmitter) + }) + + describe('createMenu', () => { + let testMenu: ActionMenu + + beforeAll(() => { + testMenu = new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [{ name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }], + }) + }) + + it(`throws an error when duplicated options are specified`, async () => { + expect( + actionMenuService.createMenu(agentContext, { + connection: mockConnectionRecord, + menu: { + title: 'menu-title', + description: 'menu-description', + options: [ + { name: 'opt1', description: 'desc1', title: 'title1' }, + { name: 'opt2', description: 'desc2', title: 'title2' }, + { name: 'opt1', description: 'desc3', title: 'title3' }, + { name: 'opt4', description: 'desc4', title: 'title4' }, + ], + }, + }) + ).rejects.toThrowError('Action Menu contains duplicated options') + }) + + it(`no previous menu: emits a menu with title, description and options`, async () => { + // No previous menu + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(null)) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + await actionMenuService.createMenu(agentContext, { + connection: mockConnectionRecord, + menu: testMenu, + }) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState: null, + actionMenuRecord: expect.objectContaining({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.AwaitingSelection, + menu: expect.objectContaining({ + description: 'menu-description', + title: 'menu-title', + options: [{ name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }], + }), + }), + }, + }) + }) + + it(`existing menu: emits a menu with title, description, options and thread`, async () => { + // Previous menu is in Done state + const previousMenuDone = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.Done, + threadId: 'threadId-1', + }) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(previousMenuDone)) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + await actionMenuService.createMenu(agentContext, { + connection: mockConnectionRecord, + menu: testMenu, + }) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState: ActionMenuState.Done, + actionMenuRecord: expect.objectContaining({ + connectionId: mockConnectionRecord.id, + threadId: 'threadId-1', + role: ActionMenuRole.Responder, + state: ActionMenuState.AwaitingSelection, + menu: expect.objectContaining({ + description: 'menu-description', + title: 'menu-title', + options: [{ name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }], + }), + }), + }, + }) + }) + + it(`existing menu, cleared: emits a menu with title, description, options and new thread`, async () => { + // Previous menu is in Done state + const previousMenuClear = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.Null, + threadId: 'threadId-1', + }) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(previousMenuClear)) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + await actionMenuService.createMenu(agentContext, { + connection: mockConnectionRecord, + menu: testMenu, + }) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState: ActionMenuState.Null, + actionMenuRecord: expect.objectContaining({ + connectionId: mockConnectionRecord.id, + threadId: expect.not.stringMatching('threadId-1'), + role: ActionMenuRole.Responder, + state: ActionMenuState.AwaitingSelection, + menu: expect.objectContaining({ + description: 'menu-description', + title: 'menu-title', + options: [{ name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }], + }), + }), + }, + }) + }) + }) + + describe('createPerform', () => { + let mockRecord: ActionMenuRecord + + beforeEach(() => { + const testMenu = new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }) + + mockRecord = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: '123', + menu: testMenu, + }) + }) + + it(`throws an error when invalid selection is provided`, async () => { + expect( + actionMenuService.createPerform(agentContext, { + actionMenuRecord: mockRecord, + performedAction: { name: 'fake' }, + }) + ).rejects.toThrowError('Selection does not match valid actions') + }) + + it(`throws an error when state is not preparing-selection`, async () => { + for (const state of Object.values(ActionMenuState).filter( + (state) => state !== ActionMenuState.PreparingSelection + )) { + mockRecord.state = state + expect( + actionMenuService.createPerform(agentContext, { + actionMenuRecord: mockRecord, + performedAction: { name: 'opt1' }, + }) + ).rejects.toThrowError( + `Action Menu record is in invalid state ${state}. Valid states are: preparing-selection.` + ) + } + }) + + it(`emits a menu with a valid selection and action menu record`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + await actionMenuService.createPerform(agentContext, { + actionMenuRecord: mockRecord, + performedAction: { name: 'opt2' }, + }) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState: ActionMenuState.PreparingSelection, + actionMenuRecord: expect.objectContaining({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.Done, + performedAction: { name: 'opt2' }, + }), + }, + }) + }) + }) + + describe('createRequest', () => { + let mockRecord: ActionMenuRecord + + beforeEach(() => { + const testMenu = new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }) + + mockRecord = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: '123', + menu: testMenu, + }) + }) + + it(`no existing record: emits event and creates new request and record`, async () => { + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(null)) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + const { message, record } = await actionMenuService.createRequest(agentContext, { + connection: mockConnectionRecord, + }) + + const expectedRecord = { + id: expect.any(String), + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + threadId: message.threadId, + state: ActionMenuState.AwaitingRootMenu, + menu: undefined, + performedAction: undefined, + } + expect(record).toMatchObject(expectedRecord) + + expect(actionMenuRepository.save).toHaveBeenCalledWith(agentContext, expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.update).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState: null, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + }) + + it(`already existing record: emits event, creates new request and updates record`, async () => { + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + const previousState = mockRecord.state + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + const { message, record } = await actionMenuService.createRequest(agentContext, { + connection: mockConnectionRecord, + }) + + const expectedRecord = { + id: expect.any(String), + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + threadId: message.threadId, + state: ActionMenuState.AwaitingRootMenu, + menu: undefined, + performedAction: undefined, + } + expect(record).toMatchObject(expectedRecord) + + expect(actionMenuRepository.update).toHaveBeenCalledWith(agentContext, expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.save).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + }) + }) + + describe('clearMenu', () => { + let mockRecord: ActionMenuRecord + + beforeEach(() => { + const testMenu = new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }) + + mockRecord = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: '123', + menu: testMenu, + performedAction: { name: 'opt1' }, + }) + }) + + it(`requester role: emits a cleared menu`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockRecord.role = ActionMenuRole.Requester + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + await actionMenuService.clearMenu(agentContext, { + actionMenuRecord: mockRecord, + }) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState: ActionMenuState.PreparingSelection, + actionMenuRecord: expect.objectContaining({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.Null, + menu: undefined, + performedAction: undefined, + }), + }, + }) + }) + + it(`responder role: emits a cleared menu`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockRecord.state = ActionMenuState.AwaitingSelection + mockRecord.role = ActionMenuRole.Responder + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + await actionMenuService.clearMenu(agentContext, { + actionMenuRecord: mockRecord, + }) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState: ActionMenuState.AwaitingSelection, + actionMenuRecord: expect.objectContaining({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.Null, + menu: undefined, + performedAction: undefined, + }), + }, + }) + }) + }) + + describe('processMenu', () => { + let mockRecord: ActionMenuRecord + let mockMenuMessage: MenuMessage + + beforeEach(() => { + mockRecord = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: '123', + menu: new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }), + performedAction: { name: 'opt1' }, + }) + + mockMenuMessage = new MenuMessage({ + title: 'incoming title', + description: 'incoming description', + options: [ + { + title: 'incoming option 1 title', + description: 'incoming option 1 description', + name: 'incoming option 1 name', + }, + ], + }) + }) + + it(`emits event and creates record when no previous record`, async () => { + const messageContext = new InboundMessageContext(mockMenuMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(null)) + + await actionMenuService.processMenu(messageContext) + + const expectedRecord = { + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: messageContext.message.threadId, + menu: expect.objectContaining({ + title: 'incoming title', + description: 'incoming description', + options: [ + { + title: 'incoming option 1 title', + description: 'incoming option 1 description', + name: 'incoming option 1 name', + }, + ], + }), + performedAction: undefined, + } + + expect(actionMenuRepository.save).toHaveBeenCalledWith(agentContext, expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.update).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState: null, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + }) + + it(`emits event and updates record when existing record`, async () => { + const messageContext = new InboundMessageContext(mockMenuMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + // It should accept any previous state + for (const state of Object.values(ActionMenuState)) { + mockRecord.state = state + const previousState = state + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + await actionMenuService.processMenu(messageContext) + + const expectedRecord = { + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Requester, + state: ActionMenuState.PreparingSelection, + threadId: messageContext.message.threadId, + menu: expect.objectContaining({ + title: 'incoming title', + description: 'incoming description', + options: [ + { + title: 'incoming option 1 title', + description: 'incoming option 1 description', + name: 'incoming option 1 name', + }, + ], + }), + performedAction: undefined, + } + + expect(actionMenuRepository.update).toHaveBeenCalledWith(agentContext, expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.save).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + } + }) + }) + + describe('processPerform', () => { + let mockRecord: ActionMenuRecord + + beforeEach(() => { + mockRecord = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.AwaitingSelection, + threadId: '123', + menu: new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }), + }) + }) + + it(`emits event and saves record when valid selection and thread Id`, async () => { + const mockPerformMessage = new PerformMessage({ + name: 'opt1', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(mockPerformMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + await actionMenuService.processPerform(messageContext) + + const expectedRecord = { + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.Done, + threadId: messageContext.message.threadId, + menu: expect.objectContaining({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }), + performedAction: { name: 'opt1' }, + } + + expect(actionMenuRepository.findSingleByQuery).toHaveBeenCalledWith( + agentContext, + expect.objectContaining({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + threadId: messageContext.message.threadId, + }) + ) + expect(actionMenuRepository.update).toHaveBeenCalledWith(agentContext, expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.save).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState: ActionMenuState.AwaitingSelection, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + }) + + it(`throws error when invalid selection`, async () => { + const mockPerformMessage = new PerformMessage({ + name: 'fake', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(mockPerformMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + expect(actionMenuService.processPerform(messageContext)).rejects.toThrowError( + 'Selection does not match valid actions' + ) + + expect(actionMenuRepository.update).not.toHaveBeenCalled() + expect(actionMenuRepository.save).not.toHaveBeenCalled() + expect(eventListenerMock).not.toHaveBeenCalled() + }) + + it(`throws error when record not found`, async () => { + const mockPerformMessage = new PerformMessage({ + name: 'opt1', + threadId: '122', + }) + + const messageContext = new InboundMessageContext(mockPerformMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(null)) + + expect(actionMenuService.processPerform(messageContext)).rejects.toThrowError( + `No Action Menu found with thread id ${mockPerformMessage.threadId}` + ) + + expect(actionMenuRepository.update).not.toHaveBeenCalled() + expect(actionMenuRepository.save).not.toHaveBeenCalled() + expect(eventListenerMock).not.toHaveBeenCalled() + }) + + it(`throws error when invalid state`, async () => { + const mockPerformMessage = new PerformMessage({ + name: 'opt1', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(mockPerformMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockRecord.state = ActionMenuState.Done + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + expect(actionMenuService.processPerform(messageContext)).rejects.toThrowError( + `Action Menu record is in invalid state ${mockRecord.state}. Valid states are: ${ActionMenuState.AwaitingSelection}.` + ) + + expect(actionMenuRepository.update).not.toHaveBeenCalled() + expect(actionMenuRepository.save).not.toHaveBeenCalled() + expect(eventListenerMock).not.toHaveBeenCalled() + }) + + it(`throws problem report error when menu has been cleared`, async () => { + const mockPerformMessage = new PerformMessage({ + name: 'opt1', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(mockPerformMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockRecord.state = ActionMenuState.Null + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + expect(actionMenuService.processPerform(messageContext)).rejects.toThrowError( + new ActionMenuProblemReportError('Action Menu has been cleared by the responder', { + problemCode: ActionMenuProblemReportReason.Timeout, + }) + ) + + expect(actionMenuRepository.update).not.toHaveBeenCalled() + expect(actionMenuRepository.save).not.toHaveBeenCalled() + expect(eventListenerMock).not.toHaveBeenCalled() + }) + }) + + describe('processRequest', () => { + let mockRecord: ActionMenuRecord + let mockMenuRequestMessage: MenuRequestMessage + + beforeEach(() => { + mockRecord = mockActionMenuRecord({ + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.PreparingRootMenu, + threadId: '123', + menu: new ActionMenu({ + description: 'menu-description', + title: 'menu-title', + options: [ + { name: 'opt1', title: 'opt1-title', description: 'opt1-desc' }, + { name: 'opt2', title: 'opt2-title', description: 'opt2-desc' }, + ], + }), + performedAction: { name: 'opt1' }, + }) + + mockMenuRequestMessage = new MenuRequestMessage({}) + }) + + it(`emits event and creates record when no previous record`, async () => { + const messageContext = new InboundMessageContext(mockMenuRequestMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(null)) + + await actionMenuService.processRequest(messageContext) + + const expectedRecord = { + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.PreparingRootMenu, + threadId: messageContext.message.threadId, + menu: undefined, + performedAction: undefined, + } + + expect(actionMenuRepository.save).toHaveBeenCalledWith(agentContext, expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.update).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState: null, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + }) + + it(`emits event and updates record when existing record`, async () => { + const messageContext = new InboundMessageContext(mockMenuRequestMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + const eventListenerMock = jest.fn() + eventEmitter.on(ActionMenuEventTypes.ActionMenuStateChanged, eventListenerMock) + + // It should accept any previous state + for (const state of Object.values(ActionMenuState)) { + mockRecord.state = state + const previousState = state + mockFunction(actionMenuRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + await actionMenuService.processRequest(messageContext) + + const expectedRecord = { + connectionId: mockConnectionRecord.id, + role: ActionMenuRole.Responder, + state: ActionMenuState.PreparingRootMenu, + threadId: messageContext.message.threadId, + menu: undefined, + performedAction: undefined, + } + + expect(actionMenuRepository.update).toHaveBeenCalledWith(agentContext, expect.objectContaining(expectedRecord)) + expect(actionMenuRepository.save).not.toHaveBeenCalled() + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: ActionMenuEventTypes.ActionMenuStateChanged, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + previousState, + actionMenuRecord: expect.objectContaining(expectedRecord), + }, + }) + } + }) + }) +}) diff --git a/packages/core/src/modules/action-menu/services/index.ts b/packages/core/src/modules/action-menu/services/index.ts new file mode 100644 index 0000000000..83362466e7 --- /dev/null +++ b/packages/core/src/modules/action-menu/services/index.ts @@ -0,0 +1,2 @@ +export * from './ActionMenuService' +export * from './ActionMenuServiceOptions' diff --git a/packages/core/src/modules/basic-messages/BasicMessagesApi.ts b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts index 9de00e5e2b..fbebfc30c2 100644 --- a/packages/core/src/modules/basic-messages/BasicMessagesApi.ts +++ b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts @@ -1,4 +1,5 @@ -import type { BasicMessageTags } from './repository/BasicMessageRecord' +import type { Query } from '../../storage/StorageService' +import type { BasicMessageRecord } from './repository/BasicMessageRecord' import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' @@ -31,18 +32,62 @@ export class BasicMessagesApi { this.registerHandlers(dispatcher) } + /** + * Send a message to an active connection + * + * @param connectionId Connection Id + * @param message Message contents + * @throws {RecordNotFoundError} If connection is not found + * @throws {MessageSendingError} If message is undeliverable + * @returns the created record + */ public async sendMessage(connectionId: string, message: string) { const connection = await this.connectionService.getById(this.agentContext, connectionId) - const basicMessage = await this.basicMessageService.createMessage(this.agentContext, message, connection) + const { message: basicMessage, record: basicMessageRecord } = await this.basicMessageService.createMessage( + this.agentContext, + message, + connection + ) const outboundMessage = createOutboundMessage(connection, basicMessage) + outboundMessage.associatedRecord = basicMessageRecord + await this.messageSender.sendMessage(this.agentContext, outboundMessage) + return basicMessageRecord } - public async findAllByQuery(query: Partial) { + /** + * Retrieve all basic messages matching a given query + * + * @param query The query + * @returns array containing all matching records + */ + public async findAllByQuery(query: Query) { return this.basicMessageService.findAllByQuery(this.agentContext, query) } + /** + * Retrieve a basic message record by id + * + * @param basicMessageRecordId The basic message record id + * @throws {RecordNotFoundError} If no record is found + * @return The basic message record + * + */ + public async getById(basicMessageRecordId: string) { + return this.basicMessageService.getById(this.agentContext, basicMessageRecordId) + } + + /** + * Delete a basic message record by id + * + * @param connectionId the basic message record id + * @throws {RecordNotFoundError} If no record is found + */ + public async deleteById(basicMessageRecordId: string) { + await this.basicMessageService.deleteById(this.agentContext, basicMessageRecordId) + } + private registerHandlers(dispatcher: Dispatcher) { dispatcher.registerHandler(new BasicMessageHandler(this.basicMessageService)) } diff --git a/packages/core/src/modules/basic-messages/__tests__/BasicMessageService.test.ts b/packages/core/src/modules/basic-messages/__tests__/BasicMessageService.test.ts index ad2fbfa547..83dd0c4c01 100644 --- a/packages/core/src/modules/basic-messages/__tests__/BasicMessageService.test.ts +++ b/packages/core/src/modules/basic-messages/__tests__/BasicMessageService.test.ts @@ -30,7 +30,7 @@ describe('BasicMessageService', () => { describe('createMessage', () => { it(`creates message and record, and emits message and basic message record`, async () => { - const message = await basicMessageService.createMessage(agentContext, 'hello', mockConnectionRecord) + const { message } = await basicMessageService.createMessage(agentContext, 'hello', mockConnectionRecord) expect(message.content).toBe('hello') diff --git a/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts new file mode 100644 index 0000000000..4f3b7205f1 --- /dev/null +++ b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts @@ -0,0 +1,110 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' +import type { ConnectionRecord } from '../../../modules/connections' + +import { Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getAgentOptions, makeConnection, waitForBasicMessage } from '../../../../tests/helpers' +import testLogger from '../../../../tests/logger' +import { Agent } from '../../../agent/Agent' +import { MessageSendingError, RecordNotFoundError } from '../../../error' +import { BasicMessage } from '../messages' +import { BasicMessageRecord } from '../repository' + +const faberConfig = getAgentOptions('Faber Basic Messages', { + endpoints: ['rxjs:faber'], +}) + +const aliceConfig = getAgentOptions('Alice Basic Messages', { + endpoints: ['rxjs:alice'], +}) + +describe('Basic Messages E2E', () => { + let faberAgent: Agent + let aliceAgent: Agent + let faberConnection: ConnectionRecord + let aliceConnection: ConnectionRecord + + beforeEach(async () => { + const faberMessages = new Subject() + const aliceMessages = new Subject() + const subjectMap = { + 'rxjs:faber': faberMessages, + 'rxjs:alice': aliceMessages, + } + + faberAgent = new Agent(faberConfig) + faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await faberAgent.initialize() + + aliceAgent = new Agent(aliceConfig) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + ;[aliceConnection, faberConnection] = await makeConnection(aliceAgent, faberAgent) + }) + + afterEach(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice and Faber exchange messages', async () => { + testLogger.test('Alice sends message to Faber') + const helloRecord = await aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello') + + expect(helloRecord.content).toBe('Hello') + + testLogger.test('Faber waits for message from Alice') + await waitForBasicMessage(faberAgent, { + content: 'Hello', + }) + + testLogger.test('Faber sends message to Alice') + const replyRecord = await faberAgent.basicMessages.sendMessage(faberConnection.id, 'How are you?') + expect(replyRecord.content).toBe('How are you?') + + testLogger.test('Alice waits until she receives message from faber') + await waitForBasicMessage(aliceAgent, { + content: 'How are you?', + }) + }) + + test('Alice is unable to send a message', async () => { + testLogger.test('Alice sends message to Faber that is undeliverable') + + const spy = jest.spyOn(aliceAgent.outboundTransports[0], 'sendMessage').mockRejectedValue(new Error('any error')) + + await expect(aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello')).rejects.toThrowError( + MessageSendingError + ) + try { + await aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello undeliverable') + } catch (error) { + const thrownError = error as MessageSendingError + expect(thrownError.message).toEqual( + `Message is undeliverable to connection ${aliceConnection.id} (${aliceConnection.theirLabel})` + ) + testLogger.test('Error thrown includes the outbound message and recently created record id') + expect(thrownError.outboundMessage.associatedRecord).toBeInstanceOf(BasicMessageRecord) + expect(thrownError.outboundMessage.payload).toBeInstanceOf(BasicMessage) + expect((thrownError.outboundMessage.payload as BasicMessage).content).toBe('Hello undeliverable') + + testLogger.test('Created record can be found and deleted by id') + const storedRecord = await aliceAgent.basicMessages.getById(thrownError.outboundMessage.associatedRecord!.id) + expect(storedRecord).toBeInstanceOf(BasicMessageRecord) + expect(storedRecord.content).toBe('Hello undeliverable') + + await aliceAgent.basicMessages.deleteById(storedRecord.id) + await expect( + aliceAgent.basicMessages.getById(thrownError.outboundMessage.associatedRecord!.id) + ).rejects.toThrowError(RecordNotFoundError) + } + spy.mockClear() + }) +}) diff --git a/packages/core/src/modules/basic-messages/services/BasicMessageService.ts b/packages/core/src/modules/basic-messages/services/BasicMessageService.ts index dff23b0f7e..eb79ae3f32 100644 --- a/packages/core/src/modules/basic-messages/services/BasicMessageService.ts +++ b/packages/core/src/modules/basic-messages/services/BasicMessageService.ts @@ -1,8 +1,8 @@ import type { AgentContext } from '../../../agent' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { Query } from '../../../storage/StorageService' import type { ConnectionRecord } from '../../connections/repository/ConnectionRecord' import type { BasicMessageStateChangedEvent } from '../BasicMessageEvents' -import type { BasicMessageTags } from '../repository' import { EventEmitter } from '../../../agent/EventEmitter' import { injectable } from '../../../plugins' @@ -35,7 +35,7 @@ export class BasicMessageService { await this.basicMessageRepository.save(agentContext, basicMessageRecord) this.emitStateChangedEvent(agentContext, basicMessageRecord, basicMessage) - return basicMessage + return { message: basicMessage, record: basicMessageRecord } } /** @@ -65,7 +65,16 @@ export class BasicMessageService { }) } - public async findAllByQuery(agentContext: AgentContext, query: Partial) { + public async findAllByQuery(agentContext: AgentContext, query: Query) { return this.basicMessageRepository.findByQuery(agentContext, query) } + + public async getById(agentContext: AgentContext, basicMessageRecordId: string) { + return this.basicMessageRepository.getById(agentContext, basicMessageRecordId) + } + + public async deleteById(agentContext: AgentContext, basicMessageRecordId: string) { + const basicMessageRecord = await this.getById(agentContext, basicMessageRecordId) + return this.basicMessageRepository.delete(agentContext, basicMessageRecord) + } } diff --git a/packages/core/src/modules/connections/ConnectionsApi.ts b/packages/core/src/modules/connections/ConnectionsApi.ts index cc4154eb08..1b4207be31 100644 --- a/packages/core/src/modules/connections/ConnectionsApi.ts +++ b/packages/core/src/modules/connections/ConnectionsApi.ts @@ -1,4 +1,6 @@ +import type { Query } from '../../storage/StorageService' import type { OutOfBandRecord } from '../oob/repository' +import type { ConnectionType } from './models' import type { ConnectionRecord } from './repository/ConnectionRecord' import type { Routing } from './services' @@ -216,6 +218,68 @@ export class ConnectionsApi { return this.connectionService.getAll(this.agentContext) } + /** + * Retrieve all connections records by specified query params + * + * @returns List containing all connection records matching specified query paramaters + */ + public findAllByQuery(query: Query) { + return this.connectionService.findAllByQuery(this.agentContext, query) + } + + /** + * Allows for the addition of connectionType to the record. + * Either updates or creates an array of string conection types + * @param connectionId + * @param type + * @throws {RecordNotFoundError} If no record is found + */ + public async addConnectionType(connectionId: string, type: ConnectionType | string) { + const record = await this.getById(connectionId) + + const tags = (record.getTag('connectionType') as string[]) || ([] as string[]) + record.setTag('connectionType', [type, ...tags]) + await this.connectionService.update(this.agentContext, record) + } + /** + * Removes the given tag from the given record found by connectionId, if the tag exists otherwise does nothing + * @param connectionId + * @param type + * @throws {RecordNotFoundError} If no record is found + */ + public async removeConnectionType(connectionId: string, type: ConnectionType | string) { + const record = await this.getById(connectionId) + + const tags = (record.getTag('connectionType') as string[]) || ([] as string[]) + + const newTags = tags.filter((value: string) => { + if (value != type) return value + }) + record.setTag('connectionType', [...newTags]) + + await this.connectionService.update(this.agentContext, record) + } + /** + * Gets the known connection types for the record matching the given connectionId + * @param connectionId + * @returns An array of known connection types or null if none exist + * @throws {RecordNotFoundError} If no record is found + */ + public async getConnectionTypes(connectionId: string) { + const record = await this.getById(connectionId) + const tags = record.getTag('connectionType') as string[] + return tags || null + } + + /** + * + * @param connectionTypes An array of connection types to query for a match for + * @returns a promise of ab array of connection records + */ + public async findAllByConnectionType(connectionTypes: [ConnectionType | string]) { + return this.connectionService.findAllByConnectionType(this.agentContext, connectionTypes) + } + /** * Retrieve a connection record by id * diff --git a/packages/core/src/modules/connections/DidExchangeProtocol.ts b/packages/core/src/modules/connections/DidExchangeProtocol.ts index 6b308adced..9e5fa64b62 100644 --- a/packages/core/src/modules/connections/DidExchangeProtocol.ts +++ b/packages/core/src/modules/connections/DidExchangeProtocol.ts @@ -1,9 +1,8 @@ import type { AgentContext } from '../../agent' -import type { ResolvedDidCommService } from '../../agent/MessageSender' import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' import type { ParsedMessageType } from '../../utils/messageType' +import type { ResolvedDidCommService } from '../didcomm' import type { PeerDidCreateOptions } from '../dids' -import type { OutOfBandDidCommService } from '../oob/domain/OutOfBandDidCommService' import type { OutOfBandRecord } from '../oob/repository' import type { ConnectionRecord } from './repository' import type { Routing } from './services/ConnectionService' @@ -201,6 +200,7 @@ export class DidExchangeProtocol { protocol: HandshakeProtocol.DidExchange, role: DidExchangeRole.Responder, state: DidExchangeState.RequestReceived, + alias: outOfBandRecord.alias, theirDid: message.did, theirLabel: message.label, threadId: message.threadId, @@ -233,10 +233,7 @@ export class DidExchangeProtocol { if (routing) { services = this.routingToServices(routing) } else if (outOfBandRecord) { - const inlineServices = outOfBandRecord.outOfBandInvitation.services.filter( - (service) => typeof service !== 'string' - ) as OutOfBandDidCommService[] - + const inlineServices = outOfBandRecord.outOfBandInvitation.getInlineServices() services = inlineServices.map((service) => ({ id: service.id, serviceEndpoint: service.serviceEndpoint, @@ -314,7 +311,9 @@ export class DidExchangeProtocol { const didDocument = await this.extractDidDocument( messageContext.agentContext, message, - outOfBandRecord.outOfBandInvitation.getRecipientKeys().map((key) => key.publicKeyBase58) + outOfBandRecord + .getTags() + .recipientKeyFingerprints.map((fingerprint) => Key.fromFingerprint(fingerprint).publicKeyBase58) ) const didRecord = new DidRecord({ id: message.did, diff --git a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index e4520b6528..9299352fe6 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -953,5 +953,19 @@ describe('ConnectionService', () => { expect(result).toEqual(expect.arrayContaining(expected)) }) + + it('findAllByQuery should return value from connectionRepository.findByQuery', async () => { + const expected = [getMockConnection(), getMockConnection()] + + mockFunction(connectionRepository.findByQuery).mockReturnValue(Promise.resolve(expected)) + const result = await connectionService.findAllByQuery(agentContext, { + state: DidExchangeState.InvitationReceived, + }) + expect(connectionRepository.findByQuery).toBeCalledWith(agentContext, { + state: DidExchangeState.InvitationReceived, + }) + + expect(result).toEqual(expect.arrayContaining(expected)) + }) }) }) diff --git a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts index 4b53e220d9..9e28621fb0 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts @@ -70,7 +70,6 @@ export class ConnectionResponseHandler implements Handler { } messageContext.connection = connectionRecord - // The presence of outOfBandRecord is not mandatory when the old connection invitation is used const connection = await this.connectionService.processResponse(messageContext, outOfBandRecord) // TODO: should we only send ping message in case of autoAcceptConnection or always? diff --git a/packages/core/src/modules/connections/models/ConnectionType.ts b/packages/core/src/modules/connections/models/ConnectionType.ts new file mode 100644 index 0000000000..85e6a5dbf9 --- /dev/null +++ b/packages/core/src/modules/connections/models/ConnectionType.ts @@ -0,0 +1,3 @@ +export enum ConnectionType { + Mediator = 'mediator', +} diff --git a/packages/core/src/modules/connections/models/index.ts b/packages/core/src/modules/connections/models/index.ts index 0c8dd1b360..69752df9c7 100644 --- a/packages/core/src/modules/connections/models/index.ts +++ b/packages/core/src/modules/connections/models/index.ts @@ -5,3 +5,4 @@ export * from './DidExchangeState' export * from './DidExchangeRole' export * from './HandshakeProtocol' export * from './did' +export * from './ConnectionType' diff --git a/packages/core/src/modules/connections/repository/ConnectionMetadataTypes.ts b/packages/core/src/modules/connections/repository/ConnectionMetadataTypes.ts new file mode 100644 index 0000000000..9609097515 --- /dev/null +++ b/packages/core/src/modules/connections/repository/ConnectionMetadataTypes.ts @@ -0,0 +1,9 @@ +export enum ConnectionMetadataKeys { + UseDidKeysForProtocol = '_internal/useDidKeysForProtocol', +} + +export type ConnectionMetadata = { + [ConnectionMetadataKeys.UseDidKeysForProtocol]: { + [protocolUri: string]: boolean + } +} diff --git a/packages/core/src/modules/connections/repository/ConnectionRecord.ts b/packages/core/src/modules/connections/repository/ConnectionRecord.ts index dca03cd576..db9512e5fc 100644 --- a/packages/core/src/modules/connections/repository/ConnectionRecord.ts +++ b/packages/core/src/modules/connections/repository/ConnectionRecord.ts @@ -1,5 +1,7 @@ import type { TagsBase } from '../../../storage/BaseRecord' import type { HandshakeProtocol } from '../models' +import type { ConnectionType } from '../models/ConnectionType' +import type { ConnectionMetadata } from './ConnectionMetadataTypes' import { AriesFrameworkError } from '../../../error' import { BaseRecord } from '../../../storage/BaseRecord' @@ -36,10 +38,11 @@ export type DefaultConnectionTags = { theirDid?: string outOfBandId?: string invitationDid?: string + connectionType?: [ConnectionType | string] } export class ConnectionRecord - extends BaseRecord + extends BaseRecord implements ConnectionRecordProps { public state!: DidExchangeState diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index aa2960dbe0..ab3f5e6121 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -1,12 +1,13 @@ import type { AgentContext } from '../../../agent' import type { AgentMessage } from '../../../agent/AgentMessage' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { Query } from '../../../storage/StorageService' import type { AckMessage } from '../../common' -import type { PeerDidCreateOptions } from '../../dids/methods/peer/PeerDidRegistrar' import type { OutOfBandDidCommService } from '../../oob/domain/OutOfBandDidCommService' import type { OutOfBandRecord } from '../../oob/repository' import type { ConnectionStateChangedEvent } from '../ConnectionEvents' import type { ConnectionProblemReportMessage } from '../messages' +import type { ConnectionType } from '../models' import type { ConnectionRecordProps } from '../repository/ConnectionRecord' import { firstValueFrom, ReplaySubject } from 'rxjs' @@ -25,7 +26,6 @@ import { indyDidFromPublicKeyBase58 } from '../../../utils/did' import { DidKey, DidRegistrarService, IndyAgentService } from '../../dids' import { DidDocumentRole } from '../../dids/domain/DidDocumentRole' import { didKeyToVerkey } from '../../dids/helpers' -import { PeerDidNumAlgo } from '../../dids/methods/peer/didPeer' import { didDocumentJsonToNumAlgo1Did } from '../../dids/methods/peer/peerDidNumAlgo1' import { DidRecord, DidRepository } from '../../dids/repository' import { DidRecordMetadataKeys } from '../../dids/repository/didRecordMetadataTypes' @@ -106,7 +106,23 @@ export class ConnectionService { // We take just the first one for now. const [invitationDid] = outOfBandInvitation.invitationDids - const didDocument = await this.registerCreatedPeerDidDocument(agentContext, didDoc) + const { did: peerDid } = await this.createDid(agentContext, { + role: DidDocumentRole.Created, + didDoc, + }) + + const { label, imageUrl } = config + const connectionRequest = new ConnectionRequestMessage({ + label: label ?? agentContext.config.label, + did: didDoc.id, + didDoc, + imageUrl: imageUrl ?? agentContext.config.connectionImageUrl, + }) + + connectionRequest.setThread({ + threadId: connectionRequest.id, + parentThreadId: outOfBandInvitation.id, + }) const connectionRecord = await this.createConnection(agentContext, { protocol: HandshakeProtocol.Connections, @@ -114,28 +130,15 @@ export class ConnectionService { state: DidExchangeState.InvitationReceived, theirLabel: outOfBandInvitation.label, alias: config?.alias, - did: didDocument.id, + did: peerDid, mediatorId, autoAcceptConnection: config?.autoAcceptConnection, outOfBandId: outOfBandRecord.id, invitationDid, imageUrl: outOfBandInvitation.imageUrl, + threadId: connectionRequest.id, }) - const { label, imageUrl, autoAcceptConnection } = config - - const connectionRequest = new ConnectionRequestMessage({ - label: label ?? agentContext.config.label, - did: didDoc.id, - didDoc, - imageUrl: imageUrl ?? agentContext.config.connectionImageUrl, - }) - - if (autoAcceptConnection !== undefined || autoAcceptConnection !== null) { - connectionRecord.autoAcceptConnection = config?.autoAcceptConnection - } - - connectionRecord.threadId = connectionRequest.id await this.updateState(agentContext, connectionRecord, DidExchangeState.RequestSent) return { @@ -163,16 +166,20 @@ export class ConnectionService { }) } - const didDocument = await this.storeReceivedPeerDidDocument(messageContext.agentContext, message.connection.didDoc) + const { did: peerDid } = await this.createDid(messageContext.agentContext, { + role: DidDocumentRole.Received, + didDoc: message.connection.didDoc, + }) const connectionRecord = await this.createConnection(messageContext.agentContext, { protocol: HandshakeProtocol.Connections, role: DidExchangeRole.Responder, state: DidExchangeState.RequestReceived, + alias: outOfBandRecord.alias, theirLabel: message.label, imageUrl: message.imageUrl, outOfBandId: outOfBandRecord.id, - theirDid: didDocument.id, + theirDid: peerDid, threadId: message.threadId, mediatorId: outOfBandRecord.mediatorId, autoAcceptConnection: outOfBandRecord.autoAcceptConnection, @@ -203,13 +210,12 @@ export class ConnectionService { const didDoc = routing ? this.createDidDoc(routing) - : this.createDidDocFromOutOfBandDidCommServices( - outOfBandRecord.outOfBandInvitation.services.filter( - (s): s is OutOfBandDidCommService => typeof s !== 'string' - ) - ) + : this.createDidDocFromOutOfBandDidCommServices(outOfBandRecord.outOfBandInvitation.getInlineServices()) - const didDocument = await this.registerCreatedPeerDidDocument(agentContext, didDoc) + const { did: peerDid } = await this.createDid(agentContext, { + role: DidDocumentRole.Created, + didDoc, + }) const connection = new Connection({ did: didDoc.id, @@ -229,7 +235,7 @@ export class ConnectionService { connectionSig: await signData(connectionJson, agentContext.wallet, signingKey), }) - connectionRecord.did = didDocument.id + connectionRecord.did = peerDid await this.updateState(agentContext, connectionRecord, DidExchangeState.ResponseSent) this.logger.debug(`Create message ${ConnectionResponseMessage.type.messageTypeUri} end`, { @@ -305,9 +311,12 @@ export class ConnectionService { throw new AriesFrameworkError('DID Document is missing.') } - const didDocument = await this.storeReceivedPeerDidDocument(messageContext.agentContext, connection.didDoc) + const { did: peerDid } = await this.createDid(messageContext.agentContext, { + role: DidDocumentRole.Received, + didDoc: connection.didDoc, + }) - connectionRecord.theirDid = didDocument.id + connectionRecord.theirDid = peerDid connectionRecord.threadId = message.threadId await this.updateState(messageContext.agentContext, connectionRecord, DidExchangeState.ResponseReceived) @@ -592,6 +601,10 @@ export class ConnectionService { return this.connectionRepository.findByQuery(agentContext, { outOfBandId }) } + public async findAllByConnectionType(agentContext: AgentContext, connectionType: [ConnectionType | string]) { + return this.connectionRepository.findByQuery(agentContext, { connectionType }) + } + public async findByInvitationDid(agentContext: AgentContext, invitationDid: string) { return this.connectionRepository.findByQuery(agentContext, { invitationDid }) } @@ -619,58 +632,25 @@ export class ConnectionService { return null } + public async findAllByQuery(agentContext: AgentContext, query: Query): Promise { + return this.connectionRepository.findByQuery(agentContext, query) + } + public async createConnection(agentContext: AgentContext, options: ConnectionRecordProps): Promise { const connectionRecord = new ConnectionRecord(options) await this.connectionRepository.save(agentContext, connectionRecord) return connectionRecord } - private async registerCreatedPeerDidDocument(agentContext: AgentContext, didDoc: DidDoc) { + private async createDid(agentContext: AgentContext, { role, didDoc }: { role: DidDocumentRole; didDoc: DidDoc }) { // Convert the legacy did doc to a new did document const didDocument = convertToNewDidDocument(didDoc) - // Register did:peer document. This will generate the id property and save it to a did record - const result = await this.didRegistrarService.create(agentContext, { - method: 'peer', - didDocument, - options: { - numAlgo: PeerDidNumAlgo.GenesisDoc, - }, - }) - - if (result.didState?.state !== 'finished') { - throw new AriesFrameworkError(`Did document creation failed: ${JSON.stringify(result.didState)}`) - } - - this.logger.debug(`Did document with did ${result.didState.did} created.`, { - did: result.didState.did, - didDocument: result.didState.didDocument, - }) - - const didRecord = await this.didRepository.getById(agentContext, result.didState.did) - - // Store the unqualified did with the legacy did document in the metadata - // Can be removed at a later stage if we know for sure we don't need it anymore - didRecord.metadata.set(DidRecordMetadataKeys.LegacyDid, { - unqualifiedDid: didDoc.id, - didDocumentString: JsonTransformer.serialize(didDoc), - }) - - await this.didRepository.update(agentContext, didRecord) - return result.didState.didDocument - } - - private async storeReceivedPeerDidDocument(agentContext: AgentContext, didDoc: DidDoc) { - // Convert the legacy did doc to a new did document - const didDocument = convertToNewDidDocument(didDoc) - - // TODO: Move this into the didcomm module, and add a method called store received did document. - // This can be called from both the did exchange and the connection protocol. const peerDid = didDocumentJsonToNumAlgo1Did(didDocument.toJSON()) didDocument.id = peerDid const didRecord = new DidRecord({ id: peerDid, - role: DidDocumentRole.Received, + role, didDocument, tags: { // We need to save the recipientKeys, so we can find the associated did @@ -695,7 +675,7 @@ export class ConnectionService { await this.didRepository.save(agentContext, didRecord) this.logger.debug('Did record created.', didRecord) - return didDocument + return { did: peerDid, didDocument } } private createDidDoc(routing: Routing) { diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index 658f723ba8..e6f48e1b53 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -1,4 +1,5 @@ import type { AgentMessage } from '../../agent/AgentMessage' +import type { Query } from '../../storage/StorageService' import type { DeleteCredentialOptions } from './CredentialServiceOptions' import type { AcceptCredentialOptions, @@ -73,6 +74,7 @@ export interface CredentialsApi + findAllByQuery(query: Query): Promise getById(credentialRecordId: string): Promise findById(credentialRecordId: string): Promise deleteById(credentialRecordId: string, options?: DeleteCredentialOptions): Promise @@ -580,6 +582,15 @@ export class CredentialsApi< return this.credentialRepository.getAll(this.agentContext) } + /** + * Retrieve all credential records by specified query params + * + * @returns List containing all credential records matching specified query paramaters + */ + public findAllByQuery(query: Query) { + return this.credentialRepository.findByQuery(this.agentContext, query) + } + /** * Find a credential record by id * diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts index 3e68856131..fa84e9d439 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts @@ -822,6 +822,16 @@ describe('V1CredentialService', () => { expect(result).toEqual(expect.arrayContaining(expected)) }) + + it('findAllByQuery should return value from credentialRepository.findByQuery', async () => { + const expected = [mockCredentialRecord(), mockCredentialRecord()] + + mockFunction(credentialRepository.findByQuery).mockReturnValue(Promise.resolve(expected)) + const result = await credentialService.findAllByQuery(agentContext, { state: CredentialState.OfferSent }) + expect(credentialRepository.findByQuery).toBeCalledWith(agentContext, { state: CredentialState.OfferSent }) + + expect(result).toEqual(expect.arrayContaining(expected)) + }) }) describe('deleteCredential', () => { diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts index 9383160f56..6b4f491304 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts @@ -781,6 +781,16 @@ describe('CredentialService', () => { expect(result).toEqual(expect.arrayContaining(expected)) }) + + it('findAllByQuery should return value from credentialRepository.findByQuery', async () => { + const expected = [mockCredentialRecord(), mockCredentialRecord()] + + mockFunction(credentialRepository.findByQuery).mockReturnValue(Promise.resolve(expected)) + const result = await credentialService.findAllByQuery(agentContext, { state: CredentialState.OfferSent }) + expect(credentialRepository.findByQuery).toBeCalledWith(agentContext, { state: CredentialState.OfferSent }) + + expect(result).toEqual(expect.arrayContaining(expected)) + }) }) describe('deleteCredential', () => { diff --git a/packages/core/src/modules/credentials/services/CredentialService.ts b/packages/core/src/modules/credentials/services/CredentialService.ts index 7642e4c4c5..356b93d2c4 100644 --- a/packages/core/src/modules/credentials/services/CredentialService.ts +++ b/packages/core/src/modules/credentials/services/CredentialService.ts @@ -5,6 +5,7 @@ import type { EventEmitter } from '../../../agent/EventEmitter' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { Logger } from '../../../logger' import type { DidCommMessageRepository } from '../../../storage' +import type { Query } from '../../../storage/StorageService' import type { ProblemReportMessage } from '../../problem-reports' import type { CredentialStateChangedEvent } from '../CredentialEvents' import type { @@ -220,6 +221,13 @@ export abstract class CredentialService + ): Promise { + return this.credentialRepository.findByQuery(agentContext, query) + } + /** * Find a credential record by id * diff --git a/packages/core/src/modules/didcomm/index.ts b/packages/core/src/modules/didcomm/index.ts new file mode 100644 index 0000000000..ff4d44346c --- /dev/null +++ b/packages/core/src/modules/didcomm/index.ts @@ -0,0 +1,2 @@ +export * from './types' +export * from './services' diff --git a/packages/core/src/modules/didcomm/services/DidCommDocumentService.ts b/packages/core/src/modules/didcomm/services/DidCommDocumentService.ts new file mode 100644 index 0000000000..4877113fa0 --- /dev/null +++ b/packages/core/src/modules/didcomm/services/DidCommDocumentService.ts @@ -0,0 +1,72 @@ +import type { AgentContext } from '../../../agent' +import type { Logger } from '../../../logger' +import type { ResolvedDidCommService } from '../types' + +import { AgentConfig } from '../../../agent/AgentConfig' +import { KeyType } from '../../../crypto' +import { injectable } from '../../../plugins' +import { DidResolverService } from '../../dids' +import { DidCommV1Service, IndyAgentService, keyReferenceToKey } from '../../dids/domain' +import { verkeyToInstanceOfKey } from '../../dids/helpers' +import { findMatchingEd25519Key } from '../util/matchingEd25519Key' + +@injectable() +export class DidCommDocumentService { + private logger: Logger + private didResolverService: DidResolverService + + public constructor(agentConfig: AgentConfig, didResolverService: DidResolverService) { + this.logger = agentConfig.logger + this.didResolverService = didResolverService + } + + public async resolveServicesFromDid(agentContext: AgentContext, did: string): Promise { + const didDocument = await this.didResolverService.resolveDidDocument(agentContext, did) + + const didCommServices: ResolvedDidCommService[] = [] + + // FIXME: we currently retrieve did documents for all didcomm services in the did document, and we don't have caching + // yet so this will re-trigger ledger resolves for each one. Should we only resolve the first service, then the second service, etc...? + for (const didCommService of didDocument.didCommServices) { + if (didCommService instanceof IndyAgentService) { + // IndyAgentService (DidComm v0) has keys encoded as raw publicKeyBase58 (verkeys) + didCommServices.push({ + id: didCommService.id, + recipientKeys: didCommService.recipientKeys.map(verkeyToInstanceOfKey), + routingKeys: didCommService.routingKeys?.map(verkeyToInstanceOfKey) || [], + serviceEndpoint: didCommService.serviceEndpoint, + }) + } else if (didCommService instanceof DidCommV1Service) { + // Resolve dids to DIDDocs to retrieve routingKeys + const routingKeys = [] + for (const routingKey of didCommService.routingKeys ?? []) { + const routingDidDocument = await this.didResolverService.resolveDidDocument(agentContext, routingKey) + routingKeys.push(keyReferenceToKey(routingDidDocument, routingKey)) + } + + // DidCommV1Service has keys encoded as key references + + // Dereference recipientKeys + const recipientKeys = didCommService.recipientKeys.map((recipientKeyReference) => { + const key = keyReferenceToKey(didDocument, recipientKeyReference) + + // try to find a matching Ed25519 key (https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html#did-document-notes) + if (key.keyType === KeyType.X25519) { + const matchingEd25519Key = findMatchingEd25519Key(key, didDocument) + if (matchingEd25519Key) return matchingEd25519Key + } + return key + }) + + didCommServices.push({ + id: didCommService.id, + recipientKeys, + routingKeys, + serviceEndpoint: didCommService.serviceEndpoint, + }) + } + } + + return didCommServices + } +} diff --git a/packages/core/src/modules/didcomm/services/__tests__/DidCommDocumentService.test.ts b/packages/core/src/modules/didcomm/services/__tests__/DidCommDocumentService.test.ts new file mode 100644 index 0000000000..ad57ed6372 --- /dev/null +++ b/packages/core/src/modules/didcomm/services/__tests__/DidCommDocumentService.test.ts @@ -0,0 +1,122 @@ +import type { AgentContext } from '../../../../agent' +import type { VerificationMethod } from '../../../dids' + +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' +import { Key, KeyType } from '../../../../crypto' +import { DidCommV1Service, DidDocument, IndyAgentService } from '../../../dids' +import { verkeyToInstanceOfKey } from '../../../dids/helpers' +import { DidResolverService } from '../../../dids/services/DidResolverService' +import { DidCommDocumentService } from '../DidCommDocumentService' + +jest.mock('../../../dids/services/DidResolverService') +const DidResolverServiceMock = DidResolverService as jest.Mock + +describe('DidCommDocumentService', () => { + const agentConfig = getAgentConfig('DidCommDocumentService') + let didCommDocumentService: DidCommDocumentService + let didResolverService: DidResolverService + let agentContext: AgentContext + + beforeEach(async () => { + didResolverService = new DidResolverServiceMock() + didCommDocumentService = new DidCommDocumentService(agentConfig, didResolverService) + agentContext = getAgentContext() + }) + + describe('resolveServicesFromDid', () => { + test('throw error when resolveDidDocument fails', async () => { + const error = new Error('test') + mockFunction(didResolverService.resolveDidDocument).mockRejectedValue(error) + + await expect(didCommDocumentService.resolveServicesFromDid(agentContext, 'did')).rejects.toThrowError(error) + }) + + test('resolves IndyAgentService', async () => { + mockFunction(didResolverService.resolveDidDocument).mockResolvedValue( + new DidDocument({ + context: ['https://w3id.org/did/v1'], + id: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h', + service: [ + new IndyAgentService({ + id: 'test-id', + serviceEndpoint: 'https://test.com', + recipientKeys: ['Q4zqM7aXqm7gDQkUVLng9h'], + routingKeys: ['DADEajsDSaksLng9h'], + priority: 5, + }), + ], + }) + ) + + const resolved = await didCommDocumentService.resolveServicesFromDid( + agentContext, + 'did:sov:Q4zqM7aXqm7gDQkUVLng9h' + ) + expect(didResolverService.resolveDidDocument).toHaveBeenCalledWith(agentContext, 'did:sov:Q4zqM7aXqm7gDQkUVLng9h') + + expect(resolved).toHaveLength(1) + expect(resolved[0]).toMatchObject({ + id: 'test-id', + serviceEndpoint: 'https://test.com', + recipientKeys: [verkeyToInstanceOfKey('Q4zqM7aXqm7gDQkUVLng9h')], + routingKeys: [verkeyToInstanceOfKey('DADEajsDSaksLng9h')], + }) + }) + + test('resolves DidCommV1Service', async () => { + const publicKeyBase58Ed25519 = 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8' + const publicKeyBase58X25519 = 'S3AQEEKkGYrrszT9D55ozVVX2XixYp8uynqVm4okbud' + + const Ed25519VerificationMethod: VerificationMethod = { + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h', + id: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h#key-1', + publicKeyBase58: publicKeyBase58Ed25519, + } + const X25519VerificationMethod: VerificationMethod = { + type: 'X25519KeyAgreementKey2019', + controller: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h', + id: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h#key-agreement-1', + publicKeyBase58: publicKeyBase58X25519, + } + + mockFunction(didResolverService.resolveDidDocument).mockResolvedValue( + new DidDocument({ + context: [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h', + verificationMethod: [Ed25519VerificationMethod, X25519VerificationMethod], + authentication: [Ed25519VerificationMethod.id], + keyAgreement: [X25519VerificationMethod.id], + service: [ + new DidCommV1Service({ + id: 'test-id', + serviceEndpoint: 'https://test.com', + recipientKeys: [X25519VerificationMethod.id], + routingKeys: [Ed25519VerificationMethod.id], + priority: 5, + }), + ], + }) + ) + + const resolved = await didCommDocumentService.resolveServicesFromDid( + agentContext, + 'did:sov:Q4zqM7aXqm7gDQkUVLng9h' + ) + expect(didResolverService.resolveDidDocument).toHaveBeenCalledWith(agentContext, 'did:sov:Q4zqM7aXqm7gDQkUVLng9h') + + const ed25519Key = Key.fromPublicKeyBase58(publicKeyBase58Ed25519, KeyType.Ed25519) + expect(resolved).toHaveLength(1) + expect(resolved[0]).toMatchObject({ + id: 'test-id', + serviceEndpoint: 'https://test.com', + recipientKeys: [ed25519Key], + routingKeys: [ed25519Key], + }) + }) + }) +}) diff --git a/packages/core/src/modules/didcomm/services/index.ts b/packages/core/src/modules/didcomm/services/index.ts new file mode 100644 index 0000000000..ae2cb50e2f --- /dev/null +++ b/packages/core/src/modules/didcomm/services/index.ts @@ -0,0 +1 @@ +export * from './DidCommDocumentService' diff --git a/packages/core/src/modules/didcomm/types.ts b/packages/core/src/modules/didcomm/types.ts new file mode 100644 index 0000000000..e8f9e9a9a8 --- /dev/null +++ b/packages/core/src/modules/didcomm/types.ts @@ -0,0 +1,8 @@ +import type { Key } from '../../crypto' + +export interface ResolvedDidCommService { + id: string + serviceEndpoint: string + recipientKeys: Key[] + routingKeys: Key[] +} diff --git a/packages/core/src/modules/didcomm/util/__tests__/matchingEd25519Key.test.ts b/packages/core/src/modules/didcomm/util/__tests__/matchingEd25519Key.test.ts new file mode 100644 index 0000000000..3987d045e3 --- /dev/null +++ b/packages/core/src/modules/didcomm/util/__tests__/matchingEd25519Key.test.ts @@ -0,0 +1,84 @@ +import type { VerificationMethod } from '../../../dids' + +import { Key, KeyType } from '../../../../crypto' +import { DidDocument } from '../../../dids' +import { findMatchingEd25519Key } from '../matchingEd25519Key' + +describe('findMatchingEd25519Key', () => { + const publicKeyBase58Ed25519 = 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8' + const Ed25519VerificationMethod: VerificationMethod = { + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:WJz9mHyW9BZksioQnRsrAo', + id: 'did:sov:WJz9mHyW9BZksioQnRsrAo#key-1', + publicKeyBase58: publicKeyBase58Ed25519, + } + + const publicKeyBase58X25519 = 'S3AQEEKkGYrrszT9D55ozVVX2XixYp8uynqVm4okbud' + const X25519VerificationMethod: VerificationMethod = { + type: 'X25519KeyAgreementKey2019', + controller: 'did:sov:WJz9mHyW9BZksioQnRsrAo', + id: 'did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1', + publicKeyBase58: publicKeyBase58X25519, + } + + describe('referenced verification method', () => { + const didDocument = new DidDocument({ + context: [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:sov:WJz9mHyW9BZksioQnRsrAo', + verificationMethod: [Ed25519VerificationMethod, X25519VerificationMethod], + authentication: [Ed25519VerificationMethod.id], + assertionMethod: [Ed25519VerificationMethod.id], + keyAgreement: [X25519VerificationMethod.id], + }) + + test('returns matching Ed25519 key if corresponding X25519 key supplied', () => { + const x25519Key = Key.fromPublicKeyBase58(publicKeyBase58X25519, KeyType.X25519) + const ed25519Key = findMatchingEd25519Key(x25519Key, didDocument) + expect(ed25519Key?.publicKeyBase58).toBe(Ed25519VerificationMethod.publicKeyBase58) + }) + + test('returns undefined if non-corresponding X25519 key supplied', () => { + const differentX25519Key = Key.fromPublicKeyBase58('Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', KeyType.X25519) + expect(findMatchingEd25519Key(differentX25519Key, didDocument)).toBeUndefined() + }) + + test('returns undefined if ed25519 key supplied', () => { + const ed25519Key = Key.fromPublicKeyBase58(publicKeyBase58Ed25519, KeyType.Ed25519) + expect(findMatchingEd25519Key(ed25519Key, didDocument)).toBeUndefined() + }) + }) + + describe('non-referenced authentication', () => { + const didDocument = new DidDocument({ + context: [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:sov:WJz9mHyW9BZksioQnRsrAo', + authentication: [Ed25519VerificationMethod], + assertionMethod: [Ed25519VerificationMethod], + keyAgreement: [X25519VerificationMethod], + }) + + test('returns matching Ed25519 key if corresponding X25519 key supplied', () => { + const x25519Key = Key.fromPublicKeyBase58(publicKeyBase58X25519, KeyType.X25519) + const ed25519Key = findMatchingEd25519Key(x25519Key, didDocument) + expect(ed25519Key?.publicKeyBase58).toBe(Ed25519VerificationMethod.publicKeyBase58) + }) + + test('returns undefined if non-corresponding X25519 key supplied', () => { + const differentX25519Key = Key.fromPublicKeyBase58('Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', KeyType.X25519) + expect(findMatchingEd25519Key(differentX25519Key, didDocument)).toBeUndefined() + }) + + test('returns undefined if ed25519 key supplied', () => { + const ed25519Key = Key.fromPublicKeyBase58(publicKeyBase58Ed25519, KeyType.Ed25519) + expect(findMatchingEd25519Key(ed25519Key, didDocument)).toBeUndefined() + }) + }) +}) diff --git a/packages/core/src/modules/didcomm/util/matchingEd25519Key.ts b/packages/core/src/modules/didcomm/util/matchingEd25519Key.ts new file mode 100644 index 0000000000..7ac297649c --- /dev/null +++ b/packages/core/src/modules/didcomm/util/matchingEd25519Key.ts @@ -0,0 +1,32 @@ +import type { DidDocument, VerificationMethod } from '../../dids' + +import { Key, KeyType } from '../../../crypto' +import { keyReferenceToKey } from '../../dids' +import { convertPublicKeyToX25519 } from '../../dids/domain/key-type/ed25519' + +/** + * Tries to find a matching Ed25519 key to the supplied X25519 key + * @param x25519Key X25519 key + * @param didDocument Did document containing all the keys + * @returns a matching Ed25519 key or `undefined` (if no matching key found) + */ +export function findMatchingEd25519Key(x25519Key: Key, didDocument: DidDocument): Key | undefined { + if (x25519Key.keyType !== KeyType.X25519) return undefined + + const verificationMethods = didDocument.verificationMethod ?? [] + const keyAgreements = didDocument.keyAgreement ?? [] + const authentications = didDocument.authentication ?? [] + const allKeyReferences: VerificationMethod[] = [ + ...verificationMethods, + ...authentications.filter((keyAgreement): keyAgreement is VerificationMethod => typeof keyAgreement !== 'string'), + ...keyAgreements.filter((keyAgreement): keyAgreement is VerificationMethod => typeof keyAgreement !== 'string'), + ] + + return allKeyReferences + .map((keyReference) => keyReferenceToKey(didDocument, keyReference.id)) + .filter((key) => key?.keyType === KeyType.Ed25519) + .find((keyEd25519) => { + const keyX25519 = Key.fromPublicKey(convertPublicKeyToX25519(keyEd25519.publicKey), KeyType.X25519) + return keyX25519.publicKeyBase58 === x25519Key.publicKeyBase58 + }) +} diff --git a/packages/core/src/modules/dids/helpers.ts b/packages/core/src/modules/dids/helpers.ts index ef3c68ab07..6170707c27 100644 --- a/packages/core/src/modules/dids/helpers.ts +++ b/packages/core/src/modules/dids/helpers.ts @@ -2,8 +2,12 @@ import { KeyType, Key } from '../../crypto' import { DidKey } from './methods/key' +export function isDidKey(key: string) { + return key.startsWith('did:key') +} + export function didKeyToVerkey(key: string) { - if (key.startsWith('did:key')) { + if (isDidKey(key)) { const publicKeyBase58 = DidKey.fromDid(key).key.publicKeyBase58 return publicKeyBase58 } @@ -11,7 +15,7 @@ export function didKeyToVerkey(key: string) { } export function verkeyToDidKey(key: string) { - if (key.startsWith('did:key')) { + if (isDidKey(key)) { return key } const publicKeyBase58 = key diff --git a/packages/core/src/modules/dids/methods/peer/createPeerDidDocumentFromServices.ts b/packages/core/src/modules/dids/methods/peer/createPeerDidDocumentFromServices.ts index 8f34254a92..184e678d76 100644 --- a/packages/core/src/modules/dids/methods/peer/createPeerDidDocumentFromServices.ts +++ b/packages/core/src/modules/dids/methods/peer/createPeerDidDocumentFromServices.ts @@ -1,4 +1,4 @@ -import type { ResolvedDidCommService } from '../../../../agent/MessageSender' +import type { ResolvedDidCommService } from '../../../didcomm' import { convertPublicKeyToX25519 } from '@stablelib/ed25519' diff --git a/packages/core/src/modules/generic-records/GenericRecordsApi.ts b/packages/core/src/modules/generic-records/GenericRecordsApi.ts index feae3a9758..56efe6667e 100644 --- a/packages/core/src/modules/generic-records/GenericRecordsApi.ts +++ b/packages/core/src/modules/generic-records/GenericRecordsApi.ts @@ -1,4 +1,5 @@ -import type { GenericRecord, GenericRecordTags, SaveGenericRecordOption } from './repository/GenericRecord' +import type { Query } from '../../storage/StorageService' +import type { GenericRecord, SaveGenericRecordOption } from './repository/GenericRecord' import { AgentContext } from '../../agent' import { InjectionSymbols } from '../../constants' @@ -79,7 +80,7 @@ export class GenericRecordsApi { return this.genericRecordsService.findById(this.agentContext, id) } - public async findAllByQuery(query: Partial): Promise { + public async findAllByQuery(query: Query): Promise { return this.genericRecordsService.findAllByQuery(this.agentContext, query) } diff --git a/packages/core/src/modules/generic-records/services/GenericRecordService.ts b/packages/core/src/modules/generic-records/services/GenericRecordService.ts index 861f0a002f..ccdf9d59d3 100644 --- a/packages/core/src/modules/generic-records/services/GenericRecordService.ts +++ b/packages/core/src/modules/generic-records/services/GenericRecordService.ts @@ -1,5 +1,6 @@ import type { AgentContext } from '../../../agent' -import type { GenericRecordTags, SaveGenericRecordOption } from '../repository/GenericRecord' +import type { Query } from '../../../storage/StorageService' +import type { SaveGenericRecordOption } from '../repository/GenericRecord' import { AriesFrameworkError } from '../../../error' import { injectable } from '../../../plugins' @@ -51,7 +52,7 @@ export class GenericRecordService { } } - public async findAllByQuery(agentContext: AgentContext, query: Partial) { + public async findAllByQuery(agentContext: AgentContext, query: Query) { return this.genericRecordsRepository.findByQuery(agentContext, query) } diff --git a/packages/core/src/modules/ledger/IndyPool.ts b/packages/core/src/modules/ledger/IndyPool.ts index 853b253b0e..c3d2249799 100644 --- a/packages/core/src/modules/ledger/IndyPool.ts +++ b/packages/core/src/modules/ledger/IndyPool.ts @@ -74,6 +74,7 @@ export class IndyPool { } this._poolHandle = undefined + this.poolConnected = undefined await this.indy.closePoolLedger(poolHandle) } diff --git a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts b/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts index 84563e5344..43cc31f66e 100644 --- a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts +++ b/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts @@ -27,7 +27,7 @@ const pools: IndyPoolConfig[] = [ indyNamespace: 'sovrin', isProduction: true, genesisTransactions: 'xxx', - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + transactionAuthorAgreement: { version: '1.0', acceptanceMechanism: 'accept' }, }, ] @@ -75,7 +75,7 @@ describe('IndyLedgerService', () => { // @ts-ignore jest.spyOn(ledgerService, 'getTransactionAuthorAgreement').mockResolvedValue({ digest: 'abcde', - version: 'abdcg', + version: '2.0', text: 'jhsdhbv', ratification_ts: 12345678, acceptanceMechanisms: { @@ -93,7 +93,7 @@ describe('IndyLedgerService', () => { 'Heinz57' ) ).rejects.toThrowError( - 'Unable to satisfy matching TAA with mechanism "accept" and version "1" in pool.\n Found ["accept"] and version 3 in pool.' + 'Unable to satisfy matching TAA with mechanism "accept" and version "1.0" in pool.\n Found ["accept"] and version 2.0 in pool.' ) }) @@ -102,7 +102,7 @@ describe('IndyLedgerService', () => { // @ts-ignore jest.spyOn(ledgerService, 'getTransactionAuthorAgreement').mockResolvedValue({ digest: 'abcde', - version: 'abdcg', + version: '1.0', text: 'jhsdhbv', ratification_ts: 12345678, acceptanceMechanisms: { @@ -120,7 +120,7 @@ describe('IndyLedgerService', () => { 'Heinz57' ) ).rejects.toThrowError( - 'Unable to satisfy matching TAA with mechanism "accept" and version "1" in pool.\n Found ["decline"] and version 1 in pool.' + 'Unable to satisfy matching TAA with mechanism "accept" and version "1.0" in pool.\n Found ["decline"] and version 1.0 in pool.' ) }) @@ -133,7 +133,7 @@ describe('IndyLedgerService', () => { // @ts-ignore jest.spyOn(ledgerService, 'getTransactionAuthorAgreement').mockResolvedValue({ digest: 'abcde', - version: 'abdcg', + version: '1.0', text: 'jhsdhbv', ratification_ts: 12345678, acceptanceMechanisms: { diff --git a/packages/core/src/modules/ledger/services/IndyLedgerService.ts b/packages/core/src/modules/ledger/services/IndyLedgerService.ts index c4c3f4a0f6..4a339e7add 100644 --- a/packages/core/src/modules/ledger/services/IndyLedgerService.ts +++ b/packages/core/src/modules/ledger/services/IndyLedgerService.ts @@ -519,7 +519,7 @@ export class IndyLedgerService { // Throw an error if the pool doesn't have the specified version and acceptance mechanism if ( - authorAgreement.acceptanceMechanisms.version !== taa.version || + authorAgreement.version !== taa.version || !(taa.acceptanceMechanism in authorAgreement.acceptanceMechanisms.aml) ) { // Throw an error with a helpful message @@ -527,7 +527,7 @@ export class IndyLedgerService { taa.acceptanceMechanism )} and version ${JSON.stringify(taa.version)} in pool.\n Found ${JSON.stringify( Object.keys(authorAgreement.acceptanceMechanisms.aml) - )} and version ${authorAgreement.acceptanceMechanisms.version} in pool.` + )} and version ${authorAgreement.version} in pool.` throw new LedgerError(errMessage) } diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts index e1561edfe2..a23c163b42 100644 --- a/packages/core/src/modules/oob/OutOfBandApi.ts +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -2,6 +2,7 @@ import type { AgentMessage } from '../../agent/AgentMessage' import type { AgentMessageReceivedEvent } from '../../agent/Events' import type { Key } from '../../crypto' import type { Attachment } from '../../decorators/attachment/Attachment' +import type { Query } from '../../storage/StorageService' import type { PlaintextMessage } from '../../types' import type { ConnectionInvitationMessage, ConnectionRecord, Routing } from '../connections' import type { HandshakeReusedEvent } from './domain/OutOfBandEvents' @@ -12,7 +13,6 @@ import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { EventEmitter } from '../../agent/EventEmitter' import { filterContextCorrelationId, AgentEventTypes } from '../../agent/Events' -import { FeatureRegistry } from '../../agent/FeatureRegistry' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' import { InjectionSymbols } from '../../constants' @@ -25,9 +25,9 @@ import { JsonEncoder, JsonTransformer } from '../../utils' import { parseMessageType, supportsIncomingMessageType } from '../../utils/messageType' import { parseInvitationUrl, parseInvitationShortUrl } from '../../utils/parseInvitation' import { ConnectionsApi, DidExchangeState, HandshakeProtocol } from '../connections' +import { DidCommDocumentService } from '../didcomm' import { DidKey } from '../dids' import { didKeyToVerkey } from '../dids/helpers' -import { outOfBandServiceToNumAlgo2Did } from '../dids/methods/peer/peerDidNumAlgo2' import { RoutingService } from '../routing/services/RoutingService' import { OutOfBandService } from './OutOfBandService' @@ -45,7 +45,7 @@ const didCommProfiles = ['didcomm/aip1', 'didcomm/aip2;env=rfc19'] export interface CreateOutOfBandInvitationConfig { label?: string - alias?: string + alias?: string // alias for a connection record to be created imageUrl?: string goalCode?: string goal?: string @@ -60,7 +60,7 @@ export interface CreateOutOfBandInvitationConfig { export interface CreateLegacyInvitationConfig { label?: string - alias?: string + alias?: string // alias for a connection record to be created imageUrl?: string multiUseInvitation?: boolean autoAcceptConnection?: boolean @@ -84,7 +84,7 @@ export class OutOfBandApi { private connectionsApi: ConnectionsApi private didCommMessageRepository: DidCommMessageRepository private dispatcher: Dispatcher - private featureRegistry: FeatureRegistry + private didCommDocumentService: DidCommDocumentService private messageSender: MessageSender private eventEmitter: EventEmitter private agentContext: AgentContext @@ -92,7 +92,7 @@ export class OutOfBandApi { public constructor( dispatcher: Dispatcher, - featureRegistry: FeatureRegistry, + didCommDocumentService: DidCommDocumentService, outOfBandService: OutOfBandService, routingService: RoutingService, connectionsApi: ConnectionsApi, @@ -103,7 +103,7 @@ export class OutOfBandApi { agentContext: AgentContext ) { this.dispatcher = dispatcher - this.featureRegistry = featureRegistry + this.didCommDocumentService = didCommDocumentService this.agentContext = agentContext this.logger = logger this.outOfBandService = outOfBandService @@ -207,9 +207,15 @@ export class OutOfBandApi { mediatorId: routing.mediatorId, role: OutOfBandRole.Sender, state: OutOfBandState.AwaitResponse, + alias: config.alias, outOfBandInvitation: outOfBandInvitation, reusable: multiUseInvitation, autoAcceptConnection, + tags: { + recipientKeyFingerprints: services + .reduce((aggr, { recipientKeys }) => [...aggr, ...recipientKeys], []) + .map((didKey) => DidKey.fromDid(didKey).key.fingerprint), + }, }) await this.outOfBandService.save(this.agentContext, outOfBandRecord) @@ -354,12 +360,33 @@ export class OutOfBandApi { ) } + const recipientKeyFingerprints: string[] = [] + for (const service of outOfBandInvitation.getServices()) { + // Resolve dids to DIDDocs to retrieve services + if (typeof service === 'string') { + this.logger.debug(`Resolving services for did ${service}.`) + const resolvedDidCommServices = await this.didCommDocumentService.resolveServicesFromDid( + this.agentContext, + service + ) + recipientKeyFingerprints.push( + ...resolvedDidCommServices + .reduce((aggr, { recipientKeys }) => [...aggr, ...recipientKeys], []) + .map((key) => key.fingerprint) + ) + } else { + recipientKeyFingerprints.push(...service.recipientKeys.map((didKey) => DidKey.fromDid(didKey).key.fingerprint)) + } + } + outOfBandRecord = new OutOfBandRecord({ role: OutOfBandRole.Receiver, state: OutOfBandState.Initial, outOfBandInvitation: outOfBandInvitation, autoAcceptConnection, + tags: { recipientKeyFingerprints }, }) + await this.outOfBandService.save(this.agentContext, outOfBandRecord) this.outOfBandService.emitStateChangedEvent(this.agentContext, outOfBandRecord, null) @@ -407,10 +434,11 @@ export class OutOfBandApi { const { outOfBandInvitation } = outOfBandRecord const { label, alias, imageUrl, autoAcceptConnection, reuseConnection, routing } = config - const { handshakeProtocols, services } = outOfBandInvitation + const { handshakeProtocols } = outOfBandInvitation + const services = outOfBandInvitation.getServices() const messages = outOfBandInvitation.getRequests() - const existingConnection = await this.findExistingConnection(services) + const existingConnection = await this.findExistingConnection(outOfBandInvitation) await this.outOfBandService.updateState(this.agentContext, outOfBandRecord, OutOfBandState.PrepareResponse) @@ -510,6 +538,15 @@ export class OutOfBandApi { return this.outOfBandService.getAll(this.agentContext) } + /** + * Retrieve all out of bands records by specified query param + * + * @returns List containing all out of band records matching specified query params + */ + public findAllByQuery(query: Query) { + return this.outOfBandService.findAllByQuery(this.agentContext, query) + } + /** * Retrieve a out of band record by id * @@ -582,26 +619,20 @@ export class OutOfBandApi { return handshakeProtocol } - private async findExistingConnection(services: Array) { - this.logger.debug('Searching for an existing connection for out-of-band invitation services.', { services }) - - // TODO: for each did we should look for a connection with the invitation did OR a connection with theirDid that matches the service did - for (const didOrService of services) { - // We need to check if the service is an instance of string because of limitations from class-validator - if (typeof didOrService === 'string' || didOrService instanceof String) { - // TODO await this.connectionsApi.findByTheirDid() - throw new AriesFrameworkError('Dids are not currently supported in out-of-band invitation services attribute.') - } + private async findExistingConnection(outOfBandInvitation: OutOfBandInvitation) { + this.logger.debug('Searching for an existing connection for out-of-band invitation.', { outOfBandInvitation }) - const did = outOfBandServiceToNumAlgo2Did(didOrService) - const connections = await this.connectionsApi.findByInvitationDid(did) - this.logger.debug(`Retrieved ${connections.length} connections for invitation did ${did}`) + for (const invitationDid of outOfBandInvitation.invitationDids) { + const connections = await this.connectionsApi.findByInvitationDid(invitationDid) + this.logger.debug(`Retrieved ${connections.length} connections for invitation did ${invitationDid}`) if (connections.length === 1) { const [firstConnection] = connections return firstConnection } else if (connections.length > 1) { - this.logger.warn(`There is more than one connection created from invitationDid ${did}. Taking the first one.`) + this.logger.warn( + `There is more than one connection created from invitationDid ${invitationDid}. Taking the first one.` + ) const [firstConnection] = connections return firstConnection } @@ -649,19 +680,36 @@ export class OutOfBandApi { this.logger.debug(`Message with type ${plaintextMessage['@type']} can be processed.`) + let serviceEndpoint: string | undefined + let recipientKeys: string[] | undefined + let routingKeys: string[] = [] + // The framework currently supports only older OOB messages with `~service` decorator. // TODO: support receiving messages with other services so we don't have to transform the service // to ~service decorator const [service] = services if (typeof service === 'string') { - throw new AriesFrameworkError('Dids are not currently supported in out-of-band invitation services attribute.') + const [didService] = await this.didCommDocumentService.resolveServicesFromDid(this.agentContext, service) + if (didService) { + serviceEndpoint = didService.serviceEndpoint + recipientKeys = didService.recipientKeys.map((key) => key.publicKeyBase58) + routingKeys = didService.routingKeys.map((key) => key.publicKeyBase58) || [] + } + } else { + serviceEndpoint = service.serviceEndpoint + recipientKeys = service.recipientKeys.map(didKeyToVerkey) + routingKeys = service.routingKeys?.map(didKeyToVerkey) || [] + } + + if (!serviceEndpoint || !recipientKeys) { + throw new AriesFrameworkError('Service not found') } const serviceDecorator = new ServiceDecorator({ - recipientKeys: service.recipientKeys.map(didKeyToVerkey), - routingKeys: service.routingKeys?.map(didKeyToVerkey) || [], - serviceEndpoint: service.serviceEndpoint, + recipientKeys, + routingKeys, + serviceEndpoint, }) plaintextMessage['~service'] = JsonTransformer.toJSON(serviceDecorator) diff --git a/packages/core/src/modules/oob/OutOfBandService.ts b/packages/core/src/modules/oob/OutOfBandService.ts index 0c78517f99..031052d2f3 100644 --- a/packages/core/src/modules/oob/OutOfBandService.ts +++ b/packages/core/src/modules/oob/OutOfBandService.ts @@ -1,6 +1,7 @@ import type { AgentContext } from '../../agent' import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' import type { Key } from '../../crypto' +import type { Query } from '../../storage/StorageService' import type { ConnectionRecord } from '../connections' import type { HandshakeReusedEvent, OutOfBandStateChangedEvent } from './domain/OutOfBandEvents' import type { OutOfBandRecord } from './repository' @@ -178,6 +179,10 @@ export class OutOfBandService { return this.outOfBandRepository.getAll(agentContext) } + public async findAllByQuery(agentContext: AgentContext, query: Query) { + return this.outOfBandRepository.findByQuery(agentContext, query) + } + public async deleteById(agentContext: AgentContext, outOfBandId: string) { const outOfBandRecord = await this.getById(agentContext, outOfBandId) return this.outOfBandRepository.delete(agentContext, outOfBandRecord) diff --git a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts index 553ed826b9..70a08b4d55 100644 --- a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts +++ b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts @@ -522,5 +522,15 @@ describe('OutOfBandService', () => { expect(result).toEqual(expect.arrayContaining(expected)) }) + + it('findAllByQuery should return value from outOfBandRepository.findByQuery', async () => { + const expected = [getMockOutOfBand(), getMockOutOfBand()] + + mockFunction(outOfBandRepository.findByQuery).mockReturnValue(Promise.resolve(expected)) + const result = await outOfBandService.findAllByQuery(agentContext, { state: OutOfBandState.Initial }) + expect(outOfBandRepository.findByQuery).toBeCalledWith(agentContext, { state: OutOfBandState.Initial }) + + expect(result).toEqual(expect.arrayContaining(expected)) + }) }) }) diff --git a/packages/core/src/modules/oob/helpers.ts b/packages/core/src/modules/oob/helpers.ts index e3677ee76d..be2fdc1b6e 100644 --- a/packages/core/src/modules/oob/helpers.ts +++ b/packages/core/src/modules/oob/helpers.ts @@ -37,7 +37,7 @@ export function convertToNewInvitation(oldInvitation: ConnectionInvitationMessag export function convertToOldInvitation(newInvitation: OutOfBandInvitation) { // Taking first service, as we can only include one service in a legacy invitation. - const [service] = newInvitation.services + const [service] = newInvitation.getServices() let options if (typeof service === 'string') { diff --git a/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts b/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts index 6e3f5d4018..39aec65941 100644 --- a/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts +++ b/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts @@ -1,4 +1,3 @@ -import type { Key } from '../../../crypto' import type { PlaintextMessage } from '../../../types' import type { HandshakeProtocol } from '../../connections' @@ -13,7 +12,6 @@ import { JsonEncoder } from '../../../utils/JsonEncoder' import { JsonTransformer } from '../../../utils/JsonTransformer' import { IsValidMessageType, parseMessageType, replaceLegacyDidSovPrefix } from '../../../utils/messageType' import { IsStringOrInstance } from '../../../utils/validators' -import { DidKey } from '../../dids' import { outOfBandServiceToNumAlgo2Did } from '../../dids/methods/peer/peerDidNumAlgo2' import { OutOfBandDidCommService } from '../domain/OutOfBandDidCommService' @@ -89,7 +87,7 @@ export class OutOfBandInvitation extends AgentMessage { } public get invitationDids() { - const dids = this.services.map((didOrService) => { + const dids = this.getServices().map((didOrService) => { if (typeof didOrService === 'string') { return didOrService } @@ -98,13 +96,18 @@ export class OutOfBandInvitation extends AgentMessage { return dids } - // TODO: this only takes into account inline didcomm services, won't work for public dids - public getRecipientKeys(): Key[] { - return this.services - .filter((s): s is OutOfBandDidCommService => typeof s !== 'string' && !(s instanceof String)) - .map((s) => s.recipientKeys) - .reduce((acc, curr) => [...acc, ...curr], []) - .map((didKey) => DidKey.fromDid(didKey).key) + // shorthand for services without the need to deal with the String DIDs + public getServices(): Array { + return this.services.map((service) => { + if (service instanceof String) return service.toString() + return service + }) + } + public getDidServices(): Array { + return this.getServices().filter((service): service is string => typeof service === 'string') + } + public getInlineServices(): Array { + return this.getServices().filter((service): service is OutOfBandDidCommService => typeof service !== 'string') } @Transform(({ value }) => replaceLegacyDidSovPrefix(value), { @@ -141,7 +144,8 @@ export class OutOfBandInvitation extends AgentMessage { @OutOfBandServiceTransformer() @IsStringOrInstance(OutOfBandDidCommService, { each: true }) @ValidateNested({ each: true }) - public services!: Array + // eslint-disable-next-line @typescript-eslint/ban-types + private services!: Array /** * Custom property. It is not part of the RFC. @@ -152,13 +156,8 @@ export class OutOfBandInvitation extends AgentMessage { } /** - * Decorator that transforms authentication json to corresponding class instances - * - * @example - * class Example { - * VerificationMethodTransformer() - * private authentication: VerificationMethod - * } + * Decorator that transforms services json to corresponding class instances + * @note Because of ValidateNested limitation, this produces instances of String for DID services except plain js string */ function OutOfBandServiceTransformer() { return Transform(({ value, type }: { value: Array; type: TransformationType }) => { diff --git a/packages/core/src/modules/oob/repository/OutOfBandRecord.ts b/packages/core/src/modules/oob/repository/OutOfBandRecord.ts index 02b821b004..ec291225c2 100644 --- a/packages/core/src/modules/oob/repository/OutOfBandRecord.ts +++ b/packages/core/src/modules/oob/repository/OutOfBandRecord.ts @@ -9,32 +9,37 @@ import { BaseRecord } from '../../../storage/BaseRecord' import { uuid } from '../../../utils/uuid' import { OutOfBandInvitation } from '../messages' +type DefaultOutOfBandRecordTags = { + role: OutOfBandRole + state: OutOfBandState + invitationId: string +} + +interface CustomOutOfBandRecordTags extends TagsBase { + recipientKeyFingerprints: string[] +} + export interface OutOfBandRecordProps { id?: string createdAt?: Date updatedAt?: Date - tags?: TagsBase + tags?: CustomOutOfBandRecordTags outOfBandInvitation: OutOfBandInvitation role: OutOfBandRole state: OutOfBandState + alias?: string autoAcceptConnection?: boolean reusable?: boolean mediatorId?: string reuseConnectionId?: string } -type DefaultOutOfBandRecordTags = { - role: OutOfBandRole - state: OutOfBandState - invitationId: string - recipientKeyFingerprints: string[] -} - -export class OutOfBandRecord extends BaseRecord { +export class OutOfBandRecord extends BaseRecord { @Type(() => OutOfBandInvitation) public outOfBandInvitation!: OutOfBandInvitation public role!: OutOfBandRole public state!: OutOfBandState + public alias?: string public reusable!: boolean public autoAcceptConnection?: boolean public mediatorId?: string @@ -52,11 +57,12 @@ export class OutOfBandRecord extends BaseRecord { this.outOfBandInvitation = props.outOfBandInvitation this.role = props.role this.state = props.state + this.alias = props.alias this.autoAcceptConnection = props.autoAcceptConnection this.reusable = props.reusable ?? false this.mediatorId = props.mediatorId this.reuseConnectionId = props.reuseConnectionId - this._tags = props.tags ?? {} + this._tags = props.tags ?? { recipientKeyFingerprints: [] } } } @@ -66,7 +72,6 @@ export class OutOfBandRecord extends BaseRecord { role: this.role, state: this.state, invitationId: this.outOfBandInvitation.id, - recipientKeyFingerprints: this.outOfBandInvitation.getRecipientKeys().map((key) => key.fingerprint), } } diff --git a/packages/core/src/modules/oob/repository/__tests__/OutOfBandRecord.test.ts b/packages/core/src/modules/oob/repository/__tests__/OutOfBandRecord.test.ts index ee649b7710..6c5cef483e 100644 --- a/packages/core/src/modules/oob/repository/__tests__/OutOfBandRecord.test.ts +++ b/packages/core/src/modules/oob/repository/__tests__/OutOfBandRecord.test.ts @@ -22,6 +22,9 @@ describe('OutOfBandRecord', () => { ], id: 'a-message-id', }), + tags: { + recipientKeyFingerprints: ['z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th'], + }, }) expect(outOfBandRecord.getTags()).toEqual({ diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index ff5fddaff7..b46b559e54 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -1,4 +1,5 @@ import type { AgentMessage } from '../../agent/AgentMessage' +import type { Query } from '../../storage/StorageService' import type { ProofService } from './ProofService' import type { AcceptPresentationOptions, @@ -81,6 +82,7 @@ export interface ProofsApi + findAllByQuery(query: Query): Promise getById(proofRecordId: string): Promise deleteById(proofId: string): Promise findById(proofRecordId: string): Promise @@ -491,6 +493,15 @@ export class ProofsApi< return this.proofRepository.getAll(this.agentContext) } + /** + * Retrieve all proof records by specified query params + * + * @returns List containing all proof records matching specified params + */ + public findAllByQuery(query: Query): Promise { + return this.proofRepository.findByQuery(this.agentContext, query) + } + /** * Retrieve a proof record by id * diff --git a/packages/core/src/modules/question-answer/QuestionAnswerApi.ts b/packages/core/src/modules/question-answer/QuestionAnswerApi.ts index 3b19628fb9..6d8004d9e6 100644 --- a/packages/core/src/modules/question-answer/QuestionAnswerApi.ts +++ b/packages/core/src/modules/question-answer/QuestionAnswerApi.ts @@ -1,4 +1,5 @@ -import type { ValidResponse } from './models' +import type { Query } from '../../storage/StorageService' +import type { QuestionAnswerRecord } from './repository' import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' @@ -8,6 +9,7 @@ import { injectable } from '../../plugins' import { ConnectionService } from '../connections' import { AnswerMessageHandler, QuestionMessageHandler } from './handlers' +import { ValidResponse } from './models' import { QuestionAnswerService } from './services' @injectable() @@ -55,7 +57,7 @@ export class QuestionAnswerApi { connectionId, { question: config.question, - validResponses: config.validResponses, + validResponses: config.validResponses.map((item) => new ValidResponse(item)), detail: config?.detail, } ) @@ -98,6 +100,26 @@ export class QuestionAnswerApi { return this.questionAnswerService.getAll(this.agentContext) } + /** + * Get all QuestionAnswer records by specified query params + * + * @returns list containing all QuestionAnswer records matching specified query params + */ + public findAllByQuery(query: Query) { + return this.questionAnswerService.findAllByQuery(this.agentContext, query) + } + + /** + * Retrieve a question answer record by id + * + * @param questionAnswerId The questionAnswer record id + * @return The question answer record or null if not found + * + */ + public findById(questionAnswerId: string) { + return this.questionAnswerService.findById(this.agentContext, questionAnswerId) + } + private registerHandlers(dispatcher: Dispatcher) { dispatcher.registerHandler(new QuestionMessageHandler(this.questionAnswerService)) dispatcher.registerHandler(new AnswerMessageHandler(this.questionAnswerService)) diff --git a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts index fa5f57f86e..1e8bf207a5 100644 --- a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts +++ b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts @@ -14,11 +14,13 @@ import { mockFunction, } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' +import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { IndyWallet } from '../../../wallet/IndyWallet' +import { DidExchangeState } from '../../connections' import { QuestionAnswerEventTypes } from '../QuestionAnswerEvents' import { QuestionAnswerRole } from '../QuestionAnswerRole' -import { QuestionMessage } from '../messages' +import { AnswerMessage, QuestionMessage } from '../messages' import { QuestionAnswerState } from '../models' import { QuestionAnswerRecord, QuestionAnswerRepository } from '../repository' import { QuestionAnswerService } from '../services' @@ -30,6 +32,7 @@ describe('QuestionAnswerService', () => { const mockConnectionRecord = getMockConnection({ id: 'd3849ac3-c981-455b-a1aa-a10bea6cead8', did: 'did:sov:C2SsBf5QUQpqSAQfhu3sd2', + state: DidExchangeState.Completed, }) let wallet: IndyWallet @@ -144,7 +147,7 @@ describe('QuestionAnswerService', () => { eventListenerMock ) - mockFunction(questionAnswerRepository.getSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + mockFunction(questionAnswerRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) await questionAnswerService.createAnswer(agentContext, mockRecord, 'Yes') @@ -165,4 +168,165 @@ describe('QuestionAnswerService', () => { }) }) }) + + describe('processReceiveQuestion', () => { + let mockRecord: QuestionAnswerRecord + + beforeAll(() => { + mockRecord = mockQuestionAnswerRecord({ + questionText: 'Alice, are you on the phone with Bob?', + connectionId: mockConnectionRecord.id, + role: QuestionAnswerRole.Responder, + signatureRequired: false, + state: QuestionAnswerState.QuestionReceived, + threadId: '123', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + }) + + it('creates record when no previous question with that thread exists', async () => { + const questionMessage = new QuestionMessage({ + questionText: 'Alice, are you on the phone with Bob?', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + + const messageContext = new InboundMessageContext(questionMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + const questionAnswerRecord = await questionAnswerService.processReceiveQuestion(messageContext) + + expect(questionAnswerRecord).toMatchObject( + expect.objectContaining({ + role: QuestionAnswerRole.Responder, + state: QuestionAnswerState.QuestionReceived, + threadId: questionMessage.id, + questionText: 'Alice, are you on the phone with Bob?', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + ) + }) + + it(`throws an error when question from the same thread exists `, async () => { + mockFunction(questionAnswerRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + const questionMessage = new QuestionMessage({ + id: '123', + questionText: 'Alice, are you on the phone with Bob?', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + + const messageContext = new InboundMessageContext(questionMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + expect(questionAnswerService.processReceiveQuestion(messageContext)).rejects.toThrowError( + `Question answer record with thread Id ${questionMessage.id} already exists.` + ) + jest.resetAllMocks() + }) + }) + + describe('receiveAnswer', () => { + let mockRecord: QuestionAnswerRecord + + beforeAll(() => { + mockRecord = mockQuestionAnswerRecord({ + questionText: 'Alice, are you on the phone with Bob?', + connectionId: mockConnectionRecord.id, + role: QuestionAnswerRole.Questioner, + signatureRequired: false, + state: QuestionAnswerState.QuestionReceived, + threadId: '123', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + }) + + it('updates state and emits event when valid response is received', async () => { + mockRecord.state = QuestionAnswerState.QuestionSent + mockFunction(questionAnswerRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + const answerMessage = new AnswerMessage({ + response: 'Yes', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(answerMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + const questionAnswerRecord = await questionAnswerService.receiveAnswer(messageContext) + + expect(questionAnswerRecord).toMatchObject( + expect.objectContaining({ + role: QuestionAnswerRole.Questioner, + state: QuestionAnswerState.AnswerReceived, + threadId: '123', + questionText: 'Alice, are you on the phone with Bob?', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + ) + jest.resetAllMocks() + }) + + it(`throws an error when no existing question is found`, async () => { + const answerMessage = new AnswerMessage({ + response: 'Yes', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(answerMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + expect(questionAnswerService.receiveAnswer(messageContext)).rejects.toThrowError( + `Question Answer record with thread Id ${answerMessage.threadId} not found.` + ) + }) + + it(`throws an error when record is in invalid state`, async () => { + mockRecord.state = QuestionAnswerState.AnswerReceived + mockFunction(questionAnswerRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + const answerMessage = new AnswerMessage({ + response: 'Yes', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(answerMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + expect(questionAnswerService.receiveAnswer(messageContext)).rejects.toThrowError( + `Question answer record is in invalid state ${mockRecord.state}. Valid states are: ${QuestionAnswerState.QuestionSent}` + ) + jest.resetAllMocks() + }) + + it(`throws an error when record is in invalid role`, async () => { + mockRecord.state = QuestionAnswerState.QuestionSent + mockRecord.role = QuestionAnswerRole.Responder + mockFunction(questionAnswerRepository.findSingleByQuery).mockReturnValue(Promise.resolve(mockRecord)) + + const answerMessage = new AnswerMessage({ + response: 'Yes', + threadId: '123', + }) + + const messageContext = new InboundMessageContext(answerMessage, { + agentContext, + connection: mockConnectionRecord, + }) + + expect(questionAnswerService.receiveAnswer(messageContext)).rejects.toThrowError( + `Invalid question answer record role ${mockRecord.role}, expected is ${QuestionAnswerRole.Questioner}` + ) + }) + jest.resetAllMocks() + }) }) diff --git a/packages/core/src/modules/question-answer/__tests__/helpers.ts b/packages/core/src/modules/question-answer/__tests__/helpers.ts new file mode 100644 index 0000000000..3d80e32238 --- /dev/null +++ b/packages/core/src/modules/question-answer/__tests__/helpers.ts @@ -0,0 +1,64 @@ +import type { Agent } from '../../../agent/Agent' +import type { QuestionAnswerStateChangedEvent } from '../QuestionAnswerEvents' +import type { QuestionAnswerRole } from '../QuestionAnswerRole' +import type { QuestionAnswerState } from '../models' +import type { Observable } from 'rxjs' + +import { catchError, filter, firstValueFrom, map, ReplaySubject, timeout } from 'rxjs' + +import { QuestionAnswerEventTypes } from '../QuestionAnswerEvents' + +export async function waitForQuestionAnswerRecord( + agent: Agent, + options: { + threadId?: string + role?: QuestionAnswerRole + state?: QuestionAnswerState + previousState?: QuestionAnswerState | null + timeoutMs?: number + } +) { + const observable = agent.events.observable( + QuestionAnswerEventTypes.QuestionAnswerStateChanged + ) + + return waitForQuestionAnswerRecordSubject(observable, options) +} + +export function waitForQuestionAnswerRecordSubject( + subject: ReplaySubject | Observable, + { + threadId, + role, + state, + previousState, + timeoutMs = 10000, + }: { + threadId?: string + role?: QuestionAnswerRole + state?: QuestionAnswerState + previousState?: QuestionAnswerState | null + timeoutMs?: number + } +) { + const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject + return firstValueFrom( + observable.pipe( + filter((e) => previousState === undefined || e.payload.previousState === previousState), + filter((e) => threadId === undefined || e.payload.questionAnswerRecord.threadId === threadId), + filter((e) => role === undefined || e.payload.questionAnswerRecord.role === role), + filter((e) => state === undefined || e.payload.questionAnswerRecord.state === state), + timeout(timeoutMs), + catchError(() => { + throw new Error( + `QuestionAnswerChangedEvent event not emitted within specified timeout: { + previousState: ${previousState}, + threadId: ${threadId}, + state: ${state} + }` + ) + }), + map((e) => e.payload.questionAnswerRecord) + ) + ) +} diff --git a/packages/core/src/modules/question-answer/__tests__/question-answer.e2e.test.ts b/packages/core/src/modules/question-answer/__tests__/question-answer.e2e.test.ts new file mode 100644 index 0000000000..4ffb638905 --- /dev/null +++ b/packages/core/src/modules/question-answer/__tests__/question-answer.e2e.test.ts @@ -0,0 +1,92 @@ +import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' +import type { ConnectionRecord } from '../../connections/repository' + +import { Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getAgentOptions, makeConnection } from '../../../../tests/helpers' +import testLogger from '../../../../tests/logger' +import { Agent } from '../../../agent/Agent' +import { QuestionAnswerRole } from '../QuestionAnswerRole' +import { QuestionAnswerState } from '../models' + +import { waitForQuestionAnswerRecord } from './helpers' + +const bobAgentOptions = getAgentOptions('Bob Question Answer', { + endpoints: ['rxjs:bob'], +}) + +const aliceAgentOptions = getAgentOptions('Alice Question Answer', { + endpoints: ['rxjs:alice'], +}) + +describe('Question Answer', () => { + let bobAgent: Agent + let aliceAgent: Agent + let aliceConnection: ConnectionRecord + + beforeEach(async () => { + const bobMessages = new Subject() + const aliceMessages = new Subject() + const subjectMap = { + 'rxjs:bob': bobMessages, + 'rxjs:alice': aliceMessages, + } + + bobAgent = new Agent(bobAgentOptions) + bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) + bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await bobAgent.initialize() + + aliceAgent = new Agent(aliceAgentOptions) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + ;[aliceConnection] = await makeConnection(aliceAgent, bobAgent) + }) + + afterEach(async () => { + await bobAgent.shutdown() + await bobAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice sends a question and Bob answers', async () => { + testLogger.test('Alice sends question to Bob') + let aliceQuestionAnswerRecord = await aliceAgent.questionAnswer.sendQuestion(aliceConnection.id, { + question: 'Do you want to play?', + validResponses: [{ text: 'Yes' }, { text: 'No' }], + }) + + testLogger.test('Bob waits for question from Alice') + const bobQuestionAnswerRecord = await waitForQuestionAnswerRecord(bobAgent, { + threadId: aliceQuestionAnswerRecord.threadId, + state: QuestionAnswerState.QuestionReceived, + }) + + expect(bobQuestionAnswerRecord.questionText).toEqual('Do you want to play?') + expect(bobQuestionAnswerRecord.validResponses).toEqual([{ text: 'Yes' }, { text: 'No' }]) + testLogger.test('Bob sends answer to Alice') + await bobAgent.questionAnswer.sendAnswer(bobQuestionAnswerRecord.id, 'Yes') + + testLogger.test('Alice waits until Bob answers') + aliceQuestionAnswerRecord = await waitForQuestionAnswerRecord(aliceAgent, { + threadId: aliceQuestionAnswerRecord.threadId, + state: QuestionAnswerState.AnswerReceived, + }) + + expect(aliceQuestionAnswerRecord.response).toEqual('Yes') + + const retrievedRecord = await aliceAgent.questionAnswer.findById(aliceQuestionAnswerRecord.id) + expect(retrievedRecord).toMatchObject( + expect.objectContaining({ + id: aliceQuestionAnswerRecord.id, + threadId: aliceQuestionAnswerRecord.threadId, + state: QuestionAnswerState.AnswerReceived, + role: QuestionAnswerRole.Questioner, + }) + ) + }) +}) diff --git a/packages/core/src/modules/question-answer/repository/QuestionAnswerRecord.ts b/packages/core/src/modules/question-answer/repository/QuestionAnswerRecord.ts index c1a4a0259d..7d649a3bc6 100644 --- a/packages/core/src/modules/question-answer/repository/QuestionAnswerRecord.ts +++ b/packages/core/src/modules/question-answer/repository/QuestionAnswerRecord.ts @@ -76,6 +76,12 @@ export class QuestionAnswerRecord extends BaseRecord { + return this.questionAnswerRepository.findSingleByQuery(agentContext, { + connectionId, + threadId, + }) + } + + /** + * Retrieve a question answer record by id * * @param questionAnswerId The questionAnswer record id * @throws {RecordNotFoundError} If no record is found - * @return The connection record + * @return The question answer record * */ public getById(agentContext: AgentContext, questionAnswerId: string): Promise { @@ -247,15 +272,28 @@ export class QuestionAnswerService { } /** - * Retrieve all QuestionAnswer records + * Retrieve a question answer record by id + * + * @param questionAnswerId The questionAnswer record id + * @return The question answer record or null if not found + * + */ + public findById(agentContext: AgentContext, questionAnswerId: string): Promise { + return this.questionAnswerRepository.findById(agentContext, questionAnswerId) + } + + /** + * Retrieve a question answer record by id + * + * @param questionAnswerId The questionAnswer record id + * @return The question answer record or null if not found * - * @returns List containing all QuestionAnswer records */ public getAll(agentContext: AgentContext) { return this.questionAnswerRepository.getAll(agentContext) } - public async findAllByQuery(agentContext: AgentContext, query: Partial) { + public async findAllByQuery(agentContext: AgentContext, query: Query) { return this.questionAnswerRepository.findByQuery(agentContext, query) } } diff --git a/packages/core/src/modules/routing/RecipientApi.ts b/packages/core/src/modules/routing/RecipientApi.ts index 293208a4a5..c761826211 100644 --- a/packages/core/src/modules/routing/RecipientApi.ts +++ b/packages/core/src/modules/routing/RecipientApi.ts @@ -1,11 +1,11 @@ -import type { OutboundWebSocketClosedEvent } from '../../transport' +import type { OutboundWebSocketClosedEvent, OutboundWebSocketOpenedEvent } from '../../transport' import type { OutboundMessage } from '../../types' import type { ConnectionRecord } from '../connections' import type { MediationStateChangedEvent } from './RoutingEvents' import type { MediationRecord } from './repository' import type { GetRoutingOptions } from './services/RoutingService' -import { firstValueFrom, interval, ReplaySubject, Subject, timer } from 'rxjs' +import { firstValueFrom, interval, merge, ReplaySubject, Subject, timer } from 'rxjs' import { delayWhen, filter, first, takeUntil, tap, throttleTime, timeout } from 'rxjs/operators' import { AgentContext } from '../../agent' @@ -52,6 +52,9 @@ export class RecipientApi { private agentContext: AgentContext private stop$: Subject + // stopMessagePickup$ is used for stop message pickup signal + private readonly stopMessagePickup$ = new Subject() + public constructor( dispatcher: Dispatcher, mediationRecipientService: MediationRecipientService, @@ -148,7 +151,22 @@ export class RecipientApi { } private async openWebSocketAndPickUp(mediator: MediationRecord, pickupStrategy: MediatorPickupStrategy) { - let interval = 50 + const { baseMediatorReconnectionIntervalMs, maximumMediatorReconnectionIntervalMs } = this.agentContext.config + let interval = baseMediatorReconnectionIntervalMs + + const stopConditions$ = merge(this.stop$, this.stopMessagePickup$).pipe() + + // Reset back off interval when the websocket is successfully opened again + this.eventEmitter + .observable(TransportEventTypes.OutboundWebSocketOpenedEvent) + .pipe( + // Stop when the agent shuts down or stop message pickup signal is received + takeUntil(stopConditions$), + filter((e) => e.payload.connectionId === mediator.connectionId) + ) + .subscribe(() => { + interval = baseMediatorReconnectionIntervalMs + }) // FIXME: this won't work for tenant agents created by the tenants module as the agent context session // could be closed. I'm not sure we want to support this as you probably don't want different tenants opening @@ -162,30 +180,35 @@ export class RecipientApi { this.eventEmitter .observable(TransportEventTypes.OutboundWebSocketClosedEvent) .pipe( - // Stop when the agent shuts down - takeUntil(this.stop$), + // Stop when the agent shuts down or stop message pickup signal is received + takeUntil(stopConditions$), filter((e) => e.payload.connectionId === mediator.connectionId), // Make sure we're not reconnecting multiple times throttleTime(interval), - // Increase the interval (recursive back-off) - tap(() => (interval *= 2)), // Wait for interval time before reconnecting - delayWhen(() => timer(interval)) + delayWhen(() => timer(interval)), + // Increase the interval (recursive back-off) + tap(() => { + interval = Math.min(interval * 2, maximumMediatorReconnectionIntervalMs) + }) ) - .subscribe(async () => { - this.logger.debug( - `Websocket connection to mediator with connectionId '${mediator.connectionId}' is closed, attempting to reconnect...` - ) - try { - if (pickupStrategy === MediatorPickupStrategy.PickUpV2) { - // Start Pickup v2 protocol to receive messages received while websocket offline - await this.sendStatusRequest({ mediatorId: mediator.id }) - } else { - await this.openMediationWebSocket(mediator) + .subscribe({ + next: async () => { + this.logger.debug( + `Websocket connection to mediator with connectionId '${mediator.connectionId}' is closed, attempting to reconnect...` + ) + try { + if (pickupStrategy === MediatorPickupStrategy.PickUpV2) { + // Start Pickup v2 protocol to receive messages received while websocket offline + await this.sendStatusRequest({ mediatorId: mediator.id }) + } else { + await this.openMediationWebSocket(mediator) + } + } catch (error) { + this.logger.warn('Unable to re-open websocket connection to mediator', { error }) } - } catch (error) { - this.logger.warn('Unable to re-open websocket connection to mediator', { error }) - } + }, + complete: () => this.logger.info(`Stopping pickup of messages from mediator '${mediator.id}'`), }) try { if (pickupStrategy === MediatorPickupStrategy.Implicit) { @@ -196,38 +219,63 @@ export class RecipientApi { } } - public async initiateMessagePickup(mediator: MediationRecord) { + /** + * Start a Message Pickup flow with a registered Mediator. + * + * @param mediator optional {MediationRecord} corresponding to the mediator to pick messages from. It will use + * default mediator otherwise + * @param pickupStrategy optional {MediatorPickupStrategy} to use in the loop. It will use Agent's default + * strategy or attempt to find it by Discover Features otherwise + * @returns + */ + public async initiateMessagePickup(mediator?: MediationRecord, pickupStrategy?: MediatorPickupStrategy) { const { mediatorPollingInterval } = this.config - const mediatorPickupStrategy = await this.getPickupStrategyForMediator(mediator) - const mediatorConnection = await this.connectionService.getById(this.agentContext, mediator.connectionId) + const mediatorRecord = mediator ?? (await this.findDefaultMediator()) + if (!mediatorRecord) { + throw new AriesFrameworkError('There is no mediator to pickup messages from') + } + + const mediatorPickupStrategy = pickupStrategy ?? (await this.getPickupStrategyForMediator(mediatorRecord)) + const mediatorConnection = await this.connectionService.getById(this.agentContext, mediatorRecord.connectionId) switch (mediatorPickupStrategy) { case MediatorPickupStrategy.PickUpV2: - this.logger.info(`Starting pickup of messages from mediator '${mediator.id}'`) - await this.openWebSocketAndPickUp(mediator, mediatorPickupStrategy) - await this.sendStatusRequest({ mediatorId: mediator.id }) + this.logger.info(`Starting pickup of messages from mediator '${mediatorRecord.id}'`) + await this.openWebSocketAndPickUp(mediatorRecord, mediatorPickupStrategy) + await this.sendStatusRequest({ mediatorId: mediatorRecord.id }) break case MediatorPickupStrategy.PickUpV1: { + const stopConditions$ = merge(this.stop$, this.stopMessagePickup$).pipe() // Explicit means polling every X seconds with batch message - this.logger.info(`Starting explicit (batch) pickup of messages from mediator '${mediator.id}'`) + this.logger.info(`Starting explicit (batch) pickup of messages from mediator '${mediatorRecord.id}'`) const subscription = interval(mediatorPollingInterval) - .pipe(takeUntil(this.stop$)) - .subscribe(async () => { - await this.pickupMessages(mediatorConnection) + .pipe(takeUntil(stopConditions$)) + .subscribe({ + next: async () => { + await this.pickupMessages(mediatorConnection) + }, + complete: () => this.logger.info(`Stopping pickup of messages from mediator '${mediatorRecord.id}'`), }) return subscription } case MediatorPickupStrategy.Implicit: // Implicit means sending ping once and keeping connection open. This requires a long-lived transport // such as WebSockets to work - this.logger.info(`Starting implicit pickup of messages from mediator '${mediator.id}'`) - await this.openWebSocketAndPickUp(mediator, mediatorPickupStrategy) + this.logger.info(`Starting implicit pickup of messages from mediator '${mediatorRecord.id}'`) + await this.openWebSocketAndPickUp(mediatorRecord, mediatorPickupStrategy) break default: - this.logger.info(`Skipping pickup of messages from mediator '${mediator.id}' due to pickup strategy none`) + this.logger.info(`Skipping pickup of messages from mediator '${mediatorRecord.id}' due to pickup strategy none`) } } + /** + * Terminate all ongoing Message Pickup loops + */ + public async stopMessagePickup() { + this.stopMessagePickup$.next(true) + } + private async sendStatusRequest(config: { mediatorId: string; recipientKey?: string }) { const mediationRecord = await this.mediationRecipientService.getById(this.agentContext, config.mediatorId) diff --git a/packages/core/src/modules/routing/__tests__/mediation.test.ts b/packages/core/src/modules/routing/__tests__/mediation.test.ts index d3814e54b3..04b2b32ab3 100644 --- a/packages/core/src/modules/routing/__tests__/mediation.test.ts +++ b/packages/core/src/modules/routing/__tests__/mediation.test.ts @@ -1,5 +1,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' +import type { AgentDependencies } from '../../../agent/AgentDependencies' +import type { InitConfig } from '../../../types' import { Subject } from 'rxjs' @@ -7,8 +9,6 @@ import { SubjectInboundTransport } from '../../../../../../tests/transport/Subje import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' import { getAgentOptions, waitForBasicMessage } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' -import { InjectionSymbols } from '../../../constants' -import { sleep } from '../../../utils/sleep' import { ConnectionRecord, HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' import { MediationState } from '../models/MediationState' @@ -33,13 +33,6 @@ describe('mediator establishment', () => { let senderAgent: Agent afterEach(async () => { - // We want to stop the mediator polling before the agent is shutdown. - // FIXME: add a way to stop mediator polling from the public api, and make sure this is - // being handled in the agent shutdown so we don't get any errors with wallets being closed. - const stop$ = recipientAgent.injectionContainer.resolve>(InjectionSymbols.Stop$) - stop$.next(true) - await sleep(1000) - await recipientAgent?.shutdown() await recipientAgent?.wallet.delete() await mediatorAgent?.shutdown() @@ -48,14 +41,16 @@ describe('mediator establishment', () => { await senderAgent?.wallet.delete() }) - test(`Mediation end-to-end flow - 1. Start mediator agent and create invitation - 2. Start recipient agent with mediatorConnectionsInvite from mediator - 3. Assert mediator and recipient are connected and mediation state is Granted - 4. Start sender agent and create connection with recipient - 5. Assert endpoint in recipient invitation for sender is mediator endpoint - 6. Send basic message from sender to recipient and assert it is received on the recipient side -`, async () => { + const e2eMediationTest = async ( + mediatorAgentOptions: { + readonly config: InitConfig + readonly dependencies: AgentDependencies + }, + recipientAgentOptions: { + config: InitConfig + dependencies: AgentDependencies + } + ) => { const mediatorMessages = new Subject() const recipientMessages = new Subject() const senderMessages = new Subject() @@ -75,7 +70,7 @@ describe('mediator establishment', () => { const mediatorOutOfBandRecord = await mediatorAgent.oob.createInvitation({ label: 'mediator invitation', handshake: true, - handshakeProtocols: [HandshakeProtocol.DidExchange], + handshakeProtocols: [HandshakeProtocol.Connections], }) // Initialize recipient with mediation connections invitation @@ -86,7 +81,6 @@ describe('mediator establishment', () => { mediatorConnectionsInvite: mediatorOutOfBandRecord.outOfBandInvitation.toUrl({ domain: 'https://example.com/ssi', }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, }) recipientAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) @@ -145,6 +139,40 @@ describe('mediator establishment', () => { }) expect(basicMessage.content).toBe(message) + } + + test(`Mediation end-to-end flow + 1. Start mediator agent and create invitation + 2. Start recipient agent with mediatorConnectionsInvite from mediator + 3. Assert mediator and recipient are connected and mediation state is Granted + 4. Start sender agent and create connection with recipient + 5. Assert endpoint in recipient invitation for sender is mediator endpoint + 6. Send basic message from sender to recipient and assert it is received on the recipient side +`, async () => { + await e2eMediationTest(mediatorAgentOptions, { + config: { + ...recipientAgentOptions.config, + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + dependencies: recipientAgentOptions.dependencies, + }) + }) + + test('Mediation end-to-end flow (use did:key in both sides)', async () => { + await e2eMediationTest( + { + config: { ...mediatorAgentOptions.config, useDidKeyInProtocols: true }, + dependencies: mediatorAgentOptions.dependencies, + }, + { + config: { + ...recipientAgentOptions.config, + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + useDidKeyInProtocols: true, + }, + dependencies: recipientAgentOptions.dependencies, + } + ) }) test('restart recipient agent and create connection through mediator after recipient agent is restarted', async () => { diff --git a/packages/core/src/modules/routing/messages/KeylistUpdateMessage.ts b/packages/core/src/modules/routing/messages/KeylistUpdateMessage.ts index e17a9edf79..b8d493881e 100644 --- a/packages/core/src/modules/routing/messages/KeylistUpdateMessage.ts +++ b/packages/core/src/modules/routing/messages/KeylistUpdateMessage.ts @@ -1,6 +1,5 @@ import { Expose, Type } from 'class-transformer' import { IsArray, ValidateNested, IsString, IsEnum, IsInstance } from 'class-validator' -import { Verkey } from 'indy-sdk' import { AgentMessage } from '../../../agent/AgentMessage' import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' @@ -11,7 +10,7 @@ export enum KeylistUpdateAction { } export class KeylistUpdate { - public constructor(options: { recipientKey: Verkey; action: KeylistUpdateAction }) { + public constructor(options: { recipientKey: string; action: KeylistUpdateAction }) { if (options) { this.recipientKey = options.recipientKey this.action = options.action @@ -20,7 +19,7 @@ export class KeylistUpdate { @IsString() @Expose({ name: 'recipient_key' }) - public recipientKey!: Verkey + public recipientKey!: string @IsEnum(KeylistUpdateAction) public action!: KeylistUpdateAction diff --git a/packages/core/src/modules/routing/messages/KeylistUpdateResponseMessage.ts b/packages/core/src/modules/routing/messages/KeylistUpdateResponseMessage.ts index 7367184e7a..88b75c694c 100644 --- a/packages/core/src/modules/routing/messages/KeylistUpdateResponseMessage.ts +++ b/packages/core/src/modules/routing/messages/KeylistUpdateResponseMessage.ts @@ -1,6 +1,5 @@ import { Expose, Type } from 'class-transformer' import { IsArray, IsEnum, IsInstance, IsString, ValidateNested } from 'class-validator' -import { Verkey } from 'indy-sdk' import { AgentMessage } from '../../../agent/AgentMessage' import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' @@ -15,7 +14,7 @@ export enum KeylistUpdateResult { } export class KeylistUpdated { - public constructor(options: { recipientKey: Verkey; action: KeylistUpdateAction; result: KeylistUpdateResult }) { + public constructor(options: { recipientKey: string; action: KeylistUpdateAction; result: KeylistUpdateResult }) { if (options) { this.recipientKey = options.recipientKey this.action = options.action @@ -25,7 +24,7 @@ export class KeylistUpdated { @IsString() @Expose({ name: 'recipient_key' }) - public recipientKey!: Verkey + public recipientKey!: string @IsEnum(KeylistUpdateAction) public action!: KeylistUpdateAction diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index ab90eeba66..b62db55446 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -2,11 +2,12 @@ import type { AgentContext } from '../../../agent' import type { AgentMessage } from '../../../agent/AgentMessage' import type { AgentMessageReceivedEvent } from '../../../agent/Events' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { Query } from '../../../storage/StorageService' import type { EncryptedMessage } from '../../../types' import type { ConnectionRecord } from '../../connections' import type { Routing } from '../../connections/services/ConnectionService' import type { MediationStateChangedEvent, KeylistUpdatedEvent } from '../RoutingEvents' -import type { KeylistUpdateResponseMessage, MediationDenyMessage, MediationGrantMessage } from '../messages' +import type { MediationDenyMessage } from '../messages' import type { StatusMessage, MessageDeliveryMessage } from '../protocol' import type { GetRoutingOptions } from './RoutingService' @@ -21,13 +22,20 @@ import { Key, KeyType } from '../../../crypto' import { AriesFrameworkError } from '../../../error' import { injectable } from '../../../plugins' import { JsonTransformer } from '../../../utils' +import { ConnectionType } from '../../connections/models/ConnectionType' +import { ConnectionMetadataKeys } from '../../connections/repository/ConnectionMetadataTypes' import { ConnectionService } from '../../connections/services/ConnectionService' -import { didKeyToVerkey } from '../../dids/helpers' +import { didKeyToVerkey, isDidKey, verkeyToDidKey } from '../../dids/helpers' import { ProblemReportError } from '../../problem-reports' import { RecipientModuleConfig } from '../RecipientModuleConfig' import { RoutingEventTypes } from '../RoutingEvents' import { RoutingProblemReportReason } from '../error' -import { KeylistUpdateAction, MediationRequestMessage } from '../messages' +import { + KeylistUpdateAction, + KeylistUpdateResponseMessage, + MediationRequestMessage, + MediationGrantMessage, +} from '../messages' import { KeylistUpdate, KeylistUpdateMessage } from '../messages/KeylistUpdateMessage' import { MediationRole, MediationState } from '../models' import { DeliveryRequestMessage, MessagesReceivedMessage, StatusRequestMessage } from '../protocol/pickup/v2/messages' @@ -85,6 +93,9 @@ export class MediationRecipientService { role: MediationRole.Recipient, connectionId: connection.id, }) + connection.setTag('connectionType', [ConnectionType.Mediator]) + await this.connectionService.update(agentContext, connection) + await this.mediationRepository.save(agentContext, mediationRecord) this.emitStateChangedEvent(agentContext, mediationRecord, null) @@ -105,6 +116,15 @@ export class MediationRecipientService { // Update record mediationRecord.endpoint = messageContext.message.endpoint + // Update connection metadata to use their key format in further protocol messages + const connectionUsesDidKey = messageContext.message.routingKeys.some(isDidKey) + await this.updateUseDidKeysFlag( + messageContext.agentContext, + connection, + MediationGrantMessage.type.protocolUri, + connectionUsesDidKey + ) + // According to RFC 0211 keys should be a did key, but base58 encoded verkey was used before // RFC was accepted. This converts the key to a public key base58 if it is a did key. mediationRecord.routingKeys = messageContext.message.routingKeys.map(didKeyToVerkey) @@ -123,12 +143,21 @@ export class MediationRecipientService { const keylist = messageContext.message.updated + // Update connection metadata to use their key format in further protocol messages + const connectionUsesDidKey = keylist.some((key) => isDidKey(key.recipientKey)) + await this.updateUseDidKeysFlag( + messageContext.agentContext, + connection, + KeylistUpdateResponseMessage.type.protocolUri, + connectionUsesDidKey + ) + // update keylist in mediationRecord for (const update of keylist) { if (update.action === KeylistUpdateAction.add) { - mediationRecord.addRecipientKey(update.recipientKey) + mediationRecord.addRecipientKey(didKeyToVerkey(update.recipientKey)) } else if (update.action === KeylistUpdateAction.remove) { - mediationRecord.removeRecipientKey(update.recipientKey) + mediationRecord.removeRecipientKey(didKeyToVerkey(update.recipientKey)) } } @@ -148,9 +177,18 @@ export class MediationRecipientService { verKey: string, timeoutMs = 15000 // TODO: this should be a configurable value in agent config ): Promise { - const message = this.createKeylistUpdateMessage(verKey) const connection = await this.connectionService.getById(agentContext, mediationRecord.connectionId) + // Use our useDidKey configuration unless we know the key formatting other party is using + let useDidKey = agentContext.config.useDidKeyInProtocols + + const useDidKeysConnectionMetadata = connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol) + if (useDidKeysConnectionMetadata) { + useDidKey = useDidKeysConnectionMetadata[KeylistUpdateMessage.type.protocolUri] ?? useDidKey + } + + const message = this.createKeylistUpdateMessage(useDidKey ? verkeyToDidKey(verKey) : verKey) + mediationRecord.assertReady() mediationRecord.assertRole(MediationRole.Recipient) @@ -359,6 +397,13 @@ export class MediationRecipientService { return this.mediationRepository.getAll(agentContext) } + public async findAllMediatorsByQuery( + agentContext: AgentContext, + query: Query + ): Promise { + return await this.mediationRepository.findByQuery(agentContext, query) + } + public async findDefaultMediator(agentContext: AgentContext): Promise { return this.mediationRepository.findSingleByQuery(agentContext, { default: true }) } @@ -405,6 +450,18 @@ export class MediationRecipientService { await this.mediationRepository.update(agentContext, mediationRecord) } } + + private async updateUseDidKeysFlag( + agentContext: AgentContext, + connection: ConnectionRecord, + protocolUri: string, + connectionUsesDidKey: boolean + ) { + const useDidKeysForProtocol = connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol) ?? {} + useDidKeysForProtocol[protocolUri] = connectionUsesDidKey + connection.metadata.set(ConnectionMetadataKeys.UseDidKeysForProtocol, useDidKeysForProtocol) + await this.connectionService.update(agentContext, connection) + } } export interface MediationProtocolMsgReturnType { diff --git a/packages/core/src/modules/routing/services/MediatorService.ts b/packages/core/src/modules/routing/services/MediatorService.ts index ed0d3a5485..af0c5cdb5f 100644 --- a/packages/core/src/modules/routing/services/MediatorService.ts +++ b/packages/core/src/modules/routing/services/MediatorService.ts @@ -1,8 +1,10 @@ import type { AgentContext } from '../../../agent' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { Query } from '../../../storage/StorageService' import type { EncryptedMessage } from '../../../types' +import type { ConnectionRecord } from '../../connections' import type { MediationStateChangedEvent } from '../RoutingEvents' -import type { ForwardMessage, KeylistUpdateMessage, MediationRequestMessage } from '../messages' +import type { ForwardMessage, MediationRequestMessage } from '../messages' import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' @@ -10,9 +12,12 @@ import { AriesFrameworkError } from '../../../error' import { Logger } from '../../../logger' import { injectable, inject } from '../../../plugins' import { JsonTransformer } from '../../../utils/JsonTransformer' -import { didKeyToVerkey } from '../../dids/helpers' +import { ConnectionService } from '../../connections' +import { ConnectionMetadataKeys } from '../../connections/repository/ConnectionMetadataTypes' +import { didKeyToVerkey, isDidKey, verkeyToDidKey } from '../../dids/helpers' import { RoutingEventTypes } from '../RoutingEvents' import { + KeylistUpdateMessage, KeylistUpdateAction, KeylistUpdated, KeylistUpdateResponseMessage, @@ -32,17 +37,21 @@ export class MediatorService { private mediationRepository: MediationRepository private mediatorRoutingRepository: MediatorRoutingRepository private eventEmitter: EventEmitter + private connectionService: ConnectionService + private _mediatorRoutingRecord?: MediatorRoutingRecord public constructor( mediationRepository: MediationRepository, mediatorRoutingRepository: MediatorRoutingRepository, eventEmitter: EventEmitter, - @inject(InjectionSymbols.Logger) logger: Logger + @inject(InjectionSymbols.Logger) logger: Logger, + connectionService: ConnectionService ) { this.mediationRepository = mediationRepository this.mediatorRoutingRepository = mediatorRoutingRepository this.eventEmitter = eventEmitter this.logger = logger + this.connectionService = connectionService } private async getRoutingKeys(agentContext: AgentContext) { @@ -93,6 +102,15 @@ export class MediatorService { mediationRecord.assertReady() mediationRecord.assertRole(MediationRole.Mediator) + // Update connection metadata to use their key format in further protocol messages + const connectionUsesDidKey = message.updates.some((update) => isDidKey(update.recipientKey)) + await this.updateUseDidKeysFlag( + messageContext.agentContext, + connection, + KeylistUpdateMessage.type.protocolUri, + connectionUsesDidKey + ) + for (const update of message.updates) { const updated = new KeylistUpdated({ action: update.action, @@ -128,9 +146,14 @@ export class MediatorService { await this.updateState(agentContext, mediationRecord, MediationState.Granted) + // Use our useDidKey configuration, as this is the first interaction for this protocol + const useDidKey = agentContext.config.useDidKeyInProtocols + const message = new MediationGrantMessage({ endpoint: agentContext.config.endpoints[0], - routingKeys: await this.getRoutingKeys(agentContext), + routingKeys: useDidKey + ? (await this.getRoutingKeys(agentContext)).map(verkeyToDidKey) + : await this.getRoutingKeys(agentContext), threadId: mediationRecord.threadId, }) @@ -188,6 +211,10 @@ export class MediatorService { return routingRecord } + public async findAllByQuery(agentContext: AgentContext, query: Query): Promise { + return await this.mediationRepository.findByQuery(agentContext, query) + } + private async updateState(agentContext: AgentContext, mediationRecord: MediationRecord, newState: MediationState) { const previousState = mediationRecord.state @@ -212,4 +239,16 @@ export class MediatorService { }, }) } + + private async updateUseDidKeysFlag( + agentContext: AgentContext, + connection: ConnectionRecord, + protocolUri: string, + connectionUsesDidKey: boolean + ) { + const useDidKeysForProtocol = connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol) ?? {} + useDidKeysForProtocol[protocolUri] = connectionUsesDidKey + connection.metadata.set(ConnectionMetadataKeys.UseDidKeysForProtocol, useDidKeysForProtocol) + await this.connectionService.update(agentContext, connection) + } } diff --git a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts index fe7e16a128..25dd20538b 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts @@ -14,12 +14,19 @@ import { AriesFrameworkError } from '../../../../error' import { uuid } from '../../../../utils/uuid' import { IndyWallet } from '../../../../wallet/IndyWallet' import { DidExchangeState } from '../../../connections' +import { ConnectionMetadataKeys } from '../../../connections/repository/ConnectionMetadataTypes' import { ConnectionRepository } from '../../../connections/repository/ConnectionRepository' import { ConnectionService } from '../../../connections/services/ConnectionService' import { DidRepository } from '../../../dids/repository/DidRepository' import { DidRegistrarService } from '../../../dids/services/DidRegistrarService' import { RecipientModuleConfig } from '../../RecipientModuleConfig' -import { MediationGrantMessage } from '../../messages' +import { RoutingEventTypes } from '../../RoutingEvents' +import { + KeylistUpdateAction, + KeylistUpdateResponseMessage, + KeylistUpdateResult, + MediationGrantMessage, +} from '../../messages' import { MediationRole, MediationState } from '../../models' import { DeliveryRequestMessage, MessageDeliveryMessage, MessagesReceivedMessage, StatusMessage } from '../../protocol' import { MediationRecord } from '../../repository/MediationRecord' @@ -123,10 +130,17 @@ describe('MediationRecipientService', () => { threadId: 'threadId', }) - const messageContext = new InboundMessageContext(mediationGrant, { connection: mockConnection, agentContext }) + const connection = getMockConnection({ + state: DidExchangeState.Completed, + }) + + const messageContext = new InboundMessageContext(mediationGrant, { connection, agentContext }) await mediationRecipientService.processMediationGrant(messageContext) + expect(connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol)).toEqual({ + 'https://didcomm.org/coordinate-mediation/1.0': false, + }) expect(mediationRecord.routingKeys).toEqual(['79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ']) }) @@ -138,10 +152,17 @@ describe('MediationRecipientService', () => { threadId: 'threadId', }) - const messageContext = new InboundMessageContext(mediationGrant, { connection: mockConnection, agentContext }) + const connection = getMockConnection({ + state: DidExchangeState.Completed, + }) + + const messageContext = new InboundMessageContext(mediationGrant, { connection, agentContext }) await mediationRecipientService.processMediationGrant(messageContext) + expect(connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol)).toEqual({ + 'https://didcomm.org/coordinate-mediation/1.0': true, + }) expect(mediationRecord.routingKeys).toEqual(['8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K']) }) }) @@ -157,6 +178,63 @@ describe('MediationRecipientService', () => { recipientKey: 'a-key', }) }) + + it('it throws an error when the mediation record has incorrect role or state', async () => { + mediationRecord.role = MediationRole.Mediator + await expect(mediationRecipientService.createStatusRequest(mediationRecord)).rejects.toThrowError( + 'Mediation record has invalid role MEDIATOR. Expected role RECIPIENT.' + ) + + mediationRecord.role = MediationRole.Recipient + mediationRecord.state = MediationState.Requested + + await expect(mediationRecipientService.createStatusRequest(mediationRecord)).rejects.toThrowError( + 'Mediation record is not ready to be used. Expected granted, found invalid state requested' + ) + }) + }) + + describe('processKeylistUpdateResults', () => { + it('it stores did:key-encoded keys in base58 format', async () => { + const spyAddRecipientKey = jest.spyOn(mediationRecord, 'addRecipientKey') + + const connection = getMockConnection({ + state: DidExchangeState.Completed, + }) + + const keylist = [ + { + result: KeylistUpdateResult.Success, + recipientKey: 'did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th', + action: KeylistUpdateAction.add, + }, + ] + + const keyListUpdateResponse = new KeylistUpdateResponseMessage({ + threadId: uuid(), + keylist, + }) + + const messageContext = new InboundMessageContext(keyListUpdateResponse, { connection, agentContext }) + + expect(connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol)).toBeNull() + + await mediationRecipientService.processKeylistUpdateResults(messageContext) + + expect(connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol)).toEqual({ + 'https://didcomm.org/coordinate-mediation/1.0': true, + }) + + expect(eventEmitter.emit).toHaveBeenCalledWith(agentContext, { + type: RoutingEventTypes.RecipientKeylistUpdated, + payload: { + mediationRecord, + keylist, + }, + }) + expect(spyAddRecipientKey).toHaveBeenCalledWith('8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K') + spyAddRecipientKey.mockClear() + }) }) describe('processStatus', () => { diff --git a/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts index 2d963ec106..531f4fe608 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts @@ -3,45 +3,71 @@ import { Subject } from 'rxjs' import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../tests/helpers' import { EventEmitter } from '../../../../agent/EventEmitter' import { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' -import { IndyWallet } from '../../../../wallet/IndyWallet' -import { DidExchangeState } from '../../../connections' -import { KeylistUpdateAction, KeylistUpdateMessage } from '../../messages' +import { ConnectionService, DidExchangeState } from '../../../connections' +import { isDidKey } from '../../../dids/helpers' +import { KeylistUpdateAction, KeylistUpdateMessage, KeylistUpdateResult } from '../../messages' import { MediationRole, MediationState } from '../../models' -import { MediationRecord } from '../../repository' +import { MediationRecord, MediatorRoutingRecord } from '../../repository' import { MediationRepository } from '../../repository/MediationRepository' import { MediatorRoutingRepository } from '../../repository/MediatorRoutingRepository' import { MediatorService } from '../MediatorService' -const agentConfig = getAgentConfig('MediatorService') - jest.mock('../../repository/MediationRepository') const MediationRepositoryMock = MediationRepository as jest.Mock jest.mock('../../repository/MediatorRoutingRepository') const MediatorRoutingRepositoryMock = MediatorRoutingRepository as jest.Mock -jest.mock('../../../../wallet/IndyWallet') -const WalletMock = IndyWallet as jest.Mock - -const agentContext = getAgentContext({ - wallet: new WalletMock(), -}) +jest.mock('../../../connections/services/ConnectionService') +const ConnectionServiceMock = ConnectionService as jest.Mock const mediationRepository = new MediationRepositoryMock() const mediatorRoutingRepository = new MediatorRoutingRepositoryMock() - -const mediatorService = new MediatorService( - mediationRepository, - mediatorRoutingRepository, - new EventEmitter(agentConfig.agentDependencies, new Subject()), - agentConfig.logger -) +const connectionService = new ConnectionServiceMock() const mockConnection = getMockConnection({ state: DidExchangeState.Completed, }) -describe('MediatorService', () => { +describe('MediatorService - default config', () => { + const agentConfig = getAgentConfig('MediatorService') + + const agentContext = getAgentContext({ + agentConfig, + }) + + const mediatorService = new MediatorService( + mediationRepository, + mediatorRoutingRepository, + new EventEmitter(agentConfig.agentDependencies, new Subject()), + agentConfig.logger, + connectionService + ) + + describe('createGrantMediationMessage', () => { + test('sends base58 encoded recipient keys by default', async () => { + const mediationRecord = new MediationRecord({ + connectionId: 'connectionId', + role: MediationRole.Mediator, + state: MediationState.Requested, + threadId: 'threadId', + }) + + mockFunction(mediationRepository.getByConnectionId).mockResolvedValue(mediationRecord) + + mockFunction(mediatorRoutingRepository.findById).mockResolvedValue( + new MediatorRoutingRecord({ + routingKeys: ['8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K'], + }) + ) + + const { message } = await mediatorService.createGrantMediationMessage(agentContext, mediationRecord) + + expect(message.routingKeys.length).toBe(1) + expect(isDidKey(message.routingKeys[0])).toBeFalsy() + }) + }) + describe('processKeylistUpdateRequest', () => { test('processes base58 encoded recipient keys', async () => { const mediationRecord = new MediationRecord({ @@ -68,39 +94,103 @@ describe('MediatorService', () => { }) const messageContext = new InboundMessageContext(keyListUpdate, { connection: mockConnection, agentContext }) - await mediatorService.processKeylistUpdateRequest(messageContext) + const response = await mediatorService.processKeylistUpdateRequest(messageContext) expect(mediationRecord.recipientKeys).toEqual(['79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ']) + expect(response.updated).toEqual([ + { + action: KeylistUpdateAction.add, + recipientKey: '79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ', + result: KeylistUpdateResult.Success, + }, + { + action: KeylistUpdateAction.remove, + recipientKey: '8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K', + result: KeylistUpdateResult.Success, + }, + ]) + }) + }) + + test('processes did:key encoded recipient keys', async () => { + const mediationRecord = new MediationRecord({ + connectionId: 'connectionId', + role: MediationRole.Mediator, + state: MediationState.Granted, + threadId: 'threadId', + recipientKeys: ['8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K'], }) - test('processes did:key encoded recipient keys', async () => { + mockFunction(mediationRepository.getByConnectionId).mockResolvedValue(mediationRecord) + + const keyListUpdate = new KeylistUpdateMessage({ + updates: [ + { + action: KeylistUpdateAction.add, + recipientKey: 'did:key:z6MkkbTaLstV4fwr1rNf5CSxdS2rGbwxi3V5y6NnVFTZ2V1w', + }, + { + action: KeylistUpdateAction.remove, + recipientKey: 'did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th', + }, + ], + }) + + const messageContext = new InboundMessageContext(keyListUpdate, { connection: mockConnection, agentContext }) + const response = await mediatorService.processKeylistUpdateRequest(messageContext) + + expect(mediationRecord.recipientKeys).toEqual(['79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ']) + expect(response.updated).toEqual([ + { + action: KeylistUpdateAction.add, + recipientKey: 'did:key:z6MkkbTaLstV4fwr1rNf5CSxdS2rGbwxi3V5y6NnVFTZ2V1w', + result: KeylistUpdateResult.Success, + }, + { + action: KeylistUpdateAction.remove, + recipientKey: 'did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th', + result: KeylistUpdateResult.Success, + }, + ]) + }) +}) + +describe('MediatorService - useDidKeyInProtocols set to true', () => { + const agentConfig = getAgentConfig('MediatorService', { useDidKeyInProtocols: true }) + + const agentContext = getAgentContext({ + agentConfig, + }) + + const mediatorService = new MediatorService( + mediationRepository, + mediatorRoutingRepository, + new EventEmitter(agentConfig.agentDependencies, new Subject()), + agentConfig.logger, + connectionService + ) + + describe('createGrantMediationMessage', () => { + test('sends did:key encoded recipient keys when config is set', async () => { const mediationRecord = new MediationRecord({ connectionId: 'connectionId', role: MediationRole.Mediator, - state: MediationState.Granted, + state: MediationState.Requested, threadId: 'threadId', - recipientKeys: ['8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K'], }) mockFunction(mediationRepository.getByConnectionId).mockResolvedValue(mediationRecord) - const keyListUpdate = new KeylistUpdateMessage({ - updates: [ - { - action: KeylistUpdateAction.add, - recipientKey: 'did:key:z6MkkbTaLstV4fwr1rNf5CSxdS2rGbwxi3V5y6NnVFTZ2V1w', - }, - { - action: KeylistUpdateAction.remove, - recipientKey: 'did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th', - }, - ], + const routingRecord = new MediatorRoutingRecord({ + routingKeys: ['8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K'], }) - const messageContext = new InboundMessageContext(keyListUpdate, { connection: mockConnection, agentContext }) - await mediatorService.processKeylistUpdateRequest(messageContext) + mockFunction(mediatorRoutingRepository.findById).mockResolvedValue(routingRecord) - expect(mediationRecord.recipientKeys).toEqual(['79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ']) + const { message } = await mediatorService.createGrantMediationMessage(agentContext, mediationRecord) + + expect(message.routingKeys.length).toBe(1) + expect(isDidKey(message.routingKeys[0])).toBeTruthy() }) }) }) diff --git a/packages/core/src/storage/StorageService.ts b/packages/core/src/storage/StorageService.ts index 790a470aa2..6ea701df56 100644 --- a/packages/core/src/storage/StorageService.ts +++ b/packages/core/src/storage/StorageService.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { AgentContext } from '../agent' import type { Constructor } from '../utils/mixins' import type { BaseRecord, TagsBase } from './BaseRecord' @@ -11,13 +12,12 @@ interface AdvancedQuery { $not?: Query } -export type Query = AdvancedQuery | SimpleQuery +export type Query> = AdvancedQuery | SimpleQuery export interface BaseRecordConstructor extends Constructor { type: string } -// eslint-disable-next-line @typescript-eslint/no-explicit-any export interface StorageService> { /** * Save record in storage diff --git a/packages/core/src/storage/migration/__tests__/__fixtures__/alice-8-connections-0.1.json b/packages/core/src/storage/migration/__tests__/__fixtures__/alice-8-connections-0.1.json index ea749feede..ece0f42270 100644 --- a/packages/core/src/storage/migration/__tests__/__fixtures__/alice-8-connections-0.1.json +++ b/packages/core/src/storage/migration/__tests__/__fixtures__/alice-8-connections-0.1.json @@ -66,6 +66,7 @@ "theirLabel": "Agent: PopulateWallet2", "state": "complete", "role": "invitee", + "alias": "connection alias", "invitation": { "@type": "https://didcomm.org/connections/1.0/invitation", "@id": "d56fd7af-852e-458e-b750-7a4f4e53d6e6", diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap index 927b02b255..d71efef325 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap @@ -782,7 +782,12 @@ Object { }, "type": "OutOfBandRecord", "value": Object { - "_tags": Object {}, + "_tags": Object { + "recipientKeyFingerprints": Array [ + "z6MkfiPMPxCQeSDZGMkCvm1Y2rBoPsmw4ZHMv71jXtcWRRiM", + ], + }, + "alias": "connection alias", "autoAcceptConnection": undefined, "createdAt": "2022-04-30T13:02:21.577Z", "id": "1-4e4f-41d9-94c4-f49351b811f1", @@ -841,7 +846,12 @@ Object { }, "type": "OutOfBandRecord", "value": Object { - "_tags": Object {}, + "_tags": Object { + "recipientKeyFingerprints": Array [ + "z6MktCZAQNGvWb4WHAjwBqPtXhZdDYorbSJkGW9vj1uhw1HD", + ], + }, + "alias": undefined, "autoAcceptConnection": undefined, "createdAt": "2022-04-30T13:02:21.608Z", "id": "2-4e4f-41d9-94c4-f49351b811f1", @@ -900,7 +910,12 @@ Object { }, "type": "OutOfBandRecord", "value": Object { - "_tags": Object {}, + "_tags": Object { + "recipientKeyFingerprints": Array [ + "z6Mkt1tsp15cnDD7wBCFgehiR2SxHX1aPxt4sueE24twH9Bd", + ], + }, + "alias": undefined, "autoAcceptConnection": false, "createdAt": "2022-04-30T13:02:21.628Z", "id": "3-4e4f-41d9-94c4-f49351b811f1", @@ -959,7 +974,12 @@ Object { }, "type": "OutOfBandRecord", "value": Object { - "_tags": Object {}, + "_tags": Object { + "recipientKeyFingerprints": Array [ + "z6Mkmod8vp2nURVktVC5ceQeyr2VUz26iu2ZANLNVg9pMawa", + ], + }, + "alias": undefined, "autoAcceptConnection": undefined, "createdAt": "2022-04-30T13:02:21.635Z", "id": "4-4e4f-41d9-94c4-f49351b811f1", @@ -1018,7 +1038,12 @@ Object { }, "type": "OutOfBandRecord", "value": Object { - "_tags": Object {}, + "_tags": Object { + "recipientKeyFingerprints": Array [ + "z6MkjDJL4X7YGoH6gjamhZR2NzowPZqtJfX5kPuNuWiVdjMr", + ], + }, + "alias": undefined, "autoAcceptConnection": false, "createdAt": "2022-04-30T13:02:21.641Z", "id": "5-4e4f-41d9-94c4-f49351b811f1", @@ -1077,6 +1102,7 @@ Object { }, "type": "OutOfBandRecord", "value": Object { + "alias": undefined, "autoAcceptConnection": true, "createdAt": "2022-04-30T13:02:21.646Z", "id": "6-4e4f-41d9-94c4-f49351b811f1", @@ -1135,7 +1161,12 @@ Object { }, "type": "OutOfBandRecord", "value": Object { - "_tags": Object {}, + "_tags": Object { + "recipientKeyFingerprints": Array [ + "z6MkuWTEmH1mUo6W96zSWyH612hFHowRzNEscPYBL2CCMyC2", + ], + }, + "alias": undefined, "autoAcceptConnection": true, "createdAt": "2022-04-30T13:02:21.653Z", "id": "7-4e4f-41d9-94c4-f49351b811f1", @@ -1230,6 +1261,7 @@ Object { }, "type": "ConnectionRecord", "value": Object { + "alias": "connection alias", "createdAt": "2022-04-30T13:02:21.577Z", "did": "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", "id": "8f4908ee-15ad-4058-9106-eda26eae735c", diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts index 520bf571aa..fe68a1d5a1 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts @@ -385,7 +385,7 @@ describe('0.1-0.2 | Connection', () => { expect(outOfBandRecord.toJSON()).toEqual({ id: expect.any(String), - _tags: {}, + _tags: { recipientKeyFingerprints: ['z6MksYU4MHtfmNhNm1uGMvANr9j4CBv2FymjiJtRgA36bSVH'] }, metadata: {}, // Checked below outOfBandInvitation: { diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts b/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts index 8166818ef3..ff88c5e156 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts @@ -12,6 +12,7 @@ import { DidExchangeRole, } from '../../../../modules/connections' import { convertToNewDidDocument } from '../../../../modules/connections/services/helpers' +import { DidKey } from '../../../../modules/dids' import { DidDocumentRole } from '../../../../modules/dids/domain/DidDocumentRole' import { DidRecord, DidRepository } from '../../../../modules/dids/repository' import { DidRecordMetadataKeys } from '../../../../modules/dids/repository/didRecordMetadataTypes' @@ -313,9 +314,15 @@ export async function migrateToOobRecord( const outOfBandInvitation = convertToNewInvitation(oldInvitation) // If both the recipientKeys and the @id match we assume the connection was created using the same invitation. + const recipientKeyFingerprints = outOfBandInvitation + .getInlineServices() + .map((s) => s.recipientKeys) + .reduce((acc, curr) => [...acc, ...curr], []) + .map((didKey) => DidKey.fromDid(didKey).key.fingerprint) + const oobRecords = await oobRepository.findByQuery(agent.context, { invitationId: oldInvitation.id, - recipientKeyFingerprints: outOfBandInvitation.getRecipientKeys().map((key) => key.fingerprint), + recipientKeyFingerprints, }) let oobRecord: OutOfBandRecord | undefined = oobRecords[0] @@ -333,11 +340,13 @@ export async function migrateToOobRecord( oobRecord = new OutOfBandRecord({ role: oobRole, state: oobState, + alias: connectionRecord.alias, autoAcceptConnection: connectionRecord.autoAcceptConnection, outOfBandInvitation, reusable: oldMultiUseInvitation, mediatorId: connectionRecord.mediatorId, createdAt: connectionRecord.createdAt, + tags: { recipientKeyFingerprints }, }) await oobRepository.save(agent.context, oobRecord) diff --git a/packages/core/src/transport/TransportEventTypes.ts b/packages/core/src/transport/TransportEventTypes.ts index b0777883e1..8916724e86 100644 --- a/packages/core/src/transport/TransportEventTypes.ts +++ b/packages/core/src/transport/TransportEventTypes.ts @@ -2,6 +2,7 @@ import type { BaseEvent } from '../agent/Events' export enum TransportEventTypes { OutboundWebSocketClosedEvent = 'OutboundWebSocketClosedEvent', + OutboundWebSocketOpenedEvent = 'OutboundWebSocketOpenedEvent', } export interface OutboundWebSocketClosedEvent extends BaseEvent { @@ -11,3 +12,11 @@ export interface OutboundWebSocketClosedEvent extends BaseEvent { connectionId?: string } } + +export interface OutboundWebSocketOpenedEvent extends BaseEvent { + type: TransportEventTypes.OutboundWebSocketOpenedEvent + payload: { + socketId: string + connectionId?: string + } +} diff --git a/packages/core/src/transport/WsOutboundTransport.ts b/packages/core/src/transport/WsOutboundTransport.ts index b0cc8e8d28..8e97107141 100644 --- a/packages/core/src/transport/WsOutboundTransport.ts +++ b/packages/core/src/transport/WsOutboundTransport.ts @@ -3,7 +3,7 @@ import type { AgentMessageReceivedEvent } from '../agent/Events' import type { Logger } from '../logger' import type { OutboundPackage } from '../types' import type { OutboundTransport } from './OutboundTransport' -import type { OutboundWebSocketClosedEvent } from './TransportEventTypes' +import type { OutboundWebSocketClosedEvent, OutboundWebSocketOpenedEvent } from './TransportEventTypes' import type WebSocket from 'ws' import { AgentEventTypes } from '../agent/Events' @@ -136,6 +136,14 @@ export class WsOutboundTransport implements OutboundTransport { socket.onopen = () => { this.logger.debug(`Successfully connected to WebSocket ${endpoint}`) resolve(socket) + + this.agent.events.emit(this.agent.context, { + type: TransportEventTypes.OutboundWebSocketOpenedEvent, + payload: { + socketId, + connectionId: connectionId, + }, + }) } socket.onerror = (error) => { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 587443ea2d..9e886472df 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,13 +1,14 @@ import type { AgentMessage } from './agent/AgentMessage' -import type { ResolvedDidCommService } from './agent/MessageSender' import type { Key } from './crypto' import type { Logger } from './logger' import type { ConnectionRecord } from './modules/connections' import type { AutoAcceptCredential } from './modules/credentials/models/CredentialAutoAcceptType' +import type { ResolvedDidCommService } from './modules/didcomm' import type { IndyPoolConfig } from './modules/ledger/IndyPool' import type { OutOfBandRecord } from './modules/oob/repository' import type { AutoAcceptProof } from './modules/proofs' import type { MediatorPickupStrategy } from './modules/routing' +import type { BaseRecord } from './storage/BaseRecord' export enum KeyDerivationMethod { /** default value in indy-sdk. Will be used when no value is provided */ @@ -26,6 +27,7 @@ export interface WalletConfig { type: string [key: string]: unknown } + masterSecretId?: string } export interface WalletConfigRekey { @@ -75,6 +77,9 @@ export interface InitConfig { mediatorPollingInterval?: number mediatorPickupStrategy?: MediatorPickupStrategy maximumMessagePickup?: number + baseMediatorReconnectionIntervalMs?: number + maximumMediatorReconnectionIntervalMs?: number + useDidKeyInProtocols?: boolean useLegacyDidSovPrefix?: boolean connectionImageUrl?: string @@ -94,6 +99,7 @@ export interface OutboundMessage { connection: ConnectionRecord sessionId?: string outOfBand?: OutOfBandRecord + associatedRecord?: BaseRecord } export interface OutboundServiceMessage { diff --git a/packages/core/src/utils/__tests__/shortenedUrl.test.ts b/packages/core/src/utils/__tests__/shortenedUrl.test.ts index a6e2364f97..5e79621e96 100644 --- a/packages/core/src/utils/__tests__/shortenedUrl.test.ts +++ b/packages/core/src/utils/__tests__/shortenedUrl.test.ts @@ -7,7 +7,7 @@ import { OutOfBandInvitation } from '../../modules/oob' import { convertToNewInvitation } from '../../modules/oob/helpers' import { JsonTransformer } from '../JsonTransformer' import { MessageValidator } from '../MessageValidator' -import { oobInvitationfromShortUrl } from '../parseInvitation' +import { oobInvitationFromShortUrl } from '../parseInvitation' const mockOobInvite = { '@type': 'did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/out-of-band/1.0/invitation', @@ -89,21 +89,32 @@ beforeAll(async () => { describe('shortened urls resolving to oob invitations', () => { test('Resolve a mocked response in the form of a oob invitation as a json object', async () => { - const short = await oobInvitationfromShortUrl(mockedResponseOobJson) + const short = await oobInvitationFromShortUrl(mockedResponseOobJson) expect(short).toEqual(outOfBandInvitationMock) }) test('Resolve a mocked response in the form of a oob invitation encoded in an url', async () => { - const short = await oobInvitationfromShortUrl(mockedResponseOobUrl) + const short = await oobInvitationFromShortUrl(mockedResponseOobUrl) + expect(short).toEqual(outOfBandInvitationMock) + }) + + test("Resolve a mocked response in the form of a oob invitation as a json object with header 'application/json; charset=utf-8'", async () => { + const short = await oobInvitationFromShortUrl({ + ...mockedResponseOobJson, + headers: new Headers({ + 'content-type': 'application/json; charset=utf-8', + }), + } as Response) expect(short).toEqual(outOfBandInvitationMock) }) }) + describe('shortened urls resolving to connection invitations', () => { test('Resolve a mocked response in the form of a connection invitation as a json object', async () => { - const short = await oobInvitationfromShortUrl(mockedResponseConnectionJson) + const short = await oobInvitationFromShortUrl(mockedResponseConnectionJson) expect(short).toEqual(connectionInvitationToNew) }) test('Resolve a mocked Response in the form of a connection invitation encoded in an url', async () => { - const short = await oobInvitationfromShortUrl(mockedResponseConnectionUrl) + const short = await oobInvitationFromShortUrl(mockedResponseConnectionUrl) expect(short).toEqual(connectionInvitationToNew) }) }) diff --git a/packages/core/src/utils/parseInvitation.ts b/packages/core/src/utils/parseInvitation.ts index 713360512e..6f3c9e8f3b 100644 --- a/packages/core/src/utils/parseInvitation.ts +++ b/packages/core/src/utils/parseInvitation.ts @@ -54,9 +54,9 @@ export const parseInvitationUrl = (invitationUrl: string): OutOfBandInvitation = } //This currently does not follow the RFC because of issues with fetch, currently uses a janky work around -export const oobInvitationfromShortUrl = async (response: Response): Promise => { +export const oobInvitationFromShortUrl = async (response: Response): Promise => { if (response) { - if (response.headers.get('Content-Type') === 'application/json' && response.ok) { + if (response.headers.get('Content-Type')?.startsWith('application/json') && response.ok) { const invitationJson = await response.json() const parsedMessageType = parseMessageType(invitationJson['@type']) if (supportsIncomingMessageType(parsedMessageType, OutOfBandInvitation.type)) { @@ -107,7 +107,7 @@ export const parseInvitationShortUrl = async ( return convertToNewInvitation(invitation) } else { try { - return oobInvitationfromShortUrl(await fetchShortUrl(invitationUrl, dependencies)) + return oobInvitationFromShortUrl(await fetchShortUrl(invitationUrl, dependencies)) } catch (error) { throw new AriesFrameworkError( 'InvitationUrl is invalid. It needs to contain one, and only one, of the following parameters: `oob`, `c_i` or `d_m`, or be valid shortened URL' diff --git a/packages/core/src/utils/validators.ts b/packages/core/src/utils/validators.ts index e81c5543bf..8e7240b5f2 100644 --- a/packages/core/src/utils/validators.ts +++ b/packages/core/src/utils/validators.ts @@ -9,12 +9,12 @@ export interface IsInstanceOrArrayOfInstancesValidationOptions extends Validatio } /** - * Checks if the value is an instance of the specified object. + * Checks if the value is a string or the specified instance */ export function IsStringOrInstance(targetType: Constructor, validationOptions?: ValidationOptions): PropertyDecorator { return ValidateBy( { - name: 'isStringOrVerificationMethod', + name: 'IsStringOrInstance', constraints: [targetType], validator: { validate: (value, args): boolean => isString(value) || isInstance(value, args?.constraints[0]), @@ -22,9 +22,7 @@ export function IsStringOrInstance(targetType: Constructor, validationOptions?: if (args?.constraints[0]) { return eachPrefix + `$property must be of type string or instance of ${args.constraints[0].name as string}` } else { - return ( - eachPrefix + `isStringOrVerificationMethod decorator expects and object as value, but got falsy value.` - ) + return eachPrefix + `IsStringOrInstance decorator expects an object as value, but got falsy value.` } }, validationOptions), }, diff --git a/packages/core/src/wallet/IndyWallet.test.ts b/packages/core/src/wallet/IndyWallet.test.ts index 04176f9b55..6ab30b6657 100644 --- a/packages/core/src/wallet/IndyWallet.test.ts +++ b/packages/core/src/wallet/IndyWallet.test.ts @@ -20,8 +20,17 @@ const walletConfig: WalletConfig = { keyDerivationMethod: KeyDerivationMethod.Raw, } +const walletConfigWithMasterSecretId: WalletConfig = { + id: 'Wallet: WalletTestWithMasterSecretId', + // generated using indy.generateWalletKey + key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', + keyDerivationMethod: KeyDerivationMethod.Raw, + masterSecretId: 'customMasterSecretId', +} + describe('IndyWallet', () => { let indyWallet: IndyWallet + const seed = 'sample-seed' const message = TypedArrayEncoder.fromString('sample-message') @@ -100,4 +109,25 @@ describe('IndyWallet', () => { }) await expect(indyWallet.verify({ key: ed25519Key, data: message, signature })).resolves.toStrictEqual(true) }) + + test('masterSecretId is equal to wallet ID by default', async () => { + expect(indyWallet.masterSecretId).toEqual(walletConfig.id) + }) +}) + +describe('IndyWallet with custom Master Secret Id', () => { + let indyWallet: IndyWallet + + beforeEach(async () => { + indyWallet = new IndyWallet(agentDependencies, testLogger, new SigningProviderRegistry([])) + await indyWallet.createAndOpen(walletConfigWithMasterSecretId) + }) + + afterEach(async () => { + await indyWallet.delete() + }) + + test('masterSecretId is set by config', async () => { + expect(indyWallet.masterSecretId).toEqual(walletConfigWithMasterSecretId.masterSecretId) + }) }) diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts index ccf614351d..b8c54a2f71 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/core/src/wallet/IndyWallet.ts @@ -78,13 +78,13 @@ export class IndyWallet implements Wallet { } public get masterSecretId() { - if (!this.isInitialized || !this.walletConfig?.id) { + if (!this.isInitialized || !(this.walletConfig?.id || this.walletConfig?.masterSecretId)) { throw new AriesFrameworkError( 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' ) } - return this.walletConfig.id + return this.walletConfig?.masterSecretId ?? this.walletConfig.id } /** @@ -155,7 +155,7 @@ export class IndyWallet implements Wallet { await this.open(walletConfig) // We need to open wallet before creating master secret because we need wallet handle here. - await this.createMasterSecret(this.handle, walletConfig.id) + await this.createMasterSecret(this.handle, this.masterSecretId) } catch (error) { // If an error ocurred while creating the master secret, we should close the wallet if (this.isInitialized) await this.close() diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 6378f64600..abb5c89fcf 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -302,7 +302,9 @@ export function getMockConnection({ export function getMockOutOfBand({ label, serviceEndpoint, - recipientKeys, + recipientKeys = [ + new DidKey(Key.fromPublicKeyBase58('ByHnpUCFb1vAfh9CFZ8ZkmUZguURW8nSw889hy6rD8L7', KeyType.Ed25519)).did, + ], mediatorId, role, state, @@ -330,9 +332,7 @@ export function getMockOutOfBand({ id: `#inline-0`, priority: 0, serviceEndpoint: serviceEndpoint ?? 'http://example.com', - recipientKeys: recipientKeys || [ - new DidKey(Key.fromPublicKeyBase58('ByHnpUCFb1vAfh9CFZ8ZkmUZguURW8nSw889hy6rD8L7', KeyType.Ed25519)).did, - ], + recipientKeys, routingKeys: [], }), ], @@ -345,6 +345,9 @@ export function getMockOutOfBand({ outOfBandInvitation: outOfBandInvitation, reusable, reuseConnectionId, + tags: { + recipientKeyFingerprints: recipientKeys.map((didKey) => DidKey.fromDid(didKey).key.fingerprint), + }, }) return outOfBandRecord } diff --git a/packages/core/tests/oob-mediation.test.ts b/packages/core/tests/oob-mediation.test.ts index 091c62b1ed..b45254f29b 100644 --- a/packages/core/tests/oob-mediation.test.ts +++ b/packages/core/tests/oob-mediation.test.ts @@ -7,6 +7,7 @@ import { SubjectInboundTransport } from '../../../tests/transport/SubjectInbound import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' import { Agent } from '../src/agent/Agent' import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' +import { ConnectionType } from '../src/modules/connections/models/ConnectionType' import { MediationState, MediatorPickupStrategy } from '../src/modules/routing' import { getAgentOptions, waitForBasicMessage } from './helpers' @@ -90,8 +91,16 @@ describe('out of band with mediation', () => { mediatorAliceConnection = await mediatorAgent.connections.returnWhenIsConnected(mediatorAliceConnection!.id) expect(mediatorAliceConnection.state).toBe(DidExchangeState.Completed) - // ========== Set meadiation between Alice and Mediator agents ========== + // ========== Set mediation between Alice and Mediator agents ========== const mediationRecord = await aliceAgent.mediationRecipient.requestAndAwaitGrant(aliceMediatorConnection) + const connectonTypes = await aliceAgent.connections.getConnectionTypes(mediationRecord.connectionId) + expect(connectonTypes).toContain(ConnectionType.Mediator) + await aliceAgent.connections.addConnectionType(mediationRecord.connectionId, 'test') + expect(await aliceAgent.connections.getConnectionTypes(mediationRecord.connectionId)).toContain('test') + await aliceAgent.connections.removeConnectionType(mediationRecord.connectionId, 'test') + expect(await aliceAgent.connections.getConnectionTypes(mediationRecord.connectionId)).toEqual([ + ConnectionType.Mediator, + ]) expect(mediationRecord.state).toBe(MediationState.Granted) await aliceAgent.mediationRecipient.setDefaultMediator(mediationRecord) diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index 9172496c09..7a454bf2a4 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -42,6 +42,7 @@ describe('out of band', () => { goal: 'To make a connection', goalCode: 'p2p-messaging', label: 'Faber College', + alias: `Faber's connection with Alice`, } const issueCredentialConfig = { @@ -158,10 +159,11 @@ describe('out of band', () => { expect(outOfBandRecord.autoAcceptConnection).toBe(true) expect(outOfBandRecord.role).toBe(OutOfBandRole.Sender) expect(outOfBandRecord.state).toBe(OutOfBandState.AwaitResponse) + expect(outOfBandRecord.alias).toBe(makeConnectionConfig.alias) expect(outOfBandRecord.reusable).toBe(false) - expect(outOfBandRecord.outOfBandInvitation.goal).toBe('To make a connection') - expect(outOfBandRecord.outOfBandInvitation.goalCode).toBe('p2p-messaging') - expect(outOfBandRecord.outOfBandInvitation.label).toBe('Faber College') + expect(outOfBandRecord.outOfBandInvitation.goal).toBe(makeConnectionConfig.goal) + expect(outOfBandRecord.outOfBandInvitation.goalCode).toBe(makeConnectionConfig.goalCode) + expect(outOfBandRecord.outOfBandInvitation.label).toBe(makeConnectionConfig.label) }) test('create OOB message only with handshake', async () => { @@ -172,7 +174,7 @@ describe('out of band', () => { expect(outOfBandInvitation.getRequests()).toBeUndefined() // expect contains services - const [service] = outOfBandInvitation.services as OutOfBandDidCommService[] + const [service] = outOfBandInvitation.getInlineServices() expect(service).toMatchObject( new OutOfBandDidCommService({ id: expect.any(String), @@ -196,7 +198,7 @@ describe('out of band', () => { expect(outOfBandInvitation.getRequests()).toHaveLength(1) // expect contains services - const [service] = outOfBandInvitation.services + const [service] = outOfBandInvitation.getServices() expect(service).toMatchObject( new OutOfBandDidCommService({ id: expect.any(String), @@ -220,7 +222,7 @@ describe('out of band', () => { expect(outOfBandInvitation.getRequests()).toHaveLength(1) // expect contains services - const [service] = outOfBandInvitation.services as OutOfBandDidCommService[] + const [service] = outOfBandInvitation.getInlineServices() expect(service).toMatchObject( new OutOfBandDidCommService({ id: expect.any(String), @@ -293,6 +295,7 @@ describe('out of band', () => { expect(aliceFaberConnection).toBeConnectedWith(faberAliceConnection!) expect(faberAliceConnection).toBeConnectedWith(aliceFaberConnection) + expect(faberAliceConnection.alias).toBe(makeConnectionConfig.alias) }) test(`make a connection with ${HandshakeProtocol.Connections} based on OOB invitation encoded in URL`, async () => { @@ -314,6 +317,7 @@ describe('out of band', () => { expect(aliceFaberConnection).toBeConnectedWith(faberAliceConnection) expect(faberAliceConnection).toBeConnectedWith(aliceFaberConnection) + expect(faberAliceConnection.alias).toBe(makeConnectionConfig.alias) }) test('make a connection based on old connection invitation encoded in URL', async () => { @@ -470,8 +474,8 @@ describe('out of band', () => { const outOfBandRecord2 = await faberAgent.oob.createInvitation(makeConnectionConfig) // Take over the recipientKeys from the first invitation so they match when encoded - const firstInvitationService = outOfBandRecord.outOfBandInvitation.services[0] as OutOfBandDidCommService - const secondInvitationService = outOfBandRecord2.outOfBandInvitation.services[0] as OutOfBandDidCommService + const [firstInvitationService] = outOfBandRecord.outOfBandInvitation.getInlineServices() + const [secondInvitationService] = outOfBandRecord2.outOfBandInvitation.getInlineServices() secondInvitationService.recipientKeys = firstInvitationService.recipientKeys aliceAgent.events.on(OutOfBandEventTypes.HandshakeReused, aliceReuseListener) @@ -689,19 +693,6 @@ describe('out of band', () => { new AriesFrameworkError('There is no message in requests~attach supported by agent.') ) }) - - test('throw an error when a did is used in the out of band message', async () => { - const { message } = await faberAgent.credentials.createOffer(credentialTemplate) - const { outOfBandInvitation } = await faberAgent.oob.createInvitation({ - ...issueCredentialConfig, - messages: [message], - }) - outOfBandInvitation.services = ['somedid'] - - await expect(aliceAgent.oob.receiveInvitation(outOfBandInvitation, receiveInvitationConfig)).rejects.toEqual( - new AriesFrameworkError('Dids are not currently supported in out-of-band invitation services attribute.') - ) - }) }) describe('createLegacyConnectionlessInvitation', () => { diff --git a/packages/core/tests/v1-connectionless-proofs.test.ts b/packages/core/tests/v1-connectionless-proofs.test.ts index 447304229f..1e2c7eb187 100644 --- a/packages/core/tests/v1-connectionless-proofs.test.ts +++ b/packages/core/tests/v1-connectionless-proofs.test.ts @@ -8,7 +8,6 @@ import { Subject, ReplaySubject } from 'rxjs' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { InjectionSymbols } from '../src' import { Agent } from '../src/agent/Agent' import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment' import { HandshakeProtocol } from '../src/modules/connections' @@ -25,7 +24,6 @@ import { } from '../src/modules/proofs' import { MediatorPickupStrategy } from '../src/modules/routing' import { LinkedAttachment } from '../src/utils/LinkedAttachment' -import { sleep } from '../src/utils/sleep' import { uuid } from '../src/utils/uuid' import { @@ -398,13 +396,7 @@ describe('Present Proof', () => { await faberProofRecordPromise - // We want to stop the mediator polling before the agent is shutdown. - // FIXME: add a way to stop mediator polling from the public api, and make sure this is - // being handled in the agent shutdown so we don't get any errors with wallets being closed. - const faberStop$ = faberAgent.injectionContainer.resolve>(InjectionSymbols.Stop$) - const aliceStop$ = aliceAgent.injectionContainer.resolve>(InjectionSymbols.Stop$) - faberStop$.next(true) - aliceStop$.next(true) - await sleep(2000) + await aliceAgent.mediationRecipient.stopMessagePickup() + await faberAgent.mediationRecipient.stopMessagePickup() }) }) diff --git a/packages/module-bbs/package.json b/packages/module-bbs/package.json index a80985b584..bed4a703c8 100644 --- a/packages/module-bbs/package.json +++ b/packages/module-bbs/package.json @@ -30,10 +30,10 @@ "@stablelib/random": "^1.0.2" }, "peerDependencies": { - "@aries-framework/core": "0.2.2" + "@aries-framework/core": "0.2.4" }, "devDependencies": { - "@aries-framework/node": "0.2.2", + "@aries-framework/node": "0.2.4", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/module-tenants/package.json b/packages/module-tenants/package.json index 0a26b432f7..dacfdc4142 100644 --- a/packages/module-tenants/package.json +++ b/packages/module-tenants/package.json @@ -24,11 +24,11 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.2", + "@aries-framework/core": "0.2.4", "async-mutex": "^0.3.2" }, "devDependencies": { - "@aries-framework/node": "0.2.2", + "@aries-framework/node": "0.2.4", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/module-tenants/src/TenantsModule.ts b/packages/module-tenants/src/TenantsModule.ts index c96dbf92db..c4948dd4e6 100644 --- a/packages/module-tenants/src/TenantsModule.ts +++ b/packages/module-tenants/src/TenantsModule.ts @@ -1,5 +1,5 @@ import type { TenantsModuleConfigOptions } from './TenantsModuleConfig' -import type { ModulesMap, DependencyManager, Module, EmptyModuleMap, Constructor } from '@aries-framework/core' +import type { Constructor, ModulesMap, DependencyManager, Module, EmptyModuleMap } from '@aries-framework/core' import { InjectionSymbols } from '@aries-framework/core' diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index 2f19ede363..1a01719007 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) + +**Note:** Version bump only for package @aries-framework/node + +## [0.2.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.2...v0.2.3) (2022-08-30) + +**Note:** Version bump only for package @aries-framework/node + ## [0.2.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.1...v0.2.2) (2022-07-15) **Note:** Version bump only for package @aries-framework/node diff --git a/packages/node/package.json b/packages/node/package.json index d7cb789c2c..e4faa84365 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/node", "main": "build/index", "types": "build/index", - "version": "0.2.2", + "version": "0.2.4", "files": [ "build", "bin" @@ -28,7 +28,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.2", + "@aries-framework/core": "0.2.4", "express": "^4.17.1", "ffi-napi": "^4.0.3", "indy-sdk": "^1.16.0-dev-1636", diff --git a/packages/react-native/CHANGELOG.md b/packages/react-native/CHANGELOG.md index 5035d17fab..d693eee5df 100644 --- a/packages/react-native/CHANGELOG.md +++ b/packages/react-native/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) + +**Note:** Version bump only for package @aries-framework/react-native + +## [0.2.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.2...v0.2.3) (2022-08-30) + +**Note:** Version bump only for package @aries-framework/react-native + ## [0.2.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.1...v0.2.2) (2022-07-15) **Note:** Version bump only for package @aries-framework/react-native diff --git a/packages/react-native/package.json b/packages/react-native/package.json index e350c12d6d..bd8067a326 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/react-native", "main": "build/index", "types": "build/index", - "version": "0.2.2", + "version": "0.2.4", "files": [ "build" ], @@ -24,7 +24,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.2", + "@aries-framework/core": "0.2.4", "@azure/core-asynciterator-polyfill": "^1.0.0", "events": "^3.3.0" }, diff --git a/samples/extension-module/dummy/DummyApi.ts b/samples/extension-module/dummy/DummyApi.ts index 82ac700f7b..eeb50b469b 100644 --- a/samples/extension-module/dummy/DummyApi.ts +++ b/samples/extension-module/dummy/DummyApi.ts @@ -1,4 +1,5 @@ import type { DummyRecord } from './repository/DummyRecord' +import type { Query } from '@aries-framework/core' import { AgentContext, ConnectionService, Dispatcher, injectable, MessageSender } from '@aries-framework/core' @@ -73,6 +74,15 @@ export class DummyApi { return this.dummyService.getAll(this.agentContext) } + /** + * Retrieve all dummy records + * + * @returns List containing all records + */ + public findAllByQuery(query: Query): Promise { + return this.dummyService.findAllByQuery(this.agentContext, query) + } + private registerHandlers(dispatcher: Dispatcher) { dispatcher.registerHandler(new DummyRequestHandler(this.dummyService)) dispatcher.registerHandler(new DummyResponseHandler(this.dummyService)) diff --git a/samples/extension-module/dummy/services/DummyService.ts b/samples/extension-module/dummy/services/DummyService.ts index 2defd9d393..22b99fc399 100644 --- a/samples/extension-module/dummy/services/DummyService.ts +++ b/samples/extension-module/dummy/services/DummyService.ts @@ -1,5 +1,5 @@ import type { DummyStateChangedEvent } from './DummyEvents' -import type { AgentContext, ConnectionRecord, InboundMessageContext } from '@aries-framework/core' +import type { Query, AgentContext, ConnectionRecord, InboundMessageContext } from '@aries-framework/core' import { injectable, JsonTransformer, EventEmitter } from '@aries-framework/core' @@ -119,6 +119,15 @@ export class DummyService { return this.dummyRepository.getAll(agentContext) } + /** + * Retrieve dummy records by query + * + * @returns List containing all dummy records matching query + */ + public findAllByQuery(agentContext: AgentContext, query: Query): Promise { + return this.dummyRepository.findByQuery(agentContext, query) + } + /** * Retrieve a dummy record by id * diff --git a/tests/e2e-test.ts b/tests/e2e-test.ts index 7c42b6a13d..86fb6dfe83 100644 --- a/tests/e2e-test.ts +++ b/tests/e2e-test.ts @@ -1,11 +1,9 @@ import type { Agent } from '@aries-framework/core' -import type { Subject } from 'rxjs' import { sleep } from '../packages/core/src/utils/sleep' import { issueCredential, makeConnection, prepareForIssuance, presentProof } from '../packages/core/tests/helpers' import { - InjectionSymbols, V1CredentialPreview, AttributeFilter, CredentialState, @@ -95,9 +93,6 @@ export async function e2eTest({ expect(verifierProof.state).toBe(ProofState.Done) // We want to stop the mediator polling before the agent is shutdown. - // FIXME: add a way to stop mediator polling from the public api, and make sure this is - // being handled in the agent shutdown so we don't get any errors with wallets being closed. - const recipientStop$ = recipientAgent.injectionContainer.resolve>(InjectionSymbols.Stop$) - recipientStop$.next(true) + await recipientAgent.mediationRecipient.stopMessagePickup() await sleep(2000) } diff --git a/yarn.lock b/yarn.lock index 700134c89b..9187c4a305 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2281,14 +2281,6 @@ "@stablelib/binary" "^1.0.1" "@stablelib/wipe" "^1.0.1" -"@stablelib/random@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@stablelib/random/-/random-1.0.2.tgz#2dece393636489bf7e19c51229dd7900eddf742c" - integrity sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w== - dependencies: - "@stablelib/binary" "^1.0.1" - "@stablelib/wipe" "^1.0.1" - "@stablelib/sha256@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@stablelib/sha256/-/sha256-1.0.1.tgz#77b6675b67f9b0ea081d2e31bda4866297a3ae4f" From 97d3073aa9300900740c3e8aee8233d38849293d Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht <61358536+blu3beri@users.noreply.github.com> Date: Tue, 11 Oct 2022 22:42:21 +0200 Subject: [PATCH 052/125] feat(question-answer)!: separate logic to a new module (#1040) Signed-off-by: blu3beri BREAKING CHANGE: question-answer module has been removed from the core and moved to a separate package. To integrate it in an Agent instance, it can be injected in constructor like this: ```ts const agent = new Agent({ config: { /* config */ }, dependencies: agentDependencies, modules: { questionAnswer: new QuestionAnswerModule(), /* other custom modules */ } }) ``` Then, module API can be accessed in `agent.modules.questionAnswer`. --- packages/core/src/agent/AgentModules.ts | 2 - packages/core/src/agent/BaseAgent.ts | 4 -- .../core/src/agent/__tests__/Agent.test.ts | 3 +- .../src/agent/__tests__/AgentModules.test.ts | 25 +++---- packages/core/src/index.ts | 11 ++- .../dids/__tests__/dids-resolver.e2e.test.ts | 6 +- .../__tests__/QuestionAnswerModule.test.ts | 29 -------- packages/core/src/wallet/index.ts | 2 +- packages/core/tests/helpers.ts | 9 ++- packages/question-answer/README.md | 31 ++++++++ packages/question-answer/jest.config.ts | 13 ++++ packages/question-answer/package.json | 40 +++++++++++ .../src}/QuestionAnswerApi.ts | 18 ++--- .../src}/QuestionAnswerEvents.ts | 2 +- .../src}/QuestionAnswerModule.ts | 5 +- .../src}/QuestionAnswerRole.ts | 0 .../__tests__/QuestionAnswerModule.test.ts | 43 +++++++++++ .../__tests__/QuestionAnswerService.test.ts | 45 ++++++------ .../question-answer/src/__tests__/utils.ts | 72 +++++++++++++++++++ .../src}/handlers/AnswerMessageHandler.ts | 2 +- .../src}/handlers/QuestionMessageHandler.ts | 2 +- .../src}/handlers/index.ts | 0 .../src}/index.ts | 0 .../src}/messages/AnswerMessage.ts | 4 +- .../src}/messages/QuestionMessage.ts | 3 +- .../src}/messages/index.ts | 0 .../src}/models/QuestionAnswerState.ts | 0 .../src}/models/ValidResponse.ts | 0 .../src}/models/index.ts | 0 .../src}/repository/QuestionAnswerRecord.ts | 8 +-- .../repository/QuestionAnswerRepository.ts | 6 +- .../src}/repository/index.ts | 0 .../src}/services/QuestionAnswerService.ts | 13 ++-- .../src}/services/index.ts | 0 .../tests}/helpers.ts | 12 ++-- .../tests}/question-answer.e2e.test.ts | 59 +++++++++------ packages/question-answer/tsconfig.build.json | 7 ++ packages/question-answer/tsconfig.json | 6 ++ 38 files changed, 332 insertions(+), 150 deletions(-) delete mode 100644 packages/core/src/modules/question-answer/__tests__/QuestionAnswerModule.test.ts create mode 100644 packages/question-answer/README.md create mode 100644 packages/question-answer/jest.config.ts create mode 100644 packages/question-answer/package.json rename packages/{core/src/modules/question-answer => question-answer/src}/QuestionAnswerApi.ts (91%) rename packages/{core/src/modules/question-answer => question-answer/src}/QuestionAnswerEvents.ts (89%) rename packages/{core/src/modules/question-answer => question-answer/src}/QuestionAnswerModule.ts (84%) rename packages/{core/src/modules/question-answer => question-answer/src}/QuestionAnswerRole.ts (100%) create mode 100644 packages/question-answer/src/__tests__/QuestionAnswerModule.test.ts rename packages/{core/src/modules/question-answer => question-answer/src}/__tests__/QuestionAnswerService.test.ts (90%) create mode 100644 packages/question-answer/src/__tests__/utils.ts rename packages/{core/src/modules/question-answer => question-answer/src}/handlers/AnswerMessageHandler.ts (87%) rename packages/{core/src/modules/question-answer => question-answer/src}/handlers/QuestionMessageHandler.ts (87%) rename packages/{core/src/modules/question-answer => question-answer/src}/handlers/index.ts (100%) rename packages/{core/src/modules/question-answer => question-answer/src}/index.ts (100%) rename packages/{core/src/modules/question-answer => question-answer/src}/messages/AnswerMessage.ts (84%) rename packages/{core/src/modules/question-answer => question-answer/src}/messages/QuestionMessage.ts (91%) rename packages/{core/src/modules/question-answer => question-answer/src}/messages/index.ts (100%) rename packages/{core/src/modules/question-answer => question-answer/src}/models/QuestionAnswerState.ts (100%) rename packages/{core/src/modules/question-answer => question-answer/src}/models/ValidResponse.ts (100%) rename packages/{core/src/modules/question-answer => question-answer/src}/models/index.ts (100%) rename packages/{core/src/modules/question-answer => question-answer/src}/repository/QuestionAnswerRecord.ts (90%) rename packages/{core/src/modules/question-answer => question-answer/src}/repository/QuestionAnswerRepository.ts (57%) rename packages/{core/src/modules/question-answer => question-answer/src}/repository/index.ts (100%) rename packages/{core/src/modules/question-answer => question-answer/src}/services/QuestionAnswerService.ts (95%) rename packages/{core/src/modules/question-answer => question-answer/src}/services/index.ts (100%) rename packages/{core/src/modules/question-answer/__tests__ => question-answer/tests}/helpers.ts (85%) rename packages/{core/src/modules/question-answer/__tests__ => question-answer/tests}/question-answer.e2e.test.ts (63%) create mode 100644 packages/question-answer/tsconfig.build.json create mode 100644 packages/question-answer/tsconfig.json diff --git a/packages/core/src/agent/AgentModules.ts b/packages/core/src/agent/AgentModules.ts index 9a750a4593..5a0ed49d24 100644 --- a/packages/core/src/agent/AgentModules.ts +++ b/packages/core/src/agent/AgentModules.ts @@ -13,7 +13,6 @@ import { IndyModule } from '../modules/indy' import { LedgerModule } from '../modules/ledger' import { OutOfBandModule } from '../modules/oob' import { ProofsModule } from '../modules/proofs' -import { QuestionAnswerModule } from '../modules/question-answer' import { MediatorModule, RecipientModule } from '../modules/routing' import { W3cVcModule } from '../modules/vc' import { WalletModule } from '../wallet' @@ -118,7 +117,6 @@ function getDefaultAgentModules(agentConfig: AgentConfig) { mediatorPollingInterval: agentConfig.mediatorPollingInterval, }), basicMessages: () => new BasicMessagesModule(), - questionAnswer: () => new QuestionAnswerModule(), actionMenu: () => new ActionMenuModule(), genericRecords: () => new GenericRecordsModule(), ledger: () => diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index 380bdecd42..847e8ffe57 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -15,7 +15,6 @@ import { GenericRecordsApi } from '../modules/generic-records' import { LedgerApi } from '../modules/ledger' import { OutOfBandApi } from '../modules/oob' import { ProofsApi } from '../modules/proofs/ProofsApi' -import { QuestionAnswerApi } from '../modules/question-answer' import { MediatorApi, RecipientApi } from '../modules/routing' import { StorageUpdateService } from '../storage' import { UpdateAssistant } from '../storage/migration/UpdateAssistant' @@ -50,7 +49,6 @@ export abstract class BaseAgent { 'https://didcomm.org/present-proof/1.0', 'https://didcomm.org/revocation_notification/1.0', 'https://didcomm.org/revocation_notification/2.0', - 'https://didcomm.org/questionanswer/1.0', ]) ) - expect(protocols.length).toEqual(16) + expect(protocols.length).toEqual(15) }) }) diff --git a/packages/core/src/agent/__tests__/AgentModules.test.ts b/packages/core/src/agent/__tests__/AgentModules.test.ts index f22bcff195..ba632aeca8 100644 --- a/packages/core/src/agent/__tests__/AgentModules.test.ts +++ b/packages/core/src/agent/__tests__/AgentModules.test.ts @@ -1,22 +1,18 @@ import type { Module } from '../../plugins' -import { - ActionMenuModule, - ConnectionsModule, - CredentialsModule, - ProofsModule, - MediatorModule, - RecipientModule, - BasicMessagesModule, - QuestionAnswerModule, - LedgerModule, - DidsModule, - OutOfBandModule, -} from '../..' import { getAgentConfig } from '../../../tests/helpers' +import { ActionMenuModule } from '../../modules/action-menu' +import { BasicMessagesModule } from '../../modules/basic-messages' +import { ConnectionsModule } from '../../modules/connections' +import { CredentialsModule } from '../../modules/credentials' +import { DidsModule } from '../../modules/dids' import { DiscoverFeaturesModule } from '../../modules/discover-features' import { GenericRecordsModule } from '../../modules/generic-records' import { IndyModule } from '../../modules/indy' +import { LedgerModule } from '../../modules/ledger' +import { OutOfBandModule } from '../../modules/oob' +import { ProofsModule } from '../../modules/proofs' +import { MediatorModule, RecipientModule } from '../../modules/routing' import { W3cVcModule } from '../../modules/vc' import { DependencyManager, injectable } from '../../plugins' import { WalletModule } from '../../wallet' @@ -70,7 +66,6 @@ describe('AgentModules', () => { mediationRecipient: expect.any(RecipientModule), basicMessages: expect.any(BasicMessagesModule), actionMenu: expect.any(ActionMenuModule), - questionAnswer: expect.any(QuestionAnswerModule), genericRecords: expect.any(GenericRecordsModule), ledger: expect.any(LedgerModule), discovery: expect.any(DiscoverFeaturesModule), @@ -96,7 +91,6 @@ describe('AgentModules', () => { mediationRecipient: expect.any(RecipientModule), basicMessages: expect.any(BasicMessagesModule), actionMenu: expect.any(ActionMenuModule), - questionAnswer: expect.any(QuestionAnswerModule), genericRecords: expect.any(GenericRecordsModule), ledger: expect.any(LedgerModule), discovery: expect.any(DiscoverFeaturesModule), @@ -125,7 +119,6 @@ describe('AgentModules', () => { mediationRecipient: expect.any(RecipientModule), basicMessages: expect.any(BasicMessagesModule), actionMenu: expect.any(ActionMenuModule), - questionAnswer: expect.any(QuestionAnswerModule), genericRecords: expect.any(GenericRecordsModule), ledger: expect.any(LedgerModule), discovery: expect.any(DiscoverFeaturesModule), diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f604e793bb..9475848a1a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -10,6 +10,7 @@ export { EventEmitter } from './agent/EventEmitter' export { FeatureRegistry } from './agent/FeatureRegistry' export { Handler, HandlerInboundMessage } from './agent/Handler' export * from './agent/models' +export * from './agent/helpers' export { AgentConfig } from './agent/AgentConfig' export { AgentMessage } from './agent/AgentMessage' export { Dispatcher } from './agent/Dispatcher' @@ -38,9 +39,6 @@ export type { TransportSession } from './agent/TransportService' export { TransportService } from './agent/TransportService' export { Attachment } from './decorators/attachment/Attachment' -import { parseInvitationUrl } from './utils/parseInvitation' -import { uuid } from './utils/uuid' - export * from './plugins' export * from './transport' export * from './modules/action-menu' @@ -52,7 +50,6 @@ export * from './modules/proofs' export * from './modules/connections' export * from './modules/ledger' export * from './modules/routing' -export * from './modules/question-answer' export * from './modules/oob' export * from './modules/dids' export * from './modules/vc' @@ -60,11 +57,13 @@ export { JsonEncoder, JsonTransformer, isJsonObject, isValidJweStructure, TypedA export * from './logger' export * from './error' export * from './wallet/error' -export * from './crypto' export { parseMessageType, IsValidMessageType } from './utils/messageType' export type { Constructor } from './utils/mixins' - export * from './agent/Events' +export * from './crypto/' + +import { parseInvitationUrl } from './utils/parseInvitation' +import { uuid } from './utils/uuid' const utils = { uuid, diff --git a/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts index 861d7f0d1a..f10a16ead7 100644 --- a/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts +++ b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts @@ -3,10 +3,12 @@ import type { Wallet } from '../../../wallet' import { convertPublicKeyToX25519 } from '@stablelib/ed25519' import { getAgentOptions } from '../../../../tests/helpers' +import { Agent } from '../../../agent/Agent' +import { InjectionSymbols } from '../../../constants' +import { Key, KeyType } from '../../../crypto' +import { JsonTransformer } from '../../../utils' import { sleep } from '../../../utils/sleep' -import { InjectionSymbols, Key, KeyType, JsonTransformer, Agent } from '@aries-framework/core' - describe('dids', () => { let agent: Agent diff --git a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerModule.test.ts b/packages/core/src/modules/question-answer/__tests__/QuestionAnswerModule.test.ts deleted file mode 100644 index 19d46a9cb0..0000000000 --- a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerModule.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { FeatureRegistry } from '../../../agent/FeatureRegistry' -import { DependencyManager } from '../../../plugins/DependencyManager' -import { QuestionAnswerApi } from '../QuestionAnswerApi' -import { QuestionAnswerModule } from '../QuestionAnswerModule' -import { QuestionAnswerRepository } from '../repository/QuestionAnswerRepository' -import { QuestionAnswerService } from '../services' - -jest.mock('../../../plugins/DependencyManager') -const DependencyManagerMock = DependencyManager as jest.Mock - -const dependencyManager = new DependencyManagerMock() - -jest.mock('../../../agent/FeatureRegistry') -const FeatureRegistryMock = FeatureRegistry as jest.Mock - -const featureRegistry = new FeatureRegistryMock() - -describe('QuestionAnswerModule', () => { - test('registers dependencies on the dependency manager', () => { - new QuestionAnswerModule().register(dependencyManager, featureRegistry) - - expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) - expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(QuestionAnswerApi) - - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(2) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(QuestionAnswerService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(QuestionAnswerRepository) - }) -}) diff --git a/packages/core/src/wallet/index.ts b/packages/core/src/wallet/index.ts index 9259969b47..6e19fc5d3c 100644 --- a/packages/core/src/wallet/index.ts +++ b/packages/core/src/wallet/index.ts @@ -1,4 +1,4 @@ export * from './Wallet' +export * from './IndyWallet' export * from './WalletApi' export * from './WalletModule' -export * from './IndyWallet' diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index abb5c89fcf..45d62417ae 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -12,6 +12,7 @@ import type { SchemaTemplate, Wallet, } from '../src' +import type { AgentModulesInput } from '../src/agent/AgentModules' import type { IndyOfferCredentialFormat } from '../src/modules/credentials/formats/indy/IndyCredentialFormat' import type { RequestProofOptions } from '../src/modules/proofs/ProofsApiOptions' import type { ProofAttributeInfo, ProofPredicateInfo } from '../src/modules/proofs/formats/indy/models' @@ -73,7 +74,11 @@ export const genesisPath = process.env.GENESIS_TXN_PATH export const publicDidSeed = process.env.TEST_AGENT_PUBLIC_DID_SEED ?? '000000000000000000000000Trustee9' export { agentDependencies } -export function getAgentOptions(name: string, extraConfig: Partial = {}) { +export function getAgentOptions( + name: string, + extraConfig: Partial = {}, + modules?: AgentModules +) { const config: InitConfig = { label: `Agent: ${name}`, walletConfig: { @@ -98,7 +103,7 @@ export function getAgentOptions(name: string, extraConfig: Partial = ...extraConfig, } - return { config, dependencies: agentDependencies } as const + return { config, modules, dependencies: agentDependencies } as const } export function getPostgresAgentOptions(name: string, extraConfig: Partial = {}) { diff --git a/packages/question-answer/README.md b/packages/question-answer/README.md new file mode 100644 index 0000000000..fe141eddd6 --- /dev/null +++ b/packages/question-answer/README.md @@ -0,0 +1,31 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript - Question Answer

+

+ License + typescript + @aries-framework/question-answer version + +

+
+ +TODO diff --git a/packages/question-answer/jest.config.ts b/packages/question-answer/jest.config.ts new file mode 100644 index 0000000000..ce53584ebf --- /dev/null +++ b/packages/question-answer/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + name: packageJson.name, + displayName: packageJson.name, +} + +export default config diff --git a/packages/question-answer/package.json b/packages/question-answer/package.json new file mode 100644 index 0000000000..650edab91e --- /dev/null +++ b/packages/question-answer/package.json @@ -0,0 +1,40 @@ +{ + "name": "@aries-framework/question-answer", + "main": "build/index", + "types": "build/index", + "version": "0.2.2", + "private": true, + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/question-answer", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/question-answer" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf -rf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@aries-framework/core": "0.2.4", + "rxjs": "^7.2.0", + "class-transformer": "0.5.1", + "class-validator": "0.13.1" + }, + "devDependencies": { + "@aries-framework/core": "0.2.4", + "@aries-framework/node": "0.2.4", + "reflect-metadata": "^0.1.13", + "rimraf": "~3.0.2", + "typescript": "~4.3.0" + } +} diff --git a/packages/core/src/modules/question-answer/QuestionAnswerApi.ts b/packages/question-answer/src/QuestionAnswerApi.ts similarity index 91% rename from packages/core/src/modules/question-answer/QuestionAnswerApi.ts rename to packages/question-answer/src/QuestionAnswerApi.ts index 6d8004d9e6..52167ebf95 100644 --- a/packages/core/src/modules/question-answer/QuestionAnswerApi.ts +++ b/packages/question-answer/src/QuestionAnswerApi.ts @@ -1,12 +1,14 @@ -import type { Query } from '../../storage/StorageService' import type { QuestionAnswerRecord } from './repository' - -import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' -import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' -import { injectable } from '../../plugins' -import { ConnectionService } from '../connections' +import type { Query } from '@aries-framework/core' + +import { + AgentContext, + ConnectionService, + createOutboundMessage, + Dispatcher, + injectable, + MessageSender, +} from '@aries-framework/core' import { AnswerMessageHandler, QuestionMessageHandler } from './handlers' import { ValidResponse } from './models' diff --git a/packages/core/src/modules/question-answer/QuestionAnswerEvents.ts b/packages/question-answer/src/QuestionAnswerEvents.ts similarity index 89% rename from packages/core/src/modules/question-answer/QuestionAnswerEvents.ts rename to packages/question-answer/src/QuestionAnswerEvents.ts index 05fe30318c..23b869e3c2 100644 --- a/packages/core/src/modules/question-answer/QuestionAnswerEvents.ts +++ b/packages/question-answer/src/QuestionAnswerEvents.ts @@ -1,6 +1,6 @@ -import type { BaseEvent } from '../../agent/Events' import type { QuestionAnswerState } from './models' import type { QuestionAnswerRecord } from './repository' +import type { BaseEvent } from '@aries-framework/core' export enum QuestionAnswerEventTypes { QuestionAnswerStateChanged = 'QuestionAnswerStateChanged', diff --git a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts b/packages/question-answer/src/QuestionAnswerModule.ts similarity index 84% rename from packages/core/src/modules/question-answer/QuestionAnswerModule.ts rename to packages/question-answer/src/QuestionAnswerModule.ts index 3525d5b9ad..92059a07b7 100644 --- a/packages/core/src/modules/question-answer/QuestionAnswerModule.ts +++ b/packages/question-answer/src/QuestionAnswerModule.ts @@ -1,7 +1,6 @@ -import type { FeatureRegistry } from '../../agent/FeatureRegistry' -import type { DependencyManager, Module } from '../../plugins' +import type { DependencyManager, FeatureRegistry, Module } from '@aries-framework/core' -import { Protocol } from '../../agent/models' +import { Protocol } from '@aries-framework/core' import { QuestionAnswerApi } from './QuestionAnswerApi' import { QuestionAnswerRole } from './QuestionAnswerRole' diff --git a/packages/core/src/modules/question-answer/QuestionAnswerRole.ts b/packages/question-answer/src/QuestionAnswerRole.ts similarity index 100% rename from packages/core/src/modules/question-answer/QuestionAnswerRole.ts rename to packages/question-answer/src/QuestionAnswerRole.ts diff --git a/packages/question-answer/src/__tests__/QuestionAnswerModule.test.ts b/packages/question-answer/src/__tests__/QuestionAnswerModule.test.ts new file mode 100644 index 0000000000..1fc0a401b7 --- /dev/null +++ b/packages/question-answer/src/__tests__/QuestionAnswerModule.test.ts @@ -0,0 +1,43 @@ +import type { DependencyManager, FeatureRegistry } from '@aries-framework/core' + +import { Protocol } from '@aries-framework/core' + +import { + QuestionAnswerApi, + QuestionAnswerModule, + QuestionAnswerRepository, + QuestionAnswerRole, + QuestionAnswerService, +} from '@aries-framework/question-answer' + +const dependencyManager = { + registerInstance: jest.fn(), + registerSingleton: jest.fn(), + registerContextScoped: jest.fn(), +} as unknown as DependencyManager + +const featureRegistry = { + register: jest.fn(), +} as unknown as FeatureRegistry + +describe('QuestionAnswerModule', () => { + test('registers dependencies on the dependency manager', () => { + const questionAnswerModule = new QuestionAnswerModule() + questionAnswerModule.register(dependencyManager, featureRegistry) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(QuestionAnswerApi) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(2) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(QuestionAnswerService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(QuestionAnswerRepository) + + expect(featureRegistry.register).toHaveBeenCalledTimes(1) + expect(featureRegistry.register).toHaveBeenCalledWith( + new Protocol({ + id: 'https://didcomm.org/questionanswer/1.0', + roles: [QuestionAnswerRole.Questioner, QuestionAnswerRole.Responder], + }) + ) + }) +}) diff --git a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts b/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts similarity index 90% rename from packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts rename to packages/question-answer/src/__tests__/QuestionAnswerService.test.ts index 1e8bf207a5..ae6432ffb9 100644 --- a/packages/core/src/modules/question-answer/__tests__/QuestionAnswerService.test.ts +++ b/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts @@ -1,29 +1,30 @@ -import type { AgentContext } from '../../../agent' -import type { AgentConfig } from '../../../agent/AgentConfig' -import type { Repository } from '../../../storage/Repository' -import type { QuestionAnswerStateChangedEvent } from '../QuestionAnswerEvents' -import type { ValidResponse } from '../models' +import type { AgentConfig, AgentContext, Repository } from '@aries-framework/core' +import type { QuestionAnswerStateChangedEvent, ValidResponse } from '@aries-framework/question-answer' +import { + EventEmitter, + IndyWallet, + SigningProviderRegistry, + InboundMessageContext, + DidExchangeState, +} from '@aries-framework/core' +import { agentDependencies } from '@aries-framework/node' import { Subject } from 'rxjs' +import { mockFunction } from '../../../core/tests/helpers' + +import { getAgentConfig, getAgentContext, getMockConnection } from './utils' + import { - agentDependencies, - getAgentConfig, - getAgentContext, - getMockConnection, - mockFunction, -} from '../../../../tests/helpers' -import { EventEmitter } from '../../../agent/EventEmitter' -import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import { SigningProviderRegistry } from '../../../crypto/signing-provider' -import { IndyWallet } from '../../../wallet/IndyWallet' -import { DidExchangeState } from '../../connections' -import { QuestionAnswerEventTypes } from '../QuestionAnswerEvents' -import { QuestionAnswerRole } from '../QuestionAnswerRole' -import { AnswerMessage, QuestionMessage } from '../messages' -import { QuestionAnswerState } from '../models' -import { QuestionAnswerRecord, QuestionAnswerRepository } from '../repository' -import { QuestionAnswerService } from '../services' + QuestionAnswerRecord, + QuestionAnswerRepository, + QuestionAnswerEventTypes, + QuestionAnswerRole, + QuestionAnswerService, + QuestionAnswerState, + QuestionMessage, + AnswerMessage, +} from '@aries-framework/question-answer' jest.mock('../repository/QuestionAnswerRepository') const QuestionAnswerRepositoryMock = QuestionAnswerRepository as jest.Mock diff --git a/packages/question-answer/src/__tests__/utils.ts b/packages/question-answer/src/__tests__/utils.ts new file mode 100644 index 0000000000..a249af58e1 --- /dev/null +++ b/packages/question-answer/src/__tests__/utils.ts @@ -0,0 +1,72 @@ +import type { ConnectionRecordProps, InitConfig, Wallet } from '@aries-framework/core' + +import { + AgentContext, + DependencyManager, + InjectionSymbols, + AgentConfig, + ConnectionRecord, + DidExchangeRole, + DidExchangeState, +} from '@aries-framework/core' +import { agentDependencies } from '@aries-framework/node' + +export function getMockConnection({ + state = DidExchangeState.InvitationReceived, + role = DidExchangeRole.Requester, + id = 'test', + did = 'test-did', + threadId = 'threadId', + tags = {}, + theirLabel, + theirDid = 'their-did', +}: Partial = {}) { + return new ConnectionRecord({ + did, + threadId, + theirDid, + id, + role, + state, + tags, + theirLabel, + }) +} + +export function getAgentConfig(name: string, extraConfig: Partial = {}) { + const { config, agentDependencies } = getBaseConfig(name, extraConfig) + return new AgentConfig(config, agentDependencies) +} + +const publicDidSeed = process.env.TEST_AGENT_PUBLIC_DID_SEED ?? '000000000000000000000000Trustee9' +export function getBaseConfig(name: string, extraConfig: Partial = {}) { + const config: InitConfig = { + label: `Agent: ${name}`, + walletConfig: { + id: `Wallet: ${name}`, + key: `Key: ${name}`, + }, + publicDidSeed, + autoAcceptConnections: true, + connectToIndyLedgersOnStartup: false, + ...extraConfig, + } + + return { config, agentDependencies } as const +} + +export function getAgentContext({ + dependencyManager = new DependencyManager(), + wallet, + agentConfig, + contextCorrelationId = 'mock', +}: { + dependencyManager?: DependencyManager + wallet?: Wallet + agentConfig?: AgentConfig + contextCorrelationId?: string +} = {}) { + if (wallet) dependencyManager.registerInstance(InjectionSymbols.Wallet, wallet) + if (agentConfig) dependencyManager.registerInstance(AgentConfig, agentConfig) + return new AgentContext({ dependencyManager, contextCorrelationId }) +} diff --git a/packages/core/src/modules/question-answer/handlers/AnswerMessageHandler.ts b/packages/question-answer/src/handlers/AnswerMessageHandler.ts similarity index 87% rename from packages/core/src/modules/question-answer/handlers/AnswerMessageHandler.ts rename to packages/question-answer/src/handlers/AnswerMessageHandler.ts index ea737c8db8..5310aeb345 100644 --- a/packages/core/src/modules/question-answer/handlers/AnswerMessageHandler.ts +++ b/packages/question-answer/src/handlers/AnswerMessageHandler.ts @@ -1,5 +1,5 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { QuestionAnswerService } from '../services' +import type { Handler, HandlerInboundMessage } from '@aries-framework/core' import { AnswerMessage } from '../messages' diff --git a/packages/core/src/modules/question-answer/handlers/QuestionMessageHandler.ts b/packages/question-answer/src/handlers/QuestionMessageHandler.ts similarity index 87% rename from packages/core/src/modules/question-answer/handlers/QuestionMessageHandler.ts rename to packages/question-answer/src/handlers/QuestionMessageHandler.ts index 18d090488a..7dbfbdc440 100644 --- a/packages/core/src/modules/question-answer/handlers/QuestionMessageHandler.ts +++ b/packages/question-answer/src/handlers/QuestionMessageHandler.ts @@ -1,5 +1,5 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { QuestionAnswerService } from '../services' +import type { Handler, HandlerInboundMessage } from '@aries-framework/core' import { QuestionMessage } from '../messages' diff --git a/packages/core/src/modules/question-answer/handlers/index.ts b/packages/question-answer/src/handlers/index.ts similarity index 100% rename from packages/core/src/modules/question-answer/handlers/index.ts rename to packages/question-answer/src/handlers/index.ts diff --git a/packages/core/src/modules/question-answer/index.ts b/packages/question-answer/src/index.ts similarity index 100% rename from packages/core/src/modules/question-answer/index.ts rename to packages/question-answer/src/index.ts diff --git a/packages/core/src/modules/question-answer/messages/AnswerMessage.ts b/packages/question-answer/src/messages/AnswerMessage.ts similarity index 84% rename from packages/core/src/modules/question-answer/messages/AnswerMessage.ts rename to packages/question-answer/src/messages/AnswerMessage.ts index df5a970372..d74498ea14 100644 --- a/packages/core/src/modules/question-answer/messages/AnswerMessage.ts +++ b/packages/question-answer/src/messages/AnswerMessage.ts @@ -1,9 +1,7 @@ +import { AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { Expose } from 'class-transformer' import { IsString } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' -import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' - export class AnswerMessage extends AgentMessage { /** * Create new AnswerMessage instance. diff --git a/packages/core/src/modules/question-answer/messages/QuestionMessage.ts b/packages/question-answer/src/messages/QuestionMessage.ts similarity index 91% rename from packages/core/src/modules/question-answer/messages/QuestionMessage.ts rename to packages/question-answer/src/messages/QuestionMessage.ts index 1bdc500e28..1cc4003cd3 100644 --- a/packages/core/src/modules/question-answer/messages/QuestionMessage.ts +++ b/packages/question-answer/src/messages/QuestionMessage.ts @@ -1,8 +1,7 @@ +import { AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsBoolean, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' -import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' import { ValidResponse } from '../models' export class QuestionMessage extends AgentMessage { diff --git a/packages/core/src/modules/question-answer/messages/index.ts b/packages/question-answer/src/messages/index.ts similarity index 100% rename from packages/core/src/modules/question-answer/messages/index.ts rename to packages/question-answer/src/messages/index.ts diff --git a/packages/core/src/modules/question-answer/models/QuestionAnswerState.ts b/packages/question-answer/src/models/QuestionAnswerState.ts similarity index 100% rename from packages/core/src/modules/question-answer/models/QuestionAnswerState.ts rename to packages/question-answer/src/models/QuestionAnswerState.ts diff --git a/packages/core/src/modules/question-answer/models/ValidResponse.ts b/packages/question-answer/src/models/ValidResponse.ts similarity index 100% rename from packages/core/src/modules/question-answer/models/ValidResponse.ts rename to packages/question-answer/src/models/ValidResponse.ts diff --git a/packages/core/src/modules/question-answer/models/index.ts b/packages/question-answer/src/models/index.ts similarity index 100% rename from packages/core/src/modules/question-answer/models/index.ts rename to packages/question-answer/src/models/index.ts diff --git a/packages/core/src/modules/question-answer/repository/QuestionAnswerRecord.ts b/packages/question-answer/src/repository/QuestionAnswerRecord.ts similarity index 90% rename from packages/core/src/modules/question-answer/repository/QuestionAnswerRecord.ts rename to packages/question-answer/src/repository/QuestionAnswerRecord.ts index 7d649a3bc6..58175470fd 100644 --- a/packages/core/src/modules/question-answer/repository/QuestionAnswerRecord.ts +++ b/packages/question-answer/src/repository/QuestionAnswerRecord.ts @@ -1,10 +1,8 @@ -import type { RecordTags, TagsBase } from '../../../storage/BaseRecord' import type { QuestionAnswerRole } from '../QuestionAnswerRole' import type { QuestionAnswerState, ValidResponse } from '../models' +import type { RecordTags, TagsBase } from '@aries-framework/core' -import { AriesFrameworkError } from '../../../error' -import { BaseRecord } from '../../../storage/BaseRecord' -import { uuid } from '../../../utils/uuid' +import { AriesFrameworkError, utils, BaseRecord } from '@aries-framework/core' export type CustomQuestionAnswerTags = TagsBase export type DefaultQuestionAnswerTags = { @@ -51,7 +49,7 @@ export class QuestionAnswerRecord extends BaseRecord { - let bobAgent: Agent - let aliceAgent: Agent + let bobAgent: Agent<{ + questionAnswer: QuestionAnswerModule + }> + let aliceAgent: Agent<{ + questionAnswer: QuestionAnswerModule + }> let aliceConnection: ConnectionRecord beforeEach(async () => { @@ -40,6 +56,7 @@ describe('Question Answer', () => { await bobAgent.initialize() aliceAgent = new Agent(aliceAgentOptions) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() @@ -55,7 +72,7 @@ describe('Question Answer', () => { test('Alice sends a question and Bob answers', async () => { testLogger.test('Alice sends question to Bob') - let aliceQuestionAnswerRecord = await aliceAgent.questionAnswer.sendQuestion(aliceConnection.id, { + let aliceQuestionAnswerRecord = await aliceAgent.modules.questionAnswer.sendQuestion(aliceConnection.id, { question: 'Do you want to play?', validResponses: [{ text: 'Yes' }, { text: 'No' }], }) @@ -69,7 +86,7 @@ describe('Question Answer', () => { expect(bobQuestionAnswerRecord.questionText).toEqual('Do you want to play?') expect(bobQuestionAnswerRecord.validResponses).toEqual([{ text: 'Yes' }, { text: 'No' }]) testLogger.test('Bob sends answer to Alice') - await bobAgent.questionAnswer.sendAnswer(bobQuestionAnswerRecord.id, 'Yes') + await bobAgent.modules.questionAnswer.sendAnswer(bobQuestionAnswerRecord.id, 'Yes') testLogger.test('Alice waits until Bob answers') aliceQuestionAnswerRecord = await waitForQuestionAnswerRecord(aliceAgent, { @@ -79,7 +96,7 @@ describe('Question Answer', () => { expect(aliceQuestionAnswerRecord.response).toEqual('Yes') - const retrievedRecord = await aliceAgent.questionAnswer.findById(aliceQuestionAnswerRecord.id) + const retrievedRecord = await aliceAgent.modules.questionAnswer.findById(aliceQuestionAnswerRecord.id) expect(retrievedRecord).toMatchObject( expect.objectContaining({ id: aliceQuestionAnswerRecord.id, diff --git a/packages/question-answer/tsconfig.build.json b/packages/question-answer/tsconfig.build.json new file mode 100644 index 0000000000..2b75d0adab --- /dev/null +++ b/packages/question-answer/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["src/**/*"] +} diff --git a/packages/question-answer/tsconfig.json b/packages/question-answer/tsconfig.json new file mode 100644 index 0000000000..46efe6f721 --- /dev/null +++ b/packages/question-answer/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + } +} From e0df0d884b1a7816c7c638406606e45f6e169ff4 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Wed, 12 Oct 2022 13:04:21 -0300 Subject: [PATCH 053/125] feat(action-menu)!: move to separate package (#1049) Signed-off-by: Ariel Gentile BREAKING CHANGE: action-menu module has been removed from the core and moved to a separate package. To integrate it in an Agent instance, it can be injected in constructor like this: ```ts const agent = new Agent({ config: { /* config */ }, dependencies: agentDependencies, modules: { actionMenu: new ActionMenuModule(), /* other custom modules */ } }) ``` Then, module API can be accessed in `agent.modules.actionMenu`. --- packages/action-menu/README.md | 78 ++++++++++++ packages/action-menu/jest.config.ts | 13 ++ packages/action-menu/package.json | 41 +++++++ .../src}/ActionMenuApi.ts | 16 +-- .../src}/ActionMenuApiOptions.ts | 3 +- .../src}/ActionMenuEvents.ts | 2 +- .../src}/ActionMenuModule.ts | 5 +- .../src}/ActionMenuRole.ts | 0 .../src}/ActionMenuState.ts | 0 .../src/__tests__/ActionMenuModule.test.ts | 43 +++++++ .../errors/ActionMenuProblemReportError.ts | 5 +- .../errors/ActionMenuProblemReportReason.ts | 0 .../ActionMenuProblemReportHandler.ts | 2 +- .../src}/handlers/MenuMessageHandler.ts | 2 +- .../handlers/MenuRequestMessageHandler.ts | 2 +- .../src}/handlers/PerformMessageHandler.ts | 2 +- .../src}/handlers/index.ts | 0 .../action-menu => action-menu/src}/index.ts | 0 .../ActionMenuProblemReportMessage.ts | 5 +- .../src}/messages/MenuMessage.ts | 3 +- .../src}/messages/MenuRequestMessage.ts | 3 +- .../src}/messages/PerformMessage.ts | 4 +- .../src}/messages/index.ts | 0 .../src}/models/ActionMenu.ts | 0 .../src}/models/ActionMenuOption.ts | 0 .../src}/models/ActionMenuOptionForm.ts | 0 .../models/ActionMenuOptionFormParameter.ts | 0 .../src}/models/ActionMenuSelection.ts | 0 .../src}/models/index.ts | 0 .../src}/repository/ActionMenuRecord.ts | 8 +- .../src}/repository/ActionMenuRepository.ts | 6 +- .../src}/repository/index.ts | 0 .../src}/services/ActionMenuService.ts | 12 +- .../src}/services/ActionMenuServiceOptions.ts | 2 +- .../__tests__/ActionMenuService.test.ts | 10 +- .../src}/services/index.ts | 0 .../tests}/action-menu.e2e.test.ts | 112 +++++++++++------- .../tests}/helpers.ts | 8 +- packages/action-menu/tsconfig.build.json | 7 ++ packages/action-menu/tsconfig.json | 6 + packages/core/src/agent/AgentModules.ts | 2 - packages/core/src/agent/BaseAgent.ts | 4 - .../core/src/agent/__tests__/Agent.test.ts | 3 +- .../src/agent/__tests__/AgentModules.test.ts | 4 - packages/core/src/index.ts | 2 +- packages/question-answer/README.md | 52 +++++++- packages/question-answer/package.json | 7 +- .../__tests__/QuestionAnswerService.test.ts | 4 +- .../question-answer/src/__tests__/utils.ts | 72 ----------- 49 files changed, 350 insertions(+), 200 deletions(-) create mode 100644 packages/action-menu/README.md create mode 100644 packages/action-menu/jest.config.ts create mode 100644 packages/action-menu/package.json rename packages/{core/src/modules/action-menu => action-menu/src}/ActionMenuApi.ts (93%) rename packages/{core/src/modules/action-menu => action-menu/src}/ActionMenuApiOptions.ts (79%) rename packages/{core/src/modules/action-menu => action-menu/src}/ActionMenuEvents.ts (88%) rename packages/{core/src/modules/action-menu => action-menu/src}/ActionMenuModule.ts (84%) rename packages/{core/src/modules/action-menu => action-menu/src}/ActionMenuRole.ts (100%) rename packages/{core/src/modules/action-menu => action-menu/src}/ActionMenuState.ts (100%) create mode 100644 packages/action-menu/src/__tests__/ActionMenuModule.test.ts rename packages/{core/src/modules/action-menu => action-menu/src}/errors/ActionMenuProblemReportError.ts (84%) rename packages/{core/src/modules/action-menu => action-menu/src}/errors/ActionMenuProblemReportReason.ts (100%) rename packages/{core/src/modules/action-menu => action-menu/src}/handlers/ActionMenuProblemReportHandler.ts (88%) rename packages/{core/src/modules/action-menu => action-menu/src}/handlers/MenuMessageHandler.ts (87%) rename packages/{core/src/modules/action-menu => action-menu/src}/handlers/MenuRequestMessageHandler.ts (88%) rename packages/{core/src/modules/action-menu => action-menu/src}/handlers/PerformMessageHandler.ts (87%) rename packages/{core/src/modules/action-menu => action-menu/src}/handlers/index.ts (100%) rename packages/{core/src/modules/action-menu => action-menu/src}/index.ts (100%) rename packages/{core/src/modules/action-menu => action-menu/src}/messages/ActionMenuProblemReportMessage.ts (71%) rename packages/{core/src/modules/action-menu => action-menu/src}/messages/MenuMessage.ts (90%) rename packages/{core/src/modules/action-menu => action-menu/src}/messages/MenuRequestMessage.ts (77%) rename packages/{core/src/modules/action-menu => action-menu/src}/messages/PerformMessage.ts (85%) rename packages/{core/src/modules/action-menu => action-menu/src}/messages/index.ts (100%) rename packages/{core/src/modules/action-menu => action-menu/src}/models/ActionMenu.ts (100%) rename packages/{core/src/modules/action-menu => action-menu/src}/models/ActionMenuOption.ts (100%) rename packages/{core/src/modules/action-menu => action-menu/src}/models/ActionMenuOptionForm.ts (100%) rename packages/{core/src/modules/action-menu => action-menu/src}/models/ActionMenuOptionFormParameter.ts (100%) rename packages/{core/src/modules/action-menu => action-menu/src}/models/ActionMenuSelection.ts (100%) rename packages/{core/src/modules/action-menu => action-menu/src}/models/index.ts (100%) rename packages/{core/src/modules/action-menu => action-menu/src}/repository/ActionMenuRecord.ts (90%) rename packages/{core/src/modules/action-menu => action-menu/src}/repository/ActionMenuRepository.ts (55%) rename packages/{core/src/modules/action-menu => action-menu/src}/repository/index.ts (100%) rename packages/{core/src/modules/action-menu => action-menu/src}/services/ActionMenuService.ts (96%) rename packages/{core/src/modules/action-menu => action-menu/src}/services/ActionMenuServiceOptions.ts (91%) rename packages/{core/src/modules/action-menu => action-menu/src}/services/__tests__/ActionMenuService.test.ts (98%) rename packages/{core/src/modules/action-menu => action-menu/src}/services/index.ts (100%) rename packages/{core/src/modules/action-menu/__tests__ => action-menu/tests}/action-menu.e2e.test.ts (75%) rename packages/{core/src/modules/action-menu/__tests__ => action-menu/tests}/helpers.ts (85%) create mode 100644 packages/action-menu/tsconfig.build.json create mode 100644 packages/action-menu/tsconfig.json delete mode 100644 packages/question-answer/src/__tests__/utils.ts diff --git a/packages/action-menu/README.md b/packages/action-menu/README.md new file mode 100644 index 0000000000..ffd98caf55 --- /dev/null +++ b/packages/action-menu/README.md @@ -0,0 +1,78 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript Action Menu Plugin

+

+ License + typescript + @aries-framework/action-menu version + +

+
+ +Action Menu plugin for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). Implements [Aries RFC 0509](https://github.com/hyperledger/aries-rfcs/blob/1795d5c2d36f664f88f5e8045042ace8e573808c/features/0509-action-menu/README.md). + +### Installation + +Make sure you have set up the correct version of Aries Framework JavaScript according to the AFJ repository. To find out which version of AFJ you need to have installed you can run the following command. This will list the required peer dependency for `@aries-framework/core`. + +```sh +npm info "@aries-framework/action-menu" peerDependencies +``` + +Then add the action-menu plugin to your project. + +```sh +yarn add @aries-framework/action-menu +``` + +### Quick start + +In order for this plugin to work, we have to inject it into the agent to access agent functionality. See the example for more information. + +### Example of usage + +```ts +import { ActionMenuModule } from '@aries-framework/action-menu' + +const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, + modules: { + actionMenu: new ActionMenuModule(), + /* other custom modules */ + }, +}) + +await agent.initialize() + +// To request root menu to a given connection (menu will be received +// asynchronously in a ActionMenuStateChangedEvent) +await agent.modules.actionMenu.requestMenu({ connectionId }) + +// To select an option from the action menu +await agent.modules.actionMenu.performAction({ + connectionId, + performedAction: { name: 'option-1' }, +}) +``` diff --git a/packages/action-menu/jest.config.ts b/packages/action-menu/jest.config.ts new file mode 100644 index 0000000000..ce53584ebf --- /dev/null +++ b/packages/action-menu/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + name: packageJson.name, + displayName: packageJson.name, +} + +export default config diff --git a/packages/action-menu/package.json b/packages/action-menu/package.json new file mode 100644 index 0000000000..4e06450149 --- /dev/null +++ b/packages/action-menu/package.json @@ -0,0 +1,41 @@ +{ + "name": "@aries-framework/action-menu", + "main": "build/index", + "types": "build/index", + "version": "0.2.4", + "private": true, + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/action-menu", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/action-menu" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf -rf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "rxjs": "^7.2.0", + "class-transformer": "0.5.1", + "class-validator": "0.13.1" + }, + "peerDependencies": { + "@aries-framework/core": "0.2.4" + }, + "devDependencies": { + "@aries-framework/node": "0.2.4", + "reflect-metadata": "^0.1.13", + "rimraf": "~3.0.2", + "typescript": "~4.3.0" + } +} diff --git a/packages/core/src/modules/action-menu/ActionMenuApi.ts b/packages/action-menu/src/ActionMenuApi.ts similarity index 93% rename from packages/core/src/modules/action-menu/ActionMenuApi.ts rename to packages/action-menu/src/ActionMenuApi.ts index 54ff506c56..6efd081e9a 100644 --- a/packages/core/src/modules/action-menu/ActionMenuApi.ts +++ b/packages/action-menu/src/ActionMenuApi.ts @@ -6,13 +6,15 @@ import type { SendMenuOptions, } from './ActionMenuApiOptions' -import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' -import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' -import { AriesFrameworkError } from '../../error' -import { injectable } from '../../plugins' -import { ConnectionService } from '../connections/services' +import { + AgentContext, + AriesFrameworkError, + ConnectionService, + Dispatcher, + MessageSender, + createOutboundMessage, + injectable, +} from '@aries-framework/core' import { ActionMenuRole } from './ActionMenuRole' import { diff --git a/packages/core/src/modules/action-menu/ActionMenuApiOptions.ts b/packages/action-menu/src/ActionMenuApiOptions.ts similarity index 79% rename from packages/core/src/modules/action-menu/ActionMenuApiOptions.ts rename to packages/action-menu/src/ActionMenuApiOptions.ts index 2ad9fcdd54..b4aea64a57 100644 --- a/packages/core/src/modules/action-menu/ActionMenuApiOptions.ts +++ b/packages/action-menu/src/ActionMenuApiOptions.ts @@ -1,6 +1,5 @@ import type { ActionMenuRole } from './ActionMenuRole' -import type { ActionMenu } from './models/ActionMenu' -import type { ActionMenuSelection } from './models/ActionMenuSelection' +import type { ActionMenu, ActionMenuSelection } from './models' export interface FindActiveMenuOptions { connectionId: string diff --git a/packages/core/src/modules/action-menu/ActionMenuEvents.ts b/packages/action-menu/src/ActionMenuEvents.ts similarity index 88% rename from packages/core/src/modules/action-menu/ActionMenuEvents.ts rename to packages/action-menu/src/ActionMenuEvents.ts index 78733fafb7..e0a052987b 100644 --- a/packages/core/src/modules/action-menu/ActionMenuEvents.ts +++ b/packages/action-menu/src/ActionMenuEvents.ts @@ -1,6 +1,6 @@ -import type { BaseEvent } from '../../agent/Events' import type { ActionMenuState } from './ActionMenuState' import type { ActionMenuRecord } from './repository' +import type { BaseEvent } from '@aries-framework/core' export enum ActionMenuEventTypes { ActionMenuStateChanged = 'ActionMenuStateChanged', diff --git a/packages/core/src/modules/action-menu/ActionMenuModule.ts b/packages/action-menu/src/ActionMenuModule.ts similarity index 84% rename from packages/core/src/modules/action-menu/ActionMenuModule.ts rename to packages/action-menu/src/ActionMenuModule.ts index 330d87afd1..09b98d4dbb 100644 --- a/packages/core/src/modules/action-menu/ActionMenuModule.ts +++ b/packages/action-menu/src/ActionMenuModule.ts @@ -1,7 +1,6 @@ -import type { FeatureRegistry } from '../../agent/FeatureRegistry' -import type { DependencyManager, Module } from '../../plugins' +import type { DependencyManager, FeatureRegistry, Module } from '@aries-framework/core' -import { Protocol } from '../../agent/models' +import { Protocol } from '@aries-framework/core' import { ActionMenuApi } from './ActionMenuApi' import { ActionMenuRole } from './ActionMenuRole' diff --git a/packages/core/src/modules/action-menu/ActionMenuRole.ts b/packages/action-menu/src/ActionMenuRole.ts similarity index 100% rename from packages/core/src/modules/action-menu/ActionMenuRole.ts rename to packages/action-menu/src/ActionMenuRole.ts diff --git a/packages/core/src/modules/action-menu/ActionMenuState.ts b/packages/action-menu/src/ActionMenuState.ts similarity index 100% rename from packages/core/src/modules/action-menu/ActionMenuState.ts rename to packages/action-menu/src/ActionMenuState.ts diff --git a/packages/action-menu/src/__tests__/ActionMenuModule.test.ts b/packages/action-menu/src/__tests__/ActionMenuModule.test.ts new file mode 100644 index 0000000000..ff53ca1221 --- /dev/null +++ b/packages/action-menu/src/__tests__/ActionMenuModule.test.ts @@ -0,0 +1,43 @@ +import type { DependencyManager, FeatureRegistry } from '@aries-framework/core' + +import { Protocol } from '@aries-framework/core' + +import { + ActionMenuApi, + ActionMenuModule, + ActionMenuRepository, + ActionMenuRole, + ActionMenuService, +} from '@aries-framework/action-menu' + +const dependencyManager = { + registerInstance: jest.fn(), + registerSingleton: jest.fn(), + registerContextScoped: jest.fn(), +} as unknown as DependencyManager + +const featureRegistry = { + register: jest.fn(), +} as unknown as FeatureRegistry + +describe('ActionMenuModule', () => { + test('registers dependencies on the dependency manager', () => { + const actionMenuModule = new ActionMenuModule() + actionMenuModule.register(dependencyManager, featureRegistry) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(ActionMenuApi) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(2) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(ActionMenuService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(ActionMenuRepository) + + expect(featureRegistry.register).toHaveBeenCalledTimes(1) + expect(featureRegistry.register).toHaveBeenCalledWith( + new Protocol({ + id: 'https://didcomm.org/action-menu/1.0', + roles: [ActionMenuRole.Requester, ActionMenuRole.Responder], + }) + ) + }) +}) diff --git a/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportError.ts b/packages/action-menu/src/errors/ActionMenuProblemReportError.ts similarity index 84% rename from packages/core/src/modules/action-menu/errors/ActionMenuProblemReportError.ts rename to packages/action-menu/src/errors/ActionMenuProblemReportError.ts index 2dcd8162e7..23a5058cd3 100644 --- a/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportError.ts +++ b/packages/action-menu/src/errors/ActionMenuProblemReportError.ts @@ -1,7 +1,8 @@ -import type { ProblemReportErrorOptions } from '../../problem-reports' import type { ActionMenuProblemReportReason } from './ActionMenuProblemReportReason' +import type { ProblemReportErrorOptions } from '@aries-framework/core' + +import { ProblemReportError } from '@aries-framework/core' -import { ProblemReportError } from '../../problem-reports' import { ActionMenuProblemReportMessage } from '../messages' interface ActionMenuProblemReportErrorOptions extends ProblemReportErrorOptions { diff --git a/packages/core/src/modules/action-menu/errors/ActionMenuProblemReportReason.ts b/packages/action-menu/src/errors/ActionMenuProblemReportReason.ts similarity index 100% rename from packages/core/src/modules/action-menu/errors/ActionMenuProblemReportReason.ts rename to packages/action-menu/src/errors/ActionMenuProblemReportReason.ts diff --git a/packages/core/src/modules/action-menu/handlers/ActionMenuProblemReportHandler.ts b/packages/action-menu/src/handlers/ActionMenuProblemReportHandler.ts similarity index 88% rename from packages/core/src/modules/action-menu/handlers/ActionMenuProblemReportHandler.ts rename to packages/action-menu/src/handlers/ActionMenuProblemReportHandler.ts index 023ffc5cc1..6f8d410365 100644 --- a/packages/core/src/modules/action-menu/handlers/ActionMenuProblemReportHandler.ts +++ b/packages/action-menu/src/handlers/ActionMenuProblemReportHandler.ts @@ -1,5 +1,5 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { ActionMenuService } from '../services' +import type { Handler, HandlerInboundMessage } from '@aries-framework/core' import { ActionMenuProblemReportMessage } from '../messages' diff --git a/packages/core/src/modules/action-menu/handlers/MenuMessageHandler.ts b/packages/action-menu/src/handlers/MenuMessageHandler.ts similarity index 87% rename from packages/core/src/modules/action-menu/handlers/MenuMessageHandler.ts rename to packages/action-menu/src/handlers/MenuMessageHandler.ts index 0e81788525..73e596f65d 100644 --- a/packages/core/src/modules/action-menu/handlers/MenuMessageHandler.ts +++ b/packages/action-menu/src/handlers/MenuMessageHandler.ts @@ -1,5 +1,5 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { ActionMenuService } from '../services' +import type { Handler, HandlerInboundMessage } from '@aries-framework/core' import { MenuMessage } from '../messages' diff --git a/packages/core/src/modules/action-menu/handlers/MenuRequestMessageHandler.ts b/packages/action-menu/src/handlers/MenuRequestMessageHandler.ts similarity index 88% rename from packages/core/src/modules/action-menu/handlers/MenuRequestMessageHandler.ts rename to packages/action-menu/src/handlers/MenuRequestMessageHandler.ts index 33277d2510..7e10aed8f5 100644 --- a/packages/core/src/modules/action-menu/handlers/MenuRequestMessageHandler.ts +++ b/packages/action-menu/src/handlers/MenuRequestMessageHandler.ts @@ -1,5 +1,5 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { ActionMenuService } from '../services' +import type { Handler, HandlerInboundMessage } from '@aries-framework/core' import { MenuRequestMessage } from '../messages' diff --git a/packages/core/src/modules/action-menu/handlers/PerformMessageHandler.ts b/packages/action-menu/src/handlers/PerformMessageHandler.ts similarity index 87% rename from packages/core/src/modules/action-menu/handlers/PerformMessageHandler.ts rename to packages/action-menu/src/handlers/PerformMessageHandler.ts index 65de15dcb0..fae36cb189 100644 --- a/packages/core/src/modules/action-menu/handlers/PerformMessageHandler.ts +++ b/packages/action-menu/src/handlers/PerformMessageHandler.ts @@ -1,5 +1,5 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { ActionMenuService } from '../services' +import type { Handler, HandlerInboundMessage } from '@aries-framework/core' import { PerformMessage } from '../messages' diff --git a/packages/core/src/modules/action-menu/handlers/index.ts b/packages/action-menu/src/handlers/index.ts similarity index 100% rename from packages/core/src/modules/action-menu/handlers/index.ts rename to packages/action-menu/src/handlers/index.ts diff --git a/packages/core/src/modules/action-menu/index.ts b/packages/action-menu/src/index.ts similarity index 100% rename from packages/core/src/modules/action-menu/index.ts rename to packages/action-menu/src/index.ts diff --git a/packages/core/src/modules/action-menu/messages/ActionMenuProblemReportMessage.ts b/packages/action-menu/src/messages/ActionMenuProblemReportMessage.ts similarity index 71% rename from packages/core/src/modules/action-menu/messages/ActionMenuProblemReportMessage.ts rename to packages/action-menu/src/messages/ActionMenuProblemReportMessage.ts index cfff53ca65..099f7172c1 100644 --- a/packages/core/src/modules/action-menu/messages/ActionMenuProblemReportMessage.ts +++ b/packages/action-menu/src/messages/ActionMenuProblemReportMessage.ts @@ -1,7 +1,6 @@ -import type { ProblemReportMessageOptions } from '../../problem-reports/messages/ProblemReportMessage' +import type { ProblemReportMessageOptions } from '@aries-framework/core' -import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' -import { ProblemReportMessage } from '../../problem-reports/messages/ProblemReportMessage' +import { IsValidMessageType, parseMessageType, ProblemReportMessage } from '@aries-framework/core' export type ActionMenuProblemReportMessageOptions = ProblemReportMessageOptions diff --git a/packages/core/src/modules/action-menu/messages/MenuMessage.ts b/packages/action-menu/src/messages/MenuMessage.ts similarity index 90% rename from packages/core/src/modules/action-menu/messages/MenuMessage.ts rename to packages/action-menu/src/messages/MenuMessage.ts index d1c87dcebe..74d8b11c1f 100644 --- a/packages/core/src/modules/action-menu/messages/MenuMessage.ts +++ b/packages/action-menu/src/messages/MenuMessage.ts @@ -1,10 +1,9 @@ import type { ActionMenuOptionOptions } from '../models' +import { AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsInstance, IsOptional, IsString } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' -import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' import { ActionMenuOption } from '../models' export interface MenuMessageOptions { diff --git a/packages/core/src/modules/action-menu/messages/MenuRequestMessage.ts b/packages/action-menu/src/messages/MenuRequestMessage.ts similarity index 77% rename from packages/core/src/modules/action-menu/messages/MenuRequestMessage.ts rename to packages/action-menu/src/messages/MenuRequestMessage.ts index d4961553c6..4eede7e578 100644 --- a/packages/core/src/modules/action-menu/messages/MenuRequestMessage.ts +++ b/packages/action-menu/src/messages/MenuRequestMessage.ts @@ -1,5 +1,4 @@ -import { AgentMessage } from '../../../agent/AgentMessage' -import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' +import { AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' export interface MenuRequestMessageOptions { id?: string diff --git a/packages/core/src/modules/action-menu/messages/PerformMessage.ts b/packages/action-menu/src/messages/PerformMessage.ts similarity index 85% rename from packages/core/src/modules/action-menu/messages/PerformMessage.ts rename to packages/action-menu/src/messages/PerformMessage.ts index 75f03f02f7..6e9b081df8 100644 --- a/packages/core/src/modules/action-menu/messages/PerformMessage.ts +++ b/packages/action-menu/src/messages/PerformMessage.ts @@ -1,8 +1,6 @@ +import { AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { IsOptional, IsString } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' -import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' - export interface PerformMessageOptions { id?: string name: string diff --git a/packages/core/src/modules/action-menu/messages/index.ts b/packages/action-menu/src/messages/index.ts similarity index 100% rename from packages/core/src/modules/action-menu/messages/index.ts rename to packages/action-menu/src/messages/index.ts diff --git a/packages/core/src/modules/action-menu/models/ActionMenu.ts b/packages/action-menu/src/models/ActionMenu.ts similarity index 100% rename from packages/core/src/modules/action-menu/models/ActionMenu.ts rename to packages/action-menu/src/models/ActionMenu.ts diff --git a/packages/core/src/modules/action-menu/models/ActionMenuOption.ts b/packages/action-menu/src/models/ActionMenuOption.ts similarity index 100% rename from packages/core/src/modules/action-menu/models/ActionMenuOption.ts rename to packages/action-menu/src/models/ActionMenuOption.ts diff --git a/packages/core/src/modules/action-menu/models/ActionMenuOptionForm.ts b/packages/action-menu/src/models/ActionMenuOptionForm.ts similarity index 100% rename from packages/core/src/modules/action-menu/models/ActionMenuOptionForm.ts rename to packages/action-menu/src/models/ActionMenuOptionForm.ts diff --git a/packages/core/src/modules/action-menu/models/ActionMenuOptionFormParameter.ts b/packages/action-menu/src/models/ActionMenuOptionFormParameter.ts similarity index 100% rename from packages/core/src/modules/action-menu/models/ActionMenuOptionFormParameter.ts rename to packages/action-menu/src/models/ActionMenuOptionFormParameter.ts diff --git a/packages/core/src/modules/action-menu/models/ActionMenuSelection.ts b/packages/action-menu/src/models/ActionMenuSelection.ts similarity index 100% rename from packages/core/src/modules/action-menu/models/ActionMenuSelection.ts rename to packages/action-menu/src/models/ActionMenuSelection.ts diff --git a/packages/core/src/modules/action-menu/models/index.ts b/packages/action-menu/src/models/index.ts similarity index 100% rename from packages/core/src/modules/action-menu/models/index.ts rename to packages/action-menu/src/models/index.ts diff --git a/packages/core/src/modules/action-menu/repository/ActionMenuRecord.ts b/packages/action-menu/src/repository/ActionMenuRecord.ts similarity index 90% rename from packages/core/src/modules/action-menu/repository/ActionMenuRecord.ts rename to packages/action-menu/src/repository/ActionMenuRecord.ts index a5eb125fc0..0560ef4559 100644 --- a/packages/core/src/modules/action-menu/repository/ActionMenuRecord.ts +++ b/packages/action-menu/src/repository/ActionMenuRecord.ts @@ -1,12 +1,10 @@ -import type { TagsBase } from '../../../storage/BaseRecord' import type { ActionMenuRole } from '../ActionMenuRole' import type { ActionMenuState } from '../ActionMenuState' +import type { TagsBase } from '@aries-framework/core' +import { AriesFrameworkError, BaseRecord, utils } from '@aries-framework/core' import { Type } from 'class-transformer' -import { AriesFrameworkError } from '../../../error' -import { BaseRecord } from '../../../storage/BaseRecord' -import { uuid } from '../../../utils/uuid' import { ActionMenuSelection, ActionMenu } from '../models' export interface ActionMenuRecordProps { @@ -51,7 +49,7 @@ export class ActionMenuRecord super() if (props) { - this.id = props.id ?? uuid() + this.id = props.id ?? utils.uuid() this.createdAt = props.createdAt ?? new Date() this.connectionId = props.connectionId this.threadId = props.threadId diff --git a/packages/core/src/modules/action-menu/repository/ActionMenuRepository.ts b/packages/action-menu/src/repository/ActionMenuRepository.ts similarity index 55% rename from packages/core/src/modules/action-menu/repository/ActionMenuRepository.ts rename to packages/action-menu/src/repository/ActionMenuRepository.ts index e22f014ec7..2337fd12c6 100644 --- a/packages/core/src/modules/action-menu/repository/ActionMenuRepository.ts +++ b/packages/action-menu/src/repository/ActionMenuRepository.ts @@ -1,8 +1,4 @@ -import { EventEmitter } from '../../../agent/EventEmitter' -import { InjectionSymbols } from '../../../constants' -import { inject, injectable } from '../../../plugins' -import { Repository } from '../../../storage/Repository' -import { StorageService } from '../../../storage/StorageService' +import { EventEmitter, InjectionSymbols, inject, injectable, Repository, StorageService } from '@aries-framework/core' import { ActionMenuRecord } from './ActionMenuRecord' diff --git a/packages/core/src/modules/action-menu/repository/index.ts b/packages/action-menu/src/repository/index.ts similarity index 100% rename from packages/core/src/modules/action-menu/repository/index.ts rename to packages/action-menu/src/repository/index.ts diff --git a/packages/core/src/modules/action-menu/services/ActionMenuService.ts b/packages/action-menu/src/services/ActionMenuService.ts similarity index 96% rename from packages/core/src/modules/action-menu/services/ActionMenuService.ts rename to packages/action-menu/src/services/ActionMenuService.ts index 2db0d2a1db..c2561f4617 100644 --- a/packages/core/src/modules/action-menu/services/ActionMenuService.ts +++ b/packages/action-menu/src/services/ActionMenuService.ts @@ -1,7 +1,3 @@ -import type { AgentContext } from '../../../agent' -import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import type { Logger } from '../../../logger' -import type { Query } from '../../../storage/StorageService' import type { ActionMenuStateChangedEvent } from '../ActionMenuEvents' import type { ActionMenuProblemReportMessage } from '../messages' import type { @@ -11,12 +7,10 @@ import type { CreateRequestOptions, FindMenuOptions, } from './ActionMenuServiceOptions' +import type { AgentContext, InboundMessageContext, Logger, Query } from '@aries-framework/core' + +import { AgentConfig, EventEmitter, AriesFrameworkError, injectable, JsonTransformer } from '@aries-framework/core' -import { AgentConfig } from '../../../agent/AgentConfig' -import { EventEmitter } from '../../../agent/EventEmitter' -import { AriesFrameworkError } from '../../../error' -import { injectable } from '../../../plugins' -import { JsonTransformer } from '../../../utils' import { ActionMenuEventTypes } from '../ActionMenuEvents' import { ActionMenuRole } from '../ActionMenuRole' import { ActionMenuState } from '../ActionMenuState' diff --git a/packages/core/src/modules/action-menu/services/ActionMenuServiceOptions.ts b/packages/action-menu/src/services/ActionMenuServiceOptions.ts similarity index 91% rename from packages/core/src/modules/action-menu/services/ActionMenuServiceOptions.ts rename to packages/action-menu/src/services/ActionMenuServiceOptions.ts index 733a6d0c76..3a7faa0fd3 100644 --- a/packages/core/src/modules/action-menu/services/ActionMenuServiceOptions.ts +++ b/packages/action-menu/src/services/ActionMenuServiceOptions.ts @@ -1,8 +1,8 @@ -import type { ConnectionRecord } from '../../connections' import type { ActionMenuRole } from '../ActionMenuRole' import type { ActionMenuSelection } from '../models' import type { ActionMenu } from '../models/ActionMenu' import type { ActionMenuRecord } from '../repository' +import type { ConnectionRecord } from '@aries-framework/core' export interface CreateRequestOptions { connection: ConnectionRecord diff --git a/packages/core/src/modules/action-menu/services/__tests__/ActionMenuService.test.ts b/packages/action-menu/src/services/__tests__/ActionMenuService.test.ts similarity index 98% rename from packages/core/src/modules/action-menu/services/__tests__/ActionMenuService.test.ts rename to packages/action-menu/src/services/__tests__/ActionMenuService.test.ts index d8ade2a3ee..909ef60ce1 100644 --- a/packages/core/src/modules/action-menu/services/__tests__/ActionMenuService.test.ts +++ b/packages/action-menu/src/services/__tests__/ActionMenuService.test.ts @@ -1,9 +1,8 @@ -import type { AgentContext } from '../../../../agent' -import type { AgentConfig } from '../../../../agent/AgentConfig' -import type { Repository } from '../../../../storage/Repository' import type { ActionMenuStateChangedEvent } from '../../ActionMenuEvents' import type { ActionMenuSelection } from '../../models' +import type { AgentContext, AgentConfig, Repository } from '@aries-framework/core' +import { DidExchangeState, EventEmitter, InboundMessageContext } from '@aries-framework/core' import { Subject } from 'rxjs' import { @@ -12,10 +11,7 @@ import { getAgentContext, getMockConnection, mockFunction, -} from '../../../../../tests/helpers' -import { EventEmitter } from '../../../../agent/EventEmitter' -import { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' -import { DidExchangeState } from '../../../connections' +} from '../../../../core/tests/helpers' import { ActionMenuEventTypes } from '../../ActionMenuEvents' import { ActionMenuRole } from '../../ActionMenuRole' import { ActionMenuState } from '../../ActionMenuState' diff --git a/packages/core/src/modules/action-menu/services/index.ts b/packages/action-menu/src/services/index.ts similarity index 100% rename from packages/core/src/modules/action-menu/services/index.ts rename to packages/action-menu/src/services/index.ts diff --git a/packages/core/src/modules/action-menu/__tests__/action-menu.e2e.test.ts b/packages/action-menu/tests/action-menu.e2e.test.ts similarity index 75% rename from packages/core/src/modules/action-menu/__tests__/action-menu.e2e.test.ts rename to packages/action-menu/tests/action-menu.e2e.test.ts index 7003f5cc3e..b15524fd93 100644 --- a/packages/core/src/modules/action-menu/__tests__/action-menu.e2e.test.ts +++ b/packages/action-menu/tests/action-menu.e2e.test.ts @@ -1,31 +1,51 @@ -import type { ConnectionRecord } from '../../..' -import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' +import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' +import type { ConnectionRecord } from '@aries-framework/core' +import { Agent } from '@aries-framework/core' import { Subject } from 'rxjs' -import { Agent } from '../../..' -import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' -import { getAgentOptions, makeConnection } from '../../../../tests/helpers' -import testLogger from '../../../../tests/logger' -import { ActionMenuRole } from '../ActionMenuRole' -import { ActionMenuState } from '../ActionMenuState' -import { ActionMenu } from '../models' -import { ActionMenuRecord } from '../repository' +import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { getAgentOptions, makeConnection } from '../../core/tests/helpers' +import testLogger from '../../core/tests/logger' import { waitForActionMenuRecord } from './helpers' -const faberAgentOptions = getAgentOptions('Faber Action Menu', { - endpoints: ['rxjs:faber'], -}) - -const aliceAgentOptions = getAgentOptions('Alice Action Menu', { - endpoints: ['rxjs:alice'], -}) +import { + ActionMenu, + ActionMenuModule, + ActionMenuRecord, + ActionMenuRole, + ActionMenuState, +} from '@aries-framework/action-menu' + +const faberAgentOptions = getAgentOptions( + 'Faber Action Menu', + { + endpoints: ['rxjs:faber'], + }, + { + actionMenu: new ActionMenuModule(), + } +) + +const aliceAgentOptions = getAgentOptions( + 'Alice Action Menu', + { + endpoints: ['rxjs:alice'], + }, + { + actionMenu: new ActionMenuModule(), + } +) describe('Action Menu', () => { - let faberAgent: Agent - let aliceAgent: Agent + let faberAgent: Agent<{ + actionMenu: ActionMenuModule + }> + let aliceAgent: Agent<{ + actionMenu: ActionMenuModule + }> let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord @@ -92,7 +112,7 @@ describe('Action Menu', () => { test('Alice requests menu to Faber and selects an option once received', async () => { testLogger.test('Alice sends menu request to Faber') - let aliceActionMenuRecord = await aliceAgent.actionMenu.requestMenu({ connectionId: aliceConnection.id }) + let aliceActionMenuRecord = await aliceAgent.modules.actionMenu.requestMenu({ connectionId: aliceConnection.id }) testLogger.test('Faber waits for menu request from Alice') await waitForActionMenuRecord(faberAgent, { @@ -100,7 +120,7 @@ describe('Action Menu', () => { }) testLogger.test('Faber sends root menu to Alice') - await faberAgent.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) + await faberAgent.modules.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) testLogger.test('Alice waits until she receives menu') aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { @@ -108,7 +128,7 @@ describe('Action Menu', () => { }) expect(aliceActionMenuRecord.menu).toEqual(rootMenu) - const faberActiveMenu = await faberAgent.actionMenu.findActiveMenu({ + const faberActiveMenu = await faberAgent.modules.actionMenu.findActiveMenu({ connectionId: faberConnection.id, role: ActionMenuRole.Responder, }) @@ -116,7 +136,7 @@ describe('Action Menu', () => { expect(faberActiveMenu?.state).toBe(ActionMenuState.AwaitingSelection) testLogger.test('Alice selects menu item') - await aliceAgent.actionMenu.performAction({ + await aliceAgent.modules.actionMenu.performAction({ connectionId: aliceConnection.id, performedAction: { name: 'option-1' }, }) @@ -127,7 +147,7 @@ describe('Action Menu', () => { }) // As Alice has responded, menu should be closed (done state) - const aliceActiveMenu = await aliceAgent.actionMenu.findActiveMenu({ + const aliceActiveMenu = await aliceAgent.modules.actionMenu.findActiveMenu({ connectionId: aliceConnection.id, role: ActionMenuRole.Requester, }) @@ -137,7 +157,7 @@ describe('Action Menu', () => { test('Faber sends root menu and Alice selects an option', async () => { testLogger.test('Faber sends root menu to Alice') - await faberAgent.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) + await faberAgent.modules.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) testLogger.test('Alice waits until she receives menu') const aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { @@ -145,7 +165,7 @@ describe('Action Menu', () => { }) expect(aliceActionMenuRecord.menu).toEqual(rootMenu) - const faberActiveMenu = await faberAgent.actionMenu.findActiveMenu({ + const faberActiveMenu = await faberAgent.modules.actionMenu.findActiveMenu({ connectionId: faberConnection.id, role: ActionMenuRole.Responder, }) @@ -153,7 +173,7 @@ describe('Action Menu', () => { expect(faberActiveMenu?.state).toBe(ActionMenuState.AwaitingSelection) testLogger.test('Alice selects menu item') - await aliceAgent.actionMenu.performAction({ + await aliceAgent.modules.actionMenu.performAction({ connectionId: aliceConnection.id, performedAction: { name: 'option-1' }, }) @@ -164,7 +184,7 @@ describe('Action Menu', () => { }) // As Alice has responded, menu should be closed (done state) - const aliceActiveMenu = await aliceAgent.actionMenu.findActiveMenu({ + const aliceActiveMenu = await aliceAgent.modules.actionMenu.findActiveMenu({ connectionId: aliceConnection.id, role: ActionMenuRole.Requester, }) @@ -174,7 +194,7 @@ describe('Action Menu', () => { test('Menu navigation', async () => { testLogger.test('Faber sends root menu ') - let faberActionMenuRecord = await faberAgent.actionMenu.sendMenu({ + let faberActionMenuRecord = await faberAgent.modules.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu, }) @@ -190,7 +210,7 @@ describe('Action Menu', () => { expect(aliceActionMenuRecord.threadId).toEqual(rootThreadId) testLogger.test('Alice selects menu item 1') - await aliceAgent.actionMenu.performAction({ + await aliceAgent.modules.actionMenu.performAction({ connectionId: aliceConnection.id, performedAction: { name: 'option-1' }, }) @@ -201,7 +221,7 @@ describe('Action Menu', () => { }) // As Alice has responded, menu should be closed (done state) - let aliceActiveMenu = await aliceAgent.actionMenu.findActiveMenu({ + let aliceActiveMenu = await aliceAgent.modules.actionMenu.findActiveMenu({ connectionId: aliceConnection.id, role: ActionMenuRole.Requester, }) @@ -210,7 +230,7 @@ describe('Action Menu', () => { expect(aliceActiveMenu?.threadId).toEqual(rootThreadId) testLogger.test('Faber sends submenu to Alice') - faberActionMenuRecord = await faberAgent.actionMenu.sendMenu({ + faberActionMenuRecord = await faberAgent.modules.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: submenu1, }) @@ -224,7 +244,7 @@ describe('Action Menu', () => { expect(aliceActionMenuRecord.threadId).toEqual(rootThreadId) testLogger.test('Alice selects menu item 1-1') - await aliceAgent.actionMenu.performAction({ + await aliceAgent.modules.actionMenu.performAction({ connectionId: aliceConnection.id, performedAction: { name: 'option-1-1' }, }) @@ -235,7 +255,7 @@ describe('Action Menu', () => { }) // As Alice has responded, menu should be closed (done state) - aliceActiveMenu = await aliceAgent.actionMenu.findActiveMenu({ + aliceActiveMenu = await aliceAgent.modules.actionMenu.findActiveMenu({ connectionId: aliceConnection.id, role: ActionMenuRole.Requester, }) @@ -244,7 +264,7 @@ describe('Action Menu', () => { expect(aliceActiveMenu?.threadId).toEqual(rootThreadId) testLogger.test('Alice sends menu request to Faber') - aliceActionMenuRecord = await aliceAgent.actionMenu.requestMenu({ connectionId: aliceConnection.id }) + aliceActionMenuRecord = await aliceAgent.modules.actionMenu.requestMenu({ connectionId: aliceConnection.id }) testLogger.test('Faber waits for menu request from Alice') faberActionMenuRecord = await waitForActionMenuRecord(faberAgent, { @@ -259,7 +279,7 @@ describe('Action Menu', () => { test('Menu clearing', async () => { testLogger.test('Faber sends root menu to Alice') - await faberAgent.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) + await faberAgent.modules.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) testLogger.test('Alice waits until she receives menu') let aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { @@ -267,20 +287,20 @@ describe('Action Menu', () => { }) expect(aliceActionMenuRecord.menu).toEqual(rootMenu) - let faberActiveMenu = await faberAgent.actionMenu.findActiveMenu({ + let faberActiveMenu = await faberAgent.modules.actionMenu.findActiveMenu({ connectionId: faberConnection.id, role: ActionMenuRole.Responder, }) expect(faberActiveMenu).toBeInstanceOf(ActionMenuRecord) expect(faberActiveMenu?.state).toBe(ActionMenuState.AwaitingSelection) - await faberAgent.actionMenu.clearActiveMenu({ + await faberAgent.modules.actionMenu.clearActiveMenu({ connectionId: faberConnection.id, role: ActionMenuRole.Responder, }) testLogger.test('Alice selects menu item') - await aliceAgent.actionMenu.performAction({ + await aliceAgent.modules.actionMenu.performAction({ connectionId: aliceConnection.id, performedAction: { name: 'option-1' }, }) @@ -295,7 +315,7 @@ describe('Action Menu', () => { }) testLogger.test('Alice request a new menu') - await aliceAgent.actionMenu.requestMenu({ + await aliceAgent.modules.actionMenu.requestMenu({ connectionId: aliceConnection.id, }) @@ -305,7 +325,7 @@ describe('Action Menu', () => { }) testLogger.test('Faber sends root menu to Alice') - await faberAgent.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) + await faberAgent.modules.actionMenu.sendMenu({ connectionId: faberConnection.id, menu: rootMenu }) testLogger.test('Alice waits until she receives menu') aliceActionMenuRecord = await waitForActionMenuRecord(aliceAgent, { @@ -313,15 +333,15 @@ describe('Action Menu', () => { }) expect(aliceActionMenuRecord.menu).toEqual(rootMenu) - faberActiveMenu = await faberAgent.actionMenu.findActiveMenu({ + faberActiveMenu = await faberAgent.modules.actionMenu.findActiveMenu({ connectionId: faberConnection.id, role: ActionMenuRole.Responder, }) expect(faberActiveMenu).toBeInstanceOf(ActionMenuRecord) expect(faberActiveMenu?.state).toBe(ActionMenuState.AwaitingSelection) - /*testLogger.test('Alice selects menu item') - await aliceAgent.actionMenu.performAction({ + testLogger.test('Alice selects menu item') + await aliceAgent.modules.actionMenu.performAction({ connectionId: aliceConnection.id, performedAction: { name: 'option-1' }, }) @@ -329,6 +349,6 @@ describe('Action Menu', () => { testLogger.test('Faber waits for menu selection from Alice') await waitForActionMenuRecord(faberAgent, { state: ActionMenuState.Done, - })*/ + }) }) }) diff --git a/packages/core/src/modules/action-menu/__tests__/helpers.ts b/packages/action-menu/tests/helpers.ts similarity index 85% rename from packages/core/src/modules/action-menu/__tests__/helpers.ts rename to packages/action-menu/tests/helpers.ts index 8d0c6c48d6..c4044b448b 100644 --- a/packages/core/src/modules/action-menu/__tests__/helpers.ts +++ b/packages/action-menu/tests/helpers.ts @@ -1,12 +1,10 @@ -import type { Agent } from '../../../agent/Agent' -import type { ActionMenuStateChangedEvent } from '../ActionMenuEvents' -import type { ActionMenuRole } from '../ActionMenuRole' -import type { ActionMenuState } from '../ActionMenuState' +import type { ActionMenuStateChangedEvent, ActionMenuRole, ActionMenuState } from '@aries-framework/action-menu' +import type { Agent } from '@aries-framework/core' import type { Observable } from 'rxjs' import { catchError, filter, firstValueFrom, map, ReplaySubject, timeout } from 'rxjs' -import { ActionMenuEventTypes } from '../ActionMenuEvents' +import { ActionMenuEventTypes } from '@aries-framework/action-menu' export async function waitForActionMenuRecord( agent: Agent, diff --git a/packages/action-menu/tsconfig.build.json b/packages/action-menu/tsconfig.build.json new file mode 100644 index 0000000000..2b75d0adab --- /dev/null +++ b/packages/action-menu/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["src/**/*"] +} diff --git a/packages/action-menu/tsconfig.json b/packages/action-menu/tsconfig.json new file mode 100644 index 0000000000..46efe6f721 --- /dev/null +++ b/packages/action-menu/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + } +} diff --git a/packages/core/src/agent/AgentModules.ts b/packages/core/src/agent/AgentModules.ts index 5a0ed49d24..05055ebf97 100644 --- a/packages/core/src/agent/AgentModules.ts +++ b/packages/core/src/agent/AgentModules.ts @@ -2,7 +2,6 @@ import type { Module, DependencyManager } from '../plugins' import type { Constructor } from '../utils/mixins' import type { AgentConfig } from './AgentConfig' -import { ActionMenuModule } from '../modules/action-menu' import { BasicMessagesModule } from '../modules/basic-messages' import { ConnectionsModule } from '../modules/connections' import { CredentialsModule } from '../modules/credentials' @@ -117,7 +116,6 @@ function getDefaultAgentModules(agentConfig: AgentConfig) { mediatorPollingInterval: agentConfig.mediatorPollingInterval, }), basicMessages: () => new BasicMessagesModule(), - actionMenu: () => new ActionMenuModule(), genericRecords: () => new GenericRecordsModule(), ledger: () => new LedgerModule({ diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index 847e8ffe57..00477a6b5c 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -5,7 +5,6 @@ import type { AgentApi, EmptyModuleMap, ModulesMap, WithoutDefaultModules } from import type { TransportSession } from './TransportService' import { AriesFrameworkError } from '../error' -import { ActionMenuApi } from '../modules/action-menu' import { BasicMessagesApi } from '../modules/basic-messages' import { ConnectionsApi } from '../modules/connections' import { CredentialsApi } from '../modules/credentials' @@ -48,7 +47,6 @@ export abstract class BaseAgent { expect(protocols).toEqual( expect.arrayContaining([ - 'https://didcomm.org/action-menu/1.0', 'https://didcomm.org/basicmessage/1.0', 'https://didcomm.org/connections/1.0', 'https://didcomm.org/coordinate-mediation/1.0', @@ -268,6 +267,6 @@ describe('Agent', () => { 'https://didcomm.org/revocation_notification/2.0', ]) ) - expect(protocols.length).toEqual(15) + expect(protocols.length).toEqual(14) }) }) diff --git a/packages/core/src/agent/__tests__/AgentModules.test.ts b/packages/core/src/agent/__tests__/AgentModules.test.ts index ba632aeca8..cfb88ab7b0 100644 --- a/packages/core/src/agent/__tests__/AgentModules.test.ts +++ b/packages/core/src/agent/__tests__/AgentModules.test.ts @@ -1,7 +1,6 @@ import type { Module } from '../../plugins' import { getAgentConfig } from '../../../tests/helpers' -import { ActionMenuModule } from '../../modules/action-menu' import { BasicMessagesModule } from '../../modules/basic-messages' import { ConnectionsModule } from '../../modules/connections' import { CredentialsModule } from '../../modules/credentials' @@ -65,7 +64,6 @@ describe('AgentModules', () => { mediator: expect.any(MediatorModule), mediationRecipient: expect.any(RecipientModule), basicMessages: expect.any(BasicMessagesModule), - actionMenu: expect.any(ActionMenuModule), genericRecords: expect.any(GenericRecordsModule), ledger: expect.any(LedgerModule), discovery: expect.any(DiscoverFeaturesModule), @@ -90,7 +88,6 @@ describe('AgentModules', () => { mediator: expect.any(MediatorModule), mediationRecipient: expect.any(RecipientModule), basicMessages: expect.any(BasicMessagesModule), - actionMenu: expect.any(ActionMenuModule), genericRecords: expect.any(GenericRecordsModule), ledger: expect.any(LedgerModule), discovery: expect.any(DiscoverFeaturesModule), @@ -118,7 +115,6 @@ describe('AgentModules', () => { mediator: expect.any(MediatorModule), mediationRecipient: expect.any(RecipientModule), basicMessages: expect.any(BasicMessagesModule), - actionMenu: expect.any(ActionMenuModule), genericRecords: expect.any(GenericRecordsModule), ledger: expect.any(LedgerModule), discovery: expect.any(DiscoverFeaturesModule), diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 9475848a1a..1c88354a33 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -41,11 +41,11 @@ export { Attachment } from './decorators/attachment/Attachment' export * from './plugins' export * from './transport' -export * from './modules/action-menu' export * from './modules/basic-messages' export * from './modules/common' export * from './modules/credentials' export * from './modules/discover-features' +export * from './modules/problem-reports' export * from './modules/proofs' export * from './modules/connections' export * from './modules/ledger' diff --git a/packages/question-answer/README.md b/packages/question-answer/README.md index fe141eddd6..00ebca6637 100644 --- a/packages/question-answer/README.md +++ b/packages/question-answer/README.md @@ -6,7 +6,7 @@ height="250px" />

-

Aries Framework JavaScript - Question Answer

+

Aries Framework JavaScript Question Answer Plugin


-TODO +Question Answer plugin for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). Implements [Aries RFC 0113](https://github.com/hyperledger/aries-rfcs/blob/1795d5c2d36f664f88f5e8045042ace8e573808c/features/0113-question-answer/README.md). + +### Installation + +Make sure you have set up the correct version of Aries Framework JavaScript according to the AFJ repository. To find out which version of AFJ you need to have installed you can run the following command. This will list the required peer dependency for `@aries-framework/core`. + +```sh +npm info "@aries-framework/question-answer" peerDependencies +``` + +Then add the question-answer plugin to your project. + +```sh +yarn add @aries-framework/question-answer +``` + +### Quick start + +In order for this plugin to work, we have to inject it into the agent to access agent functionality. See the example for more information. + +### Example of usage + +```ts +import { QuestionAnswerModule } from '@aries-framework/question-answer' + +const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, + modules: { + questionAnswer: new QuestionAnswerModule(), + /* other custom modules */ + }, +}) + +await agent.initialize() + +// To send a question to a given connection +await agent.modules.questionAnswer.sendQuestion(connectionId, { + question: 'Do you want to play?', + validResponses: [{ text: 'Yes' }, { text: 'No' }], +}) + +// Questions and Answers are received as QuestionAnswerStateChangedEvent + +// To send an answer related to a given question answer record +await agent.modules.questionAnswer.sendAnswer(questionAnswerRecordId, 'Yes') +``` diff --git a/packages/question-answer/package.json b/packages/question-answer/package.json index 650edab91e..f1e6b5f5e7 100644 --- a/packages/question-answer/package.json +++ b/packages/question-answer/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/question-answer", "main": "build/index", "types": "build/index", - "version": "0.2.2", + "version": "0.2.4", "private": true, "files": [ "build" @@ -25,13 +25,14 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.4", "rxjs": "^7.2.0", "class-transformer": "0.5.1", "class-validator": "0.13.1" }, + "peerDependencies": { + "@aries-framework/core": "0.2.4" + }, "devDependencies": { - "@aries-framework/core": "0.2.4", "@aries-framework/node": "0.2.4", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", diff --git a/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts b/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts index ae6432ffb9..bd3f071a01 100644 --- a/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts +++ b/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts @@ -11,9 +11,7 @@ import { import { agentDependencies } from '@aries-framework/node' import { Subject } from 'rxjs' -import { mockFunction } from '../../../core/tests/helpers' - -import { getAgentConfig, getAgentContext, getMockConnection } from './utils' +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../core/tests/helpers' import { QuestionAnswerRecord, diff --git a/packages/question-answer/src/__tests__/utils.ts b/packages/question-answer/src/__tests__/utils.ts deleted file mode 100644 index a249af58e1..0000000000 --- a/packages/question-answer/src/__tests__/utils.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { ConnectionRecordProps, InitConfig, Wallet } from '@aries-framework/core' - -import { - AgentContext, - DependencyManager, - InjectionSymbols, - AgentConfig, - ConnectionRecord, - DidExchangeRole, - DidExchangeState, -} from '@aries-framework/core' -import { agentDependencies } from '@aries-framework/node' - -export function getMockConnection({ - state = DidExchangeState.InvitationReceived, - role = DidExchangeRole.Requester, - id = 'test', - did = 'test-did', - threadId = 'threadId', - tags = {}, - theirLabel, - theirDid = 'their-did', -}: Partial = {}) { - return new ConnectionRecord({ - did, - threadId, - theirDid, - id, - role, - state, - tags, - theirLabel, - }) -} - -export function getAgentConfig(name: string, extraConfig: Partial = {}) { - const { config, agentDependencies } = getBaseConfig(name, extraConfig) - return new AgentConfig(config, agentDependencies) -} - -const publicDidSeed = process.env.TEST_AGENT_PUBLIC_DID_SEED ?? '000000000000000000000000Trustee9' -export function getBaseConfig(name: string, extraConfig: Partial = {}) { - const config: InitConfig = { - label: `Agent: ${name}`, - walletConfig: { - id: `Wallet: ${name}`, - key: `Key: ${name}`, - }, - publicDidSeed, - autoAcceptConnections: true, - connectToIndyLedgersOnStartup: false, - ...extraConfig, - } - - return { config, agentDependencies } as const -} - -export function getAgentContext({ - dependencyManager = new DependencyManager(), - wallet, - agentConfig, - contextCorrelationId = 'mock', -}: { - dependencyManager?: DependencyManager - wallet?: Wallet - agentConfig?: AgentConfig - contextCorrelationId?: string -} = {}) { - if (wallet) dependencyManager.registerInstance(InjectionSymbols.Wallet, wallet) - if (agentConfig) dependencyManager.registerInstance(AgentConfig, agentConfig) - return new AgentContext({ dependencyManager, contextCorrelationId }) -} From 72260cd6052fc519be365cc90b7188120d33ecfd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 16:34:27 +0200 Subject: [PATCH 054/125] chore(release): v0.2.5 (#1052) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 15 +++++++++++++++ lerna.json | 2 +- packages/core/CHANGELOG.md | 15 +++++++++++++++ packages/core/package.json | 2 +- packages/node/CHANGELOG.md | 4 ++++ packages/node/package.json | 4 ++-- packages/react-native/CHANGELOG.md | 4 ++++ packages/react-native/package.json | 4 ++-- 8 files changed, 44 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f28623e4f5..47323c1ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.5](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.4...v0.2.5) (2022-10-13) + +### Bug Fixes + +- **oob:** allow encoding in content type header ([#1037](https://github.com/hyperledger/aries-framework-javascript/issues/1037)) ([e1d6592](https://github.com/hyperledger/aries-framework-javascript/commit/e1d6592b818bc4348078ca6593eea4641caafae5)) +- **oob:** set connection alias when creating invitation ([#1047](https://github.com/hyperledger/aries-framework-javascript/issues/1047)) ([7be979a](https://github.com/hyperledger/aries-framework-javascript/commit/7be979a74b86c606db403c8df04cfc8be2aae249)) + +### Features + +- connection type ([#994](https://github.com/hyperledger/aries-framework-javascript/issues/994)) ([0d14a71](https://github.com/hyperledger/aries-framework-javascript/commit/0d14a7157e2118592829109dbc5c793faee1e201)) +- expose findAllByQuery method in modules and services ([#1044](https://github.com/hyperledger/aries-framework-javascript/issues/1044)) ([9dd95e8](https://github.com/hyperledger/aries-framework-javascript/commit/9dd95e81770d3140558196d2b5b508723f918f04)) +- improve sending error handling ([#1045](https://github.com/hyperledger/aries-framework-javascript/issues/1045)) ([a230841](https://github.com/hyperledger/aries-framework-javascript/commit/a230841aa99102bcc8b60aa2a23040f13a929a6c)) +- possibility to set masterSecretId inside of WalletConfig ([#1043](https://github.com/hyperledger/aries-framework-javascript/issues/1043)) ([8a89ad2](https://github.com/hyperledger/aries-framework-javascript/commit/8a89ad2624922e5e5455f8881d1ccc656d6b33ec)) +- use did:key flag ([#1029](https://github.com/hyperledger/aries-framework-javascript/issues/1029)) ([8efade5](https://github.com/hyperledger/aries-framework-javascript/commit/8efade5b2a885f0767ac8b10cba8582fe9ff486a)) + ## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) ### Bug Fixes diff --git a/lerna.json b/lerna.json index 86f806459b..103d37573a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.2.4", + "version": "0.2.5", "useWorkspaces": true, "npmClient": "yarn", "command": { diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 20c7b345be..f30627f4af 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.5](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.4...v0.2.5) (2022-10-13) + +### Bug Fixes + +- **oob:** allow encoding in content type header ([#1037](https://github.com/hyperledger/aries-framework-javascript/issues/1037)) ([e1d6592](https://github.com/hyperledger/aries-framework-javascript/commit/e1d6592b818bc4348078ca6593eea4641caafae5)) +- **oob:** set connection alias when creating invitation ([#1047](https://github.com/hyperledger/aries-framework-javascript/issues/1047)) ([7be979a](https://github.com/hyperledger/aries-framework-javascript/commit/7be979a74b86c606db403c8df04cfc8be2aae249)) + +### Features + +- connection type ([#994](https://github.com/hyperledger/aries-framework-javascript/issues/994)) ([0d14a71](https://github.com/hyperledger/aries-framework-javascript/commit/0d14a7157e2118592829109dbc5c793faee1e201)) +- expose findAllByQuery method in modules and services ([#1044](https://github.com/hyperledger/aries-framework-javascript/issues/1044)) ([9dd95e8](https://github.com/hyperledger/aries-framework-javascript/commit/9dd95e81770d3140558196d2b5b508723f918f04)) +- improve sending error handling ([#1045](https://github.com/hyperledger/aries-framework-javascript/issues/1045)) ([a230841](https://github.com/hyperledger/aries-framework-javascript/commit/a230841aa99102bcc8b60aa2a23040f13a929a6c)) +- possibility to set masterSecretId inside of WalletConfig ([#1043](https://github.com/hyperledger/aries-framework-javascript/issues/1043)) ([8a89ad2](https://github.com/hyperledger/aries-framework-javascript/commit/8a89ad2624922e5e5455f8881d1ccc656d6b33ec)) +- use did:key flag ([#1029](https://github.com/hyperledger/aries-framework-javascript/issues/1029)) ([8efade5](https://github.com/hyperledger/aries-framework-javascript/commit/8efade5b2a885f0767ac8b10cba8582fe9ff486a)) + ## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) ### Bug Fixes diff --git a/packages/core/package.json b/packages/core/package.json index eeb795b516..5a51d0f9cc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/core", "main": "build/index", "types": "build/index", - "version": "0.2.4", + "version": "0.2.5", "files": [ "build" ], diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index 1a01719007..7bda831ff2 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.5](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.4...v0.2.5) (2022-10-13) + +**Note:** Version bump only for package @aries-framework/node + ## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) **Note:** Version bump only for package @aries-framework/node diff --git a/packages/node/package.json b/packages/node/package.json index e4faa84365..9ad1abfb3b 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/node", "main": "build/index", "types": "build/index", - "version": "0.2.4", + "version": "0.2.5", "files": [ "build", "bin" @@ -28,7 +28,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.4", + "@aries-framework/core": "0.2.5", "express": "^4.17.1", "ffi-napi": "^4.0.3", "indy-sdk": "^1.16.0-dev-1636", diff --git a/packages/react-native/CHANGELOG.md b/packages/react-native/CHANGELOG.md index d693eee5df..31568ef45b 100644 --- a/packages/react-native/CHANGELOG.md +++ b/packages/react-native/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.5](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.4...v0.2.5) (2022-10-13) + +**Note:** Version bump only for package @aries-framework/react-native + ## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) **Note:** Version bump only for package @aries-framework/react-native diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 628c06a80d..fa2f4faf36 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/react-native", "main": "build/index", "types": "build/index", - "version": "0.2.4", + "version": "0.2.5", "files": [ "build" ], @@ -24,7 +24,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.4", + "@aries-framework/core": "0.2.5", "@azure/core-asynciterator-polyfill": "^1.0.0", "events": "^3.3.0" }, From db184a81b97b79abd756597fd5e5333df90e24b0 Mon Sep 17 00:00:00 2001 From: NB-MikeRichardson <93971245+NB-MikeRichardson@users.noreply.github.com> Date: Thu, 20 Oct 2022 10:20:29 +0300 Subject: [PATCH 055/125] refactor: fix inconsistencies in issue credential and present proof api (#1050) Signed-off-by: Mike Richardson --- .../src/modules/credentials/CredentialsApi.ts | 32 ++++-- .../protocol/v2/V2CredentialService.ts | 1 - .../core/src/modules/proofs/ProofService.ts | 7 +- packages/core/src/modules/proofs/ProofsApi.ts | 53 +++++++++- .../src/modules/proofs/ProofsApiOptions.ts | 15 ++- .../src/modules/proofs/__tests__/groupKeys.ts | 31 ++++++ .../proofs/formats/indy/IndyProofFormat.ts | 22 ++--- .../indy/IndyProofFormatsServiceOptions.ts | 18 ++-- .../proofs/models/ProofServiceOptions.ts | 13 +++ .../proofs/protocol/v1/V1ProofService.ts | 59 +++++++++++ .../messages/V1RequestPresentationMessage.ts | 14 ++- .../proofs/protocol/v2/V2ProofService.ts | 79 +++++++++++++++ packages/core/tests/v1-indy-proofs.test.ts | 88 +++++++++++++++++ packages/core/tests/v2-indy-proofs.test.ts | 99 +++++++++++++++++++ 14 files changed, 479 insertions(+), 52 deletions(-) create mode 100644 packages/core/src/modules/proofs/__tests__/groupKeys.ts diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index e6f48e1b53..d95933780d 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -45,31 +45,33 @@ import { V2CredentialService } from './protocol/v2/V2CredentialService' import { CredentialRepository } from './repository/CredentialRepository' export interface CredentialsApi[]> { - // Proposal methods + // Propose Credential methods proposeCredential(options: ProposeCredentialOptions): Promise acceptProposal(options: AcceptProposalOptions): Promise negotiateProposal(options: NegotiateProposalOptions): Promise - // Offer methods + // Offer Credential Methods offerCredential(options: OfferCredentialOptions): Promise acceptOffer(options: AcceptOfferOptions): Promise declineOffer(credentialRecordId: string): Promise negotiateOffer(options: NegotiateOfferOptions): Promise - // out of band - createOffer(options: CreateOfferOptions): Promise<{ - message: AgentMessage - credentialRecord: CredentialExchangeRecord - }> - // Request + + // Request Credential Methods // This is for beginning the exchange with a request (no proposal or offer). Only possible // (currently) with W3C. We will not implement this in phase I - // requestCredential(credentialOptions: RequestCredentialOptions): Promise // when the issuer accepts the request he issues the credential to the holder acceptRequest(options: AcceptRequestOptions): Promise - // Credential + // Issue Credential Methods acceptCredential(options: AcceptCredentialOptions): Promise + + // out of band + createOffer(options: CreateOfferOptions): Promise<{ + message: AgentMessage + credentialRecord: CredentialExchangeRecord + }> + sendProblemReport(options: SendProblemReportOptions): Promise // Record Methods @@ -78,6 +80,7 @@ export interface CredentialsApi findById(credentialRecordId: string): Promise deleteById(credentialRecordId: string, options?: DeleteCredentialOptions): Promise + update(credentialRecord: CredentialExchangeRecord): Promise getFormatData(credentialRecordId: string): Promise> // DidComm Message Records @@ -613,6 +616,15 @@ export class CredentialsApi< return service.delete(this.agentContext, credentialRecord, options) } + /** + * Update a credential exchange record + * + * @param credentialRecord the credential exchange record + */ + public async update(credentialRecord: CredentialExchangeRecord): Promise { + await this.credentialRepository.update(this.agentContext, credentialRecord) + } + public async findProposalMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts index 177af825f4..4f6ce51440 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts @@ -1091,7 +1091,6 @@ export class V2CredentialService { routingService: RoutingService ): void + public abstract findProposalMessage(agentContext: AgentContext, proofRecordId: string): Promise public abstract findRequestMessage(agentContext: AgentContext, proofRecordId: string): Promise - public abstract findPresentationMessage( agentContext: AgentContext, proofRecordId: string ): Promise - public abstract findProposalMessage(agentContext: AgentContext, proofRecordId: string): Promise - public async saveOrUpdatePresentationMessage( agentContext: AgentContext, options: { @@ -249,4 +248,6 @@ export abstract class ProofService { agentContext: AgentContext, options: CreateProofRequestFromProposalOptions ): Promise> + + public abstract getFormatData(agentContext: AgentContext, proofRecordId: string): Promise> } diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index b46b559e54..c82b09c9ef 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -5,6 +5,9 @@ import type { AcceptPresentationOptions, AcceptProposalOptions, CreateProofRequestOptions, + FindPresentationMessageReturn, + FindProposalMessageReturn, + FindRequestMessageReturn, ProposeProofOptions, RequestProofOptions, ServiceMap, @@ -24,6 +27,7 @@ import type { FormatRequestedCredentialReturn, FormatRetrievedCredentialOptions, DeleteProofOptions, + GetFormatDataReturn, } from './models/ProofServiceOptions' import type { ProofRecord } from './repository/ProofRecord' @@ -59,15 +63,15 @@ export interface ProofsApi): Promise declineRequest(proofRecordId: string): Promise + // Present + acceptPresentation(proofRecordId: string): Promise + // out of band createRequest(options: CreateProofRequestOptions): Promise<{ message: AgentMessage proofRecord: ProofRecord }> - // Present - acceptPresentation(proofRecordId: string): Promise - // Auto Select autoSelectCredentialsForProofRequest( options: AutoSelectCredentialsForProofRequestOptions @@ -84,9 +88,15 @@ export interface ProofsApi findAllByQuery(query: Query): Promise getById(proofRecordId: string): Promise - deleteById(proofId: string): Promise findById(proofRecordId: string): Promise + deleteById(proofId: string, options?: DeleteProofOptions): Promise update(proofRecord: ProofRecord): Promise + getFormatData(proofRecordId: string): Promise> + + // DidComm Message Records + findProposalMessage(proofRecordId: string): Promise> + findRequestMessage(proofRecordId: string): Promise> + findPresentationMessage(proofRecordId: string): Promise> } @injectable() @@ -334,6 +344,13 @@ export class ProofsApi< } } + /** + * Initiate a new presentation exchange as verifier by sending an out of band presentation + * request message + * + * @param options multiple properties like protocol version, proof Formats to build the proof request + * @returns the message itself and the proof record associated with the sent request message + */ public async createRequest(options: CreateProofRequestOptions): Promise<{ message: AgentMessage proofRecord: ProofRecord @@ -484,6 +501,14 @@ export class ProofsApi< return record } + + public async getFormatData(proofRecordId: string): Promise> { + const proofRecord = await this.getById(proofRecordId) + const service = this.getService(proofRecord.protocolVersion) + + return service.getFormatData(this.agentContext, proofRecordId) + } + /** * Retrieve all proof records * @@ -565,10 +590,28 @@ export class ProofsApi< * * @param proofRecord the proof record */ - public async update(proofRecord: ProofRecord) { + public async update(proofRecord: ProofRecord): Promise { await this.proofRepository.update(this.agentContext, proofRecord) } + public async findProposalMessage(proofRecordId: string): Promise> { + const record = await this.getById(proofRecordId) + const service = this.getService(record.protocolVersion) + return service.findProposalMessage(this.agentContext, proofRecordId) + } + + public async findRequestMessage(proofRecordId: string): Promise> { + const record = await this.getById(proofRecordId) + const service = this.getService(record.protocolVersion) + return service.findRequestMessage(this.agentContext, proofRecordId) + } + + public async findPresentationMessage(proofRecordId: string): Promise> { + const record = await this.getById(proofRecordId) + const service = this.getService(record.protocolVersion) + return service.findPresentationMessage(this.agentContext, proofRecordId) + } + private registerHandlers(dispatcher: Dispatcher, mediationRecipientService: MediationRecipientService) { for (const service of Object.values(this.serviceMap)) { const proofService = service as ProofService diff --git a/packages/core/src/modules/proofs/ProofsApiOptions.ts b/packages/core/src/modules/proofs/ProofsApiOptions.ts index 1e820b75d7..00fdf53a30 100644 --- a/packages/core/src/modules/proofs/ProofsApiOptions.ts +++ b/packages/core/src/modules/proofs/ProofsApiOptions.ts @@ -4,20 +4,25 @@ import type { AutoAcceptProof } from './models' import type { ProofConfig } from './models/ModuleOptions' /** - * Get the supported protocol versions based on the provided credential services. + * Get the supported protocol versions based on the provided proof services. */ export type ProtocolVersionType = PSs[number]['version'] +export type FindProposalMessageReturn = ReturnType +export type FindRequestMessageReturn = ReturnType +export type FindPresentationMessageReturn = ReturnType< + PSs[number]['findPresentationMessage'] +> /** - * Get the service map for usage in the credentials module. Will return a type mapping of protocol version to service. + * Get the service map for usage in the proofs module. Will return a type mapping of protocol version to service. * * @example * ``` - * type CredentialServiceMap = ServiceMap<[IndyCredentialFormat], [V1CredentialService]> + * type ProofServiceMap = ServiceMap<[IndyProofFormat], [V1ProofService]> * * // equal to - * type CredentialServiceMap = { - * v1: V1CredentialService + * type ProofServiceMap = { + * v1: V1ProofService * } * ``` */ diff --git a/packages/core/src/modules/proofs/__tests__/groupKeys.ts b/packages/core/src/modules/proofs/__tests__/groupKeys.ts new file mode 100644 index 0000000000..e20144792f --- /dev/null +++ b/packages/core/src/modules/proofs/__tests__/groupKeys.ts @@ -0,0 +1,31 @@ +import type { IndyProofFormat } from '../formats/indy/IndyProofFormat' +import type { GetFormatDataReturn } from '../models/ProofServiceOptions' + +import { AriesFrameworkError } from '../../../error' + +export function getGroupKeysFromIndyProofFormatData(formatData: GetFormatDataReturn<[IndyProofFormat]>): { + proposeKey1: string + proposeKey2: string + requestKey1: string + requestKey2: string +} { + const proofRequest = formatData.request?.indy + const proofProposal = formatData.proposal?.indy + if (!proofProposal) { + throw new AriesFrameworkError('missing indy proof proposal') + } + if (!proofRequest) { + throw new AriesFrameworkError('missing indy proof request') + } + const proposeAttributes = proofProposal.requested_attributes + const proposePredicates = proofProposal.requested_predicates + const requestAttributes = proofRequest.requested_attributes + const requestPredicates = proofRequest.requested_predicates + + const proposeKey1 = Object.keys(proposeAttributes)[1] + const proposeKey2 = Object.keys(proposePredicates)[0] + const requestKey1 = Object.keys(requestAttributes)[1] + const requestKey2 = Object.keys(requestPredicates)[0] + + return { proposeKey1, proposeKey2, requestKey1, requestKey2 } +} diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts index a0e9d893c4..1f6692e75a 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts @@ -9,6 +9,7 @@ import type { IndyRequestProofFormat } from '../indy/IndyProofFormatsServiceOpti import type { RequestedAttribute } from './models/RequestedAttribute' import type { IndyRequestedCredentialsOptions } from './models/RequestedCredentials' import type { RequestedPredicate } from './models/RequestedPredicate' +import type { IndyProof, IndyProofRequest } from 'indy-sdk' export interface IndyProposeProofFormat { attributes?: PresentationPreviewAttribute[] @@ -59,19 +60,10 @@ export interface IndyProofFormat extends ProofFormat { requestCredentials: IndyRequestedCredentialsFormat retrieveCredentials: IndyRetrievedCredentialsFormat } - // Format data is based on RFC 0592 - // https://github.com/hyperledger/aries-rfcs/tree/main/features/0592-indy-attachments - // formatData: { - // proposal: { - // schema_issuer_did?: string - // schema_name?: string - // schema_version?: string - // schema_id?: string - // issuer_did?: string - // cred_def_id?: string - // } - // offer: CredOffer - // request: CredReq - // credential: Cred - // } + + formatData: { + proposal: IndyProofRequest + request: IndyProofRequest + presentation: IndyProof + } } diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts index f5bc85f69a..47ddd8c489 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts @@ -1,12 +1,18 @@ import type { Attachment } from '../../../../decorators/attachment/Attachment' import type { IndyRevocationInterval } from '../../../credentials' import type { GetRequestedCredentialsConfig } from '../../models/GetRequestedCredentialsConfig' -import type { PresentationPreview } from '../../protocol/v1/models/V1PresentationPreview' +import type { + PresentationPreview, + PresentationPreviewAttribute, + PresentationPreviewPredicate, +} from '../../protocol/v1/models/V1PresentationPreview' import type { ProofRecord } from '../../repository/ProofRecord' -import type { RequestedAttribute, RequestedPredicate } from '.././indy/models' import type { ProofAttributeInfo } from '.././indy/models/ProofAttributeInfo' import type { ProofPredicateInfo } from '.././indy/models/ProofPredicateInfo' -import type { ProofRequest } from '.././indy/models/ProofRequest' +import type { IndyRequestedCredentialsFormat } from './IndyProofFormat' +import type { ProofRequest } from './models/ProofRequest' + +export type IndyPresentationProofFormat = IndyRequestedCredentialsFormat export interface IndyRequestProofFormat { name: string @@ -24,12 +30,6 @@ export interface IndyVerifyProofFormat { proofRequest: Attachment } -export interface IndyPresentationProofFormat { - requestedAttributes?: Record - requestedPredicates?: Record - selfAttestedAttributes?: Record -} - export interface GetRequestedCredentialsFormat { attachment: Attachment presentationProposal?: PresentationPreview diff --git a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts index b151253d93..47f650faf9 100644 --- a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts +++ b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts @@ -4,6 +4,13 @@ import type { ProofRecord } from '../repository' import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' import type { AutoAcceptProof } from './ProofAutoAcceptType' +export type FormatDataMessagePayload< + CFs extends ProofFormat[] = ProofFormat[], + M extends keyof ProofFormat['formatData'] = keyof ProofFormat['formatData'] +> = { + [ProofFormat in CFs[number] as ProofFormat['formatKey']]?: ProofFormat['formatData'][M] +} + interface BaseOptions { willConfirm?: boolean goalCode?: string @@ -70,3 +77,9 @@ export interface ProofRequestFromProposalOptions { export interface DeleteProofOptions { deleteAssociatedDidCommMessages?: boolean } + +export type GetFormatDataReturn = { + proposal?: FormatDataMessagePayload + request?: FormatDataMessagePayload + presentation?: FormatDataMessagePayload +} diff --git a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts index a6aa201594..4d45ddf394 100644 --- a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts @@ -24,9 +24,11 @@ import type { CreateRequestOptions, FormatRequestedCredentialReturn, FormatRetrievedCredentialOptions, + GetFormatDataReturn, GetRequestedCredentialsForProofRequestOptions, ProofRequestFromProposalOptions, } from '../../models/ProofServiceOptions' +import type { ProofRequestFormats } from '../../models/SharedOptions' import { validateOrReject } from 'class-validator' import { inject, Lifecycle, scoped } from 'tsyringe' @@ -996,6 +998,63 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { }) } + public async getFormatData( + agentContext: AgentContext, + proofRecordId: string + ): Promise> { + const [proposalMessage, requestMessage, presentationMessage] = await Promise.all([ + this.findProposalMessage(agentContext, proofRecordId), + this.findRequestMessage(agentContext, proofRecordId), + this.findPresentationMessage(agentContext, proofRecordId), + ]) + + const indyProposeProof = proposalMessage + ? JsonTransformer.toJSON(await this.rfc0592ProposalFromV1ProposeMessage(proposalMessage)) + : undefined + const indyRequestProof = requestMessage?.indyProofRequestJson ?? undefined + const indyPresentProof = presentationMessage?.indyProof ?? undefined + + return { + proposal: proposalMessage + ? { + indy: indyProposeProof, + } + : undefined, + request: requestMessage + ? { + indy: indyRequestProof, + } + : undefined, + presentation: presentationMessage + ? { + indy: indyPresentProof, + } + : undefined, + } + } + + private async rfc0592ProposalFromV1ProposeMessage( + proposalMessage: V1ProposePresentationMessage + ): Promise { + const indyFormat: IndyProposeProofFormat = { + name: 'Proof Request', + version: '1.0', + nonce: await this.generateProofRequestNonce(), + attributes: proposalMessage.presentationProposal.attributes, + predicates: proposalMessage.presentationProposal.predicates, + } + + if (!indyFormat) { + throw new AriesFrameworkError('No Indy format found.') + } + + const preview = new PresentationPreview({ + attributes: indyFormat.attributes, + predicates: indyFormat.predicates, + }) + + return IndyProofUtils.createReferentForProofRequest(indyFormat, preview) + } /** * Retrieve all proof records * diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts index b582170e39..a65dae533f 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts +++ b/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts @@ -1,4 +1,5 @@ import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' +import type { IndyProofRequest } from 'indy-sdk' import { Expose, Type } from 'class-transformer' import { IsArray, IsString, ValidateNested, IsOptional, IsInstance } from 'class-validator' @@ -66,16 +67,21 @@ export class V1RequestPresentationMessage extends AgentMessage { public requestPresentationAttachments!: Attachment[] public get indyProofRequest(): ProofRequest | null { - const attachment = this.requestPresentationAttachments.find( - (attachment) => attachment.id === INDY_PROOF_REQUEST_ATTACHMENT_ID - ) // Extract proof request from attachment - const proofRequestJson = attachment?.getDataAsJson() ?? null + const proofRequestJson = this.indyProofRequestJson const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) return proofRequest } + public get indyProofRequestJson(): IndyProofRequest | null { + const attachment = this.requestPresentationAttachments.find( + (attachment) => attachment.id === INDY_PROOF_REQUEST_ATTACHMENT_ID + ) + // Extract proof request from attachment + return attachment?.getDataAsJson() ?? null + } + public getAttachmentFormats(): ProofAttachmentFormat[] { const attachment = this.indyAttachment diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts index 662ef1db33..0c73eedf73 100644 --- a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts @@ -2,6 +2,7 @@ import type { AgentContext } from '../../../../agent' import type { AgentMessage } from '../../../../agent/AgentMessage' import type { Dispatcher } from '../../../../agent/Dispatcher' import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { Attachment } from '../../../../decorators/attachment/Attachment' import type { MediationRecipientService } from '../../../routing/services/MediationRecipientService' import type { RoutingService } from '../../../routing/services/RoutingService' import type { ProofResponseCoordinator } from '../../ProofResponseCoordinator' @@ -17,8 +18,10 @@ import type { CreateProposalOptions, CreateRequestAsResponseOptions, CreateRequestOptions, + FormatDataMessagePayload, FormatRequestedCredentialReturn, FormatRetrievedCredentialOptions, + GetFormatDataReturn, GetRequestedCredentialsForProofRequestOptions, ProofRequestFromProposalOptions, } from '../../models/ProofServiceOptions' @@ -768,6 +771,82 @@ export class V2ProofService extends P }) } + public async getFormatData(agentContext: AgentContext, proofRecordId: string): Promise { + // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. + const [proposalMessage, requestMessage, presentationMessage] = await Promise.all([ + this.findProposalMessage(agentContext, proofRecordId), + this.findRequestMessage(agentContext, proofRecordId), + this.findPresentationMessage(agentContext, proofRecordId), + ]) + + // Create object with the keys and the message formats/attachments. We can then loop over this in a generic + // way so we don't have to add the same operation code four times + const messages = { + proposal: [proposalMessage?.formats, proposalMessage?.proposalsAttach], + request: [requestMessage?.formats, requestMessage?.requestPresentationsAttach], + presentation: [presentationMessage?.formats, presentationMessage?.presentationsAttach], + } as const + + const formatData: GetFormatDataReturn = {} + + // We loop through all of the message keys as defined above + for (const [messageKey, [formats, attachments]] of Object.entries(messages)) { + // Message can be undefined, so we continue if it is not defined + if (!formats || !attachments) continue + + // Find all format services associated with the message + const formatServices = this.getFormatServicesFromMessage(formats) + + const messageFormatData: FormatDataMessagePayload = {} + + // Loop through all of the format services, for each we will extract the attachment data and assign this to the object + // using the unique format key (e.g. indy) + for (const formatService of formatServices) { + const attachment = this.getAttachmentForService(formatService, formats, attachments) + messageFormatData[formatService.formatKey] = attachment.getDataAsJson() + } + + formatData[messageKey as Exclude] = + messageFormatData + } + + return formatData + } + + private getFormatServicesFromMessage(messageFormats: ProofFormatSpec[]): ProofFormatService[] { + const formatServices = new Set() + + for (const msg of messageFormats) { + const service = this.getFormatServiceForFormat(msg) + if (service) formatServices.add(service) + } + + return Array.from(formatServices) + } + + private getAttachmentForService( + proofFormatService: ProofFormatService, + formats: ProofFormatSpec[], + attachments: Attachment[] + ) { + const attachmentId = this.getAttachmentIdForService(proofFormatService, formats) + const attachment = attachments.find((attachment) => attachment.id === attachmentId) + + if (!attachment) { + throw new AriesFrameworkError(`Attachment with id ${attachmentId} not found in attachments.`) + } + + return attachment + } + + private getAttachmentIdForService(proofFormatService: ProofFormatService, formats: ProofFormatSpec[]) { + const format = formats.find((format) => proofFormatService.supportsFormat(format.format)) + + if (!format) throw new AriesFrameworkError(`No attachment found for service ${proofFormatService.formatKey}`) + + return format.attachmentId + } + public async getRequestedCredentialsForProofRequest( agentContext: AgentContext, options: GetRequestedCredentialsForProofRequestOptions diff --git a/packages/core/tests/v1-indy-proofs.test.ts b/packages/core/tests/v1-indy-proofs.test.ts index 85e694ead1..65514eb0e3 100644 --- a/packages/core/tests/v1-indy-proofs.test.ts +++ b/packages/core/tests/v1-indy-proofs.test.ts @@ -7,6 +7,7 @@ import type { import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' import type { CredDefId } from 'indy-sdk' +import { getGroupKeysFromIndyProofFormatData } from '../src/modules/proofs/__tests__/groupKeys' import { ProofAttributeInfo, AttributeFilter, @@ -252,6 +253,93 @@ describe('Present Proof', () => { connectionId: expect.any(String), state: ProofState.Done, }) + + const proposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofRecord.id) + const requestMessage = await aliceAgent.proofs.findRequestMessage(aliceProofRecord.id) + const presentationMessage = await aliceAgent.proofs.findPresentationMessage(aliceProofRecord.id) + + expect(proposalMessage).toBeInstanceOf(V1ProposePresentationMessage) + expect(requestMessage).toBeInstanceOf(V1RequestPresentationMessage) + expect(presentationMessage).toBeInstanceOf(V1PresentationMessage) + + const formatData = await aliceAgent.proofs.getFormatData(aliceProofRecord.id) + + // eslint-disable-next-line prefer-const + let { proposeKey1, proposeKey2, requestKey1, requestKey2 } = getGroupKeysFromIndyProofFormatData(formatData) + + expect(formatData).toMatchObject({ + proposal: { + indy: { + name: 'Proof Request', + version: '1.0', + nonce: expect.any(String), + requested_attributes: { + 0: { + name: 'name', + }, + [proposeKey1]: { + name: 'image_0', + restrictions: [ + { + cred_def_id: credDefId, + }, + ], + }, + }, + requested_predicates: { + [proposeKey2]: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credDefId, + }, + ], + }, + }, + }, + }, + request: { + indy: { + name: 'Proof Request', + version: '1.0', + nonce: expect.any(String), + requested_attributes: { + 0: { + name: 'name', + }, + [requestKey1]: { + name: 'image_0', + restrictions: [ + { + cred_def_id: credDefId, + }, + ], + }, + }, + requested_predicates: { + [requestKey2]: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credDefId, + }, + ], + }, + }, + }, + }, + presentation: { + indy: { + proof: expect.any(Object), + requested_proof: expect.any(Object), + identifiers: expect.any(Array), + }, + }, + }) }) test('Faber starts with proof request to Alice', async () => { diff --git a/packages/core/tests/v2-indy-proofs.test.ts b/packages/core/tests/v2-indy-proofs.test.ts index 71d0b04096..0241964673 100644 --- a/packages/core/tests/v2-indy-proofs.test.ts +++ b/packages/core/tests/v2-indy-proofs.test.ts @@ -8,6 +8,7 @@ import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/mode import type { CredDefId } from 'indy-sdk' import { AttributeFilter, PredicateType, ProofAttributeInfo, ProofPredicateInfo, ProofState } from '../src' +import { getGroupKeysFromIndyProofFormatData } from '../src/modules/proofs/__tests__/groupKeys' import { V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST, @@ -243,6 +244,104 @@ describe('Present Proof', () => { connectionId: expect.any(String), state: ProofState.Done, }) + + const proposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofRecord.id) + const requestMessage = await aliceAgent.proofs.findRequestMessage(aliceProofRecord.id) + const presentationMessage = await aliceAgent.proofs.findPresentationMessage(aliceProofRecord.id) + + expect(proposalMessage).toBeInstanceOf(V2ProposalPresentationMessage) + expect(requestMessage).toBeInstanceOf(V2RequestPresentationMessage) + expect(presentationMessage).toBeInstanceOf(V2PresentationMessage) + + const formatData = await aliceAgent.proofs.getFormatData(aliceProofRecord.id) + + // eslint-disable-next-line prefer-const + let { proposeKey1, proposeKey2, requestKey1, requestKey2 } = getGroupKeysFromIndyProofFormatData(formatData) + + expect(formatData).toMatchObject({ + proposal: { + indy: { + name: 'abc', + version: '1.0', + nonce: expect.any(String), + requested_attributes: { + 0: { + name: 'name', + }, + [proposeKey1]: { + name: 'image_0', + restrictions: [ + { + cred_def_id: credDefId, + }, + ], + }, + }, + requested_predicates: { + [proposeKey2]: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credDefId, + }, + ], + }, + }, + }, + }, + request: { + indy: { + name: 'abc', + version: '1.0', + nonce: '947121108704767252195126', + requested_attributes: { + 0: { + name: 'name', + }, + [requestKey1]: { + name: 'image_0', + restrictions: [ + { + cred_def_id: credDefId, + }, + ], + }, + }, + requested_predicates: { + [requestKey2]: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credDefId, + }, + ], + }, + }, + }, + }, + presentation: { + indy: { + proof: { + proofs: [ + { + primary_proof: expect.any(Object), + non_revoc_proof: null, + }, + ], + aggregated_proof: { + c_hash: expect.any(String), + c_list: expect.any(Array), + }, + }, + requested_proof: expect.any(Object), + identifiers: expect.any(Array), + }, + }, + }) }) test('Faber starts with proof request to Alice', async () => { From 78d19e74aeb9c54f24ea48ee9663cae910382713 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 20 Oct 2022 10:35:58 +0300 Subject: [PATCH 056/125] chore: update versions for all packages to 0.2.5 Signed-off-by: Timo Glastra --- CHANGELOG.md | 15 +++++++++++++++ lerna.json | 2 +- packages/action-menu/package.json | 6 +++--- packages/core/CHANGELOG.md | 15 +++++++++++++++ packages/core/package.json | 2 +- packages/module-bbs/package.json | 6 +++--- packages/module-tenants/package.json | 6 +++--- packages/node/CHANGELOG.md | 4 ++++ packages/node/package.json | 4 ++-- packages/question-answer/package.json | 6 +++--- packages/react-native/CHANGELOG.md | 4 ++++ packages/react-native/package.json | 4 ++-- 12 files changed, 56 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f28623e4f5..47323c1ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.5](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.4...v0.2.5) (2022-10-13) + +### Bug Fixes + +- **oob:** allow encoding in content type header ([#1037](https://github.com/hyperledger/aries-framework-javascript/issues/1037)) ([e1d6592](https://github.com/hyperledger/aries-framework-javascript/commit/e1d6592b818bc4348078ca6593eea4641caafae5)) +- **oob:** set connection alias when creating invitation ([#1047](https://github.com/hyperledger/aries-framework-javascript/issues/1047)) ([7be979a](https://github.com/hyperledger/aries-framework-javascript/commit/7be979a74b86c606db403c8df04cfc8be2aae249)) + +### Features + +- connection type ([#994](https://github.com/hyperledger/aries-framework-javascript/issues/994)) ([0d14a71](https://github.com/hyperledger/aries-framework-javascript/commit/0d14a7157e2118592829109dbc5c793faee1e201)) +- expose findAllByQuery method in modules and services ([#1044](https://github.com/hyperledger/aries-framework-javascript/issues/1044)) ([9dd95e8](https://github.com/hyperledger/aries-framework-javascript/commit/9dd95e81770d3140558196d2b5b508723f918f04)) +- improve sending error handling ([#1045](https://github.com/hyperledger/aries-framework-javascript/issues/1045)) ([a230841](https://github.com/hyperledger/aries-framework-javascript/commit/a230841aa99102bcc8b60aa2a23040f13a929a6c)) +- possibility to set masterSecretId inside of WalletConfig ([#1043](https://github.com/hyperledger/aries-framework-javascript/issues/1043)) ([8a89ad2](https://github.com/hyperledger/aries-framework-javascript/commit/8a89ad2624922e5e5455f8881d1ccc656d6b33ec)) +- use did:key flag ([#1029](https://github.com/hyperledger/aries-framework-javascript/issues/1029)) ([8efade5](https://github.com/hyperledger/aries-framework-javascript/commit/8efade5b2a885f0767ac8b10cba8582fe9ff486a)) + ## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) ### Bug Fixes diff --git a/lerna.json b/lerna.json index 86f806459b..103d37573a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.2.4", + "version": "0.2.5", "useWorkspaces": true, "npmClient": "yarn", "command": { diff --git a/packages/action-menu/package.json b/packages/action-menu/package.json index 4e06450149..401650373a 100644 --- a/packages/action-menu/package.json +++ b/packages/action-menu/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/action-menu", "main": "build/index", "types": "build/index", - "version": "0.2.4", + "version": "0.2.5", "private": true, "files": [ "build" @@ -30,10 +30,10 @@ "class-validator": "0.13.1" }, "peerDependencies": { - "@aries-framework/core": "0.2.4" + "@aries-framework/core": "0.2.5" }, "devDependencies": { - "@aries-framework/node": "0.2.4", + "@aries-framework/node": "0.2.5", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 20c7b345be..f30627f4af 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.5](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.4...v0.2.5) (2022-10-13) + +### Bug Fixes + +- **oob:** allow encoding in content type header ([#1037](https://github.com/hyperledger/aries-framework-javascript/issues/1037)) ([e1d6592](https://github.com/hyperledger/aries-framework-javascript/commit/e1d6592b818bc4348078ca6593eea4641caafae5)) +- **oob:** set connection alias when creating invitation ([#1047](https://github.com/hyperledger/aries-framework-javascript/issues/1047)) ([7be979a](https://github.com/hyperledger/aries-framework-javascript/commit/7be979a74b86c606db403c8df04cfc8be2aae249)) + +### Features + +- connection type ([#994](https://github.com/hyperledger/aries-framework-javascript/issues/994)) ([0d14a71](https://github.com/hyperledger/aries-framework-javascript/commit/0d14a7157e2118592829109dbc5c793faee1e201)) +- expose findAllByQuery method in modules and services ([#1044](https://github.com/hyperledger/aries-framework-javascript/issues/1044)) ([9dd95e8](https://github.com/hyperledger/aries-framework-javascript/commit/9dd95e81770d3140558196d2b5b508723f918f04)) +- improve sending error handling ([#1045](https://github.com/hyperledger/aries-framework-javascript/issues/1045)) ([a230841](https://github.com/hyperledger/aries-framework-javascript/commit/a230841aa99102bcc8b60aa2a23040f13a929a6c)) +- possibility to set masterSecretId inside of WalletConfig ([#1043](https://github.com/hyperledger/aries-framework-javascript/issues/1043)) ([8a89ad2](https://github.com/hyperledger/aries-framework-javascript/commit/8a89ad2624922e5e5455f8881d1ccc656d6b33ec)) +- use did:key flag ([#1029](https://github.com/hyperledger/aries-framework-javascript/issues/1029)) ([8efade5](https://github.com/hyperledger/aries-framework-javascript/commit/8efade5b2a885f0767ac8b10cba8582fe9ff486a)) + ## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) ### Bug Fixes diff --git a/packages/core/package.json b/packages/core/package.json index 6d6add7046..3dc169184a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/core", "main": "build/index", "types": "build/index", - "version": "0.2.4", + "version": "0.2.5", "files": [ "build" ], diff --git a/packages/module-bbs/package.json b/packages/module-bbs/package.json index bed4a703c8..720f4c22e5 100644 --- a/packages/module-bbs/package.json +++ b/packages/module-bbs/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/bbs-signatures", "main": "build/index", "types": "build/index", - "version": "0.2.2", + "version": "0.2.5", "private": true, "files": [ "build" @@ -30,10 +30,10 @@ "@stablelib/random": "^1.0.2" }, "peerDependencies": { - "@aries-framework/core": "0.2.4" + "@aries-framework/core": "0.2.5" }, "devDependencies": { - "@aries-framework/node": "0.2.4", + "@aries-framework/node": "0.2.5", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/module-tenants/package.json b/packages/module-tenants/package.json index dacfdc4142..c4267795e8 100644 --- a/packages/module-tenants/package.json +++ b/packages/module-tenants/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/module-tenants", "main": "build/index", "types": "build/index", - "version": "0.2.2", + "version": "0.2.5", "files": [ "build" ], @@ -24,11 +24,11 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.4", + "@aries-framework/core": "0.2.5", "async-mutex": "^0.3.2" }, "devDependencies": { - "@aries-framework/node": "0.2.4", + "@aries-framework/node": "0.2.5", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index 1a01719007..7bda831ff2 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.5](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.4...v0.2.5) (2022-10-13) + +**Note:** Version bump only for package @aries-framework/node + ## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) **Note:** Version bump only for package @aries-framework/node diff --git a/packages/node/package.json b/packages/node/package.json index e4faa84365..9ad1abfb3b 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/node", "main": "build/index", "types": "build/index", - "version": "0.2.4", + "version": "0.2.5", "files": [ "build", "bin" @@ -28,7 +28,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.4", + "@aries-framework/core": "0.2.5", "express": "^4.17.1", "ffi-napi": "^4.0.3", "indy-sdk": "^1.16.0-dev-1636", diff --git a/packages/question-answer/package.json b/packages/question-answer/package.json index f1e6b5f5e7..d50b9d3eed 100644 --- a/packages/question-answer/package.json +++ b/packages/question-answer/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/question-answer", "main": "build/index", "types": "build/index", - "version": "0.2.4", + "version": "0.2.5", "private": true, "files": [ "build" @@ -30,10 +30,10 @@ "class-validator": "0.13.1" }, "peerDependencies": { - "@aries-framework/core": "0.2.4" + "@aries-framework/core": "0.2.5" }, "devDependencies": { - "@aries-framework/node": "0.2.4", + "@aries-framework/node": "0.2.5", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/react-native/CHANGELOG.md b/packages/react-native/CHANGELOG.md index d693eee5df..31568ef45b 100644 --- a/packages/react-native/CHANGELOG.md +++ b/packages/react-native/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.2.5](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.4...v0.2.5) (2022-10-13) + +**Note:** Version bump only for package @aries-framework/react-native + ## [0.2.4](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.3...v0.2.4) (2022-09-10) **Note:** Version bump only for package @aries-framework/react-native diff --git a/packages/react-native/package.json b/packages/react-native/package.json index bd8067a326..944a1c22b3 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/react-native", "main": "build/index", "types": "build/index", - "version": "0.2.4", + "version": "0.2.5", "files": [ "build" ], @@ -24,7 +24,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.4", + "@aries-framework/core": "0.2.5", "@azure/core-asynciterator-polyfill": "^1.0.0", "events": "^3.3.0" }, From 67daf737c39b6b133a37a91e46b129eab8401b40 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 20 Oct 2022 10:36:49 +0300 Subject: [PATCH 057/125] chore(bbs): rename module-bbs directory Signed-off-by: Timo Glastra --- packages/{module-bbs => bbs-signatures}/README.md | 0 packages/{module-bbs => bbs-signatures}/jest.config.ts | 0 packages/{module-bbs => bbs-signatures}/package.json | 4 ++-- packages/{module-bbs => bbs-signatures}/src/BbsModule.ts | 0 .../src/Bls12381g2SigningProvider.ts | 0 .../src/__tests__/BbsModule.test.ts | 0 packages/{module-bbs => bbs-signatures}/src/index.ts | 0 .../src/signature-suites/BbsBlsSignature2020.ts | 0 .../src/signature-suites/BbsBlsSignatureProof2020.ts | 0 .../src/signature-suites/index.ts | 0 .../src/types/CanonizeOptions.ts | 0 .../src/types/CreateProofOptions.ts | 0 .../src/types/CreateVerifyDataOptions.ts | 0 .../src/types/DeriveProofOptions.ts | 0 .../src/types/DidDocumentPublicKey.ts | 0 .../src/types/JsonWebKey.ts | 0 .../src/types/KeyPairOptions.ts | 0 .../src/types/KeyPairSigner.ts | 0 .../src/types/KeyPairVerifier.ts | 0 .../src/types/SignatureSuiteOptions.ts | 0 .../src/types/SuiteSignOptions.ts | 0 .../src/types/VerifyProofOptions.ts | 0 .../src/types/VerifyProofResult.ts | 0 .../src/types/VerifySignatureOptions.ts | 0 .../{module-bbs => bbs-signatures}/src/types/index.ts | 0 .../tests/bbs-signatures.e2e.test.ts | 8 ++++---- .../tests/bbs-signing-provider.e2e.test.ts | 2 +- packages/{module-bbs => bbs-signatures}/tests/fixtures.ts | 0 packages/{module-bbs => bbs-signatures}/tests/setup.ts | 0 packages/{module-bbs => bbs-signatures}/tests/util.ts | 0 .../{module-bbs => bbs-signatures}/tsconfig.build.json | 0 packages/{module-bbs => bbs-signatures}/tsconfig.json | 0 32 files changed, 7 insertions(+), 7 deletions(-) rename packages/{module-bbs => bbs-signatures}/README.md (100%) rename packages/{module-bbs => bbs-signatures}/jest.config.ts (100%) rename packages/{module-bbs => bbs-signatures}/package.json (91%) rename packages/{module-bbs => bbs-signatures}/src/BbsModule.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/Bls12381g2SigningProvider.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/__tests__/BbsModule.test.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/index.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/signature-suites/BbsBlsSignature2020.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/signature-suites/BbsBlsSignatureProof2020.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/signature-suites/index.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/CanonizeOptions.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/CreateProofOptions.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/CreateVerifyDataOptions.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/DeriveProofOptions.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/DidDocumentPublicKey.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/JsonWebKey.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/KeyPairOptions.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/KeyPairSigner.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/KeyPairVerifier.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/SignatureSuiteOptions.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/SuiteSignOptions.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/VerifyProofOptions.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/VerifyProofResult.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/VerifySignatureOptions.ts (100%) rename packages/{module-bbs => bbs-signatures}/src/types/index.ts (100%) rename packages/{module-bbs => bbs-signatures}/tests/bbs-signatures.e2e.test.ts (96%) rename packages/{module-bbs => bbs-signatures}/tests/bbs-signing-provider.e2e.test.ts (97%) rename packages/{module-bbs => bbs-signatures}/tests/fixtures.ts (100%) rename packages/{module-bbs => bbs-signatures}/tests/setup.ts (100%) rename packages/{module-bbs => bbs-signatures}/tests/util.ts (100%) rename packages/{module-bbs => bbs-signatures}/tsconfig.build.json (100%) rename packages/{module-bbs => bbs-signatures}/tsconfig.json (100%) diff --git a/packages/module-bbs/README.md b/packages/bbs-signatures/README.md similarity index 100% rename from packages/module-bbs/README.md rename to packages/bbs-signatures/README.md diff --git a/packages/module-bbs/jest.config.ts b/packages/bbs-signatures/jest.config.ts similarity index 100% rename from packages/module-bbs/jest.config.ts rename to packages/bbs-signatures/jest.config.ts diff --git a/packages/module-bbs/package.json b/packages/bbs-signatures/package.json similarity index 91% rename from packages/module-bbs/package.json rename to packages/bbs-signatures/package.json index 720f4c22e5..16c8df6f70 100644 --- a/packages/module-bbs/package.json +++ b/packages/bbs-signatures/package.json @@ -11,11 +11,11 @@ "publishConfig": { "access": "public" }, - "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/module-bbs", + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/bbs-signatures", "repository": { "type": "git", "url": "https://github.com/hyperledger/aries-framework-javascript", - "directory": "packages/module-bbs" + "directory": "packages/bbs-signatures" }, "scripts": { "build": "yarn run clean && yarn run compile", diff --git a/packages/module-bbs/src/BbsModule.ts b/packages/bbs-signatures/src/BbsModule.ts similarity index 100% rename from packages/module-bbs/src/BbsModule.ts rename to packages/bbs-signatures/src/BbsModule.ts diff --git a/packages/module-bbs/src/Bls12381g2SigningProvider.ts b/packages/bbs-signatures/src/Bls12381g2SigningProvider.ts similarity index 100% rename from packages/module-bbs/src/Bls12381g2SigningProvider.ts rename to packages/bbs-signatures/src/Bls12381g2SigningProvider.ts diff --git a/packages/module-bbs/src/__tests__/BbsModule.test.ts b/packages/bbs-signatures/src/__tests__/BbsModule.test.ts similarity index 100% rename from packages/module-bbs/src/__tests__/BbsModule.test.ts rename to packages/bbs-signatures/src/__tests__/BbsModule.test.ts diff --git a/packages/module-bbs/src/index.ts b/packages/bbs-signatures/src/index.ts similarity index 100% rename from packages/module-bbs/src/index.ts rename to packages/bbs-signatures/src/index.ts diff --git a/packages/module-bbs/src/signature-suites/BbsBlsSignature2020.ts b/packages/bbs-signatures/src/signature-suites/BbsBlsSignature2020.ts similarity index 100% rename from packages/module-bbs/src/signature-suites/BbsBlsSignature2020.ts rename to packages/bbs-signatures/src/signature-suites/BbsBlsSignature2020.ts diff --git a/packages/module-bbs/src/signature-suites/BbsBlsSignatureProof2020.ts b/packages/bbs-signatures/src/signature-suites/BbsBlsSignatureProof2020.ts similarity index 100% rename from packages/module-bbs/src/signature-suites/BbsBlsSignatureProof2020.ts rename to packages/bbs-signatures/src/signature-suites/BbsBlsSignatureProof2020.ts diff --git a/packages/module-bbs/src/signature-suites/index.ts b/packages/bbs-signatures/src/signature-suites/index.ts similarity index 100% rename from packages/module-bbs/src/signature-suites/index.ts rename to packages/bbs-signatures/src/signature-suites/index.ts diff --git a/packages/module-bbs/src/types/CanonizeOptions.ts b/packages/bbs-signatures/src/types/CanonizeOptions.ts similarity index 100% rename from packages/module-bbs/src/types/CanonizeOptions.ts rename to packages/bbs-signatures/src/types/CanonizeOptions.ts diff --git a/packages/module-bbs/src/types/CreateProofOptions.ts b/packages/bbs-signatures/src/types/CreateProofOptions.ts similarity index 100% rename from packages/module-bbs/src/types/CreateProofOptions.ts rename to packages/bbs-signatures/src/types/CreateProofOptions.ts diff --git a/packages/module-bbs/src/types/CreateVerifyDataOptions.ts b/packages/bbs-signatures/src/types/CreateVerifyDataOptions.ts similarity index 100% rename from packages/module-bbs/src/types/CreateVerifyDataOptions.ts rename to packages/bbs-signatures/src/types/CreateVerifyDataOptions.ts diff --git a/packages/module-bbs/src/types/DeriveProofOptions.ts b/packages/bbs-signatures/src/types/DeriveProofOptions.ts similarity index 100% rename from packages/module-bbs/src/types/DeriveProofOptions.ts rename to packages/bbs-signatures/src/types/DeriveProofOptions.ts diff --git a/packages/module-bbs/src/types/DidDocumentPublicKey.ts b/packages/bbs-signatures/src/types/DidDocumentPublicKey.ts similarity index 100% rename from packages/module-bbs/src/types/DidDocumentPublicKey.ts rename to packages/bbs-signatures/src/types/DidDocumentPublicKey.ts diff --git a/packages/module-bbs/src/types/JsonWebKey.ts b/packages/bbs-signatures/src/types/JsonWebKey.ts similarity index 100% rename from packages/module-bbs/src/types/JsonWebKey.ts rename to packages/bbs-signatures/src/types/JsonWebKey.ts diff --git a/packages/module-bbs/src/types/KeyPairOptions.ts b/packages/bbs-signatures/src/types/KeyPairOptions.ts similarity index 100% rename from packages/module-bbs/src/types/KeyPairOptions.ts rename to packages/bbs-signatures/src/types/KeyPairOptions.ts diff --git a/packages/module-bbs/src/types/KeyPairSigner.ts b/packages/bbs-signatures/src/types/KeyPairSigner.ts similarity index 100% rename from packages/module-bbs/src/types/KeyPairSigner.ts rename to packages/bbs-signatures/src/types/KeyPairSigner.ts diff --git a/packages/module-bbs/src/types/KeyPairVerifier.ts b/packages/bbs-signatures/src/types/KeyPairVerifier.ts similarity index 100% rename from packages/module-bbs/src/types/KeyPairVerifier.ts rename to packages/bbs-signatures/src/types/KeyPairVerifier.ts diff --git a/packages/module-bbs/src/types/SignatureSuiteOptions.ts b/packages/bbs-signatures/src/types/SignatureSuiteOptions.ts similarity index 100% rename from packages/module-bbs/src/types/SignatureSuiteOptions.ts rename to packages/bbs-signatures/src/types/SignatureSuiteOptions.ts diff --git a/packages/module-bbs/src/types/SuiteSignOptions.ts b/packages/bbs-signatures/src/types/SuiteSignOptions.ts similarity index 100% rename from packages/module-bbs/src/types/SuiteSignOptions.ts rename to packages/bbs-signatures/src/types/SuiteSignOptions.ts diff --git a/packages/module-bbs/src/types/VerifyProofOptions.ts b/packages/bbs-signatures/src/types/VerifyProofOptions.ts similarity index 100% rename from packages/module-bbs/src/types/VerifyProofOptions.ts rename to packages/bbs-signatures/src/types/VerifyProofOptions.ts diff --git a/packages/module-bbs/src/types/VerifyProofResult.ts b/packages/bbs-signatures/src/types/VerifyProofResult.ts similarity index 100% rename from packages/module-bbs/src/types/VerifyProofResult.ts rename to packages/bbs-signatures/src/types/VerifyProofResult.ts diff --git a/packages/module-bbs/src/types/VerifySignatureOptions.ts b/packages/bbs-signatures/src/types/VerifySignatureOptions.ts similarity index 100% rename from packages/module-bbs/src/types/VerifySignatureOptions.ts rename to packages/bbs-signatures/src/types/VerifySignatureOptions.ts diff --git a/packages/module-bbs/src/types/index.ts b/packages/bbs-signatures/src/types/index.ts similarity index 100% rename from packages/module-bbs/src/types/index.ts rename to packages/bbs-signatures/src/types/index.ts diff --git a/packages/module-bbs/tests/bbs-signatures.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts similarity index 96% rename from packages/module-bbs/tests/bbs-signatures.e2e.test.ts rename to packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts index 72a76fa517..bcf8e9dfa4 100644 --- a/packages/module-bbs/tests/bbs-signatures.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts @@ -1,4 +1,4 @@ -import type { W3cCredentialRepository } from '../../core/src/modules/vc/repository' +import type { W3cCredentialRepository } from '@aries-framework/core/src/modules/vc/repository' import type { AgentContext } from '@aries-framework/core' import { @@ -23,9 +23,9 @@ import { Ed25519Signature2018, } from '@aries-framework/core' -import { SignatureSuiteRegistry } from '../../core/src/modules/vc/SignatureSuiteRegistry' -import { customDocumentLoader } from '../../core/src/modules/vc/__tests__/documentLoader' -import { getAgentConfig, getAgentContext } from '../../core/tests/helpers' +import { SignatureSuiteRegistry } from '@aries-framework/core/src/modules/vc/SignatureSuiteRegistry' +import { customDocumentLoader } from '@aries-framework/core/src/modules/vc/__tests__/documentLoader' +import { getAgentConfig, getAgentContext } from '@aries-framework/core/tests/helpers' import { BbsBlsSignature2020, BbsBlsSignatureProof2020, Bls12381g2SigningProvider } from '../src' import { BbsBlsSignature2020Fixtures } from './fixtures' diff --git a/packages/module-bbs/tests/bbs-signing-provider.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts similarity index 97% rename from packages/module-bbs/tests/bbs-signing-provider.e2e.test.ts rename to packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts index b5a90765af..c0f1ed7013 100644 --- a/packages/module-bbs/tests/bbs-signing-provider.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts @@ -11,7 +11,7 @@ import { import { agentDependencies } from '@aries-framework/node' import { BBS_SIGNATURE_LENGTH } from '@mattrglobal/bbs-signatures' -import testLogger from '../../core/tests/logger' +import testLogger from '@aries-framework/core/tests/logger' import { Bls12381g2SigningProvider } from '../src' import { describeSkipNode17And18 } from './util' diff --git a/packages/module-bbs/tests/fixtures.ts b/packages/bbs-signatures/tests/fixtures.ts similarity index 100% rename from packages/module-bbs/tests/fixtures.ts rename to packages/bbs-signatures/tests/fixtures.ts diff --git a/packages/module-bbs/tests/setup.ts b/packages/bbs-signatures/tests/setup.ts similarity index 100% rename from packages/module-bbs/tests/setup.ts rename to packages/bbs-signatures/tests/setup.ts diff --git a/packages/module-bbs/tests/util.ts b/packages/bbs-signatures/tests/util.ts similarity index 100% rename from packages/module-bbs/tests/util.ts rename to packages/bbs-signatures/tests/util.ts diff --git a/packages/module-bbs/tsconfig.build.json b/packages/bbs-signatures/tsconfig.build.json similarity index 100% rename from packages/module-bbs/tsconfig.build.json rename to packages/bbs-signatures/tsconfig.build.json diff --git a/packages/module-bbs/tsconfig.json b/packages/bbs-signatures/tsconfig.json similarity index 100% rename from packages/module-bbs/tsconfig.json rename to packages/bbs-signatures/tsconfig.json From 5351f6a93446044994e32e2917c17e8a1e611f0b Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 20 Oct 2022 10:39:14 +0300 Subject: [PATCH 058/125] chore(tenants): rename module-tenants to tenants Signed-off-by: Timo Glastra --- packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts | 4 ++-- .../bbs-signatures/tests/bbs-signing-provider.e2e.test.ts | 2 +- packages/{module-tenants => tenants}/README.md | 6 +++--- packages/{module-tenants => tenants}/jest.config.ts | 0 packages/{module-tenants => tenants}/package.json | 6 +++--- packages/{module-tenants => tenants}/src/TenantAgent.ts | 0 packages/{module-tenants => tenants}/src/TenantsApi.ts | 0 .../{module-tenants => tenants}/src/TenantsApiOptions.ts | 0 packages/{module-tenants => tenants}/src/TenantsModule.ts | 0 .../{module-tenants => tenants}/src/TenantsModuleConfig.ts | 0 .../src/__tests__/TenantAgent.test.ts | 2 +- .../src/__tests__/TenantsApi.test.ts | 2 +- .../src/__tests__/TenantsModule.test.ts | 2 +- .../src/__tests__/TenantsModuleConfig.test.ts | 0 .../src/context/TenantAgentContextProvider.ts | 0 .../src/context/TenantSessionCoordinator.ts | 0 .../src/context/TenantSessionMutex.ts | 0 .../context/__tests__/TenantAgentContextProvider.test.ts | 4 ++-- .../src/context/__tests__/TenantSessionCoordinator.test.ts | 4 ++-- .../src/context/__tests__/TenantSessionMutex.test.ts | 3 ++- packages/{module-tenants => tenants}/src/context/types.ts | 0 packages/{module-tenants => tenants}/src/index.ts | 0 .../{module-tenants => tenants}/src/models/TenantConfig.ts | 0 .../src/repository/TenantRecord.ts | 0 .../src/repository/TenantRepository.ts | 0 .../src/repository/TenantRoutingRecord.ts | 0 .../src/repository/TenantRoutingRepository.ts | 0 .../src/repository/__tests__/TenantRecord.test.ts | 3 ++- .../src/repository/__tests__/TenantRoutingRecord.test.ts | 3 ++- .../repository/__tests__/TenantRoutingRepository.test.ts | 7 ++++--- .../{module-tenants => tenants}/src/repository/index.ts | 0 .../src/services/TenantRecordService.ts | 0 .../src/services/__tests__/TenantService.test.ts | 2 +- packages/{module-tenants => tenants}/src/services/index.ts | 0 packages/{module-tenants => tenants}/tests/setup.ts | 0 .../tests/tenant-sessions.e2e.test.ts | 5 ++--- .../{module-tenants => tenants}/tests/tenants.e2e.test.ts | 4 ++-- packages/{module-tenants => tenants}/tsconfig.build.json | 0 packages/{module-tenants => tenants}/tsconfig.json | 0 39 files changed, 31 insertions(+), 28 deletions(-) rename packages/{module-tenants => tenants}/README.md (81%) rename packages/{module-tenants => tenants}/jest.config.ts (100%) rename packages/{module-tenants => tenants}/package.json (85%) rename packages/{module-tenants => tenants}/src/TenantAgent.ts (100%) rename packages/{module-tenants => tenants}/src/TenantsApi.ts (100%) rename packages/{module-tenants => tenants}/src/TenantsApiOptions.ts (100%) rename packages/{module-tenants => tenants}/src/TenantsModule.ts (100%) rename packages/{module-tenants => tenants}/src/TenantsModuleConfig.ts (100%) rename packages/{module-tenants => tenants}/src/__tests__/TenantAgent.test.ts (95%) rename packages/{module-tenants => tenants}/src/__tests__/TenantsApi.test.ts (99%) rename packages/{module-tenants => tenants}/src/__tests__/TenantsModule.test.ts (95%) rename packages/{module-tenants => tenants}/src/__tests__/TenantsModuleConfig.test.ts (100%) rename packages/{module-tenants => tenants}/src/context/TenantAgentContextProvider.ts (100%) rename packages/{module-tenants => tenants}/src/context/TenantSessionCoordinator.ts (100%) rename packages/{module-tenants => tenants}/src/context/TenantSessionMutex.ts (100%) rename packages/{module-tenants => tenants}/src/context/__tests__/TenantAgentContextProvider.test.ts (98%) rename packages/{module-tenants => tenants}/src/context/__tests__/TenantSessionCoordinator.test.ts (98%) rename packages/{module-tenants => tenants}/src/context/__tests__/TenantSessionMutex.test.ts (97%) rename packages/{module-tenants => tenants}/src/context/types.ts (100%) rename packages/{module-tenants => tenants}/src/index.ts (100%) rename packages/{module-tenants => tenants}/src/models/TenantConfig.ts (100%) rename packages/{module-tenants => tenants}/src/repository/TenantRecord.ts (100%) rename packages/{module-tenants => tenants}/src/repository/TenantRepository.ts (100%) rename packages/{module-tenants => tenants}/src/repository/TenantRoutingRecord.ts (100%) rename packages/{module-tenants => tenants}/src/repository/TenantRoutingRepository.ts (100%) rename packages/{module-tenants => tenants}/src/repository/__tests__/TenantRecord.test.ts (96%) rename packages/{module-tenants => tenants}/src/repository/__tests__/TenantRoutingRecord.test.ts (97%) rename packages/{module-tenants => tenants}/src/repository/__tests__/TenantRoutingRepository.test.ts (86%) rename packages/{module-tenants => tenants}/src/repository/index.ts (100%) rename packages/{module-tenants => tenants}/src/services/TenantRecordService.ts (100%) rename packages/{module-tenants => tenants}/src/services/__tests__/TenantService.test.ts (98%) rename packages/{module-tenants => tenants}/src/services/index.ts (100%) rename packages/{module-tenants => tenants}/tests/setup.ts (100%) rename packages/{module-tenants => tenants}/tests/tenant-sessions.e2e.test.ts (95%) rename packages/{module-tenants => tenants}/tests/tenants.e2e.test.ts (98%) rename packages/{module-tenants => tenants}/tsconfig.build.json (100%) rename packages/{module-tenants => tenants}/tsconfig.json (100%) diff --git a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts index bcf8e9dfa4..13e36b9fae 100644 --- a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts @@ -1,5 +1,5 @@ -import type { W3cCredentialRepository } from '@aries-framework/core/src/modules/vc/repository' import type { AgentContext } from '@aries-framework/core' +import type { W3cCredentialRepository } from '@aries-framework/core/src/modules/vc/repository' import { VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, @@ -22,10 +22,10 @@ import { IndyWallet, Ed25519Signature2018, } from '@aries-framework/core' - import { SignatureSuiteRegistry } from '@aries-framework/core/src/modules/vc/SignatureSuiteRegistry' import { customDocumentLoader } from '@aries-framework/core/src/modules/vc/__tests__/documentLoader' import { getAgentConfig, getAgentContext } from '@aries-framework/core/tests/helpers' + import { BbsBlsSignature2020, BbsBlsSignatureProof2020, Bls12381g2SigningProvider } from '../src' import { BbsBlsSignature2020Fixtures } from './fixtures' diff --git a/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts index c0f1ed7013..5f5a5c4141 100644 --- a/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts @@ -8,10 +8,10 @@ import { SigningProviderRegistry, IndyWallet, } from '@aries-framework/core' +import testLogger from '@aries-framework/core/tests/logger' import { agentDependencies } from '@aries-framework/node' import { BBS_SIGNATURE_LENGTH } from '@mattrglobal/bbs-signatures' -import testLogger from '@aries-framework/core/tests/logger' import { Bls12381g2SigningProvider } from '../src' import { describeSkipNode17And18 } from './util' diff --git a/packages/module-tenants/README.md b/packages/tenants/README.md similarity index 81% rename from packages/module-tenants/README.md rename to packages/tenants/README.md index e0ec4b3ad4..1a75feac00 100644 --- a/packages/module-tenants/README.md +++ b/packages/tenants/README.md @@ -19,10 +19,10 @@ alt="typescript" src="https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg" />
- @aries-framework/module-tenants version

diff --git a/packages/module-tenants/jest.config.ts b/packages/tenants/jest.config.ts similarity index 100% rename from packages/module-tenants/jest.config.ts rename to packages/tenants/jest.config.ts diff --git a/packages/module-tenants/package.json b/packages/tenants/package.json similarity index 85% rename from packages/module-tenants/package.json rename to packages/tenants/package.json index c4267795e8..e9bbe844f4 100644 --- a/packages/module-tenants/package.json +++ b/packages/tenants/package.json @@ -1,5 +1,5 @@ { - "name": "@aries-framework/module-tenants", + "name": "@aries-framework/tenants", "main": "build/index", "types": "build/index", "version": "0.2.5", @@ -10,11 +10,11 @@ "publishConfig": { "access": "public" }, - "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/module-tenants", + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/tenants", "repository": { "type": "git", "url": "https://github.com/hyperledger/aries-framework-javascript", - "directory": "packages/module-tenants" + "directory": "packages/tenants" }, "scripts": { "build": "yarn run clean && yarn run compile", diff --git a/packages/module-tenants/src/TenantAgent.ts b/packages/tenants/src/TenantAgent.ts similarity index 100% rename from packages/module-tenants/src/TenantAgent.ts rename to packages/tenants/src/TenantAgent.ts diff --git a/packages/module-tenants/src/TenantsApi.ts b/packages/tenants/src/TenantsApi.ts similarity index 100% rename from packages/module-tenants/src/TenantsApi.ts rename to packages/tenants/src/TenantsApi.ts diff --git a/packages/module-tenants/src/TenantsApiOptions.ts b/packages/tenants/src/TenantsApiOptions.ts similarity index 100% rename from packages/module-tenants/src/TenantsApiOptions.ts rename to packages/tenants/src/TenantsApiOptions.ts diff --git a/packages/module-tenants/src/TenantsModule.ts b/packages/tenants/src/TenantsModule.ts similarity index 100% rename from packages/module-tenants/src/TenantsModule.ts rename to packages/tenants/src/TenantsModule.ts diff --git a/packages/module-tenants/src/TenantsModuleConfig.ts b/packages/tenants/src/TenantsModuleConfig.ts similarity index 100% rename from packages/module-tenants/src/TenantsModuleConfig.ts rename to packages/tenants/src/TenantsModuleConfig.ts diff --git a/packages/module-tenants/src/__tests__/TenantAgent.test.ts b/packages/tenants/src/__tests__/TenantAgent.test.ts similarity index 95% rename from packages/module-tenants/src/__tests__/TenantAgent.test.ts rename to packages/tenants/src/__tests__/TenantAgent.test.ts index ce599ef4bf..75da99c41c 100644 --- a/packages/module-tenants/src/__tests__/TenantAgent.test.ts +++ b/packages/tenants/src/__tests__/TenantAgent.test.ts @@ -1,6 +1,6 @@ import { Agent, AgentContext } from '@aries-framework/core' +import { agentDependencies, getAgentConfig, getAgentContext } from '@aries-framework/core/tests/helpers' -import { agentDependencies, getAgentConfig, getAgentContext } from '../../../core/tests/helpers' import { TenantAgent } from '../TenantAgent' describe('TenantAgent', () => { diff --git a/packages/module-tenants/src/__tests__/TenantsApi.test.ts b/packages/tenants/src/__tests__/TenantsApi.test.ts similarity index 99% rename from packages/module-tenants/src/__tests__/TenantsApi.test.ts rename to packages/tenants/src/__tests__/TenantsApi.test.ts index e2c5c28fed..16326d93d9 100644 --- a/packages/module-tenants/src/__tests__/TenantsApi.test.ts +++ b/packages/tenants/src/__tests__/TenantsApi.test.ts @@ -1,6 +1,6 @@ import { Agent, AgentContext, InjectionSymbols } from '@aries-framework/core' +import { getAgentContext, getAgentOptions, mockFunction } from '@aries-framework/core/tests/helpers' -import { getAgentContext, getAgentOptions, mockFunction } from '../../../core/tests/helpers' import { TenantAgent } from '../TenantAgent' import { TenantsApi } from '../TenantsApi' import { TenantAgentContextProvider } from '../context/TenantAgentContextProvider' diff --git a/packages/module-tenants/src/__tests__/TenantsModule.test.ts b/packages/tenants/src/__tests__/TenantsModule.test.ts similarity index 95% rename from packages/module-tenants/src/__tests__/TenantsModule.test.ts rename to packages/tenants/src/__tests__/TenantsModule.test.ts index fb0ab20231..def4fae18f 100644 --- a/packages/module-tenants/src/__tests__/TenantsModule.test.ts +++ b/packages/tenants/src/__tests__/TenantsModule.test.ts @@ -1,6 +1,6 @@ import { InjectionSymbols } from '@aries-framework/core' +import { DependencyManager } from '@aries-framework/core/src/plugins/DependencyManager' -import { DependencyManager } from '../../../core/src/plugins/DependencyManager' import { TenantsApi } from '../TenantsApi' import { TenantsModule } from '../TenantsModule' import { TenantsModuleConfig } from '../TenantsModuleConfig' diff --git a/packages/module-tenants/src/__tests__/TenantsModuleConfig.test.ts b/packages/tenants/src/__tests__/TenantsModuleConfig.test.ts similarity index 100% rename from packages/module-tenants/src/__tests__/TenantsModuleConfig.test.ts rename to packages/tenants/src/__tests__/TenantsModuleConfig.test.ts diff --git a/packages/module-tenants/src/context/TenantAgentContextProvider.ts b/packages/tenants/src/context/TenantAgentContextProvider.ts similarity index 100% rename from packages/module-tenants/src/context/TenantAgentContextProvider.ts rename to packages/tenants/src/context/TenantAgentContextProvider.ts diff --git a/packages/module-tenants/src/context/TenantSessionCoordinator.ts b/packages/tenants/src/context/TenantSessionCoordinator.ts similarity index 100% rename from packages/module-tenants/src/context/TenantSessionCoordinator.ts rename to packages/tenants/src/context/TenantSessionCoordinator.ts diff --git a/packages/module-tenants/src/context/TenantSessionMutex.ts b/packages/tenants/src/context/TenantSessionMutex.ts similarity index 100% rename from packages/module-tenants/src/context/TenantSessionMutex.ts rename to packages/tenants/src/context/TenantSessionMutex.ts diff --git a/packages/module-tenants/src/context/__tests__/TenantAgentContextProvider.test.ts b/packages/tenants/src/context/__tests__/TenantAgentContextProvider.test.ts similarity index 98% rename from packages/module-tenants/src/context/__tests__/TenantAgentContextProvider.test.ts rename to packages/tenants/src/context/__tests__/TenantAgentContextProvider.test.ts index aa6f80cd3b..ce5223e958 100644 --- a/packages/module-tenants/src/context/__tests__/TenantAgentContextProvider.test.ts +++ b/packages/tenants/src/context/__tests__/TenantAgentContextProvider.test.ts @@ -1,9 +1,9 @@ import type { AgentContext } from '@aries-framework/core' import { Key } from '@aries-framework/core' +import { EventEmitter } from '@aries-framework/core/src/agent/EventEmitter' +import { getAgentConfig, getAgentContext, mockFunction } from '@aries-framework/core/tests/helpers' -import { EventEmitter } from '../../../../core/src/agent/EventEmitter' -import { getAgentConfig, getAgentContext, mockFunction } from '../../../../core/tests/helpers' import { TenantRecord, TenantRoutingRecord } from '../../repository' import { TenantRecordService } from '../../services/TenantRecordService' import { TenantAgentContextProvider } from '../TenantAgentContextProvider' diff --git a/packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts b/packages/tenants/src/context/__tests__/TenantSessionCoordinator.test.ts similarity index 98% rename from packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts rename to packages/tenants/src/context/__tests__/TenantSessionCoordinator.test.ts index dd659db44c..494c9ddf87 100644 --- a/packages/module-tenants/src/context/__tests__/TenantSessionCoordinator.test.ts +++ b/packages/tenants/src/context/__tests__/TenantSessionCoordinator.test.ts @@ -2,10 +2,10 @@ import type { TenantAgentContextMapping } from '../TenantSessionCoordinator' import type { DependencyManager } from '@aries-framework/core' import { AgentContext, AgentConfig, WalletApi } from '@aries-framework/core' +import { getAgentConfig, getAgentContext, mockFunction } from '@aries-framework/core/tests/helpers' +import testLogger from '@aries-framework/core/tests/logger' import { Mutex, withTimeout } from 'async-mutex' -import { getAgentConfig, getAgentContext, mockFunction } from '../../../../core/tests/helpers' -import testLogger from '../../../../core/tests/logger' import { TenantsModuleConfig } from '../../TenantsModuleConfig' import { TenantRecord } from '../../repository' import { TenantSessionCoordinator } from '../TenantSessionCoordinator' diff --git a/packages/module-tenants/src/context/__tests__/TenantSessionMutex.test.ts b/packages/tenants/src/context/__tests__/TenantSessionMutex.test.ts similarity index 97% rename from packages/module-tenants/src/context/__tests__/TenantSessionMutex.test.ts rename to packages/tenants/src/context/__tests__/TenantSessionMutex.test.ts index 6430e9b831..95ee0ab503 100644 --- a/packages/module-tenants/src/context/__tests__/TenantSessionMutex.test.ts +++ b/packages/tenants/src/context/__tests__/TenantSessionMutex.test.ts @@ -1,4 +1,5 @@ -import testLogger from '../../../../core/tests/logger' +import testLogger from '@aries-framework/core/tests/logger' + import { TenantSessionMutex } from '../TenantSessionMutex' describe('TenantSessionMutex', () => { diff --git a/packages/module-tenants/src/context/types.ts b/packages/tenants/src/context/types.ts similarity index 100% rename from packages/module-tenants/src/context/types.ts rename to packages/tenants/src/context/types.ts diff --git a/packages/module-tenants/src/index.ts b/packages/tenants/src/index.ts similarity index 100% rename from packages/module-tenants/src/index.ts rename to packages/tenants/src/index.ts diff --git a/packages/module-tenants/src/models/TenantConfig.ts b/packages/tenants/src/models/TenantConfig.ts similarity index 100% rename from packages/module-tenants/src/models/TenantConfig.ts rename to packages/tenants/src/models/TenantConfig.ts diff --git a/packages/module-tenants/src/repository/TenantRecord.ts b/packages/tenants/src/repository/TenantRecord.ts similarity index 100% rename from packages/module-tenants/src/repository/TenantRecord.ts rename to packages/tenants/src/repository/TenantRecord.ts diff --git a/packages/module-tenants/src/repository/TenantRepository.ts b/packages/tenants/src/repository/TenantRepository.ts similarity index 100% rename from packages/module-tenants/src/repository/TenantRepository.ts rename to packages/tenants/src/repository/TenantRepository.ts diff --git a/packages/module-tenants/src/repository/TenantRoutingRecord.ts b/packages/tenants/src/repository/TenantRoutingRecord.ts similarity index 100% rename from packages/module-tenants/src/repository/TenantRoutingRecord.ts rename to packages/tenants/src/repository/TenantRoutingRecord.ts diff --git a/packages/module-tenants/src/repository/TenantRoutingRepository.ts b/packages/tenants/src/repository/TenantRoutingRepository.ts similarity index 100% rename from packages/module-tenants/src/repository/TenantRoutingRepository.ts rename to packages/tenants/src/repository/TenantRoutingRepository.ts diff --git a/packages/module-tenants/src/repository/__tests__/TenantRecord.test.ts b/packages/tenants/src/repository/__tests__/TenantRecord.test.ts similarity index 96% rename from packages/module-tenants/src/repository/__tests__/TenantRecord.test.ts rename to packages/tenants/src/repository/__tests__/TenantRecord.test.ts index 7c6e311704..6ab0b39916 100644 --- a/packages/module-tenants/src/repository/__tests__/TenantRecord.test.ts +++ b/packages/tenants/src/repository/__tests__/TenantRecord.test.ts @@ -1,4 +1,5 @@ -import { JsonTransformer } from '../../../../core/src' +import { JsonTransformer } from '@aries-framework/core/src' + import { TenantRecord } from '../TenantRecord' describe('TenantRecord', () => { diff --git a/packages/module-tenants/src/repository/__tests__/TenantRoutingRecord.test.ts b/packages/tenants/src/repository/__tests__/TenantRoutingRecord.test.ts similarity index 97% rename from packages/module-tenants/src/repository/__tests__/TenantRoutingRecord.test.ts rename to packages/tenants/src/repository/__tests__/TenantRoutingRecord.test.ts index 1d47b2bd18..7e37bd57fd 100644 --- a/packages/module-tenants/src/repository/__tests__/TenantRoutingRecord.test.ts +++ b/packages/tenants/src/repository/__tests__/TenantRoutingRecord.test.ts @@ -1,4 +1,5 @@ -import { JsonTransformer } from '../../../../core/src' +import { JsonTransformer } from '@aries-framework/core/src' + import { TenantRoutingRecord } from '../TenantRoutingRecord' describe('TenantRoutingRecord', () => { diff --git a/packages/module-tenants/src/repository/__tests__/TenantRoutingRepository.test.ts b/packages/tenants/src/repository/__tests__/TenantRoutingRepository.test.ts similarity index 86% rename from packages/module-tenants/src/repository/__tests__/TenantRoutingRepository.test.ts rename to packages/tenants/src/repository/__tests__/TenantRoutingRepository.test.ts index ed22ee31ab..d8721fd1be 100644 --- a/packages/module-tenants/src/repository/__tests__/TenantRoutingRepository.test.ts +++ b/packages/tenants/src/repository/__tests__/TenantRoutingRepository.test.ts @@ -1,7 +1,8 @@ -import type { StorageService, EventEmitter } from '../../../../core/src' +import type { StorageService, EventEmitter } from '@aries-framework/core/src' + +import { Key } from '@aries-framework/core/src' +import { getAgentContext, mockFunction } from '@aries-framework/core/tests/helpers' -import { Key } from '../../../../core/src' -import { getAgentContext, mockFunction } from '../../../../core/tests/helpers' import { TenantRoutingRecord } from '../TenantRoutingRecord' import { TenantRoutingRepository } from '../TenantRoutingRepository' diff --git a/packages/module-tenants/src/repository/index.ts b/packages/tenants/src/repository/index.ts similarity index 100% rename from packages/module-tenants/src/repository/index.ts rename to packages/tenants/src/repository/index.ts diff --git a/packages/module-tenants/src/services/TenantRecordService.ts b/packages/tenants/src/services/TenantRecordService.ts similarity index 100% rename from packages/module-tenants/src/services/TenantRecordService.ts rename to packages/tenants/src/services/TenantRecordService.ts diff --git a/packages/module-tenants/src/services/__tests__/TenantService.test.ts b/packages/tenants/src/services/__tests__/TenantService.test.ts similarity index 98% rename from packages/module-tenants/src/services/__tests__/TenantService.test.ts rename to packages/tenants/src/services/__tests__/TenantService.test.ts index 228eb597a4..e7573e5389 100644 --- a/packages/module-tenants/src/services/__tests__/TenantService.test.ts +++ b/packages/tenants/src/services/__tests__/TenantService.test.ts @@ -1,8 +1,8 @@ import type { Wallet } from '@aries-framework/core' import { Key } from '@aries-framework/core' +import { getAgentContext, mockFunction } from '@aries-framework/core/tests/helpers' -import { getAgentContext, mockFunction } from '../../../../core/tests/helpers' import { TenantRecord, TenantRoutingRecord } from '../../repository' import { TenantRepository } from '../../repository/TenantRepository' import { TenantRoutingRepository } from '../../repository/TenantRoutingRepository' diff --git a/packages/module-tenants/src/services/index.ts b/packages/tenants/src/services/index.ts similarity index 100% rename from packages/module-tenants/src/services/index.ts rename to packages/tenants/src/services/index.ts diff --git a/packages/module-tenants/tests/setup.ts b/packages/tenants/tests/setup.ts similarity index 100% rename from packages/module-tenants/tests/setup.ts rename to packages/tenants/tests/setup.ts diff --git a/packages/module-tenants/tests/tenant-sessions.e2e.test.ts b/packages/tenants/tests/tenant-sessions.e2e.test.ts similarity index 95% rename from packages/module-tenants/tests/tenant-sessions.e2e.test.ts rename to packages/tenants/tests/tenant-sessions.e2e.test.ts index 61da36bd79..f708fdbcf1 100644 --- a/packages/module-tenants/tests/tenant-sessions.e2e.test.ts +++ b/packages/tenants/tests/tenant-sessions.e2e.test.ts @@ -1,11 +1,10 @@ import type { InitConfig } from '@aries-framework/core' import { Agent } from '@aries-framework/core' +import testLogger from '@aries-framework/core/tests/logger' import { agentDependencies } from '@aries-framework/node' -import testLogger from '../../core/tests/logger' - -import { TenantsModule } from '@aries-framework/module-tenants' +import { TenantsModule } from '@aries-framework/tenants' jest.setTimeout(2000000) diff --git a/packages/module-tenants/tests/tenants.e2e.test.ts b/packages/tenants/tests/tenants.e2e.test.ts similarity index 98% rename from packages/module-tenants/tests/tenants.e2e.test.ts rename to packages/tenants/tests/tenants.e2e.test.ts index 5c2a616e57..9374d81bb0 100644 --- a/packages/module-tenants/tests/tenants.e2e.test.ts +++ b/packages/tenants/tests/tenants.e2e.test.ts @@ -1,13 +1,13 @@ import type { InitConfig } from '@aries-framework/core' import { OutOfBandRecord, Agent } from '@aries-framework/core' +import testLogger from '@aries-framework/core/tests/logger' import { agentDependencies } from '@aries-framework/node' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import testLogger from '../../core/tests/logger' -import { TenantsModule } from '@aries-framework/module-tenants' +import { TenantsModule } from '@aries-framework/tenants' jest.setTimeout(2000000) diff --git a/packages/module-tenants/tsconfig.build.json b/packages/tenants/tsconfig.build.json similarity index 100% rename from packages/module-tenants/tsconfig.build.json rename to packages/tenants/tsconfig.build.json diff --git a/packages/module-tenants/tsconfig.json b/packages/tenants/tsconfig.json similarity index 100% rename from packages/module-tenants/tsconfig.json rename to packages/tenants/tsconfig.json From 6652520a3b73a8568f53ee241357e54247ef9b79 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 20 Oct 2022 11:36:51 +0300 Subject: [PATCH 059/125] chore: fix incorrect import paths Signed-off-by: Timo Glastra --- .../src/signature-suites/BbsBlsSignatureProof2020.ts | 2 +- packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts | 8 ++++---- .../bbs-signatures/tests/bbs-signing-provider.e2e.test.ts | 2 +- packages/tenants/src/__tests__/TenantAgent.test.ts | 2 +- packages/tenants/src/__tests__/TenantsApi.test.ts | 2 +- packages/tenants/src/__tests__/TenantsModule.test.ts | 2 +- .../context/__tests__/TenantAgentContextProvider.test.ts | 4 ++-- .../context/__tests__/TenantSessionCoordinator.test.ts | 4 ++-- .../src/context/__tests__/TenantSessionMutex.test.ts | 3 +-- .../tenants/src/repository/__tests__/TenantRecord.test.ts | 2 +- .../src/repository/__tests__/TenantRoutingRecord.test.ts | 2 +- .../repository/__tests__/TenantRoutingRepository.test.ts | 6 +++--- .../tenants/src/services/__tests__/TenantService.test.ts | 2 +- packages/tenants/tests/tenant-sessions.e2e.test.ts | 3 ++- packages/tenants/tests/tenants.e2e.test.ts | 2 +- 15 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/bbs-signatures/src/signature-suites/BbsBlsSignatureProof2020.ts b/packages/bbs-signatures/src/signature-suites/BbsBlsSignatureProof2020.ts index ddf5b5d119..59d0b37eb3 100644 --- a/packages/bbs-signatures/src/signature-suites/BbsBlsSignatureProof2020.ts +++ b/packages/bbs-signatures/src/signature-suites/BbsBlsSignatureProof2020.ts @@ -13,7 +13,7 @@ import type { DeriveProofOptions, VerifyProofOptions, CreateVerifyDataOptions, CanonizeOptions } from '../types' import type { VerifyProofResult } from '../types/VerifyProofResult' -import type { JsonObject, DocumentLoader, Proof } from '@aries-framework/core/src' +import type { JsonObject, DocumentLoader, Proof } from '@aries-framework/core' import { AriesFrameworkError, TypedArrayEncoder, SECURITY_CONTEXT_URL, vcLibraries } from '@aries-framework/core' import { blsCreateProof, blsVerifyProof } from '@mattrglobal/bbs-signatures' diff --git a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts index 13e36b9fae..72a76fa517 100644 --- a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts @@ -1,5 +1,5 @@ +import type { W3cCredentialRepository } from '../../core/src/modules/vc/repository' import type { AgentContext } from '@aries-framework/core' -import type { W3cCredentialRepository } from '@aries-framework/core/src/modules/vc/repository' import { VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, @@ -22,10 +22,10 @@ import { IndyWallet, Ed25519Signature2018, } from '@aries-framework/core' -import { SignatureSuiteRegistry } from '@aries-framework/core/src/modules/vc/SignatureSuiteRegistry' -import { customDocumentLoader } from '@aries-framework/core/src/modules/vc/__tests__/documentLoader' -import { getAgentConfig, getAgentContext } from '@aries-framework/core/tests/helpers' +import { SignatureSuiteRegistry } from '../../core/src/modules/vc/SignatureSuiteRegistry' +import { customDocumentLoader } from '../../core/src/modules/vc/__tests__/documentLoader' +import { getAgentConfig, getAgentContext } from '../../core/tests/helpers' import { BbsBlsSignature2020, BbsBlsSignatureProof2020, Bls12381g2SigningProvider } from '../src' import { BbsBlsSignature2020Fixtures } from './fixtures' diff --git a/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts index 5f5a5c4141..b5a90765af 100644 --- a/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts @@ -8,10 +8,10 @@ import { SigningProviderRegistry, IndyWallet, } from '@aries-framework/core' -import testLogger from '@aries-framework/core/tests/logger' import { agentDependencies } from '@aries-framework/node' import { BBS_SIGNATURE_LENGTH } from '@mattrglobal/bbs-signatures' +import testLogger from '../../core/tests/logger' import { Bls12381g2SigningProvider } from '../src' import { describeSkipNode17And18 } from './util' diff --git a/packages/tenants/src/__tests__/TenantAgent.test.ts b/packages/tenants/src/__tests__/TenantAgent.test.ts index 75da99c41c..ce599ef4bf 100644 --- a/packages/tenants/src/__tests__/TenantAgent.test.ts +++ b/packages/tenants/src/__tests__/TenantAgent.test.ts @@ -1,6 +1,6 @@ import { Agent, AgentContext } from '@aries-framework/core' -import { agentDependencies, getAgentConfig, getAgentContext } from '@aries-framework/core/tests/helpers' +import { agentDependencies, getAgentConfig, getAgentContext } from '../../../core/tests/helpers' import { TenantAgent } from '../TenantAgent' describe('TenantAgent', () => { diff --git a/packages/tenants/src/__tests__/TenantsApi.test.ts b/packages/tenants/src/__tests__/TenantsApi.test.ts index 16326d93d9..e2c5c28fed 100644 --- a/packages/tenants/src/__tests__/TenantsApi.test.ts +++ b/packages/tenants/src/__tests__/TenantsApi.test.ts @@ -1,6 +1,6 @@ import { Agent, AgentContext, InjectionSymbols } from '@aries-framework/core' -import { getAgentContext, getAgentOptions, mockFunction } from '@aries-framework/core/tests/helpers' +import { getAgentContext, getAgentOptions, mockFunction } from '../../../core/tests/helpers' import { TenantAgent } from '../TenantAgent' import { TenantsApi } from '../TenantsApi' import { TenantAgentContextProvider } from '../context/TenantAgentContextProvider' diff --git a/packages/tenants/src/__tests__/TenantsModule.test.ts b/packages/tenants/src/__tests__/TenantsModule.test.ts index def4fae18f..fb0ab20231 100644 --- a/packages/tenants/src/__tests__/TenantsModule.test.ts +++ b/packages/tenants/src/__tests__/TenantsModule.test.ts @@ -1,6 +1,6 @@ import { InjectionSymbols } from '@aries-framework/core' -import { DependencyManager } from '@aries-framework/core/src/plugins/DependencyManager' +import { DependencyManager } from '../../../core/src/plugins/DependencyManager' import { TenantsApi } from '../TenantsApi' import { TenantsModule } from '../TenantsModule' import { TenantsModuleConfig } from '../TenantsModuleConfig' diff --git a/packages/tenants/src/context/__tests__/TenantAgentContextProvider.test.ts b/packages/tenants/src/context/__tests__/TenantAgentContextProvider.test.ts index ce5223e958..aa6f80cd3b 100644 --- a/packages/tenants/src/context/__tests__/TenantAgentContextProvider.test.ts +++ b/packages/tenants/src/context/__tests__/TenantAgentContextProvider.test.ts @@ -1,9 +1,9 @@ import type { AgentContext } from '@aries-framework/core' import { Key } from '@aries-framework/core' -import { EventEmitter } from '@aries-framework/core/src/agent/EventEmitter' -import { getAgentConfig, getAgentContext, mockFunction } from '@aries-framework/core/tests/helpers' +import { EventEmitter } from '../../../../core/src/agent/EventEmitter' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../core/tests/helpers' import { TenantRecord, TenantRoutingRecord } from '../../repository' import { TenantRecordService } from '../../services/TenantRecordService' import { TenantAgentContextProvider } from '../TenantAgentContextProvider' diff --git a/packages/tenants/src/context/__tests__/TenantSessionCoordinator.test.ts b/packages/tenants/src/context/__tests__/TenantSessionCoordinator.test.ts index 494c9ddf87..dd659db44c 100644 --- a/packages/tenants/src/context/__tests__/TenantSessionCoordinator.test.ts +++ b/packages/tenants/src/context/__tests__/TenantSessionCoordinator.test.ts @@ -2,10 +2,10 @@ import type { TenantAgentContextMapping } from '../TenantSessionCoordinator' import type { DependencyManager } from '@aries-framework/core' import { AgentContext, AgentConfig, WalletApi } from '@aries-framework/core' -import { getAgentConfig, getAgentContext, mockFunction } from '@aries-framework/core/tests/helpers' -import testLogger from '@aries-framework/core/tests/logger' import { Mutex, withTimeout } from 'async-mutex' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../core/tests/helpers' +import testLogger from '../../../../core/tests/logger' import { TenantsModuleConfig } from '../../TenantsModuleConfig' import { TenantRecord } from '../../repository' import { TenantSessionCoordinator } from '../TenantSessionCoordinator' diff --git a/packages/tenants/src/context/__tests__/TenantSessionMutex.test.ts b/packages/tenants/src/context/__tests__/TenantSessionMutex.test.ts index 95ee0ab503..6430e9b831 100644 --- a/packages/tenants/src/context/__tests__/TenantSessionMutex.test.ts +++ b/packages/tenants/src/context/__tests__/TenantSessionMutex.test.ts @@ -1,5 +1,4 @@ -import testLogger from '@aries-framework/core/tests/logger' - +import testLogger from '../../../../core/tests/logger' import { TenantSessionMutex } from '../TenantSessionMutex' describe('TenantSessionMutex', () => { diff --git a/packages/tenants/src/repository/__tests__/TenantRecord.test.ts b/packages/tenants/src/repository/__tests__/TenantRecord.test.ts index 6ab0b39916..563eac3a38 100644 --- a/packages/tenants/src/repository/__tests__/TenantRecord.test.ts +++ b/packages/tenants/src/repository/__tests__/TenantRecord.test.ts @@ -1,4 +1,4 @@ -import { JsonTransformer } from '@aries-framework/core/src' +import { JsonTransformer } from '@aries-framework/core' import { TenantRecord } from '../TenantRecord' diff --git a/packages/tenants/src/repository/__tests__/TenantRoutingRecord.test.ts b/packages/tenants/src/repository/__tests__/TenantRoutingRecord.test.ts index 7e37bd57fd..424f753480 100644 --- a/packages/tenants/src/repository/__tests__/TenantRoutingRecord.test.ts +++ b/packages/tenants/src/repository/__tests__/TenantRoutingRecord.test.ts @@ -1,4 +1,4 @@ -import { JsonTransformer } from '@aries-framework/core/src' +import { JsonTransformer } from '@aries-framework/core' import { TenantRoutingRecord } from '../TenantRoutingRecord' diff --git a/packages/tenants/src/repository/__tests__/TenantRoutingRepository.test.ts b/packages/tenants/src/repository/__tests__/TenantRoutingRepository.test.ts index d8721fd1be..46135788ca 100644 --- a/packages/tenants/src/repository/__tests__/TenantRoutingRepository.test.ts +++ b/packages/tenants/src/repository/__tests__/TenantRoutingRepository.test.ts @@ -1,8 +1,8 @@ -import type { StorageService, EventEmitter } from '@aries-framework/core/src' +import type { StorageService, EventEmitter } from '@aries-framework/core' -import { Key } from '@aries-framework/core/src' -import { getAgentContext, mockFunction } from '@aries-framework/core/tests/helpers' +import { Key } from '@aries-framework/core' +import { getAgentContext, mockFunction } from '../../../../core/tests/helpers' import { TenantRoutingRecord } from '../TenantRoutingRecord' import { TenantRoutingRepository } from '../TenantRoutingRepository' diff --git a/packages/tenants/src/services/__tests__/TenantService.test.ts b/packages/tenants/src/services/__tests__/TenantService.test.ts index e7573e5389..228eb597a4 100644 --- a/packages/tenants/src/services/__tests__/TenantService.test.ts +++ b/packages/tenants/src/services/__tests__/TenantService.test.ts @@ -1,8 +1,8 @@ import type { Wallet } from '@aries-framework/core' import { Key } from '@aries-framework/core' -import { getAgentContext, mockFunction } from '@aries-framework/core/tests/helpers' +import { getAgentContext, mockFunction } from '../../../../core/tests/helpers' import { TenantRecord, TenantRoutingRecord } from '../../repository' import { TenantRepository } from '../../repository/TenantRepository' import { TenantRoutingRepository } from '../../repository/TenantRoutingRepository' diff --git a/packages/tenants/tests/tenant-sessions.e2e.test.ts b/packages/tenants/tests/tenant-sessions.e2e.test.ts index f708fdbcf1..68d6d948cf 100644 --- a/packages/tenants/tests/tenant-sessions.e2e.test.ts +++ b/packages/tenants/tests/tenant-sessions.e2e.test.ts @@ -1,9 +1,10 @@ import type { InitConfig } from '@aries-framework/core' import { Agent } from '@aries-framework/core' -import testLogger from '@aries-framework/core/tests/logger' import { agentDependencies } from '@aries-framework/node' +import testLogger from '../../core/tests/logger' + import { TenantsModule } from '@aries-framework/tenants' jest.setTimeout(2000000) diff --git a/packages/tenants/tests/tenants.e2e.test.ts b/packages/tenants/tests/tenants.e2e.test.ts index 9374d81bb0..e9a18ef1e5 100644 --- a/packages/tenants/tests/tenants.e2e.test.ts +++ b/packages/tenants/tests/tenants.e2e.test.ts @@ -1,11 +1,11 @@ import type { InitConfig } from '@aries-framework/core' import { OutOfBandRecord, Agent } from '@aries-framework/core' -import testLogger from '@aries-framework/core/tests/logger' import { agentDependencies } from '@aries-framework/node' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import testLogger from '../../core/tests/logger' import { TenantsModule } from '@aries-framework/tenants' From 76dab16f6abe46ad01341d097979f8bcec834c50 Mon Sep 17 00:00:00 2001 From: NB-MikeRichardson <93971245+NB-MikeRichardson@users.noreply.github.com> Date: Tue, 25 Oct 2022 11:24:07 +0300 Subject: [PATCH 060/125] refactor(proofs): remove ProofProtocolVersion enum in 0.3.0-pre (#1062) Signed-off-by: Mike Richardson --- demo/src/Faber.ts | 3 +- packages/core/src/modules/proofs/ProofsApi.ts | 5 +- .../proofs/__tests__/V1ProofService.test.ts | 3 +- .../proofs/__tests__/V2ProofService.test.ts | 3 +- .../proofs/models/ProofProtocolVersion.ts | 4 - .../core/src/modules/proofs/models/index.ts | 1 - .../proofs/protocol/v1/V1ProofService.ts | 9 +- .../__tests__/indy-proof-presentation.test.ts | 9 +- .../v1/__tests__/indy-proof-proposal.test.ts | 19 ++--- .../v1/__tests__/indy-proof-request.test.ts | 23 +++-- .../proofs/protocol/v2/V2ProofService.ts | 9 +- .../__tests__/indy-proof-presentation.test.ts | 23 +++-- .../v2/__tests__/indy-proof-proposal.test.ts | 19 ++--- .../v2/__tests__/indy-proof-request.test.ts | 21 +++-- packages/core/tests/helpers.ts | 17 ++-- .../core/tests/proofs-sub-protocol.test.ts | 9 +- .../tests/v1-connectionless-proofs.test.ts | 68 +++++++-------- packages/core/tests/v1-indy-proofs.test.ts | 85 +++++++++---------- .../core/tests/v1-proofs-auto-accept.test.ts | 54 ++++++------ .../tests/v2-connectionless-proofs.test.ts | 65 +++++++------- packages/core/tests/v2-indy-proofs.test.ts | 73 ++++++++-------- .../core/tests/v2-proofs-auto-accept.test.ts | 55 ++++++------ 22 files changed, 264 insertions(+), 313 deletions(-) delete mode 100644 packages/core/src/modules/proofs/models/ProofProtocolVersion.ts diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index 8d127c1a43..267349b785 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -5,7 +5,6 @@ import type BottomBar from 'inquirer/lib/ui/bottom-bar' import { AttributeFilter, ProofAttributeInfo, - ProofProtocolVersion, utils, V1CredentialPreview, ConnectionEventTypes, @@ -190,7 +189,7 @@ export class Faber extends BaseAgent { await this.printProofFlow(greenText('\nRequesting proof...\n', false)) await this.agent.proofs.requestProof({ - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', connectionId: connectionRecord.id, proofFormats: { indy: { diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index c82b09c9ef..27b72d6fcf 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -164,7 +164,7 @@ export class ProofsApi< * to include in the message * @returns Proof record associated with the sent proposal message */ - public async proposeProof(options: ProposeProofOptions): Promise { + public async proposeProof(options: ProposeProofOptions): Promise { const service = this.getService(options.protocolVersion) const { connectionId } = options @@ -247,7 +247,7 @@ export class ProofsApi< * @param options multiple properties like connection id, protocol version, proof Formats to build the proof request * @returns Proof record associated with the sent request message */ - public async requestProof(options: RequestProofOptions): Promise { + public async requestProof(options: RequestProofOptions): Promise { const service = this.getService(options.protocolVersion) const connection = await this.connectionService.getById(this.agentContext, options.connectionId) @@ -456,6 +456,7 @@ export class ProofsApi< /** * Create a {@link RetrievedCredentials} object. Given input proof request and presentation proposal, * use credentials in the wallet to build indy requested credentials object for input to proof creation. + * * If restrictions allow, self attested attributes will be used. * * @param options multiple properties like proof record id and optional configuration diff --git a/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts b/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts index b093e049dc..7c73c959d5 100644 --- a/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts +++ b/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts @@ -18,7 +18,6 @@ import { IndyLedgerService } from '../../ledger/services' import { ProofEventTypes } from '../ProofEvents' import { PresentationProblemReportReason } from '../errors/PresentationProblemReportReason' import { IndyProofFormatService } from '../formats/indy/IndyProofFormatService' -import { ProofProtocolVersion } from '../models/ProofProtocolVersion' import { ProofState } from '../models/ProofState' import { V1ProofService } from '../protocol/v1' import { INDY_PROOF_REQUEST_ATTACHMENT_ID, V1RequestPresentationMessage } from '../protocol/v1/messages' @@ -83,7 +82,7 @@ const mockProofRecord = ({ }) const proofRecord = new ProofRecord({ - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', id, state: state || ProofState.RequestSent, threadId: threadId ?? requestPresentationMessage.id, diff --git a/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts b/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts index 34348720d8..e5781dce63 100644 --- a/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts +++ b/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts @@ -16,7 +16,6 @@ import { ProofEventTypes } from '../ProofEvents' import { PresentationProblemReportReason } from '../errors/PresentationProblemReportReason' import { V2_INDY_PRESENTATION, V2_INDY_PRESENTATION_REQUEST } from '../formats/ProofFormatConstants' import { IndyProofFormatService } from '../formats/indy/IndyProofFormatService' -import { ProofProtocolVersion } from '../models/ProofProtocolVersion' import { ProofState } from '../models/ProofState' import { V2ProofService } from '../protocol/v2/V2ProofService' import { V2PresentationProblemReportMessage, V2RequestPresentationMessage } from '../protocol/v2/messages' @@ -85,7 +84,7 @@ const mockProofRecord = ({ }) const proofRecord = new ProofRecord({ - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', id, state: state || ProofState.RequestSent, threadId: threadId ?? requestPresentationMessage.id, diff --git a/packages/core/src/modules/proofs/models/ProofProtocolVersion.ts b/packages/core/src/modules/proofs/models/ProofProtocolVersion.ts deleted file mode 100644 index 6027d21111..0000000000 --- a/packages/core/src/modules/proofs/models/ProofProtocolVersion.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum ProofProtocolVersion { - V1 = 'v1', - V2 = 'v2', -} diff --git a/packages/core/src/modules/proofs/models/index.ts b/packages/core/src/modules/proofs/models/index.ts index 827448009e..a092a0ae7e 100644 --- a/packages/core/src/modules/proofs/models/index.ts +++ b/packages/core/src/modules/proofs/models/index.ts @@ -1,4 +1,3 @@ export * from './GetRequestedCredentialsConfig' export * from './ProofAutoAcceptType' -export * from './ProofProtocolVersion' export * from './ProofState' diff --git a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts index 4d45ddf394..dd3479fcf2 100644 --- a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts @@ -55,7 +55,6 @@ import { IndyProofFormatService } from '../../formats/indy/IndyProofFormatServic import { IndyProofUtils } from '../../formats/indy/IndyProofUtils' import { ProofRequest } from '../../formats/indy/models/ProofRequest' import { RequestedCredentials } from '../../formats/indy/models/RequestedCredentials' -import { ProofProtocolVersion } from '../../models/ProofProtocolVersion' import { ProofState } from '../../models/ProofState' import { ProofRecord } from '../../repository/ProofRecord' import { ProofRepository } from '../../repository/ProofRepository' @@ -148,7 +147,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { parentThreadId: proposalMessage.thread?.parentThreadId, state: ProofState.ProposalSent, autoAcceptProof: options?.autoAcceptProof, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { @@ -244,7 +243,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { threadId: proposalMessage.threadId, parentThreadId: proposalMessage.thread?.parentThreadId, state: ProofState.ProposalReceived, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) // Assert @@ -337,7 +336,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { parentThreadId: requestPresentationMessage.thread?.parentThreadId, state: ProofState.RequestSent, autoAcceptProof: options?.autoAcceptProof, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { @@ -424,7 +423,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { threadId: proofRequestMessage.threadId, parentThreadId: proofRequestMessage.thread?.parentThreadId, state: ProofState.RequestReceived, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts index eb9effdb0d..a7bba2c894 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts @@ -6,7 +6,6 @@ import type { PresentationPreview } from '../models/V1PresentationPreview' import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage/didcomm' -import { ProofProtocolVersion } from '../../../models/ProofProtocolVersion' import { ProofState } from '../../../models/ProofState' import { V1PresentationMessage, V1ProposePresentationMessage, V1RequestPresentationMessage } from '../messages' @@ -44,7 +43,7 @@ describe('Present Proof', () => { aliceProofRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', proofFormats: { indy: { name: 'ProofRequest', @@ -100,7 +99,7 @@ describe('Present Proof', () => { id: expect.anything(), threadId: faberProofRecord.threadId, state: ProofState.ProposalReceived, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) }) @@ -146,7 +145,7 @@ describe('Present Proof', () => { id: expect.anything(), threadId: faberProofRecord.threadId, state: ProofState.RequestReceived, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) }) @@ -207,7 +206,7 @@ describe('Present Proof', () => { expect(faberProofRecord).toMatchObject({ threadId: faberProofRecord.threadId, state: ProofState.PresentationReceived, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) }) diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts index 5461c91a16..e0cf1c0751 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts @@ -1,13 +1,14 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' import type { ProposeProofOptions } from '../../../ProofsApiOptions' +import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' import type { ProofRecord } from '../../../repository/ProofRecord' +import type { V1ProofService } from '../V1ProofService' import type { PresentationPreview } from '../models/V1PresentationPreview' import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' -import { ProofProtocolVersion } from '../../../models/ProofProtocolVersion' import { ProofState } from '../../../models/ProofState' import { V1ProposePresentationMessage } from '../messages' @@ -38,9 +39,13 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const proposeOptions: ProposeProofOptions = { + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', proofFormats: { indy: { name: 'ProofRequest', @@ -51,14 +56,8 @@ describe('Present Proof', () => { }, }, comment: 'V1 propose proof test', - } - - const faberProofRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.ProposalReceived, }) - await aliceAgent.proofs.proposeProof(proposeOptions) - testLogger.test('Faber waits for presentation from Alice') faberProofRecord = await faberProofRecordPromise @@ -102,7 +101,7 @@ describe('Present Proof', () => { id: expect.anything(), threadId: faberProofRecord.threadId, state: ProofState.ProposalReceived, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) }) }) diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts index 6b096cd21c..7d371a4b9c 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts @@ -1,13 +1,14 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { ProposeProofOptions, AcceptProposalOptions } from '../../../ProofsApiOptions' +import type { AcceptProposalOptions, ProposeProofOptions } from '../../../ProofsApiOptions' +import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' import type { ProofRecord } from '../../../repository/ProofRecord' +import type { V1ProofService } from '../V1ProofService' import type { PresentationPreview } from '../models/V1PresentationPreview' import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage/didcomm' -import { ProofProtocolVersion } from '../../../models/ProofProtocolVersion' import { ProofState } from '../../../models/ProofState' import { V1ProposePresentationMessage, V1RequestPresentationMessage } from '../messages' @@ -39,9 +40,13 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const proposeOptions: ProposeProofOptions = { + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', proofFormats: { indy: { name: 'ProofRequest', @@ -52,14 +57,8 @@ describe('Present Proof', () => { }, }, comment: 'V1 propose proof test', - } - - const faberProofRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.ProposalReceived, }) - aliceProofRecord = await aliceAgent.proofs.proposeProof(proposeOptions) - testLogger.test('Faber waits for presentation from Alice') faberProofRecord = await faberProofRecordPromise @@ -102,7 +101,7 @@ describe('Present Proof', () => { id: expect.anything(), threadId: faberProofRecord.threadId, state: ProofState.ProposalReceived, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) }) @@ -150,7 +149,7 @@ describe('Present Proof', () => { id: expect.anything(), threadId: faberProofRecord.threadId, state: ProofState.RequestReceived, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) }) }) diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts index 0c73eedf73..25c5e171ca 100644 --- a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts @@ -42,7 +42,6 @@ import { PresentationProblemReportReason } from '../../errors/PresentationProble import { V2_INDY_PRESENTATION_REQUEST } from '../../formats/ProofFormatConstants' import { IndyProofFormatService } from '../../formats/indy/IndyProofFormatService' import { IndyProofUtils } from '../../formats/indy/IndyProofUtils' -import { ProofProtocolVersion } from '../../models/ProofProtocolVersion' import { ProofState } from '../../models/ProofState' import { PresentationRecordType, ProofRecord, ProofRepository } from '../../repository' @@ -114,7 +113,7 @@ export class V2ProofService extends P threadId: proposalMessage.threadId, parentThreadId: proposalMessage.thread?.parentThreadId, state: ProofState.ProposalSent, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) await this.proofRepository.save(agentContext, proofRecord) @@ -214,7 +213,7 @@ export class V2ProofService extends P threadId: proposalMessage.threadId, parentThreadId: proposalMessage.thread?.parentThreadId, state: ProofState.ProposalReceived, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) // Assert @@ -264,7 +263,7 @@ export class V2ProofService extends P threadId: requestMessage.threadId, parentThreadId: requestMessage.thread?.parentThreadId, state: ProofState.RequestSent, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) await this.proofRepository.save(agentContext, proofRecord) @@ -397,7 +396,7 @@ export class V2ProofService extends P threadId: proofRequestMessage.threadId, parentThreadId: proofRequestMessage.thread?.parentThreadId, state: ProofState.RequestReceived, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts index d02975e18b..04c06388b5 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts @@ -1,8 +1,10 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' import type { ProposeProofOptions, AcceptProposalOptions } from '../../../ProofsApiOptions' +import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' import type { ProofRecord } from '../../../repository/ProofRecord' import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' +import type { V2ProofService } from '../V2ProofService' import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' @@ -12,7 +14,6 @@ import { V2_INDY_PRESENTATION_REQUEST, V2_INDY_PRESENTATION, } from '../../../formats/ProofFormatConstants' -import { ProofProtocolVersion } from '../../../models/ProofProtocolVersion' import { ProofState } from '../../../models/ProofState' import { V2PresentationMessage, V2RequestPresentationMessage } from '../messages' import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' @@ -45,9 +46,13 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const proposeOptions: ProposeProofOptions = { + const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', proofFormats: { indy: { name: 'ProofRequest', @@ -58,14 +63,8 @@ describe('Present Proof', () => { }, }, comment: 'V2 propose proof test', - } - - const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.ProposalReceived, }) - aliceProofRecord = await aliceAgent.proofs.proposeProof(proposeOptions) - testLogger.test('Faber waits for presentation from Alice') faberProofRecord = await faberPresentationRecordPromise @@ -100,7 +99,7 @@ describe('Present Proof', () => { id: expect.anything(), threadId: faberProofRecord.threadId, state: ProofState.ProposalReceived, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) }) @@ -154,7 +153,7 @@ describe('Present Proof', () => { id: expect.anything(), threadId: faberProofRecord.threadId, state: ProofState.RequestReceived, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) }) @@ -214,7 +213,7 @@ describe('Present Proof', () => { expect(faberProofRecord).toMatchObject({ threadId: faberProofRecord.threadId, state: ProofState.PresentationReceived, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) }) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts index 5bed51e09b..8b3d37be7c 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts @@ -1,14 +1,15 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' import type { ProposeProofOptions } from '../../../ProofsApiOptions' +import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' import type { ProofRecord } from '../../../repository' import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' +import type { V2ProofService } from '../V2ProofService' import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' import { V2_INDY_PRESENTATION_PROPOSAL } from '../../../formats/ProofFormatConstants' -import { ProofProtocolVersion } from '../../../models/ProofProtocolVersion' import { ProofState } from '../../../models/ProofState' import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' @@ -39,9 +40,13 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const proposeOptions: ProposeProofOptions = { + const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', proofFormats: { indy: { name: 'ProofRequest', @@ -52,14 +57,8 @@ describe('Present Proof', () => { }, }, comment: 'V2 propose proof test', - } - - const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.ProposalReceived, }) - await aliceAgent.proofs.proposeProof(proposeOptions) - testLogger.test('Faber waits for presentation from Alice') faberPresentationRecord = await faberPresentationRecordPromise @@ -94,7 +93,7 @@ describe('Present Proof', () => { id: expect.anything(), threadId: faberPresentationRecord.threadId, state: ProofState.ProposalReceived, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) }) }) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts index 9101a956f7..c2d7ea262e 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts @@ -1,14 +1,15 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' import type { ProposeProofOptions, AcceptProposalOptions } from '../../../ProofsApiOptions' +import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' import type { ProofRecord } from '../../../repository/ProofRecord' import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' +import type { V2ProofService } from '../V2ProofService' import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' import { V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST } from '../../../formats/ProofFormatConstants' -import { ProofProtocolVersion } from '../../../models/ProofProtocolVersion' import { ProofState } from '../../../models/ProofState' import { V2RequestPresentationMessage } from '../messages' import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' @@ -41,9 +42,13 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const proposeOptions: ProposeProofOptions = { + const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', proofFormats: { indy: { name: 'ProofRequest', @@ -54,14 +59,8 @@ describe('Present Proof', () => { }, }, comment: 'V2 propose proof test', - } - - const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.ProposalReceived, }) - aliceProofRecord = await aliceAgent.proofs.proposeProof(proposeOptions) - testLogger.test('Faber waits for presentation from Alice') faberProofRecord = await faberPresentationRecordPromise @@ -96,7 +95,7 @@ describe('Present Proof', () => { id: expect.anything(), threadId: faberProofRecord.threadId, state: ProofState.ProposalReceived, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) }) @@ -150,7 +149,7 @@ describe('Present Proof', () => { id: expect.anything(), threadId: faberProofRecord.threadId, state: ProofState.RequestReceived, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) }) }) diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 45d62417ae..571eba9577 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -15,8 +15,10 @@ import type { import type { AgentModulesInput } from '../src/agent/AgentModules' import type { IndyOfferCredentialFormat } from '../src/modules/credentials/formats/indy/IndyCredentialFormat' import type { RequestProofOptions } from '../src/modules/proofs/ProofsApiOptions' +import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' import type { ProofAttributeInfo, ProofPredicateInfo } from '../src/modules/proofs/formats/indy/models' import type { AutoAcceptProof } from '../src/modules/proofs/models/ProofAutoAcceptType' +import type { V2ProofService } from '../src/modules/proofs/protocol/v2' import type { CredDef, Schema } from 'indy-sdk' import type { Observable } from 'rxjs' @@ -55,7 +57,6 @@ import { OutOfBandState } from '../src/modules/oob/domain/OutOfBandState' import { OutOfBandInvitation } from '../src/modules/oob/messages' import { OutOfBandRecord } from '../src/modules/oob/repository' import { PredicateType } from '../src/modules/proofs/formats/indy/models' -import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' import { ProofState } from '../src/modules/proofs/models/ProofState' import { PresentationPreview, @@ -581,8 +582,11 @@ export async function presentProof({ verifierAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(verifierReplay) holderAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(holderReplay) - const requestProofsOptions: RequestProofOptions = { - protocolVersion: ProofProtocolVersion.V1, + let holderProofRecordPromise = waitForProofRecordSubject(holderReplay, { + state: ProofState.RequestReceived, + }) + + let verifierRecord = await verifierAgent.proofs.requestProof({ connectionId: verifierConnectionId, proofFormats: { indy: { @@ -593,14 +597,9 @@ export async function presentProof({ nonce: '947121108704767252195123', }, }, - } - - let holderProofRecordPromise = waitForProofRecordSubject(holderReplay, { - state: ProofState.RequestReceived, + protocolVersion: 'v2', }) - let verifierRecord = await verifierAgent.proofs.requestProof(requestProofsOptions) - let holderRecord = await holderProofRecordPromise const requestedCredentials = await holderAgent.proofs.autoSelectCredentialsForProofRequest({ diff --git a/packages/core/tests/proofs-sub-protocol.test.ts b/packages/core/tests/proofs-sub-protocol.test.ts index 398454f5c3..06ec56c9aa 100644 --- a/packages/core/tests/proofs-sub-protocol.test.ts +++ b/packages/core/tests/proofs-sub-protocol.test.ts @@ -8,7 +8,6 @@ import { ProofPredicateInfo, PredicateType, } from '../src/modules/proofs/formats/indy/models' -import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' import { ProofState } from '../src/modules/proofs/models/ProofState' import { uuid } from '../src/utils/uuid' @@ -52,7 +51,7 @@ describe('Present Proof Subprotocol', () => { aliceProofRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', parentThreadId, proofFormats: { indy: { @@ -157,7 +156,7 @@ describe('Present Proof Subprotocol', () => { const faberProofRecord = await faberAgent.proofs.requestProof({ connectionId: faberConnection.id, parentThreadId, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', proofFormats: { indy: { name: 'proof-request', @@ -227,7 +226,7 @@ describe('Present Proof Subprotocol', () => { aliceProofRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', parentThreadId, proofFormats: { indy: { @@ -332,7 +331,7 @@ describe('Present Proof Subprotocol', () => { const faberProofRecord = await faberAgent.proofs.requestProof({ connectionId: faberConnection.id, parentThreadId, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', proofFormats: { indy: { name: 'proof-request', diff --git a/packages/core/tests/v1-connectionless-proofs.test.ts b/packages/core/tests/v1-connectionless-proofs.test.ts index 1e2c7eb187..6094a0d65d 100644 --- a/packages/core/tests/v1-connectionless-proofs.test.ts +++ b/packages/core/tests/v1-connectionless-proofs.test.ts @@ -1,8 +1,5 @@ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { ProofStateChangedEvent } from '../src/modules/proofs' -import type { CreateProofRequestOptions } from '../src/modules/proofs/ProofsApiOptions' -import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' -import type { V1ProofService } from '../src/modules/proofs/protocol/v1' import { Subject, ReplaySubject } from 'rxjs' @@ -13,7 +10,6 @@ import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachm import { HandshakeProtocol } from '../src/modules/connections' import { V1CredentialPreview } from '../src/modules/credentials' import { - ProofProtocolVersion, PredicateType, ProofState, ProofAttributeInfo, @@ -79,8 +75,13 @@ describe('Present Proof', () => { }), } - const outOfBandRequestOptions: CreateProofRequestOptions<[IndyProofFormat], [V1ProofService]> = { - protocolVersion: ProofProtocolVersion.V1, + let aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + state: ProofState.RequestReceived, + }) + + // eslint-disable-next-line prefer-const + let { proofRecord: faberProofRecord, message } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v1', proofFormats: { indy: { name: 'test-proof-request', @@ -90,15 +91,8 @@ describe('Present Proof', () => { requestedPredicates: predicates, }, }, - } - - let aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { - state: ProofState.RequestReceived, }) - // eslint-disable-next-line prefer-const - let { proofRecord: faberProofRecord, message } = await faberAgent.proofs.createRequest(outOfBandRequestOptions) - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ recordId: faberProofRecord.id, message, @@ -180,8 +174,17 @@ describe('Present Proof', () => { }), } - const outOfBandRequestOptions: CreateProofRequestOptions<[IndyProofFormat], [V1ProofService]> = { - protocolVersion: ProofProtocolVersion.V1, + const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + state: ProofState.Done, + }) + + const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { + state: ProofState.Done, + }) + + // eslint-disable-next-line prefer-const + let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v1', proofFormats: { indy: { name: 'test-proof-request', @@ -192,19 +195,8 @@ describe('Present Proof', () => { }, }, autoAcceptProof: AutoAcceptProof.ContentApproved, - } - - const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { - state: ProofState.Done, }) - const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { - state: ProofState.Done, - }) - - // eslint-disable-next-line prefer-const - let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest(outOfBandRequestOptions) - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ recordId: faberProofRecord.id, message, @@ -346,8 +338,17 @@ describe('Present Proof', () => { }), } - const outOfBandRequestOptions: CreateProofRequestOptions<[IndyProofFormat], [V1ProofService]> = { - protocolVersion: ProofProtocolVersion.V1, + const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + state: ProofState.Done, + }) + + const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { + state: ProofState.Done, + }) + + // eslint-disable-next-line prefer-const + let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v1', proofFormats: { indy: { name: 'test-proof-request', @@ -358,19 +359,8 @@ describe('Present Proof', () => { }, }, autoAcceptProof: AutoAcceptProof.ContentApproved, - } - - const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { - state: ProofState.Done, }) - const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { - state: ProofState.Done, - }) - - // eslint-disable-next-line prefer-const - let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest(outOfBandRequestOptions) - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ recordId: faberProofRecord.id, message, diff --git a/packages/core/tests/v1-indy-proofs.test.ts b/packages/core/tests/v1-indy-proofs.test.ts index 65514eb0e3..4246ed8278 100644 --- a/packages/core/tests/v1-indy-proofs.test.ts +++ b/packages/core/tests/v1-indy-proofs.test.ts @@ -4,6 +4,8 @@ import type { ProposeProofOptions, RequestProofOptions, } from '../src/modules/proofs/ProofsApiOptions' +import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' +import type { V1ProofService } from '../src/modules/proofs/protocol/v1' import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' import type { CredDefId } from 'indy-sdk' @@ -14,7 +16,6 @@ import { ProofPredicateInfo, PredicateType, } from '../src/modules/proofs/formats/indy/models' -import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' import { ProofState } from '../src/modules/proofs/models/ProofState' import { V1ProposePresentationMessage, @@ -56,9 +57,9 @@ describe('Present Proof', () => { // Alice sends a presentation proposal to Faber testLogger.test('Alice sends a presentation proposal to Faber') - const proposeProofOptions: ProposeProofOptions = { + const proposeProofOptions: ProposeProofOptions<[IndyProofFormat], [V1ProofService]> = { connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', proofFormats: { indy: { name: 'abc', @@ -118,7 +119,7 @@ describe('Present Proof', () => { expect(faberProofRecord).toMatchObject({ threadId: faberProofRecord.threadId, state: ProofState.ProposalReceived, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) const acceptProposalOptions: AcceptProposalOptions = { @@ -219,7 +220,7 @@ describe('Present Proof', () => { expect(faberProofRecord).toMatchObject({ threadId: faberProofRecord.threadId, state: ProofState.PresentationReceived, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) aliceProofRecordPromise = waitForProofRecord(aliceAgent, { @@ -376,8 +377,14 @@ describe('Present Proof', () => { }), } - const requestProofsOptions: RequestProofOptions = { - protocolVersion: ProofProtocolVersion.V1, + let aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + // Faber sends a presentation request to Alice + testLogger.test('Faber sends a presentation request to Alice') + faberProofRecord = await faberAgent.proofs.requestProof({ + protocolVersion: 'v1', connectionId: faberConnection.id, proofFormats: { indy: { @@ -388,16 +395,8 @@ describe('Present Proof', () => { requestedPredicates: predicates, }, }, - } - - let aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - state: ProofState.RequestReceived, }) - // Faber sends a presentation request to Alice - testLogger.test('Faber sends a presentation request to Alice') - faberProofRecord = await faberAgent.proofs.requestProof(requestProofsOptions) - // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') aliceProofRecord = await aliceProofRecordPromise @@ -427,7 +426,7 @@ describe('Present Proof', () => { expect(aliceProofRecord).toMatchObject({ threadId: aliceProofRecord.threadId, state: ProofState.RequestReceived, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) // Alice retrieves the requested credentials and accepts the presentation request @@ -489,7 +488,7 @@ describe('Present Proof', () => { expect(faberProofRecord).toMatchObject({ threadId: faberProofRecord.threadId, state: ProofState.PresentationReceived, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) aliceProofRecordPromise = waitForProofRecord(aliceAgent, { @@ -552,23 +551,21 @@ describe('Present Proof', () => { }), } - const requestProofsOptions: RequestProofOptions = { - protocolVersion: ProofProtocolVersion.V1, - connectionId: faberConnection.id, - proofFormats: { - indy: { - name: 'proof-request', - version: '1.0', - nonce: '1298236324864', - requestedAttributes: attributes, - requestedPredicates: predicates, + await expect( + faberAgent.proofs.requestProof({ + protocolVersion: 'v1', + connectionId: faberConnection.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + nonce: '1298236324864', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, }, - }, - } - - await expect(faberAgent.proofs.requestProof(requestProofsOptions)).rejects.toThrowError( - `The proof request contains duplicate predicates and attributes: age` - ) + }) + ).rejects.toThrowError(`The proof request contains duplicate predicates and attributes: age`) }) test('Faber starts with proof request to Alice but gets Problem Reported', async () => { @@ -605,8 +602,14 @@ describe('Present Proof', () => { }), } - const requestProofsOptions: RequestProofOptions = { - protocolVersion: ProofProtocolVersion.V1, + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + // Faber sends a presentation request to Alice + testLogger.test('Faber sends a presentation request to Alice') + faberProofRecord = await faberAgent.proofs.requestProof({ + protocolVersion: 'v1', connectionId: faberConnection.id, proofFormats: { indy: { @@ -617,16 +620,8 @@ describe('Present Proof', () => { requestedPredicates: predicates, }, }, - } - - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - state: ProofState.RequestReceived, }) - // Faber sends a presentation request to Alice - testLogger.test('Faber sends a presentation request to Alice') - faberProofRecord = await faberAgent.proofs.requestProof(requestProofsOptions) - // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') aliceProofRecord = await aliceProofRecordPromise @@ -656,7 +651,7 @@ describe('Present Proof', () => { expect(aliceProofRecord).toMatchObject({ threadId: aliceProofRecord.threadId, state: ProofState.RequestReceived, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) const faberProofRecordPromise = waitForProofRecord(faberAgent, { @@ -671,7 +666,7 @@ describe('Present Proof', () => { expect(faberProofRecord).toMatchObject({ threadId: aliceProofRecord.threadId, state: ProofState.Abandoned, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', }) }) }) diff --git a/packages/core/tests/v1-proofs-auto-accept.test.ts b/packages/core/tests/v1-proofs-auto-accept.test.ts index 6b74330c4f..37056c0d81 100644 --- a/packages/core/tests/v1-proofs-auto-accept.test.ts +++ b/packages/core/tests/v1-proofs-auto-accept.test.ts @@ -1,5 +1,7 @@ import type { Agent, ConnectionRecord } from '../src' import type { ProposeProofOptions, RequestProofOptions } from '../src/modules/proofs/ProofsApiOptions' +import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' +import type { V1ProofService } from '../src/modules/proofs/protocol/v1/V1ProofService' import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' import { @@ -10,7 +12,6 @@ import { ProofPredicateInfo, PredicateType, } from '../src' -import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' import { setupProofsTest, waitForProofRecord } from './helpers' import testLogger from './logger' @@ -42,9 +43,9 @@ describe('Auto accept present proof', () => { test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `always`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - const proposeProofOptions: ProposeProofOptions = { + const proposeProofOptions: ProposeProofOptions<[IndyProofFormat], [V1ProofService]> = { connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', proofFormats: { indy: { nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', @@ -98,8 +99,16 @@ describe('Auto accept present proof', () => { }), } - const requestProofsOptions: RequestProofOptions = { - protocolVersion: ProofProtocolVersion.V1, + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.Done, + }) + + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.Done, + }) + + await faberAgent.proofs.requestProof({ + protocolVersion: 'v1', connectionId: faberConnection.id, proofFormats: { indy: { @@ -110,17 +119,8 @@ describe('Auto accept present proof', () => { requestedPredicates: predicates, }, }, - } - const faberProofRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.Done, - }) - - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - state: ProofState.Done, }) - await faberAgent.proofs.requestProof(requestProofsOptions) - testLogger.test('Faber waits for presentation from Alice') await faberProofRecordPromise @@ -151,9 +151,9 @@ describe('Auto accept present proof', () => { test('Alice starts with proof proposal to Faber, both with autoacceptproof on `contentApproved`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - const proposal: ProposeProofOptions = { + const proposal: ProposeProofOptions<[IndyProofFormat], [V1ProofService]> = { connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V1, + protocolVersion: 'v1', proofFormats: { indy: { nonce: '1298236324864', @@ -219,8 +219,16 @@ describe('Auto accept present proof', () => { }), } - const requestProofsOptions: RequestProofOptions = { - protocolVersion: ProofProtocolVersion.V1, + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.Done, + }) + + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.Done, + }) + + await faberAgent.proofs.requestProof({ + protocolVersion: 'v1', connectionId: faberConnection.id, proofFormats: { indy: { @@ -231,18 +239,8 @@ describe('Auto accept present proof', () => { requestedPredicates: predicates, }, }, - } - - const faberProofRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.Done, - }) - - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - state: ProofState.Done, }) - await faberAgent.proofs.requestProof(requestProofsOptions) - testLogger.test('Faber waits for presentation from Alice') await faberProofRecordPromise diff --git a/packages/core/tests/v2-connectionless-proofs.test.ts b/packages/core/tests/v2-connectionless-proofs.test.ts index 39cd103bd9..ef9f5f69cc 100644 --- a/packages/core/tests/v2-connectionless-proofs.test.ts +++ b/packages/core/tests/v2-connectionless-proofs.test.ts @@ -21,7 +21,6 @@ import { AutoAcceptProof, ProofEventTypes, } from '../src/modules/proofs' -import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' import { MediatorPickupStrategy } from '../src/modules/routing/MediatorPickupStrategy' import { LinkedAttachment } from '../src/utils/LinkedAttachment' import { uuid } from '../src/utils/uuid' @@ -79,8 +78,13 @@ describe('Present Proof', () => { }), } - const outOfBandRequestOptions: CreateProofRequestOptions<[IndyProofFormat], [V2ProofService]> = { - protocolVersion: ProofProtocolVersion.V2, + let aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + state: ProofState.RequestReceived, + }) + + // eslint-disable-next-line prefer-const + let { proofRecord: faberProofRecord, message } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v2', proofFormats: { indy: { name: 'test-proof-request', @@ -90,15 +94,8 @@ describe('Present Proof', () => { requestedPredicates: predicates, }, }, - } - - let aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { - state: ProofState.RequestReceived, }) - // eslint-disable-next-line prefer-const - let { proofRecord: faberProofRecord, message } = await faberAgent.proofs.createRequest(outOfBandRequestOptions) - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ recordId: faberProofRecord.id, message, @@ -182,8 +179,17 @@ describe('Present Proof', () => { }), } - const outOfBandRequestOptions: CreateProofRequestOptions<[IndyProofFormat], [V2ProofService]> = { - protocolVersion: ProofProtocolVersion.V2, + const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + state: ProofState.Done, + }) + + const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { + state: ProofState.Done, + }) + + // eslint-disable-next-line prefer-const + let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v2', proofFormats: { indy: { name: 'test-proof-request', @@ -194,19 +200,8 @@ describe('Present Proof', () => { }, }, autoAcceptProof: AutoAcceptProof.ContentApproved, - } - - const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { - state: ProofState.Done, }) - const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { - state: ProofState.Done, - }) - - // eslint-disable-next-line prefer-const - let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest(outOfBandRequestOptions) - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ recordId: faberProofRecord.id, message, @@ -348,8 +343,17 @@ describe('Present Proof', () => { }), } - const outOfBandRequestOptions: CreateProofRequestOptions<[IndyProofFormat], [V2ProofService]> = { - protocolVersion: ProofProtocolVersion.V2, + const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + state: ProofState.Done, + }) + + const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { + state: ProofState.Done, + }) + + // eslint-disable-next-line prefer-const + let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v2', proofFormats: { indy: { name: 'test-proof-request', @@ -360,19 +364,8 @@ describe('Present Proof', () => { }, }, autoAcceptProof: AutoAcceptProof.ContentApproved, - } - - const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { - state: ProofState.Done, }) - const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { - state: ProofState.Done, - }) - - // eslint-disable-next-line prefer-const - let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest(outOfBandRequestOptions) - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ recordId: faberProofRecord.id, message, diff --git a/packages/core/tests/v2-indy-proofs.test.ts b/packages/core/tests/v2-indy-proofs.test.ts index 0241964673..0f8a750f16 100644 --- a/packages/core/tests/v2-indy-proofs.test.ts +++ b/packages/core/tests/v2-indy-proofs.test.ts @@ -4,7 +4,9 @@ import type { ProposeProofOptions, RequestProofOptions, } from '../src/modules/proofs/ProofsApiOptions' +import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' +import type { V2ProofService } from '../src/modules/proofs/protocol/v2' import type { CredDefId } from 'indy-sdk' import { AttributeFilter, PredicateType, ProofAttributeInfo, ProofPredicateInfo, ProofState } from '../src' @@ -14,7 +16,6 @@ import { V2_INDY_PRESENTATION_REQUEST, V2_INDY_PRESENTATION, } from '../src/modules/proofs/formats/ProofFormatConstants' -import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' import { V2PresentationMessage, V2ProposalPresentationMessage, @@ -54,9 +55,9 @@ describe('Present Proof', () => { // Alice sends a presentation proposal to Faber testLogger.test('Alice sends a presentation proposal to Faber') - const proposeProofOptions: ProposeProofOptions = { + const proposeProofOptions: ProposeProofOptions<[IndyProofFormat], [V2ProofService]> = { connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', proofFormats: { indy: { name: 'abc', @@ -108,7 +109,7 @@ describe('Present Proof', () => { expect(faberProofRecord).toMatchObject({ threadId: faberProofRecord.threadId, state: ProofState.ProposalReceived, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) const acceptProposalOptions: AcceptProposalOptions = { @@ -210,7 +211,7 @@ describe('Present Proof', () => { expect(faberProofRecord).toMatchObject({ threadId: faberProofRecord.threadId, state: ProofState.PresentationReceived, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) aliceProofRecordPromise = waitForProofRecord(aliceAgent, { @@ -378,8 +379,14 @@ describe('Present Proof', () => { }), } - const requestProofsOptions: RequestProofOptions = { - protocolVersion: ProofProtocolVersion.V2, + let aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + // Faber sends a presentation request to Alice + testLogger.test('Faber sends a presentation request to Alice') + faberProofRecord = await faberAgent.proofs.requestProof({ + protocolVersion: 'v2', connectionId: faberConnection.id, proofFormats: { indy: { @@ -390,16 +397,8 @@ describe('Present Proof', () => { requestedPredicates: predicates, }, }, - } - - let aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - state: ProofState.RequestReceived, }) - // Faber sends a presentation request to Alice - testLogger.test('Faber sends a presentation request to Alice') - faberProofRecord = await faberAgent.proofs.requestProof(requestProofsOptions) - // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') aliceProofRecord = await aliceProofRecordPromise @@ -433,7 +432,7 @@ describe('Present Proof', () => { expect(aliceProofRecord).toMatchObject({ threadId: aliceProofRecord.threadId, state: ProofState.RequestReceived, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) // Alice retrieves the requested credentials and accepts the presentation request @@ -491,7 +490,7 @@ describe('Present Proof', () => { expect(faberProofRecord).toMatchObject({ threadId: faberProofRecord.threadId, state: ProofState.PresentationReceived, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) aliceProofRecordPromise = waitForProofRecord(aliceAgent, { @@ -561,8 +560,14 @@ describe('Present Proof', () => { }), } - const requestProofsOptions: RequestProofOptions = { - protocolVersion: ProofProtocolVersion.V2, + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + // Faber sends a presentation request to Alice + testLogger.test('Faber sends a presentation request to Alice') + faberProofRecord = await faberAgent.proofs.requestProof({ + protocolVersion: 'v2', connectionId: faberConnection.id, proofFormats: { indy: { @@ -573,16 +578,8 @@ describe('Present Proof', () => { requestedPredicates: predicates, }, }, - } - - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - state: ProofState.RequestReceived, }) - // Faber sends a presentation request to Alice - testLogger.test('Faber sends a presentation request to Alice') - faberProofRecord = await faberAgent.proofs.requestProof(requestProofsOptions) - // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') aliceProofRecord = await aliceProofRecordPromise @@ -635,8 +632,14 @@ describe('Present Proof', () => { }), } - const requestProofsOptions: RequestProofOptions = { - protocolVersion: ProofProtocolVersion.V2, + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + // Faber sends a presentation request to Alice + testLogger.test('Faber sends a presentation request to Alice') + faberProofRecord = await faberAgent.proofs.requestProof({ + protocolVersion: 'v2', connectionId: faberConnection.id, proofFormats: { indy: { @@ -647,16 +650,8 @@ describe('Present Proof', () => { requestedPredicates: predicates, }, }, - } - - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - state: ProofState.RequestReceived, }) - // Faber sends a presentation request to Alice - testLogger.test('Faber sends a presentation request to Alice') - faberProofRecord = await faberAgent.proofs.requestProof(requestProofsOptions) - // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') aliceProofRecord = await aliceProofRecordPromise @@ -690,7 +685,7 @@ describe('Present Proof', () => { expect(aliceProofRecord).toMatchObject({ threadId: aliceProofRecord.threadId, state: ProofState.RequestReceived, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) const faberProofRecordPromise = waitForProofRecord(faberAgent, { @@ -705,7 +700,7 @@ describe('Present Proof', () => { expect(faberProofRecord).toMatchObject({ threadId: aliceProofRecord.threadId, state: ProofState.Abandoned, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', }) }) }) diff --git a/packages/core/tests/v2-proofs-auto-accept.test.ts b/packages/core/tests/v2-proofs-auto-accept.test.ts index 9104d20bc0..11c9f6f865 100644 --- a/packages/core/tests/v2-proofs-auto-accept.test.ts +++ b/packages/core/tests/v2-proofs-auto-accept.test.ts @@ -1,6 +1,8 @@ import type { Agent, ConnectionRecord } from '../src' import type { ProposeProofOptions, RequestProofOptions } from '../src/modules/proofs/ProofsApiOptions' +import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' +import type { V2ProofService } from '../src/modules/proofs/protocol/v2' import { AutoAcceptProof, @@ -10,7 +12,6 @@ import { ProofPredicateInfo, PredicateType, } from '../src' -import { ProofProtocolVersion } from '../src/modules/proofs/models/ProofProtocolVersion' import { setupProofsTest, waitForProofRecord } from './helpers' import testLogger from './logger' @@ -42,9 +43,9 @@ describe('Auto accept present proof', () => { test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `always`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - const proposeProofOptions: ProposeProofOptions = { + const proposeProofOptions: ProposeProofOptions<[IndyProofFormat], [V2ProofService]> = { connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', proofFormats: { indy: { nonce: '1298236324864', @@ -98,8 +99,16 @@ describe('Auto accept present proof', () => { }), } - const requestProofsOptions: RequestProofOptions = { - protocolVersion: ProofProtocolVersion.V2, + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.Done, + }) + + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.Done, + }) + + await faberAgent.proofs.requestProof({ + protocolVersion: 'v2', connectionId: faberConnection.id, proofFormats: { indy: { @@ -110,18 +119,8 @@ describe('Auto accept present proof', () => { requestedPredicates: predicates, }, }, - } - - const faberProofRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.Done, - }) - - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - state: ProofState.Done, }) - await faberAgent.proofs.requestProof(requestProofsOptions) - testLogger.test('Faber waits for presentation from Alice') await faberProofRecordPromise // Alice waits till it receives presentation ack @@ -150,9 +149,9 @@ describe('Auto accept present proof', () => { test('Alice starts with proof proposal to Faber, both with autoacceptproof on `contentApproved`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - const proposal: ProposeProofOptions = { + const proposal: ProposeProofOptions<[IndyProofFormat], [V2ProofService]> = { connectionId: aliceConnection.id, - protocolVersion: ProofProtocolVersion.V2, + protocolVersion: 'v2', proofFormats: { indy: { nonce: '1298236324864', @@ -218,8 +217,16 @@ describe('Auto accept present proof', () => { }), } - const requestProofsOptions: RequestProofOptions = { - protocolVersion: ProofProtocolVersion.V2, + const faberProofRecordPromise = waitForProofRecord(faberAgent, { + state: ProofState.Done, + }) + + const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + state: ProofState.Done, + }) + + await faberAgent.proofs.requestProof({ + protocolVersion: 'v2', connectionId: faberConnection.id, proofFormats: { indy: { @@ -230,18 +237,8 @@ describe('Auto accept present proof', () => { requestedPredicates: predicates, }, }, - } - - const faberProofRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.Done, - }) - - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - state: ProofState.Done, }) - await faberAgent.proofs.requestProof(requestProofsOptions) - testLogger.test('Faber waits for presentation from Alice') await faberProofRecordPromise From 28f54e22f4e2a84aa6eee537ef992d550ef3ff99 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Tue, 25 Oct 2022 07:37:52 -0300 Subject: [PATCH 061/125] test: increment default timeout for modules (#1069) Signed-off-by: Ariel Gentile --- DEVREADME.md | 2 +- packages/action-menu/jest.config.ts | 1 + packages/action-menu/tests/setup.ts | 3 +++ packages/question-answer/jest.config.ts | 1 + packages/question-answer/tests/setup.ts | 3 +++ packages/tenants/tests/setup.ts | 2 ++ packages/tenants/tests/tenant-sessions.e2e.test.ts | 4 +--- packages/tenants/tests/tenants.e2e.test.ts | 2 -- 8 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 packages/action-menu/tests/setup.ts create mode 100644 packages/question-answer/tests/setup.ts diff --git a/DEVREADME.md b/DEVREADME.md index c201d8192f..647e619421 100644 --- a/DEVREADME.md +++ b/DEVREADME.md @@ -76,7 +76,7 @@ GENESIS_TXN_PATH=network/genesis/local-genesis.txn TEST_AGENT_PUBLIC_DID_SEED=00 Locally, you might want to run the tests without postgres tests. You can do that by ignoring the tests: ```sh -yarn test --testPathIgnorePatterns ./packages/core/tests/postgres.test.ts -u +yarn test --testPathIgnorePatterns ./packages/core/tests/postgres.e2e.test.ts -u ``` In case you run into trouble running the tests, e.g. complaining about snapshots not being up-to-date, you can try and remove the data stored for the indy-client. On a Unix system with default setup you achieve this by running: diff --git a/packages/action-menu/jest.config.ts b/packages/action-menu/jest.config.ts index ce53584ebf..55c67d70a6 100644 --- a/packages/action-menu/jest.config.ts +++ b/packages/action-menu/jest.config.ts @@ -8,6 +8,7 @@ const config: Config.InitialOptions = { ...base, name: packageJson.name, displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], } export default config diff --git a/packages/action-menu/tests/setup.ts b/packages/action-menu/tests/setup.ts new file mode 100644 index 0000000000..4955aeb601 --- /dev/null +++ b/packages/action-menu/tests/setup.ts @@ -0,0 +1,3 @@ +import 'reflect-metadata' + +jest.setTimeout(20000) diff --git a/packages/question-answer/jest.config.ts b/packages/question-answer/jest.config.ts index ce53584ebf..55c67d70a6 100644 --- a/packages/question-answer/jest.config.ts +++ b/packages/question-answer/jest.config.ts @@ -8,6 +8,7 @@ const config: Config.InitialOptions = { ...base, name: packageJson.name, displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], } export default config diff --git a/packages/question-answer/tests/setup.ts b/packages/question-answer/tests/setup.ts new file mode 100644 index 0000000000..4955aeb601 --- /dev/null +++ b/packages/question-answer/tests/setup.ts @@ -0,0 +1,3 @@ +import 'reflect-metadata' + +jest.setTimeout(20000) diff --git a/packages/tenants/tests/setup.ts b/packages/tenants/tests/setup.ts index 3f425aaf8f..4955aeb601 100644 --- a/packages/tenants/tests/setup.ts +++ b/packages/tenants/tests/setup.ts @@ -1 +1,3 @@ import 'reflect-metadata' + +jest.setTimeout(20000) diff --git a/packages/tenants/tests/tenant-sessions.e2e.test.ts b/packages/tenants/tests/tenant-sessions.e2e.test.ts index 68d6d948cf..c1e0a212b1 100644 --- a/packages/tenants/tests/tenant-sessions.e2e.test.ts +++ b/packages/tenants/tests/tenant-sessions.e2e.test.ts @@ -7,8 +7,6 @@ import testLogger from '../../core/tests/logger' import { TenantsModule } from '@aries-framework/tenants' -jest.setTimeout(2000000) - const agentConfig: InitConfig = { label: 'Tenant Agent 1', walletConfig: { @@ -25,7 +23,7 @@ const agent = new Agent({ config: agentConfig, dependencies: agentDependencies, modules: { - tenants: new TenantsModule(), + tenants: new TenantsModule({ sessionAcquireTimeout: 10000 }), }, }) diff --git a/packages/tenants/tests/tenants.e2e.test.ts b/packages/tenants/tests/tenants.e2e.test.ts index e9a18ef1e5..42b5b599df 100644 --- a/packages/tenants/tests/tenants.e2e.test.ts +++ b/packages/tenants/tests/tenants.e2e.test.ts @@ -9,8 +9,6 @@ import testLogger from '../../core/tests/logger' import { TenantsModule } from '@aries-framework/tenants' -jest.setTimeout(2000000) - const agent1Config: InitConfig = { label: 'Tenant Agent 1', walletConfig: { From d4fd1ae16dc1fd99b043835b97b33f4baece6790 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Tue, 25 Oct 2022 08:37:38 -0300 Subject: [PATCH 062/125] feat(routing): add reconnection parameters to RecipientModuleConfig (#1070) Signed-off-by: Ariel Gentile --- packages/core/src/agent/AgentConfig.ts | 7 +++- packages/core/src/agent/AgentModules.ts | 2 ++ .../core/src/modules/routing/RecipientApi.ts | 2 +- .../modules/routing/RecipientModuleConfig.ts | 33 +++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index 10d56e61da..3b6297341b 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -104,11 +104,16 @@ export class AgentConfig { public get maximumMessagePickup() { return this.initConfig.maximumMessagePickup ?? 10 } - + /** + * @deprecated use baseMediatorReconnectionIntervalMs from the `RecipientModuleConfig` class + */ public get baseMediatorReconnectionIntervalMs() { return this.initConfig.baseMediatorReconnectionIntervalMs ?? 100 } + /** + * @deprecated use maximumMediatorReconnectionIntervalMs from the `RecipientModuleConfig` class + */ public get maximumMediatorReconnectionIntervalMs() { return this.initConfig.maximumMediatorReconnectionIntervalMs ?? Number.POSITIVE_INFINITY } diff --git a/packages/core/src/agent/AgentModules.ts b/packages/core/src/agent/AgentModules.ts index 05055ebf97..6bf2de802e 100644 --- a/packages/core/src/agent/AgentModules.ts +++ b/packages/core/src/agent/AgentModules.ts @@ -113,6 +113,8 @@ function getDefaultAgentModules(agentConfig: AgentConfig) { maximumMessagePickup: agentConfig.maximumMessagePickup, mediatorInvitationUrl: agentConfig.mediatorConnectionsInvite, mediatorPickupStrategy: agentConfig.mediatorPickupStrategy, + baseMediatorReconnectionIntervalMs: agentConfig.baseMediatorReconnectionIntervalMs, + maximumMediatorReconnectionIntervalMs: agentConfig.maximumMediatorReconnectionIntervalMs, mediatorPollingInterval: agentConfig.mediatorPollingInterval, }), basicMessages: () => new BasicMessagesModule(), diff --git a/packages/core/src/modules/routing/RecipientApi.ts b/packages/core/src/modules/routing/RecipientApi.ts index c761826211..dc99a1b46c 100644 --- a/packages/core/src/modules/routing/RecipientApi.ts +++ b/packages/core/src/modules/routing/RecipientApi.ts @@ -151,7 +151,7 @@ export class RecipientApi { } private async openWebSocketAndPickUp(mediator: MediationRecord, pickupStrategy: MediatorPickupStrategy) { - const { baseMediatorReconnectionIntervalMs, maximumMediatorReconnectionIntervalMs } = this.agentContext.config + const { baseMediatorReconnectionIntervalMs, maximumMediatorReconnectionIntervalMs } = this.config let interval = baseMediatorReconnectionIntervalMs const stopConditions$ = merge(this.stop$, this.stopMessagePickup$).pipe() diff --git a/packages/core/src/modules/routing/RecipientModuleConfig.ts b/packages/core/src/modules/routing/RecipientModuleConfig.ts index d8679c4fad..4463289936 100644 --- a/packages/core/src/modules/routing/RecipientModuleConfig.ts +++ b/packages/core/src/modules/routing/RecipientModuleConfig.ts @@ -36,6 +36,29 @@ export interface RecipientModuleConfigOptions { */ maximumMessagePickup?: number + /** + * Initial interval in milliseconds between reconnection attempts when losing connection with the mediator. This value is doubled after + * each retry, resulting in an exponential backoff strategy. + * + * For instance, if maximumMediatorReconnectionIntervalMs is b, the agent will attempt to reconnect after b, 2*b, 4*b, 8*b, 16*b, ... ms. + * + * This is only applicable when pickup protocol v2 or implicit pickup is used. + * + * @default 100 + */ + baseMediatorReconnectionIntervalMs?: number + + /** + * Maximum interval in milliseconds between reconnection attempts when losing connection with the mediator. + * + * For instance, if maximumMediatorReconnectionIntervalMs is set to 1000 and maximumMediatorReconnectionIntervalMs is set to 10000, + * the agent will attempt to reconnect after 1000, 2000, 4000, 8000, 10000, ..., 10000 ms. + * + * This is only applicable when pickup protocol v2 or implicit pickup is used. + * @default Number.POSITIVE_INFINITY + */ + maximumMediatorReconnectionIntervalMs?: number + /** * Invitation url for connection to a mediator. If provided, a connection to the mediator will be made, and the mediator will be set as default. * This is meant as the simplest form of connecting to a mediator, if more control is desired the api should be used. @@ -67,6 +90,16 @@ export class RecipientModuleConfig { return this.options.maximumMessagePickup ?? 10 } + /** See {@link RecipientModuleConfigOptions.baseMediatorReconnectionIntervalMs} */ + public get baseMediatorReconnectionIntervalMs() { + return this.options.baseMediatorReconnectionIntervalMs ?? 100 + } + + /** See {@link RecipientModuleConfigOptions.maximumMediatorReconnectionIntervalMs} */ + public get maximumMediatorReconnectionIntervalMs() { + return this.options.maximumMediatorReconnectionIntervalMs ?? Number.POSITIVE_INFINITY + } + /** See {@link RecipientModuleConfigOptions.mediatorInvitationUrl} */ public get mediatorInvitationUrl() { return this.options.mediatorInvitationUrl From f777b3ddc287ecc73ffec2d98e14d2d84a9b8039 Mon Sep 17 00:00:00 2001 From: NB-MikeRichardson <93971245+NB-MikeRichardson@users.noreply.github.com> Date: Tue, 25 Oct 2022 17:29:13 +0300 Subject: [PATCH 063/125] refactor(proofs)!: rename ProofRecord to ProofExchangeRecord (#1071) Signed-off-by: Mike Richardson --- demo/src/Alice.ts | 4 +- demo/src/AliceInquirer.ts | 4 +- demo/src/Listener.ts | 4 +- .../core/src/modules/proofs/ProofEvents.ts | 4 +- .../proofs/ProofResponseCoordinator.ts | 8 +- .../core/src/modules/proofs/ProofService.ts | 52 +- packages/core/src/modules/proofs/ProofsApi.ts | 63 +- .../proofs/__tests__/V1ProofService.test.ts | 28 +- .../proofs/__tests__/V2ProofService.test.ts | 28 +- .../indy/IndyProofFormatsServiceOptions.ts | 10 +- .../models/ProofFormatServiceOptions.ts | 10 +- .../proofs/models/ProofServiceOptions.ts | 16 +- .../proofs/protocol/v1/V1ProofService.ts | 66 +- .../__tests__/indy-proof-presentation.test.ts | 76 +- .../v1/__tests__/indy-proof-proposal.test.ts | 19 +- .../v1/__tests__/indy-proof-request.test.ts | 42 +- .../v1/handlers/V1PresentationHandler.ts | 4 +- .../handlers/V1ProposePresentationHandler.ts | 4 +- .../handlers/V1RequestPresentationHandler.ts | 4 +- .../proofs/protocol/v2/V2ProofService.ts | 61 +- .../__tests__/indy-proof-presentation.test.ts | 82 +- .../v2/__tests__/indy-proof-proposal.test.ts | 11 +- .../v2/__tests__/indy-proof-request.test.ts | 42 +- .../v2/handlers/V2PresentationHandler.ts | 4 +- .../handlers/V2ProposePresentationHandler.ts | 4 +- .../handlers/V2RequestPresentationHandler.ts | 4 +- ...{ProofRecord.ts => ProofExchangeRecord.ts} | 10 +- .../proofs/repository/ProofRepository.ts | 12 +- .../src/modules/proofs/repository/index.ts | 2 +- .../__fixtures__/alice-4-proofs-0.2.json | 8 +- .../__tests__/__snapshots__/0.2.test.ts.snap | 840 ++++-------------- .../updates/0.2-0.3/__tests__/proof.test.ts | 18 +- .../migration/updates/0.2-0.3/index.ts | 4 +- .../migration/updates/0.2-0.3/proof.ts | 18 +- packages/core/tests/helpers.ts | 21 +- .../core/tests/proofs-sub-protocol.test.ts | 94 +- .../tests/v1-connectionless-proofs.test.ts | 54 +- packages/core/tests/v1-indy-proofs.test.ts | 180 ++-- .../core/tests/v1-proofs-auto-accept.test.ts | 71 +- .../tests/v2-connectionless-proofs.test.ts | 57 +- packages/core/tests/v2-indy-proofs.test.ts | 198 +++-- .../core/tests/v2-proofs-auto-accept.test.ts | 71 +- 42 files changed, 919 insertions(+), 1393 deletions(-) rename packages/core/src/modules/proofs/repository/{ProofRecord.ts => ProofExchangeRecord.ts} (91%) diff --git a/demo/src/Alice.ts b/demo/src/Alice.ts index 67b100f8c6..252c04c632 100644 --- a/demo/src/Alice.ts +++ b/demo/src/Alice.ts @@ -1,4 +1,4 @@ -import type { ConnectionRecord, CredentialExchangeRecord, ProofRecord } from '@aries-framework/core' +import type { ConnectionRecord, CredentialExchangeRecord, ProofExchangeRecord } from '@aries-framework/core' import { BaseAgent } from './BaseAgent' import { greenText, Output, redText } from './OutputClass' @@ -51,7 +51,7 @@ export class Alice extends BaseAgent { }) } - public async acceptProofRequest(proofRecord: ProofRecord) { + public async acceptProofRequest(proofRecord: ProofExchangeRecord) { const requestedCredentials = await this.agent.proofs.autoSelectCredentialsForProofRequest({ proofRecordId: proofRecord.id, config: { diff --git a/demo/src/AliceInquirer.ts b/demo/src/AliceInquirer.ts index 457d33b528..d11e7f6021 100644 --- a/demo/src/AliceInquirer.ts +++ b/demo/src/AliceInquirer.ts @@ -1,4 +1,4 @@ -import type { CredentialExchangeRecord, ProofRecord } from '@aries-framework/core' +import type { CredentialExchangeRecord, ProofExchangeRecord } from '@aries-framework/core' import { clear } from 'console' import { textSync } from 'figlet' @@ -78,7 +78,7 @@ export class AliceInquirer extends BaseInquirer { } } - public async acceptProofRequest(proofRecord: ProofRecord) { + public async acceptProofRequest(proofRecord: ProofExchangeRecord) { const confirm = await inquirer.prompt([this.inquireConfirmation(Title.ProofRequestTitle)]) if (confirm.options === ConfirmOptions.No) { await this.alice.agent.proofs.declineRequest(proofRecord.id) diff --git a/demo/src/Listener.ts b/demo/src/Listener.ts index 97f98c741a..1da8970aa5 100644 --- a/demo/src/Listener.ts +++ b/demo/src/Listener.ts @@ -7,7 +7,7 @@ import type { BasicMessageStateChangedEvent, CredentialExchangeRecord, CredentialStateChangedEvent, - ProofRecord, + ProofExchangeRecord, ProofStateChangedEvent, } from '@aries-framework/core' import type BottomBar from 'inquirer/lib/ui/bottom-bar' @@ -78,7 +78,7 @@ export class Listener { }) } - private async newProofRequestPrompt(proofRecord: ProofRecord, aliceInquirer: AliceInquirer) { + private async newProofRequestPrompt(proofRecord: ProofExchangeRecord, aliceInquirer: AliceInquirer) { this.turnListenerOn() await aliceInquirer.acceptProofRequest(proofRecord) this.turnListenerOff() diff --git a/packages/core/src/modules/proofs/ProofEvents.ts b/packages/core/src/modules/proofs/ProofEvents.ts index ba1c7b047b..20394c56a9 100644 --- a/packages/core/src/modules/proofs/ProofEvents.ts +++ b/packages/core/src/modules/proofs/ProofEvents.ts @@ -1,6 +1,6 @@ import type { BaseEvent } from '../../agent/Events' import type { ProofState } from './models/ProofState' -import type { ProofRecord } from './repository' +import type { ProofExchangeRecord } from './repository' export enum ProofEventTypes { ProofStateChanged = 'ProofStateChanged', @@ -9,7 +9,7 @@ export enum ProofEventTypes { export interface ProofStateChangedEvent extends BaseEvent { type: typeof ProofEventTypes.ProofStateChanged payload: { - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord previousState: ProofState | null } } diff --git a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts index 24bf56dda5..8996649c4f 100644 --- a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts +++ b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts @@ -1,5 +1,5 @@ import type { AgentContext } from '../../agent/context/AgentContext' -import type { ProofRecord } from './repository' +import type { ProofExchangeRecord } from './repository' import { injectable } from '../../plugins' @@ -34,7 +34,7 @@ export class ProofResponseCoordinator { /** * Checks whether it should automatically respond to a proposal */ - public shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofRecord) { + public shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, agentContext.config.autoAcceptProofs @@ -54,7 +54,7 @@ export class ProofResponseCoordinator { /** * Checks whether it should automatically respond to a request */ - public shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofRecord) { + public shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, agentContext.config.autoAcceptProofs @@ -74,7 +74,7 @@ export class ProofResponseCoordinator { /** * Checks whether it should automatically respond to a presentation of proof */ - public shouldAutoRespondToPresentation(agentContext: AgentContext, proofRecord: ProofRecord) { + public shouldAutoRespondToPresentation(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, agentContext.config.autoAcceptProofs diff --git a/packages/core/src/modules/proofs/ProofService.ts b/packages/core/src/modules/proofs/ProofService.ts index ab84926214..9fcd016b19 100644 --- a/packages/core/src/modules/proofs/ProofService.ts +++ b/packages/core/src/modules/proofs/ProofService.ts @@ -29,7 +29,7 @@ import type { ProofRequestFromProposalOptions, } from './models/ProofServiceOptions' import type { ProofState } from './models/ProofState' -import type { ProofRecord, ProofRepository } from './repository' +import type { ProofExchangeRecord, ProofRepository } from './repository' import { JsonTransformer } from '../../utils/JsonTransformer' @@ -64,7 +64,11 @@ export abstract class ProofService { return await this.wallet.generateNonce() } - public emitStateChangedEvent(agentContext: AgentContext, proofRecord: ProofRecord, previousState: ProofState | null) { + public emitStateChangedEvent( + agentContext: AgentContext, + proofRecord: ProofExchangeRecord, + previousState: ProofState | null + ) { const clonedProof = JsonTransformer.clone(proofRecord) this.eventEmitter.emit(agentContext, { @@ -84,7 +88,7 @@ export abstract class ProofService { * @param newState The state to update to * */ - public async updateState(agentContext: AgentContext, proofRecord: ProofRecord, newState: ProofState) { + public async updateState(agentContext: AgentContext, proofRecord: ProofExchangeRecord, newState: ProofState) { const previousState = proofRecord.state proofRecord.state = newState await this.proofRepository.update(agentContext, proofRecord) @@ -92,7 +96,7 @@ export abstract class ProofService { this.emitStateChangedEvent(agentContext, proofRecord, previousState) } - public update(agentContext: AgentContext, proofRecord: ProofRecord) { + public update(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { return this.proofRepository.update(agentContext, proofRecord) } @@ -107,7 +111,7 @@ export abstract class ProofService { abstract createProposal( agentContext: AgentContext, options: CreateProposalOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> /** * Create a proposal message in response to a received proof request message @@ -122,7 +126,7 @@ export abstract class ProofService { abstract createProposalAsResponse( agentContext: AgentContext, options: CreateProposalAsResponseOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> /** * Process a received proposal message (does not accept yet) @@ -142,48 +146,54 @@ export abstract class ProofService { * 4. Loop through all format services to process proposal message * 5. Save & return record */ - abstract processProposal(messageContext: InboundMessageContext): Promise + abstract processProposal(messageContext: InboundMessageContext): Promise abstract createRequest( agentContext: AgentContext, options: CreateRequestOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> abstract createRequestAsResponse( agentContext: AgentContext, options: CreateRequestAsResponseOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - abstract processRequest(messageContext: InboundMessageContext): Promise + abstract processRequest(messageContext: InboundMessageContext): Promise abstract createPresentation( agentContext: AgentContext, options: CreatePresentationOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - abstract processPresentation(messageContext: InboundMessageContext): Promise + abstract processPresentation(messageContext: InboundMessageContext): Promise abstract createAck( agentContext: AgentContext, options: CreateAckOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - abstract processAck(messageContext: InboundMessageContext): Promise + abstract processAck(messageContext: InboundMessageContext): Promise abstract createProblemReport( agentContext: AgentContext, options: CreateProblemReportOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - abstract processProblemReport(messageContext: InboundMessageContext): Promise + abstract processProblemReport(messageContext: InboundMessageContext): Promise - public abstract shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofRecord): Promise + public abstract shouldAutoRespondToProposal( + agentContext: AgentContext, + proofRecord: ProofExchangeRecord + ): Promise - public abstract shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofRecord): Promise + public abstract shouldAutoRespondToRequest( + agentContext: AgentContext, + proofRecord: ProofExchangeRecord + ): Promise public abstract shouldAutoRespondToPresentation( agentContext: AgentContext, - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord ): Promise public abstract registerHandlers( @@ -204,7 +214,7 @@ export abstract class ProofService { public async saveOrUpdatePresentationMessage( agentContext: AgentContext, options: { - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord message: AgentMessage role: DidCommMessageRole } @@ -218,7 +228,7 @@ export abstract class ProofService { public async delete( agentContext: AgentContext, - proofRecord: ProofRecord, + proofRecord: ProofExchangeRecord, options?: DeleteProofOptions ): Promise { await this.proofRepository.delete(agentContext, proofRecord) diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index 27b72d6fcf..ce7acf47a8 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -29,7 +29,7 @@ import type { DeleteProofOptions, GetFormatDataReturn, } from './models/ProofServiceOptions' -import type { ProofRecord } from './repository/ProofRecord' +import type { ProofExchangeRecord } from './repository/ProofExchangeRecord' import { inject, injectable } from 'tsyringe' @@ -55,21 +55,21 @@ import { ProofRepository } from './repository/ProofRepository' export interface ProofsApi[]> { // Proposal methods - proposeProof(options: ProposeProofOptions): Promise - acceptProposal(options: AcceptProposalOptions): Promise + proposeProof(options: ProposeProofOptions): Promise + acceptProposal(options: AcceptProposalOptions): Promise // Request methods - requestProof(options: RequestProofOptions): Promise - acceptRequest(options: AcceptPresentationOptions): Promise - declineRequest(proofRecordId: string): Promise + requestProof(options: RequestProofOptions): Promise + acceptRequest(options: AcceptPresentationOptions): Promise + declineRequest(proofRecordId: string): Promise // Present - acceptPresentation(proofRecordId: string): Promise + acceptPresentation(proofRecordId: string): Promise // out of band createRequest(options: CreateProofRequestOptions): Promise<{ message: AgentMessage - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord }> // Auto Select @@ -82,15 +82,15 @@ export interface ProofsApi> - sendProblemReport(proofRecordId: string, message: string): Promise + sendProblemReport(proofRecordId: string, message: string): Promise // Record Methods - getAll(): Promise - findAllByQuery(query: Query): Promise - getById(proofRecordId: string): Promise - findById(proofRecordId: string): Promise + getAll(): Promise + findAllByQuery(query: Query): Promise + getById(proofRecordId: string): Promise + findById(proofRecordId: string): Promise deleteById(proofId: string, options?: DeleteProofOptions): Promise - update(proofRecord: ProofRecord): Promise + update(proofRecord: ProofExchangeRecord): Promise getFormatData(proofRecordId: string): Promise> // DidComm Message Records @@ -164,7 +164,7 @@ export class ProofsApi< * to include in the message * @returns Proof record associated with the sent proposal message */ - public async proposeProof(options: ProposeProofOptions): Promise { + public async proposeProof(options: ProposeProofOptions): Promise { const service = this.getService(options.protocolVersion) const { connectionId } = options @@ -198,7 +198,7 @@ export class ProofsApi< * @param options multiple properties like proof record id, additional configuration for creating the request * @returns Proof record associated with the presentation request */ - public async acceptProposal(options: AcceptProposalOptions): Promise { + public async acceptProposal(options: AcceptProposalOptions): Promise { const { proofRecordId } = options const proofRecord = await this.getById(proofRecordId) @@ -247,7 +247,7 @@ export class ProofsApi< * @param options multiple properties like connection id, protocol version, proof Formats to build the proof request * @returns Proof record associated with the sent request message */ - public async requestProof(options: RequestProofOptions): Promise { + public async requestProof(options: RequestProofOptions): Promise { const service = this.getService(options.protocolVersion) const connection = await this.connectionService.getById(this.agentContext, options.connectionId) @@ -278,7 +278,7 @@ export class ProofsApi< * specifying which credentials to use for the proof * @returns Proof record associated with the sent presentation message */ - public async acceptRequest(options: AcceptPresentationOptions): Promise { + public async acceptRequest(options: AcceptPresentationOptions): Promise { const { proofRecordId, proofFormats, comment } = options const record = await this.getById(proofRecordId) @@ -353,7 +353,7 @@ export class ProofsApi< */ public async createRequest(options: CreateProofRequestOptions): Promise<{ message: AgentMessage - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord }> { const service = this.getService(options.protocolVersion) @@ -367,7 +367,7 @@ export class ProofsApi< return await service.createRequest(this.agentContext, createProofRequest) } - public async declineRequest(proofRecordId: string): Promise { + public async declineRequest(proofRecordId: string): Promise { const proofRecord = await this.getById(proofRecordId) const service = this.getService(proofRecord.protocolVersion) @@ -382,11 +382,11 @@ export class ProofsApi< * Accept a presentation as prover (by sending a presentation acknowledgement message) to the connection * associated with the proof record. * - * @param proofRecordId The id of the proof record for which to accept the presentation + * @param proofRecordId The id of the proof exchange record for which to accept the presentation * @returns Proof record associated with the sent presentation acknowledgement message * */ - public async acceptPresentation(proofRecordId: string): Promise { + public async acceptPresentation(proofRecordId: string): Promise { const record = await this.getById(proofRecordId) const service = this.getService(record.protocolVersion) @@ -481,7 +481,7 @@ export class ProofsApi< * @param message message to send * @returns proof record associated with the proof problem report message */ - public async sendProblemReport(proofRecordId: string, message: string): Promise { + public async sendProblemReport(proofRecordId: string, message: string): Promise { const record = await this.getById(proofRecordId) const service = this.getService(record.protocolVersion) if (!record.connectionId) { @@ -515,7 +515,7 @@ export class ProofsApi< * * @returns List containing all proof records */ - public async getAll(): Promise { + public async getAll(): Promise { return this.proofRepository.getAll(this.agentContext) } @@ -524,7 +524,7 @@ export class ProofsApi< * * @returns List containing all proof records matching specified params */ - public findAllByQuery(query: Query): Promise { + public findAllByQuery(query: Query): Promise { return this.proofRepository.findByQuery(this.agentContext, query) } @@ -536,7 +536,7 @@ export class ProofsApi< * @return The proof record * */ - public async getById(proofRecordId: string): Promise { + public async getById(proofRecordId: string): Promise { return await this.proofRepository.getById(this.agentContext, proofRecordId) } @@ -547,7 +547,7 @@ export class ProofsApi< * @return The proof record or null if not found * */ - public async findById(proofRecordId: string): Promise { + public async findById(proofRecordId: string): Promise { return await this.proofRepository.findById(this.agentContext, proofRecordId) } @@ -571,7 +571,7 @@ export class ProofsApi< * @throws {RecordDuplicateError} If multiple records are found * @returns The proof record */ - public async getByThreadAndConnectionId(threadId: string, connectionId?: string): Promise { + public async getByThreadAndConnectionId(threadId: string, connectionId?: string): Promise { return this.proofRepository.getByThreadAndConnectionId(this.agentContext, threadId, connectionId) } @@ -582,7 +582,10 @@ export class ProofsApi< * @param parentThreadId The parent thread id * @returns List containing all proof records matching the given query */ - public async getByParentThreadAndConnectionId(parentThreadId: string, connectionId?: string): Promise { + public async getByParentThreadAndConnectionId( + parentThreadId: string, + connectionId?: string + ): Promise { return this.proofRepository.getByParentThreadAndConnectionId(this.agentContext, parentThreadId, connectionId) } @@ -591,7 +594,7 @@ export class ProofsApi< * * @param proofRecord the proof record */ - public async update(proofRecord: ProofRecord): Promise { + public async update(proofRecord: ProofExchangeRecord): Promise { await this.proofRepository.update(this.agentContext, proofRecord) } diff --git a/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts b/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts index 7c73c959d5..180a1b8a34 100644 --- a/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts +++ b/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts @@ -2,7 +2,7 @@ import type { AgentContext } from '../../../agent' import type { Wallet } from '../../../wallet/Wallet' import type { CredentialRepository } from '../../credentials/repository' import type { ProofStateChangedEvent } from '../ProofEvents' -import type { CustomProofTags } from './../repository/ProofRecord' +import type { CustomProofTags } from './../repository/ProofExchangeRecord' import { Subject } from 'rxjs' @@ -22,7 +22,7 @@ import { ProofState } from '../models/ProofState' import { V1ProofService } from '../protocol/v1' import { INDY_PROOF_REQUEST_ATTACHMENT_ID, V1RequestPresentationMessage } from '../protocol/v1/messages' import { V1PresentationProblemReportMessage } from '../protocol/v1/messages/V1PresentationProblemReportMessage' -import { ProofRecord } from '../repository/ProofRecord' +import { ProofExchangeRecord } from '../repository/ProofExchangeRecord' import { ProofRepository } from '../repository/ProofRepository' import { credDef } from './fixtures' @@ -62,7 +62,7 @@ const requestAttachment = new Attachment({ // A record is deserialized to JSON when it's stored into the storage. We want to simulate this behaviour for `offer` // object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. -const mockProofRecord = ({ +const mockProofExchangeRecord = ({ state, threadId, connectionId, @@ -81,7 +81,7 @@ const mockProofRecord = ({ requestPresentationAttachments: [requestAttachment], }) - const proofRecord = new ProofRecord({ + const proofRecord = new ProofExchangeRecord({ protocolVersion: 'v1', id, state: state || ProofState.RequestSent, @@ -156,11 +156,11 @@ describe('V1ProofService', () => { const repositorySaveSpy = jest.spyOn(proofRepository, 'save') // when - const returnedProofRecord = await proofService.processRequest(messageContext) + const returnedProofExchangeRecord = await proofService.processRequest(messageContext) // then - const expectedProofRecord = { - type: ProofRecord.name, + const expectedProofExchangeRecord = { + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), state: ProofState.RequestReceived, @@ -168,9 +168,9 @@ describe('V1ProofService', () => { connectionId: connection.id, } expect(repositorySaveSpy).toHaveBeenCalledTimes(1) - const [[, createdProofRecord]] = repositorySaveSpy.mock.calls - expect(createdProofRecord).toMatchObject(expectedProofRecord) - expect(returnedProofRecord).toMatchObject(expectedProofRecord) + const [[, createdProofExchangeRecord]] = repositorySaveSpy.mock.calls + expect(createdProofExchangeRecord).toMatchObject(expectedProofExchangeRecord) + expect(returnedProofExchangeRecord).toMatchObject(expectedProofExchangeRecord) }) test(`emits stateChange event with ${ProofState.RequestReceived}`, async () => { @@ -198,10 +198,10 @@ describe('V1ProofService', () => { describe('createProblemReport', () => { const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' - let proof: ProofRecord + let proof: ProofExchangeRecord beforeEach(() => { - proof = mockProofRecord({ + proof = mockProofExchangeRecord({ state: ProofState.RequestReceived, threadId, connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', @@ -233,11 +233,11 @@ describe('V1ProofService', () => { }) describe('processProblemReport', () => { - let proof: ProofRecord + let proof: ProofExchangeRecord let messageContext: InboundMessageContext beforeEach(() => { - proof = mockProofRecord({ + proof = mockProofExchangeRecord({ state: ProofState.RequestReceived, }) diff --git a/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts b/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts index e5781dce63..255c97a92b 100644 --- a/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts +++ b/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts @@ -1,7 +1,7 @@ import type { AgentContext } from '../../../agent' import type { Wallet } from '../../../wallet/Wallet' import type { ProofStateChangedEvent } from '../ProofEvents' -import type { CustomProofTags } from '../repository/ProofRecord' +import type { CustomProofTags } from '../repository/ProofExchangeRecord' import { Subject } from 'rxjs' @@ -19,7 +19,7 @@ import { IndyProofFormatService } from '../formats/indy/IndyProofFormatService' import { ProofState } from '../models/ProofState' import { V2ProofService } from '../protocol/v2/V2ProofService' import { V2PresentationProblemReportMessage, V2RequestPresentationMessage } from '../protocol/v2/messages' -import { ProofRecord } from '../repository/ProofRecord' +import { ProofExchangeRecord } from '../repository/ProofExchangeRecord' import { ProofRepository } from '../repository/ProofRepository' import { credDef } from './fixtures' @@ -56,7 +56,7 @@ const requestAttachment = new Attachment({ // A record is deserialized to JSON when it's stored into the storage. We want to simulate this behaviour for `offer` // object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. -const mockProofRecord = ({ +const mockProofExchangeRecord = ({ state, threadId, connectionId, @@ -83,7 +83,7 @@ const mockProofRecord = ({ comment: 'some comment', }) - const proofRecord = new ProofRecord({ + const proofRecord = new ProofExchangeRecord({ protocolVersion: 'v2', id, state: state || ProofState.RequestSent, @@ -153,11 +153,11 @@ describe('V2ProofService', () => { const repositorySaveSpy = jest.spyOn(proofRepository, 'save') // when - const returnedProofRecord = await proofService.processRequest(messageContext) + const returnedProofExchangeRecord = await proofService.processRequest(messageContext) // then - const expectedProofRecord = { - type: ProofRecord.name, + const expectedProofExchangeRecord = { + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), state: ProofState.RequestReceived, @@ -165,9 +165,9 @@ describe('V2ProofService', () => { connectionId: connection.id, } expect(repositorySaveSpy).toHaveBeenCalledTimes(1) - const [[, createdProofRecord]] = repositorySaveSpy.mock.calls - expect(createdProofRecord).toMatchObject(expectedProofRecord) - expect(returnedProofRecord).toMatchObject(expectedProofRecord) + const [[, createdProofExchangeRecord]] = repositorySaveSpy.mock.calls + expect(createdProofExchangeRecord).toMatchObject(expectedProofExchangeRecord) + expect(returnedProofExchangeRecord).toMatchObject(expectedProofExchangeRecord) }) test(`emits stateChange event with ${ProofState.RequestReceived}`, async () => { @@ -195,10 +195,10 @@ describe('V2ProofService', () => { describe('createProblemReport', () => { const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' - let proof: ProofRecord + let proof: ProofExchangeRecord beforeEach(() => { - proof = mockProofRecord({ + proof = mockProofExchangeRecord({ state: ProofState.RequestReceived, threadId, connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', @@ -230,10 +230,10 @@ describe('V2ProofService', () => { }) describe('processProblemReport', () => { - let proof: ProofRecord + let proof: ProofExchangeRecord let messageContext: InboundMessageContext beforeEach(() => { - proof = mockProofRecord({ + proof = mockProofExchangeRecord({ state: ProofState.RequestReceived, }) diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts index 47ddd8c489..8500d4646f 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts @@ -1,12 +1,8 @@ import type { Attachment } from '../../../../decorators/attachment/Attachment' import type { IndyRevocationInterval } from '../../../credentials' import type { GetRequestedCredentialsConfig } from '../../models/GetRequestedCredentialsConfig' -import type { - PresentationPreview, - PresentationPreviewAttribute, - PresentationPreviewPredicate, -} from '../../protocol/v1/models/V1PresentationPreview' -import type { ProofRecord } from '../../repository/ProofRecord' +import type { PresentationPreview } from '../../protocol/v1/models/V1PresentationPreview' +import type { ProofExchangeRecord } from '../../repository/ProofExchangeRecord' import type { ProofAttributeInfo } from '.././indy/models/ProofAttributeInfo' import type { ProofPredicateInfo } from '.././indy/models/ProofPredicateInfo' import type { IndyRequestedCredentialsFormat } from './IndyProofFormat' @@ -37,7 +33,7 @@ export interface GetRequestedCredentialsFormat { } export interface IndyProofRequestFromProposalOptions { - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord name?: string version?: string nonce?: string diff --git a/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts b/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts index 8212d2b6dd..1a377f4af2 100644 --- a/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts +++ b/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts @@ -1,6 +1,6 @@ import type { Attachment } from '../../../../decorators/attachment/Attachment' import type { ProposeProofFormats } from '../../models/SharedOptions' -import type { ProofRecord } from '../../repository' +import type { ProofExchangeRecord } from '../../repository' import type { ProofFormat, ProofFormatPayload } from '../ProofFormat' import type { ProofRequestOptions } from '../indy/models/ProofRequest' import type { ProofAttachmentFormat } from './ProofAttachmentFormat' @@ -22,7 +22,7 @@ export interface CreateProposalOptions { export interface ProcessProposalOptions { proposal: ProofAttachmentFormat - record?: ProofRecord + record?: ProofExchangeRecord } export interface CreateRequestOptions { @@ -32,7 +32,7 @@ export interface CreateRequestOptions { export interface ProcessRequestOptions { requestAttachment: ProofAttachmentFormat - record?: ProofRecord + record?: ProofExchangeRecord } export interface FormatCreatePresentationOptions { @@ -42,7 +42,7 @@ export interface FormatCreatePresentationOptions { } export interface ProcessPresentationOptions { - record: ProofRecord + record: ProofExchangeRecord formatAttachments: { request: ProofAttachmentFormat[] presentation: ProofAttachmentFormat[] @@ -55,7 +55,7 @@ export interface VerifyProofOptions { } export interface CreateProblemReportOptions { - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord description: string } diff --git a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts index 47f650faf9..3c7e7e47af 100644 --- a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts +++ b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts @@ -1,6 +1,6 @@ import type { ConnectionRecord } from '../../connections' import type { ProofFormat, ProofFormatPayload } from '../formats/ProofFormat' -import type { ProofRecord } from '../repository' +import type { ProofExchangeRecord } from '../repository' import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' import type { AutoAcceptProof } from './ProofAutoAcceptType' @@ -25,13 +25,13 @@ export interface CreateProposalOptions extends BaseOp } export interface CreateProposalAsResponseOptions extends BaseOptions { - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord proofFormats: ProofFormatPayload } export interface CreateRequestAsResponseOptions extends BaseOptions { id?: string - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord proofFormats: ProofFormatPayload } @@ -43,7 +43,7 @@ export interface CreateRequestOptions extends BaseOpt export interface CreateProofRequestFromProposalOptions extends BaseOptions { id?: string - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord } export interface FormatRetrievedCredentialOptions { @@ -55,22 +55,22 @@ export interface FormatRequestedCredentialReturn { } export interface CreatePresentationOptions extends BaseOptions { - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord proofFormats: ProofFormatPayload // lastPresentation?: boolean } export interface CreateAckOptions { - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord } export interface GetRequestedCredentialsForProofRequestOptions { - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord config?: GetRequestedCredentialsConfig } export interface ProofRequestFromProposalOptions { - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord proofFormats: ProofFormatPayload } diff --git a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts index dd3479fcf2..f850df9aaf 100644 --- a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts @@ -28,7 +28,6 @@ import type { GetRequestedCredentialsForProofRequestOptions, ProofRequestFromProposalOptions, } from '../../models/ProofServiceOptions' -import type { ProofRequestFormats } from '../../models/SharedOptions' import { validateOrReject } from 'class-validator' import { inject, Lifecycle, scoped } from 'tsyringe' @@ -56,7 +55,7 @@ import { IndyProofUtils } from '../../formats/indy/IndyProofUtils' import { ProofRequest } from '../../formats/indy/models/ProofRequest' import { RequestedCredentials } from '../../formats/indy/models/RequestedCredentials' import { ProofState } from '../../models/ProofState' -import { ProofRecord } from '../../repository/ProofRecord' +import { ProofExchangeRecord } from '../../repository/ProofExchangeRecord' import { ProofRepository } from '../../repository/ProofRepository' import { V1PresentationProblemReportError } from './errors' @@ -118,7 +117,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { public async createProposal( agentContext: AgentContext, options: CreateProposalOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { const { connectionRecord, proofFormats } = options // Assert @@ -141,7 +140,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { }) // Create record - const proofRecord = new ProofRecord({ + const proofRecord = new ProofExchangeRecord({ connectionId: connectionRecord.id, threadId: proposalMessage.threadId, parentThreadId: proposalMessage.thread?.parentThreadId, @@ -165,7 +164,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { public async createProposalAsResponse( agentContext: AgentContext, options: CreateProposalAsResponseOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { const { proofRecord, proofFormats, comment } = options // Assert @@ -202,8 +201,8 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { public async processProposal( messageContext: InboundMessageContext - ): Promise { - let proofRecord: ProofRecord + ): Promise { + let proofRecord: ProofExchangeRecord const { message: proposalMessage, connection } = messageContext this.logger.debug(`Processing presentation proposal with id ${proposalMessage.id}`) @@ -238,7 +237,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { await this.updateState(messageContext.agentContext, proofRecord, ProofState.ProposalReceived) } catch { // No proof record exists with thread id - proofRecord = new ProofRecord({ + proofRecord = new ProofExchangeRecord({ connectionId: connection?.id, threadId: proposalMessage.threadId, parentThreadId: proposalMessage.thread?.parentThreadId, @@ -267,7 +266,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { public async createRequestAsResponse( agentContext: AgentContext, options: CreateRequestAsResponseOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { const { proofRecord, comment, proofFormats } = options if (!proofFormats.indy) { throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') @@ -305,7 +304,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { public async createRequest( agentContext: AgentContext, options: CreateRequestOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { this.logger.debug(`Creating proof request`) // Assert @@ -330,7 +329,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { }) // Create record - const proofRecord = new ProofRecord({ + const proofRecord = new ProofExchangeRecord({ connectionId: options.connectionRecord?.id, threadId: requestPresentationMessage.threadId, parentThreadId: requestPresentationMessage.thread?.parentThreadId, @@ -353,8 +352,8 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { public async processRequest( messageContext: InboundMessageContext - ): Promise { - let proofRecord: ProofRecord + ): Promise { + let proofRecord: ProofExchangeRecord const { message: proofRequestMessage, connection } = messageContext this.logger.debug(`Processing presentation request with id ${proofRequestMessage.id}`) @@ -418,7 +417,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { await this.updateState(messageContext.agentContext, proofRecord, ProofState.RequestReceived) } catch { // No proof record exists with thread id - proofRecord = new ProofRecord({ + proofRecord = new ProofExchangeRecord({ connectionId: connection?.id, threadId: proofRequestMessage.threadId, parentThreadId: proofRequestMessage.thread?.parentThreadId, @@ -446,7 +445,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { public async createPresentation( agentContext: AgentContext, options: CreatePresentationOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { const { proofRecord, proofFormats } = options this.logger.debug(`Creating presentation for proof record with id ${proofRecord.id}`) @@ -516,7 +515,9 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { return { message: presentationMessage, proofRecord } } - public async processPresentation(messageContext: InboundMessageContext): Promise { + public async processPresentation( + messageContext: InboundMessageContext + ): Promise { const { message: presentationMessage, connection } = messageContext this.logger.debug(`Processing presentation with id ${presentationMessage.id}`) @@ -573,7 +574,9 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { return proofRecord } - public async processAck(messageContext: InboundMessageContext): Promise { + public async processAck( + messageContext: InboundMessageContext + ): Promise { const { message: presentationAckMessage, connection } = messageContext this.logger.debug(`Processing presentation ack with id ${presentationAckMessage.id}`) @@ -610,7 +613,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { public async createProblemReport( agentContext: AgentContext, options: CreateProblemReportOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { const msg = new V1PresentationProblemReportMessage({ description: { code: PresentationProblemReportReason.Abandoned, @@ -631,7 +634,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { public async processProblemReport( messageContext: InboundMessageContext - ): Promise { + ): Promise { const { message: presentationProblemReportMessage } = messageContext const connection = messageContext.assertReadyConnection() @@ -755,7 +758,10 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { return attachments.length ? attachments : undefined } - public async shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofRecord): Promise { + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + proofRecord: ProofExchangeRecord + ): Promise { const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: proofRecord.id, messageClass: V1ProposePresentationMessage, @@ -827,7 +833,10 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { return true } - public async shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofRecord): Promise { + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + proofRecord: ProofExchangeRecord + ): Promise { const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: proofRecord.id, messageClass: V1ProposePresentationMessage, @@ -843,7 +852,9 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { }) if (!request) { - throw new AriesFrameworkError(`Expected to find a request message for ProofRecord with id ${proofRecord.id}`) + throw new AriesFrameworkError( + `Expected to find a request message for ProofExchangeRecord with id ${proofRecord.id}` + ) } const proofRequest = request.indyProofRequest @@ -899,7 +910,10 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { return true } - public async shouldAutoRespondToPresentation(agentContext: AgentContext, proofRecord: ProofRecord): Promise { + public async shouldAutoRespondToPresentation( + agentContext: AgentContext, + proofRecord: ProofExchangeRecord + ): Promise { this.logger.debug(`Should auto respond to presentation for proof record id: ${proofRecord.id}`) return true } @@ -1059,7 +1073,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { * * @returns List containing all proof records */ - public async getAll(agentContext: AgentContext): Promise { + public async getAll(agentContext: AgentContext): Promise { return this.proofRepository.getAll(agentContext) } @@ -1076,14 +1090,14 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { agentContext: AgentContext, threadId: string, connectionId?: string - ): Promise { + ): Promise { return this.proofRepository.getSingleByQuery(agentContext, { threadId, connectionId }) } public async createAck( gentContext: AgentContext, options: CreateAckOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { const { proofRecord } = options this.logger.debug(`Creating presentation ack for proof record with id ${proofRecord.id}`) diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts index a7bba2c894..975a6aed43 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts @@ -1,12 +1,12 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { ProofRecord } from '../../../repository/ProofRecord' import type { PresentationPreview } from '../models/V1PresentationPreview' -import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' +import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage/didcomm' import { ProofState } from '../../../models/ProofState' +import { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import { V1PresentationMessage, V1ProposePresentationMessage, V1RequestPresentationMessage } from '../messages' describe('Present Proof', () => { @@ -14,8 +14,8 @@ describe('Present Proof', () => { let aliceAgent: Agent let aliceConnection: ConnectionRecord let presentationPreview: PresentationPreview - let faberProofRecord: ProofRecord - let aliceProofRecord: ProofRecord + let faberProofExchangeRecord: ProofExchangeRecord + let aliceProofExchangeRecord: ProofExchangeRecord let didCommMessageRepository: DidCommMessageRepository beforeAll(async () => { @@ -37,11 +37,11 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const faberProofRecordPromise = waitForProofRecord(faberAgent, { + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { state: ProofState.ProposalReceived, }) - aliceProofRecord = await aliceAgent.proofs.proposeProof({ + aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v1', proofFormats: { @@ -58,12 +58,12 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V1ProposePresentationMessage, }) @@ -95,9 +95,9 @@ describe('Present Proof', () => { ], }, }) - expect(faberProofRecord).toMatchObject({ + expect(faberProofExchangeRecord).toMatchObject({ id: expect.anything(), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, state: ProofState.ProposalReceived, protocolVersion: 'v1', }) @@ -105,23 +105,23 @@ describe('Present Proof', () => { test(`Faber accepts the Proposal send by Alice`, async () => { // Accept Proposal - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - threadId: faberProofRecord.threadId, + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: faberProofExchangeRecord.threadId, state: ProofState.RequestReceived, }) testLogger.test('Faber accepts presentation proposal from Alice') - faberProofRecord = await faberAgent.proofs.acceptProposal({ - proofRecordId: faberProofRecord.id, + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal({ + proofRecordId: faberProofExchangeRecord.id, }) testLogger.test('Alice waits for proof request from Faber') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V1RequestPresentationMessage, }) @@ -138,12 +138,12 @@ describe('Present Proof', () => { }, ], thread: { - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, }, }) - expect(aliceProofRecord).toMatchObject({ + expect(aliceProofExchangeRecord).toMatchObject({ id: expect.anything(), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, state: ProofState.RequestReceived, protocolVersion: 'v1', }) @@ -151,28 +151,28 @@ describe('Present Proof', () => { test(`Alice accepts presentation request from Faber`, async () => { const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, config: { filterByPresentationPreview: true, }, }) - const faberProofRecordPromise = waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.PresentationReceived, }) await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, proofFormats: { indy: requestedCredentials.proofFormats.indy }, }) // Faber waits for the presentation from Alice testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V1PresentationMessage, }) @@ -202,42 +202,42 @@ describe('Present Proof', () => { }, }) - expect(faberProofRecord.id).not.toBeNull() - expect(faberProofRecord).toMatchObject({ - threadId: faberProofRecord.threadId, + expect(faberProofExchangeRecord.id).not.toBeNull() + expect(faberProofExchangeRecord).toMatchObject({ + threadId: faberProofExchangeRecord.threadId, state: ProofState.PresentationReceived, protocolVersion: 'v1', }) }) test(`Faber accepts the presentation provided by Alice`, async () => { - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) // Faber accepts the presentation provided by Alice - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - expect(faberProofRecord).toMatchObject({ - // type: ProofRecord.name, + expect(faberProofExchangeRecord).toMatchObject({ + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - threadId: aliceProofRecord.threadId, + threadId: aliceProofExchangeRecord.threadId, connectionId: expect.any(String), isVerified: true, state: ProofState.PresentationReceived, }) - expect(aliceProofRecord).toMatchObject({ - // type: ProofRecord.name, + expect(aliceProofExchangeRecord).toMatchObject({ + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, connectionId: expect.any(String), state: ProofState.Done, }) diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts index e0cf1c0751..e2b2df04ff 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts @@ -1,12 +1,9 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { ProposeProofOptions } from '../../../ProofsApiOptions' -import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' -import type { ProofRecord } from '../../../repository/ProofRecord' -import type { V1ProofService } from '../V1ProofService' +import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { PresentationPreview } from '../models/V1PresentationPreview' -import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' +import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' import { ProofState } from '../../../models/ProofState' @@ -17,7 +14,7 @@ describe('Present Proof', () => { let aliceAgent: Agent let aliceConnection: ConnectionRecord let presentationPreview: PresentationPreview - let faberProofRecord: ProofRecord + let faberProofExchangeRecord: ProofExchangeRecord let didCommMessageRepository: DidCommMessageRepository beforeAll(async () => { @@ -39,7 +36,7 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const faberProofRecordPromise = waitForProofRecord(faberAgent, { + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { state: ProofState.ProposalReceived, }) @@ -59,12 +56,12 @@ describe('Present Proof', () => { }) testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V1ProposePresentationMessage, }) @@ -97,9 +94,9 @@ describe('Present Proof', () => { }, }) - expect(faberProofRecord).toMatchObject({ + expect(faberProofExchangeRecord).toMatchObject({ id: expect.anything(), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, state: ProofState.ProposalReceived, protocolVersion: 'v1', }) diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts index 7d371a4b9c..b60a16ad4a 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts @@ -1,12 +1,10 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProposalOptions, ProposeProofOptions } from '../../../ProofsApiOptions' -import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' -import type { ProofRecord } from '../../../repository/ProofRecord' -import type { V1ProofService } from '../V1ProofService' +import type { AcceptProposalOptions } from '../../../ProofsApiOptions' +import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { PresentationPreview } from '../models/V1PresentationPreview' -import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' +import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage/didcomm' import { ProofState } from '../../../models/ProofState' @@ -17,8 +15,8 @@ describe('Present Proof', () => { let aliceAgent: Agent let aliceConnection: ConnectionRecord let presentationPreview: PresentationPreview - let faberProofRecord: ProofRecord - let aliceProofRecord: ProofRecord + let faberProofExchangeRecord: ProofExchangeRecord + let aliceProofExchangeRecord: ProofExchangeRecord let didCommMessageRepository: DidCommMessageRepository beforeAll(async () => { @@ -40,11 +38,11 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const faberProofRecordPromise = waitForProofRecord(faberAgent, { + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { state: ProofState.ProposalReceived, }) - aliceProofRecord = await aliceAgent.proofs.proposeProof({ + aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v1', proofFormats: { @@ -60,12 +58,12 @@ describe('Present Proof', () => { }) testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V1ProposePresentationMessage, }) @@ -97,9 +95,9 @@ describe('Present Proof', () => { ], }, }) - expect(faberProofRecord).toMatchObject({ + expect(faberProofExchangeRecord).toMatchObject({ id: expect.anything(), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, state: ProofState.ProposalReceived, protocolVersion: 'v1', }) @@ -108,24 +106,24 @@ describe('Present Proof', () => { test(`Faber accepts the Proposal send by Alice and Creates Proof Request`, async () => { // Accept Proposal const acceptProposalOptions: AcceptProposalOptions = { - proofRecordId: faberProofRecord.id, + proofRecordId: faberProofExchangeRecord.id, } - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - threadId: faberProofRecord.threadId, + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: faberProofExchangeRecord.threadId, state: ProofState.RequestReceived, }) testLogger.test('Faber accepts presentation proposal from Alice') - faberProofRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) testLogger.test('Alice waits for proof request from Faber') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V1RequestPresentationMessage, }) @@ -142,12 +140,12 @@ describe('Present Proof', () => { }, ], thread: { - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, }, }) - expect(aliceProofRecord).toMatchObject({ + expect(aliceProofExchangeRecord).toMatchObject({ id: expect.anything(), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, state: ProofState.RequestReceived, protocolVersion: 'v1', }) diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts index 9c97a5991d..c55e644b44 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts @@ -2,7 +2,7 @@ import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' import type { DidCommMessageRepository } from '../../../../../storage' import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' -import type { ProofRecord } from '../../../repository' +import type { ProofExchangeRecord } from '../../../repository' import type { V1ProofService } from '../V1ProofService' import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' @@ -35,7 +35,7 @@ export class V1PresentationHandler implements Handler { } } - private async createAck(record: ProofRecord, messageContext: HandlerInboundMessage) { + private async createAck(record: ProofExchangeRecord, messageContext: HandlerInboundMessage) { this.agentConfig.logger.info( `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptProofs}` ) diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts index 19c88f33ab..31146937c2 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts @@ -5,7 +5,7 @@ import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' import type { IndyProofRequestFromProposalOptions } from '../../../formats/indy/IndyProofFormatsServiceOptions' import type { ProofRequestFromProposalOptions } from '../../../models/ProofServiceOptions' -import type { ProofRecord } from '../../../repository/ProofRecord' +import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { V1ProofService } from '../V1ProofService' import { createOutboundMessage } from '../../../../../agent/helpers' @@ -39,7 +39,7 @@ export class V1ProposePresentationHandler implements Handler { } private async createRequest( - proofRecord: ProofRecord, + proofRecord: ProofExchangeRecord, messageContext: HandlerInboundMessage ) { this.agentConfig.logger.info( diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts index e38f3ba0cb..5f7e72e7e8 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts @@ -8,7 +8,7 @@ import type { FormatRequestedCredentialReturn, FormatRetrievedCredentialOptions, } from '../../../models/ProofServiceOptions' -import type { ProofRecord } from '../../../repository/ProofRecord' +import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { V1ProofService } from '../V1ProofService' import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' @@ -50,7 +50,7 @@ export class V1RequestPresentationHandler implements Handler { } private async createPresentation( - record: ProofRecord, + record: ProofExchangeRecord, messageContext: HandlerInboundMessage ) { const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts index 25c5e171ca..50fb3efd7a 100644 --- a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts @@ -43,7 +43,7 @@ import { V2_INDY_PRESENTATION_REQUEST } from '../../formats/ProofFormatConstants import { IndyProofFormatService } from '../../formats/indy/IndyProofFormatService' import { IndyProofUtils } from '../../formats/indy/IndyProofUtils' import { ProofState } from '../../models/ProofState' -import { PresentationRecordType, ProofRecord, ProofRepository } from '../../repository' +import { PresentationRecordType, ProofExchangeRecord, ProofRepository } from '../../repository' import { V2PresentationProblemReportError } from './errors' import { V2PresentationAckHandler } from './handlers/V2PresentationAckHandler' @@ -86,7 +86,7 @@ export class V2ProofService extends P public async createProposal( agentContext: AgentContext, options: CreateProposalOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { const formats = [] for (const key of Object.keys(options.proofFormats)) { const service = this.formatServiceMap[key] @@ -108,7 +108,7 @@ export class V2ProofService extends P parentThreadId: options.parentThreadId, }) - const proofRecord = new ProofRecord({ + const proofRecord = new ProofExchangeRecord({ connectionId: options.connectionRecord.id, threadId: proposalMessage.threadId, parentThreadId: proposalMessage.thread?.parentThreadId, @@ -135,7 +135,7 @@ export class V2ProofService extends P public async createProposalAsResponse( agentContext: AgentContext, options: CreateProposalAsResponseOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { options.proofRecord.assertState(ProofState.RequestReceived) const formats = [] @@ -168,9 +168,9 @@ export class V2ProofService extends P public async processProposal( messageContext: InboundMessageContext - ): Promise { + ): Promise { const { message: proposalMessage, connection: connectionRecord } = messageContext - let proofRecord: ProofRecord + let proofRecord: ProofExchangeRecord const proposalAttachments = proposalMessage.getAttachmentFormats() @@ -208,7 +208,7 @@ export class V2ProofService extends P await this.updateState(messageContext.agentContext, proofRecord, ProofState.ProposalReceived) } catch { // No proof record exists with thread id - proofRecord = new ProofRecord({ + proofRecord = new ProofExchangeRecord({ connectionId: connectionRecord?.id, threadId: proposalMessage.threadId, parentThreadId: proposalMessage.thread?.parentThreadId, @@ -236,7 +236,7 @@ export class V2ProofService extends P public async createRequest( agentContext: AgentContext, options: CreateRequestOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { // create attachment formats const formats = [] for (const key of Object.keys(options.proofFormats)) { @@ -258,7 +258,7 @@ export class V2ProofService extends P }) // create & store proof record - const proofRecord = new ProofRecord({ + const proofRecord = new ProofExchangeRecord({ connectionId: options.connectionRecord?.id, threadId: requestMessage.threadId, parentThreadId: requestMessage.thread?.parentThreadId, @@ -286,7 +286,7 @@ export class V2ProofService extends P public async createRequestAsResponse( agentContext: AgentContext, options: CreateRequestAsResponseOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { options.proofRecord.assertState(ProofState.ProposalReceived) const proposal = await this.didCommMessageRepository.getAgentMessage(agentContext, { @@ -334,7 +334,7 @@ export class V2ProofService extends P public async processRequest( messageContext: InboundMessageContext - ): Promise { + ): Promise { const { message: proofRequestMessage, connection: connectionRecord } = messageContext const requestAttachments = proofRequestMessage.getAttachmentFormats() @@ -356,7 +356,7 @@ export class V2ProofService extends P this.logger.debug(`Received proof request`, proofRequestMessage) - let proofRecord: ProofRecord + let proofRecord: ProofExchangeRecord try { proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { @@ -391,7 +391,7 @@ export class V2ProofService extends P await this.updateState(messageContext.agentContext, proofRecord, ProofState.RequestReceived) } catch { // No proof record exists with thread id - proofRecord = new ProofRecord({ + proofRecord = new ProofExchangeRecord({ connectionId: connectionRecord?.id, threadId: proofRequestMessage.threadId, parentThreadId: proofRequestMessage.thread?.parentThreadId, @@ -419,7 +419,7 @@ export class V2ProofService extends P public async createPresentation( agentContext: AgentContext, options: CreatePresentationOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { // assert state options.proofRecord.assertState(ProofState.RequestReceived) @@ -458,7 +458,9 @@ export class V2ProofService extends P return { message: presentationMessage, proofRecord: options.proofRecord } } - public async processPresentation(messageContext: InboundMessageContext): Promise { + public async processPresentation( + messageContext: InboundMessageContext + ): Promise { const { message: presentationMessage, connection: connectionRecord } = messageContext this.logger.debug(`Processing presentation with id ${presentationMessage.id}`) @@ -533,7 +535,7 @@ export class V2ProofService extends P public async createAck( agentContext: AgentContext, options: CreateAckOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { // assert we've received the final presentation const presentation = await this.didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: options.proofRecord.id, @@ -559,7 +561,9 @@ export class V2ProofService extends P } } - public async processAck(messageContext: InboundMessageContext): Promise { + public async processAck( + messageContext: InboundMessageContext + ): Promise { const { message: ackMessage, connection: connectionRecord } = messageContext const proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { @@ -593,7 +597,7 @@ export class V2ProofService extends P public async createProblemReport( agentContext: AgentContext, options: CreateProblemReportOptions - ): Promise<{ proofRecord: ProofRecord; message: AgentMessage }> { + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { const msg = new V2PresentationProblemReportMessage({ description: { code: PresentationProblemReportReason.Abandoned, @@ -614,7 +618,7 @@ export class V2ProofService extends P public async processProblemReport( messageContext: InboundMessageContext - ): Promise { + ): Promise { const { message: presentationProblemReportMessage } = messageContext const connectionRecord = messageContext.assertReadyConnection() @@ -671,7 +675,10 @@ export class V2ProofService extends P return retVal } - public async shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofRecord): Promise { + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + proofRecord: ProofExchangeRecord + ): Promise { const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: proofRecord.id, messageClass: V2ProposalPresentationMessage, @@ -700,7 +707,10 @@ export class V2ProofService extends P return true } - public async shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofRecord): Promise { + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + proofRecord: ProofExchangeRecord + ): Promise { const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: proofRecord.id, messageClass: V2ProposalPresentationMessage, @@ -716,7 +726,9 @@ export class V2ProofService extends P }) if (!request) { - throw new AriesFrameworkError(`Expected to find a request message for ProofRecord with id ${proofRecord.id}`) + throw new AriesFrameworkError( + `Expected to find a request message for ProofExchangeRecord with id ${proofRecord.id}` + ) } const proposalAttachments = proposal.getAttachmentFormats() @@ -731,7 +743,10 @@ export class V2ProofService extends P return equalityResults.every((x) => x === true) } - public async shouldAutoRespondToPresentation(agentContext: AgentContext, proofRecord: ProofRecord): Promise { + public async shouldAutoRespondToPresentation( + agentContext: AgentContext, + proofRecord: ProofExchangeRecord + ): Promise { const request = await this.didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: proofRecord.id, messageClass: V2RequestPresentationMessage, diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts index 04c06388b5..e36fe8d4ad 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts @@ -1,12 +1,9 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { ProposeProofOptions, AcceptProposalOptions } from '../../../ProofsApiOptions' -import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' -import type { ProofRecord } from '../../../repository/ProofRecord' +import type { AcceptProposalOptions } from '../../../ProofsApiOptions' import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' -import type { V2ProofService } from '../V2ProofService' -import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' +import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' import { @@ -15,6 +12,7 @@ import { V2_INDY_PRESENTATION, } from '../../../formats/ProofFormatConstants' import { ProofState } from '../../../models/ProofState' +import { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import { V2PresentationMessage, V2RequestPresentationMessage } from '../messages' import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' @@ -23,8 +21,8 @@ describe('Present Proof', () => { let aliceAgent: Agent let aliceConnection: ConnectionRecord let presentationPreview: PresentationPreview - let faberProofRecord: ProofRecord - let aliceProofRecord: ProofRecord + let faberProofExchangeRecord: ProofExchangeRecord + let aliceProofExchangeRecord: ProofExchangeRecord let didCommMessageRepository: DidCommMessageRepository beforeAll(async () => { @@ -46,11 +44,11 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { + const faberPresentationRecordPromise = waitForProofExchangeRecord(faberAgent, { state: ProofState.ProposalReceived, }) - aliceProofRecord = await aliceAgent.proofs.proposeProof({ + aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v2', proofFormats: { @@ -66,12 +64,12 @@ describe('Present Proof', () => { }) testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await faberPresentationRecordPromise + faberProofExchangeRecord = await faberPresentationRecordPromise didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V2ProposalPresentationMessage, }) @@ -95,9 +93,9 @@ describe('Present Proof', () => { id: expect.any(String), comment: 'V2 propose proof test', }) - expect(faberProofRecord).toMatchObject({ + expect(faberProofExchangeRecord).toMatchObject({ id: expect.anything(), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, state: ProofState.ProposalReceived, protocolVersion: 'v2', }) @@ -106,24 +104,24 @@ describe('Present Proof', () => { test(`Faber accepts the Proposal send by Alice`, async () => { // Accept Proposal const acceptProposalOptions: AcceptProposalOptions = { - proofRecordId: faberProofRecord.id, + proofRecordId: faberProofExchangeRecord.id, } - const alicePresentationRecordPromise = waitForProofRecord(aliceAgent, { - threadId: faberProofRecord.threadId, + const alicePresentationRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: faberProofExchangeRecord.threadId, state: ProofState.RequestReceived, }) testLogger.test('Faber accepts presentation proposal from Alice') - faberProofRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) testLogger.test('Alice waits for proof request from Faber') - aliceProofRecord = await alicePresentationRecordPromise + aliceProofExchangeRecord = await alicePresentationRecordPromise didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V2RequestPresentationMessage, }) @@ -146,12 +144,12 @@ describe('Present Proof', () => { ], id: expect.any(String), thread: { - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, }, }) - expect(aliceProofRecord).toMatchObject({ + expect(aliceProofExchangeRecord).toMatchObject({ id: expect.anything(), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, state: ProofState.RequestReceived, protocolVersion: 'v2', }) @@ -162,28 +160,28 @@ describe('Present Proof', () => { testLogger.test('Alice accepts presentation request from Faber') const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, config: { filterByPresentationPreview: true, }, }) - const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, + const faberPresentationRecordPromise = waitForProofExchangeRecord(faberAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.PresentationReceived, }) await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, proofFormats: { indy: requestedCredentials.proofFormats.indy }, }) // Faber waits for the presentation from Alice testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await faberPresentationRecordPromise + faberProofExchangeRecord = await faberPresentationRecordPromise const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V2PresentationMessage, }) @@ -206,46 +204,46 @@ describe('Present Proof', () => { ], id: expect.any(String), thread: { - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, }, }) - expect(faberProofRecord.id).not.toBeNull() - expect(faberProofRecord).toMatchObject({ - threadId: faberProofRecord.threadId, + expect(faberProofExchangeRecord.id).not.toBeNull() + expect(faberProofExchangeRecord).toMatchObject({ + threadId: faberProofExchangeRecord.threadId, state: ProofState.PresentationReceived, protocolVersion: 'v2', }) }) test(`Faber accepts the presentation provided by Alice`, async () => { - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - expect(faberProofRecord).toMatchObject({ - // type: ProofRecord.name, + expect(faberProofExchangeRecord).toMatchObject({ + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - threadId: aliceProofRecord.threadId, + threadId: aliceProofExchangeRecord.threadId, connectionId: expect.any(String), isVerified: true, state: ProofState.PresentationReceived, }) - expect(aliceProofRecord).toMatchObject({ - // type: ProofRecord.name, + expect(aliceProofExchangeRecord).toMatchObject({ + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, connectionId: expect.any(String), state: ProofState.Done, }) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts index 8b3d37be7c..0aed8af01c 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts @@ -1,12 +1,9 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { ProposeProofOptions } from '../../../ProofsApiOptions' -import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' -import type { ProofRecord } from '../../../repository' +import type { ProofExchangeRecord } from '../../../repository' import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' -import type { V2ProofService } from '../V2ProofService' -import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' +import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' import { V2_INDY_PRESENTATION_PROPOSAL } from '../../../formats/ProofFormatConstants' @@ -18,7 +15,7 @@ describe('Present Proof', () => { let aliceAgent: Agent let aliceConnection: ConnectionRecord let presentationPreview: PresentationPreview - let faberPresentationRecord: ProofRecord + let faberPresentationRecord: ProofExchangeRecord let didCommMessageRepository: DidCommMessageRepository beforeAll(async () => { @@ -40,7 +37,7 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { + const faberPresentationRecordPromise = waitForProofExchangeRecord(faberAgent, { state: ProofState.ProposalReceived, }) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts index c2d7ea262e..5fae009dc1 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts @@ -1,12 +1,10 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { ProposeProofOptions, AcceptProposalOptions } from '../../../ProofsApiOptions' -import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' -import type { ProofRecord } from '../../../repository/ProofRecord' +import type { AcceptProposalOptions } from '../../../ProofsApiOptions' +import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' -import type { V2ProofService } from '../V2ProofService' -import { setupProofsTest, waitForProofRecord } from '../../../../../../tests/helpers' +import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' import { V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST } from '../../../formats/ProofFormatConstants' @@ -19,8 +17,8 @@ describe('Present Proof', () => { let aliceAgent: Agent let aliceConnection: ConnectionRecord let presentationPreview: PresentationPreview - let faberProofRecord: ProofRecord - let aliceProofRecord: ProofRecord + let faberProofExchangeRecord: ProofExchangeRecord + let aliceProofExchangeRecord: ProofExchangeRecord let didCommMessageRepository: DidCommMessageRepository beforeAll(async () => { @@ -42,11 +40,11 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const faberPresentationRecordPromise = waitForProofRecord(faberAgent, { + const faberPresentationRecordPromise = waitForProofExchangeRecord(faberAgent, { state: ProofState.ProposalReceived, }) - aliceProofRecord = await aliceAgent.proofs.proposeProof({ + aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v2', proofFormats: { @@ -62,12 +60,12 @@ describe('Present Proof', () => { }) testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await faberPresentationRecordPromise + faberProofExchangeRecord = await faberPresentationRecordPromise didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V2ProposalPresentationMessage, }) @@ -91,9 +89,9 @@ describe('Present Proof', () => { id: expect.any(String), comment: 'V2 propose proof test', }) - expect(faberProofRecord).toMatchObject({ + expect(faberProofExchangeRecord).toMatchObject({ id: expect.anything(), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, state: ProofState.ProposalReceived, protocolVersion: 'v2', }) @@ -102,24 +100,24 @@ describe('Present Proof', () => { test(`Faber accepts the Proposal send by Alice`, async () => { // Accept Proposal const acceptProposalOptions: AcceptProposalOptions = { - proofRecordId: faberProofRecord.id, + proofRecordId: faberProofExchangeRecord.id, } - const alicePresentationRecordPromise = waitForProofRecord(aliceAgent, { - threadId: faberProofRecord.threadId, + const alicePresentationRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: faberProofExchangeRecord.threadId, state: ProofState.RequestReceived, }) testLogger.test('Faber accepts presentation proposal from Alice') - faberProofRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) testLogger.test('Alice waits for proof request from Faber') - aliceProofRecord = await alicePresentationRecordPromise + aliceProofExchangeRecord = await alicePresentationRecordPromise didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V2RequestPresentationMessage, }) @@ -142,12 +140,12 @@ describe('Present Proof', () => { ], id: expect.any(String), thread: { - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, }, }) - expect(aliceProofRecord).toMatchObject({ + expect(aliceProofExchangeRecord).toMatchObject({ id: expect.anything(), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, state: ProofState.RequestReceived, protocolVersion: 'v2', }) diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts index d75a4a855c..c9723d8555 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts @@ -2,7 +2,7 @@ import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' import type { DidCommMessageRepository } from '../../../../../storage' import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' -import type { ProofRecord } from '../../../repository' +import type { ProofExchangeRecord } from '../../../repository' import type { V2ProofService } from '../V2ProofService' import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' @@ -35,7 +35,7 @@ export class V2PresentationHandler implements Handler { } } - private async createAck(record: ProofRecord, messageContext: HandlerInboundMessage) { + private async createAck(record: ProofExchangeRecord, messageContext: HandlerInboundMessage) { this.agentConfig.logger.info( `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptProofs}` ) diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts index 232065fd58..e3ddafe295 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts @@ -8,7 +8,7 @@ import type { CreateRequestAsResponseOptions, ProofRequestFromProposalOptions, } from '../../../models/ProofServiceOptions' -import type { ProofRecord } from '../../../repository/ProofRecord' +import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { V2ProofService } from '../V2ProofService' import { createOutboundMessage } from '../../../../../agent/helpers' @@ -43,7 +43,7 @@ export class V2ProposePresentationHandler ) { this.agentConfig.logger.info( diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts index 4f3c5ee123..39df9b627f 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts @@ -8,7 +8,7 @@ import type { FormatRequestedCredentialReturn, FormatRetrievedCredentialOptions, } from '../../../models/ProofServiceOptions' -import type { ProofRecord } from '../../../repository/ProofRecord' +import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { V2ProofService } from '../V2ProofService' import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' @@ -49,7 +49,7 @@ export class V2RequestPresentationHandler ) { const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { diff --git a/packages/core/src/modules/proofs/repository/ProofRecord.ts b/packages/core/src/modules/proofs/repository/ProofExchangeRecord.ts similarity index 91% rename from packages/core/src/modules/proofs/repository/ProofRecord.ts rename to packages/core/src/modules/proofs/repository/ProofExchangeRecord.ts index 54cb12538e..f145703dff 100644 --- a/packages/core/src/modules/proofs/repository/ProofRecord.ts +++ b/packages/core/src/modules/proofs/repository/ProofExchangeRecord.ts @@ -6,7 +6,7 @@ import { AriesFrameworkError } from '../../../error' import { BaseRecord } from '../../../storage/BaseRecord' import { uuid } from '../../../utils/uuid' -export interface ProofRecordProps { +export interface ProofExchangeRecordProps { id?: string createdAt?: Date protocolVersion: string @@ -28,9 +28,7 @@ export type DefaultProofTags = { state: ProofState } -// T-TODO: rename to proof exchange record - -export class ProofRecord extends BaseRecord { +export class ProofExchangeRecord extends BaseRecord { public connectionId?: string public threadId!: string public protocolVersion!: string @@ -41,9 +39,9 @@ export class ProofRecord extends BaseRecord { public errorMessage?: string public static readonly type = 'ProofRecord' - public readonly type = ProofRecord.type + public readonly type = ProofExchangeRecord.type - public constructor(props: ProofRecordProps) { + public constructor(props: ProofExchangeRecordProps) { super() if (props) { diff --git a/packages/core/src/modules/proofs/repository/ProofRepository.ts b/packages/core/src/modules/proofs/repository/ProofRepository.ts index cb7a5033ef..48c6453d76 100644 --- a/packages/core/src/modules/proofs/repository/ProofRepository.ts +++ b/packages/core/src/modules/proofs/repository/ProofRepository.ts @@ -6,15 +6,15 @@ import { inject, injectable } from '../../../plugins' import { Repository } from '../../../storage/Repository' import { StorageService } from '../../../storage/StorageService' -import { ProofRecord } from './ProofRecord' +import { ProofExchangeRecord } from './ProofExchangeRecord' @injectable() -export class ProofRepository extends Repository { +export class ProofRepository extends Repository { public constructor( - @inject(InjectionSymbols.StorageService) storageService: StorageService, + @inject(InjectionSymbols.StorageService) storageService: StorageService, eventEmitter: EventEmitter ) { - super(ProofRecord, storageService, eventEmitter) + super(ProofExchangeRecord, storageService, eventEmitter) } /** @@ -30,7 +30,7 @@ export class ProofRepository extends Repository { agentContext: AgentContext, threadId: string, connectionId?: string - ): Promise { + ): Promise { return this.getSingleByQuery(agentContext, { threadId, connectionId }) } @@ -45,7 +45,7 @@ export class ProofRepository extends Repository { agentContext: AgentContext, parentThreadId: string, connectionId?: string - ): Promise { + ): Promise { return this.findByQuery(agentContext, { parentThreadId, connectionId }) } } diff --git a/packages/core/src/modules/proofs/repository/index.ts b/packages/core/src/modules/proofs/repository/index.ts index 8c1bf5235e..f861fe0806 100644 --- a/packages/core/src/modules/proofs/repository/index.ts +++ b/packages/core/src/modules/proofs/repository/index.ts @@ -1,3 +1,3 @@ -export * from './ProofRecord' +export * from './ProofExchangeRecord' export * from './ProofRepository' export * from './PresentationExchangeRecord' diff --git a/packages/core/src/storage/migration/__tests__/__fixtures__/alice-4-proofs-0.2.json b/packages/core/src/storage/migration/__tests__/__fixtures__/alice-4-proofs-0.2.json index 3a479abd31..c5f554ff3e 100644 --- a/packages/core/src/storage/migration/__tests__/__fixtures__/alice-4-proofs-0.2.json +++ b/packages/core/src/storage/migration/__tests__/__fixtures__/alice-4-proofs-0.2.json @@ -68,7 +68,7 @@ "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5" }, "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "type": "ProofRecord", + "type": "ProofExchangeRecord", "tags": { "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", @@ -115,7 +115,7 @@ "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82" }, "id": "ea840186-3c77-45f4-a2e6-349811ad8994", - "type": "ProofRecord", + "type": "ProofExchangeRecord", "tags": { "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", @@ -179,7 +179,7 @@ "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5" }, "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "type": "ProofRecord", + "type": "ProofExchangeRecord", "tags": { "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", @@ -225,7 +225,7 @@ "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82" }, "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "type": "ProofRecord", + "type": "ProofExchangeRecord", "tags": { "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap index 47b6988932..f46399930f 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap @@ -2,64 +2,20 @@ exports[`UpdateAssistant | v0.2 - v0.3 should correctly update proof records and create didcomm records 1`] = ` Object { - "1-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "1-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "messageId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - "messageName": "propose-presentation", - "messageType": "https://didcomm.org/present-proof/1.0/propose-presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "receiver", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "1-4e4f-41d9-94c4-f49351b811f1", - "message": Object { - "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", - "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", - "presentation_proposal": Object { - "@type": "https://didcomm.org/present-proof/1.0/presentation-preview", - "attributes": Array [ - Object { - "cred_def_id": "7yW6SoTjHNhD3zYgm4PbK8:3:CL:472319:TAG", - "name": "name", - "value": "Alice", - }, - ], - "predicates": Array [], - }, - }, - "metadata": Object {}, - "role": "receiver", - }, - }, - "10-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "10-4e4f-41d9-94c4-f49351b811f1", + "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e": Object { + "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", "tags": Object { - "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "messageId": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", - "messageName": "presentation", - "messageType": "https://didcomm.org/present-proof/1.0/presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "sender", + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "state": "done", "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, - "type": "DidCommMessageRecord", + "type": "ProofExchangeRecord", "value": Object { - "_tags": Object {}, - "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "10-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "createdAt": "2022-09-08T19:36:06.261Z", + "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "metadata": Object {}, + "presentationMessage": Object { "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", "@type": "https://didcomm.org/present-proof/1.0/presentation", "presentations~attach": Array [ @@ -75,202 +31,54 @@ Object { "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "metadata": Object {}, - "role": "sender", - }, - }, - "2-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "2-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "messageId": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", - "messageName": "request-presentation", - "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "sender", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "2-4e4f-41d9-94c4-f49351b811f1", - "message": Object { - "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", + "requestMessage": Object { + "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", "request_presentations~attach": Array [ Object { "@id": "libindy-request-presentation-0", "data": Object { - "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjQwMzU1MDc0MDYxMTU0MzEwMzA5NzMyMiIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7IjBmZWExODk3LTQzMTEtNDhmMi1hMmMyLTM4NzRlOTkzYWVmMCI6eyJuYW1lIjoibmFtZSIsInJlc3RyaWN0aW9ucyI6W3siY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyJ9XX19LCJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6e319", - }, - "mime-type": "application/json", - }, - ], - "~thread": Object { - "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - }, - "metadata": Object {}, - "role": "sender", - }, - }, - "3-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "3-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "messageId": "4185f336-f307-4022-a27d-78d1271586f6", - "messageName": "presentation", - "messageType": "https://didcomm.org/present-proof/1.0/presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "receiver", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "3-4e4f-41d9-94c4-f49351b811f1", - "message": Object { - "@id": "4185f336-f307-4022-a27d-78d1271586f6", - "@type": "https://didcomm.org/present-proof/1.0/presentation", - "presentations~attach": Array [ - Object { - "@id": "libindy-presentation-0", - "data": Object { - "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjUyODExNDc1NTIxNzg3NzExMjI1Mzc0NSIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7Im5hbWUiOnsibmFtZSI6Im5hbWUiLCJyZXN0cmljdGlvbnMiOlt7ImNyZWRfZGVmX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODozOkNMOjQ3MjMxOTpUQUcifV19fSwicmVxdWVzdGVkX3ByZWRpY2F0ZXMiOnt9fQ==", }, "mime-type": "application/json", }, ], - "~thread": Object { - "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, }, - "metadata": Object {}, - "role": "receiver", + "state": "done", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e": Object { - "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "72c96cd1-1f26-4bf3-8a00-5c00926859a8": Object { + "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", "tags": Object { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "parentThreadId": undefined, "state": "done", - "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, - "type": "ProofRecord", + "type": "ProofExchangeRecord", "value": Object { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "createdAt": "2022-09-08T19:36:06.261Z", - "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "metadata": Object {}, - "protocolVersion": "v1", - "state": "done", - "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - }, - }, - "4-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "4-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", - "messageId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - "messageName": "request-presentation", - "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "sender", - "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "4-4e4f-41d9-94c4-f49351b811f1", - "message": Object { - "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - "@type": "https://didcomm.org/present-proof/1.0/request-presentation", - "request_presentations~attach": Array [ - Object { - "@id": "libindy-request-presentation-0", - "data": Object { - "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjUyODExNDc1NTIxNzg3NzExMjI1Mzc0NSIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7Im5hbWUiOnsibmFtZSI6Im5hbWUiLCJyZXN0cmljdGlvbnMiOlt7ImNyZWRfZGVmX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODozOkNMOjQ3MjMxOTpUQUcifV19fSwicmVxdWVzdGVkX3ByZWRpY2F0ZXMiOnt9fQ==", - }, - "mime-type": "application/json", - }, - ], - }, + "createdAt": "2022-09-08T19:36:06.208Z", + "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "isVerified": true, "metadata": Object {}, - "role": "sender", - }, - }, - "5-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "5-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", - "messageId": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", - "messageName": "presentation", - "messageType": "https://didcomm.org/present-proof/1.0/presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "receiver", - "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "5-4e4f-41d9-94c4-f49351b811f1", - "message": Object { - "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", + "presentationMessage": Object { + "@id": "4185f336-f307-4022-a27d-78d1271586f6", "@type": "https://didcomm.org/present-proof/1.0/presentation", "presentations~attach": Array [ Object { "@id": "libindy-presentation-0", "data": Object { - "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", }, "mime-type": "application/json", }, ], "~thread": Object { - "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, - "metadata": Object {}, - "role": "receiver", - }, - }, - "6-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "6-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "messageId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - "messageName": "propose-presentation", - "messageType": "https://didcomm.org/present-proof/1.0/propose-presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "sender", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "6-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "proposalMessage": Object { "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", "presentation_proposal": Object { @@ -285,30 +93,7 @@ Object { "predicates": Array [], }, }, - "metadata": Object {}, - "role": "sender", - }, - }, - "7-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "7-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "messageId": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", - "messageName": "request-presentation", - "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "receiver", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "7-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "requestMessage": Object { "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", "request_presentations~attach": Array [ @@ -324,89 +109,52 @@ Object { "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, + "state": "done", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "STORAGE_VERSION_RECORD_ID": Object { + "id": "STORAGE_VERSION_RECORD_ID", + "tags": Object {}, + "type": "StorageVersionRecord", + "value": Object { + "createdAt": "2022-09-08T19:35:53.872Z", + "id": "STORAGE_VERSION_RECORD_ID", "metadata": Object {}, - "role": "receiver", + "storageVersion": "0.3", }, }, - "72c96cd1-1f26-4bf3-8a00-5c00926859a8": Object { - "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "ea840186-3c77-45f4-a2e6-349811ad8994": Object { + "id": "ea840186-3c77-45f4-a2e6-349811ad8994", "tags": Object { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "parentThreadId": undefined, "state": "done", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, - "type": "ProofRecord", + "type": "ProofExchangeRecord", "value": Object { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "createdAt": "2022-09-08T19:36:06.208Z", - "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "createdAt": "2022-09-08T19:36:06.261Z", + "id": "ea840186-3c77-45f4-a2e6-349811ad8994", "isVerified": true, "metadata": Object {}, - "protocolVersion": "v1", - "state": "done", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - }, - "8-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "8-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "messageId": "4185f336-f307-4022-a27d-78d1271586f6", - "messageName": "presentation", - "messageType": "https://didcomm.org/present-proof/1.0/presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "sender", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "8-4e4f-41d9-94c4-f49351b811f1", - "message": Object { - "@id": "4185f336-f307-4022-a27d-78d1271586f6", + "presentationMessage": Object { + "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", "@type": "https://didcomm.org/present-proof/1.0/presentation", "presentations~attach": Array [ Object { "@id": "libindy-presentation-0", "data": Object { - "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", }, "mime-type": "application/json", }, ], "~thread": Object { - "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "metadata": Object {}, - "role": "sender", - }, - }, - "9-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "9-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "messageId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - "messageName": "request-presentation", - "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "receiver", - "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "9-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "requestMessage": Object { "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", "request_presentations~attach": Array [ @@ -419,37 +167,6 @@ Object { }, ], }, - "metadata": Object {}, - "role": "receiver", - }, - }, - "STORAGE_VERSION_RECORD_ID": Object { - "id": "STORAGE_VERSION_RECORD_ID", - "tags": Object {}, - "type": "StorageVersionRecord", - "value": Object { - "createdAt": "2022-09-08T19:35:53.872Z", - "id": "STORAGE_VERSION_RECORD_ID", - "metadata": Object {}, - "storageVersion": "0.3", - }, - }, - "ea840186-3c77-45f4-a2e6-349811ad8994": Object { - "id": "ea840186-3c77-45f4-a2e6-349811ad8994", - "tags": Object { - "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "parentThreadId": undefined, - "state": "done", - "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - }, - "type": "ProofRecord", - "value": Object { - "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "createdAt": "2022-09-08T19:36:06.261Z", - "id": "ea840186-3c77-45f4-a2e6-349811ad8994", - "isVerified": true, - "metadata": Object {}, - "protocolVersion": "v1", "state": "done", "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, @@ -458,46 +175,32 @@ Object { "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", "tags": Object { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "parentThreadId": undefined, "state": "done", "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, - "type": "ProofRecord", + "type": "ProofExchangeRecord", "value": Object { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "createdAt": "2022-09-08T19:36:06.208Z", "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", "metadata": Object {}, - "protocolVersion": "v1", - "state": "done", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - }, -} -`; - -exports[`UpdateAssistant | v0.2 - v0.3 should correctly update the proofs records and create didcomm records with auto update 1`] = ` -Object { - "1-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "1-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "messageId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - "messageName": "propose-presentation", - "messageType": "https://didcomm.org/present-proof/1.0/propose-presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "receiver", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "1-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "presentationMessage": Object { + "@id": "4185f336-f307-4022-a27d-78d1271586f6", + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "presentations~attach": Array [ + Object { + "@id": "libindy-presentation-0", + "data": Object { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "proposalMessage": Object { "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", "presentation_proposal": Object { @@ -512,69 +215,7 @@ Object { "predicates": Array [], }, }, - "metadata": Object {}, - "role": "receiver", - }, - }, - "10-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "10-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "messageId": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", - "messageName": "presentation", - "messageType": "https://didcomm.org/present-proof/1.0/presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "sender", - "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "10-4e4f-41d9-94c4-f49351b811f1", - "message": Object { - "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", - "@type": "https://didcomm.org/present-proof/1.0/presentation", - "presentations~attach": Array [ - Object { - "@id": "libindy-presentation-0", - "data": Object { - "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", - }, - "mime-type": "application/json", - }, - ], - "~thread": Object { - "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - }, - }, - "metadata": Object {}, - "role": "sender", - }, - }, - "2-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "2-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "messageId": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", - "messageName": "request-presentation", - "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "sender", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "2-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "requestMessage": Object { "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", "request_presentations~attach": Array [ @@ -590,88 +231,45 @@ Object { "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, - "metadata": Object {}, - "role": "sender", + "state": "done", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, - "3-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "3-4e4f-41d9-94c4-f49351b811f1", +} +`; + +exports[`UpdateAssistant | v0.2 - v0.3 should correctly update the proofs records and create didcomm records with auto update 1`] = ` +Object { + "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e": Object { + "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", "tags": Object { - "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "messageId": "4185f336-f307-4022-a27d-78d1271586f6", - "messageName": "presentation", - "messageType": "https://didcomm.org/present-proof/1.0/presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "receiver", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "state": "done", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, - "type": "DidCommMessageRecord", + "type": "ProofExchangeRecord", "value": Object { - "_tags": Object {}, - "associatedRecordId": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "3-4e4f-41d9-94c4-f49351b811f1", - "message": Object { - "@id": "4185f336-f307-4022-a27d-78d1271586f6", + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "createdAt": "2022-09-08T19:36:06.261Z", + "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", + "metadata": Object {}, + "presentationMessage": Object { + "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", "@type": "https://didcomm.org/present-proof/1.0/presentation", "presentations~attach": Array [ Object { "@id": "libindy-presentation-0", "data": Object { - "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", }, "mime-type": "application/json", }, ], "~thread": Object { - "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "metadata": Object {}, - "role": "receiver", - }, - }, - "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e": Object { - "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "tags": Object { - "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "parentThreadId": undefined, - "state": "done", - "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - }, - "type": "ProofRecord", - "value": Object { - "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "createdAt": "2022-09-08T19:36:06.261Z", - "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "metadata": Object {}, - "protocolVersion": "v1", - "state": "done", - "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - }, - }, - "4-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "4-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", - "messageId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - "messageName": "request-presentation", - "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "sender", - "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "4-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "requestMessage": Object { "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", "request_presentations~attach": Array [ @@ -684,69 +282,41 @@ Object { }, ], }, - "metadata": Object {}, - "role": "sender", + "state": "done", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "5-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "5-4e4f-41d9-94c4-f49351b811f1", + "72c96cd1-1f26-4bf3-8a00-5c00926859a8": Object { + "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", "tags": Object { - "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", - "messageId": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", - "messageName": "presentation", - "messageType": "https://didcomm.org/present-proof/1.0/presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "receiver", - "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "state": "done", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, - "type": "DidCommMessageRecord", + "type": "ProofExchangeRecord", "value": Object { - "_tags": Object {}, - "associatedRecordId": "ea840186-3c77-45f4-a2e6-349811ad8994", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "5-4e4f-41d9-94c4-f49351b811f1", - "message": Object { - "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", + "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", + "createdAt": "2022-09-08T19:36:06.208Z", + "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "isVerified": true, + "metadata": Object {}, + "presentationMessage": Object { + "@id": "4185f336-f307-4022-a27d-78d1271586f6", "@type": "https://didcomm.org/present-proof/1.0/presentation", "presentations~attach": Array [ Object { "@id": "libindy-presentation-0", "data": Object { - "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", }, "mime-type": "application/json", }, ], "~thread": Object { - "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, - "metadata": Object {}, - "role": "receiver", - }, - }, - "6-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "6-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "messageId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - "messageName": "propose-presentation", - "messageType": "https://didcomm.org/present-proof/1.0/propose-presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "sender", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "6-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "proposalMessage": Object { "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", "presentation_proposal": Object { @@ -761,30 +331,7 @@ Object { "predicates": Array [], }, }, - "metadata": Object {}, - "role": "sender", - }, - }, - "7-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "7-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "messageId": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", - "messageName": "request-presentation", - "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "receiver", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "7-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "requestMessage": Object { "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", "request_presentations~attach": Array [ @@ -800,89 +347,52 @@ Object { "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, + "state": "done", + "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "STORAGE_VERSION_RECORD_ID": Object { + "id": "STORAGE_VERSION_RECORD_ID", + "tags": Object {}, + "type": "StorageVersionRecord", + "value": Object { + "createdAt": "2022-09-08T19:35:53.872Z", + "id": "STORAGE_VERSION_RECORD_ID", "metadata": Object {}, - "role": "receiver", + "storageVersion": "0.3", }, }, - "72c96cd1-1f26-4bf3-8a00-5c00926859a8": Object { - "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "ea840186-3c77-45f4-a2e6-349811ad8994": Object { + "id": "ea840186-3c77-45f4-a2e6-349811ad8994", "tags": Object { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "parentThreadId": undefined, "state": "done", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, - "type": "ProofRecord", + "type": "ProofExchangeRecord", "value": Object { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "createdAt": "2022-09-08T19:36:06.208Z", - "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", + "createdAt": "2022-09-08T19:36:06.261Z", + "id": "ea840186-3c77-45f4-a2e6-349811ad8994", "isVerified": true, "metadata": Object {}, - "protocolVersion": "v1", - "state": "done", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - }, - "8-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "8-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "messageId": "4185f336-f307-4022-a27d-78d1271586f6", - "messageName": "presentation", - "messageType": "https://didcomm.org/present-proof/1.0/presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "sender", - "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "8-4e4f-41d9-94c4-f49351b811f1", - "message": Object { - "@id": "4185f336-f307-4022-a27d-78d1271586f6", + "presentationMessage": Object { + "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", "@type": "https://didcomm.org/present-proof/1.0/presentation", "presentations~attach": Array [ Object { "@id": "libindy-presentation-0", "data": Object { - "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", }, "mime-type": "application/json", }, ], "~thread": Object { - "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "metadata": Object {}, - "role": "sender", - }, - }, - "9-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "9-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "messageId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - "messageName": "request-presentation", - "messageType": "https://didcomm.org/present-proof/1.0/request-presentation", - "protocolMajorVersion": "1", - "protocolMinorVersion": "0", - "protocolName": "present-proof", - "role": "receiver", - "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - }, - "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, - "associatedRecordId": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "createdAt": "2022-01-21T22:50:20.522Z", - "id": "9-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "requestMessage": Object { "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", "request_presentations~attach": Array [ @@ -895,37 +405,6 @@ Object { }, ], }, - "metadata": Object {}, - "role": "receiver", - }, - }, - "STORAGE_VERSION_RECORD_ID": Object { - "id": "STORAGE_VERSION_RECORD_ID", - "tags": Object {}, - "type": "StorageVersionRecord", - "value": Object { - "createdAt": "2022-09-08T19:35:53.872Z", - "id": "STORAGE_VERSION_RECORD_ID", - "metadata": Object {}, - "storageVersion": "0.3", - }, - }, - "ea840186-3c77-45f4-a2e6-349811ad8994": Object { - "id": "ea840186-3c77-45f4-a2e6-349811ad8994", - "tags": Object { - "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "parentThreadId": undefined, - "state": "done", - "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", - }, - "type": "ProofRecord", - "value": Object { - "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "createdAt": "2022-09-08T19:36:06.261Z", - "id": "ea840186-3c77-45f4-a2e6-349811ad8994", - "isVerified": true, - "metadata": Object {}, - "protocolVersion": "v1", "state": "done", "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, @@ -934,17 +413,62 @@ Object { "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", "tags": Object { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", - "parentThreadId": undefined, "state": "done", "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, - "type": "ProofRecord", + "type": "ProofExchangeRecord", "value": Object { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "createdAt": "2022-09-08T19:36:06.208Z", "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", "metadata": Object {}, - "protocolVersion": "v1", + "presentationMessage": Object { + "@id": "4185f336-f307-4022-a27d-78d1271586f6", + "@type": "https://didcomm.org/present-proof/1.0/presentation", + "presentations~attach": Array [ + Object { + "@id": "libindy-presentation-0", + "data": Object { + "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, + "proposalMessage": Object { + "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", + "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", + "presentation_proposal": Object { + "@type": "https://didcomm.org/present-proof/1.0/presentation-preview", + "attributes": Array [ + Object { + "cred_def_id": "7yW6SoTjHNhD3zYgm4PbK8:3:CL:472319:TAG", + "name": "name", + "value": "Alice", + }, + ], + "predicates": Array [], + }, + }, + "requestMessage": Object { + "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", + "@type": "https://didcomm.org/present-proof/1.0/request-presentation", + "request_presentations~attach": Array [ + Object { + "@id": "libindy-request-presentation-0", + "data": Object { + "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjQwMzU1MDc0MDYxMTU0MzEwMzA5NzMyMiIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7IjBmZWExODk3LTQzMTEtNDhmMi1hMmMyLTM4NzRlOTkzYWVmMCI6eyJuYW1lIjoibmFtZSIsInJlc3RyaWN0aW9ucyI6W3siY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyJ9XX19LCJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6e319", + }, + "mime-type": "application/json", + }, + ], + "~thread": Object { + "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", + }, + }, "state": "done", "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, diff --git a/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/proof.test.ts b/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/proof.test.ts index b3026f8bf2..29e508eb4f 100644 --- a/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/proof.test.ts +++ b/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/proof.test.ts @@ -1,13 +1,13 @@ import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../../tests/helpers' import { Agent } from '../../../../../agent/Agent' -import { ProofRecord, ProofState } from '../../../../../modules/proofs' +import { ProofExchangeRecord, ProofState } from '../../../../../modules/proofs' import { ProofRepository } from '../../../../../modules/proofs/repository/ProofRepository' import { JsonTransformer } from '../../../../../utils' import { DidCommMessageRole } from '../../../../didcomm' import { DidCommMessageRepository } from '../../../../didcomm/DidCommMessageRepository' import * as testModule from '../proof' -const agentConfig = getAgentConfig('Migration ProofRecord 0.2-0.3') +const agentConfig = getAgentConfig('Migration ProofExchangeRecord 0.2-0.3') const agentContext = getAgentContext() jest.mock('../../../../../modules/proofs/repository/ProofRepository') @@ -44,13 +44,13 @@ describe('0.2-0.3 | Proof', () => { mockFunction(didCommMessageRepository.save).mockReset() }) - describe('migrateProofRecordToV0_3()', () => { + describe('migrateProofExchangeRecordToV0_3()', () => { it('should fetch all records and apply the needed updates ', async () => { - const records: ProofRecord[] = [getProof({})] + const records: ProofExchangeRecord[] = [getProof({})] mockFunction(proofRepository.getAll).mockResolvedValue(records) - await testModule.migrateProofRecordToV0_3(agent) + await testModule.migrateProofExchangeRecordToV0_3(agent) expect(proofRepository.getAll).toHaveBeenCalledTimes(1) expect(proofRepository.update).toHaveBeenCalledTimes(records.length) @@ -64,11 +64,11 @@ describe('0.2-0.3 | Proof', () => { }) }) - describe('migrateInternalProofRecordProperties()', () => { + describe('migrateInternalProofExchangeRecordProperties()', () => { it('should set the protocol version to v1 if not set on the record', async () => { const proofRecord = getProof({}) - await testModule.migrateInternalProofRecordProperties(agent, proofRecord) + await testModule.migrateInternalProofExchangeRecordProperties(agent, proofRecord) expect(proofRecord).toMatchObject({ protocolVersion: 'v1', @@ -80,7 +80,7 @@ describe('0.2-0.3 | Proof', () => { protocolVersion: 'v2', }) - await testModule.migrateInternalProofRecordProperties(agent, proofRecord) + await testModule.migrateInternalProofExchangeRecordProperties(agent, proofRecord) expect(proofRecord).toMatchObject({ protocolVersion: 'v2', @@ -305,6 +305,6 @@ function getProof({ isVerified, id, }, - ProofRecord + ProofExchangeRecord ) } diff --git a/packages/core/src/storage/migration/updates/0.2-0.3/index.ts b/packages/core/src/storage/migration/updates/0.2-0.3/index.ts index 562ddba1db..a47fa4f328 100644 --- a/packages/core/src/storage/migration/updates/0.2-0.3/index.ts +++ b/packages/core/src/storage/migration/updates/0.2-0.3/index.ts @@ -1,7 +1,7 @@ import type { BaseAgent } from '../../../../agent/BaseAgent' -import { migrateProofRecordToV0_3 } from './proof' +import { migrateProofExchangeRecordToV0_3 } from './proof' export async function updateV0_2ToV0_3(agent: Agent): Promise { - await migrateProofRecordToV0_3(agent) + await migrateProofExchangeRecordToV0_3(agent) } diff --git a/packages/core/src/storage/migration/updates/0.2-0.3/proof.ts b/packages/core/src/storage/migration/updates/0.2-0.3/proof.ts index 71a9f10cc3..7e923a3d48 100644 --- a/packages/core/src/storage/migration/updates/0.2-0.3/proof.ts +++ b/packages/core/src/storage/migration/updates/0.2-0.3/proof.ts @@ -1,5 +1,5 @@ import type { BaseAgent } from '../../../../agent/BaseAgent' -import type { ProofRecord } from '../../../../modules/proofs' +import type { ProofExchangeRecord } from '../../../../modules/proofs' import type { JsonObject } from '../../../../types' import { ProofState } from '../../../../modules/proofs/models' @@ -7,15 +7,15 @@ import { ProofRepository } from '../../../../modules/proofs/repository/ProofRepo import { DidCommMessageRepository, DidCommMessageRecord, DidCommMessageRole } from '../../../didcomm' /** - * Migrates the {@link ProofRecord} to 0.3 compatible format. It fetches all records from storage + * Migrates the {@link ProofExchangeRecord} to 0.3 compatible format. It fetches all records from storage * and applies the needed updates to the records. After a record has been transformed, it is updated * in storage and the next record will be transformed. * * The following transformations are applied: - * - {@link migrateInternalProofRecordProperties} + * - {@link migrateInternalProofExchangeRecordProperties} * - {@link moveDidCommMessages} */ -export async function migrateProofRecordToV0_3(agent: Agent) { +export async function migrateProofExchangeRecordToV0_3(agent: Agent) { agent.config.logger.info('Migrating proof records to storage version 0.3') const proofRepository = agent.dependencyManager.resolve(ProofRepository) @@ -26,7 +26,7 @@ export async function migrateProofRecordToV0_3(agent: A for (const proofRecord of allProofs) { agent.config.logger.debug(`Migrating proof record with id ${proofRecord.id} to storage version 0.3`) - await migrateInternalProofRecordProperties(agent, proofRecord) + await migrateInternalProofExchangeRecordProperties(agent, proofRecord) await moveDidCommMessages(agent, proofRecord) await proofRepository.update(agent.context, proofRecord) @@ -63,7 +63,7 @@ const didCommMessageRoleMapping = { const proofRecordMessageKeys = ['proposalMessage', 'requestMessage', 'presentationMessage'] as const -export function getProofRole(proofRecord: ProofRecord) { +export function getProofRole(proofRecord: ProofExchangeRecord) { // Proofs will only have an isVerified value when a presentation is verified, meaning we're the verifier if (proofRecord.isVerified !== undefined) { return ProofRole.Verifier @@ -99,9 +99,9 @@ export function getProofRole(proofRecord: ProofRecord) { * } * ``` */ -export async function migrateInternalProofRecordProperties( +export async function migrateInternalProofExchangeRecordProperties( agent: Agent, - proofRecord: ProofRecord + proofRecord: ProofExchangeRecord ) { agent.config.logger.debug(`Migrating internal proof record ${proofRecord.id} properties to storage version 0.3`) @@ -120,7 +120,7 @@ export async function migrateInternalProofRecordProperties(agent: Agent, proofRecord: ProofRecord) { +export async function moveDidCommMessages(agent: Agent, proofRecord: ProofExchangeRecord) { agent.config.logger.debug( `Moving didcomm messages from proof record with id ${proofRecord.id} to DidCommMessageRecord` ) diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 571eba9577..9124eaacdb 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -14,11 +14,8 @@ import type { } from '../src' import type { AgentModulesInput } from '../src/agent/AgentModules' import type { IndyOfferCredentialFormat } from '../src/modules/credentials/formats/indy/IndyCredentialFormat' -import type { RequestProofOptions } from '../src/modules/proofs/ProofsApiOptions' -import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' import type { ProofAttributeInfo, ProofPredicateInfo } from '../src/modules/proofs/formats/indy/models' import type { AutoAcceptProof } from '../src/modules/proofs/models/ProofAutoAcceptType' -import type { V2ProofService } from '../src/modules/proofs/protocol/v2' import type { CredDef, Schema } from 'indy-sdk' import type { Observable } from 'rxjs' @@ -166,7 +163,7 @@ export function getAgentContext({ return new AgentContext({ dependencyManager, contextCorrelationId }) } -export async function waitForProofRecord( +export async function waitForProofExchangeRecord( agent: Agent, options: { threadId?: string @@ -178,10 +175,10 @@ export async function waitForProofRecord( ) { const observable = agent.events.observable(ProofEventTypes.ProofStateChanged) - return waitForProofRecordSubject(observable, options) + return waitForProofExchangeRecordSubject(observable, options) } -export function waitForProofRecordSubject( +export function waitForProofExchangeRecordSubject( subject: ReplaySubject | Observable, { threadId, @@ -582,7 +579,7 @@ export async function presentProof({ verifierAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(verifierReplay) holderAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(holderReplay) - let holderProofRecordPromise = waitForProofRecordSubject(holderReplay, { + let holderProofExchangeRecordPromise = waitForProofExchangeRecordSubject(holderReplay, { state: ProofState.RequestReceived, }) @@ -600,7 +597,7 @@ export async function presentProof({ protocolVersion: 'v2', }) - let holderRecord = await holderProofRecordPromise + let holderRecord = await holderProofExchangeRecordPromise const requestedCredentials = await holderAgent.proofs.autoSelectCredentialsForProofRequest({ proofRecordId: holderRecord.id, @@ -609,7 +606,7 @@ export async function presentProof({ }, }) - const verifierProofRecordPromise = waitForProofRecordSubject(verifierReplay, { + const verifierProofExchangeRecordPromise = waitForProofExchangeRecordSubject(verifierReplay, { threadId: holderRecord.threadId, state: ProofState.PresentationReceived, }) @@ -619,18 +616,18 @@ export async function presentProof({ proofFormats: { indy: requestedCredentials.proofFormats.indy }, }) - verifierRecord = await verifierProofRecordPromise + verifierRecord = await verifierProofExchangeRecordPromise // assert presentation is valid expect(verifierRecord.isVerified).toBe(true) - holderProofRecordPromise = waitForProofRecordSubject(holderReplay, { + holderProofExchangeRecordPromise = waitForProofExchangeRecordSubject(holderReplay, { threadId: holderRecord.threadId, state: ProofState.Done, }) verifierRecord = await verifierAgent.proofs.acceptPresentation(verifierRecord.id) - holderRecord = await holderProofRecordPromise + holderRecord = await holderProofExchangeRecordPromise return { verifierProof: verifierRecord, diff --git a/packages/core/tests/proofs-sub-protocol.test.ts b/packages/core/tests/proofs-sub-protocol.test.ts index 06ec56c9aa..271bd089c3 100644 --- a/packages/core/tests/proofs-sub-protocol.test.ts +++ b/packages/core/tests/proofs-sub-protocol.test.ts @@ -1,4 +1,4 @@ -import type { Agent, ConnectionRecord, ProofRecord } from '../src' +import type { Agent, ConnectionRecord, ProofExchangeRecord } from '../src' import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' import type { CredDefId } from 'indy-sdk' @@ -11,7 +11,7 @@ import { import { ProofState } from '../src/modules/proofs/models/ProofState' import { uuid } from '../src/utils/uuid' -import { setupProofsTest, waitForProofRecord } from './helpers' +import { setupProofsTest, waitForProofExchangeRecord } from './helpers' import testLogger from './logger' describe('Present Proof Subprotocol', () => { @@ -20,7 +20,7 @@ describe('Present Proof Subprotocol', () => { let credDefId: CredDefId let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord - let aliceProofRecord: ProofRecord + let aliceProofExchangeRecord: ProofExchangeRecord let presentationPreview: PresentationPreview beforeAll(async () => { @@ -44,12 +44,12 @@ describe('Present Proof Subprotocol', () => { // Alice sends a presentation proposal to Faber testLogger.test('Alice sends a presentation proposal to Faber') - const faberProofRecordPromise = waitForProofRecord(faberAgent, { + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { parentThreadId, state: ProofState.ProposalReceived, }) - aliceProofRecord = await aliceAgent.proofs.proposeProof({ + aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v1', parentThreadId, @@ -64,22 +64,22 @@ describe('Present Proof Subprotocol', () => { }, }) - expect(aliceProofRecord.parentThreadId).toBe(parentThreadId) + expect(aliceProofExchangeRecord.parentThreadId).toBe(parentThreadId) const proofsByParentThread = await aliceAgent.proofs.getByParentThreadAndConnectionId(parentThreadId) expect(proofsByParentThread.length).toEqual(1) expect(proofsByParentThread[0].parentThreadId).toBe(parentThreadId) - const threadId = aliceProofRecord.threadId + const threadId = aliceProofExchangeRecord.threadId testLogger.test('Faber waits for a presentation proposal from Alice') - let faberProofRecord = await faberProofRecordPromise + let faberProofExchangeRecord = await faberProofExchangeRecordPromise // Faber accepts the presentation proposal from Alice testLogger.test('Faber accepts the presentation proposal from Alice') - await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofRecord.id }) + await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofExchangeRecord.id }) testLogger.test('Alice waits till it receives presentation ack') - await waitForProofRecord(aliceAgent, { + await waitForProofExchangeRecord(aliceAgent, { threadId, parentThreadId, state: ProofState.RequestReceived, @@ -88,18 +88,18 @@ describe('Present Proof Subprotocol', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, config: { filterByPresentationPreview: true, }, }) await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, proofFormats: requestedCredentials.proofFormats, }) testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await waitForProofRecord(faberAgent, { + faberProofExchangeRecord = await waitForProofExchangeRecord(faberAgent, { threadId, parentThreadId, state: ProofState.PresentationReceived, @@ -107,11 +107,11 @@ describe('Present Proof Subprotocol', () => { // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') - await waitForProofRecord(aliceAgent, { + await waitForProofExchangeRecord(aliceAgent, { threadId, parentThreadId, state: ProofState.Done, @@ -146,14 +146,14 @@ describe('Present Proof Subprotocol', () => { }), } - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { parentThreadId, state: ProofState.RequestReceived, }) // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') - const faberProofRecord = await faberAgent.proofs.requestProof({ + const faberProofExchangeRecord = await faberAgent.proofs.requestProof({ connectionId: faberConnection.id, parentThreadId, protocolVersion: 'v1', @@ -168,33 +168,33 @@ describe('Present Proof Subprotocol', () => { }, }) - expect(faberProofRecord.parentThreadId).toBe(parentThreadId) + expect(faberProofExchangeRecord.parentThreadId).toBe(parentThreadId) const proofsByParentThread = await faberAgent.proofs.getByParentThreadAndConnectionId(parentThreadId) expect(proofsByParentThread.length).toEqual(1) expect(proofsByParentThread[0].parentThreadId).toBe(parentThreadId) - const threadId = faberProofRecord.threadId + const threadId = faberProofExchangeRecord.threadId // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - const aliceProofRecord = await aliceProofRecordPromise + const aliceProofExchangeRecord = await aliceProofExchangeRecordPromise // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, config: { filterByPresentationPreview: true, }, }) await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, proofFormats: requestedCredentials.proofFormats, }) // Faber waits until it receives a presentation from Alice testLogger.test('Faber waits for presentation from Alice') - await waitForProofRecord(faberAgent, { + await waitForProofExchangeRecord(faberAgent, { threadId, parentThreadId, state: ProofState.PresentationReceived, @@ -202,11 +202,11 @@ describe('Present Proof Subprotocol', () => { // Faber accepts the presentation testLogger.test('Faber accept the presentation from Alice') - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) // Alice waits until she receives a presentation acknowledgement testLogger.test('Alice waits for acceptance by Faber') - await waitForProofRecord(aliceAgent, { + await waitForProofExchangeRecord(aliceAgent, { threadId, parentThreadId, state: ProofState.Done, @@ -219,12 +219,12 @@ describe('Present Proof Subprotocol', () => { // Alice sends a presentation proposal to Faber testLogger.test('Alice sends a presentation proposal to Faber') - const faberProofRecordPromise = waitForProofRecord(faberAgent, { + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { parentThreadId, state: ProofState.ProposalReceived, }) - aliceProofRecord = await aliceAgent.proofs.proposeProof({ + aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v2', parentThreadId, @@ -239,22 +239,22 @@ describe('Present Proof Subprotocol', () => { }, }) - expect(aliceProofRecord.parentThreadId).toBe(parentThreadId) + expect(aliceProofExchangeRecord.parentThreadId).toBe(parentThreadId) const proofsByParentThread = await aliceAgent.proofs.getByParentThreadAndConnectionId(parentThreadId) expect(proofsByParentThread.length).toEqual(1) expect(proofsByParentThread[0].parentThreadId).toBe(parentThreadId) - const threadId = aliceProofRecord.threadId + const threadId = aliceProofExchangeRecord.threadId testLogger.test('Faber waits for a presentation proposal from Alice') - let faberProofRecord = await faberProofRecordPromise + let faberProofExchangeRecord = await faberProofExchangeRecordPromise // Faber accepts the presentation proposal from Alice testLogger.test('Faber accepts the presentation proposal from Alice') - await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofRecord.id }) + await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofExchangeRecord.id }) testLogger.test('Alice waits till it receives presentation ack') - await waitForProofRecord(aliceAgent, { + await waitForProofExchangeRecord(aliceAgent, { threadId, parentThreadId, state: ProofState.RequestReceived, @@ -263,18 +263,18 @@ describe('Present Proof Subprotocol', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, config: { filterByPresentationPreview: true, }, }) await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, proofFormats: requestedCredentials.proofFormats, }) testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await waitForProofRecord(faberAgent, { + faberProofExchangeRecord = await waitForProofExchangeRecord(faberAgent, { threadId, parentThreadId, state: ProofState.PresentationReceived, @@ -282,11 +282,11 @@ describe('Present Proof Subprotocol', () => { // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') - await waitForProofRecord(aliceAgent, { + await waitForProofExchangeRecord(aliceAgent, { threadId, parentThreadId, state: ProofState.Done, @@ -321,14 +321,14 @@ describe('Present Proof Subprotocol', () => { }), } - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { parentThreadId, state: ProofState.RequestReceived, }) // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') - const faberProofRecord = await faberAgent.proofs.requestProof({ + const faberProofExchangeRecord = await faberAgent.proofs.requestProof({ connectionId: faberConnection.id, parentThreadId, protocolVersion: 'v2', @@ -343,33 +343,33 @@ describe('Present Proof Subprotocol', () => { }, }) - expect(faberProofRecord.parentThreadId).toBe(parentThreadId) + expect(faberProofExchangeRecord.parentThreadId).toBe(parentThreadId) const proofsByParentThread = await faberAgent.proofs.getByParentThreadAndConnectionId(parentThreadId) expect(proofsByParentThread.length).toEqual(1) expect(proofsByParentThread[0].parentThreadId).toBe(parentThreadId) - const threadId = faberProofRecord.threadId + const threadId = faberProofExchangeRecord.threadId // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - const aliceProofRecord = await aliceProofRecordPromise + const aliceProofExchangeRecord = await aliceProofExchangeRecordPromise // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, config: { filterByPresentationPreview: true, }, }) await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, proofFormats: requestedCredentials.proofFormats, }) // Faber waits until it receives a presentation from Alice testLogger.test('Faber waits for presentation from Alice') - await waitForProofRecord(faberAgent, { + await waitForProofExchangeRecord(faberAgent, { threadId, parentThreadId, state: ProofState.PresentationReceived, @@ -377,11 +377,11 @@ describe('Present Proof Subprotocol', () => { // Faber accepts the presentation testLogger.test('Faber accept the presentation from Alice') - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) // Alice waits until she receives a presentation acknowledgement testLogger.test('Alice waits for acceptance by Faber') - await waitForProofRecord(aliceAgent, { + await waitForProofExchangeRecord(aliceAgent, { threadId, parentThreadId, state: ProofState.Done, diff --git a/packages/core/tests/v1-connectionless-proofs.test.ts b/packages/core/tests/v1-connectionless-proofs.test.ts index 6094a0d65d..d2fe8af3c3 100644 --- a/packages/core/tests/v1-connectionless-proofs.test.ts +++ b/packages/core/tests/v1-connectionless-proofs.test.ts @@ -28,7 +28,7 @@ import { makeConnection, prepareForIssuance, setupProofsTest, - waitForProofRecordSubject, + waitForProofExchangeRecordSubject, } from './helpers' import testLogger from './logger' @@ -75,12 +75,12 @@ describe('Present Proof', () => { }), } - let aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + let aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { state: ProofState.RequestReceived, }) // eslint-disable-next-line prefer-const - let { proofRecord: faberProofRecord, message } = await faberAgent.proofs.createRequest({ + let { proofRecord: faberProofExchangeRecord, message } = await faberAgent.proofs.createRequest({ protocolVersion: 'v1', proofFormats: { indy: { @@ -94,49 +94,49 @@ describe('Present Proof', () => { }) const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofRecord.id, + recordId: faberProofExchangeRecord.id, message, domain: 'https://a-domain.com', }) await aliceAgent.receiveMessage(requestMessage.toJSON()) testLogger.test('Alice waits for presentation request from Faber') - let aliceProofRecord = await aliceProofRecordPromise + let aliceProofExchangeRecord = await aliceProofExchangeRecordPromise testLogger.test('Alice accepts presentation request from Faber') const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, config: { filterByPresentationPreview: true, }, }) - const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { - threadId: aliceProofRecord.threadId, + const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.PresentationReceived, }) await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, proofFormats: { indy: requestedCredentials.proofFormats.indy }, }) testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise // assert presentation is valid - expect(faberProofRecord.isVerified).toBe(true) + expect(faberProofExchangeRecord.isVerified).toBe(true) - aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { - threadId: aliceProofRecord.threadId, + aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) // Faber accepts presentation - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) // Alice waits till it receives presentation ack - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise }) test('Faber starts with connection-less proof requests to Alice with auto-accept enabled', async () => { @@ -174,16 +174,16 @@ describe('Present Proof', () => { }), } - const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { state: ProofState.Done, }) - const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { + const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { state: ProofState.Done, }) // eslint-disable-next-line prefer-const - let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest({ + let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ protocolVersion: 'v1', proofFormats: { indy: { @@ -198,16 +198,16 @@ describe('Present Proof', () => { }) const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofRecord.id, + recordId: faberProofExchangeRecord.id, message, domain: 'https://a-domain.com', }) await aliceAgent.receiveMessage(requestMessage.toJSON()) - await aliceProofRecordPromise + await aliceProofExchangeRecordPromise - await faberProofRecordPromise + await faberProofExchangeRecordPromise }) test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => { @@ -338,16 +338,16 @@ describe('Present Proof', () => { }), } - const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { state: ProofState.Done, }) - const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { + const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { state: ProofState.Done, }) // eslint-disable-next-line prefer-const - let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest({ + let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ protocolVersion: 'v1', proofFormats: { indy: { @@ -362,7 +362,7 @@ describe('Present Proof', () => { }) const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofRecord.id, + recordId: faberProofExchangeRecord.id, message, domain: 'https://a-domain.com', }) @@ -382,9 +382,9 @@ describe('Present Proof', () => { await aliceAgent.receiveMessage(requestMessage.toJSON()) - await aliceProofRecordPromise + await aliceProofExchangeRecordPromise - await faberProofRecordPromise + await faberProofExchangeRecordPromise await aliceAgent.mediationRecipient.stopMessagePickup() await faberAgent.mediationRecipient.stopMessagePickup() diff --git a/packages/core/tests/v1-indy-proofs.test.ts b/packages/core/tests/v1-indy-proofs.test.ts index 4246ed8278..b8142c4b17 100644 --- a/packages/core/tests/v1-indy-proofs.test.ts +++ b/packages/core/tests/v1-indy-proofs.test.ts @@ -1,14 +1,9 @@ -import type { Agent, ConnectionRecord, ProofRecord } from '../src' -import type { - AcceptProposalOptions, - ProposeProofOptions, - RequestProofOptions, -} from '../src/modules/proofs/ProofsApiOptions' -import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' -import type { V1ProofService } from '../src/modules/proofs/protocol/v1' +import type { Agent, ConnectionRecord } from '../src' +import type { AcceptProposalOptions } from '../src/modules/proofs/ProofsApiOptions' import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' import type { CredDefId } from 'indy-sdk' +import { ProofExchangeRecord } from '../src' import { getGroupKeysFromIndyProofFormatData } from '../src/modules/proofs/__tests__/groupKeys' import { ProofAttributeInfo, @@ -24,7 +19,7 @@ import { } from '../src/modules/proofs/protocol/v1/messages' import { DidCommMessageRepository } from '../src/storage/didcomm' -import { setupProofsTest, waitForProofRecord } from './helpers' +import { setupProofsTest, waitForProofExchangeRecord } from './helpers' import testLogger from './logger' describe('Present Proof', () => { @@ -33,8 +28,8 @@ describe('Present Proof', () => { let credDefId: CredDefId let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord - let faberProofRecord: ProofRecord - let aliceProofRecord: ProofRecord + let faberProofExchangeRecord: ProofExchangeRecord + let aliceProofExchangeRecord: ProofExchangeRecord let presentationPreview: PresentationPreview let didCommMessageRepository: DidCommMessageRepository @@ -57,7 +52,11 @@ describe('Present Proof', () => { // Alice sends a presentation proposal to Faber testLogger.test('Alice sends a presentation proposal to Faber') - const proposeProofOptions: ProposeProofOptions<[IndyProofFormat], [V1ProofService]> = { + let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v1', proofFormats: { @@ -69,22 +68,16 @@ describe('Present Proof', () => { predicates: presentationPreview.predicates, }, }, - } - - let faberProofRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.ProposalReceived, }) - aliceProofRecord = await aliceAgent.proofs.proposeProof(proposeProofOptions) - // Faber waits for a presentation proposal from Alice testLogger.test('Faber waits for a presentation proposal from Alice') - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V1ProposePresentationMessage, }) @@ -115,32 +108,32 @@ describe('Present Proof', () => { ], }, }) - expect(faberProofRecord.id).not.toBeNull() - expect(faberProofRecord).toMatchObject({ - threadId: faberProofRecord.threadId, + expect(faberProofExchangeRecord.id).not.toBeNull() + expect(faberProofExchangeRecord).toMatchObject({ + threadId: faberProofExchangeRecord.threadId, state: ProofState.ProposalReceived, protocolVersion: 'v1', }) const acceptProposalOptions: AcceptProposalOptions = { - proofRecordId: faberProofRecord.id, + proofRecordId: faberProofExchangeRecord.id, } - let aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, + let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.RequestReceived, }) // Faber accepts the presentation proposal from Alice testLogger.test('Faber accepts presentation proposal from Alice') - faberProofRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V1RequestPresentationMessage, }) @@ -157,7 +150,7 @@ describe('Present Proof', () => { }, ], thread: { - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, }, }) @@ -165,28 +158,28 @@ describe('Present Proof', () => { testLogger.test('Alice accepts presentation request from Faber') const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, config: { filterByPresentationPreview: true, }, }) - faberProofRecordPromise = waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, + faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.PresentationReceived, }) await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, proofFormats: { indy: requestedCredentials.proofFormats.indy }, }) // Faber waits for the presentation from Alice testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V1PresentationMessage, }) @@ -216,54 +209,54 @@ describe('Present Proof', () => { }, }) - expect(faberProofRecord.id).not.toBeNull() - expect(faberProofRecord).toMatchObject({ - threadId: faberProofRecord.threadId, + expect(faberProofExchangeRecord.id).not.toBeNull() + expect(faberProofExchangeRecord).toMatchObject({ + threadId: faberProofExchangeRecord.threadId, state: ProofState.PresentationReceived, protocolVersion: 'v1', }) - aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, + aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - expect(faberProofRecord).toMatchObject({ - // type: ProofRecord.name, + expect(faberProofExchangeRecord).toMatchObject({ + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - threadId: aliceProofRecord.threadId, + threadId: aliceProofExchangeRecord.threadId, connectionId: expect.any(String), isVerified: true, state: ProofState.PresentationReceived, }) - expect(aliceProofRecord).toMatchObject({ - // type: ProofRecord.name, + expect(aliceProofExchangeRecord).toMatchObject({ + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, connectionId: expect.any(String), state: ProofState.Done, }) - const proposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofRecord.id) - const requestMessage = await aliceAgent.proofs.findRequestMessage(aliceProofRecord.id) - const presentationMessage = await aliceAgent.proofs.findPresentationMessage(aliceProofRecord.id) + const proposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofExchangeRecord.id) + const requestMessage = await aliceAgent.proofs.findRequestMessage(aliceProofExchangeRecord.id) + const presentationMessage = await aliceAgent.proofs.findPresentationMessage(aliceProofExchangeRecord.id) expect(proposalMessage).toBeInstanceOf(V1ProposePresentationMessage) expect(requestMessage).toBeInstanceOf(V1RequestPresentationMessage) expect(presentationMessage).toBeInstanceOf(V1PresentationMessage) - const formatData = await aliceAgent.proofs.getFormatData(aliceProofRecord.id) + const formatData = await aliceAgent.proofs.getFormatData(aliceProofExchangeRecord.id) // eslint-disable-next-line prefer-const let { proposeKey1, proposeKey2, requestKey1, requestKey2 } = getGroupKeysFromIndyProofFormatData(formatData) @@ -377,13 +370,13 @@ describe('Present Proof', () => { }), } - let aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') - faberProofRecord = await faberAgent.proofs.requestProof({ + faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v1', connectionId: faberConnection.id, proofFormats: { @@ -399,12 +392,12 @@ describe('Present Proof', () => { // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V1RequestPresentationMessage, }) @@ -422,9 +415,9 @@ describe('Present Proof', () => { ], }) - expect(aliceProofRecord.id).not.toBeNull() - expect(aliceProofRecord).toMatchObject({ - threadId: aliceProofRecord.threadId, + expect(aliceProofExchangeRecord.id).not.toBeNull() + expect(aliceProofExchangeRecord).toMatchObject({ + threadId: aliceProofExchangeRecord.threadId, state: ProofState.RequestReceived, protocolVersion: 'v1', }) @@ -433,28 +426,28 @@ describe('Present Proof', () => { testLogger.test('Alice accepts presentation request from Faber') const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, config: { filterByPresentationPreview: true, }, }) - const faberProofRecordPromise = waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.PresentationReceived, }) await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, proofFormats: { indy: requestedCredentials.proofFormats.indy }, }) // Faber waits until it receives a presentation from Alice testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V1PresentationMessage, }) @@ -484,41 +477,41 @@ describe('Present Proof', () => { }, }) - expect(faberProofRecord.id).not.toBeNull() - expect(faberProofRecord).toMatchObject({ - threadId: faberProofRecord.threadId, + expect(faberProofExchangeRecord.id).not.toBeNull() + expect(faberProofExchangeRecord).toMatchObject({ + threadId: faberProofExchangeRecord.threadId, state: ProofState.PresentationReceived, protocolVersion: 'v1', }) - aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, + aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) // Faber accepts the presentation testLogger.test('Faber accept the presentation from Alice') - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) // Alice waits until she receives a presentation acknowledgement testLogger.test('Alice waits for acceptance by Faber') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - expect(faberProofRecord).toMatchObject({ - // type: ProofRecord.name, + expect(faberProofExchangeRecord).toMatchObject({ + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - threadId: aliceProofRecord.threadId, + threadId: aliceProofExchangeRecord.threadId, connectionId: expect.any(String), isVerified: true, state: ProofState.PresentationReceived, }) - expect(aliceProofRecord).toMatchObject({ - // type: ProofRecord.name, + expect(aliceProofExchangeRecord).toMatchObject({ + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, connectionId: expect.any(String), state: ProofState.Done, }) @@ -602,13 +595,13 @@ describe('Present Proof', () => { }), } - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') - faberProofRecord = await faberAgent.proofs.requestProof({ + faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v1', connectionId: faberConnection.id, proofFormats: { @@ -624,12 +617,12 @@ describe('Present Proof', () => { // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V1RequestPresentationMessage, }) @@ -647,24 +640,27 @@ describe('Present Proof', () => { ], }) - expect(aliceProofRecord.id).not.toBeNull() - expect(aliceProofRecord).toMatchObject({ - threadId: aliceProofRecord.threadId, + expect(aliceProofExchangeRecord.id).not.toBeNull() + expect(aliceProofExchangeRecord).toMatchObject({ + threadId: aliceProofExchangeRecord.threadId, state: ProofState.RequestReceived, protocolVersion: 'v1', }) - const faberProofRecordPromise = waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Abandoned, }) - aliceProofRecord = await aliceAgent.proofs.sendProblemReport(aliceProofRecord.id, 'Problem inside proof request') + aliceProofExchangeRecord = await aliceAgent.proofs.sendProblemReport( + aliceProofExchangeRecord.id, + 'Problem inside proof request' + ) - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise - expect(faberProofRecord).toMatchObject({ - threadId: aliceProofRecord.threadId, + expect(faberProofExchangeRecord).toMatchObject({ + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Abandoned, protocolVersion: 'v1', }) diff --git a/packages/core/tests/v1-proofs-auto-accept.test.ts b/packages/core/tests/v1-proofs-auto-accept.test.ts index 37056c0d81..fbca03df04 100644 --- a/packages/core/tests/v1-proofs-auto-accept.test.ts +++ b/packages/core/tests/v1-proofs-auto-accept.test.ts @@ -1,7 +1,4 @@ import type { Agent, ConnectionRecord } from '../src' -import type { ProposeProofOptions, RequestProofOptions } from '../src/modules/proofs/ProofsApiOptions' -import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' -import type { V1ProofService } from '../src/modules/proofs/protocol/v1/V1ProofService' import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' import { @@ -13,7 +10,7 @@ import { PredicateType, } from '../src' -import { setupProofsTest, waitForProofRecord } from './helpers' +import { setupProofsTest, waitForProofExchangeRecord } from './helpers' import testLogger from './logger' describe('Auto accept present proof', () => { @@ -43,7 +40,15 @@ describe('Auto accept present proof', () => { test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `always`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - const proposeProofOptions: ProposeProofOptions<[IndyProofFormat], [V1ProofService]> = { + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.Done, + }) + + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + state: ProofState.Done, + }) + + await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v1', proofFormats: { @@ -55,23 +60,13 @@ describe('Auto accept present proof', () => { predicates: presentationPreview.predicates, }, }, - } - - const faberProofRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.Done, - }) - - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - state: ProofState.Done, }) - await aliceAgent.proofs.proposeProof(proposeProofOptions) - testLogger.test('Faber waits for presentation from Alice') - await faberProofRecordPromise + await faberProofExchangeRecordPromise testLogger.test('Alice waits till it receives presentation ack') - await aliceProofRecordPromise + await aliceProofExchangeRecordPromise }) test('Faber starts with proof requests to Alice, both with autoAcceptProof on `always`', async () => { @@ -99,11 +94,11 @@ describe('Auto accept present proof', () => { }), } - const faberProofRecordPromise = waitForProofRecord(faberAgent, { + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { state: ProofState.Done, }) - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done, }) @@ -123,10 +118,10 @@ describe('Auto accept present proof', () => { testLogger.test('Faber waits for presentation from Alice') - await faberProofRecordPromise + await faberProofExchangeRecordPromise // Alice waits till it receives presentation ack - await aliceProofRecordPromise + await aliceProofExchangeRecordPromise }) }) @@ -151,7 +146,11 @@ describe('Auto accept present proof', () => { test('Alice starts with proof proposal to Faber, both with autoacceptproof on `contentApproved`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - const proposal: ProposeProofOptions<[IndyProofFormat], [V1ProofService]> = { + let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v1', proofFormats: { @@ -163,35 +162,29 @@ describe('Auto accept present proof', () => { predicates: presentationPreview.predicates, }, }, - } - - let faberProofRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.ProposalReceived, }) - const aliceProofRecord = await aliceAgent.proofs.proposeProof(proposal) - testLogger.test('Faber waits for presentation proposal from Alice') - await faberProofRecordPromise + await faberProofExchangeRecordPromise testLogger.test('Faber accepts presentation proposal from Alice') - faberProofRecordPromise = waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, + faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) testLogger.test('Faber waits for presentation from Alice') - await faberProofRecordPromise + await faberProofExchangeRecordPromise // Alice waits till it receives presentation ack - await aliceProofRecordPromise + await aliceProofExchangeRecordPromise }) test('Faber starts with proof requests to Alice, both with autoacceptproof on `contentApproved`', async () => { @@ -219,11 +212,11 @@ describe('Auto accept present proof', () => { }), } - const faberProofRecordPromise = waitForProofRecord(faberAgent, { + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { state: ProofState.Done, }) - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done, }) @@ -242,10 +235,10 @@ describe('Auto accept present proof', () => { }) testLogger.test('Faber waits for presentation from Alice') - await faberProofRecordPromise + await faberProofExchangeRecordPromise // Alice waits till it receives presentation ack - await aliceProofRecordPromise + await aliceProofExchangeRecordPromise }) }) }) diff --git a/packages/core/tests/v2-connectionless-proofs.test.ts b/packages/core/tests/v2-connectionless-proofs.test.ts index ef9f5f69cc..29263d692e 100644 --- a/packages/core/tests/v2-connectionless-proofs.test.ts +++ b/packages/core/tests/v2-connectionless-proofs.test.ts @@ -1,8 +1,5 @@ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { ProofStateChangedEvent } from '../src/modules/proofs' -import type { CreateProofRequestOptions } from '../src/modules/proofs/ProofsApiOptions' -import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' -import type { V2ProofService } from '../src/modules/proofs/protocol/v2' import { Subject, ReplaySubject } from 'rxjs' @@ -31,7 +28,7 @@ import { makeConnection, prepareForIssuance, setupProofsTest, - waitForProofRecordSubject, + waitForProofExchangeRecordSubject, } from './helpers' import testLogger from './logger' @@ -78,12 +75,12 @@ describe('Present Proof', () => { }), } - let aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + let aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { state: ProofState.RequestReceived, }) // eslint-disable-next-line prefer-const - let { proofRecord: faberProofRecord, message } = await faberAgent.proofs.createRequest({ + let { proofRecord: faberProofExchangeRecord, message } = await faberAgent.proofs.createRequest({ protocolVersion: 'v2', proofFormats: { indy: { @@ -97,7 +94,7 @@ describe('Present Proof', () => { }) const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofRecord.id, + recordId: faberProofExchangeRecord.id, message, domain: 'https://a-domain.com', }) @@ -105,43 +102,43 @@ describe('Present Proof', () => { await aliceAgent.receiveMessage(requestMessage.toJSON()) testLogger.test('Alice waits for presentation request from Faber') - let aliceProofRecord = await aliceProofRecordPromise + let aliceProofExchangeRecord = await aliceProofExchangeRecordPromise testLogger.test('Alice accepts presentation request from Faber') const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, config: { filterByPresentationPreview: true, }, }) - const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { - threadId: aliceProofRecord.threadId, + const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.PresentationReceived, }) await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, proofFormats: { indy: requestedCredentials.proofFormats.indy }, }) testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise // assert presentation is valid - expect(faberProofRecord.isVerified).toBe(true) + expect(faberProofExchangeRecord.isVerified).toBe(true) - aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { - threadId: aliceProofRecord.threadId, + aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) // Faber accepts presentation - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) // Alice waits till it receives presentation ack - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise }) test('Faber starts with connection-less proof requests to Alice with auto-accept enabled', async () => { @@ -179,16 +176,16 @@ describe('Present Proof', () => { }), } - const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { state: ProofState.Done, }) - const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { + const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { state: ProofState.Done, }) // eslint-disable-next-line prefer-const - let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest({ + let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ protocolVersion: 'v2', proofFormats: { indy: { @@ -203,15 +200,15 @@ describe('Present Proof', () => { }) const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofRecord.id, + recordId: faberProofExchangeRecord.id, message, domain: 'https://a-domain.com', }) await aliceAgent.receiveMessage(requestMessage.toJSON()) - await aliceProofRecordPromise + await aliceProofExchangeRecordPromise - await faberProofRecordPromise + await faberProofExchangeRecordPromise }) test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => { @@ -343,16 +340,16 @@ describe('Present Proof', () => { }), } - const aliceProofRecordPromise = waitForProofRecordSubject(aliceReplay, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { state: ProofState.Done, }) - const faberProofRecordPromise = waitForProofRecordSubject(faberReplay, { + const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { state: ProofState.Done, }) // eslint-disable-next-line prefer-const - let { message, proofRecord: faberProofRecord } = await faberAgent.proofs.createRequest({ + let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ protocolVersion: 'v2', proofFormats: { indy: { @@ -367,7 +364,7 @@ describe('Present Proof', () => { }) const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofRecord.id, + recordId: faberProofExchangeRecord.id, message, domain: 'https://a-domain.com', }) @@ -387,8 +384,8 @@ describe('Present Proof', () => { await aliceAgent.receiveMessage(requestMessage.toJSON()) - await aliceProofRecordPromise + await aliceProofExchangeRecordPromise - await faberProofRecordPromise + await faberProofExchangeRecordPromise }) }) diff --git a/packages/core/tests/v2-indy-proofs.test.ts b/packages/core/tests/v2-indy-proofs.test.ts index 0f8a750f16..d59df461bb 100644 --- a/packages/core/tests/v2-indy-proofs.test.ts +++ b/packages/core/tests/v2-indy-proofs.test.ts @@ -1,15 +1,16 @@ -import type { Agent, ConnectionRecord, ProofRecord } from '../src' -import type { - AcceptProposalOptions, - ProposeProofOptions, - RequestProofOptions, -} from '../src/modules/proofs/ProofsApiOptions' -import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' +import type { Agent, ConnectionRecord } from '../src' +import type { AcceptProposalOptions } from '../src/modules/proofs/ProofsApiOptions' import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' -import type { V2ProofService } from '../src/modules/proofs/protocol/v2' import type { CredDefId } from 'indy-sdk' -import { AttributeFilter, PredicateType, ProofAttributeInfo, ProofPredicateInfo, ProofState } from '../src' +import { + ProofExchangeRecord, + AttributeFilter, + PredicateType, + ProofAttributeInfo, + ProofPredicateInfo, + ProofState, +} from '../src' import { getGroupKeysFromIndyProofFormatData } from '../src/modules/proofs/__tests__/groupKeys' import { V2_INDY_PRESENTATION_PROPOSAL, @@ -23,7 +24,7 @@ import { } from '../src/modules/proofs/protocol/v2/messages' import { DidCommMessageRepository } from '../src/storage/didcomm' -import { setupProofsTest, waitForProofRecord } from './helpers' +import { setupProofsTest, waitForProofExchangeRecord } from './helpers' import testLogger from './logger' describe('Present Proof', () => { @@ -32,8 +33,8 @@ describe('Present Proof', () => { let credDefId: CredDefId let aliceConnection: ConnectionRecord let faberConnection: ConnectionRecord - let faberProofRecord: ProofRecord - let aliceProofRecord: ProofRecord + let faberProofExchangeRecord: ProofExchangeRecord + let aliceProofExchangeRecord: ProofExchangeRecord let presentationPreview: PresentationPreview let didCommMessageRepository: DidCommMessageRepository @@ -55,7 +56,11 @@ describe('Present Proof', () => { // Alice sends a presentation proposal to Faber testLogger.test('Alice sends a presentation proposal to Faber') - const proposeProofOptions: ProposeProofOptions<[IndyProofFormat], [V2ProofService]> = { + let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v2', proofFormats: { @@ -67,22 +72,16 @@ describe('Present Proof', () => { predicates: presentationPreview.predicates, }, }, - } - - let faberProofRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.ProposalReceived, }) - aliceProofRecord = await aliceAgent.proofs.proposeProof(proposeProofOptions) - // Faber waits for a presentation proposal from Alice testLogger.test('Faber waits for a presentation proposal from Alice') - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V2ProposalPresentationMessage, }) @@ -105,31 +104,31 @@ describe('Present Proof', () => { ], id: expect.any(String), }) - expect(faberProofRecord.id).not.toBeNull() - expect(faberProofRecord).toMatchObject({ - threadId: faberProofRecord.threadId, + expect(faberProofExchangeRecord.id).not.toBeNull() + expect(faberProofExchangeRecord).toMatchObject({ + threadId: faberProofExchangeRecord.threadId, state: ProofState.ProposalReceived, protocolVersion: 'v2', }) const acceptProposalOptions: AcceptProposalOptions = { - proofRecordId: faberProofRecord.id, + proofRecordId: faberProofExchangeRecord.id, } - let aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) // Faber accepts the presentation proposal from Alice testLogger.test('Faber accepts presentation proposal from Alice') - faberProofRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V2RequestPresentationMessage, }) @@ -152,7 +151,7 @@ describe('Present Proof', () => { ], id: expect.any(String), thread: { - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, }, }) @@ -160,28 +159,28 @@ describe('Present Proof', () => { testLogger.test('Alice accepts presentation request from Faber') const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, config: { filterByPresentationPreview: true, }, }) - faberProofRecordPromise = waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, + faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.PresentationReceived, }) await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, proofFormats: { indy: requestedCredentials.proofFormats.indy }, }) // Faber waits for the presentation from Alice testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V2PresentationMessage, }) @@ -204,57 +203,57 @@ describe('Present Proof', () => { ], id: expect.any(String), thread: { - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, }, }) - expect(faberProofRecord.id).not.toBeNull() - expect(faberProofRecord).toMatchObject({ - threadId: faberProofRecord.threadId, + expect(faberProofExchangeRecord.id).not.toBeNull() + expect(faberProofExchangeRecord).toMatchObject({ + threadId: faberProofExchangeRecord.threadId, state: ProofState.PresentationReceived, protocolVersion: 'v2', }) - aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, + aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - expect(faberProofRecord).toMatchObject({ - // type: ProofRecord.name, + expect(faberProofExchangeRecord).toMatchObject({ + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - threadId: aliceProofRecord.threadId, + threadId: aliceProofExchangeRecord.threadId, connectionId: expect.any(String), isVerified: true, state: ProofState.PresentationReceived, }) - expect(aliceProofRecord).toMatchObject({ - // type: ProofRecord.name, + expect(aliceProofExchangeRecord).toMatchObject({ + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, connectionId: expect.any(String), state: ProofState.Done, }) - const proposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofRecord.id) - const requestMessage = await aliceAgent.proofs.findRequestMessage(aliceProofRecord.id) - const presentationMessage = await aliceAgent.proofs.findPresentationMessage(aliceProofRecord.id) + const proposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofExchangeRecord.id) + const requestMessage = await aliceAgent.proofs.findRequestMessage(aliceProofExchangeRecord.id) + const presentationMessage = await aliceAgent.proofs.findPresentationMessage(aliceProofExchangeRecord.id) expect(proposalMessage).toBeInstanceOf(V2ProposalPresentationMessage) expect(requestMessage).toBeInstanceOf(V2RequestPresentationMessage) expect(presentationMessage).toBeInstanceOf(V2PresentationMessage) - const formatData = await aliceAgent.proofs.getFormatData(aliceProofRecord.id) + const formatData = await aliceAgent.proofs.getFormatData(aliceProofExchangeRecord.id) // eslint-disable-next-line prefer-const let { proposeKey1, proposeKey2, requestKey1, requestKey2 } = getGroupKeysFromIndyProofFormatData(formatData) @@ -379,13 +378,13 @@ describe('Present Proof', () => { }), } - let aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') - faberProofRecord = await faberAgent.proofs.requestProof({ + faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v2', connectionId: faberConnection.id, proofFormats: { @@ -401,10 +400,10 @@ describe('Present Proof', () => { // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V2RequestPresentationMessage, }) @@ -428,9 +427,9 @@ describe('Present Proof', () => { id: expect.any(String), }) - expect(aliceProofRecord.id).not.toBeNull() - expect(aliceProofRecord).toMatchObject({ - threadId: aliceProofRecord.threadId, + expect(aliceProofExchangeRecord.id).not.toBeNull() + expect(aliceProofExchangeRecord).toMatchObject({ + threadId: aliceProofExchangeRecord.threadId, state: ProofState.RequestReceived, protocolVersion: 'v2', }) @@ -439,28 +438,28 @@ describe('Present Proof', () => { testLogger.test('Alice accepts presentation request from Faber') const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, config: { filterByPresentationPreview: true, }, }) - const faberProofRecordPromise = waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.PresentationReceived, }) await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofRecord.id, + proofRecordId: aliceProofExchangeRecord.id, proofFormats: { indy: requestedCredentials.proofFormats.indy }, }) // Faber waits until it receives a presentation from Alice testLogger.test('Faber waits for presentation from Alice') - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V2PresentationMessage, }) @@ -483,44 +482,44 @@ describe('Present Proof', () => { ], id: expect.any(String), thread: { - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, }, }) - expect(faberProofRecord.id).not.toBeNull() - expect(faberProofRecord).toMatchObject({ - threadId: faberProofRecord.threadId, + expect(faberProofExchangeRecord.id).not.toBeNull() + expect(faberProofExchangeRecord).toMatchObject({ + threadId: faberProofExchangeRecord.threadId, state: ProofState.PresentationReceived, protocolVersion: 'v2', }) - aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, + aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) // Faber accepts the presentation testLogger.test('Faber accept the presentation from Alice') - await faberAgent.proofs.acceptPresentation(faberProofRecord.id) + await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) // Alice waits until she receives a presentation acknowledgement testLogger.test('Alice waits for acceptance by Faber') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - expect(faberProofRecord).toMatchObject({ - // type: ProofRecord.name, + expect(faberProofExchangeRecord).toMatchObject({ + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - threadId: aliceProofRecord.threadId, + threadId: aliceProofExchangeRecord.threadId, connectionId: expect.any(String), isVerified: true, state: ProofState.PresentationReceived, }) - expect(aliceProofRecord).toMatchObject({ - // type: ProofRecord.name, + expect(aliceProofExchangeRecord).toMatchObject({ + type: ProofExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - threadId: faberProofRecord.threadId, + threadId: faberProofExchangeRecord.threadId, connectionId: expect.any(String), state: ProofState.Done, }) @@ -560,13 +559,13 @@ describe('Present Proof', () => { }), } - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') - faberProofRecord = await faberAgent.proofs.requestProof({ + faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v2', connectionId: faberConnection.id, proofFormats: { @@ -582,10 +581,10 @@ describe('Present Proof', () => { // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise const retrievedCredentials = await faberAgent.proofs.getRequestedCredentialsForProofRequest({ - proofRecordId: faberProofRecord.id, + proofRecordId: faberProofExchangeRecord.id, config: {}, }) @@ -632,13 +631,13 @@ describe('Present Proof', () => { }), } - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') - faberProofRecord = await faberAgent.proofs.requestProof({ + faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v2', connectionId: faberConnection.id, proofFormats: { @@ -654,10 +653,10 @@ describe('Present Proof', () => { // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - aliceProofRecord = await aliceProofRecordPromise + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofRecord.id, + associatedRecordId: faberProofExchangeRecord.id, messageClass: V2RequestPresentationMessage, }) @@ -681,24 +680,27 @@ describe('Present Proof', () => { id: expect.any(String), }) - expect(aliceProofRecord.id).not.toBeNull() - expect(aliceProofRecord).toMatchObject({ - threadId: aliceProofRecord.threadId, + expect(aliceProofExchangeRecord.id).not.toBeNull() + expect(aliceProofExchangeRecord).toMatchObject({ + threadId: aliceProofExchangeRecord.threadId, state: ProofState.RequestReceived, protocolVersion: 'v2', }) - const faberProofRecordPromise = waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Abandoned, }) - aliceProofRecord = await aliceAgent.proofs.sendProblemReport(aliceProofRecord.id, 'Problem inside proof request') + aliceProofExchangeRecord = await aliceAgent.proofs.sendProblemReport( + aliceProofExchangeRecord.id, + 'Problem inside proof request' + ) - faberProofRecord = await faberProofRecordPromise + faberProofExchangeRecord = await faberProofExchangeRecordPromise - expect(faberProofRecord).toMatchObject({ - threadId: aliceProofRecord.threadId, + expect(faberProofExchangeRecord).toMatchObject({ + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Abandoned, protocolVersion: 'v2', }) diff --git a/packages/core/tests/v2-proofs-auto-accept.test.ts b/packages/core/tests/v2-proofs-auto-accept.test.ts index 11c9f6f865..aa58d430d5 100644 --- a/packages/core/tests/v2-proofs-auto-accept.test.ts +++ b/packages/core/tests/v2-proofs-auto-accept.test.ts @@ -1,8 +1,5 @@ import type { Agent, ConnectionRecord } from '../src' -import type { ProposeProofOptions, RequestProofOptions } from '../src/modules/proofs/ProofsApiOptions' -import type { IndyProofFormat } from '../src/modules/proofs/formats/indy/IndyProofFormat' import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' -import type { V2ProofService } from '../src/modules/proofs/protocol/v2' import { AutoAcceptProof, @@ -13,7 +10,7 @@ import { PredicateType, } from '../src' -import { setupProofsTest, waitForProofRecord } from './helpers' +import { setupProofsTest, waitForProofExchangeRecord } from './helpers' import testLogger from './logger' describe('Auto accept present proof', () => { @@ -43,7 +40,15 @@ describe('Auto accept present proof', () => { test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `always`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - const proposeProofOptions: ProposeProofOptions<[IndyProofFormat], [V2ProofService]> = { + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.Done, + }) + + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + state: ProofState.Done, + }) + + await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v2', proofFormats: { @@ -55,23 +60,13 @@ describe('Auto accept present proof', () => { predicates: presentationPreview.predicates, }, }, - } - - const faberProofRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.Done, - }) - - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - state: ProofState.Done, }) - await aliceAgent.proofs.proposeProof(proposeProofOptions) - testLogger.test('Faber waits for presentation from Alice') - await faberProofRecordPromise + await faberProofExchangeRecordPromise testLogger.test('Alice waits till it receives presentation ack') - await aliceProofRecordPromise + await aliceProofExchangeRecordPromise }) test('Faber starts with proof requests to Alice, both with autoAcceptProof on `always`', async () => { @@ -99,11 +94,11 @@ describe('Auto accept present proof', () => { }), } - const faberProofRecordPromise = waitForProofRecord(faberAgent, { + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { state: ProofState.Done, }) - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done, }) @@ -122,9 +117,9 @@ describe('Auto accept present proof', () => { }) testLogger.test('Faber waits for presentation from Alice') - await faberProofRecordPromise + await faberProofExchangeRecordPromise // Alice waits till it receives presentation ack - await aliceProofRecordPromise + await aliceProofExchangeRecordPromise }) }) @@ -149,7 +144,11 @@ describe('Auto accept present proof', () => { test('Alice starts with proof proposal to Faber, both with autoacceptproof on `contentApproved`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - const proposal: ProposeProofOptions<[IndyProofFormat], [V2ProofService]> = { + let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v2', proofFormats: { @@ -161,35 +160,29 @@ describe('Auto accept present proof', () => { version: '1.0', }, }, - } - - let faberProofRecordPromise = waitForProofRecord(faberAgent, { - state: ProofState.ProposalReceived, }) - const aliceProofRecord = await aliceAgent.proofs.proposeProof(proposal) - testLogger.test('Faber waits for presentation proposal from Alice') - await faberProofRecordPromise + await faberProofExchangeRecordPromise testLogger.test('Faber accepts presentation proposal from Alice') - faberProofRecordPromise = waitForProofRecord(faberAgent, { - threadId: aliceProofRecord.threadId, + faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { - threadId: aliceProofRecord.threadId, + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) testLogger.test('Faber waits for presentation from Alice') - await faberProofRecordPromise + await faberProofExchangeRecordPromise // Alice waits till it receives presentation ack - await aliceProofRecordPromise + await aliceProofExchangeRecordPromise }) test('Faber starts with proof requests to Alice, both with autoacceptproof on `contentApproved`', async () => { @@ -217,11 +210,11 @@ describe('Auto accept present proof', () => { }), } - const faberProofRecordPromise = waitForProofRecord(faberAgent, { + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { state: ProofState.Done, }) - const aliceProofRecordPromise = waitForProofRecord(aliceAgent, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done, }) @@ -240,10 +233,10 @@ describe('Auto accept present proof', () => { }) testLogger.test('Faber waits for presentation from Alice') - await faberProofRecordPromise + await faberProofExchangeRecordPromise // Alice waits till it receives presentation ack - await aliceProofRecordPromise + await aliceProofExchangeRecordPromise }) }) }) From c6762bbe9d64ac5220915af3425d493e505dcc2c Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 27 Oct 2022 18:19:37 +0200 Subject: [PATCH 064/125] fix(react-native): move bbs dep to bbs package (#1076) Signed-off-by: Timo Glastra --- DEVREADME.md | 5 ---- packages/bbs-signatures/README.md | 44 ++++++++++++++++++++++++++++ packages/bbs-signatures/package.json | 8 ++++- packages/react-native/package.json | 4 +-- yarn.lock | 5 ---- 5 files changed, 52 insertions(+), 14 deletions(-) diff --git a/DEVREADME.md b/DEVREADME.md index 647e619421..cea95a96da 100644 --- a/DEVREADME.md +++ b/DEVREADME.md @@ -2,11 +2,6 @@ This file is intended for developers working on the internals of the framework. If you're just looking how to get started with the framework, see the [docs](./docs) -## Installing dependencies - -Right now, as a patch that will later be changed, some platforms will have an "error" when installing the dependencies with yarn. This is because the BBS signatures library that we use is built for Linux x86 and MacOS x86 (and not Windows and MacOS arm). This means that it will show that it could not download the binary. -This is not an error for developers, the library that fails is `node-bbs-signaturs` and is an optional dependency for perfomance improvements. It will fallback to a, slower, wasm build. - ## Running tests Test are executed using jest. Some test require either the **mediator agents** or the **ledger** to be running. When running tests that require a connection to the ledger pool, you need to set the `TEST_AGENT_PUBLIC_DID_SEED` and `GENESIS_TXN_PATH` environment variables. diff --git a/packages/bbs-signatures/README.md b/packages/bbs-signatures/README.md index 0bf81ab439..2da1dfe930 100644 --- a/packages/bbs-signatures/README.md +++ b/packages/bbs-signatures/README.md @@ -29,3 +29,47 @@
Aries Framework JavaScript BBS Module provides an optional addon to Aries Framework JavaScript to use BBS signatures in W3C VC exchange. + +## Installation + +```sh +yarn add @aries-framework/bbs-signatures +``` + +### React Native + +When using AFJ inside the React Native environment, temporarily, a dependency for creating keys, signing and verifying, with bbs keys must be swapped. Inside your `package.json` the following must be added. This is only needed for React Native environments + +#### yarn + +```diff ++ "resolutions": { ++ "@mattrglobal/bbs-signatures": "@animo-id/react-native-bbs-signatures@^0.1.0", ++ }, + "dependencies": { + ... ++ "@animo-id/react-native-bbs-signatures": "^0.1.0", + } +``` + +#### npm + +```diff ++ "overrides": { ++ "@mattrglobal/bbs-signatures": "@animo-id/react-native-bbs-signatures@^0.1.0", ++ }, + "dependencies": { + ... ++ "@animo-id/react-native-bbs-signatures": "^0.1.0", + } +``` + +The resolution field says that any instance of `@mattrglobal/bbs-signatures` in any child dependency must be swapped with `@animo-id/react-native-bbs-signatures`. + +The added dependency is required for autolinking and should be the same as the one used in the resolution. + +[React Native Bbs Signature](https://github.com/animo/react-native-bbs-signatures) has some quirks with setting it up correctly. If any errors occur while using this library, please refer to their README for the installation guide. + +### Issue with `node-bbs-signatures` + +Right now some platforms will see an "error" when installing the `@aries-framework/bbs-signatures` package. This is because the BBS signatures library that we use under the hood is built for Linux x86 and MacOS x86 (and not Windows and MacOS arm). This means that it will show that it could not download the binary. This is not an error for developers, the library that fails is `node-bbs-signatures` and is an optional dependency for performance improvements. It will fallback to a (slower) wasm build. diff --git a/packages/bbs-signatures/package.json b/packages/bbs-signatures/package.json index 16c8df6f70..8d54629716 100644 --- a/packages/bbs-signatures/package.json +++ b/packages/bbs-signatures/package.json @@ -30,12 +30,18 @@ "@stablelib/random": "^1.0.2" }, "peerDependencies": { - "@aries-framework/core": "0.2.5" + "@aries-framework/core": "0.2.5", + "@animo-id/react-native-bbs-signatures": "^0.1.0" }, "devDependencies": { "@aries-framework/node": "0.2.5", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" + }, + "peerDependenciesMeta": { + "@animo-id/react-native-bbs-signatures": { + "optional": true + } } } diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 944a1c22b3..a53d68ee31 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -29,7 +29,6 @@ "events": "^3.3.0" }, "devDependencies": { - "@animo-id/react-native-bbs-signatures": "^0.1.0", "@types/indy-sdk-react-native": "npm:@types/indy-sdk@^1.16.21", "@types/react-native": "^0.64.10", "indy-sdk-react-native": "^0.2.2", @@ -43,7 +42,6 @@ "peerDependencies": { "indy-sdk-react-native": "^0.2.2", "react-native-fs": "^2.18.0", - "react-native-get-random-values": "^1.7.0", - "@animo-id/react-native-bbs-signatures": "^0.1.0" + "react-native-get-random-values": "^1.7.0" } } diff --git a/yarn.lock b/yarn.lock index 9187c4a305..a097054007 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,11 +10,6 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@animo-id/react-native-bbs-signatures@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@animo-id/react-native-bbs-signatures/-/react-native-bbs-signatures-0.1.0.tgz#f62bc16b867c9f690977982d66f0a03566b21ad2" - integrity sha512-7qvsiWhGfUev8ngE8YzF6ON9PtCID5LiYVYM4EC5eyj80gCdhx3R46CI7K1qbqIlGsoTYQ/Xx5Ubo5Ji9eaUEA== - "@azure/core-asynciterator-polyfill@^1.0.0": version "1.0.2" resolved "https://registry.yarnpkg.com/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.2.tgz#0dd3849fb8d97f062a39db0e5cadc9ffaf861fec" From d215e84ec42cb5721b4bc7382720193bbadde3fb Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 27 Oct 2022 22:32:14 +0200 Subject: [PATCH 065/125] chore(react-native)!: update indy-sdk-react-native to 0.3.0 (#1077) Signed-off-by: Timo Glastra --- packages/react-native/package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react-native/package.json b/packages/react-native/package.json index a53d68ee31..990f78d7bb 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -31,7 +31,7 @@ "devDependencies": { "@types/indy-sdk-react-native": "npm:@types/indy-sdk@^1.16.21", "@types/react-native": "^0.64.10", - "indy-sdk-react-native": "^0.2.2", + "indy-sdk-react-native": "^0.3.0", "react": "17.0.1", "react-native": "0.64.2", "react-native-fs": "^2.18.0", @@ -40,7 +40,7 @@ "typescript": "~4.3.0" }, "peerDependencies": { - "indy-sdk-react-native": "^0.2.2", + "indy-sdk-react-native": "^0.3.0", "react-native-fs": "^2.18.0", "react-native-get-random-values": "^1.7.0" } diff --git a/yarn.lock b/yarn.lock index a097054007..49d2b136aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5951,10 +5951,10 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indy-sdk-react-native@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/indy-sdk-react-native/-/indy-sdk-react-native-0.2.2.tgz#6bd92e9444349e20d90f1461c5a44b406d1bc659" - integrity sha512-5eZEvHls18JEuaQxjycSkIYrUVDzMCBh5NunEm7RMLDIfQjjSjLOErDurzWFHbCCt8ruIjBtErSZUfjL5atoig== +indy-sdk-react-native@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/indy-sdk-react-native/-/indy-sdk-react-native-0.3.0.tgz#37b20476bf1207d3dea7b66dba65bf44ed0c903a" + integrity sha512-3qaB4R7QDNQRI9ijpSvMaow/HlZYMB2LdJlRtbhefmrjQYwpz9oSqB595NPKajBIoIxzgDaUdBkK7kmwMY90Xg== dependencies: buffer "^6.0.2" From a635565793ee57c430bcf2b57db18e6b2ceebc37 Mon Sep 17 00:00:00 2001 From: Mo <10432473+morrieinmaas@users.noreply.github.com> Date: Mon, 31 Oct 2022 12:00:13 +0100 Subject: [PATCH 066/125] docs: remove remaining docs (#1080) Signed-off-by: Moriarty --- docs/getting-started/ledger.md | 113 ------------------------- docs/getting-started/proofs.md | 147 --------------------------------- 2 files changed, 260 deletions(-) delete mode 100644 docs/getting-started/ledger.md delete mode 100644 docs/getting-started/proofs.md diff --git a/docs/getting-started/ledger.md b/docs/getting-started/ledger.md deleted file mode 100644 index a79308d53b..0000000000 --- a/docs/getting-started/ledger.md +++ /dev/null @@ -1,113 +0,0 @@ -# Ledger - -- [Configuration](#configuration) - - [Pool Selector Algorithm](#pool-selector-algorithm) - - [iOS Multiple Ledgers Troubleshooting](#ios-multiple-ledgers-troubleshooting) - -## Configuration - -Ledgers to be used by the agent can be specified in the agent configuration using the `indyLedgers` config. Only indy ledgers are supported at the moment. The `indyLedgers` property is an array of objects with the following properties. Either `genesisPath` or `genesisTransactions` must be set, but not both: - -- `id`\*: The id (or name) of the ledger, also used as the pool name -- `isProduction`\*: Whether the ledger is a production ledger. This is used by the pool selector algorithm to know which ledger to use for certain interactions (i.e. prefer production ledgers over non-production ledgers) -- `genesisPath`: The path to the genesis file to use for connecting to an Indy ledger. -- `genesisTransactions`: String of genesis transactions to use for connecting to an Indy ledger. - -```ts -const agentConfig: InitConfig = { - indyLedgers: [ - { - id: 'sovrin-main', - didIndyNamespace: 'sovrin', - isProduction: true, - genesisPath: './genesis/sovrin-main.txn', - }, - { - id: 'bcovrin-test', - didIndyNamespace: 'bcovrin:test', - isProduction: false, - genesisTransactions: 'XXXX', - }, - ], -} -``` - -### Pool Selector Algorithm - -The pool selector algorithm automatically determines which pool (network/ledger) to use for a certain operation. For **write operations**, the first pool is always used. For **read operations** the process is a bit more complicated and mostly based on [this](https://docs.google.com/document/d/109C_eMsuZnTnYe2OAd02jAts1vC4axwEKIq7_4dnNVA) google doc. - -The order of the ledgers in the `indyLedgers` configuration object matters. The pool selection algorithm works as follows: - -- When the DID is anchored on only one of the configured ledgers, use that ledger -- When the DID is anchored on multiple of the configured ledgers - - Use the first ledger (order of `indyLedgers`) with a self certified DID - - If none of the DIDs are self certified use the first production ledger (order of `indyLedgers` with `isProduction: true`) - - If none of the DIDs are self certified or come from production ledgers, use the first non production ledger (order of `indyLedgers` with `isProduction: false`) -- When the DID is not anchored on any of the configured ledgers, a `LedgerNotFoundError` will be thrown. - -### iOS Multiple Ledgers Troubleshooting - -With having multiple ledgers, you can run into issues relating to credential issuance or other ledger operation in iOS release environments (as seen in [#647](https://github.com/hyperledger/aries-framework-javascript/issues/647)). This can appear as a soft crash preventing usage of the ledger. If you're able to get the logs, they might look similar to the following: - -``` -Undefined error: 0 -thread '' panicked at 'FIXME: IndyError { inner: Too many open files - -IO error }', src/libcore/result.rs:1165:5 -``` - -This issue results as too many files/resources have been opened in the process of connecting to the ledgers. IOS defaults the limit to 256 (rlimit). This is likely something that can and should be addressed in indy-sdk or indy-vdr in the future. - -#### Reduce Configured Ledgers - -This issue is specifically tied to the number of ledgers configured, and can be addressed by reducing the number of ledgers configured. - -#### Increase Open Files Limit - -In your apps `main.m`, you can add the following to log and increase the rlimit (if it's less than the `NEW_SOFT_LIMIT`, in this case, 1024): - -```main.m - struct rlimit rlim; - unsigned long long NEW_SOFT_LIMIT = 1024; - - //Fetch existing file limits, adjust file limits if possible - if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) { - NSLog(@"Current soft RLimit: %llu", rlim.rlim_cur); - NSLog(@"Current hard RLimit: %llu", rlim.rlim_max); - - // Adjust only if the limit is less than NEW_SOFT_LIMIT - if(rlim.rlim_cur < NEW_SOFT_LIMIT){ - rlim.rlim_cur = NEW_SOFT_LIMIT; - } - - if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) { - NSLog(@"Can't set RLimits"); - } - } else { - NSLog(@"Can't fetch RLimits"); - } - - // Re-fetch file limits - if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) { - NSLog(@"New soft RLimit: %llu", rlim.rlim_cur); - NSLog(@"New hard RLimit: %llu", rlim.rlim_max); - } else { - NSLog(@"Can't fetch RLimits"); - } -``` - -Once run, the logs will look like: - -``` -2022-05-24 15:46:32.256188-0600 AriesBifold[731:288330] Current soft RLimit: 256 -2022-05-24 15:46:32.256343-0600 AriesBifold[731:288330] Current hard RLimit: 9223372036854775807 -2022-05-24 15:46:32.256358-0600 AriesBifold[731:288330] New soft RLimit: 1024 -2022-05-24 15:46:32.256369-0600 AriesBifold[731:288330] New hard RLimit: 9223372036854775807 -``` - -Example main.m file with the above changes: https://github.com/hyperledger/aries-mobile-agent-react-native/commit/b420d1df5c4bf236969aafad1e46000111fe30d5 - -Helpful resources: - -- https://pubs.opengroup.org/onlinepubs/009695399/functions/getrlimit.html -- https://stackoverflow.com/a/62074374 diff --git a/docs/getting-started/proofs.md b/docs/getting-started/proofs.md deleted file mode 100644 index 82422ea69f..0000000000 --- a/docs/getting-started/proofs.md +++ /dev/null @@ -1,147 +0,0 @@ -# Proofs - -As mentioned in the previous documentation ([Credentials](5-credentials.md)), after receiving a credential and saving it to your wallet, you will need to show it to a verifier who will verify the authenticity of this credential and that the credential assertions are not tampered with. - -In VC proofs, we have two involved parties: - -- Holder (prover) -- Verifier - -The process for proving your VC starts by a verifier to request a presentation from a prover, and for the prover to respond by presenting a proof to the verifier or the prover to send a presentation proposal to the verifier. - -## Method 1 - Prover (holder) responds to presentation request from the verifier - -> Note: This setup is assumed for a react native mobile agent - -> Note: This process assumes there is an established connection between the prover and the verifier - -## Full Example Code - -```ts -const handleProofStateChange = async (event: ProofStateChangedEvent) => { - const proofRecord = event.payload.proofRecord - // previous state -> presentation-sent new state: done - if (event.payload.previousState === ProofState.PresentationSent && proofRecord.state === ProofState.Done) { - Alert.alert('Credential Proved!') - return - } - if (proofRecord.state === ProofState.RequestReceived) { - const proofRequest = proofRecord.requestMessage?.indyProofRequest - - //Retrieve credentials - const retrievedCredentials = await agent.proofs.getRequestedCredentialsForProofRequest(proofRecord.id, { - filterByPresentationPreview: true, - }) - - const requestedCredentials = agent.proofs.autoSelectCredentialsForProofRequest(retrievedCredentials) - agent.proofs.acceptRequest(event.payload.proofRecord.id, requestedCredentials) - } -} -``` - -### 1. Configure agent - -Please make sure you reviewed the [agent setup overview](0-agent.md). - -### 2. Configure proof events handler - -This handler will be triggered whenever there is a Proof state change. - -```ts -const handleProofStateChange = async (agent: Agent, event: ProofStateChangedEvent) => { - console.log( - `>> Proof state changed: ${event.payload.proofRecord.id}, previous state -> ${event.payload.previousState} new state: ${event.payload.proofRecord.state}` - ) - - if (event.payload.proofRecord.state === ProofState.RequestReceived) { - const retrievedCredentials = await agent.proofs.getRequestedCredentialsForProofRequest( - event.payload.proofRecord.id, - { - filterByPresentationPreview: true, - } - ) - - const requestedCredentials = agent.proofs.autoSelectCredentialsForProofRequest(retrievedCredentials) - - agent.proofs.acceptRequest(event.payload.proofRecord.id, requestedCredentials) - } -} -``` - -- `filterByPresentationPreview`: Whether to filter the retrieved credentials using the presentation preview. This configuration will only have effect if a presentation proposal message is available containing a presentation preview.. - -Make sure to add the event listener to the agent after initializing the wallet - -```ts -agent.events.on(ProofEventTypes.ProofStateChanged, handleProofStateChange) -``` - -## Manually accepting proof request - -```ts -const handleProofStateChange = async (event: ProofStateChangedEvent) => { - .. - - //Construct pop up message - var message = '>> Proof Request Recieved <<\n'; - message += `To prove:${proofRequest?.name}\n`; - message += 'Attributes to prove:\n'; - - //Loop through requested attributes - Object.values(proofRequest.requestedAttributes).forEach(attr => { - message += `${attr.name}\n`; - }); - - message += `Accept proof request?`; - Alert.alert('Attention!', message, [ - { - text: 'Accept', - onPress: () => { - agent.proofs.acceptRequest(event.payload.proofRecord.id, - requestedCredentials, - ); - }, - }, - { - text: 'Reject', - onPress: () => { - //User rejected - }, - }, - ]); - } - }; -``` - -By sending the response to the verifier, the verifier will go through the process of verifying the VC and respond with an ack message. - -To give some context to the user you can add the following code to the Proof event handler - -```ts -const handleProofStateChange = async (agent: Agent, event: ProofStateChangedEvent) => { - ... - if ( - event.payload.previousState === ProofState.PresentationSent && - event.payload.proofRecord.state === ProofState.Done - ) { - console.log('Done proving credentials'); - Alert.alert('Credential Proved!'); - return; - } - .... - }; -``` - -## Method 2 - Prover sends a presentation proposal to verifier - -> To do - -## Connectionless Proof Request - -> To do - -## References - -- [Verifiable credentials model](https://www.w3.org/TR/vc-data-model/). -- [Present Proof Protocol 1.0](https://github.com/hyperledger/aries-rfcs/blob/main/features/0037-present-proof/README.md). -- [Present Proof Protocol 2.0](https://github.com/hyperledger/aries-rfcs/blob/main/features/0454-present-proof-v2/README.md). From 7850a27ecde9766a4b2031521a8ca35ca683d6ea Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Mon, 31 Oct 2022 08:47:54 -0300 Subject: [PATCH 067/125] refactor(proofs)!: make nonce optional and add missing exports (#1073) Signed-off-by: Ariel Gentile --- .../src/modules/credentials/CredentialsApi.ts | 62 +++++----- .../credentials/CredentialsApiOptions.ts | 40 +++--- .../formats/CredentialFormatService.ts | 11 +- .../formats/CredentialFormatServiceOptions.ts | 12 +- .../indy/IndyCredentialFormatService.ts | 8 +- .../v1-connectionless-credentials.e2e.test.ts | 10 +- .../v1-credentials-auto-accept.e2e.test.ts | 6 +- .../protocol/v2/V2CredentialService.ts | 4 +- .../v2-connectionless-credentials.e2e.test.ts | 10 +- .../v2-credentials-auto-accept.e2e.test.ts | 6 +- .../discover-features/DiscoverFeaturesApi.ts | 10 +- .../DiscoverFeaturesApiOptions.ts | 12 +- .../core/src/modules/proofs/ProofService.ts | 4 - packages/core/src/modules/proofs/ProofsApi.ts | 36 +++--- .../src/modules/proofs/ProofsApiOptions.ts | 24 ++-- .../formats/ProofFormatServiceOptions.ts | 10 +- .../core/src/modules/proofs/formats/index.ts | 6 + .../proofs/formats/indy/IndyProofFormat.ts | 26 +--- .../formats/indy/IndyProofFormatService.ts | 113 +++++++++++++++-- .../indy/IndyProofFormatsServiceOptions.ts | 6 +- .../proofs/formats/indy/IndyProofUtils.ts | 115 ------------------ .../proofs/formats/indy/errors/index.ts | 2 + .../src/modules/proofs/formats/indy/index.ts | 4 + .../proofs/formats/indy/models/index.ts | 8 +- .../modules/proofs/formats/models/index.ts | 2 + packages/core/src/modules/proofs/index.ts | 15 ++- .../core/src/modules/proofs/messages/index.ts | 1 + .../core/src/modules/proofs/protocol/index.ts | 2 + .../proofs/protocol/v1/V1ProofService.ts | 19 ++- .../v1/__tests__/indy-proof-request.test.ts | 4 +- .../handlers/V1ProposePresentationHandler.ts | 2 +- .../src/modules/proofs/protocol/v1/index.ts | 3 + .../proofs/protocol/v1/messages/index.ts | 1 + .../proofs/protocol/v1/models/index.ts | 1 + .../proofs/protocol/v2/V2ProofService.ts | 12 +- .../__tests__/indy-proof-presentation.test.ts | 4 +- .../v2/__tests__/indy-proof-request.test.ts | 4 +- .../src/modules/proofs/protocol/v2/index.ts | 2 + packages/core/src/wallet/WalletApi.ts | 4 + packages/core/tests/helpers.ts | 6 +- packages/core/tests/v1-indy-proofs.test.ts | 8 +- packages/core/tests/v2-indy-proofs.test.ts | 9 +- 42 files changed, 323 insertions(+), 321 deletions(-) create mode 100644 packages/core/src/modules/proofs/formats/index.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/IndyProofUtils.ts create mode 100644 packages/core/src/modules/proofs/formats/indy/errors/index.ts create mode 100644 packages/core/src/modules/proofs/formats/indy/index.ts create mode 100644 packages/core/src/modules/proofs/formats/models/index.ts create mode 100644 packages/core/src/modules/proofs/messages/index.ts create mode 100644 packages/core/src/modules/proofs/protocol/index.ts diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index d95933780d..110b24867b 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -3,21 +3,21 @@ import type { Query } from '../../storage/StorageService' import type { DeleteCredentialOptions } from './CredentialServiceOptions' import type { AcceptCredentialOptions, - AcceptOfferOptions, - AcceptProposalOptions, - AcceptRequestOptions, + AcceptCredentialOfferOptions, + AcceptCredentialProposalOptions, + AcceptCredentialRequestOptions, CreateOfferOptions, FindCredentialMessageReturn, - FindOfferMessageReturn, - FindProposalMessageReturn, - FindRequestMessageReturn, + FindCredentialOfferMessageReturn, + FindCredentialProposalMessageReturn, + FindCredentialRequestMessageReturn, GetFormatDataReturn, - NegotiateOfferOptions, - NegotiateProposalOptions, + NegotiateCredentialOfferOptions, + NegotiateCredentialProposalOptions, OfferCredentialOptions, ProposeCredentialOptions, - SendProblemReportOptions, - ServiceMap, + SendCredentialProblemReportOptions, + CredentialServiceMap, } from './CredentialsApiOptions' import type { CredentialFormat } from './formats' import type { IndyCredentialFormat } from './formats/indy/IndyCredentialFormat' @@ -47,21 +47,21 @@ import { CredentialRepository } from './repository/CredentialRepository' export interface CredentialsApi[]> { // Propose Credential methods proposeCredential(options: ProposeCredentialOptions): Promise - acceptProposal(options: AcceptProposalOptions): Promise - negotiateProposal(options: NegotiateProposalOptions): Promise + acceptProposal(options: AcceptCredentialProposalOptions): Promise + negotiateProposal(options: NegotiateCredentialProposalOptions): Promise // Offer Credential Methods offerCredential(options: OfferCredentialOptions): Promise - acceptOffer(options: AcceptOfferOptions): Promise + acceptOffer(options: AcceptCredentialOfferOptions): Promise declineOffer(credentialRecordId: string): Promise - negotiateOffer(options: NegotiateOfferOptions): Promise + negotiateOffer(options: NegotiateCredentialOfferOptions): Promise // Request Credential Methods // This is for beginning the exchange with a request (no proposal or offer). Only possible // (currently) with W3C. We will not implement this in phase I // when the issuer accepts the request he issues the credential to the holder - acceptRequest(options: AcceptRequestOptions): Promise + acceptRequest(options: AcceptCredentialRequestOptions): Promise // Issue Credential Methods acceptCredential(options: AcceptCredentialOptions): Promise @@ -72,7 +72,7 @@ export interface CredentialsApi - sendProblemReport(options: SendProblemReportOptions): Promise + sendProblemReport(options: SendCredentialProblemReportOptions): Promise // Record Methods getAll(): Promise @@ -84,9 +84,9 @@ export interface CredentialsApi> // DidComm Message Records - findProposalMessage(credentialExchangeId: string): Promise> - findOfferMessage(credentialExchangeId: string): Promise> - findRequestMessage(credentialExchangeId: string): Promise> + findProposalMessage(credentialExchangeId: string): Promise> + findOfferMessage(credentialExchangeId: string): Promise> + findRequestMessage(credentialExchangeId: string): Promise> findCredentialMessage(credentialExchangeId: string): Promise> } @@ -108,7 +108,7 @@ export class CredentialsApi< private didCommMessageRepository: DidCommMessageRepository private routingService: RoutingService private logger: Logger - private serviceMap: ServiceMap + private serviceMap: CredentialServiceMap public constructor( messageSender: MessageSender, @@ -141,7 +141,7 @@ export class CredentialsApi< [service.version]: service, }), {} - ) as ServiceMap + ) as CredentialServiceMap this.logger.debug(`Initializing Credentials Module for agent ${this.agentContext.config.label}`) } @@ -195,7 +195,7 @@ export class CredentialsApi< * @returns Credential exchange record associated with the credential offer * */ - public async acceptProposal(options: AcceptProposalOptions): Promise { + public async acceptProposal(options: AcceptCredentialProposalOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) if (!credentialRecord.connectionId) { @@ -227,11 +227,11 @@ export class CredentialsApi< * Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection * associated with the credential record. * - * @param options configuration for the offer see {@link NegotiateProposalOptions} + * @param options configuration for the offer see {@link NegotiateCredentialProposalOptions} * @returns Credential exchange record associated with the credential offer * */ - public async negotiateProposal(options: NegotiateProposalOptions): Promise { + public async negotiateProposal(options: NegotiateCredentialProposalOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) if (!credentialRecord.connectionId) { @@ -291,7 +291,7 @@ export class CredentialsApi< * @param options The object containing config options of the offer to be accepted * @returns Object containing offer associated credential record */ - public async acceptOffer(options: AcceptOfferOptions): Promise { + public async acceptOffer(options: AcceptCredentialOfferOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) const service = this.getService(credentialRecord.protocolVersion) @@ -369,7 +369,7 @@ export class CredentialsApi< return credentialRecord } - public async negotiateOffer(options: NegotiateOfferOptions): Promise { + public async negotiateOffer(options: NegotiateCredentialOfferOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) const service = this.getService(credentialRecord.protocolVersion) @@ -424,7 +424,7 @@ export class CredentialsApi< * @param options The object containing config options of the request * @returns CredentialExchangeRecord updated with information pertaining to this request */ - public async acceptRequest(options: AcceptRequestOptions): Promise { + public async acceptRequest(options: AcceptCredentialRequestOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) // with version we can get the Service @@ -539,7 +539,7 @@ export class CredentialsApi< * @param message message to send * @returns credential record associated with the credential problem report message */ - public async sendProblemReport(options: SendProblemReportOptions) { + public async sendProblemReport(options: SendCredentialProblemReportOptions) { const credentialRecord = await this.getById(options.credentialRecordId) if (!credentialRecord.connectionId) { throw new AriesFrameworkError(`No connectionId found for credential record '${credentialRecord.id}'.`) @@ -625,19 +625,19 @@ export class CredentialsApi< await this.credentialRepository.update(this.agentContext, credentialRecord) } - public async findProposalMessage(credentialExchangeId: string): Promise> { + public async findProposalMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) return service.findProposalMessage(this.agentContext, credentialExchangeId) } - public async findOfferMessage(credentialExchangeId: string): Promise> { + public async findOfferMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) return service.findOfferMessage(this.agentContext, credentialExchangeId) } - public async findRequestMessage(credentialExchangeId: string): Promise> { + public async findRequestMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) return service.findRequestMessage(this.agentContext, credentialExchangeId) diff --git a/packages/core/src/modules/credentials/CredentialsApiOptions.ts b/packages/core/src/modules/credentials/CredentialsApiOptions.ts index 8dc345bcae..86059ca443 100644 --- a/packages/core/src/modules/credentials/CredentialsApiOptions.ts +++ b/packages/core/src/modules/credentials/CredentialsApiOptions.ts @@ -6,9 +6,15 @@ import type { CredentialService } from './services' // re-export GetFormatDataReturn type from service, as it is also used in the module export type { GetFormatDataReturn } -export type FindProposalMessageReturn = ReturnType -export type FindOfferMessageReturn = ReturnType -export type FindRequestMessageReturn = ReturnType +export type FindCredentialProposalMessageReturn = ReturnType< + CSs[number]['findProposalMessage'] +> +export type FindCredentialOfferMessageReturn = ReturnType< + CSs[number]['findOfferMessage'] +> +export type FindCredentialRequestMessageReturn = ReturnType< + CSs[number]['findRequestMessage'] +> export type FindCredentialMessageReturn = ReturnType< CSs[number]['findCredentialMessage'] > @@ -16,22 +22,22 @@ export type FindCredentialMessageReturn = Retur /** * Get the supported protocol versions based on the provided credential services. */ -export type ProtocolVersionType = CSs[number]['version'] +export type CredentialProtocolVersionType = CSs[number]['version'] /** * Get the service map for usage in the credentials module. Will return a type mapping of protocol version to service. * * @example * ``` - * type CredentialServiceMap = ServiceMap<[IndyCredentialFormat], [V1CredentialService]> + * type ServiceMap = CredentialServiceMap<[IndyCredentialFormat], [V1CredentialService]> * * // equal to - * type CredentialServiceMap = { + * type ServiceMap = { * v1: V1CredentialService * } * ``` */ -export type ServiceMap[]> = { +export type CredentialServiceMap[]> = { [CS in CSs[number] as CS['version']]: CredentialService } @@ -48,7 +54,7 @@ export interface ProposeCredentialOptions< CSs extends CredentialService[] = CredentialService[] > extends BaseOptions { connectionId: string - protocolVersion: ProtocolVersionType + protocolVersion: CredentialProtocolVersionType credentialFormats: CredentialFormatPayload } @@ -57,7 +63,8 @@ export interface ProposeCredentialOptions< * * credentialFormats is optional because this is an accept method */ -export interface AcceptProposalOptions extends BaseOptions { +export interface AcceptCredentialProposalOptions + extends BaseOptions { credentialRecordId: string credentialFormats?: CredentialFormatPayload } @@ -65,7 +72,8 @@ export interface AcceptProposalOptions extends BaseOptions { +export interface NegotiateCredentialProposalOptions + extends BaseOptions { credentialRecordId: string credentialFormats: CredentialFormatPayload } @@ -77,7 +85,7 @@ export interface CreateOfferOptions< CFs extends CredentialFormat[] = CredentialFormat[], CSs extends CredentialService[] = CredentialService[] > extends BaseOptions { - protocolVersion: ProtocolVersionType + protocolVersion: CredentialProtocolVersionType credentialFormats: CredentialFormatPayload } @@ -97,7 +105,7 @@ export interface OfferCredentialOptions< * * credentialFormats is optional because this is an accept method */ -export interface AcceptOfferOptions extends BaseOptions { +export interface AcceptCredentialOfferOptions extends BaseOptions { credentialRecordId: string credentialFormats?: CredentialFormatPayload } @@ -105,7 +113,8 @@ export interface AcceptOfferOptions extends BaseOptions { +export interface NegotiateCredentialOfferOptions + extends BaseOptions { credentialRecordId: string credentialFormats: CredentialFormatPayload } @@ -115,7 +124,8 @@ export interface NegotiateOfferOptions extends BaseOptions { +export interface AcceptCredentialRequestOptions + extends BaseOptions { credentialRecordId: string credentialFormats?: CredentialFormatPayload autoAcceptCredential?: AutoAcceptCredential @@ -132,7 +142,7 @@ export interface AcceptCredentialOptions { /** * Interface for CredentialsApi.sendProblemReport. Will send a problem-report message */ -export interface SendProblemReportOptions { +export interface SendCredentialProblemReportOptions { credentialRecordId: string message: string } diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts index bf3c842d29..68a8d5ab8d 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts @@ -7,7 +7,7 @@ import type { FormatCreateOfferOptions, FormatCreateOfferReturn, FormatCreateRequestOptions, - FormatCreateReturn, + CredentialFormatCreateReturn, FormatAcceptRequestOptions, FormatAcceptOfferOptions, FormatAcceptProposalOptions, @@ -41,18 +41,21 @@ export abstract class CredentialFormatService ): Promise abstract processOffer(agentContext: AgentContext, options: FormatProcessOptions): Promise - abstract acceptOffer(agentContext: AgentContext, options: FormatAcceptOfferOptions): Promise + abstract acceptOffer( + agentContext: AgentContext, + options: FormatAcceptOfferOptions + ): Promise // request methods abstract createRequest( agentContext: AgentContext, options: FormatCreateRequestOptions - ): Promise + ): Promise abstract processRequest(agentContext: AgentContext, options: FormatProcessOptions): Promise abstract acceptRequest( agentContext: AgentContext, options: FormatAcceptRequestOptions - ): Promise + ): Promise // credential methods abstract processCredential(agentContext: AgentContext, options: FormatProcessOptions): Promise diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts index e68e6c41ae..1a6cc4db11 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts @@ -10,15 +10,15 @@ import type { CredentialFormatService } from './CredentialFormatService' * * @example * ``` - * type CredentialFormatServiceMap = FormatServiceMap<[IndyCredentialFormat]> + * type FormatServiceMap = CredentialFormatServiceMap<[IndyCredentialFormat]> * * // equal to - * type CredentialFormatServiceMap = { + * type FormatServiceMap = { * indy: CredentialFormatService * } * ``` */ -export type FormatServiceMap = { +export type CredentialFormatServiceMap = { [CF in CFs[number] as CF['formatKey']]: CredentialFormatService } @@ -27,7 +27,7 @@ export type FormatServiceMap = { * * It requires an attachment and a format to be returned. */ -export interface FormatCreateReturn { +export interface CredentialFormatCreateReturn { format: CredentialFormatSpec attachment: Attachment } @@ -53,7 +53,7 @@ export interface FormatAcceptProposalOptions { proposalAttachment: Attachment } -export interface FormatCreateProposalReturn extends FormatCreateReturn { +export interface FormatCreateProposalReturn extends CredentialFormatCreateReturn { previewAttributes?: CredentialPreviewAttribute[] } @@ -71,7 +71,7 @@ export interface FormatAcceptOfferOptions { offerAttachment: Attachment } -export interface FormatCreateOfferReturn extends FormatCreateReturn { +export interface FormatCreateOfferReturn extends CredentialFormatCreateReturn { previewAttributes?: CredentialPreviewAttribute[] } diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts index 2a29055c1d..4847cf9fd7 100644 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts @@ -15,7 +15,7 @@ import type { FormatCreateOfferReturn, FormatCreateProposalOptions, FormatCreateProposalReturn, - FormatCreateReturn, + CredentialFormatCreateReturn, FormatProcessOptions, } from '../CredentialFormatServiceOptions' import type { IndyCredentialFormat } from './IndyCredentialFormat' @@ -213,7 +213,7 @@ export class IndyCredentialFormatService extends CredentialFormatService - ): Promise { + ): Promise { const indyFormat = credentialFormats?.indy const holderDid = indyFormat?.holderDid ?? (await this.getIndyHolderDid(agentContext, credentialRecord)) @@ -251,7 +251,7 @@ export class IndyCredentialFormatService extends CredentialFormatService { + public async createRequest(): Promise { throw new AriesFrameworkError('Starting from a request is not supported for indy credentials') } @@ -266,7 +266,7 @@ export class IndyCredentialFormatService extends CredentialFormatService - ): Promise { + ): Promise { // Assert credential attributes const credentialAttributes = credentialRecord.credentialAttributes if (!credentialAttributes) { diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts index 085142c8ea..b51d3cb45c 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts @@ -1,6 +1,10 @@ import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { AcceptOfferOptions, AcceptRequestOptions, CreateOfferOptions } from '../../../CredentialsApiOptions' +import type { + AcceptCredentialOfferOptions, + AcceptCredentialRequestOptions, + CreateOfferOptions, +} from '../../../CredentialsApiOptions' import { ReplaySubject, Subject } from 'rxjs' @@ -104,7 +108,7 @@ describe('V1 Connectionless Credentials', () => { }) testLogger.test('Alice sends credential request to Faber') - const acceptOfferOptions: AcceptOfferOptions = { + const acceptOfferOptions: AcceptCredentialOfferOptions = { credentialRecordId: aliceCredentialRecord.id, } const credentialRecord = await aliceAgent.credentials.acceptOffer(acceptOfferOptions) @@ -116,7 +120,7 @@ describe('V1 Connectionless Credentials', () => { }) testLogger.test('Faber sends credential to Alice') - const options: AcceptRequestOptions = { + const options: AcceptCredentialRequestOptions = { credentialRecordId: faberCredentialRecord.id, comment: 'V1 Indy Credential', } diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts index 75cbdcae29..5201f2464b 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts @@ -1,6 +1,6 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections' -import type { AcceptOfferOptions, AcceptProposalOptions } from '../../../CredentialsApiOptions' +import type { AcceptCredentialOfferOptions, AcceptCredentialProposalOptions } from '../../../CredentialsApiOptions' import type { Schema } from 'indy-sdk' import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' @@ -188,7 +188,7 @@ describe('credentials', () => { state: CredentialState.ProposalReceived, }) - const options: AcceptProposalOptions = { + const options: AcceptCredentialProposalOptions = { credentialRecordId: faberCredentialExchangeRecord.id, comment: 'V1 Indy Offer', credentialFormats: { @@ -290,7 +290,7 @@ describe('credentials', () => { }) if (aliceCredentialExchangeRecord.connectionId) { - const acceptOfferOptions: AcceptOfferOptions = { + const acceptOfferOptions: AcceptCredentialOfferOptions = { credentialRecordId: aliceCredentialExchangeRecord.id, } testLogger.test('alice sends credential request to faber') diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts index 4f6ce51440..9ce5a7166b 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts @@ -22,7 +22,7 @@ import type { CredentialFormat, CredentialFormatPayload, CredentialFormatService, - FormatServiceMap, + CredentialFormatServiceMap, } from '../../formats' import type { CredentialFormatSpec } from '../../models' @@ -96,7 +96,7 @@ export class V2CredentialService + ) as CredentialFormatServiceMap this.registerHandlers() } diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts index 0382f05f1c..ad2c6431d2 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts @@ -1,6 +1,10 @@ import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { AcceptOfferOptions, AcceptRequestOptions, CreateOfferOptions } from '../../../CredentialsApiOptions' +import type { + AcceptCredentialOfferOptions, + AcceptCredentialRequestOptions, + CreateOfferOptions, +} from '../../../CredentialsApiOptions' import { ReplaySubject, Subject } from 'rxjs' @@ -104,7 +108,7 @@ describe('V2 Connectionless Credentials', () => { }) testLogger.test('Alice sends credential request to Faber') - const acceptOfferOptions: AcceptOfferOptions = { + const acceptOfferOptions: AcceptCredentialOfferOptions = { credentialRecordId: aliceCredentialRecord.id, } const credentialRecord = await aliceAgent.credentials.acceptOffer(acceptOfferOptions) @@ -116,7 +120,7 @@ describe('V2 Connectionless Credentials', () => { }) testLogger.test('Faber sends credential to Alice') - const options: AcceptRequestOptions = { + const options: AcceptCredentialRequestOptions = { credentialRecordId: faberCredentialRecord.id, comment: 'V2 Indy Credential', } diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts index 6486f445d4..19bcf6b6f0 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts @@ -1,6 +1,6 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections' -import type { AcceptOfferOptions, AcceptProposalOptions } from '../../../CredentialsApiOptions' +import type { AcceptCredentialOfferOptions, AcceptCredentialProposalOptions } from '../../../CredentialsApiOptions' import type { Schema } from 'indy-sdk' import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' @@ -204,7 +204,7 @@ describe('v2 credentials', () => { state: CredentialState.Done, }) - const options: AcceptProposalOptions = { + const options: AcceptCredentialProposalOptions = { credentialRecordId: faberPropReceivedRecord.id, comment: 'V2 Indy Offer', credentialFormats: { @@ -308,7 +308,7 @@ describe('v2 credentials', () => { state: CredentialState.Done, }) - const acceptOfferOptions: AcceptOfferOptions = { + const acceptOfferOptions: AcceptCredentialOfferOptions = { credentialRecordId: aliceOfferReceivedRecord.id, } testLogger.test('alice sends credential request to faber') diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts index 1f49e65624..faa198b1de 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts @@ -1,5 +1,9 @@ import type { Feature } from '../../agent/models' -import type { DiscloseFeaturesOptions, QueryFeaturesOptions, ServiceMap } from './DiscoverFeaturesApiOptions' +import type { + DiscloseFeaturesOptions, + QueryFeaturesOptions, + DiscoverFeaturesServiceMap, +} from './DiscoverFeaturesApiOptions' import type { DiscoverFeaturesDisclosureReceivedEvent } from './DiscoverFeaturesEvents' import type { DiscoverFeaturesService } from './services' @@ -42,7 +46,7 @@ export class DiscoverFeaturesApi< private eventEmitter: EventEmitter private stop$: Subject private agentContext: AgentContext - private serviceMap: ServiceMap + private serviceMap: DiscoverFeaturesServiceMap public constructor( connectionService: ConnectionService, @@ -68,7 +72,7 @@ export class DiscoverFeaturesApi< [service.version]: service, }), {} - ) as ServiceMap + ) as DiscoverFeaturesServiceMap } public getService(protocolVersion: PVT): DiscoverFeaturesService { diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts index 5cd0b88d38..7cdcc18cb4 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts @@ -4,23 +4,23 @@ import type { DiscoverFeaturesService } from './services' /** * Get the supported protocol versions based on the provided discover features services. */ -export type ProtocolVersionType = DFSs[number]['version'] +export type DiscoverFeaturesProtocolVersionType = DFSs[number]['version'] /** * Get the service map for usage in the discover features module. Will return a type mapping of protocol version to service. * * @example * ``` - * type DiscoverFeaturesServiceMap = ServiceMap<[V1DiscoverFeaturesService,V2DiscoverFeaturesService]> + * type ServiceMap = DiscoverFeaturesServiceMap<[V1DiscoverFeaturesService,V2DiscoverFeaturesService]> * * // equal to - * type DiscoverFeaturesServiceMap = { + * type ServiceMap = { * v1: V1DiscoverFeatureService * v2: V2DiscoverFeaturesService * } * ``` */ -export type ServiceMap = { +export type DiscoverFeaturesServiceMap = { [DFS in DFSs[number] as DFS['version']]: DiscoverFeaturesService } @@ -30,7 +30,7 @@ interface BaseOptions { export interface QueryFeaturesOptions extends BaseOptions { - protocolVersion: ProtocolVersionType + protocolVersion: DiscoverFeaturesProtocolVersionType queries: FeatureQueryOptions[] awaitDisclosures?: boolean awaitDisclosuresTimeoutMs?: number @@ -39,7 +39,7 @@ export interface QueryFeaturesOptions extends BaseOptions { - protocolVersion: ProtocolVersionType + protocolVersion: DiscoverFeaturesProtocolVersionType disclosureQueries: FeatureQueryOptions[] threadId?: string } diff --git a/packages/core/src/modules/proofs/ProofService.ts b/packages/core/src/modules/proofs/ProofService.ts index 9fcd016b19..c0735a9718 100644 --- a/packages/core/src/modules/proofs/ProofService.ts +++ b/packages/core/src/modules/proofs/ProofService.ts @@ -60,10 +60,6 @@ export abstract class ProofService { } abstract readonly version: string - public async generateProofRequestNonce() { - return await this.wallet.generateNonce() - } - public emitStateChangedEvent( agentContext: AgentContext, proofRecord: ProofExchangeRecord, diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index ce7acf47a8..3e337b7ee8 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -2,15 +2,15 @@ import type { AgentMessage } from '../../agent/AgentMessage' import type { Query } from '../../storage/StorageService' import type { ProofService } from './ProofService' import type { - AcceptPresentationOptions, - AcceptProposalOptions, + AcceptProofPresentationOptions, + AcceptProofProposalOptions, CreateProofRequestOptions, - FindPresentationMessageReturn, - FindProposalMessageReturn, - FindRequestMessageReturn, + FindProofPresentationMessageReturn, + FindProofProposalMessageReturn, + FindProofRequestMessageReturn, ProposeProofOptions, RequestProofOptions, - ServiceMap, + ProofServiceMap, } from './ProofsApiOptions' import type { ProofFormat } from './formats/ProofFormat' import type { IndyProofFormat } from './formats/indy/IndyProofFormat' @@ -56,11 +56,11 @@ import { ProofRepository } from './repository/ProofRepository' export interface ProofsApi[]> { // Proposal methods proposeProof(options: ProposeProofOptions): Promise - acceptProposal(options: AcceptProposalOptions): Promise + acceptProposal(options: AcceptProofProposalOptions): Promise // Request methods requestProof(options: RequestProofOptions): Promise - acceptRequest(options: AcceptPresentationOptions): Promise + acceptRequest(options: AcceptProofPresentationOptions): Promise declineRequest(proofRecordId: string): Promise // Present @@ -94,9 +94,9 @@ export interface ProofsApi> // DidComm Message Records - findProposalMessage(proofRecordId: string): Promise> - findRequestMessage(proofRecordId: string): Promise> - findPresentationMessage(proofRecordId: string): Promise> + findProposalMessage(proofRecordId: string): Promise> + findRequestMessage(proofRecordId: string): Promise> + findPresentationMessage(proofRecordId: string): Promise> } @injectable() @@ -112,7 +112,7 @@ export class ProofsApi< private agentContext: AgentContext private agentConfig: AgentConfig private logger: Logger - private serviceMap: ServiceMap + private serviceMap: ProofServiceMap public constructor( dispatcher: Dispatcher, @@ -141,7 +141,7 @@ export class ProofsApi< [service.version]: service, }), {} - ) as ServiceMap + ) as ProofServiceMap this.logger.debug(`Initializing Proofs Module for agent ${this.agentContext.config.label}`) @@ -198,7 +198,7 @@ export class ProofsApi< * @param options multiple properties like proof record id, additional configuration for creating the request * @returns Proof record associated with the presentation request */ - public async acceptProposal(options: AcceptProposalOptions): Promise { + public async acceptProposal(options: AcceptProofProposalOptions): Promise { const { proofRecordId } = options const proofRecord = await this.getById(proofRecordId) @@ -278,7 +278,7 @@ export class ProofsApi< * specifying which credentials to use for the proof * @returns Proof record associated with the sent presentation message */ - public async acceptRequest(options: AcceptPresentationOptions): Promise { + public async acceptRequest(options: AcceptProofPresentationOptions): Promise { const { proofRecordId, proofFormats, comment } = options const record = await this.getById(proofRecordId) @@ -598,19 +598,19 @@ export class ProofsApi< await this.proofRepository.update(this.agentContext, proofRecord) } - public async findProposalMessage(proofRecordId: string): Promise> { + public async findProposalMessage(proofRecordId: string): Promise> { const record = await this.getById(proofRecordId) const service = this.getService(record.protocolVersion) return service.findProposalMessage(this.agentContext, proofRecordId) } - public async findRequestMessage(proofRecordId: string): Promise> { + public async findRequestMessage(proofRecordId: string): Promise> { const record = await this.getById(proofRecordId) const service = this.getService(record.protocolVersion) return service.findRequestMessage(this.agentContext, proofRecordId) } - public async findPresentationMessage(proofRecordId: string): Promise> { + public async findPresentationMessage(proofRecordId: string): Promise> { const record = await this.getById(proofRecordId) const service = this.getService(record.protocolVersion) return service.findPresentationMessage(this.agentContext, proofRecordId) diff --git a/packages/core/src/modules/proofs/ProofsApiOptions.ts b/packages/core/src/modules/proofs/ProofsApiOptions.ts index 00fdf53a30..8e89ec5121 100644 --- a/packages/core/src/modules/proofs/ProofsApiOptions.ts +++ b/packages/core/src/modules/proofs/ProofsApiOptions.ts @@ -6,10 +6,10 @@ import type { ProofConfig } from './models/ModuleOptions' /** * Get the supported protocol versions based on the provided proof services. */ -export type ProtocolVersionType = PSs[number]['version'] -export type FindProposalMessageReturn = ReturnType -export type FindRequestMessageReturn = ReturnType -export type FindPresentationMessageReturn = ReturnType< +export type ProofsProtocolVersionType = PSs[number]['version'] +export type FindProofProposalMessageReturn = ReturnType +export type FindProofRequestMessageReturn = ReturnType +export type FindProofPresentationMessageReturn = ReturnType< PSs[number]['findPresentationMessage'] > @@ -18,15 +18,15 @@ export type FindPresentationMessageReturn = ReturnTy * * @example * ``` - * type ProofServiceMap = ServiceMap<[IndyProofFormat], [V1ProofService]> + * type ServiceMap = ProofServiceMap<[IndyProofFormat], [V1ProofService]> * * // equal to - * type ProofServiceMap = { + * type ServiceMap = { * v1: V1ProofService * } * ``` */ -export type ServiceMap[]> = { +export type ProofServiceMap[]> = { [PS in PSs[number] as PS['version']]: ProofService } @@ -35,20 +35,20 @@ export interface ProposeProofOptions< PSs extends ProofService[] = ProofService[] > { connectionId: string - protocolVersion: ProtocolVersionType + protocolVersion: ProofsProtocolVersionType proofFormats: ProofFormatPayload comment?: string goalCode?: string autoAcceptProof?: AutoAcceptProof parentThreadId?: string } -export interface AcceptPresentationOptions { +export interface AcceptProofPresentationOptions { proofRecordId: string comment?: string proofFormats: ProofFormatPayload } -export interface AcceptProposalOptions { +export interface AcceptProofProposalOptions { proofRecordId: string config?: ProofConfig goalCode?: string @@ -60,7 +60,7 @@ export interface RequestProofOptions< PFs extends ProofFormat[] = ProofFormat[], PSs extends ProofService[] = ProofService[] > { - protocolVersion: ProtocolVersionType + protocolVersion: ProofsProtocolVersionType connectionId: string proofFormats: ProofFormatPayload comment?: string @@ -72,7 +72,7 @@ export interface CreateProofRequestOptions< PFs extends ProofFormat[] = ProofFormat[], PSs extends ProofService[] = ProofService[] > { - protocolVersion: ProtocolVersionType + protocolVersion: ProofsProtocolVersionType proofFormats: ProofFormatPayload comment?: string autoAcceptProof?: AutoAcceptProof diff --git a/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts b/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts index 12a0a69dc9..0fcd3d405c 100644 --- a/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts +++ b/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts @@ -8,15 +8,15 @@ import type { ProofFormatService } from './ProofFormatService' * * @example * ``` - * type ProofFormatServiceMap = FormatServiceMap<[IndyProofFormat]> + * type FormatServiceMap = ProofFormatServiceMap<[IndyProofFormat]> * * // equal to - * type ProofFormatServiceMap = { - * indy: ProofFormatService + * type FormatServiceMap = { + * indy: ProofFormatServiceMap * } * ``` */ -export type FormatServiceMap = { +export type ProofFormatServiceMap = { [PF in PFs[number] as PF['formatKey']]: ProofFormatService } @@ -25,7 +25,7 @@ export type FormatServiceMap = { * * It requires an attachment and a format to be returned. */ -export interface FormatCreateReturn { +export interface ProofFormatCreateReturn { format: ProofFormatSpec attachment: Attachment } diff --git a/packages/core/src/modules/proofs/formats/index.ts b/packages/core/src/modules/proofs/formats/index.ts new file mode 100644 index 0000000000..efb4e8a6ab --- /dev/null +++ b/packages/core/src/modules/proofs/formats/index.ts @@ -0,0 +1,6 @@ +export * from './indy' +export * from './models' +export * from './ProofFormat' +export * from './ProofFormatConstants' +export * from './ProofFormatService' +export * from './ProofFormatServiceOptions' diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts index 1f6692e75a..8d6769be1e 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts @@ -1,9 +1,4 @@ -import type { LinkedAttachment } from '../../../../utils/LinkedAttachment' -import type { CredentialPreviewAttributeOptions } from '../../../credentials' -import type { - PresentationPreviewAttribute, - PresentationPreviewPredicate, -} from '../../protocol/v1/models/V1PresentationPreview' +import type { PresentationPreviewAttribute, PresentationPreviewPredicate } from '../../protocol/v1' import type { ProofFormat } from '../ProofFormat' import type { IndyRequestProofFormat } from '../indy/IndyProofFormatsServiceOptions' import type { RequestedAttribute } from './models/RequestedAttribute' @@ -14,22 +9,9 @@ import type { IndyProof, IndyProofRequest } from 'indy-sdk' export interface IndyProposeProofFormat { attributes?: PresentationPreviewAttribute[] predicates?: PresentationPreviewPredicate[] - nonce: string - name: string - version: string -} - -/** - * This defines the module payload for calling CredentialsApi.acceptProposal - */ -export interface IndyAcceptProposalFormat { - credentialDefinitionId?: string - attributes?: CredentialPreviewAttributeOptions[] - linkedAttachments?: LinkedAttachment[] -} - -export interface IndyAcceptOfferFormat { - holderDid?: string + nonce?: string + name?: string + version?: string } export interface IndyRequestedCredentialsFormat { diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts index 83aca03ca6..1d51478e76 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts @@ -6,6 +6,7 @@ import type { FormatRetrievedCredentialOptions, } from '../../models/ProofServiceOptions' import type { ProofRequestFormats } from '../../models/SharedOptions' +import type { PresentationPreviewAttribute } from '../../protocol/v1/models' import type { ProofAttachmentFormat } from '../models/ProofAttachmentFormat' import type { CreatePresentationFormatsOptions, @@ -19,9 +20,8 @@ import type { ProcessRequestOptions, VerifyProofOptions, } from '../models/ProofFormatServiceOptions' -import type { IndyProofFormat } from './IndyProofFormat' +import type { IndyProofFormat, IndyProposeProofFormat } from './IndyProofFormat' import type { GetRequestedCredentialsFormat } from './IndyProofFormatsServiceOptions' -import type { ProofAttributeInfo, ProofPredicateInfo } from './models' import type { CredDef, IndyProof, Schema } from 'indy-sdk' import { Lifecycle, scoped } from 'tsyringe' @@ -43,7 +43,7 @@ import { IndyCredentialUtils } from '../../../credentials/formats/indy/IndyCrede import { IndyHolderService, IndyVerifierService, IndyRevocationService } from '../../../indy' import { IndyLedgerService } from '../../../ledger' import { ProofFormatSpec } from '../../models/ProofFormatSpec' -import { PartialProof } from '../../protocol/v1/models' +import { PartialProof, PresentationPreview } from '../../protocol/v1/models' import { V2_INDY_PRESENTATION_REQUEST, V2_INDY_PRESENTATION_PROPOSAL, @@ -53,7 +53,13 @@ import { ProofFormatService } from '../ProofFormatService' import { InvalidEncodedValueError } from './errors/InvalidEncodedValueError' import { MissingIndyProofMessageError } from './errors/MissingIndyProofMessageError' -import { RequestedAttribute, RequestedPredicate } from './models' +import { + AttributeFilter, + ProofAttributeInfo, + ProofPredicateInfo, + RequestedAttribute, + RequestedPredicate, +} from './models' import { ProofRequest } from './models/ProofRequest' import { RequestedCredentials } from './models/RequestedCredentials' import { RetrievedCredentials } from './models/RetrievedCredentials' @@ -131,11 +137,11 @@ export class IndyProofFormatService extends ProofFormatService { if (!options.formats.indy) { throw Error('Missing indy format to create proposal attachment format') } - const indyFormat = options.formats.indy + const proofRequest = await this.createRequestFromPreview(options.formats.indy) return await this.createProofAttachment({ id: options.id ?? uuid(), - proofProposalOptions: indyFormat, + proofProposalOptions: proofRequest, }) } @@ -183,9 +189,16 @@ export class IndyProofFormatService extends ProofFormatService { throw new AriesFrameworkError('Missing indy format to create proof request attachment format.') } + const indyFormat = options.formats.indy + return this.createRequestAttachment({ id: options.id ?? uuid(), - proofRequestOptions: options.formats.indy, + proofRequestOptions: { + ...indyFormat, + name: indyFormat.name ?? 'proof-request', + version: indyFormat.version ?? '1.0', + nonce: indyFormat.nonce ?? (await this.wallet.generateNonce()), + }, }) } @@ -643,4 +656,90 @@ export class IndyProofFormatService extends ProofFormatService { return { revoked: undefined, deltaTimestamp: undefined } } + + public async createRequestFromPreview(indyFormat: IndyProposeProofFormat): Promise { + const preview = new PresentationPreview({ + attributes: indyFormat.attributes, + predicates: indyFormat.predicates, + }) + + const proofRequest = await this.createReferentForProofRequest(indyFormat, preview) + + return proofRequest + } + + public async createReferentForProofRequest( + indyFormat: IndyProposeProofFormat, + preview: PresentationPreview + ): Promise { + const proofRequest = new ProofRequest({ + name: indyFormat.name ?? 'proof-request', + version: indyFormat.version ?? '1.0', + nonce: indyFormat.nonce ?? (await this.wallet.generateNonce()), + }) + + /** + * Create mapping of attributes by referent. This required the + * attributes to come from the same credential. + * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#referent + * + * { + * "referent1": [Attribute1, Attribute2], + * "referent2": [Attribute3] + * } + */ + const attributesByReferent: Record = {} + for (const proposedAttributes of preview.attributes) { + if (!proposedAttributes.referent) proposedAttributes.referent = uuid() + + const referentAttributes = attributesByReferent[proposedAttributes.referent] + + // Referent key already exist, add to list + if (referentAttributes) { + referentAttributes.push(proposedAttributes) + } + + // Referent key does not exist yet, create new entry + else { + attributesByReferent[proposedAttributes.referent] = [proposedAttributes] + } + } + + // Transform attributes by referent to requested attributes + for (const [referent, proposedAttributes] of Object.entries(attributesByReferent)) { + // Either attributeName or attributeNames will be undefined + const attributeName = proposedAttributes.length == 1 ? proposedAttributes[0].name : undefined + const attributeNames = proposedAttributes.length > 1 ? proposedAttributes.map((a) => a.name) : undefined + + const requestedAttribute = new ProofAttributeInfo({ + name: attributeName, + names: attributeNames, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: proposedAttributes[0].credentialDefinitionId, + }), + ], + }) + + proofRequest.requestedAttributes.set(referent, requestedAttribute) + } + + // Transform proposed predicates to requested predicates + for (const proposedPredicate of preview.predicates) { + const requestedPredicate = new ProofPredicateInfo({ + name: proposedPredicate.name, + predicateType: proposedPredicate.predicate, + predicateValue: proposedPredicate.threshold, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: proposedPredicate.credentialDefinitionId, + }), + ], + }) + + proofRequest.requestedPredicates.set(uuid(), requestedPredicate) + } + + return proofRequest + } } diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts index 8500d4646f..93c40ec7e9 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts @@ -11,9 +11,9 @@ import type { ProofRequest } from './models/ProofRequest' export type IndyPresentationProofFormat = IndyRequestedCredentialsFormat export interface IndyRequestProofFormat { - name: string - version: string - nonce: string + name?: string + version?: string + nonce?: string nonRevoked?: IndyRevocationInterval ver?: '1.0' | '2.0' requestedAttributes?: Record | Map diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofUtils.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofUtils.ts deleted file mode 100644 index 137d9edb25..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofUtils.ts +++ /dev/null @@ -1,115 +0,0 @@ -import type { CreateProposalOptions } from '../../models/ProofServiceOptions' -import type { ProofRequestFormats } from '../../models/SharedOptions' -import type { PresentationPreviewAttribute } from '../../protocol/v1/models/V1PresentationPreview' -import type { IndyProofFormat, IndyProposeProofFormat } from './IndyProofFormat' - -import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' -import { uuid } from '../../../../utils/uuid' -import { PresentationPreview } from '../../protocol/v1/models/V1PresentationPreview' - -import { AttributeFilter } from './models/AttributeFilter' -import { ProofAttributeInfo } from './models/ProofAttributeInfo' -import { ProofPredicateInfo } from './models/ProofPredicateInfo' -import { ProofRequest } from './models/ProofRequest' - -export class IndyProofUtils { - public static async createRequestFromPreview( - options: CreateProposalOptions<[IndyProofFormat]> - ): Promise { - const indyFormat = options.proofFormats?.indy - - if (!indyFormat) { - throw new AriesFrameworkError('No Indy format found.') - } - - const preview = new PresentationPreview({ - attributes: indyFormat.attributes, - predicates: indyFormat.predicates, - }) - - if (!preview) { - throw new AriesFrameworkError(`No preview found`) - } - - const proofRequest = IndyProofUtils.createReferentForProofRequest(indyFormat, preview) - - return { - indy: proofRequest, - } - } - - public static createReferentForProofRequest( - indyFormat: IndyProposeProofFormat, - preview: PresentationPreview - ): ProofRequest { - const proofRequest = new ProofRequest({ - name: indyFormat.name, - version: indyFormat.version, - nonce: indyFormat.nonce, - }) - - /** - * Create mapping of attributes by referent. This required the - * attributes to come from the same credential. - * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#referent - * - * { - * "referent1": [Attribute1, Attribute2], - * "referent2": [Attribute3] - * } - */ - const attributesByReferent: Record = {} - for (const proposedAttributes of preview.attributes) { - if (!proposedAttributes.referent) proposedAttributes.referent = uuid() - - const referentAttributes = attributesByReferent[proposedAttributes.referent] - - // Referent key already exist, add to list - if (referentAttributes) { - referentAttributes.push(proposedAttributes) - } - - // Referent key does not exist yet, create new entry - else { - attributesByReferent[proposedAttributes.referent] = [proposedAttributes] - } - } - - // Transform attributes by referent to requested attributes - for (const [referent, proposedAttributes] of Object.entries(attributesByReferent)) { - // Either attributeName or attributeNames will be undefined - const attributeName = proposedAttributes.length == 1 ? proposedAttributes[0].name : undefined - const attributeNames = proposedAttributes.length > 1 ? proposedAttributes.map((a) => a.name) : undefined - - const requestedAttribute = new ProofAttributeInfo({ - name: attributeName, - names: attributeNames, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: proposedAttributes[0].credentialDefinitionId, - }), - ], - }) - - proofRequest.requestedAttributes.set(referent, requestedAttribute) - } - - // Transform proposed predicates to requested predicates - for (const proposedPredicate of preview.predicates) { - const requestedPredicate = new ProofPredicateInfo({ - name: proposedPredicate.name, - predicateType: proposedPredicate.predicate, - predicateValue: proposedPredicate.threshold, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: proposedPredicate.credentialDefinitionId, - }), - ], - }) - - proofRequest.requestedPredicates.set(uuid(), requestedPredicate) - } - - return proofRequest - } -} diff --git a/packages/core/src/modules/proofs/formats/indy/errors/index.ts b/packages/core/src/modules/proofs/formats/indy/errors/index.ts new file mode 100644 index 0000000000..0f0b302726 --- /dev/null +++ b/packages/core/src/modules/proofs/formats/indy/errors/index.ts @@ -0,0 +1,2 @@ +export * from './InvalidEncodedValueError' +export * from './MissingIndyProofMessageError' diff --git a/packages/core/src/modules/proofs/formats/indy/index.ts b/packages/core/src/modules/proofs/formats/indy/index.ts new file mode 100644 index 0000000000..c94afb8629 --- /dev/null +++ b/packages/core/src/modules/proofs/formats/indy/index.ts @@ -0,0 +1,4 @@ +export * from './errors' +export * from './models' +export * from './IndyProofFormat' +export * from './IndyProofFormatsServiceOptions' diff --git a/packages/core/src/modules/proofs/formats/indy/models/index.ts b/packages/core/src/modules/proofs/formats/indy/models/index.ts index b38776d360..978b3ee89f 100644 --- a/packages/core/src/modules/proofs/formats/indy/models/index.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/index.ts @@ -1,7 +1,9 @@ +export * from './AttributeFilter' +export * from './PredicateType' export * from './ProofAttributeInfo' export * from './ProofPredicateInfo' +export * from './ProofRequest' export * from './RequestedAttribute' +export * from './RequestedCredentials' export * from './RequestedPredicate' -export * from './ProofRequest' -export * from './AttributeFilter' -export * from './PredicateType' +export * from './RetrievedCredentials' diff --git a/packages/core/src/modules/proofs/formats/models/index.ts b/packages/core/src/modules/proofs/formats/models/index.ts new file mode 100644 index 0000000000..968a6b53ee --- /dev/null +++ b/packages/core/src/modules/proofs/formats/models/index.ts @@ -0,0 +1,2 @@ +export * from './ProofAttachmentFormat' +export * from './ProofFormatServiceOptions' diff --git a/packages/core/src/modules/proofs/index.ts b/packages/core/src/modules/proofs/index.ts index 7660d50fa2..5d4f5f16c7 100644 --- a/packages/core/src/modules/proofs/index.ts +++ b/packages/core/src/modules/proofs/index.ts @@ -1,10 +1,13 @@ -export * from './protocol/v1/messages' -export * from './protocol/v1/models' -export * from './protocol/v2/messages' -export * from './ProofService' +export * from './errors' +export * from './formats' +export * from './messages' export * from './models' +export * from './protocol' export * from './repository' export * from './ProofEvents' -export * from './formats/indy/models' -export * from './formats/indy/IndyProofUtils' +export * from './ProofResponseCoordinator' +export * from './ProofsApi' +export * from './ProofsApiOptions' +export * from './ProofService' export * from './ProofsModule' +export * from './ProofsModuleConfig' diff --git a/packages/core/src/modules/proofs/messages/index.ts b/packages/core/src/modules/proofs/messages/index.ts new file mode 100644 index 0000000000..1f395b2d57 --- /dev/null +++ b/packages/core/src/modules/proofs/messages/index.ts @@ -0,0 +1 @@ +export * from './PresentationAckMessage' diff --git a/packages/core/src/modules/proofs/protocol/index.ts b/packages/core/src/modules/proofs/protocol/index.ts new file mode 100644 index 0000000000..4d9da63573 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/index.ts @@ -0,0 +1,2 @@ +export * from './v1' +export * from './v2' diff --git a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts index f850df9aaf..dc572f431b 100644 --- a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts @@ -7,7 +7,6 @@ import type { MediationRecipientService } from '../../../routing/services/Mediat import type { RoutingService } from '../../../routing/services/RoutingService' import type { ProofResponseCoordinator } from '../../ProofResponseCoordinator' import type { ProofFormat } from '../../formats/ProofFormat' -import type { ProofFormatService } from '../../formats/ProofFormatService' import type { IndyProofFormat, IndyProposeProofFormat } from '../../formats/indy/IndyProofFormat' import type { ProofAttributeInfo } from '../../formats/indy/models' import type { @@ -51,7 +50,6 @@ import { IndyLedgerService } from '../../../ledger/services/IndyLedgerService' import { ProofService } from '../../ProofService' import { PresentationProblemReportReason } from '../../errors/PresentationProblemReportReason' import { IndyProofFormatService } from '../../formats/indy/IndyProofFormatService' -import { IndyProofUtils } from '../../formats/indy/IndyProofUtils' import { ProofRequest } from '../../formats/indy/models/ProofRequest' import { RequestedCredentials } from '../../formats/indy/models/RequestedCredentials' import { ProofState } from '../../models/ProofState' @@ -88,7 +86,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { private ledgerService: IndyLedgerService private indyHolderService: IndyHolderService private indyRevocationService: IndyRevocationService - private indyProofFormatService: ProofFormatService + private indyProofFormatService: IndyProofFormatService public constructor( proofRepository: ProofRepository, @@ -112,6 +110,9 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { this.indyRevocationService = indyRevocationService } + /** + * The version of the present proof protocol this service supports + */ public readonly version = 'v1' as const public async createProposal( @@ -652,10 +653,6 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { return proofRecord } - public async generateProofRequestNonce() { - return this.wallet.generateNonce() - } - public async createProofRequestFromProposal( agentContext: AgentContext, options: CreateProofRequestFromProposalOptions @@ -673,10 +670,10 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { const indyProposeProofFormat: IndyProposeProofFormat = { name: 'Proof Request', version: '1.0', - nonce: await this.generateProofRequestNonce(), + nonce: await this.wallet.generateNonce(), } - const proofRequest: ProofRequest = IndyProofUtils.createReferentForProofRequest( + const proofRequest: ProofRequest = await this.indyProofFormatService.createReferentForProofRequest( indyProposeProofFormat, proposalMessage.presentationProposal ) @@ -1052,7 +1049,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { const indyFormat: IndyProposeProofFormat = { name: 'Proof Request', version: '1.0', - nonce: await this.generateProofRequestNonce(), + nonce: await this.wallet.generateNonce(), attributes: proposalMessage.presentationProposal.attributes, predicates: proposalMessage.presentationProposal.predicates, } @@ -1066,7 +1063,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { predicates: indyFormat.predicates, }) - return IndyProofUtils.createReferentForProofRequest(indyFormat, preview) + return this.indyProofFormatService.createReferentForProofRequest(indyFormat, preview) } /** * Retrieve all proof records diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts index b60a16ad4a..130f5cf04a 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts @@ -1,6 +1,6 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProposalOptions } from '../../../ProofsApiOptions' +import type { AcceptProofProposalOptions } from '../../../ProofsApiOptions' import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { PresentationPreview } from '../models/V1PresentationPreview' @@ -105,7 +105,7 @@ describe('Present Proof', () => { test(`Faber accepts the Proposal send by Alice and Creates Proof Request`, async () => { // Accept Proposal - const acceptProposalOptions: AcceptProposalOptions = { + const acceptProposalOptions: AcceptProofProposalOptions = { proofRecordId: faberProofExchangeRecord.id, } diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts index 31146937c2..4a9fe4b104 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts @@ -64,7 +64,7 @@ export class V1ProposePresentationHandler implements Handler { const proofRequestFromProposalOptions: IndyProofRequestFromProposalOptions = { name: 'proof-request', version: '1.0', - nonce: await this.proofService.generateProofRequestNonce(), + nonce: await messageContext.agentContext.wallet.generateNonce(), proofRecord, } diff --git a/packages/core/src/modules/proofs/protocol/v1/index.ts b/packages/core/src/modules/proofs/protocol/v1/index.ts index 1b43254564..a7e92f64d6 100644 --- a/packages/core/src/modules/proofs/protocol/v1/index.ts +++ b/packages/core/src/modules/proofs/protocol/v1/index.ts @@ -1 +1,4 @@ +export * from './errors' +export * from './messages' +export * from './models' export * from './V1ProofService' diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/index.ts b/packages/core/src/modules/proofs/protocol/v1/messages/index.ts index 01d16d4e87..5aef9dbd79 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/index.ts +++ b/packages/core/src/modules/proofs/protocol/v1/messages/index.ts @@ -1,4 +1,5 @@ export * from './V1ProposePresentationMessage' export * from './V1RequestPresentationMessage' +export * from './V1PresentationProblemReportMessage' export * from './V1PresentationMessage' export * from './V1PresentationAckMessage' diff --git a/packages/core/src/modules/proofs/protocol/v1/models/index.ts b/packages/core/src/modules/proofs/protocol/v1/models/index.ts index f9d9b12a47..35ec9d0545 100644 --- a/packages/core/src/modules/proofs/protocol/v1/models/index.ts +++ b/packages/core/src/modules/proofs/protocol/v1/models/index.ts @@ -2,3 +2,4 @@ export * from './PartialProof' export * from './ProofAttribute' export * from './ProofIdentifier' export * from './RequestedProof' +export * from './V1PresentationPreview' diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts index 50fb3efd7a..3a457ef538 100644 --- a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts @@ -41,7 +41,6 @@ import { ProofService } from '../../ProofService' import { PresentationProblemReportReason } from '../../errors/PresentationProblemReportReason' import { V2_INDY_PRESENTATION_REQUEST } from '../../formats/ProofFormatConstants' import { IndyProofFormatService } from '../../formats/indy/IndyProofFormatService' -import { IndyProofUtils } from '../../formats/indy/IndyProofUtils' import { ProofState } from '../../models/ProofState' import { PresentationRecordType, ProofExchangeRecord, ProofRepository } from '../../repository' @@ -79,7 +78,7 @@ export class V2ProofService extends P } /** - * The version of the issue credential protocol this service supports + * The version of the present proof protocol this service supports */ public readonly version = 'v2' as const @@ -90,14 +89,7 @@ export class V2ProofService extends P const formats = [] for (const key of Object.keys(options.proofFormats)) { const service = this.formatServiceMap[key] - formats.push( - await service.createProposal({ - formats: - key === PresentationRecordType.Indy - ? await IndyProofUtils.createRequestFromPreview(options) - : options.proofFormats, - }) - ) + formats.push(await service.createProposal({ formats: options.proofFormats })) } const proposalMessage = new V2ProposalPresentationMessage({ diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts index e36fe8d4ad..08d5978d80 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts @@ -1,6 +1,6 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProposalOptions } from '../../../ProofsApiOptions' +import type { AcceptProofProposalOptions } from '../../../ProofsApiOptions' import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' @@ -103,7 +103,7 @@ describe('Present Proof', () => { test(`Faber accepts the Proposal send by Alice`, async () => { // Accept Proposal - const acceptProposalOptions: AcceptProposalOptions = { + const acceptProposalOptions: AcceptProofProposalOptions = { proofRecordId: faberProofExchangeRecord.id, } diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts index 5fae009dc1..d30a0c02b9 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts @@ -1,6 +1,6 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProposalOptions } from '../../../ProofsApiOptions' +import type { AcceptProofProposalOptions } from '../../../ProofsApiOptions' import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' @@ -99,7 +99,7 @@ describe('Present Proof', () => { test(`Faber accepts the Proposal send by Alice`, async () => { // Accept Proposal - const acceptProposalOptions: AcceptProposalOptions = { + const acceptProposalOptions: AcceptProofProposalOptions = { proofRecordId: faberProofExchangeRecord.id, } diff --git a/packages/core/src/modules/proofs/protocol/v2/index.ts b/packages/core/src/modules/proofs/protocol/v2/index.ts index 960bb99e85..0fb0534d95 100644 --- a/packages/core/src/modules/proofs/protocol/v2/index.ts +++ b/packages/core/src/modules/proofs/protocol/v2/index.ts @@ -1 +1,3 @@ +export * from './errors' +export * from './messages' export * from './V2ProofService' diff --git a/packages/core/src/wallet/WalletApi.ts b/packages/core/src/wallet/WalletApi.ts index c59cd8fb54..a845123160 100644 --- a/packages/core/src/wallet/WalletApi.ts +++ b/packages/core/src/wallet/WalletApi.ts @@ -94,6 +94,10 @@ export class WalletApi { await this.wallet.rotateKey(walletConfig) } + public async generateNonce(): Promise { + return await this.wallet.generateNonce() + } + public async delete(): Promise { await this.wallet.delete() } diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 9124eaacdb..39b79cd84e 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { - AcceptOfferOptions, + AcceptCredentialOfferOptions, BasicMessage, BasicMessageStateChangedEvent, ConnectionRecordProps, @@ -468,7 +468,7 @@ export async function issueCredential({ state: CredentialState.OfferReceived, }) - const acceptOfferOptions: AcceptOfferOptions = { + const acceptOfferOptions: AcceptCredentialOfferOptions = { credentialRecordId: holderCredentialRecord.id, autoAcceptCredential: AutoAcceptCredential.ContentApproved, } @@ -536,7 +536,7 @@ export async function issueConnectionLessCredential({ threadId: issuerCredentialRecord.threadId, state: CredentialState.OfferReceived, }) - const acceptOfferOptions: AcceptOfferOptions = { + const acceptOfferOptions: AcceptCredentialOfferOptions = { credentialRecordId: holderCredentialRecord.id, autoAcceptCredential: AutoAcceptCredential.ContentApproved, } diff --git a/packages/core/tests/v1-indy-proofs.test.ts b/packages/core/tests/v1-indy-proofs.test.ts index b8142c4b17..81b523d659 100644 --- a/packages/core/tests/v1-indy-proofs.test.ts +++ b/packages/core/tests/v1-indy-proofs.test.ts @@ -1,5 +1,5 @@ import type { Agent, ConnectionRecord } from '../src' -import type { AcceptProposalOptions } from '../src/modules/proofs/ProofsApiOptions' +import type { AcceptProofProposalOptions } from '../src/modules/proofs/ProofsApiOptions' import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' import type { CredDefId } from 'indy-sdk' @@ -61,9 +61,6 @@ describe('Present Proof', () => { protocolVersion: 'v1', proofFormats: { indy: { - name: 'abc', - version: '1.0', - nonce: '947121108704767252195126', attributes: presentationPreview.attributes, predicates: presentationPreview.predicates, }, @@ -115,7 +112,7 @@ describe('Present Proof', () => { protocolVersion: 'v1', }) - const acceptProposalOptions: AcceptProposalOptions = { + const acceptProposalOptions: AcceptProofProposalOptions = { proofRecordId: faberProofExchangeRecord.id, } @@ -383,7 +380,6 @@ describe('Present Proof', () => { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', requestedAttributes: attributes, requestedPredicates: predicates, }, diff --git a/packages/core/tests/v2-indy-proofs.test.ts b/packages/core/tests/v2-indy-proofs.test.ts index d59df461bb..9be131c559 100644 --- a/packages/core/tests/v2-indy-proofs.test.ts +++ b/packages/core/tests/v2-indy-proofs.test.ts @@ -1,5 +1,5 @@ import type { Agent, ConnectionRecord } from '../src' -import type { AcceptProposalOptions } from '../src/modules/proofs/ProofsApiOptions' +import type { AcceptProofProposalOptions } from '../src/modules/proofs/ProofsApiOptions' import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' import type { CredDefId } from 'indy-sdk' @@ -111,7 +111,7 @@ describe('Present Proof', () => { protocolVersion: 'v2', }) - const acceptProposalOptions: AcceptProposalOptions = { + const acceptProposalOptions: AcceptProofProposalOptions = { proofRecordId: faberProofExchangeRecord.id, } @@ -263,7 +263,7 @@ describe('Present Proof', () => { indy: { name: 'abc', version: '1.0', - nonce: expect.any(String), + nonce: '947121108704767252195126', requested_attributes: { 0: { name: 'name', @@ -389,9 +389,6 @@ describe('Present Proof', () => { connectionId: faberConnection.id, proofFormats: { indy: { - name: 'proof-request', - version: '1.0', - nonce: '1298236324864', requestedAttributes: attributes, requestedPredicates: predicates, }, From fa553b4578499b7cfbf041d854d348c2cf8dae4b Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Wed, 2 Nov 2022 07:00:06 -0300 Subject: [PATCH 068/125] chore: make action-menu and question-answer public (#1082) Signed-off-by: Ariel Gentile --- packages/action-menu/package.json | 1 - packages/question-answer/package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/action-menu/package.json b/packages/action-menu/package.json index 401650373a..eb59188728 100644 --- a/packages/action-menu/package.json +++ b/packages/action-menu/package.json @@ -3,7 +3,6 @@ "main": "build/index", "types": "build/index", "version": "0.2.5", - "private": true, "files": [ "build" ], diff --git a/packages/question-answer/package.json b/packages/question-answer/package.json index d50b9d3eed..ef9a010635 100644 --- a/packages/question-answer/package.json +++ b/packages/question-answer/package.json @@ -3,7 +3,6 @@ "main": "build/index", "types": "build/index", "version": "0.2.5", - "private": true, "files": [ "build" ], From bd01e408dc4980bb4457a0c758e9706615ea269d Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Wed, 2 Nov 2022 13:50:24 -0300 Subject: [PATCH 069/125] ci: use graph-type all for lerna publish (#1084) Signed-off-by: Ariel Gentile --- lerna.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lerna.json b/lerna.json index 103d37573a..22cb1ca6ea 100644 --- a/lerna.json +++ b/lerna.json @@ -6,6 +6,9 @@ "command": { "version": { "allowBranch": "main" + }, + "publish": { + "graphType": "all" } } } From ab403c92c874b940937ef0f534697ac8ce7a2e00 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Wed, 2 Nov 2022 15:16:25 -0300 Subject: [PATCH 070/125] chore: update extension module sample (#1083) Signed-off-by: Ariel Gentile --- jest.config.ts | 6 +- packages/core/src/index.ts | 1 + samples/extension-module/README.md | 29 +++-- samples/extension-module/dummy/DummyModule.ts | 12 ++- .../dummy/DummyModuleConfig.ts | 25 +++++ .../dummy/handlers/DummyRequestHandler.ts | 9 +- samples/extension-module/dummy/index.ts | 1 + .../dummy/messages/DummyRequestMessage.ts | 4 +- .../dummy/services/DummyService.ts | 13 ++- samples/extension-module/jest.config.ts | 13 +++ samples/extension-module/requester.ts | 15 ++- samples/extension-module/responder.ts | 26 ++--- .../extension-module/tests/dummy.e2e.test.ts | 101 ++++++++++++++++++ samples/extension-module/tests/helpers.ts | 57 ++++++++++ samples/extension-module/tests/setup.ts | 1 + samples/extension-module/tsconfig.json | 2 +- 16 files changed, 281 insertions(+), 34 deletions(-) create mode 100644 samples/extension-module/dummy/DummyModuleConfig.ts create mode 100644 samples/extension-module/jest.config.ts create mode 100644 samples/extension-module/tests/dummy.e2e.test.ts create mode 100644 samples/extension-module/tests/helpers.ts create mode 100644 samples/extension-module/tests/setup.ts diff --git a/jest.config.ts b/jest.config.ts index 3916848941..d2f8f320db 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -5,7 +5,11 @@ import base from './jest.config.base' const config: Config.InitialOptions = { ...base, roots: [''], - projects: ['/packages/*', '/tests/jest.config.ts'], + projects: [ + '/packages/*', + '/tests/jest.config.ts', + '/samples/extension-module/jest.config.ts', + ], } export default config diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1c88354a33..355383f062 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -38,6 +38,7 @@ export * from './wallet' export type { TransportSession } from './agent/TransportService' export { TransportService } from './agent/TransportService' export { Attachment } from './decorators/attachment/Attachment' +export { ReturnRouteTypes } from './decorators/transport/TransportDecorator' export * from './plugins' export * from './transport' diff --git a/samples/extension-module/README.md b/samples/extension-module/README.md index 952ea962f5..c57acf74f1 100644 --- a/samples/extension-module/README.md +++ b/samples/extension-module/README.md @@ -16,35 +16,42 @@ This example consists of a module that implements a very simple request-response - Define events (inherited from `BaseEvent`) - Create a singleton service class that manages records and repository, and also trigger events using Agent's `EventEmitter` - Create a singleton api class that registers handlers in Agent's `Dispatcher` and provides a simple API to do requests and responses, with the aid of service classes and Agent's `MessageSender` -- Create a module class that registers all the above on the dependency manager so it can be be injected from the `Agent` instance. +- Create a module class that registers all the above on the dependency manager so it can be be injected from the `Agent` instance, and also register the features (such as protocols) the module adds to the Agent. ## Usage -In order to use this module, you first need to register `DummyModule` on the `Agent` instance. After that you need to resolve the `DummyApi` to interact with the public api of the module. Make sure to register and resolve the api **before** initializing the agent. +In order to use this module, you first need to register `DummyModule` on the `Agent` instance. This can be done by adding an entry for it in `AgentOptions`'s modules property: ```ts -import { DummyModule, DummyApi } from './dummy' - -const agent = new Agent(/** agent config... */) +import { DummyModule } from './dummy' // Register the module with it's dependencies -agent.dependencyManager.registerModules(new DummyModule()) - -const dummyApi = agent.dependencyManager.resolve(DummyApi) +const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, + modules: { + dummy: new DummyModule({ + /* module config */ + }), + /* other custom modules */ + }, +}) await agent.initialize() ``` -Then, Dummy module API methods can be called, and events listeners can be created: +Then, Dummy module API methods can be called from `agent.modules.dummy` namespace, and events listeners can be created: ```ts agent.events.on(DummyEventTypes.StateChanged, async (event: DummyStateChangedEvent) => { if (event.payload.dummyRecord.state === DummyState.RequestReceived) { - await dummyApi.respond(event.payload.dummyRecord) + await agent.modules.dummy.respond(event.payload.dummyRecord) } }) -const record = await dummyApi.request(connection) +const record = await agent.modules.dummy.request(connection) ``` ## Run demo diff --git a/samples/extension-module/dummy/DummyModule.ts b/samples/extension-module/dummy/DummyModule.ts index 7aab0695c1..e844dceb41 100644 --- a/samples/extension-module/dummy/DummyModule.ts +++ b/samples/extension-module/dummy/DummyModule.ts @@ -1,18 +1,28 @@ +import type { DummyModuleConfigOptions } from './DummyModuleConfig' import type { DependencyManager, FeatureRegistry, Module } from '@aries-framework/core' import { Protocol } from '@aries-framework/core' import { DummyApi } from './DummyApi' +import { DummyModuleConfig } from './DummyModuleConfig' import { DummyRepository } from './repository' import { DummyService } from './services' export class DummyModule implements Module { - public api = DummyApi + public readonly config: DummyModuleConfig + public readonly api = DummyApi + + public constructor(config?: DummyModuleConfigOptions) { + this.config = new DummyModuleConfig(config) + } public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { // Api dependencyManager.registerContextScoped(DummyApi) + // Config + dependencyManager.registerInstance(DummyModuleConfig, this.config) + dependencyManager.registerSingleton(DummyRepository) dependencyManager.registerSingleton(DummyService) diff --git a/samples/extension-module/dummy/DummyModuleConfig.ts b/samples/extension-module/dummy/DummyModuleConfig.ts new file mode 100644 index 0000000000..ef742f4b3f --- /dev/null +++ b/samples/extension-module/dummy/DummyModuleConfig.ts @@ -0,0 +1,25 @@ +/** + * DummyModuleConfigOptions defines the interface for the options of the DummyModuleConfig class. + * This can contain optional parameters that have default values in the config class itself. + */ +export interface DummyModuleConfigOptions { + /** + * Whether to automatically accept request messages. + * + * @default false + */ + autoAcceptRequests?: boolean +} + +export class DummyModuleConfig { + private options: DummyModuleConfigOptions + + public constructor(options?: DummyModuleConfigOptions) { + this.options = options ?? {} + } + + /** See {@link DummyModuleConfigOptions.autoAcceptRequests} */ + public get autoAcceptRequests() { + return this.options.autoAcceptRequests ?? false + } +} diff --git a/samples/extension-module/dummy/handlers/DummyRequestHandler.ts b/samples/extension-module/dummy/handlers/DummyRequestHandler.ts index c5b1e047e6..ef5d5471ec 100644 --- a/samples/extension-module/dummy/handlers/DummyRequestHandler.ts +++ b/samples/extension-module/dummy/handlers/DummyRequestHandler.ts @@ -1,6 +1,8 @@ import type { DummyService } from '../services' import type { Handler, HandlerInboundMessage } from '@aries-framework/core' +import { createOutboundMessage } from '@aries-framework/core' + import { DummyRequestMessage } from '../messages' export class DummyRequestHandler implements Handler { @@ -12,8 +14,11 @@ export class DummyRequestHandler implements Handler { } public async handle(inboundMessage: HandlerInboundMessage) { - inboundMessage.assertReadyConnection() + const connection = inboundMessage.assertReadyConnection() + const responseMessage = await this.dummyService.processRequest(inboundMessage) - await this.dummyService.processRequest(inboundMessage) + if (responseMessage) { + return createOutboundMessage(connection, responseMessage) + } } } diff --git a/samples/extension-module/dummy/index.ts b/samples/extension-module/dummy/index.ts index 3849e17339..f2014dc391 100644 --- a/samples/extension-module/dummy/index.ts +++ b/samples/extension-module/dummy/index.ts @@ -4,3 +4,4 @@ export * from './messages' export * from './services' export * from './repository' export * from './DummyModule' +export * from './DummyModuleConfig' diff --git a/samples/extension-module/dummy/messages/DummyRequestMessage.ts b/samples/extension-module/dummy/messages/DummyRequestMessage.ts index 4b93734810..5ca16f25dd 100644 --- a/samples/extension-module/dummy/messages/DummyRequestMessage.ts +++ b/samples/extension-module/dummy/messages/DummyRequestMessage.ts @@ -1,4 +1,4 @@ -import { AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' +import { AgentMessage, IsValidMessageType, parseMessageType, ReturnRouteTypes } from '@aries-framework/core' export interface DummyRequestMessageOptions { id?: string @@ -11,6 +11,8 @@ export class DummyRequestMessage extends AgentMessage { if (options) { this.id = options.id ?? this.generateId() } + + this.setReturnRouting(ReturnRouteTypes.all) } @IsValidMessageType(DummyRequestMessage.type) diff --git a/samples/extension-module/dummy/services/DummyService.ts b/samples/extension-module/dummy/services/DummyService.ts index 22b99fc399..0aa7a56747 100644 --- a/samples/extension-module/dummy/services/DummyService.ts +++ b/samples/extension-module/dummy/services/DummyService.ts @@ -3,6 +3,7 @@ import type { Query, AgentContext, ConnectionRecord, InboundMessageContext } fro import { injectable, JsonTransformer, EventEmitter } from '@aries-framework/core' +import { DummyModuleConfig } from '../DummyModuleConfig' import { DummyRequestMessage, DummyResponseMessage } from '../messages' import { DummyRecord } from '../repository/DummyRecord' import { DummyRepository } from '../repository/DummyRepository' @@ -14,8 +15,14 @@ import { DummyEventTypes } from './DummyEvents' export class DummyService { private dummyRepository: DummyRepository private eventEmitter: EventEmitter + private dummyModuleConfig: DummyModuleConfig - public constructor(dummyRepository: DummyRepository, eventEmitter: EventEmitter) { + public constructor( + dummyModuleConfig: DummyModuleConfig, + dummyRepository: DummyRepository, + eventEmitter: EventEmitter + ) { + this.dummyModuleConfig = dummyModuleConfig this.dummyRepository = dummyRepository this.eventEmitter = eventEmitter } @@ -80,7 +87,9 @@ export class DummyService { this.emitStateChangedEvent(messageContext.agentContext, record, null) - return record + if (this.dummyModuleConfig.autoAcceptRequests) { + return await this.createResponse(messageContext.agentContext, record) + } } /** diff --git a/samples/extension-module/jest.config.ts b/samples/extension-module/jest.config.ts new file mode 100644 index 0000000000..93c0197296 --- /dev/null +++ b/samples/extension-module/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/samples/extension-module/requester.ts b/samples/extension-module/requester.ts index 3a4a7726f3..ac83b076ce 100644 --- a/samples/extension-module/requester.ts +++ b/samples/extension-module/requester.ts @@ -1,6 +1,13 @@ import type { DummyRecord, DummyStateChangedEvent } from './dummy' -import { Agent, AriesFrameworkError, ConsoleLogger, LogLevel, WsOutboundTransport } from '@aries-framework/core' +import { + HttpOutboundTransport, + Agent, + AriesFrameworkError, + ConsoleLogger, + LogLevel, + WsOutboundTransport, +} from '@aries-framework/core' import { agentDependencies } from '@aries-framework/node' import { filter, first, firstValueFrom, map, ReplaySubject, timeout } from 'rxjs' @@ -10,6 +17,7 @@ const run = async () => { // Create transports const port = process.env.RESPONDER_PORT ? Number(process.env.RESPONDER_PORT) : 3002 const wsOutboundTransport = new WsOutboundTransport() + const httpOutboundTransport = new HttpOutboundTransport() // Setup the agent const agent = new Agent({ @@ -19,7 +27,7 @@ const run = async () => { id: 'requester', key: 'requester', }, - logger: new ConsoleLogger(LogLevel.test), + logger: new ConsoleLogger(LogLevel.info), autoAcceptConnections: true, }, modules: { @@ -30,6 +38,7 @@ const run = async () => { // Register transports agent.registerOutboundTransport(wsOutboundTransport) + agent.registerOutboundTransport(httpOutboundTransport) // Now agent will handle messages and events from Dummy protocol @@ -59,7 +68,7 @@ const run = async () => { // Send a dummy request and wait for response const record = await agent.modules.dummy.request(connectionRecord.id) - agent.config.logger.info(`Request received for Dummy Record: ${record.id}`) + agent.config.logger.info(`Request sent for Dummy Record: ${record.id}`) const dummyRecord = await firstValueFrom(subject) agent.config.logger.info(`Response received for Dummy Record: ${dummyRecord.id}`) diff --git a/samples/extension-module/responder.ts b/samples/extension-module/responder.ts index 9235db89c7..91b106ecf4 100644 --- a/samples/extension-module/responder.ts +++ b/samples/extension-module/responder.ts @@ -1,7 +1,7 @@ import type { DummyStateChangedEvent } from './dummy' import type { Socket } from 'net' -import { Agent, ConsoleLogger, LogLevel, WsOutboundTransport } from '@aries-framework/core' +import { Agent, ConsoleLogger, LogLevel } from '@aries-framework/core' import { agentDependencies, HttpInboundTransport, WsInboundTransport } from '@aries-framework/node' import express from 'express' import { Server } from 'ws' @@ -11,27 +11,27 @@ import { DummyModule, DummyEventTypes, DummyState } from './dummy' const run = async () => { // Create transports const port = process.env.RESPONDER_PORT ? Number(process.env.RESPONDER_PORT) : 3002 + const autoAcceptRequests = true const app = express() const socketServer = new Server({ noServer: true }) const httpInboundTransport = new HttpInboundTransport({ app, port }) const wsInboundTransport = new WsInboundTransport({ server: socketServer }) - const wsOutboundTransport = new WsOutboundTransport() // Setup the agent const agent = new Agent({ config: { label: 'Dummy-powered agent - responder', - endpoints: [`ws://localhost:${port}`], + endpoints: [`http://localhost:${port}`], walletConfig: { id: 'responder', key: 'responder', }, - logger: new ConsoleLogger(LogLevel.test), + logger: new ConsoleLogger(LogLevel.debug), autoAcceptConnections: true, }, modules: { - dummy: new DummyModule(), + dummy: new DummyModule({ autoAcceptRequests }), }, dependencies: agentDependencies, }) @@ -39,7 +39,6 @@ const run = async () => { // Register transports agent.registerInboundTransport(httpInboundTransport) agent.registerInboundTransport(wsInboundTransport) - agent.registerInboundTransport(wsOutboundTransport) // Allow to create invitation, no other way to ask for invitation yet app.get('/invitation', async (req, res) => { @@ -58,12 +57,15 @@ const run = async () => { }) }) - // Subscribe to dummy record events - agent.events.on(DummyEventTypes.StateChanged, async (event: DummyStateChangedEvent) => { - if (event.payload.dummyRecord.state === DummyState.RequestReceived) { - await agent.modules.dummy.respond(event.payload.dummyRecord.id) - } - }) + // If autoAcceptRequests is enabled, the handler will automatically respond + // (no need to subscribe to event and manually accept) + if (!autoAcceptRequests) { + agent.events.on(DummyEventTypes.StateChanged, async (event: DummyStateChangedEvent) => { + if (event.payload.dummyRecord.state === DummyState.RequestReceived) { + await agent.modules.dummy.respond(event.payload.dummyRecord.id) + } + }) + } agent.config.logger.info(`Responder listening to port ${port}`) } diff --git a/samples/extension-module/tests/dummy.e2e.test.ts b/samples/extension-module/tests/dummy.e2e.test.ts new file mode 100644 index 0000000000..c9aa891d02 --- /dev/null +++ b/samples/extension-module/tests/dummy.e2e.test.ts @@ -0,0 +1,101 @@ +import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' +import type { ConnectionRecord } from '@aries-framework/core' + +import { Agent } from '@aries-framework/core' +import { Subject } from 'rxjs' + +import { getAgentOptions, makeConnection } from '../../../packages/core/tests/helpers' +import testLogger from '../../../packages/core/tests/logger' +import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { DummyModule } from '../dummy/DummyModule' +import { DummyState } from '../dummy/repository' + +import { waitForDummyRecord } from './helpers' + +const bobAgentOptions = getAgentOptions( + 'Bob Dummy', + { + endpoints: ['rxjs:bob'], + }, + { + dummy: new DummyModule(), + } +) + +const aliceAgentOptions = getAgentOptions( + 'Alice Dummy', + { + endpoints: ['rxjs:alice'], + }, + { + dummy: new DummyModule(), + } +) + +describe('Dummy extension module test', () => { + let bobAgent: Agent<{ + dummy: DummyModule + }> + let aliceAgent: Agent<{ + dummy: DummyModule + }> + let aliceConnection: ConnectionRecord + + beforeEach(async () => { + const bobMessages = new Subject() + const aliceMessages = new Subject() + const subjectMap = { + 'rxjs:bob': bobMessages, + 'rxjs:alice': aliceMessages, + } + + bobAgent = new Agent(bobAgentOptions) + bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) + bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await bobAgent.initialize() + + aliceAgent = new Agent(aliceAgentOptions) + + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + ;[aliceConnection] = await makeConnection(aliceAgent, bobAgent) + }) + + afterEach(async () => { + await bobAgent.shutdown() + await bobAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice sends a request and Bob answers', async () => { + testLogger.test('Alice sends request to Bob') + let aliceDummyRecord = await aliceAgent.modules.dummy.request(aliceConnection.id) + + testLogger.test('Bob waits for request from Alice') + const bobDummyRecord = await waitForDummyRecord(bobAgent, { + threadId: aliceDummyRecord.threadId, + state: DummyState.RequestReceived, + }) + + testLogger.test('Bob sends response to Alice') + await bobAgent.modules.dummy.respond(bobDummyRecord.id) + + testLogger.test('Alice waits until Bob responds') + aliceDummyRecord = await waitForDummyRecord(aliceAgent, { + threadId: aliceDummyRecord.threadId, + state: DummyState.ResponseReceived, + }) + + const retrievedRecord = (await aliceAgent.modules.dummy.getAll()).find((item) => item.id === aliceDummyRecord.id) + expect(retrievedRecord).toMatchObject( + expect.objectContaining({ + id: aliceDummyRecord.id, + threadId: aliceDummyRecord.threadId, + state: DummyState.ResponseReceived, + }) + ) + }) +}) diff --git a/samples/extension-module/tests/helpers.ts b/samples/extension-module/tests/helpers.ts new file mode 100644 index 0000000000..4f06c00169 --- /dev/null +++ b/samples/extension-module/tests/helpers.ts @@ -0,0 +1,57 @@ +import type { DummyState } from '../dummy/repository' +import type { DummyStateChangedEvent } from '../dummy/services' +import type { Agent } from '@aries-framework/core' +import type { Observable } from 'rxjs' + +import { catchError, filter, firstValueFrom, map, ReplaySubject, timeout } from 'rxjs' + +import { DummyEventTypes } from '../dummy/services' + +export async function waitForDummyRecord( + agent: Agent, + options: { + threadId?: string + state?: DummyState + previousState?: DummyState | null + timeoutMs?: number + } +) { + const observable = agent.events.observable(DummyEventTypes.StateChanged) + + return waitForDummyRecordSubject(observable, options) +} + +export function waitForDummyRecordSubject( + subject: ReplaySubject | Observable, + { + threadId, + state, + previousState, + timeoutMs = 10000, + }: { + threadId?: string + state?: DummyState + previousState?: DummyState | null + timeoutMs?: number + } +) { + const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject + return firstValueFrom( + observable.pipe( + filter((e) => previousState === undefined || e.payload.previousState === previousState), + filter((e) => threadId === undefined || e.payload.dummyRecord.threadId === threadId), + filter((e) => state === undefined || e.payload.dummyRecord.state === state), + timeout(timeoutMs), + catchError(() => { + throw new Error( + `DummyStateChangedEvent event not emitted within specified timeout: { + previousState: ${previousState}, + threadId: ${threadId}, + state: ${state} + }` + ) + }), + map((e) => e.payload.dummyRecord) + ) + ) +} diff --git a/samples/extension-module/tests/setup.ts b/samples/extension-module/tests/setup.ts new file mode 100644 index 0000000000..226f7031fa --- /dev/null +++ b/samples/extension-module/tests/setup.ts @@ -0,0 +1 @@ +jest.setTimeout(20000) diff --git a/samples/extension-module/tsconfig.json b/samples/extension-module/tsconfig.json index 2e05131598..46efe6f721 100644 --- a/samples/extension-module/tsconfig.json +++ b/samples/extension-module/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "types": ["node"] + "types": ["jest"] } } From ef20f1ef420e5345825cc9e79f52ecfb191489fc Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Mon, 7 Nov 2022 05:58:23 -0300 Subject: [PATCH 071/125] fix(connections): do not log AgentContext object (#1085) Signed-off-by: Ariel Gentile --- .../src/modules/connections/DidExchangeProtocol.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/core/src/modules/connections/DidExchangeProtocol.ts b/packages/core/src/modules/connections/DidExchangeProtocol.ts index 9e5fa64b62..eed6f3fb49 100644 --- a/packages/core/src/modules/connections/DidExchangeProtocol.ts +++ b/packages/core/src/modules/connections/DidExchangeProtocol.ts @@ -135,7 +135,9 @@ export class DidExchangeProtocol { messageContext: InboundMessageContext, outOfBandRecord: OutOfBandRecord ): Promise { - this.logger.debug(`Process message ${DidExchangeRequestMessage.type.messageTypeUri} start`, messageContext) + this.logger.debug(`Process message ${DidExchangeRequestMessage.type.messageTypeUri} start`, { + message: messageContext.message, + }) outOfBandRecord.assertRole(OutOfBandRole.Sender) outOfBandRecord.assertState(OutOfBandState.AwaitResponse) @@ -275,7 +277,10 @@ export class DidExchangeProtocol { messageContext: InboundMessageContext, outOfBandRecord: OutOfBandRecord ): Promise { - this.logger.debug(`Process message ${DidExchangeResponseMessage.type.messageTypeUri} start`, messageContext) + this.logger.debug(`Process message ${DidExchangeResponseMessage.type.messageTypeUri} start`, { + message: messageContext.message, + }) + const { connection: connectionRecord, message } = messageContext if (!connectionRecord) { @@ -377,7 +382,10 @@ export class DidExchangeProtocol { messageContext: InboundMessageContext, outOfBandRecord: OutOfBandRecord ): Promise { - this.logger.debug(`Process message ${DidExchangeCompleteMessage.type.messageTypeUri} start`, messageContext) + this.logger.debug(`Process message ${DidExchangeCompleteMessage.type.messageTypeUri} start`, { + message: messageContext.message, + }) + const { connection: connectionRecord, message } = messageContext if (!connectionRecord) { From 15cfd91d1c6ba8e3f8355db4c4941fcbd85382ac Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Wed, 9 Nov 2022 11:41:19 -0300 Subject: [PATCH 072/125] fix(routing): async message pickup on init (#1093) Signed-off-by: Ariel Gentile --- packages/core/src/modules/routing/RecipientApi.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/modules/routing/RecipientApi.ts b/packages/core/src/modules/routing/RecipientApi.ts index dc99a1b46c..fd876a4cc7 100644 --- a/packages/core/src/modules/routing/RecipientApi.ts +++ b/packages/core/src/modules/routing/RecipientApi.ts @@ -101,7 +101,9 @@ export class RecipientApi { // Poll for messages from mediator const defaultMediator = await this.findDefaultMediator() if (defaultMediator) { - await this.initiateMessagePickup(defaultMediator) + this.initiateMessagePickup(defaultMediator).catch((error) => { + this.logger.warn(`Error initiating message pickup with mediator ${defaultMediator.id}`, { error }) + }) } } From 674775692bd60b2a0d8a726fa0ed3603b4fc724e Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht <61358536+blu3beri@users.noreply.github.com> Date: Fri, 11 Nov 2022 11:37:19 +0100 Subject: [PATCH 073/125] fix(demo): direct import to remove warnings (#1094) Signed-off-by: blu3beri --- demo/src/AliceInquirer.ts | 16 ++++++++-------- demo/src/BaseInquirer.ts | 4 ++-- demo/src/FaberInquirer.ts | 12 ++++++------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/demo/src/AliceInquirer.ts b/demo/src/AliceInquirer.ts index d11e7f6021..9752eab5aa 100644 --- a/demo/src/AliceInquirer.ts +++ b/demo/src/AliceInquirer.ts @@ -2,7 +2,7 @@ import type { CredentialExchangeRecord, ProofExchangeRecord } from '@aries-frame import { clear } from 'console' import { textSync } from 'figlet' -import inquirer from 'inquirer' +import { prompt } from 'inquirer' import { Alice } from './Alice' import { BaseInquirer, ConfirmOptions } from './BaseInquirer' @@ -42,10 +42,10 @@ export class AliceInquirer extends BaseInquirer { } private async getPromptChoice() { - if (this.alice.connectionRecordFaberId) return inquirer.prompt([this.inquireOptions(this.promptOptionsString)]) + if (this.alice.connectionRecordFaberId) return prompt([this.inquireOptions(this.promptOptionsString)]) const reducedOption = [PromptOptions.ReceiveConnectionUrl, PromptOptions.Exit, PromptOptions.Restart] - return inquirer.prompt([this.inquireOptions(reducedOption)]) + return prompt([this.inquireOptions(reducedOption)]) } public async processAnswer() { @@ -70,7 +70,7 @@ export class AliceInquirer extends BaseInquirer { } public async acceptCredentialOffer(credentialRecord: CredentialExchangeRecord) { - const confirm = await inquirer.prompt([this.inquireConfirmation(Title.CredentialOfferTitle)]) + const confirm = await prompt([this.inquireConfirmation(Title.CredentialOfferTitle)]) if (confirm.options === ConfirmOptions.No) { await this.alice.agent.credentials.declineOffer(credentialRecord.id) } else if (confirm.options === ConfirmOptions.Yes) { @@ -79,7 +79,7 @@ export class AliceInquirer extends BaseInquirer { } public async acceptProofRequest(proofRecord: ProofExchangeRecord) { - const confirm = await inquirer.prompt([this.inquireConfirmation(Title.ProofRequestTitle)]) + const confirm = await prompt([this.inquireConfirmation(Title.ProofRequestTitle)]) if (confirm.options === ConfirmOptions.No) { await this.alice.agent.proofs.declineRequest(proofRecord.id) } else if (confirm.options === ConfirmOptions.Yes) { @@ -89,7 +89,7 @@ export class AliceInquirer extends BaseInquirer { public async connection() { const title = Title.InvitationTitle - const getUrl = await inquirer.prompt([this.inquireInput(title)]) + const getUrl = await prompt([this.inquireInput(title)]) await this.alice.acceptConnection(getUrl.input) if (!this.alice.connected) return @@ -105,7 +105,7 @@ export class AliceInquirer extends BaseInquirer { } public async exit() { - const confirm = await inquirer.prompt([this.inquireConfirmation(Title.ConfirmTitle)]) + const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)]) if (confirm.options === ConfirmOptions.No) { return } else if (confirm.options === ConfirmOptions.Yes) { @@ -114,7 +114,7 @@ export class AliceInquirer extends BaseInquirer { } public async restart() { - const confirm = await inquirer.prompt([this.inquireConfirmation(Title.ConfirmTitle)]) + const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)]) if (confirm.options === ConfirmOptions.No) { await this.processAnswer() return diff --git a/demo/src/BaseInquirer.ts b/demo/src/BaseInquirer.ts index 6c43a5eb11..358d72b632 100644 --- a/demo/src/BaseInquirer.ts +++ b/demo/src/BaseInquirer.ts @@ -1,4 +1,4 @@ -import inquirer from 'inquirer' +import { prompt } from 'inquirer' import { Title } from './OutputClass' @@ -48,7 +48,7 @@ export class BaseInquirer { public async inquireMessage() { this.inputInquirer.message = Title.MessageTitle - const message = await inquirer.prompt([this.inputInquirer]) + const message = await prompt([this.inputInquirer]) return message.input[0] === 'q' ? null : message.input } diff --git a/demo/src/FaberInquirer.ts b/demo/src/FaberInquirer.ts index 98c1ccabb6..6324c5a1d6 100644 --- a/demo/src/FaberInquirer.ts +++ b/demo/src/FaberInquirer.ts @@ -1,6 +1,6 @@ import { clear } from 'console' import { textSync } from 'figlet' -import inquirer from 'inquirer' +import { prompt } from 'inquirer' import { BaseInquirer, ConfirmOptions } from './BaseInquirer' import { Faber } from './Faber' @@ -42,10 +42,10 @@ export class FaberInquirer extends BaseInquirer { } private async getPromptChoice() { - if (this.faber.outOfBandId) return inquirer.prompt([this.inquireOptions(this.promptOptionsString)]) + if (this.faber.outOfBandId) return prompt([this.inquireOptions(this.promptOptionsString)]) const reducedOption = [PromptOptions.CreateConnection, PromptOptions.Exit, PromptOptions.Restart] - return inquirer.prompt([this.inquireOptions(reducedOption)]) + return prompt([this.inquireOptions(reducedOption)]) } public async processAnswer() { @@ -80,7 +80,7 @@ export class FaberInquirer extends BaseInquirer { } public async exitUseCase(title: string) { - const confirm = await inquirer.prompt([this.inquireConfirmation(title)]) + const confirm = await prompt([this.inquireConfirmation(title)]) if (confirm.options === ConfirmOptions.No) { return false } else if (confirm.options === ConfirmOptions.Yes) { @@ -108,7 +108,7 @@ export class FaberInquirer extends BaseInquirer { } public async exit() { - const confirm = await inquirer.prompt([this.inquireConfirmation(Title.ConfirmTitle)]) + const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)]) if (confirm.options === ConfirmOptions.No) { return } else if (confirm.options === ConfirmOptions.Yes) { @@ -117,7 +117,7 @@ export class FaberInquirer extends BaseInquirer { } public async restart() { - const confirm = await inquirer.prompt([this.inquireConfirmation(Title.ConfirmTitle)]) + const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)]) if (confirm.options === ConfirmOptions.No) { await this.processAnswer() return From 574e6a62ebbd77902c50da821afdfd1b1558abe7 Mon Sep 17 00:00:00 2001 From: NB-MikeRichardson <93971245+NB-MikeRichardson@users.noreply.github.com> Date: Fri, 11 Nov 2022 15:03:09 +0200 Subject: [PATCH 074/125] feat: issue credentials v2 (W3C/JSON-LD) (#1092) Signed-off-by: Mike Richardson --- .gitignore | 1 - ...proof.credentials.propose-offerBbs.test.ts | 310 ++++++++++ .../src/modules/credentials/CredentialsApi.ts | 3 +- .../modules/credentials/CredentialsModule.ts | 2 + .../__tests__/CredentialsModule.test.ts | 5 +- .../formats/CredentialFormatService.ts | 3 +- .../formats/CredentialFormatServiceOptions.ts | 10 + .../IndyCredentialFormatService.test.ts | 437 ++++++++++++++ .../JsonLdCredentialFormatService.test.ts | 567 ++++++++++++++++++ .../src/modules/credentials/formats/index.ts | 1 + .../indy/IndyCredentialFormatService.ts | 7 +- .../formats/jsonld/JsonLdCredentialFormat.ts | 25 + .../jsonld/JsonLdCredentialFormatService.ts | 435 ++++++++++++++ .../formats/jsonld/JsonLdCredentialOptions.ts | 30 + .../formats/jsonld/JsonLdOptionsRFC0593.ts | 59 ++ .../credentials/formats/jsonld/index.ts | 4 + .../__tests__/V1CredentialServiceCred.test.ts | 4 +- .../v1-credentials-auto-accept.e2e.test.ts | 111 ++-- .../v2/CredentialFormatCoordinator.ts | 8 + .../protocol/v2/V2CredentialService.ts | 25 +- .../__tests__/V2CredentialServiceCred.test.ts | 9 + .../V2CredentialServiceOffer.test.ts | 8 + .../v2-credentials-auto-accept.e2e.test.ts | 3 - ...ldproof.connectionless-credentials.test.ts | 167 ++++++ ...v2.ldproof.credentials-auto-accept.test.ts | 382 ++++++++++++ ...f.credentials.propose-offerED25519.test.ts | 551 +++++++++++++++++ .../v2/handlers/V2IssueCredentialHandler.ts | 1 - .../v2/handlers/V2OfferCredentialHandler.ts | 1 - .../v2/handlers/V2RequestCredentialHandler.ts | 2 +- .../src/modules/dids/domain/DidDocument.ts | 3 +- .../proofs/formats/ProofFormatService.ts | 4 +- .../formats/indy/IndyProofFormatService.ts | 4 +- .../models/ProofFormatServiceOptions.ts | 2 +- .../vc/models/W3cCredentialServiceOptions.ts | 5 - packages/core/src/utils/objEqual.ts | 23 + packages/core/tests/helpers.ts | 33 +- yarn.lock | 326 +++++----- 37 files changed, 3305 insertions(+), 266 deletions(-) create mode 100644 packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts create mode 100644 packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts create mode 100644 packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts create mode 100644 packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts create mode 100644 packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts create mode 100644 packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialOptions.ts create mode 100644 packages/core/src/modules/credentials/formats/jsonld/JsonLdOptionsRFC0593.ts create mode 100644 packages/core/src/modules/credentials/formats/jsonld/index.ts create mode 100644 packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts create mode 100644 packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts create mode 100644 packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts create mode 100644 packages/core/src/utils/objEqual.ts diff --git a/.gitignore b/.gitignore index 76a2db60c0..4d15c7409b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,4 @@ coverage .DS_Store logs.txt logs/ -packages/core/src/__tests__/genesis-von.txn lerna-debug.log \ No newline at end of file diff --git a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts new file mode 100644 index 0000000000..2ca92faa8d --- /dev/null +++ b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts @@ -0,0 +1,310 @@ +import type { Agent } from '../../core/src/agent/Agent' +import type { ConnectionRecord } from '../../core/src/modules/connections' +import type { SignCredentialOptionsRFC0593 } from '../../core/src/modules/credentials/formats/jsonld' +import type { Wallet } from '../../core/src/wallet' + +import { InjectionSymbols } from '../../core/src/constants' +import { KeyType } from '../../core/src/crypto' +import { CredentialState } from '../../core/src/modules/credentials/models' +import { V2IssueCredentialMessage } from '../../core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage' +import { V2OfferCredentialMessage } from '../../core/src/modules/credentials/protocol/v2/messages/V2OfferCredentialMessage' +import { CredentialExchangeRecord } from '../../core/src/modules/credentials/repository/CredentialExchangeRecord' +import { DidKey } from '../../core/src/modules/dids' +import { CREDENTIALS_CONTEXT_V1_URL, SECURITY_CONTEXT_BBS_URL } from '../../core/src/modules/vc' +import { W3cCredential } from '../../core/src/modules/vc/models/credential/W3cCredential' +import { DidCommMessageRepository } from '../../core/src/storage' +import { JsonTransformer } from '../../core/src/utils/JsonTransformer' +import { setupCredentialTests, waitForCredentialRecord } from '../../core/tests/helpers' +import testLogger from '../../core/tests/logger' + +import { describeSkipNode17And18 } from './util' + +let faberAgent: Agent +let aliceAgent: Agent +let aliceConnection: ConnectionRecord +let aliceCredentialRecord: CredentialExchangeRecord +let faberCredentialRecord: CredentialExchangeRecord + +const TEST_LD_DOCUMENT = { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: '', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, +} + +describeSkipNode17And18('credentials, BBS+ signature', () => { + let wallet + let issuerDidKey: DidKey + let didCommMessageRepository: DidCommMessageRepository + let signCredentialOptions: SignCredentialOptionsRFC0593 + const seed = 'testseed000000000000000000000001' + beforeAll(async () => { + ;({ faberAgent, aliceAgent, aliceConnection } = await setupCredentialTests( + 'Faber Agent Credentials LD BBS+', + 'Alice Agent Credentials LD BBS+' + )) + wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) + await wallet.createDid({ seed }) + const key = await wallet.createKey({ keyType: KeyType.Bls12381g2, seed }) + + issuerDidKey = new DidKey(key) + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice starts with V2 (ld format, BbsBlsSignature2020 signature) credential proposal to Faber', async () => { + testLogger.test('Alice sends (v2 jsonld) credential proposal to Faber') + // set the propose options + + const credentialJson = TEST_LD_DOCUMENT + credentialJson.issuer = issuerDidKey.did + + const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) + + signCredentialOptions = { + credential, + options: { + proofType: 'BbsBlsSignature2020', + proofPurpose: 'assertionMethod', + }, + } + + testLogger.test('Alice sends (v2, Indy) credential proposal to Faber') + + const credentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + credentialFormats: { + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test for W3C Credentials', + }) + + expect(credentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(credentialExchangeRecord.protocolVersion).toEqual('v2') + expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) + expect(credentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential proposal from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: credentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + testLogger.test('Faber sends credential offer to Alice') + await faberAgent.credentials.acceptProposal({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 W3C Offer', + credentialFormats: { + jsonld: signCredentialOptions, + }, + }) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) + + const offerMessage = await didCommMessageRepository.findAgentMessage(aliceAgent.context, { + associatedRecordId: aliceCredentialRecord.id, + messageClass: V2OfferCredentialMessage, + }) + + expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', + '@id': expect.any(String), + comment: 'V2 W3C Offer', + formats: [ + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc-detail@v1.0', + }, + ], + 'offers~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~service': undefined, + '~attach': undefined, + '~please_ack': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + credential_preview: expect.any(Object), + replacement_id: undefined, + }) + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + + if (!aliceCredentialRecord.connectionId) { + throw new Error('Missing Connection Id') + } + + const offerCredentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + credentialFormats: { + jsonld: undefined, + }, + }) + + expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential request from Alice') + await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + + await faberAgent.credentials.acceptRequest({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 W3C Offer', + credentialFormats: { + jsonld: signCredentialOptions, + }, + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Alice sends credential ack to Faber') + await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: expect.any(String), + connectionId: expect.any(String), + state: CredentialState.CredentialReceived, + }) + + const credentialMessage = await didCommMessageRepository.getAgentMessage(faberAgent.context, { + associatedRecordId: faberCredentialRecord.id, + messageClass: V2IssueCredentialMessage, + }) + + const w3cCredential = credentialMessage.credentialAttachments[0].getDataAsJson() + + expect(w3cCredential).toMatchObject({ + context: [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: + 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + proof: { + type: 'BbsBlsSignature2020', + created: expect.any(String), + verificationMethod: + 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa#zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + proofPurpose: 'assertionMethod', + proofValue: expect.any(String), + }, + }) + + expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', + '@id': expect.any(String), + comment: 'V2 W3C Offer', + formats: [ + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc@1.0', + }, + ], + 'credentials~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~please_ack': { on: ['RECEIPT'] }, + '~service': undefined, + '~attach': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + }) + }) +}) diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index 110b24867b..419f6cfc67 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -21,6 +21,7 @@ import type { } from './CredentialsApiOptions' import type { CredentialFormat } from './formats' import type { IndyCredentialFormat } from './formats/indy/IndyCredentialFormat' +import type { JsonLdCredentialFormat } from './formats/jsonld/JsonLdCredentialFormat' import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' import type { CredentialService } from './services/CredentialService' @@ -92,7 +93,7 @@ export interface CredentialsApi[] = [V1CredentialService, V2CredentialService] > implements CredentialsApi { diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index a54a20b1a7..8e9926ff87 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -7,6 +7,7 @@ import { Protocol } from '../../agent/models' import { CredentialsApi } from './CredentialsApi' import { CredentialsModuleConfig } from './CredentialsModuleConfig' import { IndyCredentialFormatService } from './formats/indy' +import { JsonLdCredentialFormatService } from './formats/jsonld/JsonLdCredentialFormatService' import { RevocationNotificationService } from './protocol/revocation-notification/services' import { V1CredentialService } from './protocol/v1' import { V2CredentialService } from './protocol/v2' @@ -60,5 +61,6 @@ export class CredentialsModule implements Module { // Credential Formats dependencyManager.registerSingleton(IndyCredentialFormatService) + dependencyManager.registerSingleton(JsonLdCredentialFormatService) } } diff --git a/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts index cb76cc2840..9aec292944 100644 --- a/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts +++ b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts @@ -4,6 +4,7 @@ import { CredentialsApi } from '../CredentialsApi' import { CredentialsModule } from '../CredentialsModule' import { CredentialsModuleConfig } from '../CredentialsModuleConfig' import { IndyCredentialFormatService } from '../formats' +import { JsonLdCredentialFormatService } from '../formats/jsonld/JsonLdCredentialFormatService' import { V1CredentialService, V2CredentialService } from '../protocol' import { RevocationNotificationService } from '../protocol/revocation-notification/services' import { CredentialRepository } from '../repository' @@ -29,11 +30,13 @@ describe('CredentialsModule', () => { expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) expect(dependencyManager.registerInstance).toHaveBeenCalledWith(CredentialsModuleConfig, credentialsModule.config) - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(5) + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(6) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V1CredentialService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V2CredentialService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(RevocationNotificationService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(CredentialRepository) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyCredentialFormatService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(JsonLdCredentialFormatService) }) }) diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts index 68a8d5ab8d..f16edfb147 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts @@ -15,6 +15,7 @@ import type { FormatAutoRespondOfferOptions, FormatAutoRespondProposalOptions, FormatAutoRespondRequestOptions, + FormatProcessCredentialOptions, } from './CredentialFormatServiceOptions' import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' @@ -58,7 +59,7 @@ export abstract class CredentialFormatService // credential methods - abstract processCredential(agentContext: AgentContext, options: FormatProcessOptions): Promise + abstract processCredential(agentContext: AgentContext, options: FormatProcessCredentialOptions): Promise // auto accept methods abstract shouldAutoRespondToProposal(agentContext: AgentContext, options: FormatAutoRespondProposalOptions): boolean diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts index 1a6cc4db11..0c4d6044af 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts @@ -40,6 +40,10 @@ export interface FormatProcessOptions { credentialRecord: CredentialExchangeRecord } +export interface FormatProcessCredentialOptions extends FormatProcessOptions { + requestAttachment: Attachment +} + export interface FormatCreateProposalOptions { credentialRecord: CredentialExchangeRecord credentialFormats: CredentialFormatPayload<[CF], 'createProposal'> @@ -89,6 +93,12 @@ export interface FormatAcceptRequestOptions { offerAttachment?: Attachment } +export interface FormatAcceptCredentialOptions { + credentialRecord: CredentialExchangeRecord + attachId?: string + requestAttachment: Attachment + offerAttachment?: Attachment +} // Auto accept method interfaces export interface FormatAutoRespondProposalOptions { credentialRecord: CredentialExchangeRecord diff --git a/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts new file mode 100644 index 0000000000..fee7df817f --- /dev/null +++ b/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts @@ -0,0 +1,437 @@ +import type { AgentContext } from '../../../../agent' +import type { AgentConfig } from '../../../../agent/AgentConfig' +import type { ParseRevocationRegistryDefinitionTemplate } from '../../../ledger/services/IndyLedgerService' +import type { CredentialFormatService } from '../../formats' +import type { IndyCredentialFormat } from '../../formats/indy/IndyCredentialFormat' +import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' +import type { V2OfferCredentialMessageOptions } from '../../protocol/v2/messages/V2OfferCredentialMessage' +import type { CustomCredentialTags } from '../../repository/CredentialExchangeRecord' +import type { RevocRegDef } from 'indy-sdk' + +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' +import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' +import { JsonEncoder } from '../../../../utils/JsonEncoder' +import { ConnectionService } from '../../../connections/services/ConnectionService' +import { DidResolverService } from '../../../dids/services/DidResolverService' +import { IndyHolderService } from '../../../indy/services/IndyHolderService' +import { IndyIssuerService } from '../../../indy/services/IndyIssuerService' +import { IndyLedgerService } from '../../../ledger/services/IndyLedgerService' +import { credDef, credReq, schema } from '../../__tests__/fixtures' +import { IndyCredentialFormatService } from '../../formats' +import { IndyCredentialUtils } from '../../formats/indy/IndyCredentialUtils' +import { CredentialState } from '../../models' +import { + INDY_CREDENTIAL_ATTACHMENT_ID, + INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, +} from '../../protocol/v1/messages' +import { V2CredentialPreview } from '../../protocol/v2/messages' +import { V2OfferCredentialMessage } from '../../protocol/v2/messages/V2OfferCredentialMessage' +import { CredentialMetadataKeys } from '../../repository' +import { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' + +jest.mock('../../../../modules/ledger/services/IndyLedgerService') +jest.mock('../../../indy/services/IndyHolderService') +jest.mock('../../../indy/services/IndyIssuerService') +jest.mock('../../../dids/services/DidResolverService') +jest.mock('../../../connections/services/ConnectionService') + +const IndyLedgerServiceMock = IndyLedgerService as jest.Mock +const IndyHolderServiceMock = IndyHolderService as jest.Mock +const IndyIssuerServiceMock = IndyIssuerService as jest.Mock +const ConnectionServiceMock = ConnectionService as jest.Mock +const DidResolverServiceMock = DidResolverService as jest.Mock + +const values = { + x: { + raw: 'x', + encoded: 'y', + }, +} +const cred = { + schema_id: 'xsxs', + cred_def_id: 'xdxd', + rev_reg_id: 'x', + values: values, + signature: undefined, + signature_correctness_proof: undefined, +} + +const revDef: RevocRegDef = { + id: 'x', + revocDefType: 'CL_ACCUM', + tag: 'x', + credDefId: 'x', + value: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + maxCredNum: 33, + tailsHash: 'd', + tailsLocation: 'x', + publicKeys: ['x'], + }, + ver: 't', +} + +const revocationTemplate: ParseRevocationRegistryDefinitionTemplate = { + revocationRegistryDefinition: revDef, + revocationRegistryDefinitionTxnTime: 42, +} + +const credentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', +}) + +const offerAttachment = new Attachment({ + id: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + mimeType: 'application/json', + data: new AttachmentData({ + base64: + 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0', + }), +}) + +const requestAttachment = new Attachment({ + id: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(credReq), + }), +}) + +const credentialAttachment = new Attachment({ + id: INDY_CREDENTIAL_ATTACHMENT_ID, + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64({ + values: IndyCredentialUtils.convertAttributesToValues(credentialPreview.attributes), + }), + }), +}) + +// A record is deserialized to JSON when it's stored into the storage. We want to simulate this behaviour for `offer` +// object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. +const mockCredentialRecord = ({ + state, + metadata, + threadId, + connectionId, + tags, + id, + credentialAttributes, +}: { + state?: CredentialState + metadata?: { indyRequest: Record } + tags?: CustomCredentialTags + threadId?: string + connectionId?: string + id?: string + credentialAttributes?: CredentialPreviewAttribute[] +} = {}) => { + const offerOptions: V2OfferCredentialMessageOptions = { + id: '', + formats: [ + { + attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + format: 'hlindy/cred-abstract@v2.0', + }, + ], + comment: 'some comment', + credentialPreview: credentialPreview, + offerAttachments: [offerAttachment], + replacementId: undefined, + } + const offerMessage = new V2OfferCredentialMessage(offerOptions) + + const credentialRecord = new CredentialExchangeRecord({ + id, + credentialAttributes: credentialAttributes || credentialPreview.attributes, + state: state || CredentialState.OfferSent, + threadId: threadId ?? offerMessage.id, + connectionId: connectionId ?? '123', + tags, + protocolVersion: 'v2', + }) + + if (metadata?.indyRequest) { + credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, { ...metadata.indyRequest }) + } + + return credentialRecord +} +let indyFormatService: CredentialFormatService +let indyLedgerService: IndyLedgerService +let indyIssuerService: IndyIssuerService +let indyHolderService: IndyHolderService +let didResolverService: DidResolverService +let connectionService: ConnectionService +let agentConfig: AgentConfig +let credentialRecord: CredentialExchangeRecord + +describe('Indy CredentialFormatService', () => { + let agentContext: AgentContext + beforeEach(async () => { + agentContext = getAgentContext() + agentConfig = getAgentConfig('CredentialServiceTest') + + indyIssuerService = new IndyIssuerServiceMock() + indyHolderService = new IndyHolderServiceMock() + indyLedgerService = new IndyLedgerServiceMock() + didResolverService = new DidResolverServiceMock() + connectionService = new ConnectionServiceMock() + + indyFormatService = new IndyCredentialFormatService( + indyIssuerService, + indyLedgerService, + indyHolderService, + connectionService, + didResolverService, + agentConfig.logger + ) + + mockFunction(indyLedgerService.getSchema).mockReturnValue(Promise.resolve(schema)) + }) + + describe('Create Credential Proposal / Offer', () => { + test(`Creates Credential Proposal`, async () => { + // when + const { attachment, previewAttributes, format } = await indyFormatService.createProposal(agentContext, { + credentialRecord: mockCredentialRecord(), + credentialFormats: { + indy: { + credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', + schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + schemaName: 'ahoy', + schemaVersion: '1.0', + schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', + issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + attributes: credentialPreview.attributes, + }, + }, + }) + + // then + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJzY2hlbWFfaXNzdWVyX2RpZCI6IkdNbTR2TXc4TExyTEpqcDgxa1JSTHAiLCJzY2hlbWFfaWQiOiJxN0FUd1RZYlFEZ2lpZ1ZpalVBZWo6Mjp0ZXN0OjEuMCIsInNjaGVtYV9uYW1lIjoiYWhveSIsInNjaGVtYV92ZXJzaW9uIjoiMS4wIiwiY3JlZF9kZWZfaWQiOiJUaDdNcFRhUlpWUlluUGlhYmRzODFZOjM6Q0w6MTc6VEFHIiwiaXNzdWVyX2RpZCI6IkdNbTR2TXc4TExyTEpqcDgxa1JSTHAifQ==', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + + expect(previewAttributes).toMatchObject([ + { + mimeType: 'text/plain', + name: 'name', + value: 'John', + }, + { + mimeType: 'text/plain', + name: 'age', + value: '99', + }, + ]) + + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'hlindy/cred-filter@v2.0', + }) + }) + + test(`Creates Credential Offer`, async () => { + // when + const { attachment, previewAttributes, format } = await indyFormatService.createOffer(agentContext, { + credentialRecord: mockCredentialRecord(), + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', + }, + }, + }) + + // then + expect(indyIssuerService.createCredentialOffer).toHaveBeenCalledTimes(1) + + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0=', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + + expect(previewAttributes).toMatchObject([ + { + mimeType: 'text/plain', + name: 'name', + value: 'John', + }, + { + mimeType: 'text/plain', + name: 'age', + value: '99', + }, + ]) + + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'hlindy/cred-abstract@v2.0', + }) + }) + }) + describe('Process Credential Offer', () => { + test(`processes credential offer - returns modified credential record (adds metadata)`, async () => { + // given + const credentialRecord = mockCredentialRecord({ + state: CredentialState.OfferReceived, + threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }) + + // when + await indyFormatService.processOffer(agentContext, { attachment: offerAttachment, credentialRecord }) + }) + }) + + describe('Create Credential Request', () => { + test('returns credential request message base on existing credential offer message', async () => { + // given + const credentialRecord = mockCredentialRecord({ + state: CredentialState.OfferReceived, + threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }) + + mockFunction(indyLedgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) + + // when + const { format, attachment } = await indyFormatService.acceptOffer(agentContext, { + credentialRecord, + credentialFormats: { + indy: { + holderDid: 'holderDid', + }, + }, + offerAttachment, + }) + + // then + expect(indyHolderService.createCredentialRequest).toHaveBeenCalledTimes(1) + + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJwcm92ZXJfZGlkIjoiaG9sZGVyRGlkIiwiY3JlZF9kZWZfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjM6Q0w6MTY6VEFHIiwiYmxpbmRlZF9tcyI6e30sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnt9LCJub25jZSI6Im5vbmNlIn0=', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'hlindy/cred-req@v2.0', + }) + + const credentialRequestMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyCredential) + + expect(credentialRequestMetadata?.schemaId).toBe('aaa') + expect(credentialRequestMetadata?.credentialDefinitionId).toBe('Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG') + }) + }) + + describe('Accept request', () => { + test('Creates a credentials', async () => { + // given + const credentialRecord = mockCredentialRecord({ + state: CredentialState.RequestReceived, + threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }) + + mockFunction(indyIssuerService.createCredential).mockReturnValue(Promise.resolve([cred, 'x'])) + + // when + const { format, attachment } = await indyFormatService.acceptRequest(agentContext, { + credentialRecord, + requestAttachment, + offerAttachment, + attachId: INDY_CREDENTIAL_ATTACHMENT_ID, + }) + + expect(attachment).toMatchObject({ + id: 'libindy-cred-0', + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJzY2hlbWFfaWQiOiJ4c3hzIiwiY3JlZF9kZWZfaWQiOiJ4ZHhkIiwicmV2X3JlZ19pZCI6IngiLCJ2YWx1ZXMiOnsieCI6eyJyYXciOiJ4IiwiZW5jb2RlZCI6InkifX19', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'hlindy/cred@v2.0', + }) + }) + }) + + describe('Process Credential', () => { + test('finds credential record by thread ID and saves credential attachment into the wallet', async () => { + // given + credentialRecord = mockCredentialRecord({ + state: CredentialState.RequestSent, + metadata: { indyRequest: { cred_req: 'meta-data' } }, + }) + mockFunction(indyLedgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) + mockFunction(indyLedgerService.getRevocationRegistryDefinition).mockReturnValue( + Promise.resolve(revocationTemplate) + ) + mockFunction(indyHolderService.storeCredential).mockReturnValue(Promise.resolve('100')) + + // when + await indyFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachment, + credentialRecord, + }) + + // then + expect(indyHolderService.storeCredential).toHaveBeenCalledTimes(1) + expect(credentialRecord.credentials.length).toBe(1) + expect(credentialRecord.credentials[0].credentialRecordType).toBe('indy') + expect(credentialRecord.credentials[0].credentialRecordId).toBe('100') + }) + }) +}) diff --git a/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts new file mode 100644 index 0000000000..a66b8abe65 --- /dev/null +++ b/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts @@ -0,0 +1,567 @@ +import type { AgentContext } from '../../../../agent' +import type { CredentialFormatService } from '../../formats' +import type { + JsonLdAcceptRequestOptions, + JsonLdCredentialFormat, + SignCredentialOptionsRFC0593, +} from '../../formats/jsonld/JsonLdCredentialFormat' +import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' +import type { V2OfferCredentialMessageOptions } from '../../protocol/v2/messages/V2OfferCredentialMessage' +import type { CustomCredentialTags } from '../../repository/CredentialExchangeRecord' + +import { getAgentContext, mockFunction } from '../../../../../tests/helpers' +import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' +import { JsonTransformer } from '../../../../utils' +import { JsonEncoder } from '../../../../utils/JsonEncoder' +import { DidResolverService } from '../../../dids/services/DidResolverService' +import { W3cCredentialRecord, W3cCredentialService } from '../../../vc' +import { Ed25519Signature2018Fixtures } from '../../../vc/__tests__/fixtures' +import { CREDENTIALS_CONTEXT_V1_URL } from '../../../vc/constants' +import { W3cVerifiableCredential } from '../../../vc/models' +import { W3cCredential } from '../../../vc/models/credential/W3cCredential' +import { JsonLdCredentialFormatService } from '../../formats/jsonld/JsonLdCredentialFormatService' +import { CredentialState } from '../../models' +import { INDY_CREDENTIAL_OFFER_ATTACHMENT_ID } from '../../protocol/v1/messages' +import { V2CredentialPreview } from '../../protocol/v2/messages' +import { V2OfferCredentialMessage } from '../../protocol/v2/messages/V2OfferCredentialMessage' +import { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' + +jest.mock('../../../vc/W3cCredentialService') +jest.mock('../../../dids/services/DidResolverService') + +const W3cCredentialServiceMock = W3cCredentialService as jest.Mock +const DidResolverServiceMock = DidResolverService as jest.Mock + +const didDocument = { + context: [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + verificationMethod: [ + { + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + type: 'Ed25519VerificationKey2018', + controller: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + publicKeyBase58: '3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx', + }, + ], + authentication: [ + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + ], + assertionMethod: [ + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + ], + keyAgreement: [ + { + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6LSbkodSr6SU2trs8VUgnrnWtSm7BAPG245ggrBmSrxbv1R', + type: 'X25519KeyAgreementKey2019', + controller: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + publicKeyBase58: '5dTvYHaNaB7mk7iA9LqCJEHG2dGZQsvoi8WGzDRtYEf', + }, + ], +} + +const vcJson = { + ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + credentialSubject: { + ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED.credentialSubject, + alumniOf: 'oops', + }, +} + +const vc = JsonTransformer.fromJSON(vcJson, W3cVerifiableCredential) + +const credentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', +}) + +const offerAttachment = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: + 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0', + }), +}) + +const credentialAttachment = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(vc), + }), +}) + +// A record is deserialized to JSON when it's stored into the storage. We want to simulate this behaviour for `offer` +// object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. +const mockCredentialRecord = ({ + state, + threadId, + connectionId, + tags, + id, + credentialAttributes, +}: { + state?: CredentialState + tags?: CustomCredentialTags + threadId?: string + connectionId?: string + id?: string + credentialAttributes?: CredentialPreviewAttribute[] +} = {}) => { + const offerOptions: V2OfferCredentialMessageOptions = { + id: '', + formats: [ + { + attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + format: 'hlindy/cred-abstract@v2.0', + }, + ], + comment: 'some comment', + credentialPreview: credentialPreview, + offerAttachments: [offerAttachment], + replacementId: undefined, + } + const offerMessage = new V2OfferCredentialMessage(offerOptions) + + const credentialRecord = new CredentialExchangeRecord({ + id, + credentialAttributes: credentialAttributes || credentialPreview.attributes, + state: state || CredentialState.OfferSent, + threadId: threadId ?? offerMessage.id, + connectionId: connectionId ?? '123', + tags, + protocolVersion: 'v2', + }) + + return credentialRecord +} +const inputDoc = { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, + alumniOf: 'oops', + }, +} +const verificationMethod = `8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K#8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K` +const credential = JsonTransformer.fromJSON(inputDoc, W3cCredential) + +const signCredentialOptions: SignCredentialOptionsRFC0593 = { + credential, + options: { + proofPurpose: 'assertionMethod', + proofType: 'Ed25519Signature2018', + }, +} + +const requestAttachment = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(signCredentialOptions), + }), +}) +let jsonldFormatService: CredentialFormatService +let w3cCredentialService: W3cCredentialService +let didResolver: DidResolverService + +describe('JsonLd CredentialFormatService', () => { + let agentContext: AgentContext + beforeEach(async () => { + agentContext = getAgentContext() + w3cCredentialService = new W3cCredentialServiceMock() + didResolver = new DidResolverServiceMock() + jsonldFormatService = new JsonLdCredentialFormatService(w3cCredentialService, didResolver) + }) + + describe('Create JsonLd Credential Proposal / Offer', () => { + test(`Creates JsonLd Credential Proposal`, async () => { + // when + const { attachment, format } = await jsonldFormatService.createProposal(agentContext, { + credentialRecord: mockCredentialRecord(), + credentialFormats: { + jsonld: signCredentialOptions, + }, + }) + + // then + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJjcmVkZW50aWFsIjp7ImNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZ2czNDJZY3B1azI2M1I5ZDhBcTZNVWF4UG4xRERlSHlHbzM4RWVmWG1nREwiLCJpc3N1YW5jZURhdGUiOiIyMDE3LTEwLTIyVDEyOjIzOjQ4WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjaGVsb3Igb2YgU2NpZW5jZSBhbmQgQXJ0cyJ9LCJhbHVtbmlPZiI6Im9vcHMifX0sIm9wdGlvbnMiOnsicHJvb2ZQdXJwb3NlIjoiYXNzZXJ0aW9uTWV0aG9kIiwicHJvb2ZUeXBlIjoiRWQyNTUxOVNpZ25hdHVyZTIwMTgifX0=', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'aries/ld-proof-vc-detail@v1.0', + }) + }) + + test(`Creates JsonLd Credential Offer`, async () => { + // when + const { attachment, previewAttributes, format } = await jsonldFormatService.createOffer(agentContext, { + credentialFormats: { + jsonld: signCredentialOptions, + }, + credentialRecord: mockCredentialRecord(), + }) + + // then + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJjcmVkZW50aWFsIjp7ImNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZ2czNDJZY3B1azI2M1I5ZDhBcTZNVWF4UG4xRERlSHlHbzM4RWVmWG1nREwiLCJpc3N1YW5jZURhdGUiOiIyMDE3LTEwLTIyVDEyOjIzOjQ4WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjaGVsb3Igb2YgU2NpZW5jZSBhbmQgQXJ0cyJ9LCJhbHVtbmlPZiI6Im9vcHMifX0sIm9wdGlvbnMiOnsicHJvb2ZQdXJwb3NlIjoiYXNzZXJ0aW9uTWV0aG9kIiwicHJvb2ZUeXBlIjoiRWQyNTUxOVNpZ25hdHVyZTIwMTgifX0=', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + + expect(previewAttributes).toBeUndefined() + + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'aries/ld-proof-vc-detail@v1.0', + }) + }) + }) + + describe('Accept Credential Offer', () => { + test('returns credential request message base on existing credential offer message', async () => { + // when + const { attachment, format } = await jsonldFormatService.acceptOffer(agentContext, { + credentialFormats: { + jsonld: undefined, + }, + offerAttachment, + credentialRecord: mockCredentialRecord({ + state: CredentialState.OfferReceived, + threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }), + }) + + // then + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: + 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0=', + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'aries/ld-proof-vc-detail@v1.0', + }) + }) + }) + + describe('Accept Request', () => { + const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' + + test('Derive Verification Method', async () => { + mockFunction(didResolver.resolveDidDocument).mockReturnValue(Promise.resolve(didDocument as any)) + mockFunction(w3cCredentialService.getVerificationMethodTypesByProofType).mockReturnValue([ + 'Ed25519VerificationKey2018', + ]) + + const service = jsonldFormatService as JsonLdCredentialFormatService + const credentialRequest = requestAttachment.getDataAsJson() + + // calls private method in the format service + const verificationMethod = await service['deriveVerificationMethod']( + agentContext, + signCredentialOptions.credential, + credentialRequest + ) + expect(verificationMethod).toBe( + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL' + ) + }) + + test('Creates a credential', async () => { + // given + mockFunction(w3cCredentialService.signCredential).mockReturnValue(Promise.resolve(vc)) + + const credentialRecord = mockCredentialRecord({ + state: CredentialState.RequestReceived, + threadId, + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }) + + const acceptRequestOptions: JsonLdAcceptRequestOptions = { + ...signCredentialOptions, + verificationMethod, + } + + const { format, attachment } = await jsonldFormatService.acceptRequest(agentContext, { + credentialRecord, + credentialFormats: { + jsonld: acceptRequestOptions, + }, + requestAttachment, + offerAttachment, + }) + //then + expect(w3cCredentialService.signCredential).toHaveBeenCalledTimes(1) + + expect(attachment).toMatchObject({ + id: expect.any(String), + description: undefined, + filename: undefined, + mimeType: 'application/json', + lastmodTime: undefined, + byteCount: undefined, + data: { + base64: expect.any(String), + json: undefined, + links: undefined, + jws: undefined, + sha256: undefined, + }, + }) + expect(format).toMatchObject({ + attachId: expect.any(String), + format: 'aries/ld-proof-vc@1.0', + }) + }) + }) + + describe('Process Credential', () => { + const credentialRecord = mockCredentialRecord({ + state: CredentialState.RequestSent, + }) + let w3c: W3cCredentialRecord + let signCredentialOptionsWithProperty: SignCredentialOptionsRFC0593 + beforeEach(async () => { + signCredentialOptionsWithProperty = signCredentialOptions + signCredentialOptionsWithProperty.options = { + proofPurpose: 'assertionMethod', + proofType: 'Ed25519Signature2018', + } + + w3c = new W3cCredentialRecord({ + id: 'foo', + createdAt: new Date(), + credential: vc, + tags: { + expandedTypes: [ + 'https://www.w3.org/2018/credentials#VerifiableCredential', + 'https://example.org/examples#UniversityDegreeCredential', + ], + }, + }) + }) + test('finds credential record by thread ID and saves credential attachment into the wallet', async () => { + // given + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + + // when + await jsonldFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachment, + credentialRecord, + }) + + // then + expect(w3cCredentialService.storeCredential).toHaveBeenCalledTimes(1) + expect(credentialRecord.credentials.length).toBe(1) + expect(credentialRecord.credentials[0].credentialRecordType).toBe('w3c') + expect(credentialRecord.credentials[0].credentialRecordId).toBe('foo') + }) + + test('throws error if credential subject not equal to request subject', async () => { + const vcJson = { + ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + credentialSubject: { + ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED.credentialSubject, + // missing alumni + }, + } + + const vc = JsonTransformer.fromJSON(vcJson, W3cVerifiableCredential) + const credentialAttachment = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(vc), + }), + }) + + // given + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + + // when/then + await expect( + jsonldFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachment, + credentialRecord, + }) + ).rejects.toThrow('Received credential subject does not match subject from credential request') + }) + + test('throws error if credential domain not equal to request domain', async () => { + signCredentialOptionsWithProperty.options.domain = 'https://test.com' + const requestAttachmentWithDomain = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(signCredentialOptionsWithProperty), + }), + }) + // given + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + + // when/then + await expect( + jsonldFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachmentWithDomain, + credentialRecord, + }) + ).rejects.toThrow('Received credential proof domain does not match domain from credential request') + }) + + test('throws error if credential challenge not equal to request challenge', async () => { + signCredentialOptionsWithProperty.options.challenge = '7bf32d0b-39d4-41f3-96b6-45de52988e4c' + const requestAttachmentWithChallenge = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(signCredentialOptionsWithProperty), + }), + }) + + // given + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + + // when/then + await expect( + jsonldFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachmentWithChallenge, + credentialRecord, + }) + ).rejects.toThrow('Received credential proof challenge does not match challenge from credential request') + }) + + test('throws error if credential proof type not equal to request proof type', async () => { + signCredentialOptionsWithProperty.options.proofType = 'Ed25519Signature2016' + const requestAttachmentWithProofType = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(signCredentialOptionsWithProperty), + }), + }) + + // given + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + + // when/then + await expect( + jsonldFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachmentWithProofType, + credentialRecord, + }) + ).rejects.toThrow('Received credential proof type does not match proof type from credential request') + }) + + test('throws error if credential proof purpose not equal to request proof purpose', async () => { + signCredentialOptionsWithProperty.options.proofPurpose = 'authentication' + const requestAttachmentWithProofPurpose = new Attachment({ + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(signCredentialOptionsWithProperty), + }), + }) + + // given + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + + // when/then + await expect( + jsonldFormatService.processCredential(agentContext, { + attachment: credentialAttachment, + requestAttachment: requestAttachmentWithProofPurpose, + credentialRecord, + }) + ).rejects.toThrow('Received credential proof purpose does not match proof purpose from credential request') + }) + + test('are credentials equal', async () => { + const message1 = new Attachment({ + id: 'cdb0669b-7cd6-46bc-b1c7-7034f86083ac', + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(inputDoc), + }), + }) + + const message2 = new Attachment({ + id: '9a8ff4fb-ac86-478f-b7f9-fbf3f9cc60e6', + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(inputDoc), + }), + }) + + // indirectly test areCredentialsEqual as black box rather than expose that method in the API + let areCredentialsEqual = jsonldFormatService.shouldAutoRespondToProposal(agentContext, { + credentialRecord, + proposalAttachment: message1, + offerAttachment: message2, + }) + expect(areCredentialsEqual).toBe(true) + + const inputDoc2 = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + } + message2.data = new AttachmentData({ + base64: JsonEncoder.toBase64(inputDoc2), + }) + + areCredentialsEqual = jsonldFormatService.shouldAutoRespondToProposal(agentContext, { + credentialRecord, + proposalAttachment: message1, + offerAttachment: message2, + }) + expect(areCredentialsEqual).toBe(false) + }) + }) +}) diff --git a/packages/core/src/modules/credentials/formats/index.ts b/packages/core/src/modules/credentials/formats/index.ts index fd4da3ca50..614b3559a3 100644 --- a/packages/core/src/modules/credentials/formats/index.ts +++ b/packages/core/src/modules/credentials/formats/index.ts @@ -2,3 +2,4 @@ export * from './CredentialFormatService' export * from './CredentialFormatServiceOptions' export * from './CredentialFormat' export * from './indy' +export * from './jsonld' diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts index 4847cf9fd7..c97dc188cb 100644 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts @@ -94,7 +94,7 @@ export class IndyCredentialFormatService extends CredentialFormatService { + private w3cCredentialService: W3cCredentialService + private didResolver: DidResolverService + + public constructor(w3cCredentialService: W3cCredentialService, didResolver: DidResolverService) { + super() + this.w3cCredentialService = w3cCredentialService + this.didResolver = didResolver + } + + public readonly formatKey = 'jsonld' as const + public readonly credentialRecordType = 'w3c' as const + + /** + * Create a {@link AttachmentFormats} object dependent on the message type. + * + * @param options The object containing all the options for the proposed credential + * @returns object containing associated attachment, formats and filtersAttach elements + * + */ + public async createProposal( + agentContext: AgentContext, + { credentialFormats }: FormatCreateProposalOptions + ): Promise { + const format = new CredentialFormatSpec({ + format: JSONLD_VC_DETAIL, + }) + + const jsonLdFormat = credentialFormats.jsonld + if (!jsonLdFormat) { + throw new AriesFrameworkError('Missing jsonld payload in createProposal') + } + + const jsonLdCredential = new JsonLdCredentialDetail(jsonLdFormat) + MessageValidator.validateSync(jsonLdCredential) + + // jsonLdFormat is now of type SignCredentialOptionsRFC0593 + const attachment = this.getFormatData(jsonLdFormat, format.attachId) + return { format, attachment } + } + + /** + * Method called on reception of a propose credential message + * @param options the options needed to accept the proposal + */ + public async processProposal(agentContext: AgentContext, { attachment }: FormatProcessOptions): Promise { + const credProposalJson = attachment.getDataAsJson() + + if (!credProposalJson) { + throw new AriesFrameworkError('Missing jsonld credential proposal data payload') + } + + const messageToValidate = new JsonLdCredentialDetail(credProposalJson) + MessageValidator.validateSync(messageToValidate) + } + + public async acceptProposal( + agentContext: AgentContext, + { attachId, credentialFormats, proposalAttachment }: FormatAcceptProposalOptions + ): Promise { + // if the offer has an attachment Id use that, otherwise the generated id of the formats object + const format = new CredentialFormatSpec({ + attachId, + format: JSONLD_VC_DETAIL, + }) + + const jsonLdFormat = credentialFormats?.jsonld + if (jsonLdFormat) { + // if there is an offer, validate + const jsonLdCredentialOffer = new JsonLdCredentialDetail(jsonLdFormat) + MessageValidator.validateSync(jsonLdCredentialOffer) + } + + const credentialProposal = proposalAttachment.getDataAsJson() + const jsonLdCredentialProposal = new JsonLdCredentialDetail(credentialProposal) + MessageValidator.validateSync(jsonLdCredentialProposal) + + const offerData = jsonLdFormat ?? credentialProposal + + const attachment = this.getFormatData(offerData, format.attachId) + + return { format, attachment } + } + + /** + * Create a {@link AttachmentFormats} object dependent on the message type. + * + * @param options The object containing all the options for the credential offer + * @returns object containing associated attachment, formats and offersAttach elements + * + */ + public async createOffer( + agentContext: AgentContext, + { credentialFormats, attachId }: FormatCreateOfferOptions + ): Promise { + // if the offer has an attachment Id use that, otherwise the generated id of the formats object + const format = new CredentialFormatSpec({ + attachId, + format: JSONLD_VC_DETAIL, + }) + + const jsonLdFormat = credentialFormats?.jsonld + if (!jsonLdFormat) { + throw new AriesFrameworkError('Missing jsonld payload in createOffer') + } + + const jsonLdCredential = new JsonLdCredentialDetail(jsonLdFormat) + + MessageValidator.validateSync(jsonLdCredential) + const attachment = this.getFormatData(jsonLdFormat, format.attachId) + + return { format, attachment } + } + + public async processOffer(agentContext: AgentContext, { attachment }: FormatProcessOptions) { + const credentialOfferJson = attachment.getDataAsJson() + + if (!credentialOfferJson) { + throw new AriesFrameworkError('Missing jsonld credential offer data payload') + } + + const jsonLdCredential = new JsonLdCredentialDetail(credentialOfferJson) + MessageValidator.validateSync(jsonLdCredential) + } + + public async acceptOffer( + agentContext: AgentContext, + { credentialFormats, attachId, offerAttachment }: FormatAcceptOfferOptions + ): Promise { + const jsonLdFormat = credentialFormats?.jsonld + + const credentialOffer = offerAttachment.getDataAsJson() + const requestData = jsonLdFormat ?? credentialOffer + + const jsonLdCredential = new JsonLdCredentialDetail(requestData) + MessageValidator.validateSync(jsonLdCredential) + + const format = new CredentialFormatSpec({ + attachId, + format: JSONLD_VC_DETAIL, + }) + + const attachment = this.getFormatData(requestData, format.attachId) + return { format, attachment } + } + + /** + * Create a credential attachment format for a credential request. + * + * @param options The object containing all the options for the credential request is derived + * @returns object containing associated attachment, formats and requestAttach elements + * + */ + public async createRequest( + agentContext: AgentContext, + { credentialFormats }: FormatCreateRequestOptions + ): Promise { + const jsonLdFormat = credentialFormats?.jsonld + + const format = new CredentialFormatSpec({ + format: JSONLD_VC_DETAIL, + }) + + if (!jsonLdFormat) { + throw new AriesFrameworkError('Missing jsonld payload in createRequest') + } + + const jsonLdCredential = new JsonLdCredentialDetail(jsonLdFormat) + MessageValidator.validateSync(jsonLdCredential) + + const attachment = this.getFormatData(jsonLdFormat, format.attachId) + + return { format, attachment } + } + + public async processRequest(agentContext: AgentContext, { attachment }: FormatProcessOptions): Promise { + const requestJson = attachment.getDataAsJson() + + if (!requestJson) { + throw new AriesFrameworkError('Missing jsonld credential request data payload') + } + + const jsonLdCredential = new JsonLdCredentialDetail(requestJson) + MessageValidator.validateSync(jsonLdCredential) + } + + public async acceptRequest( + agentContext: AgentContext, + { credentialFormats, attachId, requestAttachment }: FormatAcceptRequestOptions + ): Promise { + const jsonLdFormat = credentialFormats?.jsonld + + // sign credential here. credential to be signed is received as the request attachment + // (attachment in the request message from holder to issuer) + const credentialRequest = requestAttachment.getDataAsJson() + + const credentialData = jsonLdFormat ?? credentialRequest + const jsonLdCredential = new JsonLdCredentialDetail(credentialData) + MessageValidator.validateSync(jsonLdCredential) + + const verificationMethod = + credentialFormats?.jsonld?.verificationMethod ?? + (await this.deriveVerificationMethod(agentContext, credentialData.credential, credentialRequest)) + + if (!verificationMethod) { + throw new AriesFrameworkError('Missing verification method in credential data') + } + const format = new CredentialFormatSpec({ + attachId, + format: JSONLD_VC, + }) + + const options = credentialData.options + + if (options.challenge || options.domain || options.credentialStatus) { + throw new AriesFrameworkError( + 'The fields challenge, domain and credentialStatus not currently supported in credential options ' + ) + } + + const verifiableCredential = await this.w3cCredentialService.signCredential(agentContext, { + credential: JsonTransformer.fromJSON(credentialData.credential, W3cCredential), + proofType: credentialData.options.proofType, + verificationMethod: verificationMethod, + }) + + const attachment = this.getFormatData(verifiableCredential, format.attachId) + return { format, attachment } + } + + /** + * Derive a verification method using the issuer from the given verifiable credential + * @param credential the verifiable credential we want to sign + * @return the verification method derived from this credential and its associated issuer did, keys etc. + */ + private async deriveVerificationMethod( + agentContext: AgentContext, + credential: W3cCredential, + credentialRequest: SignCredentialOptionsRFC0593 + ): Promise { + // extract issuer from vc (can be string or Issuer) + let issuerDid = credential.issuer + + if (typeof issuerDid !== 'string') { + issuerDid = issuerDid.id + } + // this will throw an error if the issuer did is invalid + const issuerDidDocument = await this.didResolver.resolveDidDocument(agentContext, issuerDid) + + // find first key which matches proof type + const proofType = credentialRequest.options.proofType + + // actually gets the key type(s) + const keyType = this.w3cCredentialService.getVerificationMethodTypesByProofType(proofType) + + if (!keyType || keyType.length === 0) { + throw new AriesFrameworkError(`No Key Type found for proofType ${proofType}`) + } + + const verificationMethod = await findVerificationMethodByKeyType(keyType[0], issuerDidDocument) + if (!verificationMethod) { + throw new AriesFrameworkError(`Missing verification method for key type ${keyType}`) + } + + return verificationMethod.id + } + /** + * Processes an incoming credential - retrieve metadata, retrieve payload and store it in the Indy wallet + * @param options the issue credential message wrapped inside this object + * @param credentialRecord the credential exchange record for this credential + */ + public async processCredential( + agentContext: AgentContext, + { credentialRecord, attachment, requestAttachment }: FormatProcessCredentialOptions + ): Promise { + const credentialAsJson = attachment.getDataAsJson() + const credential = JsonTransformer.fromJSON(credentialAsJson, W3cVerifiableCredential) + MessageValidator.validateSync(credential) + + // compare stuff in the proof object of the credential and request...based on aca-py + + const requestAsJson = requestAttachment.getDataAsJson() + const request = JsonTransformer.fromJSON(requestAsJson, JsonLdCredentialDetail) + + if (Array.isArray(credential.proof)) { + // question: what do we compare here, each element of the proof array with the request??? + throw new AriesFrameworkError('Credential arrays are not supported') + } else { + // do checks here + this.compareCredentialSubject(credential, request.credential) + this.compareProofs(credential.proof, request.options) + } + + const verifiableCredential = await this.w3cCredentialService.storeCredential(agentContext, { + credential: credential, + }) + + credentialRecord.credentials.push({ + credentialRecordType: this.credentialRecordType, + credentialRecordId: verifiableCredential.id, + }) + } + + private compareCredentialSubject(credential: W3cVerifiableCredential, request: W3cCredential): void { + if (!deepEqual(credential.credentialSubject, request.credentialSubject)) { + throw new AriesFrameworkError('Received credential subject does not match subject from credential request') + } + } + + private compareProofs(credentialProof: LinkedDataProof, requestProof: JsonLdOptionsRFC0593): void { + if (credentialProof.domain !== requestProof.domain) { + throw new AriesFrameworkError('Received credential proof domain does not match domain from credential request') + } + + if (credentialProof.challenge !== requestProof.challenge) { + throw new AriesFrameworkError( + 'Received credential proof challenge does not match challenge from credential request' + ) + } + + if (credentialProof.type !== requestProof.proofType) { + throw new AriesFrameworkError('Received credential proof type does not match proof type from credential request') + } + + if (credentialProof.proofPurpose !== requestProof.proofPurpose) { + throw new AriesFrameworkError( + 'Received credential proof purpose does not match proof purpose from credential request' + ) + } + } + + public supportsFormat(format: string): boolean { + const supportedFormats = [JSONLD_VC_DETAIL, JSONLD_VC] + + return supportedFormats.includes(format) + } + + public async deleteCredentialById(): Promise { + throw new Error('Not implemented.') + } + + public areCredentialsEqual = (message1: Attachment, message2: Attachment): boolean => { + // FIXME: this implementation doesn't make sense. We can't loop over stringified objects... + const obj1 = message1.getDataAsJson() + const obj2 = message2.getDataAsJson() + + return deepEqual(obj1, obj2) + } + + public shouldAutoRespondToProposal( + agentContext: AgentContext, + { offerAttachment, proposalAttachment }: FormatAutoRespondProposalOptions + ) { + return this.areCredentialsEqual(proposalAttachment, offerAttachment) + } + + public shouldAutoRespondToOffer( + agentContext: AgentContext, + { offerAttachment, proposalAttachment }: FormatAutoRespondOfferOptions + ) { + return this.areCredentialsEqual(proposalAttachment, offerAttachment) + } + + public shouldAutoRespondToRequest( + agentContext: AgentContext, + { offerAttachment, requestAttachment }: FormatAutoRespondRequestOptions + ) { + return this.areCredentialsEqual(offerAttachment, requestAttachment) + } + + public shouldAutoRespondToCredential( + agentContext: AgentContext, + { credentialAttachment, requestAttachment }: FormatAutoRespondCredentialOptions + ) { + const credentialAsJson = credentialAttachment.getDataAsJson() + const credential = JsonTransformer.fromJSON(credentialAsJson, W3cVerifiableCredential) + + if (Array.isArray(credential.proof)) { + throw new AriesFrameworkError('Credential arrays are not supported') + } else { + // do checks here + try { + const requestAsJson = requestAttachment.getDataAsJson() + const request = JsonTransformer.fromJSON(requestAsJson, JsonLdCredentialDetail) + this.compareCredentialSubject(credential, request.credential) + this.compareProofs(credential.proof, request.options) + return true + } catch (error) { + return false + } + } + } +} diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialOptions.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialOptions.ts new file mode 100644 index 0000000000..410a190902 --- /dev/null +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialOptions.ts @@ -0,0 +1,30 @@ +import { Expose, Type } from 'class-transformer' + +import { W3cCredential } from '../../../vc/models/credential/W3cCredential' + +import { JsonLdOptionsRFC0593 } from './JsonLdOptionsRFC0593' + +export interface JsonLdCredentialDetailOptions { + credential: W3cCredential + options: JsonLdOptionsRFC0593 +} + +/** + * Class providing validation for the V2 json ld credential as per RFC0593 (used to sign credentials) + * + */ +export class JsonLdCredentialDetail { + public constructor(options: JsonLdCredentialDetailOptions) { + if (options) { + this.credential = options.credential + this.options = options.options + } + } + + @Type(() => W3cCredential) + public credential!: W3cCredential + + @Expose({ name: 'options' }) + @Type(() => JsonLdOptionsRFC0593) + public options!: JsonLdOptionsRFC0593 +} diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdOptionsRFC0593.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdOptionsRFC0593.ts new file mode 100644 index 0000000000..77eff33a6c --- /dev/null +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdOptionsRFC0593.ts @@ -0,0 +1,59 @@ +import { IsObject, IsOptional, IsString } from 'class-validator' + +export interface JsonLdOptionsCredentialStatusOptions { + type: string +} + +export class JsonLdOptionsCredentialStatus { + public constructor(options: JsonLdOptionsCredentialStatusOptions) { + if (options) { + this.type = options.type + } + } + @IsString() + public type!: string +} + +export interface JsonLdOptionsRFC0593Options { + proofPurpose: string + created?: string + domain?: string + challenge?: string + credentialStatus?: JsonLdOptionsCredentialStatus + proofType: string +} + +export class JsonLdOptionsRFC0593 { + public constructor(options: JsonLdOptionsRFC0593Options) { + if (options) { + this.proofPurpose = options.proofPurpose + this.created = options.created + this.domain = options.domain + this.challenge = options.challenge + this.credentialStatus = options.credentialStatus + this.proofType = options.proofType + } + } + + @IsString() + public proofPurpose!: string + + @IsString() + @IsOptional() + public created?: string + + @IsString() + @IsOptional() + public domain?: string + + @IsString() + @IsOptional() + public challenge?: string + + @IsString() + public proofType!: string + + @IsOptional() + @IsObject() + public credentialStatus?: JsonLdOptionsCredentialStatus +} diff --git a/packages/core/src/modules/credentials/formats/jsonld/index.ts b/packages/core/src/modules/credentials/formats/jsonld/index.ts new file mode 100644 index 0000000000..bce1b302d7 --- /dev/null +++ b/packages/core/src/modules/credentials/formats/jsonld/index.ts @@ -0,0 +1,4 @@ +export * from './JsonLdCredentialFormatService' +export * from './JsonLdCredentialFormat' +export * from './JsonLdCredentialOptions' +export * from './JsonLdOptionsRFC0593' diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts index fa84e9d439..6c627e0fb6 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts @@ -29,7 +29,9 @@ import { credDef, credReq } from '../../../__tests__/fixtures' import { CredentialProblemReportReason } from '../../../errors/CredentialProblemReportReason' import { IndyCredentialFormatService } from '../../../formats/indy/IndyCredentialFormatService' import { IndyCredentialUtils } from '../../../formats/indy/IndyCredentialUtils' -import { CredentialState, AutoAcceptCredential, CredentialFormatSpec } from '../../../models' +import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' +import { CredentialFormatSpec } from '../../../models/CredentialFormatSpec' +import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { CredentialMetadataKeys } from '../../../repository/CredentialMetadataTypes' import { CredentialRepository } from '../../../repository/CredentialRepository' diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts index 5201f2464b..42da4ed4da 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts @@ -7,7 +7,6 @@ import { setupCredentialTests, waitForCredentialRecord } from '../../../../../.. import testLogger from '../../../../../../tests/logger' import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { sleep } from '../../../../../utils/sleep' import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' @@ -341,60 +340,6 @@ describe('credentials', () => { } }) - test('Alice starts with V1 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { - testLogger.test('Alice sends credential proposal to Faber') - const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, - protocolVersion: 'v1', - credentialFormats: { - indy: { - attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, - }, - }, - comment: 'v1 propose credential test', - }) - - testLogger.test('Faber waits for credential proposal from Alice') - let faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialExchangeRecord.threadId, - state: CredentialState.ProposalReceived, - }) - - await faberAgent.credentials.negotiateProposal({ - credentialRecordId: faberCredentialExchangeRecord.id, - credentialFormats: { - indy: { - credentialDefinitionId: credDefId, - attributes: newCredentialPreview.attributes, - }, - }, - }) - - testLogger.test('Alice waits for credential offer from Faber') - - const record = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.OfferReceived, - }) - - // below values are not in json object - expect(record.id).not.toBeNull() - expect(record.getTags()).toEqual({ - threadId: record.threadId, - state: record.state, - connectionId: aliceConnection.id, - credentialIds: [], - }) - - // Check if the state of the credential records did not change - faberCredentialExchangeRecord = await faberAgent.credentials.getById(faberCredentialExchangeRecord.id) - faberCredentialExchangeRecord.assertState(CredentialState.OfferSent) - - const aliceRecord = await aliceAgent.credentials.getById(record.id) - aliceRecord.assertState(CredentialState.OfferReceived) - }) - test('Faber starts with V1 credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { testLogger.test('Faber sends credential offer to Alice') let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ @@ -442,8 +387,6 @@ describe('credentials', () => { state: CredentialState.ProposalReceived, }) - await sleep(5000) - // Check if the state of fabers credential record did not change const faberRecord = await faberAgent.credentials.getById(faberCredentialExchangeRecord.id) faberRecord.assertState(CredentialState.ProposalReceived) @@ -451,5 +394,59 @@ describe('credentials', () => { aliceCredentialExchangeRecord = await aliceAgent.credentials.getById(aliceCredentialExchangeRecord.id) aliceCredentialExchangeRecord.assertState(CredentialState.ProposalSent) }) + + test('Alice starts with V1 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + testLogger.test('Alice sends credential proposal to Faber') + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnection.id, + protocolVersion: 'v1', + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + comment: 'v1 propose credential test', + }) + + testLogger.test('Faber waits for credential proposal from Alice') + let faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + await faberAgent.credentials.negotiateProposal({ + credentialRecordId: faberCredentialExchangeRecord.id, + credentialFormats: { + indy: { + credentialDefinitionId: credDefId, + attributes: newCredentialPreview.attributes, + }, + }, + }) + + testLogger.test('Alice waits for credential offer from Faber') + + const record = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(record.id).not.toBeNull() + expect(record.getTags()).toEqual({ + threadId: record.threadId, + state: record.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + + // Check if the state of the credential records did not change + faberCredentialExchangeRecord = await faberAgent.credentials.getById(faberCredentialExchangeRecord.id) + faberCredentialExchangeRecord.assertState(CredentialState.OfferSent) + + const aliceRecord = await aliceAgent.credentials.getById(record.id) + aliceRecord.assertState(CredentialState.OfferReceived) + }) }) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts index 0ee7ea021c..1a6777bd63 100644 --- a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts +++ b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts @@ -503,18 +503,26 @@ export class CredentialFormatCoordinator { { credentialRecord, message, + requestMessage, formatServices, }: { credentialRecord: CredentialExchangeRecord message: V2IssueCredentialMessage + requestMessage: V2RequestCredentialMessage formatServices: CredentialFormatService[] } ) { for (const formatService of formatServices) { const attachment = this.getAttachmentForService(formatService, message.formats, message.credentialAttachments) + const requestAttachment = this.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) await formatService.processCredential(agentContext, { attachment, + requestAttachment, credentialRecord, }) } diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts index 9ce5a7166b..390dac4e1d 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts @@ -24,7 +24,7 @@ import type { CredentialFormatService, CredentialFormatServiceMap, } from '../../formats' -import type { CredentialFormatSpec } from '../../models' +import type { CredentialFormatSpec } from '../../models/CredentialFormatSpec' import { Dispatcher } from '../../../../agent/Dispatcher' import { EventEmitter } from '../../../../agent/EventEmitter' @@ -40,6 +40,7 @@ import { RoutingService } from '../../../routing/services/RoutingService' import { CredentialsModuleConfig } from '../../CredentialsModuleConfig' import { CredentialProblemReportReason } from '../../errors' import { IndyCredentialFormatService } from '../../formats/indy/IndyCredentialFormatService' +import { JsonLdCredentialFormatService } from '../../formats/jsonld/JsonLdCredentialFormatService' import { AutoAcceptCredential, CredentialState } from '../../models' import { CredentialExchangeRecord, CredentialRepository } from '../../repository' import { CredentialService } from '../../services/CredentialService' @@ -49,12 +50,12 @@ import { arePreviewAttributesEqual } from '../../util/previewAttributes' import { CredentialFormatCoordinator } from './CredentialFormatCoordinator' import { V2CredentialAckHandler, - V2CredentialProblemReportHandler, V2IssueCredentialHandler, V2OfferCredentialHandler, V2ProposeCredentialHandler, V2RequestCredentialHandler, } from './handlers' +import { V2CredentialProblemReportHandler } from './handlers/V2CredentialProblemReportHandler' import { V2CredentialAckMessage, V2CredentialProblemReportMessage, @@ -80,6 +81,7 @@ export class V2CredentialService ({ ...formatServiceMap, [formatService.formatKey]: formatService, @@ -567,7 +569,7 @@ export class V2CredentialService const IndyCredentialFormatServiceMock = IndyCredentialFormatService as jest.Mock +const JsonLdCredentialFormatServiceMock = JsonLdCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock const RoutingServiceMock = RoutingService as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock @@ -59,6 +62,7 @@ const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() const routingService = new RoutingServiceMock() const indyCredentialFormatService = new IndyCredentialFormatServiceMock() +const jsonLdCredentialFormatService = new JsonLdCredentialFormatServiceMock() const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() @@ -66,6 +70,10 @@ const connectionService = new ConnectionServiceMock() // @ts-ignore indyCredentialFormatService.formatKey = 'indy' +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +jsonLdCredentialFormatService.formatKey = 'jsonld' + const agentConfig = getAgentConfig('V2CredentialServiceCredTest') const agentContext = getAgentContext() @@ -261,6 +269,7 @@ describe('CredentialService', () => { eventEmitter, credentialRepository, indyCredentialFormatService, + jsonLdCredentialFormatService, agentConfig.logger, new CredentialsModuleConfig() ) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts index 45c987a818..a580005723 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts @@ -19,6 +19,7 @@ import { CredentialEventTypes } from '../../../CredentialEvents' import { CredentialsModuleConfig } from '../../../CredentialsModuleConfig' import { credDef, schema } from '../../../__tests__/fixtures' import { IndyCredentialFormatService } from '../../../formats/indy/IndyCredentialFormatService' +import { JsonLdCredentialFormatService } from '../../../formats/jsonld/JsonLdCredentialFormatService' import { CredentialFormatSpec } from '../../../models' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' @@ -31,6 +32,7 @@ import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' jest.mock('../../../repository/CredentialRepository') jest.mock('../../../../ledger/services/IndyLedgerService') jest.mock('../../../formats/indy/IndyCredentialFormatService') +jest.mock('../../../formats/jsonld/JsonLdCredentialFormatService') jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') jest.mock('../../../../routing/services/RoutingService') jest.mock('../../../../connections/services/ConnectionService') @@ -40,6 +42,7 @@ jest.mock('../../../../../agent/Dispatcher') const CredentialRepositoryMock = CredentialRepository as jest.Mock const IndyLedgerServiceMock = IndyLedgerService as jest.Mock const IndyCredentialFormatServiceMock = IndyCredentialFormatService as jest.Mock +const JsonLdCredentialFormatServiceMock = JsonLdCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock const RoutingServiceMock = RoutingService as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock @@ -50,6 +53,7 @@ const didCommMessageRepository = new DidCommMessageRepositoryMock() const routingService = new RoutingServiceMock() const indyLedgerService = new IndyLedgerServiceMock() const indyCredentialFormatService = new IndyCredentialFormatServiceMock() +const jsonLdCredentialFormatService = new JsonLdCredentialFormatServiceMock() const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() @@ -57,6 +61,9 @@ const connectionService = new ConnectionServiceMock() // @ts-ignore indyCredentialFormatService.formatKey = 'indy' +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +jsonLdCredentialFormatService.formatKey = 'jsonld' const agentConfig = getAgentConfig('V2CredentialServiceOfferTest') const agentContext = getAgentContext() @@ -104,6 +111,7 @@ describe('V2CredentialServiceOffer', () => { eventEmitter, credentialRepository, indyCredentialFormatService, + jsonLdCredentialFormatService, agentConfig.logger, new CredentialsModuleConfig() ) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts index 19bcf6b6f0..0bd25c345e 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts @@ -168,9 +168,6 @@ describe('v2 credentials', () => { await aliceAgent.wallet.delete() }) - // ============================== - // TESTS v2 BEGIN - // ========================== test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { testLogger.test('Alice sends credential proposal to Faber') const schemaId = schema.id diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts new file mode 100644 index 0000000000..5d9d85f596 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts @@ -0,0 +1,167 @@ +import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import type { Wallet } from '../../../../../wallet' +import type { CredentialStateChangedEvent } from '../../../CredentialEvents' +import type { CreateOfferOptions } from '../../../CredentialsApiOptions' +import type { SignCredentialOptionsRFC0593 } from '../../../formats/jsonld/JsonLdCredentialFormat' + +import { ReplaySubject, Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' +import { JsonTransformer } from '../../../../../../src/utils' +import { getAgentOptions, prepareForIssuance, waitForCredentialRecordSubject } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { Agent } from '../../../../../agent/Agent' +import { InjectionSymbols } from '../../../../../constants' +import { Ed25519Signature2018Fixtures } from '../../../../../modules/vc/__tests__/fixtures' +import { W3cCredential } from '../../../../vc/models/' +import { CredentialEventTypes } from '../../../CredentialEvents' +import { CredentialState } from '../../../models' +import { CredentialExchangeRecord } from '../../../repository' + +const faberAgentOptions = getAgentOptions('Faber LD connection-less Credentials V2', { + endpoints: ['rxjs:faber'], +}) + +const aliceAgentOptions = getAgentOptions('Alice LD connection-less Credentials V2', { + endpoints: ['rxjs:alice'], +}) + +let wallet +let credential: W3cCredential +let signCredentialOptions: SignCredentialOptionsRFC0593 + +describe('credentials', () => { + let faberAgent: Agent + let aliceAgent: Agent + let faberReplay: ReplaySubject + let aliceReplay: ReplaySubject + const seed = 'testseed000000000000000000000001' + + beforeEach(async () => { + const faberMessages = new Subject() + const aliceMessages = new Subject() + + const subjectMap = { + 'rxjs:faber': faberMessages, + 'rxjs:alice': aliceMessages, + } + faberAgent = new Agent(faberAgentOptions) + faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await faberAgent.initialize() + + aliceAgent = new Agent(aliceAgentOptions) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + + await prepareForIssuance(faberAgent, ['name', 'age']) + + faberReplay = new ReplaySubject() + aliceReplay = new ReplaySubject() + + faberAgent.events + .observable(CredentialEventTypes.CredentialStateChanged) + .subscribe(faberReplay) + aliceAgent.events + .observable(CredentialEventTypes.CredentialStateChanged) + .subscribe(aliceReplay) + wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) + await wallet.createDid({ seed }) + + credential = JsonTransformer.fromJSON(Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT, W3cCredential) + + signCredentialOptions = { + credential, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, + } + }) + + afterEach(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Faber starts with V2 W3C connection-less credential offer to Alice', async () => { + const offerOptions: CreateOfferOptions = { + comment: 'V2 Out of Band offer (W3C)', + credentialFormats: { + jsonld: signCredentialOptions, + }, + protocolVersion: 'v2', + } + testLogger.test('Faber sends credential offer to Alice') + + // eslint-disable-next-line prefer-const + let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer(offerOptions) + + const { message: offerMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberCredentialRecord.id, + message, + domain: 'https://a-domain.com', + }) + await aliceAgent.receiveMessage(offerMessage.toJSON()) + + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + testLogger.test('Alice sends credential request to Faber') + + const credentialRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + }) + + testLogger.test('Faber waits for credential request from Alice') + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: credentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + faberCredentialRecord = await faberAgent.credentials.acceptRequest({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Alice sends credential ack to Faber') + aliceCredentialRecord = await aliceAgent.credentials.acceptCredential({ + credentialRecordId: aliceCredentialRecord.id, + }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + threadId: expect.any(String), + }) + + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + threadId: expect.any(String), + }) + }) +}) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts new file mode 100644 index 0000000000..d91a5afb01 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts @@ -0,0 +1,382 @@ +import type { ProposeCredentialOptions } from '../../..' +import type { Agent } from '../../../../../agent/Agent' +import type { Wallet } from '../../../../../wallet' +import type { ConnectionRecord } from '../../../../connections' +import type { + JsonLdCredentialFormat, + SignCredentialOptionsRFC0593, +} from '../../../formats/jsonld/JsonLdCredentialFormat' +import type { V2CredentialService } from '../V2CredentialService' + +import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { InjectionSymbols } from '../../../../../constants' +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { Ed25519Signature2018Fixtures } from '../../../../../modules/vc/__tests__/fixtures' +import { W3cCredential } from '../../../../../modules/vc/models' +import { JsonTransformer } from '../../../../../utils' +import { AutoAcceptCredential, CredentialState } from '../../../models' +import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' + +describe('credentials', () => { + let faberAgent: Agent + let aliceAgent: Agent + let faberConnection: ConnectionRecord + let aliceConnection: ConnectionRecord + let aliceCredentialRecord: CredentialExchangeRecord + let credential: W3cCredential + let signCredentialOptions: SignCredentialOptionsRFC0593 + let wallet + const seed = 'testseed000000000000000000000001' + + describe('Auto accept on `always`', () => { + beforeAll(async () => { + ;({ faberAgent, aliceAgent, faberConnection, aliceConnection } = await setupCredentialTests( + 'faber agent: always v2 jsonld', + 'alice agent: always v2 jsonld', + AutoAcceptCredential.Always + )) + + credential = JsonTransformer.fromJSON(Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT, W3cCredential) + wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) + await wallet.createDid({ seed }) + signCredentialOptions = { + credential, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, + } + }) + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { + testLogger.test('Alice sends credential proposal to Faber') + + const options: ProposeCredentialOptions< + [JsonLdCredentialFormat], + [V2CredentialService<[JsonLdCredentialFormat]>] + > = { + connectionId: aliceConnection.id, + protocolVersion: 'v2', + credentialFormats: { + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test', + } + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(options) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber waits for credential ack from Alice') + aliceCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: {}, + state: CredentialState.Done, + }) + }) + test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `always`', async () => { + testLogger.test('Faber sends V2 credential offer to Alice as start of protocol process') + + const faberCredentialExchangeRecord: CredentialExchangeRecord = await faberAgent.credentials.offerCredential({ + comment: 'some comment about credential', + connectionId: faberConnection.id, + credentialFormats: { + jsonld: signCredentialOptions, + }, + protocolVersion: 'v2', + }) + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + testLogger.test('Faber waits for credential ack from Alice') + const faberCredentialRecord: CredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: {}, + state: CredentialState.CredentialReceived, + }) + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + }) + }) + }) + + describe('Auto accept on `contentApproved`', () => { + beforeAll(async () => { + ;({ faberAgent, aliceAgent, faberConnection, aliceConnection } = await setupCredentialTests( + 'faber agent: content-approved v2 jsonld', + 'alice agent: content-approved v2 jsonld', + AutoAcceptCredential.ContentApproved + )) + credential = JsonTransformer.fromJSON(Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT, W3cCredential) + wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) + await wallet.createDid({ seed }) + signCredentialOptions = { + credential, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, + } + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { + testLogger.test('Alice sends credential proposal to Faber') + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + credentialFormats: { + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test', + }) + + testLogger.test('Faber waits for credential proposal from Alice') + let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + testLogger.test('Faber sends credential offer to Alice') + const faberCredentialExchangeRecord = await faberAgent.credentials.acceptProposal({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 JsonLd Offer', + credentialFormats: { + jsonld: signCredentialOptions, + }, + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber waits for credential ack from Alice') + + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: {}, + state: CredentialState.CredentialReceived, + }) + + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: {}, + state: CredentialState.Done, + }) + }) + test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `contentApproved`', async () => { + testLogger.test('Faber sends credential offer to Alice') + + let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ + comment: 'some comment about credential', + connectionId: faberConnection.id, + credentialFormats: { + jsonld: signCredentialOptions, + }, + protocolVersion: 'v2', + }) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: aliceCredentialRecord.threadId, + state: aliceCredentialRecord.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + if (!aliceCredentialRecord.connectionId) { + throw new AriesFrameworkError('missing alice connection id') + } + + // we do not need to specify connection id in this object + // it is either connectionless or included in the offer message + testLogger.test('Alice sends credential request to faber') + faberCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber waits for credential ack from Alice') + + const faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: {}, + state: CredentialState.CredentialReceived, + }) + + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + }) + }) + test('Faber starts with V2 credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + testLogger.test('Faber sends credential offer to Alice') + + const faberCredentialExchangeRecord: CredentialExchangeRecord = await faberAgent.credentials.offerCredential({ + comment: 'some comment about credential', + connectionId: faberConnection.id, + credentialFormats: { + jsonld: signCredentialOptions, + }, + protocolVersion: 'v2', + }) + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: aliceCredentialRecord.threadId, + state: aliceCredentialRecord.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + + testLogger.test('Alice sends credential request to Faber') + + const aliceExchangeCredentialRecord = await aliceAgent.credentials.negotiateOffer({ + credentialRecordId: aliceCredentialRecord.id, + credentialFormats: { + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test', + }) + + testLogger.test('Faber waits for credential proposal from Alice') + const faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceExchangeCredentialRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + // Check if the state of faber credential record did not change + const faberRecord = await faberAgent.credentials.getById(faberCredentialRecord.id) + faberRecord.assertState(CredentialState.ProposalReceived) + + aliceCredentialRecord = await aliceAgent.credentials.getById(aliceCredentialRecord.id) + aliceCredentialRecord.assertState(CredentialState.ProposalSent) + }) + test('Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + testLogger.test('Alice sends credential proposal to Faber') + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + credentialFormats: { + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test', + }) + + testLogger.test('Faber waits for credential proposal from Alice') + let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + await faberAgent.credentials.negotiateProposal({ + credentialRecordId: faberCredentialRecord.id, + credentialFormats: { + jsonld: signCredentialOptions, + }, + }) + + testLogger.test('Alice waits for credential offer from Faber') + + const record = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(record.id).not.toBeNull() + expect(record.getTags()).toEqual({ + threadId: record.threadId, + state: record.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + expect(record.type).toBe(CredentialExchangeRecord.type) + + // Check if the state of the credential records did not change + faberCredentialRecord = await faberAgent.credentials.getById(faberCredentialRecord.id) + faberCredentialRecord.assertState(CredentialState.OfferSent) + + const aliceRecord = await aliceAgent.credentials.getById(record.id) + aliceRecord.assertState(CredentialState.OfferReceived) + }) + }) +}) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts new file mode 100644 index 0000000000..f712bfa43e --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -0,0 +1,551 @@ +import type { Agent } from '../../../../../agent/Agent' +import type { Wallet } from '../../../../../wallet' +import type { ConnectionRecord } from '../../../../connections' +import type { SignCredentialOptionsRFC0593 } from '../../../formats/jsonld/JsonLdCredentialFormat' + +import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { InjectionSymbols } from '../../../../../constants' +import { DidCommMessageRepository } from '../../../../../storage' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { W3cCredential } from '../../../../vc/models/credential/W3cCredential' +import { CredentialState } from '../../../models' +import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import { V2CredentialPreview } from '../messages' +import { V2IssueCredentialMessage } from '../messages/V2IssueCredentialMessage' +import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' + +describe('credentials', () => { + let faberAgent: Agent + let aliceAgent: Agent + let aliceConnection: ConnectionRecord + let aliceCredentialRecord: CredentialExchangeRecord + let faberCredentialRecord: CredentialExchangeRecord + + let didCommMessageRepository: DidCommMessageRepository + + const inputDoc = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + } + + const credential = JsonTransformer.fromJSON(inputDoc, W3cCredential) + + let signCredentialOptions: SignCredentialOptionsRFC0593 + + let wallet + const seed = 'testseed000000000000000000000001' + let credDefId: string + + beforeAll(async () => { + ;({ faberAgent, aliceAgent, credDefId, aliceConnection } = await setupCredentialTests( + 'Faber Agent Credentials LD', + 'Alice Agent Credentials LD' + )) + wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) + await wallet.createDid({ seed }) + signCredentialOptions = { + credential, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, + } + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice starts with V2 (ld format, Ed25519 signature) credential proposal to Faber', async () => { + testLogger.test('Alice sends (v2 jsonld) credential proposal to Faber') + + const credentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + credentialFormats: { + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test for W3C Credentials', + }) + + expect(credentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(credentialExchangeRecord.protocolVersion).toEqual('v2') + expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) + expect(credentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential proposal from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: credentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + testLogger.test('Faber sends credential offer to Alice') + await faberAgent.credentials.acceptProposal({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 W3C Offer', + credentialFormats: { + jsonld: signCredentialOptions, + }, + }) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) + + const offerMessage = await didCommMessageRepository.findAgentMessage(aliceAgent.context, { + associatedRecordId: aliceCredentialRecord.id, + messageClass: V2OfferCredentialMessage, + }) + + expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', + '@id': expect.any(String), + comment: 'V2 W3C Offer', + formats: [ + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc-detail@v1.0', + }, + ], + 'offers~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~service': undefined, + '~attach': undefined, + '~please_ack': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + credential_preview: expect.any(Object), + replacement_id: undefined, + }) + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + + if (aliceCredentialRecord.connectionId) { + const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + credentialFormats: { + jsonld: undefined, + }, + }) + + expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential request from Alice') + await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + + await faberAgent.credentials.acceptRequest({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Alice sends credential ack to Faber') + await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: expect.any(String), + connectionId: expect.any(String), + state: CredentialState.CredentialReceived, + }) + + const credentialMessage = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberCredentialRecord.id, + messageClass: V2IssueCredentialMessage, + }) + + expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', + '@id': expect.any(String), + comment: 'V2 Indy Credential', + formats: [ + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc@1.0', + }, + ], + 'credentials~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~please_ack': { on: ['RECEIPT'] }, + '~service': undefined, + '~attach': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + }) + } + }) + + test('Multiple Formats: Alice starts with V2 (both ld and indy formats) credential proposal to Faber', async () => { + testLogger.test('Alice sends (v2 jsonld) credential proposal to Faber') + // set the propose options - using both indy and ld credential formats here + const credentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'some x-ray', + profile_picture: 'profile picture', + }) + const testAttributes = { + attributes: credentialPreview.attributes, + schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + schemaName: 'ahoy', + schemaVersion: '1.0', + schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', + issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag', + } + + testLogger.test('Alice sends (v2, Indy) credential proposal to Faber') + + const credentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + credentialFormats: { + indy: testAttributes, + jsonld: signCredentialOptions, + }, + comment: 'v2 propose credential test', + }) + + expect(credentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(credentialExchangeRecord.protocolVersion).toEqual('v2') + expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) + expect(credentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential proposal from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: credentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + testLogger.test('Faber sends credential offer to Alice') + + await faberAgent.credentials.acceptProposal({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 W3C & INDY Proposals', + credentialFormats: { + indy: { + credentialDefinitionId: credDefId, + attributes: credentialPreview.attributes, + }, + jsonld: signCredentialOptions, + }, + }) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + // didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) + + const offerMessage = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberCredentialRecord.id, + messageClass: V2OfferCredentialMessage, + }) + + const credOfferJson = offerMessage?.offerAttachments[1].getDataAsJson() + + expect(credOfferJson).toMatchObject({ + credential: { + context: [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + // type: [Array], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + }, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, + }) + expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', + '@id': expect.any(String), + comment: 'V2 W3C & INDY Proposals', + formats: [ + { + attach_id: expect.any(String), + format: 'hlindy/cred-abstract@v2.0', + }, + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc-detail@v1.0', + }, + ], + credential_preview: { + '@type': 'https://didcomm.org/issue-credential/2.0/credential-preview', + attributes: expect.any(Array), + }, + 'offers~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~service': undefined, + '~attach': undefined, + '~please_ack': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + replacement_id: undefined, + }) + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + + if (aliceCredentialRecord.connectionId) { + const offerCredentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + }) + + expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential request from Alice') + await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + + await faberAgent.credentials.acceptRequest({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Alice sends credential ack to Faber') + await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: expect.any(String), + connectionId: expect.any(String), + state: CredentialState.CredentialReceived, + }) + + const credentialMessage = await didCommMessageRepository.getAgentMessage(faberAgent.context, { + associatedRecordId: faberCredentialRecord.id, + messageClass: V2IssueCredentialMessage, + }) + + const w3cCredential = credentialMessage.credentialAttachments[1].getDataAsJson() + + expect(w3cCredential).toMatchObject({ + context: [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + proof: { + type: 'Ed25519Signature2018', + created: expect.any(String), + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + proofPurpose: 'assertionMethod', + }, + }) + + expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', + '@id': expect.any(String), + comment: 'V2 Indy Credential', + formats: [ + { + attach_id: expect.any(String), + format: 'hlindy/cred@v2.0', + }, + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc@1.0', + }, + ], + 'credentials~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~please_ack': { on: ['RECEIPT'] }, + '~service': undefined, + '~attach': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + }) + } + }) +}) diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts index d2279dd013..8f1db634e9 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts @@ -53,7 +53,6 @@ export class V2IssueCredentialHandler implements Handler { const { message } = await this.credentialService.acceptCredential(messageContext.agentContext, { credentialRecord, }) - if (messageContext.connection) { return createOutboundMessage(messageContext.connection, message) } else if (requestMessage?.service && messageContext.message.service) { diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts index f656ae8351..095a5a5a0f 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts @@ -38,7 +38,6 @@ export class V2OfferCredentialHandler implements Handler { credentialRecord, offerMessage: messageContext.message, }) - if (shouldAutoRespond) { return await this.acceptOffer(credentialRecord, messageContext) } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts index b8076c9a7b..e30286f988 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts @@ -76,6 +76,6 @@ export class V2RequestCredentialHandler implements Handler { }) } - this.logger.error(`Could not automatically create credential request`) + this.logger.error(`Could not automatically issue credential`) } } diff --git a/packages/core/src/modules/dids/domain/DidDocument.ts b/packages/core/src/modules/dids/domain/DidDocument.ts index 4f3a066835..5316933952 100644 --- a/packages/core/src/modules/dids/domain/DidDocument.ts +++ b/packages/core/src/modules/dids/domain/DidDocument.ts @@ -11,7 +11,7 @@ import { getKeyDidMappingByVerificationMethod } from './key-type' import { IndyAgentService, ServiceTransformer, DidCommV1Service } from './service' import { VerificationMethodTransformer, VerificationMethod, IsStringOrVerificationMethod } from './verificationMethod' -type DidPurpose = +export type DidPurpose = | 'authentication' | 'keyAgreement' | 'assertionMethod' @@ -234,7 +234,6 @@ export async function findVerificationMethodByKeyType( 'capabilityInvocation', 'capabilityDelegation', ] - for await (const purpose of didVerificationMethods) { const key: VerificationMethod[] | (string | VerificationMethod)[] | undefined = didDocument[purpose] if (key instanceof Array) { diff --git a/packages/core/src/modules/proofs/formats/ProofFormatService.ts b/packages/core/src/modules/proofs/formats/ProofFormatService.ts index 92e00838ff..1ce367cf33 100644 --- a/packages/core/src/modules/proofs/formats/ProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/ProofFormatService.ts @@ -13,7 +13,7 @@ import type { GetRequestedCredentialsFormat } from './indy/IndyProofFormatsServi import type { ProofAttachmentFormat } from './models/ProofAttachmentFormat' import type { CreatePresentationFormatsOptions, - CreateProposalOptions, + FormatCreateProofProposalOptions, CreateRequestOptions, FormatCreatePresentationOptions, ProcessPresentationOptions, @@ -40,7 +40,7 @@ export abstract class ProofFormatService { this.agentConfig = agentConfig } - abstract createProposal(options: CreateProposalOptions): Promise + abstract createProposal(options: FormatCreateProofProposalOptions): Promise abstract processProposal(options: ProcessProposalOptions): Promise diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts index 1d51478e76..12641a4fb4 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts @@ -11,7 +11,7 @@ import type { ProofAttachmentFormat } from '../models/ProofAttachmentFormat' import type { CreatePresentationFormatsOptions, CreateProofAttachmentOptions, - CreateProposalOptions, + FormatCreateProofProposalOptions, CreateRequestAttachmentOptions, CreateRequestOptions, FormatCreatePresentationOptions, @@ -133,7 +133,7 @@ export class IndyProofFormatService extends ProofFormatService { return { format, attachment } } - public async createProposal(options: CreateProposalOptions): Promise { + public async createProposal(options: FormatCreateProofProposalOptions): Promise { if (!options.formats.indy) { throw Error('Missing indy format to create proposal attachment format') } diff --git a/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts b/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts index 1a377f4af2..2538aaf1b4 100644 --- a/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts +++ b/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts @@ -15,7 +15,7 @@ export interface CreateProofAttachmentOptions { proofProposalOptions: ProofRequestOptions } -export interface CreateProposalOptions { +export interface FormatCreateProofProposalOptions { id?: string formats: ProposeProofFormats } diff --git a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts index 66d12e5d64..c15302a7a8 100644 --- a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts +++ b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts @@ -12,11 +12,6 @@ export interface SignCredentialOptions { verificationMethod: string proofPurpose?: ProofPurpose created?: string - domain?: string - challenge?: string - credentialStatus?: { - type: string - } } export interface VerifyCredentialOptions { diff --git a/packages/core/src/utils/objEqual.ts b/packages/core/src/utils/objEqual.ts new file mode 100644 index 0000000000..2909b5c450 --- /dev/null +++ b/packages/core/src/utils/objEqual.ts @@ -0,0 +1,23 @@ +export function deepEqual(a: any, b: any): boolean { + if (typeof a == 'object' && a != null && typeof b == 'object' && b != null) { + const count = [0, 0] + for (const key in a) count[0]++ + for (const key in b) count[1]++ + if (count[0] - count[1] != 0) { + return false + } + for (const key in a) { + if (!(key in b) || !deepEqual(a[key], b[key])) { + return false + } + } + for (const key in b) { + if (!(key in a) || !deepEqual(b[key], a[key])) { + return false + } + } + return true + } else { + return a === b + } +} diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 39b79cd84e..a8b89bfaec 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -25,6 +25,7 @@ import { catchError, filter, map, timeout } from 'rxjs/operators' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { BbsModule } from '../../bbs-signatures/src/BbsModule' import { agentDependencies, WalletScheme } from '../../node/src' import { Agent, @@ -100,7 +101,6 @@ export function getAgentOptions( logger: new TestLogger(LogLevel.off, name), ...extraConfig, } - return { config, modules, dependencies: agentDependencies } as const } @@ -223,7 +223,7 @@ export function waitForCredentialRecordSubject( threadId, state, previousState, - timeoutMs = 10000, + timeoutMs = 15000, // sign and store credential in W3c credential service take several seconds }: { threadId?: string state?: CredentialState @@ -666,15 +666,28 @@ export async function setupCredentialTests( 'rxjs:faber': faberMessages, 'rxjs:alice': aliceMessages, } - const faberAgentOptions = getAgentOptions(faberName, { - endpoints: ['rxjs:faber'], - autoAcceptCredentials, - }) - const aliceAgentOptions = getAgentOptions(aliceName, { - endpoints: ['rxjs:alice'], - autoAcceptCredentials, - }) + // TODO remove the dependency on BbsModule + const modules = { + bbs: new BbsModule(), + } + const faberAgentOptions = getAgentOptions( + faberName, + { + endpoints: ['rxjs:faber'], + autoAcceptCredentials, + }, + modules + ) + + const aliceAgentOptions = getAgentOptions( + aliceName, + { + endpoints: ['rxjs:alice'], + autoAcceptCredentials, + }, + modules + ) const faberAgent = new Agent(faberAgentOptions) faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) diff --git a/yarn.lock b/yarn.lock index 49d2b136aa..030c9671e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,25 +29,25 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.0.tgz#2a592fd89bacb1fcde68de31bee4f2f2dacb0e86" - integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.1.tgz#72d647b4ff6a4f82878d184613353af1dd0290f9" + integrity sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg== "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.0.tgz#d2f5f4f2033c00de8096be3c9f45772563e150c3" - integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.1.tgz#c8fa615c5e88e272564ace3d42fbc8b17bfeb22b" + integrity sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" "@babel/generator" "^7.19.0" - "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.1" "@babel/helper-module-transforms" "^7.19.0" "@babel/helpers" "^7.19.0" - "@babel/parser" "^7.19.0" + "@babel/parser" "^7.19.1" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" + "@babel/traverse" "^7.19.1" "@babel/types" "^7.19.0" convert-source-map "^1.7.0" debug "^4.1.0" @@ -79,14 +79,14 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz#537ec8339d53e806ed422f1e06c8f17d55b96bb0" - integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz#7f630911d83b408b76fe584831c98e5395d7a17c" + integrity sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg== dependencies: - "@babel/compat-data" "^7.19.0" + "@babel/compat-data" "^7.19.1" "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.20.2" + browserslist "^4.21.3" semver "^6.3.0" "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0": @@ -110,7 +110,7 @@ "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.1.0" -"@babel/helper-define-polyfill-provider@^0.3.2", "@babel/helper-define-polyfill-provider@^0.3.3": +"@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== @@ -190,15 +190,15 @@ integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== "@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz#1092e002feca980fbbb0bd4d51b74a65c6a500e6" - integrity sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" + integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-member-expression-to-functions" "^7.18.9" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/traverse" "^7.19.1" + "@babel/types" "^7.19.0" "@babel/helper-simple-access@^7.18.6": version "7.18.6" @@ -227,9 +227,9 @@ integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== "@babel/helper-validator-identifier@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" - integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== "@babel/helper-validator-option@^7.18.6": version "7.18.6" @@ -254,10 +254,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c" - integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.1.tgz#6f6d6c2e621aad19a92544cc217ed13f1aac5b4c" + integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A== "@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.1.0": version "7.18.6" @@ -606,15 +606,15 @@ regenerator-transform "^0.15.0" "@babel/plugin-transform-runtime@^7.0.0": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz#37d14d1fa810a368fd635d4d1476c0154144a96f" - integrity sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.1.tgz#a3df2d7312eea624c7889a2dcd37fd1dfd25b2c6" + integrity sha512-2nJjTUFIzBMP/f/miLxEK9vxwW/KUXsdvN4sR//TmuDhe6yU2h57WmIOE12Gng3MDP/xpjUV/ToZRdcf8Yj4fA== dependencies: "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.9" - babel-plugin-polyfill-corejs2 "^0.3.2" - babel-plugin-polyfill-corejs3 "^0.5.3" - babel-plugin-polyfill-regenerator "^0.4.0" + "@babel/helper-plugin-utils" "^7.19.0" + babel-plugin-polyfill-corejs2 "^0.3.3" + babel-plugin-polyfill-corejs3 "^0.6.0" + babel-plugin-polyfill-regenerator "^0.4.1" semver "^6.3.0" "@babel/plugin-transform-shorthand-properties@^7.0.0": @@ -647,9 +647,9 @@ "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-typescript@^7.18.6", "@babel/plugin-transform-typescript@^7.5.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.0.tgz#50c3a68ec8efd5e040bde2cd764e8e16bc0cbeaf" - integrity sha512-DOOIywxPpkQHXijXv+s9MDAyZcLp12oYRl3CMWZ6u7TjSoCBq/KqHR/nNFR3+i2xqheZxoF0H2XyL7B6xeSRuA== + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.1.tgz#adcf180a041dcbd29257ad31b0c65d4de531ce8d" + integrity sha512-+ILcOU+6mWLlvCwnL920m2Ow3wWx3Wo8n2t5aROQmV55GZt+hOiLvBaa3DNzRjSEHa1aauRs4/YLmkCfFkhhRQ== dependencies: "@babel/helper-create-class-features-plugin" "^7.19.0" "@babel/helper-plugin-utils" "^7.19.0" @@ -708,10 +708,10 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.18.9", "@babel/traverse@^7.19.0", "@babel/traverse@^7.7.2": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.0.tgz#eb9c561c7360005c592cc645abafe0c3c4548eed" - integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.7.2": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.1.tgz#0fafe100a8c2a603b4718b1d9bf2568d1d193347" + integrity sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA== dependencies: "@babel/code-frame" "^7.18.6" "@babel/generator" "^7.19.0" @@ -719,7 +719,7 @@ "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.0" + "@babel/parser" "^7.19.1" "@babel/types" "^7.19.0" debug "^4.1.0" globals "^11.1.0" @@ -2351,9 +2351,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.18.1" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.1.tgz#ce5e2c8c272b99b7a9fd69fa39f0b4cd85028bd9" - integrity sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA== + version "7.18.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.2.tgz#235bf339d17185bdec25e024ca19cce257cc7309" + integrity sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg== dependencies: "@babel/types" "^7.3.0" @@ -2403,18 +2403,18 @@ integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== "@types/express-serve-static-core@^4.17.18": - version "4.17.30" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz#0f2f99617fa8f9696170c46152ccf7500b34ac04" - integrity sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ== + version "4.17.31" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz#a1139efeab4e7323834bb0226e62ac019f474b2f" + integrity sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" "@types/express@^4.17.13": - version "4.17.13" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" - integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + version "4.17.14" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" + integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^4.17.18" @@ -2422,9 +2422,9 @@ "@types/serve-static" "*" "@types/ffi-napi@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@types/ffi-napi/-/ffi-napi-4.0.5.tgz#0b2dc2d549361947a117d55156ff34fd9632c3df" - integrity sha512-WDPpCcHaPhHmP1FIw3ds/+OLt8bYQ/h3SO7o+8kH771PL21kHVzTwii7+WyMBXMQrBsR6xVU2y7w+h+9ggpaQw== + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/ffi-napi/-/ffi-napi-4.0.6.tgz#cd1c65cc9e701de664e640ccb17a2e823a674d44" + integrity sha512-yrBtqeVD1aeVo271jXVEo3iAtbzSGVGRssJv9W9JlUfg5Z5FgHJx2MV88GRwVATu/XWg6zyenW/cb1MNAuOtaQ== dependencies: "@types/node" "*" "@types/ref-napi" "*" @@ -2542,9 +2542,9 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prettier@^2.1.5": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.0.tgz#ea03e9f0376a4446f44797ca19d9c46c36e352dc" - integrity sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A== + version "2.7.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e" + integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow== "@types/prop-types@*": version "15.7.5" @@ -2569,25 +2569,25 @@ "@types/react" "^17" "@types/react@^17": - version "17.0.49" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.49.tgz#df87ba4ca8b7942209c3dc655846724539dc1049" - integrity sha512-CCBPMZaPhcKkYUTqFs/hOWqKjPxhTEmnZWjlHHgIMop67DsXywf9B5Os9Hz8KSacjNOgIdnZVJamwl232uxoPg== + version "17.0.50" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.50.tgz#39abb4f7098f546cfcd6b51207c90c4295ee81fc" + integrity sha512-ZCBHzpDb5skMnc1zFXAXnL3l1FAdi+xZvwxK+PkglMmBrwjpp9nKaWuEvrGnSifCJmBFGxZOOFuwC6KH/s0NuA== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" csstype "^3.0.2" "@types/ref-napi@*", "@types/ref-napi@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/ref-napi/-/ref-napi-3.0.4.tgz#d7edc063b244c85767867ce1167ec2d7051728a1" - integrity sha512-ng8SCmdZbz1GHaW3qgGoX9IaHoIvgMqgBHLe3sv18NbAkHVgnjRW8fJq51VTUm4lnJyLu60q9/002o7qjOg13g== + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/ref-napi/-/ref-napi-3.0.5.tgz#8db441d381737af5c353d7dd89c7593b5f2080c8" + integrity sha512-u+L/RdwTuJes3pDypOVR/MtcqzoULu8Z8yulP6Tw5z7eXV1ba1llizNVFtI/m2iPfDy/dPPt+3ar1QCgonTzsw== dependencies: "@types/node" "*" "@types/ref-struct-di@*": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@types/ref-struct-di/-/ref-struct-di-1.1.6.tgz#9775753b24ba5bf248dd66d79d4fdb7cebef6e95" - integrity sha512-+Sa2H3ynDYo2ungR3d5kmNetlkAYNqQVjJvs1k7i6zvo7Zu/qb+OsrXU54RuiOYJCwY9piN+hOd4YRRaiEOqgw== + version "1.1.7" + resolved "https://registry.yarnpkg.com/@types/ref-struct-di/-/ref-struct-di-1.1.7.tgz#85e0149858a81a14f12f15ff31a6dffa42bab2d3" + integrity sha512-nnHR26qrCnQqxwHTv+rqzu/hGgDZl45TUs4bO6ZjpuC8/M2JoXFxk63xrWmAmqsLe55oxOgAWssyr3YHAMY89g== dependencies: "@types/ref-napi" "*" @@ -2622,9 +2622,9 @@ integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/validator@^13.1.3": - version "13.7.6" - resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.6.tgz#631f1acd15cbac9cb0a114da7e87575f1c95b46a" - integrity sha512-uBsnWETsUagQ0n6G2wcXNIufpTNJir0zqzG4p62fhnwzs48d/iuOWEEo0d3iUxN7D+9R/8CSvWGKS+KmaD0mWA== + version "13.7.7" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.7.tgz#e87cf34dd08522d21acf30130fd8941f433b81b5" + integrity sha512-jiEw2kTUJ8Jsh4A1K4b5Pkjj9Xz6FktLLOQ36ZVLRkmxFbpTvAV2VRoKMojz8UlZxNg/2dZqzpigH4JYn1bkQg== "@types/varint@^6.0.0": version "6.0.0" @@ -3256,7 +3256,7 @@ babel-plugin-jest-hoist@^27.5.1: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" -babel-plugin-polyfill-corejs2@^0.3.2: +babel-plugin-polyfill-corejs2@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== @@ -3265,15 +3265,15 @@ babel-plugin-polyfill-corejs2@^0.3.2: "@babel/helper-define-polyfill-provider" "^0.3.3" semver "^6.1.1" -babel-plugin-polyfill-corejs3@^0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7" - integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw== +babel-plugin-polyfill-corejs3@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a" + integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.2" - core-js-compat "^3.21.0" + "@babel/helper-define-polyfill-provider" "^0.3.3" + core-js-compat "^3.25.1" -babel-plugin-polyfill-regenerator@^0.4.0: +babel-plugin-polyfill-regenerator@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== @@ -3494,15 +3494,15 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.20.2, browserslist@^4.21.3: - version "4.21.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" - integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== +browserslist@^4.21.3, browserslist@^4.21.4: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== dependencies: - caniuse-lite "^1.0.30001370" - electron-to-chromium "^1.4.202" + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" node-releases "^2.0.6" - update-browserslist-db "^1.0.5" + update-browserslist-db "^1.0.9" bs-logger@0.x: version "0.2.6" @@ -3653,10 +3653,10 @@ camelcase@^6.0.0, camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001370: - version "1.0.30001399" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001399.tgz#1bf994ca375d7f33f8d01ce03b7d5139e8587873" - integrity sha512-4vQ90tMKS+FkvuVWS5/QY1+d805ODxZiKFzsU8o/RsVJz49ZSRR8EjykLJbqhzdPgadbX6wB538wOzle3JniRA== +caniuse-lite@^1.0.30001400: + version "1.0.30001412" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001412.tgz#30f67d55a865da43e0aeec003f073ea8764d5d7c" + integrity sha512-+TeEIee1gS5bYOiuf+PS/kp2mrXic37Hl66VY6EAfxasIk5fELTktK2oOezYed12H8w7jt3s512PpulQidPjwA== canonicalize@^1.0.1: version "1.0.8" @@ -4167,12 +4167,12 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== -core-js-compat@^3.21.0: - version "3.25.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.1.tgz#6f13a90de52f89bbe6267e5620a412c7f7ff7e42" - integrity sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw== +core-js-compat@^3.25.1: + version "3.25.3" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.3.tgz#d6a442a03f4eade4555d4e640e6a06151dd95d38" + integrity sha512-xVtYpJQ5grszDHEUU9O7XbjjcZ0ccX3LgQsyqSvTnjX97ZqEgn9F5srmrwwwMtbKzDllyFPL+O+2OFMl1lU4TQ== dependencies: - browserslist "^4.21.3" + browserslist "^4.21.4" core-util-is@1.0.2: version "1.0.2" @@ -4268,9 +4268,9 @@ cssstyle@^2.3.0: cssom "~0.3.6" csstype@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2" - integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA== + version "3.1.1" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" + integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== dargs@^7.0.0: version "7.0.0" @@ -4348,9 +4348,9 @@ decamelize@^1.1.0, decamelize@^1.2.0: integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== decimal.js@^10.2.1: - version "10.4.0" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.0.tgz#97a7448873b01e92e5ff9117d89a7bca8e63e0fe" - integrity sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg== + version "10.4.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.1.tgz#be75eeac4a2281aace80c1a8753587c27ef053e7" + integrity sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw== decode-uri-component@^0.2.0: version "0.2.0" @@ -4577,10 +4577,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.202: - version "1.4.248" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.248.tgz#dd2dab68277e91e8452536ee9265f484066f94ad" - integrity sha512-qShjzEYpa57NnhbW2K+g+Fl+eNoDvQ7I+2MRwWnU6Z6F0HhXekzsECCLv+y2OJUsRodjqoSfwHkIX42VUFtUzg== +electron-to-chromium@^1.4.251: + version "1.4.262" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.262.tgz#25715dfbae4c2e0640517cba184715241ecd8e63" + integrity sha512-Ckn5haqmGh/xS8IbcgK3dnwAVnhDyo/WQnklWn6yaMucYTq7NNxwlGE8ElzEOnonzRLzUCo2Ot3vUb2GYUF2Hw== emittery@^0.8.1: version "0.8.1" @@ -4656,21 +4656,21 @@ errorhandler@^1.5.0: escape-html "~1.0.3" es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.1: - version "1.20.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.2.tgz#8495a07bc56d342a3b8ea3ab01bd986700c2ccb3" - integrity sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ== + version "1.20.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.3.tgz#90b143ff7aedc8b3d189bcfac7f1e3e3f81e9da1" + integrity sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" function.prototype.name "^1.1.5" - get-intrinsic "^1.1.2" + get-intrinsic "^1.1.3" get-symbol-description "^1.0.0" has "^1.0.3" has-property-descriptors "^1.0.0" has-symbols "^1.0.3" internal-slot "^1.0.3" - is-callable "^1.2.4" + is-callable "^1.2.6" is-negative-zero "^2.0.2" is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" @@ -4680,6 +4680,7 @@ es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19 object-keys "^1.1.1" object.assign "^4.1.4" regexp.prototype.flags "^1.4.3" + safe-regex-test "^1.0.0" string.prototype.trimend "^1.0.5" string.prototype.trimstart "^1.0.5" unbox-primitive "^1.0.2" @@ -5158,9 +5159,9 @@ fastq@^1.6.0: reusify "^1.0.4" fb-watchman@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" - integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== dependencies: bser "2.1.1" @@ -5313,9 +5314,9 @@ flatted@^3.1.0: integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== flow-parser@0.*: - version "0.186.0" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.186.0.tgz#ef6f4c7a3d8eb29fdd96e1d1f651b7ccb210f8e9" - integrity sha512-QaPJczRxNc/yvp3pawws439VZ/vHGq+i1/mZ3bEdSaRy8scPgZgiWklSB6jN7y5NR9sfgL4GGIiBcMXTj3Opqg== + version "0.187.1" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.187.1.tgz#52b2c7ebd7544b75bda0676380138bc5b3de3177" + integrity sha512-ZvlTeakTTMmYGukt4EIQtLEp4ie45W+jK325uukGgiqFg2Rl7TdpOJQbOLUN2xMeGS+WvXaK0uIJ3coPGDXFGQ== flow-parser@^0.121.0: version "0.121.0" @@ -5497,7 +5498,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.2: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== @@ -6102,10 +6103,10 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.2.4: - version "1.2.5" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.5.tgz#6123e0b1fef5d7591514b371bb018204892f1a2b" - integrity sha512-ZIWRujF6MvYGkEuHMYtFRkL2wAtFw89EHfKlXrkPkjQZZRWeh9L1q3SV13NIfHnqxugjLvAOkEHx9mb1zcMnEw== +is-callable@^1.1.4, is-callable@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== is-ci@^2.0.0: version "2.0.0" @@ -6932,9 +6933,9 @@ jetifier@^1.6.2: integrity sha512-3Zi16h6L5tXDRQJTb221cnRoVG9/9OvreLdLU2/ZjRv/GILL+2Cemt0IKvkowwkDpvouAU1DQPOJ7qaiHeIdrw== joi@^17.2.1: - version "17.6.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2" - integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw== + version "17.6.1" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.1.tgz#e77422f277091711599634ac39a409e599d7bdaa" + integrity sha512-Hl7/iBklIX345OCM1TiFSCZRVaAOLDGlWCp0Df2vWYgBgjkezaR7Kvm3joBciBHQjZj5sxXs859r6eqsRSlG8w== dependencies: "@hapi/hoek" "^9.0.0" "@hapi/topo" "^5.0.0" @@ -9177,9 +9178,9 @@ rc@^1.2.8: strip-json-comments "~2.0.1" react-devtools-core@^4.6.0: - version "4.25.0" - resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.25.0.tgz#78b11a2c9f81dd9ebff3745ab4ee2147cc96c12a" - integrity sha512-iewRrnu0ZnmfL+jJayKphXj04CFh6i3ezVnpCtcnZbTPSQgN09XqHAzXbKbqNDl7aTg9QLNkQRP6M3DvdrinWA== + version "4.26.0" + resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.26.0.tgz#d3d0f59d62ccf1ac03017a7e92f0fe71455019cc" + integrity sha512-OO0Q+vXtHYCXvRQ6elLiOUph3MjsCpuYktGTLnBpizYm46f8tAPuJKihGkwsceitHSJNpzNIjJaYHgX96CyTUQ== dependencies: shell-quote "^1.6.1" ws "^7" @@ -9455,10 +9456,10 @@ reflect-metadata@^0.1.13: resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== -regenerate-unicode-properties@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" - integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== +regenerate-unicode-properties@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" + integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== dependencies: regenerate "^1.4.2" @@ -9502,26 +9503,26 @@ regexpp@^3.1.0: integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== regexpu-core@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.1.0.tgz#2f8504c3fd0ebe11215783a41541e21c79942c6d" - integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA== + version "5.2.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.1.tgz#a69c26f324c1e962e9ffd0b88b055caba8089139" + integrity sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ== dependencies: regenerate "^1.4.2" - regenerate-unicode-properties "^10.0.1" - regjsgen "^0.6.0" - regjsparser "^0.8.2" + regenerate-unicode-properties "^10.1.0" + regjsgen "^0.7.1" + regjsparser "^0.9.1" unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" -regjsgen@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" - integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== +regjsgen@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.7.1.tgz#ee5ef30e18d3f09b7c369b76e7c2373ed25546f6" + integrity sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA== -regjsparser@^0.8.2: - version "0.8.4" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" - integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== dependencies: jsesc "~0.5.0" @@ -9714,9 +9715,9 @@ rxjs@^6.6.0: tslib "^1.9.0" rxjs@^7.2.0: - version "7.5.6" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.6.tgz#0446577557862afd6903517ce7cae79ecb9662bc" - integrity sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw== + version "7.5.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39" + integrity sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA== dependencies: tslib "^2.1.0" @@ -9730,6 +9731,15 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, s resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -10797,9 +10807,9 @@ uglify-es@^3.1.9: source-map "~0.6.1" uglify-js@^3.1.4: - version "3.17.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.0.tgz#55bd6e9d19ce5eef0d5ad17cd1f587d85b180a85" - integrity sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg== + version "3.17.2" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.2.tgz#f55f668b9a64b213977ae688703b6bbb7ca861c6" + integrity sha512-bbxglRjsGQMchfvXZNusUcYgiB9Hx2K4AHYXQy2DITZ9Rd+JzhX7+hoocE5Winr7z2oHvPsekkBwXtigvxevXg== uid-number@0.0.6: version "0.0.6" @@ -10845,9 +10855,9 @@ unicode-match-property-value-ecmascript@^2.0.0: integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== unicode-property-aliases-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" - integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== union-value@^1.0.0: version "1.0.1" @@ -10911,10 +10921,10 @@ upath@^2.0.1: resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== -update-browserslist-db@^1.0.5: - version "1.0.8" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.8.tgz#2f0b711327668eee01bbecddcf4a7c7954a7f8e2" - integrity sha512-GHg7C4M7oJSJYW/ED/5QOJ7nL/E0lwTOBGsOorA7jqHr8ExUhPfwAotIAmdSw/LWv3SMLSNpzTAgeLG9zaZKTA== +update-browserslist-db@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz#2924d3927367a38d5c555413a7ce138fc95fcb18" + integrity sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg== dependencies: escalade "^3.1.1" picocolors "^1.0.0" From 85d62ece3a7a1178d9990ed24ca5116deee15db4 Mon Sep 17 00:00:00 2001 From: Sai Ranjit Tummalapalli <34263716+sairanjit@users.noreply.github.com> Date: Sat, 12 Nov 2022 01:37:56 +0530 Subject: [PATCH 075/125] refactor: remove dependency on indy ledger service from sov did resolver/registrar (#1086) Signed-off-by: Sai Ranjit Tummalapalli --- .../core/src/modules/dids/DidsModuleConfig.ts | 12 +- .../modules/dids/__tests__/DidsModule.test.ts | 8 +- .../dids/__tests__/DidsModuleConfig.test.ts | 8 +- .../dids/__tests__/dids-registrar.e2e.test.ts | 2 +- ...Registrar.ts => IndySdkSovDidRegistrar.ts} | 97 ++++++++-- .../dids/methods/sov/IndySdkSovDidResolver.ts | 98 ++++++++++ .../dids/methods/sov/SovDidResolver.ts | 47 ----- ...test.ts => IndySdkSovDidRegistrar.test.ts} | 70 ++++--- ....test.ts => IndySdkSovDidResolver.test.ts} | 62 +++++-- .../src/modules/dids/methods/sov/index.ts | 4 +- packages/core/src/modules/ledger/LedgerApi.ts | 6 + .../__tests__/IndyLedgerService.test.ts | 156 ---------------- .../ledger/__tests__/IndyPoolService.test.ts | 157 ++++++++++++++++ .../ledger/services/IndyLedgerService.ts | 175 ++++-------------- .../ledger/services/IndyPoolService.ts | 148 ++++++++++++++- 15 files changed, 625 insertions(+), 425 deletions(-) rename packages/core/src/modules/dids/methods/sov/{SovDidRegistrar.ts => IndySdkSovDidRegistrar.ts} (69%) create mode 100644 packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts delete mode 100644 packages/core/src/modules/dids/methods/sov/SovDidResolver.ts rename packages/core/src/modules/dids/methods/sov/__tests__/{SovDidRegistrar.test.ts => IndySdkSovDidRegistrar.test.ts} (82%) rename packages/core/src/modules/dids/methods/sov/__tests__/{SovDidResolver.test.ts => IndySdkSovDidResolver.test.ts} (52%) delete mode 100644 packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts diff --git a/packages/core/src/modules/dids/DidsModuleConfig.ts b/packages/core/src/modules/dids/DidsModuleConfig.ts index e05bd0daca..d4d71fa90b 100644 --- a/packages/core/src/modules/dids/DidsModuleConfig.ts +++ b/packages/core/src/modules/dids/DidsModuleConfig.ts @@ -3,11 +3,11 @@ import type { DidRegistrar, DidResolver } from './domain' import { KeyDidRegistrar, - SovDidRegistrar, + IndySdkSovDidRegistrar, PeerDidRegistrar, KeyDidResolver, PeerDidResolver, - SovDidResolver, + IndySdkSovDidResolver, WebDidResolver, } from './methods' @@ -25,7 +25,7 @@ export interface DidsModuleConfigOptions { * registered, as it is needed for the connections and out of band module to function. Other did methods can be * disabled. * - * @default [KeyDidRegistrar, SovDidRegistrar, PeerDidRegistrar] + * @default [KeyDidRegistrar, IndySdkSovDidRegistrar, PeerDidRegistrar] */ registrars?: Constructor[] @@ -38,7 +38,7 @@ export interface DidsModuleConfigOptions { * registered, as it is needed for the connections and out of band module to function. Other did methods can be * disabled. * - * @default [SovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver] + * @default [IndySdkSovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver] */ resolvers?: Constructor[] } @@ -52,7 +52,7 @@ export class DidsModuleConfig { /** See {@link DidsModuleConfigOptions.registrars} */ public get registrars() { - const registrars = this.options.registrars ?? [KeyDidRegistrar, SovDidRegistrar, PeerDidRegistrar] + const registrars = this.options.registrars ?? [KeyDidRegistrar, IndySdkSovDidRegistrar, PeerDidRegistrar] // If the peer did registrar is not included yet, add it return registrars.includes(PeerDidRegistrar) ? registrars : [...registrars, PeerDidRegistrar] @@ -60,7 +60,7 @@ export class DidsModuleConfig { /** See {@link DidsModuleConfigOptions.resolvers} */ public get resolvers() { - const resolvers = this.options.resolvers ?? [SovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver] + const resolvers = this.options.resolvers ?? [IndySdkSovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver] // If the peer did resolver is not included yet, add it return resolvers.includes(PeerDidResolver) ? resolvers : [...resolvers, PeerDidResolver] diff --git a/packages/core/src/modules/dids/__tests__/DidsModule.test.ts b/packages/core/src/modules/dids/__tests__/DidsModule.test.ts index 3a372fea59..5e9ca00503 100644 --- a/packages/core/src/modules/dids/__tests__/DidsModule.test.ts +++ b/packages/core/src/modules/dids/__tests__/DidsModule.test.ts @@ -11,8 +11,8 @@ import { KeyDidResolver, PeerDidRegistrar, PeerDidResolver, - SovDidRegistrar, - SovDidResolver, + IndySdkSovDidRegistrar, + IndySdkSovDidResolver, WebDidResolver, } from '../methods' import { DidRepository } from '../repository' @@ -39,13 +39,13 @@ describe('DidsModule', () => { expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRepository) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, SovDidResolver) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, IndySdkSovDidResolver) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, WebDidResolver) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, KeyDidResolver) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, PeerDidResolver) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarToken, KeyDidRegistrar) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarToken, SovDidRegistrar) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarToken, IndySdkSovDidRegistrar) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarToken, PeerDidRegistrar) }) diff --git a/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts b/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts index 08edee502e..af8fb61448 100644 --- a/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts +++ b/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts @@ -3,11 +3,11 @@ import type { DidRegistrar, DidResolver } from '../domain' import { KeyDidRegistrar, - SovDidRegistrar, + IndySdkSovDidRegistrar, PeerDidRegistrar, KeyDidResolver, PeerDidResolver, - SovDidResolver, + IndySdkSovDidResolver, WebDidResolver, } from '..' import { DidsModuleConfig } from '../DidsModuleConfig' @@ -16,8 +16,8 @@ describe('DidsModuleConfig', () => { test('sets default values', () => { const config = new DidsModuleConfig() - expect(config.registrars).toEqual([KeyDidRegistrar, SovDidRegistrar, PeerDidRegistrar]) - expect(config.resolvers).toEqual([SovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver]) + expect(config.registrars).toEqual([KeyDidRegistrar, IndySdkSovDidRegistrar, PeerDidRegistrar]) + expect(config.resolvers).toEqual([IndySdkSovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver]) }) test('sets values', () => { diff --git a/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts index 08b41b3b7b..7dd78ca824 100644 --- a/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts +++ b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts @@ -1,6 +1,6 @@ import type { KeyDidCreateOptions } from '../methods/key/KeyDidRegistrar' import type { PeerDidNumAlgo0CreateOptions } from '../methods/peer/PeerDidRegistrar' -import type { SovDidCreateOptions } from '../methods/sov/SovDidRegistrar' +import type { SovDidCreateOptions } from '../methods/sov/IndySdkSovDidRegistrar' import type { Wallet } from '@aries-framework/core' import { convertPublicKeyToX25519, generateKeyPairFromSeed } from '@stablelib/ed25519' diff --git a/packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts similarity index 69% rename from packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts rename to packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts index a1d83176b1..265975327b 100644 --- a/packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts +++ b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts @@ -1,41 +1,44 @@ import type { AgentContext } from '../../../../agent' -import type { IndyEndpointAttrib } from '../../../ledger' +import type { IndyEndpointAttrib, IndyPool } from '../../../ledger' import type { DidRegistrar } from '../../domain/DidRegistrar' import type { DidCreateOptions, DidCreateResult, DidDeactivateResult, DidUpdateResult } from '../../types' import type * as Indy from 'indy-sdk' import { AgentDependencies } from '../../../../agent/AgentDependencies' import { InjectionSymbols } from '../../../../constants' +import { IndySdkError } from '../../../../error' +import { Logger } from '../../../../logger' import { inject, injectable } from '../../../../plugins' +import { isIndyError } from '../../../../utils/indyError' import { assertIndyWallet } from '../../../../wallet/util/assertIndyWallet' -import { IndyLedgerService, IndyPoolService } from '../../../ledger' +import { IndyPoolService } from '../../../ledger' import { DidDocumentRole } from '../../domain/DidDocumentRole' import { DidRecord, DidRepository } from '../../repository' import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './util' @injectable() -export class SovDidRegistrar implements DidRegistrar { +export class IndySdkSovDidRegistrar implements DidRegistrar { public readonly supportedMethods = ['sov'] private didRepository: DidRepository private indy: typeof Indy - private indyLedgerService: IndyLedgerService private indyPoolService: IndyPoolService + private logger: Logger public constructor( didRepository: DidRepository, - indyLedgerService: IndyLedgerService, indyPoolService: IndyPoolService, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies + @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, + @inject(InjectionSymbols.Logger) logger: Logger ) { this.didRepository = didRepository this.indy = agentDependencies.indy - this.indyLedgerService = indyLedgerService this.indyPoolService = indyPoolService + this.logger = logger } public async create(agentContext: AgentContext, options: SovDidCreateOptions): Promise { - const { alias, role, submitterDid } = options.options + const { alias, role, submitterDid, indyNamespace } = options.options const seed = options.secret?.seed if (seed && (typeof seed !== 'string' || seed.length !== 32)) { @@ -76,21 +79,15 @@ export class SovDidRegistrar implements DidRegistrar { // TODO: it should be possible to pass the pool used for writing to the indy ledger service. // The easiest way to do this would be to make the submitterDid a fully qualified did, including the indy namespace. - await this.indyLedgerService.registerPublicDid( - agentContext, - unqualifiedSubmitterDid, - unqualifiedIndyDid, - verkey, - alias, - role - ) + const pool = this.indyPoolService.getPoolForNamespace(indyNamespace) + await this.registerPublicDid(agentContext, unqualifiedSubmitterDid, unqualifiedIndyDid, verkey, alias, pool, role) // Create did document const didDocumentBuilder = sovDidDocumentFromDid(qualifiedSovDid, verkey) // Add services if endpoints object was passed. if (options.options.endpoints) { - await this.indyLedgerService.setEndpointsForDid(agentContext, unqualifiedIndyDid, options.options.endpoints) + await this.setEndpointsForDid(agentContext, unqualifiedIndyDid, options.options.endpoints, pool) addServicesFromEndpointsAttrib( didDocumentBuilder, qualifiedSovDid, @@ -102,7 +99,7 @@ export class SovDidRegistrar implements DidRegistrar { // Build did document. const didDocument = didDocumentBuilder.build() - const didIndyNamespace = this.indyPoolService.ledgerWritePool.config.indyNamespace + const didIndyNamespace = pool.config.indyNamespace const qualifiedIndyDid = `did:indy:${didIndyNamespace}:${unqualifiedIndyDid}` // Save the did so we know we created it and can issue with it @@ -170,6 +167,69 @@ export class SovDidRegistrar implements DidRegistrar { }, } } + + public async registerPublicDid( + agentContext: AgentContext, + submitterDid: string, + targetDid: string, + verkey: string, + alias: string, + pool: IndyPool, + role?: Indy.NymRole + ) { + try { + this.logger.debug(`Register public did '${targetDid}' on ledger '${pool.id}'`) + + const request = await this.indy.buildNymRequest(submitterDid, targetDid, verkey, alias, role || null) + + const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, submitterDid) + + this.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.id}'`, { + response, + }) + + return targetDid + } catch (error) { + this.logger.error(`Error registering public did '${targetDid}' on ledger '${pool.id}'`, { + error, + submitterDid, + targetDid, + verkey, + alias, + role, + pool: pool.id, + }) + + throw error + } + } + + public async setEndpointsForDid( + agentContext: AgentContext, + did: string, + endpoints: IndyEndpointAttrib, + pool: IndyPool + ): Promise { + try { + this.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.id}'`, endpoints) + + const request = await this.indy.buildAttribRequest(did, did, null, { endpoint: endpoints }, null) + + const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, did) + this.logger.debug(`Successfully set endpoints for did '${did}' on ledger '${pool.id}'`, { + response, + endpoints, + }) + } catch (error) { + this.logger.error(`Error setting endpoints for did '${did}' on ledger '${pool.id}'`, { + error, + did, + endpoints, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } } export interface SovDidCreateOptions extends DidCreateOptions { @@ -182,6 +242,7 @@ export interface SovDidCreateOptions extends DidCreateOptions { alias: string role?: Indy.NymRole endpoints?: IndyEndpointAttrib + indyNamespace?: string submitterDid: string } secret?: { diff --git a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts new file mode 100644 index 0000000000..ecf324e644 --- /dev/null +++ b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts @@ -0,0 +1,98 @@ +import type { AgentContext } from '../../../../agent' +import type { IndyEndpointAttrib } from '../../../ledger' +import type { DidResolver } from '../../domain/DidResolver' +import type { DidResolutionResult, ParsedDid } from '../../types' +import type * as Indy from 'indy-sdk' + +import { isIndyError } from '../../../..//utils/indyError' +import { AgentDependencies } from '../../../../agent/AgentDependencies' +import { InjectionSymbols } from '../../../../constants' +import { IndySdkError } from '../../../../error' +import { Logger } from '../../../../logger' +import { inject, injectable } from '../../../../plugins' +import { IndyPoolService } from '../../../ledger' + +import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './util' + +@injectable() +export class IndySdkSovDidResolver implements DidResolver { + private indy: typeof Indy + private indyPoolService: IndyPoolService + private logger: Logger + + public constructor( + indyPoolService: IndyPoolService, + @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, + @inject(InjectionSymbols.Logger) logger: Logger + ) { + this.indy = agentDependencies.indy + this.indyPoolService = indyPoolService + this.logger = logger + } + + public readonly supportedMethods = ['sov'] + + public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { + const didDocumentMetadata = {} + + try { + const nym = await this.getPublicDid(agentContext, parsed.id) + const endpoints = await this.getEndpointsForDid(agentContext, parsed.id) + + const keyAgreementId = `${parsed.did}#key-agreement-1` + const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) + addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) + + return { + didDocument: builder.build(), + didDocumentMetadata, + didResolutionMetadata: { contentType: 'application/did+ld+json' }, + } + } catch (error) { + return { + didDocument: null, + didDocumentMetadata, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did '${did}': ${error}`, + }, + } + } + } + + private async getPublicDid(agentContext: AgentContext, did: string) { + // Getting the pool for a did also retrieves the DID. We can just use that + const { did: didResponse } = await this.indyPoolService.getPoolForDid(agentContext, did) + + return didResponse + } + + private async getEndpointsForDid(agentContext: AgentContext, did: string) { + const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) + + try { + this.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.id}'`) + + const request = await this.indy.buildGetAttribRequest(null, did, 'endpoint', null, null) + + this.logger.debug(`Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.id}'`) + const response = await this.indyPoolService.submitReadRequest(pool, request) + + if (!response.result.data) return {} + + const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib + this.logger.debug(`Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.id}'`, { + response, + endpoints, + }) + + return endpoints ?? {} + } catch (error) { + this.logger.error(`Error retrieving endpoints for did '${did}' from ledger '${pool.id}'`, { + error, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } +} diff --git a/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts b/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts deleted file mode 100644 index 79636d3fff..0000000000 --- a/packages/core/src/modules/dids/methods/sov/SovDidResolver.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { DidResolver } from '../../domain/DidResolver' -import type { DidResolutionResult, ParsedDid } from '../../types' - -import { injectable } from '../../../../plugins' -import { IndyLedgerService } from '../../../ledger' - -import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './util' - -@injectable() -export class SovDidResolver implements DidResolver { - private indyLedgerService: IndyLedgerService - - public constructor(indyLedgerService: IndyLedgerService) { - this.indyLedgerService = indyLedgerService - } - - public readonly supportedMethods = ['sov'] - - public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { - const didDocumentMetadata = {} - - try { - const nym = await this.indyLedgerService.getPublicDid(agentContext, parsed.id) - const endpoints = await this.indyLedgerService.getEndpointsForDid(agentContext, parsed.id) - - const keyAgreementId = `${parsed.did}#key-agreement-1` - const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) - addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) - - return { - didDocument: builder.build(), - didDocumentMetadata, - didResolutionMetadata: { contentType: 'application/did+ld+json' }, - } - } catch (error) { - return { - didDocument: null, - didDocumentMetadata, - didResolutionMetadata: { - error: 'notFound', - message: `resolver_error: Unable to resolve did '${did}': ${error}`, - }, - } - } - } -} diff --git a/packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts similarity index 82% rename from packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts rename to packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts index 073b67a3e8..0c0275897c 100644 --- a/packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts +++ b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts @@ -6,24 +6,22 @@ import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../ import { SigningProviderRegistry } from '../../../../../crypto/signing-provider' import { JsonTransformer } from '../../../../../utils/JsonTransformer' import { IndyWallet } from '../../../../../wallet/IndyWallet' -import { IndyLedgerService } from '../../../../ledger/services/IndyLedgerService' import { IndyPoolService } from '../../../../ledger/services/IndyPoolService' import { DidDocumentRole } from '../../../domain/DidDocumentRole' import { DidRepository } from '../../../repository/DidRepository' -import { SovDidRegistrar } from '../SovDidRegistrar' +import { IndySdkSovDidRegistrar } from '../IndySdkSovDidRegistrar' jest.mock('../../../repository/DidRepository') const DidRepositoryMock = DidRepository as jest.Mock -jest.mock('../../../../ledger/services/IndyLedgerService') -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock - jest.mock('../../../../ledger/services/IndyPoolService') const IndyPoolServiceMock = IndyPoolService as jest.Mock const indyPoolServiceMock = new IndyPoolServiceMock() -mockProperty(indyPoolServiceMock, 'ledgerWritePool', { config: { id: 'pool1', indyNamespace: 'pool1' } } as IndyPool) +mockFunction(indyPoolServiceMock.getPoolForNamespace).mockReturnValue({ + config: { id: 'pool1', indyNamespace: 'pool1' }, +} as IndyPool) -const agentConfig = getAgentConfig('SovDidRegistrar') +const agentConfig = getAgentConfig('IndySdkSovDidRegistrar') const createDidMock = jest.fn(async () => ['R1xKJw17sUoXhejEpugMYJ', 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu']) @@ -35,20 +33,24 @@ const agentContext = getAgentContext({ }) const didRepositoryMock = new DidRepositoryMock() -const indyLedgerServiceMock = new IndyLedgerServiceMock() -const sovDidRegistrar = new SovDidRegistrar(didRepositoryMock, indyLedgerServiceMock, indyPoolServiceMock, { - ...agentConfig.agentDependencies, - indy: { createAndStoreMyDid: createDidMock } as unknown as typeof Indy, -}) +const indySdkSovDidRegistrar = new IndySdkSovDidRegistrar( + didRepositoryMock, + indyPoolServiceMock, + { + ...agentConfig.agentDependencies, + indy: { createAndStoreMyDid: createDidMock } as unknown as typeof Indy, + }, + agentConfig.logger +) describe('DidRegistrar', () => { - describe('SovDidRegistrar', () => { + describe('IndySdkSovDidRegistrar', () => { afterEach(() => { jest.clearAllMocks() }) it('should return an error state if an invalid seed is provided', async () => { - const result = await sovDidRegistrar.create(agentContext, { + const result = await indySdkSovDidRegistrar.create(agentContext, { method: 'sov', options: { @@ -75,7 +77,7 @@ describe('DidRegistrar', () => { wallet: {} as unknown as Wallet, }) - const result = await sovDidRegistrar.create(agentContext, { + const result = await indySdkSovDidRegistrar.create(agentContext, { method: 'sov', options: { @@ -98,7 +100,7 @@ describe('DidRegistrar', () => { }) it('should return an error state if the submitter did is not qualified with did:sov', async () => { - const result = await sovDidRegistrar.create(agentContext, { + const result = await indySdkSovDidRegistrar.create(agentContext, { method: 'sov', options: { submitterDid: 'BzCbsNYhMrjHiqZDTUASHg', @@ -118,7 +120,11 @@ describe('DidRegistrar', () => { it('should correctly create a did:sov document without services', async () => { const seed = '96213c3d7fc8d4d6754c712fd969598e' - const result = await sovDidRegistrar.create(agentContext, { + + const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('R1xKJw17sUoXhejEpugMYJ')) + + const result = await indySdkSovDidRegistrar.create(agentContext, { method: 'sov', options: { alias: 'Hello', @@ -130,7 +136,7 @@ describe('DidRegistrar', () => { }, }) - expect(indyLedgerServiceMock.registerPublicDid).toHaveBeenCalledWith( + expect(registerPublicDidSpy).toHaveBeenCalledWith( agentContext, // Unqualified submitter did 'BzCbsNYhMrjHiqZDTUASHg', @@ -140,6 +146,8 @@ describe('DidRegistrar', () => { 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', // Alias 'Hello', + // Pool + { config: { id: 'pool1', indyNamespace: 'pool1' } }, // Role 'STEWARD' ) @@ -187,7 +195,14 @@ describe('DidRegistrar', () => { it('should correctly create a did:sov document with services', async () => { const seed = '96213c3d7fc8d4d6754c712fd969598e' - const result = await sovDidRegistrar.create(agentContext, { + + const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('R1xKJw17sUoXhejEpugMYJ')) + + const setEndpointsForDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'setEndpointsForDid') + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + const result = await indySdkSovDidRegistrar.create(agentContext, { method: 'sov', options: { alias: 'Hello', @@ -204,7 +219,7 @@ describe('DidRegistrar', () => { }, }) - expect(indyLedgerServiceMock.registerPublicDid).toHaveBeenCalledWith( + expect(registerPublicDidSpy).toHaveBeenCalledWith( agentContext, // Unqualified submitter did 'BzCbsNYhMrjHiqZDTUASHg', @@ -214,6 +229,8 @@ describe('DidRegistrar', () => { 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', // Alias 'Hello', + // Pool + { config: { id: 'pool1', indyNamespace: 'pool1' } }, // Role 'STEWARD' ) @@ -285,7 +302,14 @@ describe('DidRegistrar', () => { it('should store the did document', async () => { const seed = '96213c3d7fc8d4d6754c712fd969598e' - await sovDidRegistrar.create(agentContext, { + + const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('did')) + + const setEndpointsForDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'setEndpointsForDid') + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + await indySdkSovDidRegistrar.create(agentContext, { method: 'sov', options: { alias: 'Hello', @@ -317,7 +341,7 @@ describe('DidRegistrar', () => { }) it('should return an error state when calling update', async () => { - const result = await sovDidRegistrar.update() + const result = await indySdkSovDidRegistrar.update() expect(result).toEqual({ didDocumentMetadata: {}, @@ -330,7 +354,7 @@ describe('DidRegistrar', () => { }) it('should return an error state when calling deactivate', async () => { - const result = await sovDidRegistrar.deactivate() + const result = await indySdkSovDidRegistrar.deactivate() expect(result).toEqual({ didDocumentMetadata: {}, diff --git a/packages/core/src/modules/dids/methods/sov/__tests__/SovDidResolver.test.ts b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts similarity index 52% rename from packages/core/src/modules/dids/methods/sov/__tests__/SovDidResolver.test.ts rename to packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts index b1dd46280f..2ca3c3f82b 100644 --- a/packages/core/src/modules/dids/methods/sov/__tests__/SovDidResolver.test.ts +++ b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts @@ -1,27 +1,41 @@ import type { AgentContext } from '../../../../../agent' +import type { IndyPool } from '../../../../ledger' import type { IndyEndpointAttrib } from '../../../../ledger/services/IndyLedgerService' import type { GetNymResponse } from 'indy-sdk' -import { getAgentContext, mockFunction } from '../../../../../../tests/helpers' +import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../../../tests/helpers' +import { SigningProviderRegistry } from '../../../../../crypto/signing-provider' import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { IndyLedgerService } from '../../../../ledger/services/IndyLedgerService' +import { IndyWallet } from '../../../../../wallet/IndyWallet' +import { IndyPoolService } from '../../../../ledger/services/IndyPoolService' import didSovR1xKJw17sUoXhejEpugMYJFixture from '../../../__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json' import didSovWJz9mHyW9BZksioQnRsrAoFixture from '../../../__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json' import { parseDid } from '../../../domain/parse' -import { SovDidResolver } from '../SovDidResolver' +import { IndySdkSovDidResolver } from '../IndySdkSovDidResolver' -jest.mock('../../../../ledger/services/IndyLedgerService') -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock +jest.mock('../../../../ledger/services/IndyPoolService') +const IndyPoolServiceMock = IndyPoolService as jest.Mock +const indyPoolServiceMock = new IndyPoolServiceMock() +mockFunction(indyPoolServiceMock.getPoolForNamespace).mockReturnValue({ + config: { id: 'pool1', indyNamespace: 'pool1' }, +} as IndyPool) + +const agentConfig = getAgentConfig('IndySdkSovDidResolver') + +const wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) +mockProperty(wallet, 'handle', 10) describe('DidResolver', () => { - describe('SovDidResolver', () => { - let ledgerService: IndyLedgerService - let sovDidResolver: SovDidResolver + describe('IndySdkSovDidResolver', () => { + let indySdkSovDidResolver: IndySdkSovDidResolver let agentContext: AgentContext beforeEach(() => { - ledgerService = new IndyLedgerServiceMock() - sovDidResolver = new SovDidResolver(ledgerService) + indySdkSovDidResolver = new IndySdkSovDidResolver( + indyPoolServiceMock, + agentConfig.agentDependencies, + agentConfig.logger + ) agentContext = getAgentContext() }) @@ -40,10 +54,15 @@ describe('DidResolver', () => { hub: 'https://hub.com', } - mockFunction(ledgerService.getPublicDid).mockResolvedValue(nymResponse) - mockFunction(ledgerService.getEndpointsForDid).mockResolvedValue(endpoints) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) - const result = await sovDidResolver.resolve(agentContext, did, parseDid(did)) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) + + const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) expect(JsonTransformer.toJSON(result)).toMatchObject({ didDocument: didSovR1xKJw17sUoXhejEpugMYJFixture, @@ -69,10 +88,15 @@ describe('DidResolver', () => { routingKeys: ['routingKey1', 'routingKey2'], } - mockFunction(ledgerService.getPublicDid).mockReturnValue(Promise.resolve(nymResponse)) - mockFunction(ledgerService.getEndpointsForDid).mockReturnValue(Promise.resolve(endpoints)) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) - const result = await sovDidResolver.resolve(agentContext, did, parseDid(did)) + const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) expect(JsonTransformer.toJSON(result)).toMatchObject({ didDocument: didSovWJz9mHyW9BZksioQnRsrAoFixture, @@ -86,9 +110,11 @@ describe('DidResolver', () => { it('should return did resolution metadata with error if the indy ledger service throws an error', async () => { const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' - mockFunction(ledgerService.getPublicDid).mockRejectedValue(new Error('Error retrieving did')) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockRejectedValue(new Error('Error retrieving did')) - const result = await sovDidResolver.resolve(agentContext, did, parseDid(did)) + const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) expect(result).toMatchObject({ didDocument: null, diff --git a/packages/core/src/modules/dids/methods/sov/index.ts b/packages/core/src/modules/dids/methods/sov/index.ts index 82c05ea971..13a8e8aa2f 100644 --- a/packages/core/src/modules/dids/methods/sov/index.ts +++ b/packages/core/src/modules/dids/methods/sov/index.ts @@ -1,2 +1,2 @@ -export * from './SovDidRegistrar' -export * from './SovDidResolver' +export * from './IndySdkSovDidRegistrar' +export * from './IndySdkSovDidResolver' diff --git a/packages/core/src/modules/ledger/LedgerApi.ts b/packages/core/src/modules/ledger/LedgerApi.ts index 1cf2deef4f..bb836cf50d 100644 --- a/packages/core/src/modules/ledger/LedgerApi.ts +++ b/packages/core/src/modules/ledger/LedgerApi.ts @@ -55,6 +55,9 @@ export class LedgerApi { await this.ledgerService.connectToPools() } + /** + * @deprecated use agent.dids.create instead + */ public async registerPublicDid(did: string, verkey: string, alias: string, role?: NymRole) { const myPublicDid = this.agentContext.wallet.publicDid?.did @@ -65,6 +68,9 @@ export class LedgerApi { return this.ledgerService.registerPublicDid(this.agentContext, myPublicDid, did, verkey, alias, role) } + /** + * @deprecated use agent.dids.resolve instead + */ public async getPublicDid(did: string) { return this.ledgerService.getPublicDid(this.agentContext, did) } diff --git a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts b/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts deleted file mode 100644 index 43cc31f66e..0000000000 --- a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type { IndyPoolConfig } from '../IndyPool' -import type { LedgerReadReplyResponse, LedgerWriteReplyResponse } from 'indy-sdk' - -import { Subject } from 'rxjs' - -import { NodeFileSystem } from '../../../../../node/src/NodeFileSystem' -import { getAgentConfig, getAgentContext, mockFunction } from '../../../../tests/helpers' -import { CacheRepository } from '../../../cache/CacheRepository' -import { SigningProviderRegistry } from '../../../crypto/signing-provider' -import { IndyWallet } from '../../../wallet/IndyWallet' -import { IndyIssuerService } from '../../indy/services/IndyIssuerService' -import { IndyPool } from '../IndyPool' -import { IndyLedgerService } from '../services/IndyLedgerService' -import { IndyPoolService } from '../services/IndyPoolService' - -jest.mock('../services/IndyPoolService') -const IndyPoolServiceMock = IndyPoolService as jest.Mock -jest.mock('../../indy/services/IndyIssuerService') -const IndyIssuerServiceMock = IndyIssuerService as jest.Mock -jest.mock('../../../cache/CacheRepository') -const CacheRepositoryMock = CacheRepository as jest.Mock - -const pools: IndyPoolConfig[] = [ - { - id: 'sovrin', - indyNamespace: 'sovrin', - isProduction: true, - genesisTransactions: 'xxx', - transactionAuthorAgreement: { version: '1.0', acceptanceMechanism: 'accept' }, - }, -] - -describe('IndyLedgerService', () => { - const config = getAgentConfig('IndyLedgerServiceTest', { - indyLedgers: pools, - }) - let wallet: IndyWallet - let agentContext: AgentContext - let poolService: IndyPoolService - let cacheRepository: CacheRepository - let indyIssuerService: IndyIssuerService - let ledgerService: IndyLedgerService - - beforeAll(async () => { - wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([])) - agentContext = getAgentContext() - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(config.walletConfig!) - }) - - afterAll(async () => { - await wallet.delete() - }) - - beforeEach(async () => { - cacheRepository = new CacheRepositoryMock() - mockFunction(cacheRepository.findById).mockResolvedValue(null) - indyIssuerService = new IndyIssuerServiceMock() - poolService = new IndyPoolServiceMock() - const pool = new IndyPool(pools[0], config.agentDependencies, config.logger, new Subject(), new NodeFileSystem()) - jest.spyOn(pool, 'submitWriteRequest').mockResolvedValue({} as LedgerWriteReplyResponse) - jest.spyOn(pool, 'submitReadRequest').mockResolvedValue({} as LedgerReadReplyResponse) - jest.spyOn(pool, 'connect').mockResolvedValue(0) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - poolService.ledgerWritePool = pool - - ledgerService = new IndyLedgerService(config.agentDependencies, config.logger, indyIssuerService, poolService) - }) - - describe('LedgerServiceWrite', () => { - it('should throw an error if the config version does not match', async () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(ledgerService, 'getTransactionAuthorAgreement').mockResolvedValue({ - digest: 'abcde', - version: '2.0', - text: 'jhsdhbv', - ratification_ts: 12345678, - acceptanceMechanisms: { - aml: { accept: 'accept' }, - amlContext: 'accept', - version: '3', - }, - } as never) - await expect( - ledgerService.registerPublicDid( - agentContext, - 'BBPoJqRKatdcfLEAFL7exC', - 'N8NQHLtCKfPmWMgCSdfa7h', - 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', - 'Heinz57' - ) - ).rejects.toThrowError( - 'Unable to satisfy matching TAA with mechanism "accept" and version "1.0" in pool.\n Found ["accept"] and version 2.0 in pool.' - ) - }) - - it('should throw an error if the config acceptance mechanism does not match', async () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(ledgerService, 'getTransactionAuthorAgreement').mockResolvedValue({ - digest: 'abcde', - version: '1.0', - text: 'jhsdhbv', - ratification_ts: 12345678, - acceptanceMechanisms: { - aml: { decline: 'accept' }, - amlContext: 'accept', - version: '1', - }, - } as never) - await expect( - ledgerService.registerPublicDid( - agentContext, - 'BBPoJqRKatdcfLEAFL7exC', - 'N8NQHLtCKfPmWMgCSdfa7h', - 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', - 'Heinz57' - ) - ).rejects.toThrowError( - 'Unable to satisfy matching TAA with mechanism "accept" and version "1.0" in pool.\n Found ["decline"] and version 1.0 in pool.' - ) - }) - - it('should throw an error if no config is present', async () => { - poolService.ledgerWritePool.authorAgreement = undefined - poolService.ledgerWritePool.config.transactionAuthorAgreement = undefined - - ledgerService = new IndyLedgerService(config.agentDependencies, config.logger, indyIssuerService, poolService) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(ledgerService, 'getTransactionAuthorAgreement').mockResolvedValue({ - digest: 'abcde', - version: '1.0', - text: 'jhsdhbv', - ratification_ts: 12345678, - acceptanceMechanisms: { - aml: { accept: 'accept' }, - amlContext: 'accept', - version: '3', - }, - } as never) - await expect( - ledgerService.registerPublicDid( - agentContext, - 'BBPoJqRKatdcfLEAFL7exC', - 'N8NQHLtCKfPmWMgCSdfa7h', - 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', - 'Heinz57' - ) - ).rejects.toThrowError(/Please, specify a transaction author agreement with version and acceptance mechanism/) - }) - }) -}) diff --git a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts b/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts index 1cdae2af73..073d08686f 100644 --- a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts +++ b/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts @@ -300,4 +300,161 @@ describe('IndyPoolService', () => { }) }) }) + + describe('getPoolForNamespace', () => { + it('should throw a LedgerNotConfiguredError error if no pools are configured on the pool service', async () => { + poolService.setPools([]) + + expect(() => poolService.getPoolForNamespace()).toThrow(LedgerNotConfiguredError) + }) + + it('should return the first pool if indyNamespace is not provided', async () => { + const expectedPool = pools[0] + + expect(poolService.getPoolForNamespace().id).toEqual(expectedPool.id) + }) + + it('should throw a LedgerNotFoundError error if any of the pools did not have the provided indyNamespace', async () => { + const indyNameSpace = 'test' + const responses = pools.map((pool) => pool.indyNamespace) + + poolService.pools.forEach((pool, index) => { + const spy = jest.spyOn(pool, 'didIndyNamespace', 'get') + spy.mockReturnValueOnce(responses[index]) + }) + + expect(() => poolService.getPoolForNamespace(indyNameSpace)).toThrow(LedgerNotFoundError) + }) + + it('should return the first pool that indyNamespace matches', async () => { + const expectedPool = pools[3] + const indyNameSpace = 'indicio' + const responses = pools.map((pool) => pool.indyNamespace) + + poolService.pools.forEach((pool, index) => { + const spy = jest.spyOn(pool, 'didIndyNamespace', 'get') + spy.mockReturnValueOnce(responses[index]) + }) + + const pool = poolService.getPoolForNamespace(indyNameSpace) + + expect(pool.id).toEqual(expectedPool.id) + }) + }) + + describe('submitWriteRequest', () => { + it('should throw an error if the config version does not match', async () => { + const pool = poolService.getPoolForNamespace() + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(poolService, 'getTransactionAuthorAgreement').mockResolvedValue({ + digest: 'abcde', + version: '2.0', + text: 'jhsdhbv', + ratification_ts: 12345678, + acceptanceMechanisms: { + aml: { accept: 'accept' }, + amlContext: 'accept', + version: '3', + }, + } as never) + await expect( + poolService.submitWriteRequest( + agentContext, + pool, + { + reqId: 1668174449192969000, + identifier: 'BBPoJqRKatdcfLEAFL7exC', + operation: { + type: '1', + dest: 'N8NQHLtCKfPmWMgCSdfa7h', + verkey: 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', + alias: 'Heinz57', + }, + protocolVersion: 2, + }, + 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf' + ) + ).rejects.toThrowError( + 'Unable to satisfy matching TAA with mechanism "accept" and version "1" in pool.\n Found ["accept"] and version 2.0 in pool.' + ) + }) + + it('should throw an error if the config acceptance mechanism does not match', async () => { + const pool = poolService.getPoolForNamespace() + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(poolService, 'getTransactionAuthorAgreement').mockResolvedValue({ + digest: 'abcde', + version: '1.0', + text: 'jhsdhbv', + ratification_ts: 12345678, + acceptanceMechanisms: { + aml: { decline: 'accept' }, + amlContext: 'accept', + version: '1', + }, + } as never) + await expect( + poolService.submitWriteRequest( + agentContext, + pool, + { + reqId: 1668174449192969000, + identifier: 'BBPoJqRKatdcfLEAFL7exC', + operation: { + type: '1', + dest: 'N8NQHLtCKfPmWMgCSdfa7h', + verkey: 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', + alias: 'Heinz57', + }, + protocolVersion: 2, + }, + 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf' + ) + ).rejects.toThrowError( + 'Unable to satisfy matching TAA with mechanism "accept" and version "1" in pool.\n Found ["decline"] and version 1.0 in pool.' + ) + }) + + it('should throw an error if no config is present', async () => { + const pool = poolService.getPoolForNamespace() + pool.authorAgreement = undefined + pool.config.transactionAuthorAgreement = undefined + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(poolService, 'getTransactionAuthorAgreement').mockResolvedValue({ + digest: 'abcde', + version: '1.0', + text: 'jhsdhbv', + ratification_ts: 12345678, + acceptanceMechanisms: { + aml: { accept: 'accept' }, + amlContext: 'accept', + version: '3', + }, + } as never) + await expect( + poolService.submitWriteRequest( + agentContext, + pool, + { + reqId: 1668174449192969000, + identifier: 'BBPoJqRKatdcfLEAFL7exC', + operation: { + type: '1', + dest: 'N8NQHLtCKfPmWMgCSdfa7h', + verkey: 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', + alias: 'Heinz57', + }, + protocolVersion: 2, + }, + 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf' + ) + ).rejects.toThrowError(/Please, specify a transaction author agreement with version and acceptance mechanism/) + }) + }) }) diff --git a/packages/core/src/modules/ledger/services/IndyLedgerService.ts b/packages/core/src/modules/ledger/services/IndyLedgerService.ts index 4a339e7add..df5e535d6d 100644 --- a/packages/core/src/modules/ledger/services/IndyLedgerService.ts +++ b/packages/core/src/modules/ledger/services/IndyLedgerService.ts @@ -1,14 +1,6 @@ import type { AgentContext } from '../../../agent' -import type { AcceptanceMechanisms, AuthorAgreement, IndyPool, IndyPoolConfig } from '../IndyPool' -import type { - CredDef, - default as Indy, - LedgerReadReplyResponse, - LedgerRequest, - LedgerWriteReplyResponse, - NymRole, - Schema, -} from 'indy-sdk' +import type { IndyPoolConfig } from '../IndyPool' +import type { CredDef, default as Indy, NymRole, Schema } from 'indy-sdk' import { AgentDependencies } from '../../../agent/AgentDependencies' import { InjectionSymbols } from '../../../constants' @@ -21,9 +13,7 @@ import { didFromSchemaId, } from '../../../utils/did' import { isIndyError } from '../../../utils/indyError' -import { assertIndyWallet } from '../../../wallet/util/assertIndyWallet' import { IndyIssuerService } from '../../indy/services/IndyIssuerService' -import { LedgerError } from '../error/LedgerError' import { IndyPoolService } from './IndyPoolService' @@ -51,6 +41,9 @@ export class IndyLedgerService { return this.indyPoolService.setPools(poolConfigs) } + /** + * @deprecated + */ public getDidIndyWriteNamespace(): string { return this.indyPoolService.ledgerWritePool.config.indyNamespace } @@ -59,6 +52,9 @@ export class IndyLedgerService { return this.indyPoolService.connectToPools() } + /** + * @deprecated + */ public async registerPublicDid( agentContext: AgentContext, submitterDid: string, @@ -67,14 +63,14 @@ export class IndyLedgerService { alias: string, role?: NymRole ) { - const pool = this.indyPoolService.ledgerWritePool + const pool = this.indyPoolService.getPoolForNamespace() try { this.logger.debug(`Register public did '${targetDid}' on ledger '${pool.id}'`) const request = await this.indy.buildNymRequest(submitterDid, targetDid, verkey, alias, role || null) - const response = await this.submitWriteRequest(agentContext, pool, request, submitterDid) + const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, submitterDid) this.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.id}'`, { response, @@ -96,6 +92,9 @@ export class IndyLedgerService { } } + /** + * @deprecated + */ public async getPublicDid(agentContext: AgentContext, did: string) { // Getting the pool for a did also retrieves the DID. We can just use that const { did: didResponse } = await this.indyPoolService.getPoolForDid(agentContext, did) @@ -103,19 +102,22 @@ export class IndyLedgerService { return didResponse } + /** + * @deprecated + */ public async setEndpointsForDid( agentContext: AgentContext, did: string, endpoints: IndyEndpointAttrib ): Promise { - const pool = this.indyPoolService.ledgerWritePool + const pool = this.indyPoolService.getPoolForNamespace() try { this.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.id}'`, endpoints) const request = await this.indy.buildAttribRequest(did, did, null, { endpoint: endpoints }, null) - const response = await this.submitWriteRequest(agentContext, pool, request, did) + const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, did) this.logger.debug(`Successfully set endpoints for did '${did}' on ledger '${pool.id}'`, { response, endpoints, @@ -131,6 +133,9 @@ export class IndyLedgerService { } } + /** + * @deprecated + */ public async getEndpointsForDid(agentContext: AgentContext, did: string) { const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) @@ -140,7 +145,7 @@ export class IndyLedgerService { const request = await this.indy.buildGetAttribRequest(null, did, 'endpoint', null, null) this.logger.debug(`Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.id}'`) - const response = await this.submitReadRequest(pool, request) + const response = await this.indyPoolService.submitReadRequest(pool, request) if (!response.result.data) return {} @@ -165,7 +170,7 @@ export class IndyLedgerService { did: string, schemaTemplate: SchemaTemplate ): Promise { - const pool = this.indyPoolService.ledgerWritePool + const pool = this.indyPoolService.getPoolForNamespace() try { this.logger.debug(`Register schema on ledger '${pool.id}' with did '${did}'`, schemaTemplate) @@ -174,7 +179,7 @@ export class IndyLedgerService { const request = await this.indy.buildSchemaRequest(did, schema) - const response = await this.submitWriteRequest(agentContext, pool, request, did) + const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, did) this.logger.debug(`Registered schema '${schema.id}' on ledger '${pool.id}'`, { response, schema, @@ -204,7 +209,7 @@ export class IndyLedgerService { const request = await this.indy.buildGetSchemaRequest(null, schemaId) this.logger.trace(`Submitting get schema request for schema '${schemaId}' to ledger '${pool.id}'`) - const response = await this.submitReadRequest(pool, request) + const response = await this.indyPoolService.submitReadRequest(pool, request) this.logger.trace(`Got un-parsed schema '${schemaId}' from ledger '${pool.id}'`, { response, @@ -231,7 +236,7 @@ export class IndyLedgerService { did: string, credentialDefinitionTemplate: CredentialDefinitionTemplate ): Promise { - const pool = this.indyPoolService.ledgerWritePool + const pool = this.indyPoolService.getPoolForNamespace() try { this.logger.debug( @@ -250,7 +255,7 @@ export class IndyLedgerService { const request = await this.indy.buildCredDefRequest(did, credentialDefinition) - const response = await this.submitWriteRequest(agentContext, pool, request, did) + const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, did) this.logger.debug(`Registered credential definition '${credentialDefinition.id}' on ledger '${pool.id}'`, { response, @@ -285,7 +290,7 @@ export class IndyLedgerService { `Submitting get credential definition request for credential definition '${credentialDefinitionId}' to ledger '${pool.id}'` ) - const response = await this.submitReadRequest(pool, request) + const response = await this.indyPoolService.submitReadRequest(pool, request) this.logger.trace(`Got un-parsed credential definition '${credentialDefinitionId}' from ledger '${pool.id}'`, { response, }) @@ -328,7 +333,7 @@ export class IndyLedgerService { this.logger.trace( `Submitting get revocation registry definition request for revocation registry definition '${revocationRegistryDefinitionId}' to ledger` ) - const response = await this.submitReadRequest(pool, request) + const response = await this.indyPoolService.submitReadRequest(pool, request) this.logger.trace( `Got un-parsed revocation registry definition '${revocationRegistryDefinitionId}' from ledger '${pool.id}'`, { @@ -382,7 +387,7 @@ export class IndyLedgerService { `Submitting get revocation registry delta request for revocation registry '${revocationRegistryDefinitionId}' to ledger` ) - const response = await this.submitReadRequest(pool, request) + const response = await this.indyPoolService.submitReadRequest(pool, request) this.logger.trace( `Got revocation registry delta unparsed-response '${revocationRegistryDefinitionId}' from ledger`, { @@ -436,7 +441,7 @@ export class IndyLedgerService { this.logger.trace( `Submitting get revocation registry request for revocation registry '${revocationRegistryDefinitionId}' to ledger` ) - const response = await this.submitReadRequest(pool, request) + const response = await this.indyPoolService.submitReadRequest(pool, request) this.logger.trace( `Got un-parsed revocation registry '${revocationRegistryDefinitionId}' from ledger '${pool.id}'`, { @@ -460,124 +465,6 @@ export class IndyLedgerService { throw error } } - - private async submitWriteRequest( - agentContext: AgentContext, - pool: IndyPool, - request: LedgerRequest, - signDid: string - ): Promise { - try { - const requestWithTaa = await this.appendTaa(pool, request) - const signedRequestWithTaa = await this.signRequest(agentContext, signDid, requestWithTaa) - - const response = await pool.submitWriteRequest(signedRequestWithTaa) - - return response - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async submitReadRequest(pool: IndyPool, request: LedgerRequest): Promise { - try { - const response = await pool.submitReadRequest(request) - - return response - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async signRequest(agentContext: AgentContext, did: string, request: LedgerRequest): Promise { - assertIndyWallet(agentContext.wallet) - - try { - return this.indy.signRequest(agentContext.wallet.handle, did, request) - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async appendTaa(pool: IndyPool, request: Indy.LedgerRequest) { - try { - const authorAgreement = await this.getTransactionAuthorAgreement(pool) - const taa = pool.config.transactionAuthorAgreement - - // If ledger does not have TAA, we can just send request - if (authorAgreement == null) { - return request - } - // Ledger has taa but user has not specified which one to use - if (!taa) { - throw new LedgerError( - `Please, specify a transaction author agreement with version and acceptance mechanism. ${JSON.stringify( - authorAgreement - )}` - ) - } - - // Throw an error if the pool doesn't have the specified version and acceptance mechanism - if ( - authorAgreement.version !== taa.version || - !(taa.acceptanceMechanism in authorAgreement.acceptanceMechanisms.aml) - ) { - // Throw an error with a helpful message - const errMessage = `Unable to satisfy matching TAA with mechanism ${JSON.stringify( - taa.acceptanceMechanism - )} and version ${JSON.stringify(taa.version)} in pool.\n Found ${JSON.stringify( - Object.keys(authorAgreement.acceptanceMechanisms.aml) - )} and version ${authorAgreement.version} in pool.` - throw new LedgerError(errMessage) - } - - const requestWithTaa = await this.indy.appendTxnAuthorAgreementAcceptanceToRequest( - request, - authorAgreement.text, - taa.version, - authorAgreement.digest, - taa.acceptanceMechanism, - // Current time since epoch - // We can't use ratification_ts, as it must be greater than 1499906902 - Math.floor(new Date().getTime() / 1000) - ) - - return requestWithTaa - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async getTransactionAuthorAgreement(pool: IndyPool): Promise { - try { - // TODO Replace this condition with memoization - if (pool.authorAgreement !== undefined) { - return pool.authorAgreement - } - - const taaRequest = await this.indy.buildGetTxnAuthorAgreementRequest(null) - const taaResponse = await this.submitReadRequest(pool, taaRequest) - const acceptanceMechanismRequest = await this.indy.buildGetAcceptanceMechanismsRequest(null) - const acceptanceMechanismResponse = await this.submitReadRequest(pool, acceptanceMechanismRequest) - - // TAA can be null - if (taaResponse.result.data == null) { - pool.authorAgreement = null - return null - } - - // If TAA is not null, we can be sure AcceptanceMechanisms is also not null - const authorAgreement = taaResponse.result.data as AuthorAgreement - const acceptanceMechanisms = acceptanceMechanismResponse.result.data as AcceptanceMechanisms - pool.authorAgreement = { - ...authorAgreement, - acceptanceMechanisms, - } - return pool.authorAgreement - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } } export interface SchemaTemplate { diff --git a/packages/core/src/modules/ledger/services/IndyPoolService.ts b/packages/core/src/modules/ledger/services/IndyPoolService.ts index f45c338f2b..dd5095b08d 100644 --- a/packages/core/src/modules/ledger/services/IndyPoolService.ts +++ b/packages/core/src/modules/ledger/services/IndyPoolService.ts @@ -1,6 +1,6 @@ import type { AgentContext } from '../../../agent' -import type { IndyPoolConfig } from '../IndyPool' -import type * as Indy from 'indy-sdk' +import type { AcceptanceMechanisms, AuthorAgreement, IndyPoolConfig } from '../IndyPool' +import type { default as Indy, LedgerReadReplyResponse, LedgerRequest, LedgerWriteReplyResponse } from 'indy-sdk' import { Subject } from 'rxjs' @@ -14,6 +14,7 @@ import { FileSystem } from '../../../storage/FileSystem' import { isSelfCertifiedDid } from '../../../utils/did' import { isIndyError } from '../../../utils/indyError' import { allSettled, onlyFulfilled, onlyRejected } from '../../../utils/promises' +import { assertIndyWallet } from '../../../wallet/util/assertIndyWallet' import { IndyPool } from '../IndyPool' import { LedgerError } from '../error/LedgerError' import { LedgerNotConfiguredError } from '../error/LedgerNotConfiguredError' @@ -73,6 +74,7 @@ export class IndyPoolService { } /** + * @deprecated use instead getPoolForNamespace * Get the pool used for writing to the ledger. For now we always use the first pool * as the pool that writes to the ledger */ @@ -171,6 +173,148 @@ export class IndyPoolService { } } + /** + * Get the most appropriate pool for the given indyNamespace + */ + public getPoolForNamespace(indyNamespace?: string) { + if (this.pools.length === 0) { + throw new LedgerNotConfiguredError( + "No indy ledgers configured. Provide at least one pool configuration in the 'indyLedgers' agent configuration" + ) + } + + if (!indyNamespace) { + this.logger.warn('Not passing the indyNamespace is deprecated and will be removed in the future version.') + return this.pools[0] + } + + const pool = this.pools.find((pool) => pool.didIndyNamespace === indyNamespace) + + if (!pool) { + throw new LedgerNotFoundError(`No ledgers found for IndyNamespace '${indyNamespace}'.`) + } + + return pool + } + + public async submitWriteRequest( + agentContext: AgentContext, + pool: IndyPool, + request: LedgerRequest, + signDid: string + ): Promise { + try { + const requestWithTaa = await this.appendTaa(pool, request) + const signedRequestWithTaa = await this.signRequest(agentContext, signDid, requestWithTaa) + + const response = await pool.submitWriteRequest(signedRequestWithTaa) + + return response + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async submitReadRequest(pool: IndyPool, request: LedgerRequest): Promise { + try { + const response = await pool.submitReadRequest(request) + + return response + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + private async signRequest(agentContext: AgentContext, did: string, request: LedgerRequest): Promise { + assertIndyWallet(agentContext.wallet) + + try { + return this.indy.signRequest(agentContext.wallet.handle, did, request) + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + private async appendTaa(pool: IndyPool, request: Indy.LedgerRequest) { + try { + const authorAgreement = await this.getTransactionAuthorAgreement(pool) + const taa = pool.config.transactionAuthorAgreement + + // If ledger does not have TAA, we can just send request + if (authorAgreement == null) { + return request + } + // Ledger has taa but user has not specified which one to use + if (!taa) { + throw new LedgerError( + `Please, specify a transaction author agreement with version and acceptance mechanism. ${JSON.stringify( + authorAgreement + )}` + ) + } + + // Throw an error if the pool doesn't have the specified version and acceptance mechanism + if ( + authorAgreement.version !== taa.version || + !(taa.acceptanceMechanism in authorAgreement.acceptanceMechanisms.aml) + ) { + // Throw an error with a helpful message + const errMessage = `Unable to satisfy matching TAA with mechanism ${JSON.stringify( + taa.acceptanceMechanism + )} and version ${JSON.stringify(taa.version)} in pool.\n Found ${JSON.stringify( + Object.keys(authorAgreement.acceptanceMechanisms.aml) + )} and version ${authorAgreement.version} in pool.` + throw new LedgerError(errMessage) + } + + const requestWithTaa = await this.indy.appendTxnAuthorAgreementAcceptanceToRequest( + request, + authorAgreement.text, + taa.version, + authorAgreement.digest, + taa.acceptanceMechanism, + // Current time since epoch + // We can't use ratification_ts, as it must be greater than 1499906902 + Math.floor(new Date().getTime() / 1000) + ) + + return requestWithTaa + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + private async getTransactionAuthorAgreement(pool: IndyPool): Promise { + try { + // TODO Replace this condition with memoization + if (pool.authorAgreement !== undefined) { + return pool.authorAgreement + } + + const taaRequest = await this.indy.buildGetTxnAuthorAgreementRequest(null) + const taaResponse = await this.submitReadRequest(pool, taaRequest) + const acceptanceMechanismRequest = await this.indy.buildGetAcceptanceMechanismsRequest(null) + const acceptanceMechanismResponse = await this.submitReadRequest(pool, acceptanceMechanismRequest) + + // TAA can be null + if (taaResponse.result.data == null) { + pool.authorAgreement = null + return null + } + + // If TAA is not null, we can be sure AcceptanceMechanisms is also not null + const authorAgreement = taaResponse.result.data as AuthorAgreement + const acceptanceMechanisms = acceptanceMechanismResponse.result.data as AcceptanceMechanisms + pool.authorAgreement = { + ...authorAgreement, + acceptanceMechanisms, + } + return pool.authorAgreement + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + private async getDidFromPool(did: string, pool: IndyPool): Promise { try { this.logger.trace(`Get public did '${did}' from ledger '${pool.id}'`) From 1667aa24c017795e04bdc565392f3ddfe6f3b846 Mon Sep 17 00:00:00 2001 From: Jim Ezesinachi Date: Wed, 16 Nov 2022 11:16:36 +0100 Subject: [PATCH 076/125] chore: deleted PresentationExchangeRecord.ts (#1098) Signed-off-by: Jim Ezesinachi --- .../modules/proofs/protocol/v2/V2ProofService.ts | 15 ++++++++++----- .../repository/PresentationExchangeRecord.ts | 4 ---- .../core/src/modules/proofs/repository/index.ts | 1 - 3 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 packages/core/src/modules/proofs/repository/PresentationExchangeRecord.ts diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts index 3a457ef538..38842b31a4 100644 --- a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts @@ -6,6 +6,7 @@ import type { Attachment } from '../../../../decorators/attachment/Attachment' import type { MediationRecipientService } from '../../../routing/services/MediationRecipientService' import type { RoutingService } from '../../../routing/services/RoutingService' import type { ProofResponseCoordinator } from '../../ProofResponseCoordinator' +import type { ProofFormatServiceMap } from '../../formats' import type { ProofFormat } from '../../formats/ProofFormat' import type { ProofFormatService } from '../../formats/ProofFormatService' import type { CreateProblemReportOptions } from '../../formats/models/ProofFormatServiceOptions' @@ -42,7 +43,7 @@ import { PresentationProblemReportReason } from '../../errors/PresentationProble import { V2_INDY_PRESENTATION_REQUEST } from '../../formats/ProofFormatConstants' import { IndyProofFormatService } from '../../formats/indy/IndyProofFormatService' import { ProofState } from '../../models/ProofState' -import { PresentationRecordType, ProofExchangeRecord, ProofRepository } from '../../repository' +import { ProofExchangeRecord, ProofRepository } from '../../repository' import { V2PresentationProblemReportError } from './errors' import { V2PresentationAckHandler } from './handlers/V2PresentationAckHandler' @@ -71,10 +72,14 @@ export class V2ProofService extends P ) { super(agentConfig, proofRepository, connectionService, didCommMessageRepository, wallet, eventEmitter) this.wallet = wallet - this.formatServiceMap = { - [PresentationRecordType.Indy]: indyProofFormatService, - // other format services to be added to the map - } + // Dynamically build format service map. This will be extracted once services are registered dynamically + this.formatServiceMap = [indyProofFormatService].reduce( + (formatServiceMap, formatService) => ({ + ...formatServiceMap, + [formatService.formatKey]: formatService, + }), + {} + ) as ProofFormatServiceMap } /** diff --git a/packages/core/src/modules/proofs/repository/PresentationExchangeRecord.ts b/packages/core/src/modules/proofs/repository/PresentationExchangeRecord.ts deleted file mode 100644 index f9fa20f6b9..0000000000 --- a/packages/core/src/modules/proofs/repository/PresentationExchangeRecord.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum PresentationRecordType { - Indy = 'indy', - PresentationExchange = 'presentationExchange', -} diff --git a/packages/core/src/modules/proofs/repository/index.ts b/packages/core/src/modules/proofs/repository/index.ts index f861fe0806..9937a507b5 100644 --- a/packages/core/src/modules/proofs/repository/index.ts +++ b/packages/core/src/modules/proofs/repository/index.ts @@ -1,3 +1,2 @@ export * from './ProofExchangeRecord' export * from './ProofRepository' -export * from './PresentationExchangeRecord' From d82ad01f458ddea70ccdbf08c4ac172cae68d022 Mon Sep 17 00:00:00 2001 From: Jim Ezesinachi Date: Thu, 17 Nov 2022 12:44:17 +0100 Subject: [PATCH 077/125] refactor: remove ACK status type of fail (#1107) Signed-off-by: Jim Ezesinachi --- packages/core/src/modules/common/messages/AckMessage.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/modules/common/messages/AckMessage.ts b/packages/core/src/modules/common/messages/AckMessage.ts index 7e833a9513..933bfa7620 100644 --- a/packages/core/src/modules/common/messages/AckMessage.ts +++ b/packages/core/src/modules/common/messages/AckMessage.ts @@ -8,7 +8,6 @@ import { IsValidMessageType, parseMessageType } from '../../../utils/messageType */ export enum AckStatus { OK = 'OK', - FAIL = 'FAIL', PENDING = 'PENDING', } From 427a80f7759e029222119cf815a866fe9899a170 Mon Sep 17 00:00:00 2001 From: Jim Ezesinachi Date: Wed, 23 Nov 2022 05:32:09 +0100 Subject: [PATCH 078/125] fix: remove sensitive information from agent config toJSON() method (#1112) Signed-off-by: Jim Ezesinachi --- packages/core/src/agent/AgentConfig.ts | 12 ++++++++++-- packages/core/src/wallet/WalletApi.ts | 9 ++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index 3b6297341b..5e20051ff8 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -191,8 +191,16 @@ export class AgentConfig { public toJSON() { return { ...this.initConfig, - logger: this.logger !== undefined, - agentDependencies: this.agentDependencies != undefined, + walletConfig: { + ...this.walletConfig, + key: this.walletConfig?.key ? '[*****]' : undefined, + storage: { + ...this.walletConfig?.storage, + credentials: this.walletConfig?.storage?.credentials ? '[*****]' : undefined, + }, + }, + logger: this.logger.logLevel, + agentDependencies: Boolean(this.agentDependencies), label: this.label, } } diff --git a/packages/core/src/wallet/WalletApi.ts b/packages/core/src/wallet/WalletApi.ts index a845123160..548df623cf 100644 --- a/packages/core/src/wallet/WalletApi.ts +++ b/packages/core/src/wallet/WalletApi.ts @@ -43,7 +43,14 @@ export class WalletApi { } public async initialize(walletConfig: WalletConfig): Promise { - this.logger.info(`Initializing wallet '${walletConfig.id}'`, walletConfig) + this.logger.info(`Initializing wallet '${walletConfig.id}'`, { + ...walletConfig, + key: walletConfig?.key ? '[*****]' : undefined, + storage: { + ...walletConfig?.storage, + credentials: walletConfig?.storage?.credentials ? '[*****]' : undefined, + }, + }) if (this.isInitialized) { throw new WalletError( From 03cdf397b61253d2eb20694049baf74843b7ed92 Mon Sep 17 00:00:00 2001 From: Niall Shaw <100220424+niall-shaw@users.noreply.github.com> Date: Wed, 23 Nov 2022 13:52:42 +0100 Subject: [PATCH 079/125] feat: specify httpinboundtransport path (#1115) Signed-off-by: Niall Shaw --- packages/node/src/transport/HttpInboundTransport.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node/src/transport/HttpInboundTransport.ts b/packages/node/src/transport/HttpInboundTransport.ts index 4c7fea9fdf..2bc1161954 100644 --- a/packages/node/src/transport/HttpInboundTransport.ts +++ b/packages/node/src/transport/HttpInboundTransport.ts @@ -8,17 +8,19 @@ import express, { text } from 'express' export class HttpInboundTransport implements InboundTransport { public readonly app: Express private port: number + private path: string private _server?: Server public get server() { return this._server } - public constructor({ app, port }: { app?: Express; port: number }) { + public constructor({ app, path, port }: { app?: Express; path?: string; port: number }) { this.port = port // Create Express App this.app = app ?? express() + this.path = path ?? '/' this.app.use( text({ @@ -36,7 +38,7 @@ export class HttpInboundTransport implements InboundTransport { port: this.port, }) - this.app.post('/', async (req, res) => { + this.app.post(this.path, async (req, res) => { const session = new HttpTransportSession(utils.uuid(), req, res) try { const message = req.body From c68145aa291332571b286d9bc9406432207e3278 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Thu, 24 Nov 2022 17:59:22 -0500 Subject: [PATCH 080/125] feat!: add AgentMessageSentEvent and associate records to outbound messages (#1099) Signed-off-by: Ariel Gentile --- packages/action-menu/src/ActionMenuApi.ts | 29 +- packages/core/src/agent/Dispatcher.ts | 45 ++- packages/core/src/agent/Events.ts | 10 + packages/core/src/agent/Handler.ts | 5 +- packages/core/src/agent/MessageReceiver.ts | 9 +- packages/core/src/agent/MessageSender.ts | 168 ++++++--- .../src/agent/__tests__/MessageSender.test.ts | 335 ++++++++++++++---- packages/core/src/agent/helpers.ts | 34 -- .../agent/models/OutboundMessageContext.ts | 76 ++++ .../agent/models/OutboundMessageSendStatus.ts | 6 + packages/core/src/agent/models/index.ts | 2 + .../core/src/error/MessageSendingError.ts | 11 +- packages/core/src/index.ts | 1 - .../basic-messages/BasicMessagesApi.ts | 11 +- .../__tests__/basic-messages.e2e.test.ts | 12 +- .../src/modules/connections/ConnectionsApi.ts | 38 +- .../handlers/ConnectionRequestHandler.ts | 8 +- .../handlers/ConnectionResponseHandler.ts | 4 +- .../handlers/DidExchangeRequestHandler.ts | 8 +- .../handlers/DidExchangeResponseHandler.ts | 26 +- .../connections/services/TrustPingService.ts | 6 +- .../src/modules/credentials/CredentialsApi.ts | 122 +++++-- .../v1/handlers/V1IssueCredentialHandler.ts | 18 +- .../v1/handlers/V1OfferCredentialHandler.ts | 18 +- .../v1/handlers/V1ProposeCredentialHandler.ts | 8 +- .../v1/handlers/V1RequestCredentialHandler.ts | 18 +- .../v2/handlers/V2IssueCredentialHandler.ts | 18 +- .../v2/handlers/V2OfferCredentialHandler.ts | 18 +- .../v2/handlers/V2ProposeCredentialHandler.ts | 8 +- .../v2/handlers/V2RequestCredentialHandler.ts | 18 +- .../discover-features/DiscoverFeaturesApi.ts | 16 +- .../v1/handlers/V1QueryMessageHandler.ts | 7 +- .../v2/handlers/V2QueriesMessageHandler.ts | 7 +- packages/core/src/modules/oob/OutOfBandApi.ts | 9 +- .../oob/handlers/HandshakeReuseHandler.ts | 7 +- packages/core/src/modules/proofs/ProofsApi.ts | 82 +++-- .../v1/handlers/V1PresentationHandler.ts | 18 +- .../handlers/V1ProposePresentationHandler.ts | 8 +- .../handlers/V1RequestPresentationHandler.ts | 18 +- .../v2/handlers/V2PresentationHandler.ts | 18 +- .../handlers/V2ProposePresentationHandler.ts | 8 +- .../handlers/V2RequestPresentationHandler.ts | 18 +- .../core/src/modules/routing/MediatorApi.ts | 10 +- .../core/src/modules/routing/RecipientApi.ts | 65 ++-- .../routing/handlers/KeylistUpdateHandler.ts | 7 +- .../handlers/MediationRequestHandler.ts | 8 +- .../pickup/v1/MessagePickupService.ts | 4 +- .../pickup/v2/V2MessagePickupService.ts | 10 +- .../v2/handlers/MessageDeliveryHandler.ts | 7 +- .../pickup/v2/handlers/StatusHandler.ts | 7 +- .../services/MediationRecipientService.ts | 12 +- .../__tests__/V2MessagePickupService.test.ts | 68 ++-- packages/core/src/types.ts | 14 - .../core/tests/multi-protocol-version.test.ts | 10 +- .../question-answer/src/QuestionAnswerApi.ts | 20 +- samples/extension-module/dummy/DummyApi.ts | 21 +- .../dummy/handlers/DummyRequestHandler.ts | 4 +- 57 files changed, 1082 insertions(+), 491 deletions(-) delete mode 100644 packages/core/src/agent/helpers.ts create mode 100644 packages/core/src/agent/models/OutboundMessageContext.ts create mode 100644 packages/core/src/agent/models/OutboundMessageSendStatus.ts diff --git a/packages/action-menu/src/ActionMenuApi.ts b/packages/action-menu/src/ActionMenuApi.ts index 6efd081e9a..c8569894c7 100644 --- a/packages/action-menu/src/ActionMenuApi.ts +++ b/packages/action-menu/src/ActionMenuApi.ts @@ -12,7 +12,7 @@ import { ConnectionService, Dispatcher, MessageSender, - createOutboundMessage, + OutboundMessageContext, injectable, } from '@aries-framework/core' @@ -59,8 +59,13 @@ export class ActionMenuApi { connection, }) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: record, + }) + + await this.messageSender.sendMessage(outboundMessageContext) return record } @@ -80,8 +85,13 @@ export class ActionMenuApi { menu: options.menu, }) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: record, + }) + + await this.messageSender.sendMessage(outboundMessageContext) return record } @@ -109,8 +119,13 @@ export class ActionMenuApi { performedAction: options.performedAction, }) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: record, + }) + + await this.messageSender.sendMessage(outboundMessageContext) return record } diff --git a/packages/core/src/agent/Dispatcher.ts b/packages/core/src/agent/Dispatcher.ts index e55a324f85..acc6bd9a05 100644 --- a/packages/core/src/agent/Dispatcher.ts +++ b/packages/core/src/agent/Dispatcher.ts @@ -1,4 +1,3 @@ -import type { OutboundMessage, OutboundServiceMessage } from '../types' import type { AgentMessage } from './AgentMessage' import type { AgentMessageProcessedEvent } from './Events' import type { Handler } from './Handler' @@ -14,7 +13,7 @@ import { ProblemReportMessage } from './../modules/problem-reports/messages/Prob import { EventEmitter } from './EventEmitter' import { AgentEventTypes } from './Events' import { MessageSender } from './MessageSender' -import { isOutboundServiceMessage } from './helpers' +import { OutboundMessageContext } from './models' @injectable() class Dispatcher { @@ -38,14 +37,14 @@ class Dispatcher { } public async dispatch(messageContext: InboundMessageContext): Promise { - const message = messageContext.message + const { agentContext, connection, senderKey, recipientKey, message } = messageContext const handler = this.getHandlerForType(message.type) if (!handler) { throw new AriesFrameworkError(`No handler for message type "${message.type}" found`) } - let outboundMessage: OutboundMessage | OutboundServiceMessage | void + let outboundMessage: OutboundMessageContext | void try { outboundMessage = await handler.handle(messageContext) @@ -54,43 +53,39 @@ class Dispatcher { if (problemReportMessage instanceof ProblemReportMessage && messageContext.connection) { problemReportMessage.setThread({ - threadId: messageContext.message.threadId, + threadId: message.threadId, }) - outboundMessage = { - payload: problemReportMessage, + outboundMessage = new OutboundMessageContext(problemReportMessage, { + agentContext, connection: messageContext.connection, - } + }) } else { this.logger.error(`Error handling message with type ${message.type}`, { message: message.toJSON(), error, - senderKey: messageContext.senderKey?.fingerprint, - recipientKey: messageContext.recipientKey?.fingerprint, - connectionId: messageContext.connection?.id, + senderKey: senderKey?.fingerprint, + recipientKey: recipientKey?.fingerprint, + connectionId: connection?.id, }) throw error } } - if (outboundMessage && isOutboundServiceMessage(outboundMessage)) { - await this.messageSender.sendMessageToService(messageContext.agentContext, { - message: outboundMessage.payload, - service: outboundMessage.service, - senderKey: outboundMessage.senderKey, - returnRoute: true, - }) - } else if (outboundMessage) { - outboundMessage.sessionId = messageContext.sessionId - await this.messageSender.sendMessage(messageContext.agentContext, outboundMessage) + if (outboundMessage) { + if (outboundMessage.isOutboundServiceMessage()) { + await this.messageSender.sendMessageToService(outboundMessage) + } else { + outboundMessage.sessionId = messageContext.sessionId + await this.messageSender.sendMessage(outboundMessage) + } } - // Emit event that allows to hook into received messages - this.eventEmitter.emit(messageContext.agentContext, { + this.eventEmitter.emit(agentContext, { type: AgentEventTypes.AgentMessageProcessed, payload: { - message: messageContext.message, - connection: messageContext.connection, + message, + connection, }, }) } diff --git a/packages/core/src/agent/Events.ts b/packages/core/src/agent/Events.ts index 3688479795..4e7bb4b076 100644 --- a/packages/core/src/agent/Events.ts +++ b/packages/core/src/agent/Events.ts @@ -1,5 +1,6 @@ import type { ConnectionRecord } from '../modules/connections' import type { AgentMessage } from './AgentMessage' +import type { OutboundMessageContext, OutboundMessageSendStatus } from './models' import type { Observable } from 'rxjs' import { filter } from 'rxjs' @@ -13,6 +14,7 @@ export function filterContextCorrelationId(contextCorrelationId: string) { export enum AgentEventTypes { AgentMessageReceived = 'AgentMessageReceived', AgentMessageProcessed = 'AgentMessageProcessed', + AgentMessageSent = 'AgentMessageSent', } export interface EventMetadata { @@ -41,3 +43,11 @@ export interface AgentMessageProcessedEvent extends BaseEvent { connection?: ConnectionRecord } } + +export interface AgentMessageSentEvent extends BaseEvent { + type: typeof AgentEventTypes.AgentMessageSent + payload: { + message: OutboundMessageContext + status: OutboundMessageSendStatus + } +} diff --git a/packages/core/src/agent/Handler.ts b/packages/core/src/agent/Handler.ts index 7a43bbbbbe..e736aad3da 100644 --- a/packages/core/src/agent/Handler.ts +++ b/packages/core/src/agent/Handler.ts @@ -1,11 +1,10 @@ -import type { OutboundMessage, OutboundServiceMessage } from '../types' import type { ConstructableAgentMessage } from './AgentMessage' -import type { InboundMessageContext } from './models/InboundMessageContext' +import type { InboundMessageContext, OutboundMessageContext } from './models' export interface Handler { readonly supportedMessages: readonly ConstructableAgentMessage[] - handle(messageContext: InboundMessageContext): Promise + handle(messageContext: InboundMessageContext): Promise } /** diff --git a/packages/core/src/agent/MessageReceiver.ts b/packages/core/src/agent/MessageReceiver.ts index 88b052cc42..2c94f6596f 100644 --- a/packages/core/src/agent/MessageReceiver.ts +++ b/packages/core/src/agent/MessageReceiver.ts @@ -21,8 +21,7 @@ import { EnvelopeService } from './EnvelopeService' import { MessageSender } from './MessageSender' import { TransportService } from './TransportService' import { AgentContextProvider } from './context' -import { createOutboundMessage } from './helpers' -import { InboundMessageContext } from './models/InboundMessageContext' +import { InboundMessageContext, OutboundMessageContext } from './models' @injectable() export class MessageReceiver { @@ -277,9 +276,9 @@ export class MessageReceiver { problemReportMessage.setThread({ threadId: plaintextMessage['@id'], }) - const outboundMessage = createOutboundMessage(connection, problemReportMessage) - if (outboundMessage) { - await this.messageSender.sendMessage(agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(problemReportMessage, { agentContext, connection }) + if (outboundMessageContext) { + await this.messageSender.sendMessage(outboundMessageContext) } } } diff --git a/packages/core/src/agent/MessageSender.ts b/packages/core/src/agent/MessageSender.ts index bbbcb1be8f..ac9ac731a2 100644 --- a/packages/core/src/agent/MessageSender.ts +++ b/packages/core/src/agent/MessageSender.ts @@ -1,12 +1,12 @@ -import type { Key } from '../crypto' import type { ConnectionRecord } from '../modules/connections' import type { ResolvedDidCommService } from '../modules/didcomm' import type { DidDocument } from '../modules/dids' import type { OutOfBandRecord } from '../modules/oob/repository' import type { OutboundTransport } from '../transport/OutboundTransport' -import type { OutboundMessage, OutboundPackage, EncryptedMessage } from '../types' +import type { OutboundPackage, EncryptedMessage } from '../types' import type { AgentMessage } from './AgentMessage' import type { EnvelopeKeys } from './EnvelopeService' +import type { AgentMessageSentEvent } from './Events' import type { TransportSession } from './TransportService' import type { AgentContext } from './context' @@ -18,14 +18,16 @@ import { DidCommDocumentService } from '../modules/didcomm' import { getKeyDidMappingByVerificationMethod } from '../modules/dids/domain/key-type' import { didKeyToInstanceOfKey } from '../modules/dids/helpers' import { DidResolverService } from '../modules/dids/services/DidResolverService' -import { OutOfBandRepository } from '../modules/oob/repository' import { inject, injectable } from '../plugins' import { MessageRepository } from '../storage/MessageRepository' import { MessageValidator } from '../utils/MessageValidator' import { getProtocolScheme } from '../utils/uri' import { EnvelopeService } from './EnvelopeService' +import { EventEmitter } from './EventEmitter' +import { AgentEventTypes } from './Events' import { TransportService } from './TransportService' +import { OutboundMessageContext, OutboundMessageSendStatus } from './models' export interface TransportPriorityOptions { schemes: string[] @@ -40,7 +42,7 @@ export class MessageSender { private logger: Logger private didResolverService: DidResolverService private didCommDocumentService: DidCommDocumentService - private outOfBandRepository: OutOfBandRepository + private eventEmitter: EventEmitter public readonly outboundTransports: OutboundTransport[] = [] public constructor( @@ -50,7 +52,7 @@ export class MessageSender { @inject(InjectionSymbols.Logger) logger: Logger, didResolverService: DidResolverService, didCommDocumentService: DidCommDocumentService, - outOfBandRepository: OutOfBandRepository + eventEmitter: EventEmitter ) { this.envelopeService = envelopeService this.transportService = transportService @@ -58,7 +60,7 @@ export class MessageSender { this.logger = logger this.didResolverService = didResolverService this.didCommDocumentService = didCommDocumentService - this.outOfBandRepository = outOfBandRepository + this.eventEmitter = eventEmitter this.outboundTransports = [] } @@ -178,17 +180,24 @@ export class MessageSender { } public async sendMessage( - agentContext: AgentContext, - outboundMessage: OutboundMessage, + outboundMessageContext: OutboundMessageContext, options?: { transportPriority?: TransportPriorityOptions } ) { - const { connection, outOfBand, sessionId, payload } = outboundMessage + const { agentContext, connection, outOfBand, sessionId, message } = outboundMessageContext const errors: Error[] = [] + if (!connection) { + this.logger.error('Outbound message has no associated connection') + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) + throw new MessageSendingError('Outbound message has no associated connection', { + outboundMessageContext, + }) + } + this.logger.debug('Send outbound message', { - message: payload, + message, connectionId: connection.id, }) @@ -202,10 +211,11 @@ export class MessageSender { session = this.transportService.findSessionByConnectionId(connection.id) } - if (session?.inboundMessage?.hasReturnRouting(payload.threadId)) { - this.logger.debug(`Found session with return routing for message '${payload.id}' (connection '${connection.id}'`) + if (session?.inboundMessage?.hasReturnRouting(message.threadId)) { + this.logger.debug(`Found session with return routing for message '${message.id}' (connection '${connection.id}'`) try { - await this.sendMessageToSession(agentContext, session, payload) + await this.sendMessageToSession(agentContext, session, message) + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.SentToSession) return } catch (error) { errors.push(error) @@ -214,22 +224,46 @@ export class MessageSender { } // Retrieve DIDComm services - const { services, queueService } = await this.retrieveServicesByConnection( - agentContext, - connection, - options?.transportPriority, - outOfBand - ) + let services: ResolvedDidCommService[] = [] + let queueService: ResolvedDidCommService | undefined + + try { + ;({ services, queueService } = await this.retrieveServicesByConnection( + agentContext, + connection, + options?.transportPriority, + outOfBand + )) + } catch (error) { + this.logger.error(`Unable to retrieve services for connection '${connection.id}`) + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) + throw new MessageSendingError(`Unable to retrieve services for connection '${connection.id}`, { + outboundMessageContext, + cause: error, + }) + } if (!connection.did) { this.logger.error(`Unable to send message using connection '${connection.id}' that doesn't have a did`) + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) throw new MessageSendingError( `Unable to send message using connection '${connection.id}' that doesn't have a did`, - { outboundMessage } + { outboundMessageContext } ) } - const ourDidDocument = await this.didResolverService.resolveDidDocument(agentContext, connection.did) + let ourDidDocument: DidDocument + try { + ourDidDocument = await this.didResolverService.resolveDidDocument(agentContext, connection.did) + } catch (error) { + this.logger.error(`Unable to resolve DID Document for '${connection.did}`) + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) + throw new MessageSendingError(`Unable to resolve DID Document for '${connection.did}`, { + outboundMessageContext, + cause: error, + }) + } + const ourAuthenticationKeys = getAuthenticationKeys(ourDidDocument) // TODO We're selecting just the first authentication key. Is it ok? @@ -244,19 +278,24 @@ export class MessageSender { const [firstOurAuthenticationKey] = ourAuthenticationKeys // If the returnRoute is already set we won't override it. This allows to set the returnRoute manually if this is desired. const shouldAddReturnRoute = - payload.transport?.returnRoute === undefined && !this.transportService.hasInboundEndpoint(ourDidDocument) + message.transport?.returnRoute === undefined && !this.transportService.hasInboundEndpoint(ourDidDocument) // Loop trough all available services and try to send the message for await (const service of services) { try { // Enable return routing if the our did document does not have any inbound endpoint for given sender key - await this.sendMessageToService(agentContext, { - message: payload, - service, - senderKey: firstOurAuthenticationKey, - returnRoute: shouldAddReturnRoute, - connectionId: connection.id, - }) + await this.sendToService( + new OutboundMessageContext(message, { + agentContext, + serviceParams: { + service, + senderKey: firstOurAuthenticationKey, + returnRoute: shouldAddReturnRoute, + }, + connection, + }) + ) + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.SentToTransport) return } catch (error) { errors.push(error) @@ -281,39 +320,57 @@ export class MessageSender { senderKey: firstOurAuthenticationKey, } - const encryptedMessage = await this.envelopeService.packMessage(agentContext, payload, keys) + const encryptedMessage = await this.envelopeService.packMessage(agentContext, message, keys) await this.messageRepository.add(connection.id, encryptedMessage) + + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.QueuedForPickup) + return } // Message is undeliverable this.logger.error(`Message is undeliverable to connection ${connection.id} (${connection.theirLabel})`, { - message: payload, + message, errors, connection, }) + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) + throw new MessageSendingError( `Message is undeliverable to connection ${connection.id} (${connection.theirLabel})`, - { outboundMessage } + { outboundMessageContext } ) } - public async sendMessageToService( - agentContext: AgentContext, - { - message, - service, - senderKey, - returnRoute, - connectionId, - }: { - message: AgentMessage - service: ResolvedDidCommService - senderKey: Key - returnRoute?: boolean - connectionId?: string + public async sendMessageToService(outboundMessageContext: OutboundMessageContext) { + try { + await this.sendToService(outboundMessageContext) + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.SentToTransport) + } catch (error) { + this.logger.error( + `Message is undeliverable to service with id ${outboundMessageContext.serviceParams?.service.id}: ${error.message}`, + { + message: outboundMessageContext.message, + error, + } + ) + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) + + throw new MessageSendingError( + `Message is undeliverable to service with id ${outboundMessageContext.serviceParams?.service.id}: ${error.message}`, + { outboundMessageContext } + ) } - ) { + } + + private async sendToService(outboundMessageContext: OutboundMessageContext) { + const { agentContext, message, serviceParams, connection } = outboundMessageContext + + if (!serviceParams) { + throw new AriesFrameworkError('No service parameters found in outbound message context') + } + const { service, senderKey, returnRoute } = serviceParams + if (this.outboundTransports.length === 0) { throw new AriesFrameworkError('Agent has no outbound transport!') } @@ -350,7 +407,7 @@ export class MessageSender { const outboundPackage = await this.packMessage(agentContext, { message, keys, endpoint: service.serviceEndpoint }) outboundPackage.endpoint = service.serviceEndpoint - outboundPackage.connectionId = connectionId + outboundPackage.connectionId = connection?.id for (const transport of this.outboundTransports) { const protocolScheme = getProtocolScheme(service.serviceEndpoint) if (!protocolScheme) { @@ -360,7 +417,9 @@ export class MessageSender { return } } - throw new AriesFrameworkError(`Unable to send message to service: ${service.serviceEndpoint}`) + throw new MessageSendingError(`Unable to send message to service: ${service.serviceEndpoint}`, { + outboundMessageContext, + }) } private async retrieveServicesByConnection( @@ -427,6 +486,17 @@ export class MessageSender { ) return { services, queueService } } + + private emitMessageSentEvent(outboundMessageContext: OutboundMessageContext, status: OutboundMessageSendStatus) { + const { agentContext } = outboundMessageContext + this.eventEmitter.emit(agentContext, { + type: AgentEventTypes.AgentMessageSent, + payload: { + message: outboundMessageContext, + status, + }, + }) + } } export function isDidCommTransportQueue(serviceEndpoint: string): serviceEndpoint is typeof DID_COMM_TRANSPORT_QUEUE { diff --git a/packages/core/src/agent/__tests__/MessageSender.test.ts b/packages/core/src/agent/__tests__/MessageSender.test.ts index 7776df1ea8..35d566c3b0 100644 --- a/packages/core/src/agent/__tests__/MessageSender.test.ts +++ b/packages/core/src/agent/__tests__/MessageSender.test.ts @@ -1,12 +1,22 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import type { ConnectionRecord } from '../../modules/connections' import type { ResolvedDidCommService } from '../../modules/didcomm' import type { DidDocumentService } from '../../modules/dids' import type { MessageRepository } from '../../storage/MessageRepository' import type { OutboundTransport } from '../../transport' -import type { OutboundMessage, EncryptedMessage } from '../../types' +import type { EncryptedMessage } from '../../types' +import type { AgentMessageSentEvent } from '../Events' + +import { Subject } from 'rxjs' import { TestMessage } from '../../../tests/TestMessage' -import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../tests/helpers' +import { + agentDependencies, + getAgentConfig, + getAgentContext, + getMockConnection, + mockFunction, +} from '../../../tests/helpers' import testLogger from '../../../tests/logger' import { Key, KeyType } from '../../crypto' import { ReturnRouteTypes } from '../../decorators/transport/TransportDecorator' @@ -14,12 +24,13 @@ import { DidCommDocumentService } from '../../modules/didcomm' import { DidResolverService, DidDocument, VerificationMethod } from '../../modules/dids' import { DidCommV1Service } from '../../modules/dids/domain/service/DidCommV1Service' import { verkeyToInstanceOfKey } from '../../modules/dids/helpers' -import { OutOfBandRepository } from '../../modules/oob' import { InMemoryMessageRepository } from '../../storage/InMemoryMessageRepository' import { EnvelopeService as EnvelopeServiceImpl } from '../EnvelopeService' +import { EventEmitter } from '../EventEmitter' +import { AgentEventTypes } from '../Events' import { MessageSender } from '../MessageSender' import { TransportService } from '../TransportService' -import { createOutboundMessage } from '../helpers' +import { OutboundMessageContext, OutboundMessageSendStatus } from '../models' import { DummyTransportSession } from './stubs' @@ -27,14 +38,12 @@ jest.mock('../TransportService') jest.mock('../EnvelopeService') jest.mock('../../modules/dids/services/DidResolverService') jest.mock('../../modules/didcomm/services/DidCommDocumentService') -jest.mock('../../modules/oob/repository/OutOfBandRepository') const logger = testLogger const TransportServiceMock = TransportService as jest.MockedClass const DidResolverServiceMock = DidResolverService as jest.Mock const DidCommDocumentServiceMock = DidCommDocumentService as jest.Mock -const OutOfBandRepositoryMock = OutOfBandRepository as jest.Mock class DummyHttpOutboundTransport implements OutboundTransport { public start(): Promise { @@ -83,7 +92,7 @@ describe('MessageSender', () => { const didResolverService = new DidResolverServiceMock() const didCommDocumentService = new DidCommDocumentServiceMock() - const outOfBandRepository = new OutOfBandRepositoryMock() + const eventEmitter = new EventEmitter(agentDependencies, new Subject()) const didResolverServiceResolveMock = mockFunction(didResolverService.resolveDidDocument) const didResolverServiceResolveDidServicesMock = mockFunction(didCommDocumentService.resolveServicesFromDid) @@ -125,15 +134,18 @@ describe('MessageSender', () => { let outboundTransport: OutboundTransport let messageRepository: MessageRepository let connection: ConnectionRecord - let outboundMessage: OutboundMessage + let outboundMessageContext: OutboundMessageContext const agentConfig = getAgentConfig('MessageSender') const agentContext = getAgentContext() + const eventListenerMock = jest.fn() describe('sendMessage', () => { beforeEach(() => { TransportServiceMock.mockClear() DidResolverServiceMock.mockClear() + eventEmitter.on(AgentEventTypes.AgentMessageSent, eventListenerMock) + outboundTransport = new DummyHttpOutboundTransport() messageRepository = new InMemoryMessageRepository(agentConfig.logger) messageSender = new MessageSender( @@ -143,7 +155,7 @@ describe('MessageSender', () => { logger, didResolverService, didCommDocumentService, - outOfBandRepository + eventEmitter ) connection = getMockConnection({ id: 'test-123', @@ -151,7 +163,7 @@ describe('MessageSender', () => { theirDid: 'did:peer:1theirdid', theirLabel: 'Test 123', }) - outboundMessage = createOutboundMessage(connection, new TestMessage()) + outboundMessageContext = new OutboundMessageContext(new TestMessage(), { agentContext, connection }) envelopeServicePackMessageMock.mockReturnValue(Promise.resolve(encryptedMessage)) transportServiceHasInboundEndpoint.mockReturnValue(true) @@ -167,13 +179,25 @@ describe('MessageSender', () => { }) afterEach(() => { + eventEmitter.off(AgentEventTypes.AgentMessageSent, eventListenerMock) + jest.resetAllMocks() }) test('throw error when there is no outbound transport', async () => { - await expect(messageSender.sendMessage(agentContext, outboundMessage)).rejects.toThrow( + await expect(messageSender.sendMessage(outboundMessageContext)).rejects.toThrow( /Message is undeliverable to connection/ ) + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: outboundMessageContext, + status: OutboundMessageSendStatus.Undeliverable, + }, + }) }) test('throw error when there is no service or queue', async () => { @@ -182,9 +206,19 @@ describe('MessageSender', () => { didResolverServiceResolveMock.mockResolvedValue(getMockDidDocument({ service: [] })) didResolverServiceResolveDidServicesMock.mockResolvedValue([]) - await expect(messageSender.sendMessage(agentContext, outboundMessage)).rejects.toThrow( + await expect(messageSender.sendMessage(outboundMessageContext)).rejects.toThrow( `Message is undeliverable to connection test-123 (Test 123)` ) + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: outboundMessageContext, + status: OutboundMessageSendStatus.Undeliverable, + }, + }) }) test('call send message when session send method fails', async () => { @@ -195,7 +229,18 @@ describe('MessageSender', () => { messageSender.registerOutboundTransport(outboundTransport) const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') - await messageSender.sendMessage(agentContext, outboundMessage) + await messageSender.sendMessage(outboundMessageContext) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: outboundMessageContext, + status: OutboundMessageSendStatus.SentToTransport, + }, + }) expect(sendMessageSpy).toHaveBeenCalledWith({ connectionId: 'test-123', @@ -211,7 +256,18 @@ describe('MessageSender', () => { const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') - await messageSender.sendMessage(agentContext, outboundMessage) + await messageSender.sendMessage(outboundMessageContext) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: outboundMessageContext, + status: OutboundMessageSendStatus.SentToTransport, + }, + }) expect(didResolverServiceResolveDidServicesMock).toHaveBeenCalledWith(agentContext, connection.theirDid) expect(sendMessageSpy).toHaveBeenCalledWith({ @@ -230,9 +286,20 @@ describe('MessageSender', () => { new Error(`Unable to resolve did document for did '${connection.theirDid}': notFound`) ) - await expect(messageSender.sendMessage(agentContext, outboundMessage)).rejects.toThrowError( - `Unable to resolve did document for did '${connection.theirDid}': notFound` + await expect(messageSender.sendMessage(outboundMessageContext)).rejects.toThrowError( + `Unable to resolve DID Document for '${connection.did}` ) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: outboundMessageContext, + status: OutboundMessageSendStatus.Undeliverable, + }, + }) }) test('call send message when session send method fails with missing keys', async () => { @@ -242,7 +309,18 @@ describe('MessageSender', () => { messageSender.registerOutboundTransport(outboundTransport) const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') - await messageSender.sendMessage(agentContext, outboundMessage) + await messageSender.sendMessage(outboundMessageContext) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: outboundMessageContext, + status: OutboundMessageSendStatus.SentToTransport, + }, + }) expect(sendMessageSpy).toHaveBeenCalledWith({ connectionId: 'test-123', @@ -259,7 +337,24 @@ describe('MessageSender', () => { const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') const sendMessageToServiceSpy = jest.spyOn(messageSender, 'sendMessageToService') - await messageSender.sendMessage(agentContext, { ...outboundMessage, sessionId: 'session-123' }) + const contextWithSessionId = new OutboundMessageContext(outboundMessageContext.message, { + agentContext: outboundMessageContext.agentContext, + connection: outboundMessageContext.connection, + sessionId: 'session-123', + }) + + await messageSender.sendMessage(contextWithSessionId) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: contextWithSessionId, + status: OutboundMessageSendStatus.SentToSession, + }, + }) expect(session.send).toHaveBeenCalledTimes(1) expect(session.send).toHaveBeenNthCalledWith(1, encryptedMessage) @@ -271,64 +366,119 @@ describe('MessageSender', () => { test('call send message on session when there is a session for a given connection', async () => { messageSender.registerOutboundTransport(outboundTransport) const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') - const sendMessageToServiceSpy = jest.spyOn(messageSender, 'sendMessageToService') + //@ts-ignore + const sendToServiceSpy = jest.spyOn(messageSender, 'sendToService') - await messageSender.sendMessage(agentContext, outboundMessage) + await messageSender.sendMessage(outboundMessageContext) - const [[, sendMessage]] = sendMessageToServiceSpy.mock.calls + //@ts-ignore + const [[sendMessage]] = sendToServiceSpy.mock.calls + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: outboundMessageContext, + status: OutboundMessageSendStatus.SentToTransport, + }, + }) expect(sendMessage).toMatchObject({ - connectionId: 'test-123', - message: outboundMessage.payload, - returnRoute: false, - service: { - serviceEndpoint: firstDidCommService.serviceEndpoint, + connection: { + id: 'test-123', + }, + message: outboundMessageContext.message, + serviceParams: { + returnRoute: false, + service: { + serviceEndpoint: firstDidCommService.serviceEndpoint, + }, }, }) - expect(sendMessage.senderKey.publicKeyBase58).toEqual('EoGusetSxDJktp493VCyh981nUnzMamTRjvBaHZAy68d') - expect(sendMessage.service.recipientKeys.map((key) => key.publicKeyBase58)).toEqual([ + //@ts-ignore + expect(sendMessage.serviceParams.senderKey.publicKeyBase58).toEqual( + 'EoGusetSxDJktp493VCyh981nUnzMamTRjvBaHZAy68d' + ) + + //@ts-ignore + expect(sendMessage.serviceParams.service.recipientKeys.map((key) => key.publicKeyBase58)).toEqual([ 'EoGusetSxDJktp493VCyh981nUnzMamTRjvBaHZAy68d', ]) - expect(sendMessageToServiceSpy).toHaveBeenCalledTimes(1) + expect(sendToServiceSpy).toHaveBeenCalledTimes(1) expect(sendMessageSpy).toHaveBeenCalledTimes(1) }) - test('calls sendMessageToService with payload and endpoint from second DidComm service when the first fails', async () => { + test('calls sendToService with payload and endpoint from second DidComm service when the first fails', async () => { messageSender.registerOutboundTransport(outboundTransport) const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') - const sendMessageToServiceSpy = jest.spyOn(messageSender, 'sendMessageToService') + //@ts-ignore + const sendToServiceSpy = jest.spyOn(messageSender, 'sendToService') // Simulate the case when the first call fails sendMessageSpy.mockRejectedValueOnce(new Error()) - await messageSender.sendMessage(agentContext, outboundMessage) + await messageSender.sendMessage(outboundMessageContext) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: outboundMessageContext, + status: OutboundMessageSendStatus.SentToTransport, + }, + }) + + //@ts-ignore + const [, [sendMessage]] = sendToServiceSpy.mock.calls - const [, [, sendMessage]] = sendMessageToServiceSpy.mock.calls expect(sendMessage).toMatchObject({ - connectionId: 'test-123', - message: outboundMessage.payload, - returnRoute: false, - service: { - serviceEndpoint: secondDidCommService.serviceEndpoint, + agentContext, + connection: { + id: 'test-123', + }, + message: outboundMessageContext.message, + serviceParams: { + returnRoute: false, + service: { + serviceEndpoint: secondDidCommService.serviceEndpoint, + }, }, }) - expect(sendMessage.senderKey.publicKeyBase58).toEqual('EoGusetSxDJktp493VCyh981nUnzMamTRjvBaHZAy68d') - expect(sendMessage.service.recipientKeys.map((key) => key.publicKeyBase58)).toEqual([ + //@ts-ignore + expect(sendMessage.serviceParams.senderKey.publicKeyBase58).toEqual( + 'EoGusetSxDJktp493VCyh981nUnzMamTRjvBaHZAy68d' + ) + //@ts-ignore + expect(sendMessage.serviceParams.service.recipientKeys.map((key) => key.publicKeyBase58)).toEqual([ 'EoGusetSxDJktp493VCyh981nUnzMamTRjvBaHZAy68d', ]) - expect(sendMessageToServiceSpy).toHaveBeenCalledTimes(2) + expect(sendToServiceSpy).toHaveBeenCalledTimes(2) expect(sendMessageSpy).toHaveBeenCalledTimes(2) }) test('throw error when message endpoint is not supported by outbound transport schemes', async () => { messageSender.registerOutboundTransport(new DummyWsOutboundTransport()) - await expect(messageSender.sendMessage(agentContext, outboundMessage)).rejects.toThrow( + await expect(messageSender.sendMessage(outboundMessageContext)).rejects.toThrow( /Message is undeliverable to connection/ ) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: outboundMessageContext, + status: OutboundMessageSendStatus.Undeliverable, + }, + }) }) }) @@ -350,34 +500,66 @@ describe('MessageSender', () => { logger, didResolverService, didCommDocumentService, - outOfBandRepository + eventEmitter ) + eventEmitter.on(AgentEventTypes.AgentMessageSent, eventListenerMock) + envelopeServicePackMessageMock.mockReturnValue(Promise.resolve(encryptedMessage)) }) afterEach(() => { jest.resetAllMocks() + eventEmitter.off(AgentEventTypes.AgentMessageSent, eventListenerMock) }) test('throws error when there is no outbound transport', async () => { - await expect( - messageSender.sendMessageToService(agentContext, { - message: new TestMessage(), + outboundMessageContext = new OutboundMessageContext(new TestMessage(), { + agentContext, + serviceParams: { senderKey, service, - }) - ).rejects.toThrow(`Agent has no outbound transport!`) + }, + }) + await expect(messageSender.sendMessageToService(outboundMessageContext)).rejects.toThrow( + `Agent has no outbound transport!` + ) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: outboundMessageContext, + status: OutboundMessageSendStatus.Undeliverable, + }, + }) }) test('calls send message with payload and endpoint from DIDComm service', async () => { messageSender.registerOutboundTransport(outboundTransport) const sendMessageSpy = jest.spyOn(outboundTransport, 'sendMessage') - await messageSender.sendMessageToService(agentContext, { - message: new TestMessage(), - senderKey, - service, + outboundMessageContext = new OutboundMessageContext(new TestMessage(), { + agentContext, + serviceParams: { + senderKey, + service, + }, + }) + + await messageSender.sendMessageToService(outboundMessageContext) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: outboundMessageContext, + status: OutboundMessageSendStatus.SentToTransport, + }, }) expect(sendMessageSpy).toHaveBeenCalledWith({ @@ -395,10 +577,25 @@ describe('MessageSender', () => { const message = new TestMessage() message.setReturnRouting(ReturnRouteTypes.all) - await messageSender.sendMessageToService(agentContext, { - message, - senderKey, - service, + outboundMessageContext = new OutboundMessageContext(message, { + agentContext, + serviceParams: { + senderKey, + service, + }, + }) + + await messageSender.sendMessageToService(outboundMessageContext) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: outboundMessageContext, + status: OutboundMessageSendStatus.SentToTransport, + }, }) expect(sendMessageSpy).toHaveBeenCalledWith({ @@ -411,13 +608,27 @@ describe('MessageSender', () => { test('throw error when message endpoint is not supported by outbound transport schemes', async () => { messageSender.registerOutboundTransport(new DummyWsOutboundTransport()) - await expect( - messageSender.sendMessageToService(agentContext, { - message: new TestMessage(), + outboundMessageContext = new OutboundMessageContext(new TestMessage(), { + agentContext, + serviceParams: { senderKey, service, - }) - ).rejects.toThrow(/Unable to send message to service/) + }, + }) + + await expect(messageSender.sendMessageToService(outboundMessageContext)).rejects.toThrow( + /Unable to send message to service/ + ) + expect(eventListenerMock).toHaveBeenCalledWith({ + type: AgentEventTypes.AgentMessageSent, + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + message: outboundMessageContext, + status: OutboundMessageSendStatus.Undeliverable, + }, + }) }) }) @@ -432,7 +643,7 @@ describe('MessageSender', () => { logger, didResolverService, didCommDocumentService, - outOfBandRepository + eventEmitter ) connection = getMockConnection() diff --git a/packages/core/src/agent/helpers.ts b/packages/core/src/agent/helpers.ts deleted file mode 100644 index fcfb906220..0000000000 --- a/packages/core/src/agent/helpers.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { Key } from '../crypto' -import type { ConnectionRecord } from '../modules/connections' -import type { ResolvedDidCommService } from '../modules/didcomm' -import type { OutOfBandRecord } from '../modules/oob/repository' -import type { OutboundMessage, OutboundServiceMessage } from '../types' -import type { AgentMessage } from './AgentMessage' - -export function createOutboundMessage( - connection: ConnectionRecord, - payload: T, - outOfBand?: OutOfBandRecord -): OutboundMessage { - return { - connection, - outOfBand, - payload, - } -} - -export function createOutboundServiceMessage(options: { - payload: T - service: ResolvedDidCommService - senderKey: Key -}): OutboundServiceMessage { - return options -} - -export function isOutboundServiceMessage( - message: OutboundMessage | OutboundServiceMessage -): message is OutboundServiceMessage { - const service = (message as OutboundServiceMessage).service - - return service !== undefined -} diff --git a/packages/core/src/agent/models/OutboundMessageContext.ts b/packages/core/src/agent/models/OutboundMessageContext.ts new file mode 100644 index 0000000000..e61929a47d --- /dev/null +++ b/packages/core/src/agent/models/OutboundMessageContext.ts @@ -0,0 +1,76 @@ +import type { Key } from '../../crypto' +import type { ConnectionRecord } from '../../modules/connections' +import type { ResolvedDidCommService } from '../../modules/didcomm' +import type { OutOfBandRecord } from '../../modules/oob' +import type { BaseRecord } from '../../storage/BaseRecord' +import type { AgentMessage } from '../AgentMessage' +import type { AgentContext } from '../context' + +import { AriesFrameworkError } from '../../error' + +export interface ServiceMessageParams { + senderKey: Key + service: ResolvedDidCommService + returnRoute?: boolean +} + +export interface OutboundMessageContextParams { + agentContext: AgentContext + associatedRecord?: BaseRecord + connection?: ConnectionRecord + serviceParams?: ServiceMessageParams + outOfBand?: OutOfBandRecord + sessionId?: string +} + +export class OutboundMessageContext { + public message: T + public connection?: ConnectionRecord + public serviceParams?: ServiceMessageParams + public outOfBand?: OutOfBandRecord + public associatedRecord?: BaseRecord + public sessionId?: string + public readonly agentContext: AgentContext + + public constructor(message: T, context: OutboundMessageContextParams) { + this.message = message + this.connection = context.connection + this.sessionId = context.sessionId + this.outOfBand = context.outOfBand + this.serviceParams = context.serviceParams + this.associatedRecord = context.associatedRecord + this.agentContext = context.agentContext + } + + /** + * Assert the outbound message has a ready connection associated with it. + * + * @throws {AriesFrameworkError} if there is no connection or the connection is not ready + */ + public assertReadyConnection(): ConnectionRecord { + if (!this.connection) { + throw new AriesFrameworkError(`No connection associated with outgoing message ${this.message.type}`) + } + + // Make sure connection is ready + this.connection.assertReady() + + return this.connection + } + + public isOutboundServiceMessage(): boolean { + return this.serviceParams?.service !== undefined + } + + public toJSON() { + return { + message: this.message, + outOfBand: this.outOfBand, + associatedRecord: this.associatedRecord, + sessionId: this.sessionId, + serviceParams: this.serviceParams, + agentContext: this.agentContext.toJSON(), + connection: this.connection, + } + } +} diff --git a/packages/core/src/agent/models/OutboundMessageSendStatus.ts b/packages/core/src/agent/models/OutboundMessageSendStatus.ts new file mode 100644 index 0000000000..6fdb4f7f68 --- /dev/null +++ b/packages/core/src/agent/models/OutboundMessageSendStatus.ts @@ -0,0 +1,6 @@ +export enum OutboundMessageSendStatus { + SentToSession = 'SentToSession', + SentToTransport = 'SentToTransport', + QueuedForPickup = 'QueuedForPickup', + Undeliverable = 'Undeliverable', +} diff --git a/packages/core/src/agent/models/index.ts b/packages/core/src/agent/models/index.ts index 3a9ffdf3ca..1383036898 100644 --- a/packages/core/src/agent/models/index.ts +++ b/packages/core/src/agent/models/index.ts @@ -1,2 +1,4 @@ export * from './features' export * from './InboundMessageContext' +export * from './OutboundMessageContext' +export * from './OutboundMessageSendStatus' diff --git a/packages/core/src/error/MessageSendingError.ts b/packages/core/src/error/MessageSendingError.ts index 6ebc95a23d..6d0ddc46aa 100644 --- a/packages/core/src/error/MessageSendingError.ts +++ b/packages/core/src/error/MessageSendingError.ts @@ -1,11 +1,14 @@ -import type { OutboundMessage } from '../types' +import type { OutboundMessageContext } from '../agent/models' import { AriesFrameworkError } from './AriesFrameworkError' export class MessageSendingError extends AriesFrameworkError { - public outboundMessage: OutboundMessage - public constructor(message: string, { outboundMessage, cause }: { outboundMessage: OutboundMessage; cause?: Error }) { + public outboundMessageContext: OutboundMessageContext + public constructor( + message: string, + { outboundMessageContext, cause }: { outboundMessageContext: OutboundMessageContext; cause?: Error } + ) { super(message, { cause }) - this.outboundMessage = outboundMessage + this.outboundMessageContext = outboundMessageContext } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 355383f062..1003c073b5 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -10,7 +10,6 @@ export { EventEmitter } from './agent/EventEmitter' export { FeatureRegistry } from './agent/FeatureRegistry' export { Handler, HandlerInboundMessage } from './agent/Handler' export * from './agent/models' -export * from './agent/helpers' export { AgentConfig } from './agent/AgentConfig' export { AgentMessage } from './agent/AgentMessage' export { Dispatcher } from './agent/Dispatcher' diff --git a/packages/core/src/modules/basic-messages/BasicMessagesApi.ts b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts index fbebfc30c2..17bb32d080 100644 --- a/packages/core/src/modules/basic-messages/BasicMessagesApi.ts +++ b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts @@ -4,7 +4,7 @@ import type { BasicMessageRecord } from './repository/BasicMessageRecord' import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' +import { OutboundMessageContext } from '../../agent/models' import { injectable } from '../../plugins' import { ConnectionService } from '../connections' @@ -49,10 +49,13 @@ export class BasicMessagesApi { message, connection ) - const outboundMessage = createOutboundMessage(connection, basicMessage) - outboundMessage.associatedRecord = basicMessageRecord + const outboundMessageContext = new OutboundMessageContext(basicMessage, { + agentContext: this.agentContext, + connection, + associatedRecord: basicMessageRecord, + }) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + await this.messageSender.sendMessage(outboundMessageContext) return basicMessageRecord } diff --git a/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts index 4f3b7205f1..e4e2d0dd17 100644 --- a/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts +++ b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts @@ -91,18 +91,20 @@ describe('Basic Messages E2E', () => { `Message is undeliverable to connection ${aliceConnection.id} (${aliceConnection.theirLabel})` ) testLogger.test('Error thrown includes the outbound message and recently created record id') - expect(thrownError.outboundMessage.associatedRecord).toBeInstanceOf(BasicMessageRecord) - expect(thrownError.outboundMessage.payload).toBeInstanceOf(BasicMessage) - expect((thrownError.outboundMessage.payload as BasicMessage).content).toBe('Hello undeliverable') + expect(thrownError.outboundMessageContext.associatedRecord).toBeInstanceOf(BasicMessageRecord) + expect(thrownError.outboundMessageContext.message).toBeInstanceOf(BasicMessage) + expect((thrownError.outboundMessageContext.message as BasicMessage).content).toBe('Hello undeliverable') testLogger.test('Created record can be found and deleted by id') - const storedRecord = await aliceAgent.basicMessages.getById(thrownError.outboundMessage.associatedRecord!.id) + const storedRecord = await aliceAgent.basicMessages.getById( + thrownError.outboundMessageContext.associatedRecord!.id + ) expect(storedRecord).toBeInstanceOf(BasicMessageRecord) expect(storedRecord.content).toBe('Hello undeliverable') await aliceAgent.basicMessages.deleteById(storedRecord.id) await expect( - aliceAgent.basicMessages.getById(thrownError.outboundMessage.associatedRecord!.id) + aliceAgent.basicMessages.getById(thrownError.outboundMessageContext.associatedRecord!.id) ).rejects.toThrowError(RecordNotFoundError) } spy.mockClear() diff --git a/packages/core/src/modules/connections/ConnectionsApi.ts b/packages/core/src/modules/connections/ConnectionsApi.ts index 1b4207be31..a384132463 100644 --- a/packages/core/src/modules/connections/ConnectionsApi.ts +++ b/packages/core/src/modules/connections/ConnectionsApi.ts @@ -7,7 +7,7 @@ import type { Routing } from './services' import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' +import { OutboundMessageContext } from '../../agent/models' import { ReturnRouteTypes } from '../../decorators/transport/TransportDecorator' import { AriesFrameworkError } from '../../error' import { injectable } from '../../plugins' @@ -114,8 +114,12 @@ export class ConnectionsApi { } const { message, connectionRecord } = result - const outboundMessage = createOutboundMessage(connectionRecord, message, outOfBandRecord) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection: connectionRecord, + outOfBand: outOfBandRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) return connectionRecord } @@ -140,24 +144,30 @@ export class ConnectionsApi { throw new AriesFrameworkError(`Out-of-band record ${connectionRecord.outOfBandId} not found.`) } - let outboundMessage + let outboundMessageContext if (connectionRecord.protocol === HandshakeProtocol.DidExchange) { const message = await this.didExchangeProtocol.createResponse( this.agentContext, connectionRecord, outOfBandRecord ) - outboundMessage = createOutboundMessage(connectionRecord, message) + outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection: connectionRecord, + }) } else { const { message } = await this.connectionService.createResponse( this.agentContext, connectionRecord, outOfBandRecord ) - outboundMessage = createOutboundMessage(connectionRecord, message) + outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection: connectionRecord, + }) } - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + await this.messageSender.sendMessage(outboundMessageContext) return connectionRecord } @@ -171,7 +181,7 @@ export class ConnectionsApi { public async acceptResponse(connectionId: string): Promise { const connectionRecord = await this.connectionService.getById(this.agentContext, connectionId) - let outboundMessage + let outboundMessageContext if (connectionRecord.protocol === HandshakeProtocol.DidExchange) { if (!connectionRecord.outOfBandId) { throw new AriesFrameworkError(`Connection ${connectionRecord.id} does not have outOfBandId!`) @@ -190,7 +200,10 @@ export class ConnectionsApi { // 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 message.setReturnRouting(ReturnRouteTypes.none) - outboundMessage = createOutboundMessage(connectionRecord, message) + outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection: connectionRecord, + }) } else { const { message } = await this.connectionService.createTrustPing(this.agentContext, connectionRecord, { responseRequested: false, @@ -198,10 +211,13 @@ export class ConnectionsApi { // 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 message.setReturnRouting(ReturnRouteTypes.none) - outboundMessage = createOutboundMessage(connectionRecord, message) + outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection: connectionRecord, + }) } - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + await this.messageSender.sendMessage(outboundMessageContext) return connectionRecord } diff --git a/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts index 1291546616..77056ed0fd 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts @@ -5,7 +5,7 @@ import type { RoutingService } from '../../routing/services/RoutingService' import type { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' import type { ConnectionService } from '../services/ConnectionService' -import { createOutboundMessage } from '../../../agent/helpers' +import { OutboundMessageContext } from '../../../agent/models' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' import { ConnectionRequestMessage } from '../messages' @@ -69,7 +69,11 @@ export class ConnectionRequestHandler implements Handler { outOfBandRecord, routing ) - return createOutboundMessage(connectionRecord, message, outOfBandRecord) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: connectionRecord, + outOfBand: outOfBandRecord, + }) } } } diff --git a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts index 9e28621fb0..28794676c6 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts @@ -4,7 +4,7 @@ import type { OutOfBandService } from '../../oob/OutOfBandService' import type { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' import type { ConnectionService } from '../services/ConnectionService' -import { createOutboundMessage } from '../../../agent/helpers' +import { OutboundMessageContext } from '../../../agent/models' import { ReturnRouteTypes } from '../../../decorators/transport/TransportDecorator' import { AriesFrameworkError } from '../../../error' import { ConnectionResponseMessage } from '../messages' @@ -83,7 +83,7 @@ export class ConnectionResponseHandler implements Handler { // 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 message.setReturnRouting(ReturnRouteTypes.none) - return createOutboundMessage(connection, message) + return new OutboundMessageContext(message, { agentContext: messageContext.agentContext, connection }) } } } diff --git a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts index f9d46cec7b..309d351726 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts @@ -5,7 +5,7 @@ import type { RoutingService } from '../../routing/services/RoutingService' import type { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' import type { DidExchangeProtocol } from '../DidExchangeProtocol' -import { createOutboundMessage } from '../../../agent/helpers' +import { OutboundMessageContext } from '../../../agent/models' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' import { OutOfBandState } from '../../oob/domain/OutOfBandState' import { DidExchangeRequestMessage } from '../messages' @@ -85,7 +85,11 @@ export class DidExchangeRequestHandler implements Handler { outOfBandRecord, routing ) - return createOutboundMessage(connectionRecord, message, outOfBandRecord) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: connectionRecord, + outOfBand: outOfBandRecord, + }) } } } diff --git a/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts index f2b40697ea..3f71f85251 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts @@ -5,7 +5,7 @@ import type { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' import type { DidExchangeProtocol } from '../DidExchangeProtocol' import type { ConnectionService } from '../services' -import { createOutboundMessage } from '../../../agent/helpers' +import { OutboundMessageContext } from '../../../agent/models' import { ReturnRouteTypes } from '../../../decorators/transport/TransportDecorator' import { AriesFrameworkError } from '../../../error' import { OutOfBandState } from '../../oob/domain/OutOfBandState' @@ -35,13 +35,13 @@ export class DidExchangeResponseHandler implements Handler { } public async handle(messageContext: HandlerInboundMessage) { - const { recipientKey, senderKey, message } = messageContext + const { agentContext, recipientKey, senderKey, message } = messageContext if (!recipientKey || !senderKey) { throw new AriesFrameworkError('Unable to process connection response without sender key or recipient key') } - const connectionRecord = await this.connectionService.getByThreadId(messageContext.agentContext, message.threadId) + const connectionRecord = await this.connectionService.getByThreadId(agentContext, message.threadId) if (!connectionRecord) { throw new AriesFrameworkError(`Connection for thread ID ${message.threadId} not found!`) } @@ -50,10 +50,7 @@ export class DidExchangeResponseHandler implements Handler { throw new AriesFrameworkError(`Connection record ${connectionRecord.id} has no 'did'`) } - const ourDidDocument = await this.didResolverService.resolveDidDocument( - messageContext.agentContext, - connectionRecord.did - ) + const ourDidDocument = await this.didResolverService.resolveDidDocument(agentContext, connectionRecord.did) if (!ourDidDocument) { throw new AriesFrameworkError(`Did document for did ${connectionRecord.did} was not resolved`) } @@ -77,10 +74,7 @@ export class DidExchangeResponseHandler implements Handler { throw new AriesFrameworkError(`Connection ${connectionRecord.id} does not have outOfBandId!`) } - const outOfBandRecord = await this.outOfBandService.findById( - messageContext.agentContext, - connectionRecord.outOfBandId - ) + const outOfBandRecord = await this.outOfBandService.findById(agentContext, connectionRecord.outOfBandId) if (!outOfBandRecord) { throw new AriesFrameworkError( @@ -103,19 +97,15 @@ export class DidExchangeResponseHandler implements Handler { // In AATH we have a separate step to send the complete. So for now we'll only do it // if auto accept is enabled if (connection.autoAcceptConnection ?? this.connectionsModuleConfig.autoAcceptConnections) { - const message = await this.didExchangeProtocol.createComplete( - messageContext.agentContext, - connection, - outOfBandRecord - ) + const message = await this.didExchangeProtocol.createComplete(agentContext, connection, outOfBandRecord) // 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 message.setReturnRouting(ReturnRouteTypes.none) if (!outOfBandRecord.reusable) { - await this.outOfBandService.updateState(messageContext.agentContext, outOfBandRecord, OutOfBandState.Done) + await this.outOfBandService.updateState(agentContext, outOfBandRecord, OutOfBandState.Done) } - return createOutboundMessage(connection, message) + return new OutboundMessageContext(message, { agentContext, connection }) } } } diff --git a/packages/core/src/modules/connections/services/TrustPingService.ts b/packages/core/src/modules/connections/services/TrustPingService.ts index 1b6a9bbdf2..17032e089e 100644 --- a/packages/core/src/modules/connections/services/TrustPingService.ts +++ b/packages/core/src/modules/connections/services/TrustPingService.ts @@ -2,19 +2,19 @@ import type { InboundMessageContext } from '../../../agent/models/InboundMessage import type { TrustPingMessage } from '../messages' import type { ConnectionRecord } from '../repository/ConnectionRecord' -import { createOutboundMessage } from '../../../agent/helpers' +import { OutboundMessageContext } from '../../../agent/models' import { injectable } from '../../../plugins' import { TrustPingResponseMessage } from '../messages' @injectable() export class TrustPingService { - public processPing({ message }: InboundMessageContext, connection: ConnectionRecord) { + public processPing({ message, agentContext }: InboundMessageContext, connection: ConnectionRecord) { if (message.responseRequested) { const response = new TrustPingResponseMessage({ threadId: message.id, }) - return createOutboundMessage(connection, response) + return new OutboundMessageContext(response, { agentContext, connection }) } } diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index 419f6cfc67..3eefc4857a 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -27,7 +27,7 @@ import type { CredentialService } from './services/CredentialService' import { AgentContext } from '../../agent' import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' +import { OutboundMessageContext } from '../../agent/models' import { InjectionSymbols } from '../../constants' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' @@ -181,10 +181,14 @@ export class CredentialsApi< this.logger.debug('We have a message (sending outbound): ', message) // send the message here - const outbound = createOutboundMessage(connection, message) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: credentialRecord, + }) this.logger.debug('In proposeCredential: Send Proposal to Issuer') - await this.messageSender.sendMessage(this.agentContext, outbound) + await this.messageSender.sendMessage(outboundMessageContext) return credentialRecord } @@ -218,8 +222,12 @@ export class CredentialsApi< // send the message const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outbound = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outbound) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: credentialRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) return credentialRecord } @@ -252,8 +260,12 @@ export class CredentialsApi< }) const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: credentialRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) return credentialRecord } @@ -279,8 +291,12 @@ export class CredentialsApi< }) this.logger.debug('Offer Message successfully created; message= ', message) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: credentialRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) return credentialRecord } @@ -311,8 +327,12 @@ export class CredentialsApi< autoAcceptCredential: options.autoAcceptCredential, }) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: credentialRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) return credentialRecord } @@ -342,12 +362,16 @@ export class CredentialsApi< associatedRecordId: credentialRecord.id, }) - await this.messageSender.sendMessageToService(this.agentContext, { - message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, - }) + await this.messageSender.sendMessageToService( + new OutboundMessageContext(message, { + agentContext: this.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }, + }) + ) return credentialRecord } @@ -388,8 +412,12 @@ export class CredentialsApi< } const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: credentialRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) return credentialRecord } @@ -447,8 +475,12 @@ export class CredentialsApi< // Use connection if present if (credentialRecord.connectionId) { const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: credentialRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) return credentialRecord } @@ -464,12 +496,16 @@ export class CredentialsApi< associatedRecordId: credentialRecord.id, }) - await this.messageSender.sendMessageToService(this.agentContext, { - message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, - }) + await this.messageSender.sendMessageToService( + new OutboundMessageContext(message, { + agentContext: this.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }, + }) + ) return credentialRecord } @@ -506,9 +542,13 @@ export class CredentialsApi< if (credentialRecord.connectionId) { const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, message) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: credentialRecord, + }) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + await this.messageSender.sendMessage(outboundMessageContext) return credentialRecord } @@ -517,12 +557,16 @@ export class CredentialsApi< const recipientService = credentialMessage.service const ourService = requestMessage.service - await this.messageSender.sendMessageToService(this.agentContext, { - message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, - }) + await this.messageSender.sendMessageToService( + new OutboundMessageContext(message, { + agentContext: this.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }, + }) + ) return credentialRecord } @@ -552,8 +596,12 @@ export class CredentialsApi< problemReportMessage.setThread({ threadId: credentialRecord.threadId, }) - const outboundMessage = createOutboundMessage(connection, problemReportMessage) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(problemReportMessage, { + agentContext: this.agentContext, + connection, + associatedRecord: credentialRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) return credentialRecord } diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts index c8b986d97e..e30f82e101 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts @@ -4,7 +4,7 @@ import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialService } from '../V1CredentialService' -import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { V1IssueCredentialMessage, V1RequestCredentialMessage } from '../messages' export class V1IssueCredentialHandler implements Handler { @@ -51,15 +51,21 @@ export class V1IssueCredentialHandler implements Handler { }) if (messageContext.connection) { - return createOutboundMessage(messageContext.connection, message) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: credentialRecord, + }) } else if (messageContext.message.service && requestMessage.service) { const recipientService = messageContext.message.service const ourService = requestMessage.service - return createOutboundServiceMessage({ - payload: message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + }, }) } diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts index 510c5de434..bb85fd199a 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts @@ -5,7 +5,7 @@ import type { RoutingService } from '../../../../routing/services/RoutingService import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialService } from '../V1CredentialService' -import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' import { DidCommMessageRole } from '../../../../../storage' import { V1OfferCredentialMessage } from '../messages' @@ -50,7 +50,11 @@ export class V1OfferCredentialHandler implements Handler { if (messageContext.connection) { const { message } = await this.credentialService.acceptOffer(messageContext.agentContext, { credentialRecord }) - return createOutboundMessage(messageContext.connection, message) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: credentialRecord, + }) } else if (messageContext.message.service) { const routing = await this.routingService.getRouting(messageContext.agentContext) const ourService = new ServiceDecorator({ @@ -77,10 +81,12 @@ export class V1OfferCredentialHandler implements Handler { associatedRecordId: credentialRecord.id, }) - return createOutboundServiceMessage({ - payload: message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + }, }) } diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts index 05dc7371af..f427a97670 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts @@ -3,7 +3,7 @@ import type { Logger } from '../../../../../logger' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialService } from '../V1CredentialService' -import { createOutboundMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { V1ProposeCredentialMessage } from '../messages' export class V1ProposeCredentialHandler implements Handler { @@ -47,6 +47,10 @@ export class V1ProposeCredentialHandler implements Handler { credentialRecord, }) - return createOutboundMessage(messageContext.connection, message) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: credentialRecord, + }) } } diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts index f155001022..ba85e05ae8 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts @@ -4,7 +4,7 @@ import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialService } from '../V1CredentialService' -import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { DidCommMessageRole } from '../../../../../storage' import { V1RequestCredentialMessage } from '../messages' @@ -50,7 +50,11 @@ export class V1RequestCredentialHandler implements Handler { }) if (messageContext.connection) { - return createOutboundMessage(messageContext.connection, message) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: credentialRecord, + }) } else if (messageContext.message.service && offerMessage?.service) { const recipientService = messageContext.message.service const ourService = offerMessage.service @@ -64,10 +68,12 @@ export class V1RequestCredentialHandler implements Handler { associatedRecordId: credentialRecord.id, }) - return createOutboundServiceMessage({ - payload: message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + }, }) } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts index 8f1db634e9..308be12bc1 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts @@ -5,7 +5,7 @@ import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V2CredentialService } from '../V2CredentialService' -import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { V2IssueCredentialMessage } from '../messages/V2IssueCredentialMessage' import { V2RequestCredentialMessage } from '../messages/V2RequestCredentialMessage' @@ -54,15 +54,21 @@ export class V2IssueCredentialHandler implements Handler { credentialRecord, }) if (messageContext.connection) { - return createOutboundMessage(messageContext.connection, message) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: credentialRecord, + }) } else if (requestMessage?.service && messageContext.message.service) { const recipientService = messageContext.message.service const ourService = requestMessage.service - return createOutboundServiceMessage({ - payload: message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + }, }) } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts index 095a5a5a0f..d1d4248661 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts @@ -6,7 +6,7 @@ import type { RoutingService } from '../../../../routing/services/RoutingService import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V2CredentialService } from '../V2CredentialService' -import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' import { DidCommMessageRole } from '../../../../../storage' import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' @@ -54,7 +54,11 @@ export class V2OfferCredentialHandler implements Handler { const { message } = await this.credentialService.acceptOffer(messageContext.agentContext, { credentialRecord, }) - return createOutboundMessage(messageContext.connection, message) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: credentialRecord, + }) } else if (offerMessage?.service) { const routing = await this.routingService.getRouting(messageContext.agentContext) const ourService = new ServiceDecorator({ @@ -77,10 +81,12 @@ export class V2OfferCredentialHandler implements Handler { associatedRecordId: credentialRecord.id, }) - return createOutboundServiceMessage({ - payload: message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + }, }) } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts index c005480617..d536303a33 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts @@ -4,7 +4,7 @@ import type { Logger } from '../../../../../logger' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V2CredentialService } from '../V2CredentialService' -import { createOutboundMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { V2ProposeCredentialMessage } from '../messages/V2ProposeCredentialMessage' export class V2ProposeCredentialHandler implements Handler { @@ -44,6 +44,10 @@ export class V2ProposeCredentialHandler implements Handler { const { message } = await this.credentialService.acceptProposal(messageContext.agentContext, { credentialRecord }) - return createOutboundMessage(messageContext.connection, message) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: credentialRecord, + }) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts index e30286f988..72b436174d 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts @@ -5,7 +5,7 @@ import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository' import type { V2CredentialService } from '../V2CredentialService' -import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { DidCommMessageRole } from '../../../../../storage' import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' import { V2RequestCredentialMessage } from '../messages/V2RequestCredentialMessage' @@ -56,7 +56,11 @@ export class V2RequestCredentialHandler implements Handler { }) if (messageContext.connection) { - return createOutboundMessage(messageContext.connection, message) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: credentialRecord, + }) } else if (messageContext.message.service && offerMessage?.service) { const recipientService = messageContext.message.service const ourService = offerMessage.service @@ -69,10 +73,12 @@ export class V2RequestCredentialHandler implements Handler { role: DidCommMessageRole.Sender, }) - return createOutboundServiceMessage({ - payload: message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + }, }) } diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts index faa198b1de..94e376f08d 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts @@ -13,7 +13,7 @@ import { catchError, filter, map, takeUntil, timeout } from 'rxjs/operators' import { AgentContext } from '../../agent' import { EventEmitter } from '../../agent/EventEmitter' import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' +import { OutboundMessageContext } from '../../agent/models' import { InjectionSymbols } from '../../constants' import { AriesFrameworkError } from '../../error' import { inject, injectable } from '../../plugins' @@ -103,7 +103,10 @@ export class DiscoverFeaturesApi< comment: options.comment, }) - const outbound = createOutboundMessage(connection, queryMessage) + const outboundMessageContext = new OutboundMessageContext(queryMessage, { + agentContext: this.agentContext, + connection, + }) const replaySubject = new ReplaySubject(1) if (options.awaitDisclosures) { @@ -125,7 +128,7 @@ export class DiscoverFeaturesApi< .subscribe(replaySubject) } - await this.messageSender.sendMessage(this.agentContext, outbound) + await this.messageSender.sendMessage(outboundMessageContext) return { features: options.awaitDisclosures ? await firstValueFrom(replaySubject) : undefined } } @@ -151,7 +154,10 @@ export class DiscoverFeaturesApi< threadId: options.threadId, }) - const outbound = createOutboundMessage(connection, disclosuresMessage) - await this.messageSender.sendMessage(this.agentContext, outbound) + const outboundMessageContext = new OutboundMessageContext(disclosuresMessage, { + agentContext: this.agentContext, + connection, + }) + await this.messageSender.sendMessage(outboundMessageContext) } } diff --git a/packages/core/src/modules/discover-features/protocol/v1/handlers/V1QueryMessageHandler.ts b/packages/core/src/modules/discover-features/protocol/v1/handlers/V1QueryMessageHandler.ts index 3ef5a66231..9a5bacf74c 100644 --- a/packages/core/src/modules/discover-features/protocol/v1/handlers/V1QueryMessageHandler.ts +++ b/packages/core/src/modules/discover-features/protocol/v1/handlers/V1QueryMessageHandler.ts @@ -1,7 +1,7 @@ import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' import type { V1DiscoverFeaturesService } from '../V1DiscoverFeaturesService' -import { createOutboundMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { V1QueryMessage } from '../messages' export class V1QueryMessageHandler implements Handler { @@ -18,7 +18,10 @@ export class V1QueryMessageHandler implements Handler { const discloseMessage = await this.discoverFeaturesService.processQuery(inboundMessage) if (discloseMessage) { - return createOutboundMessage(connection, discloseMessage.message) + return new OutboundMessageContext(discloseMessage.message, { + agentContext: inboundMessage.agentContext, + connection, + }) } } } diff --git a/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts index d637bf2bc7..8664dd2240 100644 --- a/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts +++ b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts @@ -1,7 +1,7 @@ import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' import type { V2DiscoverFeaturesService } from '../V2DiscoverFeaturesService' -import { createOutboundMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { V2QueriesMessage } from '../messages' export class V2QueriesMessageHandler implements Handler { @@ -18,7 +18,10 @@ export class V2QueriesMessageHandler implements Handler { const discloseMessage = await this.discoverFeaturesService.processQuery(inboundMessage) if (discloseMessage) { - return createOutboundMessage(connection, discloseMessage.message) + return new OutboundMessageContext(discloseMessage.message, { + agentContext: inboundMessage.agentContext, + connection, + }) } } } diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts index a23c163b42..f66ad984d3 100644 --- a/packages/core/src/modules/oob/OutOfBandApi.ts +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -14,7 +14,7 @@ import { Dispatcher } from '../../agent/Dispatcher' import { EventEmitter } from '../../agent/EventEmitter' import { filterContextCorrelationId, AgentEventTypes } from '../../agent/Events' import { MessageSender } from '../../agent/MessageSender' -import { createOutboundMessage } from '../../agent/helpers' +import { OutboundMessageContext } from '../../agent/models' import { InjectionSymbols } from '../../constants' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' @@ -748,8 +748,11 @@ export class OutOfBandApi { ) ) - const outbound = createOutboundMessage(connectionRecord, reuseMessage) - await this.messageSender.sendMessage(this.agentContext, outbound) + const outboundMessageContext = new OutboundMessageContext(reuseMessage, { + agentContext: this.agentContext, + connection: connectionRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) return reuseAcceptedEventPromise } diff --git a/packages/core/src/modules/oob/handlers/HandshakeReuseHandler.ts b/packages/core/src/modules/oob/handlers/HandshakeReuseHandler.ts index 632eddd96a..43ec159d2e 100644 --- a/packages/core/src/modules/oob/handlers/HandshakeReuseHandler.ts +++ b/packages/core/src/modules/oob/handlers/HandshakeReuseHandler.ts @@ -2,7 +2,7 @@ import type { Handler } from '../../../agent/Handler' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { OutOfBandService } from '../OutOfBandService' -import { createOutboundMessage } from '../../../agent/helpers' +import { OutboundMessageContext } from '../../../agent/models' import { HandshakeReuseMessage } from '../messages/HandshakeReuseMessage' export class HandshakeReuseHandler implements Handler { @@ -17,6 +17,9 @@ export class HandshakeReuseHandler implements Handler { const connectionRecord = messageContext.assertReadyConnection() const handshakeReuseAcceptedMessage = await this.outOfBandService.processHandshakeReuse(messageContext) - return createOutboundMessage(connectionRecord, handshakeReuseAcceptedMessage) + return new OutboundMessageContext(handshakeReuseAcceptedMessage, { + agentContext: messageContext.agentContext, + connection: connectionRecord, + }) } } diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index 3e337b7ee8..1bec0aec43 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -37,7 +37,7 @@ import { AgentConfig } from '../../agent/AgentConfig' import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' import { AgentContext } from '../../agent/context/AgentContext' -import { createOutboundMessage } from '../../agent/helpers' +import { OutboundMessageContext } from '../../agent/models' import { InjectionSymbols } from '../../constants' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' @@ -185,8 +185,12 @@ export class ProofsApi< const { message, proofRecord } = await service.createProposal(this.agentContext, proposalOptions) - const outbound = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outbound) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: proofRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) return proofRecord } @@ -234,8 +238,12 @@ export class ProofsApi< const { message } = await service.createRequestAsResponse(this.agentContext, requestOptions) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: proofRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) return proofRecord } @@ -264,8 +272,12 @@ export class ProofsApi< } const { message, proofRecord } = await service.createRequest(this.agentContext, createProofRequest) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: proofRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) return proofRecord } @@ -301,8 +313,12 @@ export class ProofsApi< // Assert connection.assertReady() - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: proofRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) return proofRecord } @@ -327,12 +343,16 @@ export class ProofsApi< role: DidCommMessageRole.Sender, }) - await this.messageSender.sendMessageToService(this.agentContext, { - message, - service: recipientService.resolvedDidCommService, - senderKey: message.service.resolvedDidCommService.recipientKeys[0], - returnRoute: true, - }) + await this.messageSender.sendMessageToService( + new OutboundMessageContext(message, { + agentContext: this.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: message.service.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }, + }) + ) return proofRecord } @@ -405,20 +425,28 @@ export class ProofsApi< // Assert connection.assertReady() - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: proofRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) } // Use ~service decorator otherwise else if (requestMessage?.service && presentationMessage?.service) { const recipientService = presentationMessage?.service const ourService = requestMessage.service - await this.messageSender.sendMessageToService(this.agentContext, { - message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, - }) + await this.messageSender.sendMessageToService( + new OutboundMessageContext(message, { + agentContext: this.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }, + }) + ) } // Cannot send message without credentialId or ~service decorator else { @@ -497,8 +525,12 @@ export class ProofsApi< description: message, }) - const outboundMessage = createOutboundMessage(connection, problemReport) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(problemReport, { + agentContext: this.agentContext, + connection, + associatedRecord: record, + }) + await this.messageSender.sendMessage(outboundMessageContext) return record } diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts index c55e644b44..c3fcd713d2 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts @@ -5,7 +5,7 @@ import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator import type { ProofExchangeRecord } from '../../../repository' import type { V1ProofService } from '../V1ProofService' -import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { V1PresentationMessage, V1RequestPresentationMessage } from '../messages' export class V1PresentationHandler implements Handler { @@ -55,15 +55,21 @@ export class V1PresentationHandler implements Handler { }) if (messageContext.connection) { - return createOutboundMessage(messageContext.connection, message) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: proofRecord, + }) } else if (requestMessage?.service && presentationMessage?.service) { const recipientService = presentationMessage?.service const ourService = requestMessage?.service - return createOutboundServiceMessage({ - payload: message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + }, }) } diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts index 4a9fe4b104..4e9d437669 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts @@ -8,7 +8,7 @@ import type { ProofRequestFromProposalOptions } from '../../../models/ProofServi import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { V1ProofService } from '../V1ProofService' -import { createOutboundMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { AriesFrameworkError } from '../../../../../error' import { V1ProposePresentationMessage } from '../messages' @@ -99,6 +99,10 @@ export class V1ProposePresentationHandler implements Handler { willConfirm: true, }) - return createOutboundMessage(messageContext.connection, message) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: proofRecord, + }) } } diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts index 5f7e72e7e8..5a0455baba 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts @@ -11,7 +11,7 @@ import type { import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { V1ProofService } from '../V1ProofService' -import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../../../../error' import { DidCommMessageRole } from '../../../../../storage' @@ -96,7 +96,11 @@ export class V1RequestPresentationHandler implements Handler { }) if (messageContext.connection) { - return createOutboundMessage(messageContext.connection, message) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: proofRecord, + }) } else if (requestMessage.service) { const routing = await this.routingService.getRouting(messageContext.agentContext) message.service = new ServiceDecorator({ @@ -111,10 +115,12 @@ export class V1RequestPresentationHandler implements Handler { associatedRecordId: proofRecord.id, role: DidCommMessageRole.Sender, }) - return createOutboundServiceMessage({ - payload: message, - service: recipientService.resolvedDidCommService, - senderKey: message.service.resolvedDidCommService.recipientKeys[0], + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: message.service.resolvedDidCommService.recipientKeys[0], + }, }) } diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts index c9723d8555..c812b6e8bd 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts @@ -5,7 +5,7 @@ import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator import type { ProofExchangeRecord } from '../../../repository' import type { V2ProofService } from '../V2ProofService' -import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { V2PresentationMessage, V2RequestPresentationMessage } from '../messages' export class V2PresentationHandler implements Handler { @@ -55,15 +55,21 @@ export class V2PresentationHandler implements Handler { }) if (messageContext.connection) { - return createOutboundMessage(messageContext.connection, message) + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: proofRecord, + }) } else if (requestMessage?.service && presentationMessage?.service) { const recipientService = presentationMessage?.service const ourService = requestMessage?.service - return createOutboundServiceMessage({ - payload: message, - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + }, }) } diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts index e3ddafe295..e822ed7a32 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts @@ -11,7 +11,7 @@ import type { import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { V2ProofService } from '../V2ProofService' -import { createOutboundMessage } from '../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../agent/models' import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' @@ -90,6 +90,10 @@ export class V2ProposePresentationHandler 0 ? new MessageDeliveryMessage({ threadId: messageContext.message.threadId, @@ -93,7 +93,7 @@ export class V2MessagePickupService { messageCount: 0, }) - return createOutboundMessage(connection, outboundMessage) + return new OutboundMessageContext(outboundMessageContext, { agentContext: messageContext.agentContext, connection }) } public async processMessagesReceived(messageContext: InboundMessageContext) { @@ -113,7 +113,7 @@ export class V2MessagePickupService { messageCount: await this.messageRepository.getAvailableMessageCount(connection.id), }) - return createOutboundMessage(connection, statusMessage) + return new OutboundMessageContext(statusMessage, { agentContext: messageContext.agentContext, connection }) } protected registerHandlers() { diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessageDeliveryHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessageDeliveryHandler.ts index 07b01a3d7f..fb9db1d25b 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessageDeliveryHandler.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessageDeliveryHandler.ts @@ -2,7 +2,7 @@ import type { Handler } from '../../../../../../agent/Handler' import type { InboundMessageContext } from '../../../../../../agent/models/InboundMessageContext' import type { MediationRecipientService } from '../../../../services' -import { createOutboundMessage } from '../../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../../agent/models' import { MessageDeliveryMessage } from '../messages/MessageDeliveryMessage' export class MessageDeliveryHandler implements Handler { @@ -18,7 +18,10 @@ export class MessageDeliveryHandler implements Handler { const deliveryReceivedMessage = await this.mediationRecipientService.processDelivery(messageContext) if (deliveryReceivedMessage) { - return createOutboundMessage(connection, deliveryReceivedMessage) + return new OutboundMessageContext(deliveryReceivedMessage, { + agentContext: messageContext.agentContext, + connection, + }) } } } diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusHandler.ts index 08e4278c61..163f1a9c4f 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusHandler.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusHandler.ts @@ -2,7 +2,7 @@ import type { Handler } from '../../../../../../agent/Handler' import type { InboundMessageContext } from '../../../../../../agent/models/InboundMessageContext' import type { MediationRecipientService } from '../../../../services' -import { createOutboundMessage } from '../../../../../../agent/helpers' +import { OutboundMessageContext } from '../../../../../../agent/models' import { StatusMessage } from '../messages' export class StatusHandler implements Handler { @@ -18,7 +18,10 @@ export class StatusHandler implements Handler { const deliveryRequestMessage = await this.mediatorRecipientService.processStatus(messageContext) if (deliveryRequestMessage) { - return createOutboundMessage(connection, deliveryRequestMessage) + return new OutboundMessageContext(deliveryRequestMessage, { + agentContext: messageContext.agentContext, + connection, + }) } } } diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index b62db55446..f05f66e20f 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -17,7 +17,7 @@ import { filter, first, timeout } from 'rxjs/operators' import { EventEmitter } from '../../../agent/EventEmitter' import { filterContextCorrelationId, AgentEventTypes } from '../../../agent/Events' import { MessageSender } from '../../../agent/MessageSender' -import { createOutboundMessage } from '../../../agent/helpers' +import { OutboundMessageContext } from '../../../agent/models' import { Key, KeyType } from '../../../crypto' import { AriesFrameworkError } from '../../../error' import { injectable } from '../../../plugins' @@ -209,8 +209,8 @@ export class MediationRecipientService { ) .subscribe(subject) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(message, { agentContext, connection }) + await this.messageSender.sendMessage(outboundMessageContext) const keylistUpdate = await firstValueFrom(subject) return keylistUpdate.payload.mediationRecord @@ -297,8 +297,10 @@ export class MediationRecipientService { const websocketSchemes = ['ws', 'wss'] await this.messageSender.sendMessage( - messageContext.agentContext, - createOutboundMessage(connectionRecord, message), + new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: connectionRecord, + }), { transportPriority: { schemes: websocketSchemes, diff --git a/packages/core/src/modules/routing/services/__tests__/V2MessagePickupService.test.ts b/packages/core/src/modules/routing/services/__tests__/V2MessagePickupService.test.ts index e72ce7c425..53012f73b0 100644 --- a/packages/core/src/modules/routing/services/__tests__/V2MessagePickupService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/V2MessagePickupService.test.ts @@ -60,17 +60,17 @@ describe('V2MessagePickupService', () => { const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) - const { connection, payload } = await pickupService.processStatusRequest(messageContext) + const { connection, message } = await pickupService.processStatusRequest(messageContext) expect(connection).toEqual(mockConnection) - expect(payload).toEqual( + expect(message).toEqual( new StatusMessage({ - id: payload.id, + id: message.id, threadId: statusRequest.threadId, messageCount: 0, }) ) - expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(connection.id) + expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(mockConnection.id) }) test('multiple messages in queue', async () => { @@ -79,17 +79,17 @@ describe('V2MessagePickupService', () => { const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) - const { connection, payload } = await pickupService.processStatusRequest(messageContext) + const { connection, message } = await pickupService.processStatusRequest(messageContext) expect(connection).toEqual(mockConnection) - expect(payload).toEqual( + expect(message).toEqual( new StatusMessage({ - id: payload.id, + id: message.id, threadId: statusRequest.threadId, messageCount: 5, }) ) - expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(connection.id) + expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(mockConnection.id) }) test('status request specifying recipient key', async () => { @@ -115,17 +115,17 @@ describe('V2MessagePickupService', () => { const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection, agentContext }) - const { connection, payload } = await pickupService.processDeliveryRequest(messageContext) + const { connection, message } = await pickupService.processDeliveryRequest(messageContext) expect(connection).toEqual(mockConnection) - expect(payload).toEqual( + expect(message).toEqual( new StatusMessage({ - id: payload.id, + id: message.id, threadId: deliveryRequest.threadId, messageCount: 0, }) ) - expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(connection.id, 10, true) + expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 10, true) }) test('less messages in queue than limit', async () => { @@ -135,13 +135,13 @@ describe('V2MessagePickupService', () => { const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection, agentContext }) - const { connection, payload } = await pickupService.processDeliveryRequest(messageContext) + const { connection, message } = await pickupService.processDeliveryRequest(messageContext) expect(connection).toEqual(mockConnection) - expect(payload).toBeInstanceOf(MessageDeliveryMessage) - expect(payload.threadId).toEqual(deliveryRequest.threadId) - expect(payload.appendedAttachments?.length).toEqual(3) - expect(payload.appendedAttachments).toEqual( + expect(message).toBeInstanceOf(MessageDeliveryMessage) + expect(message.threadId).toEqual(deliveryRequest.threadId) + expect(message.appendedAttachments?.length).toEqual(3) + expect(message.appendedAttachments).toEqual( expect.arrayContaining( queuedMessages.map((msg) => expect.objectContaining({ @@ -152,7 +152,7 @@ describe('V2MessagePickupService', () => { ) ) ) - expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(connection.id, 10, true) + expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 10, true) }) test('more messages in queue than limit', async () => { @@ -162,13 +162,13 @@ describe('V2MessagePickupService', () => { const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection, agentContext }) - const { connection, payload } = await pickupService.processDeliveryRequest(messageContext) + const { connection, message } = await pickupService.processDeliveryRequest(messageContext) expect(connection).toEqual(mockConnection) - expect(payload).toBeInstanceOf(MessageDeliveryMessage) - expect(payload.threadId).toEqual(deliveryRequest.threadId) - expect(payload.appendedAttachments?.length).toEqual(2) - expect(payload.appendedAttachments).toEqual( + expect(message).toBeInstanceOf(MessageDeliveryMessage) + expect(message.threadId).toEqual(deliveryRequest.threadId) + expect(message.appendedAttachments?.length).toEqual(2) + expect(message.appendedAttachments).toEqual( expect.arrayContaining( queuedMessages.slice(0, 2).map((msg) => expect.objectContaining({ @@ -179,7 +179,7 @@ describe('V2MessagePickupService', () => { ) ) ) - expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(connection.id, 2, true) + expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 2, true) }) test('delivery request specifying recipient key', async () => { @@ -209,18 +209,18 @@ describe('V2MessagePickupService', () => { const messageContext = new InboundMessageContext(messagesReceived, { connection: mockConnection, agentContext }) - const { connection, payload } = await pickupService.processMessagesReceived(messageContext) + const { connection, message } = await pickupService.processMessagesReceived(messageContext) expect(connection).toEqual(mockConnection) - expect(payload).toEqual( + expect(message).toEqual( new StatusMessage({ - id: payload.id, + id: message.id, threadId: messagesReceived.threadId, messageCount: 4, }) ) - expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(connection.id) - expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(connection.id, 2) + expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(mockConnection.id) + expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 2) }) test('all messages have been received', async () => { @@ -233,19 +233,19 @@ describe('V2MessagePickupService', () => { const messageContext = new InboundMessageContext(messagesReceived, { connection: mockConnection, agentContext }) - const { connection, payload } = await pickupService.processMessagesReceived(messageContext) + const { connection, message } = await pickupService.processMessagesReceived(messageContext) expect(connection).toEqual(mockConnection) - expect(payload).toEqual( + expect(message).toEqual( new StatusMessage({ - id: payload.id, + id: message.id, threadId: messagesReceived.threadId, messageCount: 0, }) ) - expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(connection.id) - expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(connection.id, 2) + expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(mockConnection.id) + expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 2) }) }) }) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 9e886472df..6d409b000e 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -94,20 +94,6 @@ export interface PlaintextMessage { [key: string]: unknown } -export interface OutboundMessage { - payload: T - connection: ConnectionRecord - sessionId?: string - outOfBand?: OutOfBandRecord - associatedRecord?: BaseRecord -} - -export interface OutboundServiceMessage { - payload: T - service: ResolvedDidCommService - senderKey: Key -} - export interface OutboundPackage { payload: EncryptedMessage responseRequested?: boolean diff --git a/packages/core/tests/multi-protocol-version.test.ts b/packages/core/tests/multi-protocol-version.test.ts index 83dc39986a..07b7b057f0 100644 --- a/packages/core/tests/multi-protocol-version.test.ts +++ b/packages/core/tests/multi-protocol-version.test.ts @@ -8,7 +8,7 @@ import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutbou import { parseMessageType, MessageSender, Dispatcher, AgentMessage, IsValidMessageType } from '../src' import { Agent } from '../src/agent/Agent' import { AgentEventTypes } from '../src/agent/Events' -import { createOutboundMessage } from '../src/agent/helpers' +import { OutboundMessageContext } from '../src/agent/models' import { getAgentOptions } from './helpers' @@ -84,7 +84,9 @@ describe('multi version protocols', () => { ) ) - await bobMessageSender.sendMessage(bobAgent.context, createOutboundMessage(bobConnection, new TestMessageV11())) + await bobMessageSender.sendMessage( + new OutboundMessageContext(new TestMessageV11(), { agentContext: bobAgent.context, connection: bobConnection }) + ) // Wait for the agent message processed event to be called await agentMessageV11ProcessedPromise @@ -99,7 +101,9 @@ describe('multi version protocols', () => { ) ) - await bobMessageSender.sendMessage(bobAgent.context, createOutboundMessage(bobConnection, new TestMessageV15())) + await bobMessageSender.sendMessage( + new OutboundMessageContext(new TestMessageV15(), { agentContext: bobAgent.context, connection: bobConnection }) + ) await agentMessageV15ProcessedPromise expect(mockHandle).toHaveBeenCalledTimes(2) diff --git a/packages/question-answer/src/QuestionAnswerApi.ts b/packages/question-answer/src/QuestionAnswerApi.ts index 52167ebf95..777e3ff12f 100644 --- a/packages/question-answer/src/QuestionAnswerApi.ts +++ b/packages/question-answer/src/QuestionAnswerApi.ts @@ -4,7 +4,7 @@ import type { Query } from '@aries-framework/core' import { AgentContext, ConnectionService, - createOutboundMessage, + OutboundMessageContext, Dispatcher, injectable, MessageSender, @@ -63,8 +63,13 @@ export class QuestionAnswerApi { detail: config?.detail, } ) - const outboundMessage = createOutboundMessage(connection, questionMessage) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(questionMessage, { + agentContext: this.agentContext, + connection, + associatedRecord: questionAnswerRecord, + }) + + await this.messageSender.sendMessage(outboundMessageContext) return questionAnswerRecord } @@ -87,8 +92,13 @@ export class QuestionAnswerApi { const connection = await this.connectionService.getById(this.agentContext, questionRecord.connectionId) - const outboundMessage = createOutboundMessage(connection, answerMessage) - await this.messageSender.sendMessage(this.agentContext, outboundMessage) + const outboundMessageContext = new OutboundMessageContext(answerMessage, { + agentContext: this.agentContext, + connection, + associatedRecord: questionAnswerRecord, + }) + + await this.messageSender.sendMessage(outboundMessageContext) return questionAnswerRecord } diff --git a/samples/extension-module/dummy/DummyApi.ts b/samples/extension-module/dummy/DummyApi.ts index eeb50b469b..dded85085f 100644 --- a/samples/extension-module/dummy/DummyApi.ts +++ b/samples/extension-module/dummy/DummyApi.ts @@ -1,7 +1,14 @@ import type { DummyRecord } from './repository/DummyRecord' import type { Query } from '@aries-framework/core' -import { AgentContext, ConnectionService, Dispatcher, injectable, MessageSender } from '@aries-framework/core' +import { + OutboundMessageContext, + AgentContext, + ConnectionService, + Dispatcher, + injectable, + MessageSender, +} from '@aries-framework/core' import { DummyRequestHandler, DummyResponseHandler } from './handlers' import { DummyState } from './repository' @@ -37,9 +44,11 @@ export class DummyApi { */ public async request(connectionId: string) { const connection = await this.connectionService.getById(this.agentContext, connectionId) - const { record, message: payload } = await this.dummyService.createRequest(this.agentContext, connection) + const { record, message } = await this.dummyService.createRequest(this.agentContext, connection) - await this.messageSender.sendMessage(this.agentContext, { connection, payload }) + await this.messageSender.sendMessage( + new OutboundMessageContext(message, { agentContext: this.agentContext, connection }) + ) await this.dummyService.updateState(this.agentContext, record, DummyState.RequestSent) @@ -56,9 +65,11 @@ export class DummyApi { const record = await this.dummyService.getById(this.agentContext, dummyId) const connection = await this.connectionService.getById(this.agentContext, record.connectionId) - const payload = await this.dummyService.createResponse(this.agentContext, record) + const message = await this.dummyService.createResponse(this.agentContext, record) - await this.messageSender.sendMessage(this.agentContext, { connection, payload }) + await this.messageSender.sendMessage( + new OutboundMessageContext(message, { agentContext: this.agentContext, connection, associatedRecord: record }) + ) await this.dummyService.updateState(this.agentContext, record, DummyState.ResponseSent) diff --git a/samples/extension-module/dummy/handlers/DummyRequestHandler.ts b/samples/extension-module/dummy/handlers/DummyRequestHandler.ts index ef5d5471ec..7b0de4ea72 100644 --- a/samples/extension-module/dummy/handlers/DummyRequestHandler.ts +++ b/samples/extension-module/dummy/handlers/DummyRequestHandler.ts @@ -1,7 +1,7 @@ import type { DummyService } from '../services' import type { Handler, HandlerInboundMessage } from '@aries-framework/core' -import { createOutboundMessage } from '@aries-framework/core' +import { OutboundMessageContext } from '@aries-framework/core' import { DummyRequestMessage } from '../messages' @@ -18,7 +18,7 @@ export class DummyRequestHandler implements Handler { const responseMessage = await this.dummyService.processRequest(inboundMessage) if (responseMessage) { - return createOutboundMessage(connection, responseMessage) + return new OutboundMessageContext(responseMessage, { agentContext: inboundMessage.agentContext, connection }) } } } From c4e96799d8390225ba5aaecced19c79ec1f12fa8 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht <61358536+blu3beri@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:57:30 +0100 Subject: [PATCH 081/125] fix(problem-report): proper string interpolation (#1120) Signed-off-by: blu3beri --- packages/core/src/agent/MessageReceiver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/agent/MessageReceiver.ts b/packages/core/src/agent/MessageReceiver.ts index 2c94f6596f..7cf8d79308 100644 --- a/packages/core/src/agent/MessageReceiver.ts +++ b/packages/core/src/agent/MessageReceiver.ts @@ -265,7 +265,7 @@ export class MessageReceiver { ) { const messageType = parseMessageType(plaintextMessage['@type']) if (canHandleMessageType(ProblemReportMessage, messageType)) { - throw new AriesFrameworkError(`Not sending problem report in response to problem report: {message}`) + throw new AriesFrameworkError(`Not sending problem report in response to problem report: ${message}`) } const problemReportMessage = new ProblemReportMessage({ description: { From f294129821cd6fcb9b82d875f19cab5a63310b23 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht <61358536+blu3beri@users.noreply.github.com> Date: Thu, 1 Dec 2022 15:30:36 +0100 Subject: [PATCH 082/125] fix(proofs): await shouldAutoRespond to correctly handle the check (#1116) --- .../jsonld/JsonLdCredentialFormatService.ts | 6 +- .../protocol/v1/V1CredentialService.ts | 2 +- .../proofs/ProofResponseCoordinator.ts | 6 +- .../formats/indy/IndyProofFormatService.ts | 15 ++- .../proofs/protocol/v1/V1ProofService.ts | 10 +- .../v1/handlers/V1PresentationHandler.ts | 7 +- .../handlers/V1ProposePresentationHandler.ts | 8 +- .../handlers/V1RequestPresentationHandler.ts | 8 +- .../proofs/protocol/v2/V2ProofService.ts | 17 ++-- .../v2/handlers/V2PresentationHandler.ts | 7 +- .../handlers/V2ProposePresentationHandler.ts | 7 +- .../handlers/V2RequestPresentationHandler.ts | 8 +- .../migration/__tests__/backup.test.ts | 6 +- packages/core/src/utils/MessageValidator.ts | 6 +- .../src/utils/__tests__/deepEquality.test.ts | 82 +++++++++++++++++ .../src/utils/__tests__/objectCheck.test.ts | 34 ------- .../src/utils/__tests__/shortenedUrl.test.ts | 4 +- packages/core/src/utils/deepEquality.ts | 55 +++++++++++ packages/core/src/utils/index.ts | 2 + packages/core/src/utils/objEqual.ts | 23 ----- packages/core/src/utils/objectCheck.ts | 70 -------------- packages/core/src/utils/objectEquality.ts | 19 ++++ packages/core/src/utils/parseInvitation.ts | 4 +- .../core/tests/v1-proofs-auto-accept.test.ts | 83 ++++++----------- .../core/tests/v2-proofs-auto-accept.test.ts | 91 +++++++------------ 25 files changed, 291 insertions(+), 289 deletions(-) create mode 100644 packages/core/src/utils/__tests__/deepEquality.test.ts delete mode 100644 packages/core/src/utils/__tests__/objectCheck.test.ts create mode 100644 packages/core/src/utils/deepEquality.ts delete mode 100644 packages/core/src/utils/objEqual.ts delete mode 100644 packages/core/src/utils/objectCheck.ts create mode 100644 packages/core/src/utils/objectEquality.ts diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts index b6c0d6c6c5..e8fdd4f329 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts @@ -24,9 +24,9 @@ import type { JsonLdOptionsRFC0593 } from './JsonLdOptionsRFC0593' import { injectable } from 'tsyringe' import { AriesFrameworkError } from '../../../../error' +import { objectEquality } from '../../../../utils' import { JsonTransformer } from '../../../../utils/JsonTransformer' import { MessageValidator } from '../../../../utils/MessageValidator' -import { deepEqual } from '../../../../utils/objEqual' import { findVerificationMethodByKeyType } from '../../../dids/domain/DidDocument' import { DidResolverService } from '../../../dids/services/DidResolverService' import { W3cCredentialService } from '../../../vc' @@ -344,7 +344,7 @@ export class JsonLdCredentialFormatService extends CredentialFormatService { + ) { const { credentialRecord, proposalMessage } = options const autoAccept = composeAutoAccept( credentialRecord.autoAcceptCredential, diff --git a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts index 8996649c4f..77c9a04fd5 100644 --- a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts +++ b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts @@ -34,7 +34,7 @@ export class ProofResponseCoordinator { /** * Checks whether it should automatically respond to a proposal */ - public shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { + public async shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, agentContext.config.autoAcceptProofs @@ -54,7 +54,7 @@ export class ProofResponseCoordinator { /** * Checks whether it should automatically respond to a request */ - public shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { + public async shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, agentContext.config.autoAcceptProofs @@ -74,7 +74,7 @@ export class ProofResponseCoordinator { /** * Checks whether it should automatically respond to a presentation of proof */ - public shouldAutoRespondToPresentation(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { + public async shouldAutoRespondToPresentation(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { const autoAccept = ProofResponseCoordinator.composeAutoAccept( proofRecord.autoAcceptProof, agentContext.config.autoAcceptProofs diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts index 12641a4fb4..1aec763e65 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts @@ -31,11 +31,10 @@ import { Attachment, AttachmentData } from '../../../../decorators/attachment/At import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' import { ConsoleLogger, LogLevel } from '../../../../logger' import { DidCommMessageRepository } from '../../../../storage/didcomm/DidCommMessageRepository' -import { checkProofRequestForDuplicates } from '../../../../utils' +import { checkProofRequestForDuplicates, deepEquality } from '../../../../utils' import { JsonEncoder } from '../../../../utils/JsonEncoder' import { JsonTransformer } from '../../../../utils/JsonTransformer' import { MessageValidator } from '../../../../utils/MessageValidator' -import { objectEquals } from '../../../../utils/objectCheck' import { uuid } from '../../../../utils/uuid' import { IndyWallet } from '../../../../wallet/IndyWallet' import { IndyCredential, IndyCredentialInfo } from '../../../credentials' @@ -121,7 +120,7 @@ export class IndyProofFormatService extends ProofFormatService { }) const request = new ProofRequest(options.proofProposalOptions) - await MessageValidator.validateSync(request) + MessageValidator.validateSync(request) const attachment = new Attachment({ id: options.id, @@ -157,7 +156,7 @@ export class IndyProofFormatService extends ProofFormatService { const proposalMessage = JsonTransformer.fromJSON(proofProposalJson, ProofRequest) - await MessageValidator.validateSync(proposalMessage) + MessageValidator.validateSync(proposalMessage) } public async createRequestAsResponse( @@ -213,7 +212,7 @@ export class IndyProofFormatService extends ProofFormatService { `Missing required base64 or json encoded attachment data for presentation request with thread id ${options.record?.threadId}` ) } - await MessageValidator.validateSync(proofRequest) + MessageValidator.validateSync(proofRequest) // Assert attribute and predicate (group) names do not match checkProofRequestForDuplicates(proofRequest) @@ -356,8 +355,8 @@ export class IndyProofFormatService extends ProofFormatService { const requestAttachmentData = JsonTransformer.fromJSON(requestAttachmentJson, ProofRequest) if ( - objectEquals(proposalAttachmentData.requestedAttributes, requestAttachmentData.requestedAttributes) && - objectEquals(proposalAttachmentData.requestedPredicates, requestAttachmentData.requestedPredicates) + deepEquality(proposalAttachmentData.requestedAttributes, requestAttachmentData.requestedAttributes) && + deepEquality(proposalAttachmentData.requestedPredicates, requestAttachmentData.requestedPredicates) ) { return true } @@ -606,7 +605,7 @@ export class IndyProofFormatService extends ProofFormatService { if (!proofRequest) { throw new AriesFrameworkError(`Missing required base64 or json encoded attachment data for presentation request.`) } - await MessageValidator.validateSync(proofRequest) + MessageValidator.validateSync(proofRequest) // Assert attribute and predicate (group) names do not match checkProofRequestForDuplicates(proofRequest) diff --git a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts index dc572f431b..97f49bb572 100644 --- a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts @@ -764,10 +764,8 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { messageClass: V1ProposePresentationMessage, }) - if (!proposal) { - return false - } - await MessageValidator.validateSync(proposal) + if (!proposal) return false + MessageValidator.validateSync(proposal) // check the proposal against a possible previous request const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { @@ -775,9 +773,7 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { messageClass: V1RequestPresentationMessage, }) - if (!request) { - return false - } + if (!request) return false const proofRequest = request.indyProofRequest diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts index c3fcd713d2..7ecd43d6ee 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts @@ -30,7 +30,12 @@ export class V1PresentationHandler implements Handler { public async handle(messageContext: HandlerInboundMessage) { const proofRecord = await this.proofService.processPresentation(messageContext) - if (this.proofResponseCoordinator.shouldAutoRespondToPresentation(messageContext.agentContext, proofRecord)) { + const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToPresentation( + messageContext.agentContext, + proofRecord + ) + + if (shouldAutoRespond) { return await this.createAck(proofRecord, messageContext) } } diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts index 4e9d437669..bec5d693b0 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts @@ -33,7 +33,13 @@ export class V1ProposePresentationHandler implements Handler { public async handle(messageContext: HandlerInboundMessage) { const proofRecord = await this.proofService.processProposal(messageContext) - if (this.proofResponseCoordinator.shouldAutoRespondToProposal(messageContext.agentContext, proofRecord)) { + + const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToProposal( + messageContext.agentContext, + proofRecord + ) + + if (shouldAutoRespond) { return await this.createRequest(proofRecord, messageContext) } } diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts index 5a0455baba..addfdb3a6c 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts @@ -44,7 +44,13 @@ export class V1RequestPresentationHandler implements Handler { public async handle(messageContext: HandlerInboundMessage) { const proofRecord = await this.proofService.processRequest(messageContext) - if (this.proofResponseCoordinator.shouldAutoRespondToRequest(messageContext.agentContext, proofRecord)) { + + const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToRequest( + messageContext.agentContext, + proofRecord + ) + + if (shouldAutoRespond) { return await this.createPresentation(proofRecord, messageContext) } } diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts index 38842b31a4..7ef492d4bb 100644 --- a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts @@ -545,7 +545,7 @@ export class V2ProofService extends P ) } - const msg = new V2PresentationAckMessage({ + const message = new V2PresentationAckMessage({ threadId: options.proofRecord.threadId, status: AckStatus.OK, }) @@ -553,7 +553,7 @@ export class V2ProofService extends P await this.updateState(agentContext, options.proofRecord, ProofState.Done) return { - message: msg, + message, proofRecord: options.proofRecord, } } @@ -681,17 +681,16 @@ export class V2ProofService extends P messageClass: V2ProposalPresentationMessage, }) - if (!proposal) { - return false - } + if (!proposal) return false + const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: proofRecord.id, messageClass: V2RequestPresentationMessage, }) - if (!request) { - return true - } - await MessageValidator.validateSync(proposal) + + if (!request) return false + + MessageValidator.validateSync(proposal) const proposalAttachments = proposal.getAttachmentFormats() const requestAttachments = request.getAttachmentFormats() diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts index c812b6e8bd..a0f2b3e5d1 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts @@ -30,7 +30,12 @@ export class V2PresentationHandler implements Handler { public async handle(messageContext: HandlerInboundMessage) { const proofRecord = await this.proofService.processPresentation(messageContext) - if (this.proofResponseCoordinator.shouldAutoRespondToPresentation(messageContext.agentContext, proofRecord)) { + const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToPresentation( + messageContext.agentContext, + proofRecord + ) + + if (shouldAutoRespond) { return await this.createAck(proofRecord, messageContext) } } diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts index e822ed7a32..bbbf3f2b38 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts @@ -37,7 +37,12 @@ export class V2ProposePresentationHandler) { const proofRecord = await this.proofService.processProposal(messageContext) - if (this.proofResponseCoordinator.shouldAutoRespondToProposal(messageContext.agentContext, proofRecord)) { + const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToProposal( + messageContext.agentContext, + proofRecord + ) + + if (shouldAutoRespond) { return this.createRequest(proofRecord, messageContext) } } diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts index 5c6bc970e7..fb8c7767d4 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts @@ -43,7 +43,13 @@ export class V2RequestPresentationHandler) { const proofRecord = await this.proofService.processRequest(messageContext) - if (this.proofResponseCoordinator.shouldAutoRespondToRequest(messageContext.agentContext, proofRecord)) { + + const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToRequest( + messageContext.agentContext, + proofRecord + ) + + if (shouldAutoRespond) { return await this.createPresentation(proofRecord, messageContext) } } diff --git a/packages/core/src/storage/migration/__tests__/backup.test.ts b/packages/core/src/storage/migration/__tests__/backup.test.ts index 5bcf588afb..b1efe9b580 100644 --- a/packages/core/src/storage/migration/__tests__/backup.test.ts +++ b/packages/core/src/storage/migration/__tests__/backup.test.ts @@ -35,10 +35,12 @@ describe('UpdateAssistant | Backup', () => { backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` // If tests fail it's possible the cleanup has been skipped. So remove before running tests - if (await fileSystem.exists(backupPath)) { + const doesFileSystemExist = await fileSystem.exists(backupPath) + if (doesFileSystemExist) { unlinkSync(backupPath) } - if (await fileSystem.exists(`${backupPath}-error`)) { + const doesbackupFileSystemExist = await fileSystem.exists(`${backupPath}-error`) + if (doesbackupFileSystemExist) { unlinkSync(`${backupPath}-error`) } diff --git a/packages/core/src/utils/MessageValidator.ts b/packages/core/src/utils/MessageValidator.ts index 82e1afc408..386cb49377 100644 --- a/packages/core/src/utils/MessageValidator.ts +++ b/packages/core/src/utils/MessageValidator.ts @@ -7,13 +7,13 @@ export class MessageValidator { /** * * @param classInstance the class instance to validate - * @returns nothing + * @returns void * @throws array of validation errors {@link ValidationError} */ // eslint-disable-next-line @typescript-eslint/ban-types public static validateSync(classInstance: T & {}) { - // NOTE: validateSync (strangely) return an Array of errors so we - // have to transform that into an error of choice and throw that. + // NOTE: validateSync returns an Array of errors. + // We have to transform that into an error of choice and throw that. const errors = validateSync(classInstance) if (isValidationErrorArray(errors)) { throw new ClassValidationError('Failed to validate class.', { diff --git a/packages/core/src/utils/__tests__/deepEquality.test.ts b/packages/core/src/utils/__tests__/deepEquality.test.ts new file mode 100644 index 0000000000..645a35e329 --- /dev/null +++ b/packages/core/src/utils/__tests__/deepEquality.test.ts @@ -0,0 +1,82 @@ +import { Metadata } from '../../storage/Metadata' +import { deepEquality } from '../deepEquality' + +describe('deepEquality', () => { + test('evaluates to true with equal maps', () => { + const a = new Map([ + ['foo', 1], + ['bar', 2], + ['baz', 3], + ]) + const b = new Map([ + ['foo', 1], + ['bar', 2], + ['baz', 3], + ]) + expect(deepEquality(a, b)).toBe(true) + }) + test('evaluates to false with unequal maps', () => { + const c = new Map([ + ['foo', 1], + ['baz', 3], + ['bar', 2], + ]) + + const d = new Map([ + ['foo', 1], + ['bar', 2], + ['qux', 3], + ]) + expect(deepEquality(c, d)).toBe(false) + }) + + test('evaluates to true with equal maps with different order', () => { + const a = new Map([ + ['baz', 3], + ['bar', 2], + ['foo', 1], + ]) + const b = new Map([ + ['foo', 1], + ['bar', 2], + ['baz', 3], + ]) + expect(deepEquality(a, b)).toBe(true) + }) + test('evaluates to true with equal primitives', () => { + expect(deepEquality(1, 1)).toBe(true) + expect(deepEquality(true, true)).toBe(true) + expect(deepEquality('a', 'a')).toBe(true) + }) + + test('evaluates to false with unequal primitives', () => { + expect(deepEquality(1, 2)).toBe(false) + expect(deepEquality(true, false)).toBe(false) + expect(deepEquality('a', 'b')).toBe(false) + }) + + test('evaluates to true with equal complex types', () => { + const fn = () => 'hello World!' + expect(deepEquality(fn, fn)).toBe(true) + expect(deepEquality({}, {})).toBe(true) + expect(deepEquality({ foo: 'bar' }, { foo: 'bar' })).toBe(true) + expect(deepEquality({ foo: 'baz', bar: 'bar' }, { bar: 'bar', foo: 'baz' })).toBe(true) + expect(deepEquality(Metadata, Metadata)).toBe(true) + expect(deepEquality(new Metadata({}), new Metadata({}))).toBe(true) + }) + + test('evaluates to false with unequal complex types', () => { + const fn = () => 'hello World!' + const fnTwo = () => 'Goodbye World!' + class Bar { + public constructor() { + 'yes' + } + } + expect(deepEquality(fn, fnTwo)).toBe(false) + expect(deepEquality({ bar: 'foo' }, { a: 'b' })).toBe(false) + expect(deepEquality({ b: 'a' }, { b: 'a', c: 'd' })).toBe(false) + expect(deepEquality(Metadata, Bar)).toBe(false) + expect(deepEquality(new Metadata({}), new Bar())).toBe(false) + }) +}) diff --git a/packages/core/src/utils/__tests__/objectCheck.test.ts b/packages/core/src/utils/__tests__/objectCheck.test.ts deleted file mode 100644 index d2517c2d0e..0000000000 --- a/packages/core/src/utils/__tests__/objectCheck.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { objectEquals } from '../objectCheck' - -describe('objectEquals', () => { - it('correctly evaluates whether two map objects are equal', () => { - const mapA: Map = new Map([ - ['foo', 1], - ['bar', 2], - ['baz', 3], - ]) - const mapB: Map = new Map([ - ['foo', 1], - ['bar', 2], - ['baz', 3], - ]) - let retVal = objectEquals(mapA, mapB) - - // if the order is different, maps still equal - const mapC: Map = new Map([ - ['foo', 1], - ['baz', 3], - ['bar', 2], - ]) - retVal = objectEquals(mapA, mapC) - expect(retVal).toBe(true) - - const mapD: Map = new Map([ - ['foo', 1], - ['bar', 2], - ['qux', 3], - ]) - retVal = objectEquals(mapA, mapD) - expect(retVal).toBe(false) - }) -}) diff --git a/packages/core/src/utils/__tests__/shortenedUrl.test.ts b/packages/core/src/utils/__tests__/shortenedUrl.test.ts index 5e79621e96..55180a141d 100644 --- a/packages/core/src/utils/__tests__/shortenedUrl.test.ts +++ b/packages/core/src/utils/__tests__/shortenedUrl.test.ts @@ -81,9 +81,9 @@ let connectionInvitationToNew: OutOfBandInvitation beforeAll(async () => { outOfBandInvitationMock = await JsonTransformer.fromJSON(mockOobInvite, OutOfBandInvitation) - await MessageValidator.validateSync(outOfBandInvitationMock) + MessageValidator.validateSync(outOfBandInvitationMock) connectionInvitationMock = await JsonTransformer.fromJSON(mockConnectionInvite, ConnectionInvitationMessage) - await MessageValidator.validateSync(connectionInvitationMock) + MessageValidator.validateSync(connectionInvitationMock) connectionInvitationToNew = convertToNewInvitation(connectionInvitationMock) }) diff --git a/packages/core/src/utils/deepEquality.ts b/packages/core/src/utils/deepEquality.ts new file mode 100644 index 0000000000..0ebfae7971 --- /dev/null +++ b/packages/core/src/utils/deepEquality.ts @@ -0,0 +1,55 @@ +import { objectEquality } from './objectEquality' + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function deepEquality(x: any, y: any): boolean { + // We do a simple equals here to check primitives, functions, regex, etc. + // This will only happen if the typing of the function is ignored + const isXSimpleEqualY = simpleEqual(x, y) + if (isXSimpleEqualY !== undefined) return isXSimpleEqualY + + if (!(x instanceof Map) || !(y instanceof Map)) return objectEquality(x, y) + + const xMap = x as Map + const yMap = y as Map + + // At this point we are sure we have two instances of a Map + const xKeys = Array.from(xMap.keys()) + const yKeys = Array.from(yMap.keys()) + + // Keys from both maps are not equal, content has not been verified, yet + if (!equalsIgnoreOrder(xKeys, yKeys)) return false + + // Here we recursively check whether the value of xMap is equals to the value of yMap + return Array.from(xMap.entries()).every(([key, xVal]) => deepEquality(xVal, yMap.get(key))) +} + +/** + * @note This will only work for primitive array equality + */ +function equalsIgnoreOrder(a: Array, b: Array): boolean { + if (a.length !== b.length) return false + return a.every((k) => b.includes(k)) +} + +// We take any here as we have to check some properties, they will be undefined if they do not exist +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function simpleEqual(x: any, y: any) { + // short circuit for easy equality + if (x === y) return true + + if ((x === null || x === undefined) && (y === null || y === undefined)) return x === y + + // after this just checking type of one would be enough + if (x.constructor !== y.constructor) return false + + // if they are functions, they should exactly refer to same one (because of closures) + if (x instanceof Function) return x === y + + // if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES) + if (x instanceof RegExp) return x === y + + if (x.valueOf && y.valueOf && x.valueOf() === y.valueOf()) return true + + // if they are dates, they must had equal valueOf + if (x instanceof Date || y instanceof Date) return false +} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index cb4f5d92e7..c0dfd135df 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -12,3 +12,5 @@ export * from './Hasher' export * from './validators' export * from './type' export * from './indyIdentifiers' +export * from './deepEquality' +export * from './objectEquality' diff --git a/packages/core/src/utils/objEqual.ts b/packages/core/src/utils/objEqual.ts deleted file mode 100644 index 2909b5c450..0000000000 --- a/packages/core/src/utils/objEqual.ts +++ /dev/null @@ -1,23 +0,0 @@ -export function deepEqual(a: any, b: any): boolean { - if (typeof a == 'object' && a != null && typeof b == 'object' && b != null) { - const count = [0, 0] - for (const key in a) count[0]++ - for (const key in b) count[1]++ - if (count[0] - count[1] != 0) { - return false - } - for (const key in a) { - if (!(key in b) || !deepEqual(a[key], b[key])) { - return false - } - } - for (const key in b) { - if (!(key in a) || !deepEqual(b[key], a[key])) { - return false - } - } - return true - } else { - return a === b - } -} diff --git a/packages/core/src/utils/objectCheck.ts b/packages/core/src/utils/objectCheck.ts deleted file mode 100644 index a511b28df6..0000000000 --- a/packages/core/src/utils/objectCheck.ts +++ /dev/null @@ -1,70 +0,0 @@ -export function objectEquals(x: Map, y: Map): boolean { - if (x === null || x === undefined || y === null || y === undefined) { - return x === y - } - - // after this just checking type of one would be enough - if (x.constructor !== y.constructor) { - return false - } - // if they are functions, they should exactly refer to same one (because of closures) - if (x instanceof Function) { - return x === y - } - // if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES) - if (x instanceof RegExp) { - return x === y - } - if (x === y || x.valueOf() === y.valueOf()) { - return true - } - - // if they are dates, they must had equal valueOf - if (x instanceof Date) { - return false - } - - // if they are strictly equal, they both need to be object at least - if (!(x instanceof Object)) { - return false - } - if (!(y instanceof Object)) { - return false - } - - const xkeys = Array.from(x.keys()) - const ykeys = Array.from(y.keys()) - if (!equalsIgnoreOrder(xkeys, ykeys)) { - return false - } - return ( - xkeys.every(function (i) { - return xkeys.indexOf(i) !== -1 - }) && - xkeys.every(function (xkey) { - // get the x map entries for this key - - const xval: any = x.get(xkey) - const yval: any = y.get(xkey) - - const a: Map = new Map([[xkey, xval]]) - if (a.size === 1) { - return xval === yval - } - // get the y map entries for this key - const b: Map = new Map([[xkey, yval]]) - return objectEquals(a, b) - }) - ) -} - -function equalsIgnoreOrder(a: string[], b: string[]): boolean { - if (a.length !== b.length) return false - const uniqueValues = new Set([...a, ...b]) - for (const v of uniqueValues) { - const aCount = a.filter((e) => e === v).length - const bCount = b.filter((e) => e === v).length - if (aCount !== bCount) return false - } - return true -} diff --git a/packages/core/src/utils/objectEquality.ts b/packages/core/src/utils/objectEquality.ts new file mode 100644 index 0000000000..9d0a18e0b3 --- /dev/null +++ b/packages/core/src/utils/objectEquality.ts @@ -0,0 +1,19 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function objectEquality(a: any, b: any): boolean { + if (typeof a == 'object' && a != null && typeof b == 'object' && b != null) { + if (Object.keys(a).length !== Object.keys(b).length) return false + for (const key in a) { + if (!(key in b) || !objectEquality(a[key], b[key])) { + return false + } + } + for (const key in b) { + if (!(key in a) || !objectEquality(b[key], a[key])) { + return false + } + } + return true + } else { + return a === b + } +} diff --git a/packages/core/src/utils/parseInvitation.ts b/packages/core/src/utils/parseInvitation.ts index 6f3c9e8f3b..6af07072e2 100644 --- a/packages/core/src/utils/parseInvitation.ts +++ b/packages/core/src/utils/parseInvitation.ts @@ -61,11 +61,11 @@ export const oobInvitationFromShortUrl = async (response: Response): Promise { test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `always`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.Done, - }) - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - state: ProofState.Done, - }) - await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v1', @@ -63,10 +55,11 @@ describe('Auto accept present proof', () => { }) testLogger.test('Faber waits for presentation from Alice') - await faberProofExchangeRecordPromise - testLogger.test('Alice waits till it receives presentation ack') - await aliceProofExchangeRecordPromise + await Promise.all([ + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + ]) }) test('Faber starts with proof requests to Alice, both with autoAcceptProof on `always`', async () => { @@ -94,14 +87,6 @@ describe('Auto accept present proof', () => { }), } - const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.Done, - }) - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - state: ProofState.Done, - }) - await faberAgent.proofs.requestProof({ protocolVersion: 'v1', connectionId: faberConnection.id, @@ -117,11 +102,10 @@ describe('Auto accept present proof', () => { }) testLogger.test('Faber waits for presentation from Alice') - - await faberProofExchangeRecordPromise - - // Alice waits till it receives presentation ack - await aliceProofExchangeRecordPromise + await Promise.all([ + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + ]) }) }) @@ -146,10 +130,6 @@ describe('Auto accept present proof', () => { test('Alice starts with proof proposal to Faber, both with autoacceptproof on `contentApproved`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v1', @@ -165,26 +145,18 @@ describe('Auto accept present proof', () => { }) testLogger.test('Faber waits for presentation proposal from Alice') - - await faberProofExchangeRecordPromise - - testLogger.test('Faber accepts presentation proposal from Alice') - - faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.Done, - }) - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + const faberProofExchangeRecord = await waitForProofExchangeRecord(faberAgent, { threadId: aliceProofExchangeRecord.threadId, - state: ProofState.Done, + state: ProofState.ProposalReceived, }) - testLogger.test('Faber waits for presentation from Alice') + testLogger.test('Faber accepts presentation proposal from Alice') + await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofExchangeRecord.id }) - await faberProofExchangeRecordPromise - // Alice waits till it receives presentation ack - await aliceProofExchangeRecordPromise + await Promise.all([ + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + ]) }) test('Faber starts with proof requests to Alice, both with autoacceptproof on `contentApproved`', async () => { @@ -212,14 +184,6 @@ describe('Auto accept present proof', () => { }), } - const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.Done, - }) - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - state: ProofState.Done, - }) - await faberAgent.proofs.requestProof({ protocolVersion: 'v1', connectionId: faberConnection.id, @@ -234,11 +198,18 @@ describe('Auto accept present proof', () => { }, }) - testLogger.test('Faber waits for presentation from Alice') - await faberProofExchangeRecordPromise + testLogger.test('Alice waits for request from Faber') + const { id: proofRecordId } = await waitForProofExchangeRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + const { proofFormats } = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ proofRecordId }) + await aliceAgent.proofs.acceptRequest({ proofRecordId, proofFormats }) - // Alice waits till it receives presentation ack - await aliceProofExchangeRecordPromise + await Promise.all([ + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + ]) }) }) }) diff --git a/packages/core/tests/v2-proofs-auto-accept.test.ts b/packages/core/tests/v2-proofs-auto-accept.test.ts index aa58d430d5..0ab3d81e13 100644 --- a/packages/core/tests/v2-proofs-auto-accept.test.ts +++ b/packages/core/tests/v2-proofs-auto-accept.test.ts @@ -40,14 +40,6 @@ describe('Auto accept present proof', () => { test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `always`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.Done, - }) - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - state: ProofState.Done, - }) - await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v2', @@ -63,10 +55,11 @@ describe('Auto accept present proof', () => { }) testLogger.test('Faber waits for presentation from Alice') - await faberProofExchangeRecordPromise - testLogger.test('Alice waits till it receives presentation ack') - await aliceProofExchangeRecordPromise + await Promise.all([ + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + ]) }) test('Faber starts with proof requests to Alice, both with autoAcceptProof on `always`', async () => { @@ -94,14 +87,6 @@ describe('Auto accept present proof', () => { }), } - const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.Done, - }) - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - state: ProofState.Done, - }) - await faberAgent.proofs.requestProof({ protocolVersion: 'v2', connectionId: faberConnection.id, @@ -116,10 +101,12 @@ describe('Auto accept present proof', () => { }, }) - testLogger.test('Faber waits for presentation from Alice') - await faberProofExchangeRecordPromise - // Alice waits till it receives presentation ack - await aliceProofExchangeRecordPromise + testLogger.test('Alice waits for presentation from Faber') + testLogger.test('Faber waits till it receives presentation ack') + await Promise.all([ + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + ]) }) }) @@ -141,14 +128,10 @@ describe('Auto accept present proof', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with proof proposal to Faber, both with autoacceptproof on `contentApproved`', async () => { + test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `contentApproved`', async () => { testLogger.test('Alice sends presentation proposal to Faber') - let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v2', proofFormats: { @@ -162,30 +145,20 @@ describe('Auto accept present proof', () => { }, }) - testLogger.test('Faber waits for presentation proposal from Alice') - - await faberProofExchangeRecordPromise - - testLogger.test('Faber accepts presentation proposal from Alice') - - faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.Done, - }) - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.Done, + const { id: proofRecordId } = await waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, }) - testLogger.test('Faber waits for presentation from Alice') + testLogger.test('Faber accepts presentation proposal from Alice') + await faberAgent.proofs.acceptProposal({ proofRecordId }) - await faberProofExchangeRecordPromise - // Alice waits till it receives presentation ack - await aliceProofExchangeRecordPromise + await Promise.all([ + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + ]) }) - test('Faber starts with proof requests to Alice, both with autoacceptproof on `contentApproved`', async () => { + test('Faber starts with proof requests to Alice, both with autoAcceptProof on `contentApproved`', async () => { testLogger.test('Faber sends presentation request to Alice') const attributes = { name: new ProofAttributeInfo({ @@ -210,14 +183,6 @@ describe('Auto accept present proof', () => { }), } - const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.Done, - }) - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - state: ProofState.Done, - }) - await faberAgent.proofs.requestProof({ protocolVersion: 'v2', connectionId: faberConnection.id, @@ -232,11 +197,17 @@ describe('Auto accept present proof', () => { }, }) - testLogger.test('Faber waits for presentation from Alice') - await faberProofExchangeRecordPromise + testLogger.test('Alice waits for request from Faber') + const { id: proofRecordId } = await waitForProofExchangeRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + const { proofFormats } = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ proofRecordId }) + await aliceAgent.proofs.acceptRequest({ proofRecordId, proofFormats }) - // Alice waits till it receives presentation ack - await aliceProofExchangeRecordPromise + await Promise.all([ + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + ]) }) }) }) From 157a357b192b73ea738b5fdf23a5776e7b69c6ce Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 1 Dec 2022 17:07:02 +0100 Subject: [PATCH 083/125] refactor(oob)!: merge oob invitation parsing (#1134) --- packages/core/src/agent/Agent.ts | 2 +- packages/core/src/modules/oob/OutOfBandApi.ts | 20 +++++-------------- packages/core/src/utils/parseInvitation.ts | 4 ++-- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 1d57029d15..133029bc81 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -211,7 +211,7 @@ export class Agent extends } protected async getMediationConnection(mediatorInvitationUrl: string) { - const outOfBandInvitation = this.oob.parseInvitation(mediatorInvitationUrl) + const outOfBandInvitation = await this.oob.parseInvitation(mediatorInvitationUrl) const outOfBandRecord = await this.oob.findByInvitationId(outOfBandInvitation.id) const [connection] = outOfBandRecord ? await this.connections.findAllByOutOfBandId(outOfBandRecord.id) : [] diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts index f66ad984d3..535d76135f 100644 --- a/packages/core/src/modules/oob/OutOfBandApi.ts +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -23,7 +23,7 @@ import { inject, injectable } from '../../plugins' import { DidCommMessageRepository, DidCommMessageRole } from '../../storage' import { JsonEncoder, JsonTransformer } from '../../utils' import { parseMessageType, supportsIncomingMessageType } from '../../utils/messageType' -import { parseInvitationUrl, parseInvitationShortUrl } from '../../utils/parseInvitation' +import { parseInvitationShortUrl } from '../../utils/parseInvitation' import { ConnectionsApi, DidExchangeState, HandshakeProtocol } from '../connections' import { DidCommDocumentService } from '../didcomm' import { DidKey } from '../dids' @@ -281,7 +281,7 @@ export class OutOfBandApi { * @returns out-of-band record and connection record if one has been created */ public async receiveInvitationFromUrl(invitationUrl: string, config: ReceiveOutOfBandInvitationConfig = {}) { - const message = await this.parseInvitationShortUrl(invitationUrl) + const message = await this.parseInvitation(invitationUrl) return this.receiveInvitation(message, config) } @@ -289,24 +289,14 @@ export class OutOfBandApi { /** * Parses URL containing encoded invitation and returns invitation message. * - * @param invitationUrl URL containing encoded invitation - * - * @returns OutOfBandInvitation - */ - public parseInvitation(invitationUrl: string): OutOfBandInvitation { - return parseInvitationUrl(invitationUrl) - } - - /** - * Parses URL containing encoded invitation and returns invitation message. Compatible with - * parsing shortened URLs + * Will fetch the url if the url does not contain a base64 encoded invitation. * * @param invitationUrl URL containing encoded invitation * * @returns OutOfBandInvitation */ - public async parseInvitationShortUrl(invitation: string): Promise { - return await parseInvitationShortUrl(invitation, this.agentContext.config.agentDependencies) + public async parseInvitation(invitationUrl: string): Promise { + return parseInvitationShortUrl(invitationUrl, this.agentContext.config.agentDependencies) } /** diff --git a/packages/core/src/utils/parseInvitation.ts b/packages/core/src/utils/parseInvitation.ts index 6af07072e2..3a908af7bd 100644 --- a/packages/core/src/utils/parseInvitation.ts +++ b/packages/core/src/utils/parseInvitation.ts @@ -53,7 +53,7 @@ export const parseInvitationUrl = (invitationUrl: string): OutOfBandInvitation = ) } -//This currently does not follow the RFC because of issues with fetch, currently uses a janky work around +// This currently does not follow the RFC because of issues with fetch, currently uses a janky work around export const oobInvitationFromShortUrl = async (response: Response): Promise => { if (response) { if (response.headers.get('Content-Type')?.startsWith('application/json') && response.ok) { @@ -90,7 +90,7 @@ export const oobInvitationFromShortUrl = async (response: Response): Promise Date: Wed, 7 Dec 2022 22:43:11 -0300 Subject: [PATCH 084/125] fix: expose AttachmentData and DiscoverFeaturesEvents (#1146) Signed-off-by: Ariel Gentile --- packages/core/src/agent/models/OutboundMessageContext.ts | 1 + packages/core/src/index.ts | 2 +- packages/core/src/modules/discover-features/index.ts | 1 + packages/core/src/types.ts | 6 ------ 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/core/src/agent/models/OutboundMessageContext.ts b/packages/core/src/agent/models/OutboundMessageContext.ts index e61929a47d..465b339eb2 100644 --- a/packages/core/src/agent/models/OutboundMessageContext.ts +++ b/packages/core/src/agent/models/OutboundMessageContext.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { Key } from '../../crypto' import type { ConnectionRecord } from '../../modules/connections' import type { ResolvedDidCommService } from '../../modules/didcomm' diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1003c073b5..bc90ed89bc 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -36,7 +36,7 @@ export { InjectionSymbols } from './constants' export * from './wallet' export type { TransportSession } from './agent/TransportService' export { TransportService } from './agent/TransportService' -export { Attachment } from './decorators/attachment/Attachment' +export { Attachment, AttachmentData } from './decorators/attachment/Attachment' export { ReturnRouteTypes } from './decorators/transport/TransportDecorator' export * from './plugins' diff --git a/packages/core/src/modules/discover-features/index.ts b/packages/core/src/modules/discover-features/index.ts index cfebfc79c6..acdaae6633 100644 --- a/packages/core/src/modules/discover-features/index.ts +++ b/packages/core/src/modules/discover-features/index.ts @@ -1,3 +1,4 @@ export * from './DiscoverFeaturesApi' +export * from './DiscoverFeaturesEvents' export * from './DiscoverFeaturesModule' export * from './protocol' diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 6d409b000e..66b1e0856a 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,14 +1,8 @@ -import type { AgentMessage } from './agent/AgentMessage' -import type { Key } from './crypto' import type { Logger } from './logger' -import type { ConnectionRecord } from './modules/connections' import type { AutoAcceptCredential } from './modules/credentials/models/CredentialAutoAcceptType' -import type { ResolvedDidCommService } from './modules/didcomm' import type { IndyPoolConfig } from './modules/ledger/IndyPool' -import type { OutOfBandRecord } from './modules/oob/repository' import type { AutoAcceptProof } from './modules/proofs' import type { MediatorPickupStrategy } from './modules/routing' -import type { BaseRecord } from './storage/BaseRecord' export enum KeyDerivationMethod { /** default value in indy-sdk. Will be used when no value is provided */ From 9e242c97bf3c4c45e1885faa2493c1106465e2a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Dec 2022 04:50:41 +0000 Subject: [PATCH 085/125] build(deps): bump decode-uri-component from 0.2.0 to 0.2.2 (#1139) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 030c9671e1..3bd2eab136 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4353,9 +4353,9 @@ decimal.js@^10.2.1: integrity sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw== decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== dedent@^0.7.0: version "0.7.0" From 9f10da85d8739f7be6c5e6624ba5f53a1d6a3116 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Fri, 9 Dec 2022 10:04:57 -0300 Subject: [PATCH 086/125] feat!: use did:key in protocols by default (#1149) Signed-off-by: Ariel Gentile BREAKING CHANGE: `useDidKeyInProtocols` configuration parameter is now enabled by default. If your agent only interacts with modern agents (e.g. AFJ 0.2.5 and newer) this will not represent any issue. Otherwise it is safer to explicitly set it to `false`. However, keep in mind that we expect this setting to be deprecated in the future, so we encourage you to update all your agents to use did:key. --- packages/core/src/agent/AgentConfig.ts | 2 +- .../src/modules/routing/__tests__/mediation.test.ts | 6 +++--- .../services/__tests__/MediatorService.test.ts | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index 5e20051ff8..29ee8e8add 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -125,7 +125,7 @@ export class AgentConfig { * in a given protocol (i.e. it does not support Aries RFC 0360). */ public get useDidKeyInProtocols() { - return this.initConfig.useDidKeyInProtocols ?? false + return this.initConfig.useDidKeyInProtocols ?? true } public get endpoints(): [string, ...string[]] { diff --git a/packages/core/src/modules/routing/__tests__/mediation.test.ts b/packages/core/src/modules/routing/__tests__/mediation.test.ts index 04b2b32ab3..7c9bfab59f 100644 --- a/packages/core/src/modules/routing/__tests__/mediation.test.ts +++ b/packages/core/src/modules/routing/__tests__/mediation.test.ts @@ -158,17 +158,17 @@ describe('mediator establishment', () => { }) }) - test('Mediation end-to-end flow (use did:key in both sides)', async () => { + test('Mediation end-to-end flow (not using did:key)', async () => { await e2eMediationTest( { - config: { ...mediatorAgentOptions.config, useDidKeyInProtocols: true }, + config: { ...mediatorAgentOptions.config, useDidKeyInProtocols: false }, dependencies: mediatorAgentOptions.dependencies, }, { config: { ...recipientAgentOptions.config, mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, - useDidKeyInProtocols: true, + useDidKeyInProtocols: false, }, dependencies: recipientAgentOptions.dependencies, } diff --git a/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts index 531f4fe608..017de44042 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediatorService.test.ts @@ -45,7 +45,7 @@ describe('MediatorService - default config', () => { ) describe('createGrantMediationMessage', () => { - test('sends base58 encoded recipient keys by default', async () => { + test('sends did:key encoded recipient keys by default', async () => { const mediationRecord = new MediationRecord({ connectionId: 'connectionId', role: MediationRole.Mediator, @@ -64,7 +64,7 @@ describe('MediatorService - default config', () => { const { message } = await mediatorService.createGrantMediationMessage(agentContext, mediationRecord) expect(message.routingKeys.length).toBe(1) - expect(isDidKey(message.routingKeys[0])).toBeFalsy() + expect(isDidKey(message.routingKeys[0])).toBeTruthy() }) }) @@ -155,8 +155,8 @@ describe('MediatorService - default config', () => { }) }) -describe('MediatorService - useDidKeyInProtocols set to true', () => { - const agentConfig = getAgentConfig('MediatorService', { useDidKeyInProtocols: true }) +describe('MediatorService - useDidKeyInProtocols set to false', () => { + const agentConfig = getAgentConfig('MediatorService', { useDidKeyInProtocols: false }) const agentContext = getAgentContext({ agentConfig, @@ -171,7 +171,7 @@ describe('MediatorService - useDidKeyInProtocols set to true', () => { ) describe('createGrantMediationMessage', () => { - test('sends did:key encoded recipient keys when config is set', async () => { + test('sends base58 encoded recipient keys when config is set', async () => { const mediationRecord = new MediationRecord({ connectionId: 'connectionId', role: MediationRole.Mediator, @@ -190,7 +190,7 @@ describe('MediatorService - useDidKeyInProtocols set to true', () => { const { message } = await mediatorService.createGrantMediationMessage(agentContext, mediationRecord) expect(message.routingKeys.length).toBe(1) - expect(isDidKey(message.routingKeys[0])).toBeTruthy() + expect(isDidKey(message.routingKeys[0])).toBeFalsy() }) }) }) From 3c040b68e0c8a7f5625df427a2ace28f0223bfbc Mon Sep 17 00:00:00 2001 From: Mo <10432473+morrieinmaas@users.noreply.github.com> Date: Sun, 11 Dec 2022 10:30:06 +0100 Subject: [PATCH 087/125] fix: expose OutOfBandEvents (#1151) Signed-off-by: Moriarty --- packages/core/src/modules/oob/domain/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/modules/oob/domain/index.ts b/packages/core/src/modules/oob/domain/index.ts index 43c069cafb..b49b6338b3 100644 --- a/packages/core/src/modules/oob/domain/index.ts +++ b/packages/core/src/modules/oob/domain/index.ts @@ -1,3 +1,4 @@ export * from './OutOfBandRole' export * from './OutOfBandState' export * from './OutOfBandDidCommService' +export * from './OutOfBandEvents' From 36d465669c6714b00167b17fe2924f3c53b5fa68 Mon Sep 17 00:00:00 2001 From: Karim Stekelenburg Date: Mon, 12 Dec 2022 10:13:54 +0100 Subject: [PATCH 088/125] fix: use custom document loader in jsonld.frame (#1119) Fixes https://github.com/hyperledger/aries-framework-javascript/issues/1111 Signed-off-by: Karim Stekelenburg --- .../modules/vc/__tests__/contexts/bbs_v1.ts | 41 ++++++++++++++++++- .../modules/vc/__tests__/documentLoader.ts | 21 +++++++--- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/packages/core/src/modules/vc/__tests__/contexts/bbs_v1.ts b/packages/core/src/modules/vc/__tests__/contexts/bbs_v1.ts index d7fa000420..897de0a4eb 100644 --- a/packages/core/src/modules/vc/__tests__/contexts/bbs_v1.ts +++ b/packages/core/src/modules/vc/__tests__/contexts/bbs_v1.ts @@ -51,6 +51,7 @@ export const BBS_V1 = { '@protected': true, id: '@id', type: '@type', + challenge: 'https://w3id.org/security#challenge', created: { '@id': 'http://purl.org/dc/terms/created', @@ -86,7 +87,43 @@ export const BBS_V1 = { }, }, }, - Bls12381G1Key2020: 'https://w3id.org/security#Bls12381G1Key2020', - Bls12381G2Key2020: 'https://w3id.org/security#Bls12381G2Key2020', + Bls12381G1Key2020: { + '@id': 'https://w3id.org/security#Bls12381G1Key2020', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + controller: { + '@id': 'https://w3id.org/security#controller', + '@type': '@id', + }, + revoked: { + '@id': 'https://w3id.org/security#revoked', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + publicKeyBase58: { + '@id': 'https://w3id.org/security#publicKeyBase58', + }, + }, + }, + Bls12381G2Key2020: { + '@id': 'https://w3id.org/security#Bls12381G2Key2020', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + controller: { + '@id': 'https://w3id.org/security#controller', + '@type': '@id', + }, + revoked: { + '@id': 'https://w3id.org/security#revoked', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + publicKeyBase58: { + '@id': 'https://w3id.org/security#publicKeyBase58', + }, + }, + }, }, } diff --git a/packages/core/src/modules/vc/__tests__/documentLoader.ts b/packages/core/src/modules/vc/__tests__/documentLoader.ts index 6816beded2..ce0781ffac 100644 --- a/packages/core/src/modules/vc/__tests__/documentLoader.ts +++ b/packages/core/src/modules/vc/__tests__/documentLoader.ts @@ -67,6 +67,7 @@ export const DOCUMENTS = { DID_V1_CONTEXT_URL: DID_V1, CREDENTIALS_CONTEXT_V1_URL: CREDENTIALS_V1, SECURITY_CONTEXT_BBS_URL: BBS_V1, + 'https://w3id.org/security/suites/bls12381-2020/v1': BBS_V1, 'https://w3id.org/security/bbs/v1': BBS_V1, 'https://w3id.org/security/v1': SECURITY_V1, 'https://w3id.org/security/v2': SECURITY_V2, @@ -75,13 +76,14 @@ export const DOCUMENTS = { 'https://www.w3.org/2018/credentials/examples/v1': EXAMPLES_V1, 'https://www.w3.org/2018/credentials/v1': CREDENTIALS_V1, 'https://w3id.org/did/v1': DID_V1, + 'https://www.w3.org/ns/did/v1': DID_V1, 'https://w3id.org/citizenship/v1': CITIZENSHIP_V1, 'https://www.w3.org/ns/odrl.jsonld': ODRL, 'http://schema.org/': SCHEMA_ORG, 'https://w3id.org/vaccination/v1': VACCINATION_V1, } -export const customDocumentLoader = async (url: string): Promise => { +async function _customDocumentLoader(url: string): Promise { let result = DOCUMENTS[url] if (!result) { @@ -94,11 +96,16 @@ export const customDocumentLoader = async (url: string): Promise Date: Mon, 12 Dec 2022 17:35:18 +0530 Subject: [PATCH 089/125] refactor(proofs): remove proofrequest property (#1153) Signed-off-by: Tipu Singh resolves https://github.com/hyperledger/aries-framework-javascript/issues/1114 --- .../proofs/formats/indy/IndyProofFormatsServiceOptions.ts | 2 -- .../proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts index 93c40ec7e9..b1ff554453 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts @@ -6,7 +6,6 @@ import type { ProofExchangeRecord } from '../../repository/ProofExchangeRecord' import type { ProofAttributeInfo } from '.././indy/models/ProofAttributeInfo' import type { ProofPredicateInfo } from '.././indy/models/ProofPredicateInfo' import type { IndyRequestedCredentialsFormat } from './IndyProofFormat' -import type { ProofRequest } from './models/ProofRequest' export type IndyPresentationProofFormat = IndyRequestedCredentialsFormat @@ -18,7 +17,6 @@ export interface IndyRequestProofFormat { ver?: '1.0' | '2.0' requestedAttributes?: Record | Map requestedPredicates?: Record | Map - proofRequest?: ProofRequest } export interface IndyVerifyProofFormat { diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts index bec5d693b0..a3c220ed44 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts @@ -96,7 +96,6 @@ export class V1ProposePresentationHandler implements Handler { requestedAttributes: indyProofRequest.indy?.requestedAttributes, requestedPredicates: indyProofRequest.indy?.requestedPredicates, ver: indyProofRequest.indy?.ver, - proofRequest: indyProofRequest.indy?.proofRequest, nonce: indyProofRequest.indy?.nonce, }, }, From 979c69506996fb1853e200b53d052d474f497bf1 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Mon, 12 Dec 2022 10:01:46 -0300 Subject: [PATCH 090/125] fix(routing): add connection type on mediation grant (#1147) Signed-off-by: Ariel Gentile --- .../src/modules/connections/ConnectionsApi.ts | 23 +++--- .../__tests__/ConnectionService.test.ts | 51 ++++++++++++ .../repository/ConnectionRecord.ts | 4 +- .../connections/services/ConnectionService.ts | 24 +++++- .../services/MediationRecipientService.ts | 4 +- packages/core/tests/connections.test.ts | 77 ++++++++++++------- packages/core/tests/oob-mediation.test.ts | 19 +++-- 7 files changed, 147 insertions(+), 55 deletions(-) diff --git a/packages/core/src/modules/connections/ConnectionsApi.ts b/packages/core/src/modules/connections/ConnectionsApi.ts index a384132463..aad4beb170 100644 --- a/packages/core/src/modules/connections/ConnectionsApi.ts +++ b/packages/core/src/modules/connections/ConnectionsApi.ts @@ -253,10 +253,11 @@ export class ConnectionsApi { public async addConnectionType(connectionId: string, type: ConnectionType | string) { const record = await this.getById(connectionId) - const tags = (record.getTag('connectionType') as string[]) || ([] as string[]) - record.setTag('connectionType', [type, ...tags]) - await this.connectionService.update(this.agentContext, record) + await this.connectionService.addConnectionType(this.agentContext, record, type) + + return record } + /** * Removes the given tag from the given record found by connectionId, if the tag exists otherwise does nothing * @param connectionId @@ -266,15 +267,11 @@ export class ConnectionsApi { public async removeConnectionType(connectionId: string, type: ConnectionType | string) { const record = await this.getById(connectionId) - const tags = (record.getTag('connectionType') as string[]) || ([] as string[]) - - const newTags = tags.filter((value: string) => { - if (value != type) return value - }) - record.setTag('connectionType', [...newTags]) + await this.connectionService.removeConnectionType(this.agentContext, record, type) - await this.connectionService.update(this.agentContext, record) + return record } + /** * Gets the known connection types for the record matching the given connectionId * @param connectionId @@ -283,8 +280,8 @@ export class ConnectionsApi { */ public async getConnectionTypes(connectionId: string) { const record = await this.getById(connectionId) - const tags = record.getTag('connectionType') as string[] - return tags || null + + return this.connectionService.getConnectionTypes(record) } /** @@ -292,7 +289,7 @@ export class ConnectionsApi { * @param connectionTypes An array of connection types to query for a match for * @returns a promise of ab array of connection records */ - public async findAllByConnectionType(connectionTypes: [ConnectionType | string]) { + public async findAllByConnectionType(connectionTypes: Array) { return this.connectionService.findAllByConnectionType(this.agentContext, connectionTypes) } diff --git a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index 9299352fe6..e00e7d4522 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -968,4 +968,55 @@ describe('ConnectionService', () => { expect(result).toEqual(expect.arrayContaining(expected)) }) }) + + describe('connectionType', () => { + it('addConnectionType', async () => { + const connection = getMockConnection() + + await connectionService.addConnectionType(agentContext, connection, 'type-1') + let connectionTypes = await connectionService.getConnectionTypes(connection) + expect(connectionTypes).toMatchObject(['type-1']) + + await connectionService.addConnectionType(agentContext, connection, 'type-2') + await connectionService.addConnectionType(agentContext, connection, 'type-3') + + connectionTypes = await connectionService.getConnectionTypes(connection) + expect(connectionTypes.sort()).toMatchObject(['type-1', 'type-2', 'type-3'].sort()) + }) + + it('removeConnectionType - existing type', async () => { + const connection = getMockConnection() + + connection.setTag('connectionType', ['type-1', 'type-2', 'type-3']) + let connectionTypes = await connectionService.getConnectionTypes(connection) + expect(connectionTypes).toMatchObject(['type-1', 'type-2', 'type-3']) + + await connectionService.removeConnectionType(agentContext, connection, 'type-2') + connectionTypes = await connectionService.getConnectionTypes(connection) + expect(connectionTypes.sort()).toMatchObject(['type-1', 'type-3'].sort()) + }) + + it('removeConnectionType - type not existent', async () => { + const connection = getMockConnection() + + connection.setTag('connectionType', ['type-1', 'type-2', 'type-3']) + let connectionTypes = await connectionService.getConnectionTypes(connection) + expect(connectionTypes).toMatchObject(['type-1', 'type-2', 'type-3']) + + await connectionService.removeConnectionType(agentContext, connection, 'type-4') + connectionTypes = await connectionService.getConnectionTypes(connection) + expect(connectionTypes.sort()).toMatchObject(['type-1', 'type-2', 'type-3'].sort()) + }) + + it('removeConnectionType - no previous types', async () => { + const connection = getMockConnection() + + let connectionTypes = await connectionService.getConnectionTypes(connection) + expect(connectionTypes).toMatchObject([]) + + await connectionService.removeConnectionType(agentContext, connection, 'type-4') + connectionTypes = await connectionService.getConnectionTypes(connection) + expect(connectionTypes).toMatchObject([]) + }) + }) }) diff --git a/packages/core/src/modules/connections/repository/ConnectionRecord.ts b/packages/core/src/modules/connections/repository/ConnectionRecord.ts index db9512e5fc..4cdaca805d 100644 --- a/packages/core/src/modules/connections/repository/ConnectionRecord.ts +++ b/packages/core/src/modules/connections/repository/ConnectionRecord.ts @@ -38,7 +38,7 @@ export type DefaultConnectionTags = { theirDid?: string outOfBandId?: string invitationDid?: string - connectionType?: [ConnectionType | string] + connectionType?: Array } export class ConnectionRecord @@ -91,7 +91,7 @@ export class ConnectionRecord } } - public getTags() { + public getTags(): DefaultConnectionTags & CustomConnectionTags { return { ...this._tags, state: this.state, diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index ab3f5e6121..32dbf95fed 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -601,8 +601,8 @@ export class ConnectionService { return this.connectionRepository.findByQuery(agentContext, { outOfBandId }) } - public async findAllByConnectionType(agentContext: AgentContext, connectionType: [ConnectionType | string]) { - return this.connectionRepository.findByQuery(agentContext, { connectionType }) + public async findAllByConnectionType(agentContext: AgentContext, connectionTypes: Array) { + return this.connectionRepository.findByQuery(agentContext, { connectionType: connectionTypes }) } public async findByInvitationDid(agentContext: AgentContext, invitationDid: string) { @@ -642,6 +642,26 @@ export class ConnectionService { return connectionRecord } + public async addConnectionType(agentContext: AgentContext, connectionRecord: ConnectionRecord, type: string) { + const tags = connectionRecord.getTags().connectionType || [] + connectionRecord.setTag('connectionType', [type, ...tags]) + await this.update(agentContext, connectionRecord) + } + + public async removeConnectionType(agentContext: AgentContext, connectionRecord: ConnectionRecord, type: string) { + const tags = connectionRecord.getTags().connectionType || [] + + const newTags = tags.filter((value: string) => value !== type) + connectionRecord.setTag('connectionType', [...newTags]) + + await this.update(agentContext, connectionRecord) + } + + public async getConnectionTypes(connectionRecord: ConnectionRecord) { + const tags = connectionRecord.getTags().connectionType + return tags || [] + } + private async createDid(agentContext: AgentContext, { role, didDoc }: { role: DidDocumentRole; didDoc: DidDoc }) { // Convert the legacy did doc to a new did document const didDocument = convertToNewDidDocument(didDoc) diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index f05f66e20f..59b8b602d8 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -93,8 +93,8 @@ export class MediationRecipientService { role: MediationRole.Recipient, connectionId: connection.id, }) - connection.setTag('connectionType', [ConnectionType.Mediator]) - await this.connectionService.update(agentContext, connection) + + await this.connectionService.addConnectionType(agentContext, connection, ConnectionType.Mediator) await this.mediationRepository.save(agentContext, mediationRecord) this.emitStateChangedEvent(agentContext, mediationRecord, null) diff --git a/packages/core/tests/connections.test.ts b/packages/core/tests/connections.test.ts index 01461162e2..a438c7e132 100644 --- a/packages/core/tests/connections.test.ts +++ b/packages/core/tests/connections.test.ts @@ -16,16 +16,7 @@ describe('connections', () => { let aliceAgent: Agent let acmeAgent: Agent - afterEach(async () => { - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - await acmeAgent.shutdown() - await acmeAgent.wallet.delete() - }) - - it('one should be able to make multiple connections using a multi use invite', async () => { + beforeEach(async () => { const faberAgentOptions = getAgentOptions('Faber Agent Connections', { endpoints: ['rxjs:faber'], }) @@ -59,7 +50,18 @@ describe('connections', () => { acmeAgent.registerInboundTransport(new SubjectInboundTransport(acmeMessages)) acmeAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await acmeAgent.initialize() + }) + + afterEach(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + await acmeAgent.shutdown() + await acmeAgent.wallet.delete() + }) + it('one should be able to make multiple connections using a multi use invite', async () => { const faberOutOfBandRecord = await faberAgent.oob.createInvitation({ handshakeProtocols: [HandshakeProtocol.Connections], multiUseInvitation: true, @@ -94,28 +96,47 @@ describe('connections', () => { return expect(faberOutOfBandRecord.state).toBe(OutOfBandState.AwaitResponse) }) - xit('should be able to make multiple connections using a multi use invite', async () => { - const faberMessages = new Subject() - const subjectMap = { - 'rxjs:faber': faberMessages, - } - - const faberAgentOptions = getAgentOptions('Faber Agent Connections 2', { - endpoints: ['rxjs:faber'], + it('tag connections with multiple types and query them', async () => { + const faberOutOfBandRecord = await faberAgent.oob.createInvitation({ + handshakeProtocols: [HandshakeProtocol.Connections], + multiUseInvitation: true, }) - const aliceAgentOptions = getAgentOptions('Alice Agent Connections 2') - // Faber defines both inbound and outbound transports - faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() + const invitation = faberOutOfBandRecord.outOfBandInvitation + const invitationUrl = invitation.toUrl({ domain: 'https://example.com' }) - // Alice only has outbound transport - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await aliceAgent.initialize() + // Receive invitation first time with alice agent + let { connectionRecord: aliceFaberConnection } = await aliceAgent.oob.receiveInvitationFromUrl(invitationUrl) + aliceFaberConnection = await aliceAgent.connections.returnWhenIsConnected(aliceFaberConnection!.id) + expect(aliceFaberConnection.state).toBe(DidExchangeState.Completed) + // Mark connection with three different types + aliceFaberConnection = await aliceAgent.connections.addConnectionType(aliceFaberConnection.id, 'alice-faber-1') + aliceFaberConnection = await aliceAgent.connections.addConnectionType(aliceFaberConnection.id, 'alice-faber-2') + aliceFaberConnection = await aliceAgent.connections.addConnectionType(aliceFaberConnection.id, 'alice-faber-3') + + // Now search for them + let connectionsFound = await aliceAgent.connections.findAllByConnectionType(['alice-faber-4']) + expect(connectionsFound).toEqual([]) + connectionsFound = await aliceAgent.connections.findAllByConnectionType(['alice-faber-1']) + expect(connectionsFound.map((item) => item.id)).toMatchObject([aliceFaberConnection.id]) + connectionsFound = await aliceAgent.connections.findAllByConnectionType(['alice-faber-2']) + expect(connectionsFound.map((item) => item.id)).toMatchObject([aliceFaberConnection.id]) + connectionsFound = await aliceAgent.connections.findAllByConnectionType(['alice-faber-3']) + expect(connectionsFound.map((item) => item.id)).toMatchObject([aliceFaberConnection.id]) + connectionsFound = await aliceAgent.connections.findAllByConnectionType(['alice-faber-1', 'alice-faber-3']) + expect(connectionsFound.map((item) => item.id)).toMatchObject([aliceFaberConnection.id]) + connectionsFound = await aliceAgent.connections.findAllByConnectionType([ + 'alice-faber-1', + 'alice-faber-2', + 'alice-faber-3', + ]) + expect(connectionsFound.map((item) => item.id)).toMatchObject([aliceFaberConnection.id]) + connectionsFound = await aliceAgent.connections.findAllByConnectionType(['alice-faber-1', 'alice-faber-4']) + expect(connectionsFound).toEqual([]) + }) + + xit('should be able to make multiple connections using a multi use invite', async () => { const faberOutOfBandRecord = await faberAgent.oob.createInvitation({ handshakeProtocols: [HandshakeProtocol.Connections], multiUseInvitation: true, diff --git a/packages/core/tests/oob-mediation.test.ts b/packages/core/tests/oob-mediation.test.ts index b45254f29b..f170f235ed 100644 --- a/packages/core/tests/oob-mediation.test.ts +++ b/packages/core/tests/oob-mediation.test.ts @@ -87,20 +87,23 @@ describe('out of band with mediation', () => { aliceMediatorConnection = await aliceAgent.connections.returnWhenIsConnected(aliceMediatorConnection!.id) expect(aliceMediatorConnection.state).toBe(DidExchangeState.Completed) + // Tag the connection with an initial type + aliceMediatorConnection = await aliceAgent.connections.addConnectionType(aliceMediatorConnection.id, 'initial-type') + let [mediatorAliceConnection] = await mediatorAgent.connections.findAllByOutOfBandId(mediationOutOfBandRecord.id) mediatorAliceConnection = await mediatorAgent.connections.returnWhenIsConnected(mediatorAliceConnection!.id) expect(mediatorAliceConnection.state).toBe(DidExchangeState.Completed) // ========== Set mediation between Alice and Mediator agents ========== + let connectionTypes = await aliceAgent.connections.getConnectionTypes(aliceMediatorConnection.id) + expect(connectionTypes).toMatchObject(['initial-type']) + const mediationRecord = await aliceAgent.mediationRecipient.requestAndAwaitGrant(aliceMediatorConnection) - const connectonTypes = await aliceAgent.connections.getConnectionTypes(mediationRecord.connectionId) - expect(connectonTypes).toContain(ConnectionType.Mediator) - await aliceAgent.connections.addConnectionType(mediationRecord.connectionId, 'test') - expect(await aliceAgent.connections.getConnectionTypes(mediationRecord.connectionId)).toContain('test') - await aliceAgent.connections.removeConnectionType(mediationRecord.connectionId, 'test') - expect(await aliceAgent.connections.getConnectionTypes(mediationRecord.connectionId)).toEqual([ - ConnectionType.Mediator, - ]) + connectionTypes = await aliceAgent.connections.getConnectionTypes(mediationRecord.connectionId) + expect(connectionTypes.sort()).toMatchObject(['initial-type', ConnectionType.Mediator].sort()) + await aliceAgent.connections.removeConnectionType(mediationRecord.connectionId, 'initial-type') + connectionTypes = await aliceAgent.connections.getConnectionTypes(mediationRecord.connectionId) + expect(connectionTypes).toMatchObject([ConnectionType.Mediator]) expect(mediationRecord.state).toBe(MediationState.Granted) await aliceAgent.mediationRecipient.setDefaultMediator(mediationRecord) From 1af57fde5016300e243eafbbdea5ea26bd8ef313 Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Mon, 12 Dec 2022 12:37:00 -0300 Subject: [PATCH 091/125] feat: remove keys on mediator when deleting connections (#1143) Signed-off-by: Ariel Gentile --- .../src/modules/connections/ConnectionsApi.ts | 13 ++ packages/core/src/modules/oob/OutOfBandApi.ts | 19 ++- .../core/src/modules/routing/RecipientApi.ts | 21 ++- .../routing/messages/KeylistUpdateMessage.ts | 7 +- .../services/MediationRecipientService.ts | 60 +++++--- .../routing/services/RoutingService.ts | 16 +++ packages/core/tests/connections.test.ts | 135 +++++++++++++++++- packages/core/tests/oob-mediation.test.ts | 88 ++++++++++-- 8 files changed, 322 insertions(+), 37 deletions(-) diff --git a/packages/core/src/modules/connections/ConnectionsApi.ts b/packages/core/src/modules/connections/ConnectionsApi.ts index aad4beb170..03729dec8d 100644 --- a/packages/core/src/modules/connections/ConnectionsApi.ts +++ b/packages/core/src/modules/connections/ConnectionsApi.ts @@ -321,6 +321,19 @@ export class ConnectionsApi { * @param connectionId the connection record id */ public async deleteById(connectionId: string) { + const connection = await this.connectionService.getById(this.agentContext, connectionId) + + if (connection.mediatorId && connection.did) { + const did = await this.didResolverService.resolve(this.agentContext, connection.did) + + if (did.didDocument) { + await this.routingService.removeRouting(this.agentContext, { + recipientKeys: did.didDocument.recipientKeys, + mediatorId: connection.mediatorId, + }) + } + } + return this.connectionService.deleteById(this.agentContext, connectionId) } diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts index 535d76135f..0936c60539 100644 --- a/packages/core/src/modules/oob/OutOfBandApi.ts +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -1,6 +1,5 @@ import type { AgentMessage } from '../../agent/AgentMessage' import type { AgentMessageReceivedEvent } from '../../agent/Events' -import type { Key } from '../../crypto' import type { Attachment } from '../../decorators/attachment/Attachment' import type { Query } from '../../storage/StorageService' import type { PlaintextMessage } from '../../types' @@ -16,6 +15,7 @@ import { filterContextCorrelationId, AgentEventTypes } from '../../agent/Events' import { MessageSender } from '../../agent/MessageSender' import { OutboundMessageContext } from '../../agent/models' import { InjectionSymbols } from '../../constants' +import { Key } from '../../crypto' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' import { Logger } from '../../logger' @@ -416,7 +416,6 @@ export class OutOfBandApi { label?: string alias?: string imageUrl?: string - mediatorId?: string routing?: Routing } ) { @@ -565,6 +564,22 @@ export class OutOfBandApi { * @param outOfBandId the out of band record id */ public async deleteById(outOfBandId: string) { + const outOfBandRecord = await this.getById(outOfBandId) + + const relatedConnections = await this.connectionsApi.findAllByOutOfBandId(outOfBandId) + + // If it uses mediation and there are no related connections, proceed to delete keys from mediator + // Note: if OOB Record is reusable, it is safe to delete it because every connection created from + // it will use its own recipient key + if (outOfBandRecord.mediatorId && (relatedConnections.length === 0 || outOfBandRecord.reusable)) { + const recipientKeys = outOfBandRecord.getTags().recipientKeyFingerprints.map((item) => Key.fromFingerprint(item)) + + await this.routingService.removeRouting(this.agentContext, { + recipientKeys, + mediatorId: outOfBandRecord.mediatorId, + }) + } + return this.outOfBandService.deleteById(this.agentContext, outOfBandId) } diff --git a/packages/core/src/modules/routing/RecipientApi.ts b/packages/core/src/modules/routing/RecipientApi.ts index fc897c8f2d..c58ec32521 100644 --- a/packages/core/src/modules/routing/RecipientApi.ts +++ b/packages/core/src/modules/routing/RecipientApi.ts @@ -18,8 +18,10 @@ import { AriesFrameworkError } from '../../error' import { Logger } from '../../logger' import { inject, injectable } from '../../plugins' import { TransportEventTypes } from '../../transport' +import { ConnectionMetadataKeys } from '../connections/repository/ConnectionMetadataTypes' import { ConnectionService } from '../connections/services' import { DidsApi } from '../dids' +import { verkeyToDidKey } from '../dids/helpers' import { DiscoverFeaturesApi } from '../discover-features' import { MediatorPickupStrategy } from './MediatorPickupStrategy' @@ -28,6 +30,7 @@ import { RoutingEventTypes } from './RoutingEvents' import { KeylistUpdateResponseHandler } from './handlers/KeylistUpdateResponseHandler' import { MediationDenyHandler } from './handlers/MediationDenyHandler' import { MediationGrantHandler } from './handlers/MediationGrantHandler' +import { KeylistUpdate, KeylistUpdateAction, KeylistUpdateMessage } from './messages' import { MediationState } from './models/MediationState' import { StatusRequestMessage, BatchPickupMessage, StatusMessage } from './protocol' import { StatusHandler, MessageDeliveryHandler } from './protocol/pickup/v2/handlers' @@ -370,8 +373,22 @@ export class RecipientApi { return mediationRecord } - public async notifyKeylistUpdate(connection: ConnectionRecord, verkey: string) { - const message = this.mediationRecipientService.createKeylistUpdateMessage(verkey) + public async notifyKeylistUpdate(connection: ConnectionRecord, verkey: string, action?: KeylistUpdateAction) { + // Use our useDidKey configuration unless we know the key formatting other party is using + let useDidKey = this.agentContext.config.useDidKeyInProtocols + + const useDidKeysConnectionMetadata = connection.metadata.get(ConnectionMetadataKeys.UseDidKeysForProtocol) + if (useDidKeysConnectionMetadata) { + useDidKey = useDidKeysConnectionMetadata[KeylistUpdateMessage.type.protocolUri] ?? useDidKey + } + + const message = this.mediationRecipientService.createKeylistUpdateMessage([ + new KeylistUpdate({ + action: action ?? KeylistUpdateAction.add, + recipientKey: useDidKey ? verkeyToDidKey(verkey) : verkey, + }), + ]) + const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, connection, diff --git a/packages/core/src/modules/routing/messages/KeylistUpdateMessage.ts b/packages/core/src/modules/routing/messages/KeylistUpdateMessage.ts index b8d493881e..be83d5b021 100644 --- a/packages/core/src/modules/routing/messages/KeylistUpdateMessage.ts +++ b/packages/core/src/modules/routing/messages/KeylistUpdateMessage.ts @@ -9,8 +9,13 @@ export enum KeylistUpdateAction { remove = 'remove', } +export interface KeylistUpdateOptions { + recipientKey: string + action: KeylistUpdateAction +} + export class KeylistUpdate { - public constructor(options: { recipientKey: string; action: KeylistUpdateAction }) { + public constructor(options: KeylistUpdateOptions) { if (options) { this.recipientKey = options.recipientKey this.action = options.action diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index 59b8b602d8..c2ff4acba6 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -9,7 +9,7 @@ import type { Routing } from '../../connections/services/ConnectionService' import type { MediationStateChangedEvent, KeylistUpdatedEvent } from '../RoutingEvents' import type { MediationDenyMessage } from '../messages' import type { StatusMessage, MessageDeliveryMessage } from '../protocol' -import type { GetRoutingOptions } from './RoutingService' +import type { GetRoutingOptions, RemoveRoutingOptions } from './RoutingService' import { firstValueFrom, ReplaySubject } from 'rxjs' import { filter, first, timeout } from 'rxjs/operators' @@ -25,7 +25,8 @@ import { JsonTransformer } from '../../../utils' import { ConnectionType } from '../../connections/models/ConnectionType' import { ConnectionMetadataKeys } from '../../connections/repository/ConnectionMetadataTypes' import { ConnectionService } from '../../connections/services/ConnectionService' -import { didKeyToVerkey, isDidKey, verkeyToDidKey } from '../../dids/helpers' +import { DidKey } from '../../dids' +import { didKeyToVerkey, isDidKey } from '../../dids/helpers' import { ProblemReportError } from '../../problem-reports' import { RecipientModuleConfig } from '../RecipientModuleConfig' import { RoutingEventTypes } from '../RoutingEvents' @@ -174,7 +175,7 @@ export class MediationRecipientService { public async keylistUpdateAndAwait( agentContext: AgentContext, mediationRecord: MediationRecord, - verKey: string, + updates: { recipientKey: Key; action: KeylistUpdateAction }[], timeoutMs = 15000 // TODO: this should be a configurable value in agent config ): Promise { const connection = await this.connectionService.getById(agentContext, mediationRecord.connectionId) @@ -187,7 +188,15 @@ export class MediationRecipientService { useDidKey = useDidKeysConnectionMetadata[KeylistUpdateMessage.type.protocolUri] ?? useDidKey } - const message = this.createKeylistUpdateMessage(useDidKey ? verkeyToDidKey(verKey) : verKey) + const message = this.createKeylistUpdateMessage( + updates.map( + (item) => + new KeylistUpdate({ + action: item.action, + recipientKey: useDidKey ? new DidKey(item.recipientKey).did : item.recipientKey.publicKeyBase58, + }) + ) + ) mediationRecord.assertReady() mediationRecord.assertRole(MediationRole.Recipient) @@ -216,14 +225,9 @@ export class MediationRecipientService { return keylistUpdate.payload.mediationRecord } - public createKeylistUpdateMessage(verkey: string): KeylistUpdateMessage { + public createKeylistUpdateMessage(updates: KeylistUpdate[]): KeylistUpdateMessage { const keylistUpdateMessage = new KeylistUpdateMessage({ - updates: [ - new KeylistUpdate({ - action: KeylistUpdateAction.add, - recipientKey: verkey, - }), - ], + updates, }) return keylistUpdateMessage } @@ -247,19 +251,43 @@ export class MediationRecipientService { if (!mediationRecord) return routing // new did has been created and mediator needs to be updated with the public key. - mediationRecord = await this.keylistUpdateAndAwait( - agentContext, - mediationRecord, - routing.recipientKey.publicKeyBase58 - ) + mediationRecord = await this.keylistUpdateAndAwait(agentContext, mediationRecord, [ + { + recipientKey: routing.recipientKey, + action: KeylistUpdateAction.add, + }, + ]) return { ...routing, + mediatorId: mediationRecord.id, endpoints: mediationRecord.endpoint ? [mediationRecord.endpoint] : routing.endpoints, routingKeys: mediationRecord.routingKeys.map((key) => Key.fromPublicKeyBase58(key, KeyType.Ed25519)), } } + public async removeMediationRouting( + agentContext: AgentContext, + { recipientKeys, mediatorId }: RemoveRoutingOptions + ): Promise { + const mediationRecord = await this.getById(agentContext, mediatorId) + + if (!mediationRecord) { + throw new AriesFrameworkError('No mediation record to remove routing from has been found') + } + + await this.keylistUpdateAndAwait( + agentContext, + mediationRecord, + recipientKeys.map((item) => { + return { + recipientKey: item, + action: KeylistUpdateAction.remove, + } + }) + ) + } + public async processMediationDeny(messageContext: InboundMessageContext) { const connection = messageContext.assertReadyConnection() diff --git a/packages/core/src/modules/routing/services/RoutingService.ts b/packages/core/src/modules/routing/services/RoutingService.ts index 357cb05d3d..7c21b62ec4 100644 --- a/packages/core/src/modules/routing/services/RoutingService.ts +++ b/packages/core/src/modules/routing/services/RoutingService.ts @@ -52,6 +52,10 @@ export class RoutingService { return routing } + + public async removeRouting(agentContext: AgentContext, options: RemoveRoutingOptions) { + await this.mediationRecipientService.removeMediationRouting(agentContext, options) + } } export interface GetRoutingOptions { @@ -66,3 +70,15 @@ export interface GetRoutingOptions { */ useDefaultMediator?: boolean } + +export interface RemoveRoutingOptions { + /** + * Keys to remove routing from + */ + recipientKeys: Key[] + + /** + * Identifier of the mediator used when routing has been set up + */ + mediatorId: string +} diff --git a/packages/core/tests/connections.test.ts b/packages/core/tests/connections.test.ts index a438c7e132..20ed36613e 100644 --- a/packages/core/tests/connections.test.ts +++ b/packages/core/tests/connections.test.ts @@ -1,12 +1,22 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' +import type { AgentMessageProcessedEvent, KeylistUpdate } from '../src' -import { Subject } from 'rxjs' +import { filter, firstValueFrom, map, Subject, timeout } from 'rxjs' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { DidExchangeState, HandshakeProtocol } from '../src' +import { + Key, + AgentEventTypes, + KeylistUpdateMessage, + MediatorPickupStrategy, + DidExchangeState, + HandshakeProtocol, + KeylistUpdateAction, +} from '../src' 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' @@ -15,6 +25,7 @@ describe('connections', () => { let faberAgent: Agent let aliceAgent: Agent let acmeAgent: Agent + let mediatorAgent: Agent beforeEach(async () => { const faberAgentOptions = getAgentOptions('Faber Agent Connections', { @@ -26,14 +37,21 @@ describe('connections', () => { const acmeAgentOptions = getAgentOptions('Acme Agent Connections', { endpoints: ['rxjs:acme'], }) + const mediatorAgentOptions = getAgentOptions('Mediator Agent Connections', { + endpoints: ['rxjs:mediator'], + autoAcceptMediationRequests: true, + }) const faberMessages = new Subject() const aliceMessages = new Subject() const acmeMessages = new Subject() + const mediatorMessages = new Subject() + const subjectMap = { 'rxjs:faber': faberMessages, 'rxjs:alice': aliceMessages, 'rxjs:acme': acmeMessages, + 'rxjs:mediator': mediatorMessages, } faberAgent = new Agent(faberAgentOptions) @@ -50,6 +68,11 @@ describe('connections', () => { acmeAgent.registerInboundTransport(new SubjectInboundTransport(acmeMessages)) acmeAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await acmeAgent.initialize() + + mediatorAgent = new Agent(mediatorAgentOptions) + mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) + mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await mediatorAgent.initialize() }) afterEach(async () => { @@ -59,6 +82,8 @@ describe('connections', () => { await aliceAgent.wallet.delete() await acmeAgent.shutdown() await acmeAgent.wallet.delete() + await mediatorAgent.shutdown() + await mediatorAgent.wallet.delete() }) it('one should be able to make multiple connections using a multi use invite', async () => { @@ -170,4 +195,110 @@ describe('connections', () => { return expect(faberOutOfBandRecord.state).toBe(OutOfBandState.AwaitResponse) }) + + it('agent using mediator should be able to make multiple connections using a multi use invite', async () => { + // Make Faber use a mediator + const { outOfBandInvitation: mediatorOutOfBandInvitation } = await mediatorAgent.oob.createInvitation({}) + let { connectionRecord } = await faberAgent.oob.receiveInvitation(mediatorOutOfBandInvitation) + connectionRecord = await faberAgent.connections.returnWhenIsConnected(connectionRecord!.id) + await faberAgent.mediationRecipient.provision(connectionRecord!) + await faberAgent.mediationRecipient.initialize() + + // Create observable for event + const keyAddMessageObservable = mediatorAgent.events + .observable(AgentEventTypes.AgentMessageProcessed) + .pipe( + filter((event) => event.payload.message.type === KeylistUpdateMessage.type.messageTypeUri), + map((event) => event.payload.message as KeylistUpdateMessage), + timeout(5000) + ) + + const keylistAddEvents: KeylistUpdate[] = [] + keyAddMessageObservable.subscribe((value) => { + value.updates.forEach((update) => + keylistAddEvents.push({ action: update.action, recipientKey: didKeyToVerkey(update.recipientKey) }) + ) + }) + + // Now create invitations that will be mediated + 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 first time with alice agent + let { connectionRecord: aliceFaberConnection } = await aliceAgent.oob.receiveInvitationFromUrl(invitationUrl) + aliceFaberConnection = await aliceAgent.connections.returnWhenIsConnected(aliceFaberConnection!.id) + expect(aliceFaberConnection.state).toBe(DidExchangeState.Completed) + + // Receive invitation second time with acme agent + let { connectionRecord: acmeFaberConnection } = await acmeAgent.oob.receiveInvitationFromUrl(invitationUrl, { + reuseConnection: false, + }) + acmeFaberConnection = await acmeAgent.connections.returnWhenIsConnected(acmeFaberConnection!.id) + expect(acmeFaberConnection.state).toBe(DidExchangeState.Completed) + + let faberAliceConnection = await faberAgent.connections.getByThreadId(aliceFaberConnection.threadId!) + let faberAcmeConnection = await faberAgent.connections.getByThreadId(acmeFaberConnection.threadId!) + + faberAliceConnection = await faberAgent.connections.returnWhenIsConnected(faberAliceConnection.id) + faberAcmeConnection = await faberAgent.connections.returnWhenIsConnected(faberAcmeConnection.id) + + expect(faberAliceConnection).toBeConnectedWith(aliceFaberConnection) + expect(faberAcmeConnection).toBeConnectedWith(acmeFaberConnection) + + expect(faberAliceConnection.id).not.toBe(faberAcmeConnection.id) + + expect(faberOutOfBandRecord.state).toBe(OutOfBandState.AwaitResponse) + + // Mediator should have received all new keys (the one of the invitation + the ones generated on each connection) + expect(keylistAddEvents.length).toEqual(3) + + expect(keylistAddEvents).toEqual( + expect.arrayContaining([ + { + action: KeylistUpdateAction.add, + recipientKey: Key.fromFingerprint(faberOutOfBandRecord.getTags().recipientKeyFingerprints[0]).publicKeyBase58, + }, + { + action: KeylistUpdateAction.add, + recipientKey: (await faberAgent.dids.resolveDidDocument(faberAliceConnection.did!)).recipientKeys[0] + .publicKeyBase58, + }, + { + action: KeylistUpdateAction.add, + recipientKey: (await faberAgent.dids.resolveDidDocument(faberAcmeConnection.did!)).recipientKeys[0] + .publicKeyBase58, + }, + ]) + ) + + for (const connection of [faberAcmeConnection, faberAliceConnection]) { + const keyRemoveMessagePromise = firstValueFrom( + mediatorAgent.events.observable(AgentEventTypes.AgentMessageProcessed).pipe( + filter((event) => event.payload.message.type === KeylistUpdateMessage.type.messageTypeUri), + map((event) => event.payload.message as KeylistUpdateMessage), + timeout(5000) + ) + ) + + await faberAgent.connections.deleteById(connection.id) + + const keyRemoveMessage = await keyRemoveMessagePromise + expect(keyRemoveMessage.updates.length).toEqual(1) + + expect( + keyRemoveMessage.updates.map((update) => ({ + action: update.action, + recipientKey: didKeyToVerkey(update.recipientKey), + }))[0] + ).toEqual({ + action: KeylistUpdateAction.remove, + recipientKey: (await faberAgent.dids.resolveDidDocument(connection.did!)).recipientKeys[0].publicKeyBase58, + }) + } + }) }) diff --git a/packages/core/tests/oob-mediation.test.ts b/packages/core/tests/oob-mediation.test.ts index f170f235ed..f085b41f88 100644 --- a/packages/core/tests/oob-mediation.test.ts +++ b/packages/core/tests/oob-mediation.test.ts @@ -1,14 +1,23 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' +import type { AgentMessageProcessedEvent } from '../src/agent/Events' +import type { OutOfBandDidCommService } from '../src/modules/oob' -import { Subject } from 'rxjs' +import { filter, firstValueFrom, map, Subject, timeout } from 'rxjs' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' import { Agent } from '../src/agent/Agent' +import { AgentEventTypes } from '../src/agent/Events' import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' import { ConnectionType } from '../src/modules/connections/models/ConnectionType' -import { MediationState, MediatorPickupStrategy } from '../src/modules/routing' +import { didKeyToVerkey } from '../src/modules/dids/helpers' +import { + KeylistUpdateMessage, + KeylistUpdateAction, + MediationState, + MediatorPickupStrategy, +} from '../src/modules/routing' import { getAgentOptions, waitForBasicMessage } from './helpers' @@ -63,18 +72,7 @@ describe('out of band with mediation', () => { mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await mediatorAgent.initialize() - }) - - afterAll(async () => { - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - await mediatorAgent.shutdown() - await mediatorAgent.wallet.delete() - }) - test(`make a connection with ${HandshakeProtocol.DidExchange} on OOB invitation encoded in URL`, async () => { // ========== Make a connection between Alice and Mediator agents ========== const mediationOutOfBandRecord = await mediatorAgent.oob.createInvitation(makeConnectionConfig) const { outOfBandInvitation: mediatorOutOfBandInvitation } = mediationOutOfBandRecord @@ -110,9 +108,21 @@ describe('out of band with mediation', () => { await aliceAgent.mediationRecipient.initiateMessagePickup(mediationRecord) const defaultMediator = await aliceAgent.mediationRecipient.findDefaultMediator() expect(defaultMediator?.id).toBe(mediationRecord.id) + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + await mediatorAgent.shutdown() + await mediatorAgent.wallet.delete() + }) + test(`make a connection with ${HandshakeProtocol.DidExchange} on OOB invitation encoded in URL`, async () => { // ========== Make a connection between Alice and Faber ========== - const outOfBandRecord = await faberAgent.oob.createInvitation(makeConnectionConfig) + const outOfBandRecord = await faberAgent.oob.createInvitation({ multiUseInvitation: false }) + const { outOfBandInvitation } = outOfBandRecord const urlMessage = outOfBandInvitation.toUrl({ domain: 'http://example.com' }) @@ -133,4 +143,54 @@ describe('out of band with mediation', () => { expect(basicMessage.content).toBe('hello') }) + + test(`create and delete OOB invitation when using mediation`, async () => { + // Alice creates an invitation: the key is notified to her mediator + + const keyAddMessagePromise = firstValueFrom( + mediatorAgent.events.observable(AgentEventTypes.AgentMessageProcessed).pipe( + filter((event) => event.payload.message.type === KeylistUpdateMessage.type.messageTypeUri), + map((event) => event.payload.message as KeylistUpdateMessage), + timeout(5000) + ) + ) + + const outOfBandRecord = await aliceAgent.oob.createInvitation({}) + const { outOfBandInvitation } = outOfBandRecord + + const keyAddMessage = await keyAddMessagePromise + + expect(keyAddMessage.updates.length).toEqual(1) + expect( + keyAddMessage.updates.map((update) => ({ + action: update.action, + recipientKey: didKeyToVerkey(update.recipientKey), + }))[0] + ).toEqual({ + action: KeylistUpdateAction.add, + recipientKey: didKeyToVerkey((outOfBandInvitation.getServices()[0] as OutOfBandDidCommService).recipientKeys[0]), + }) + + const keyRemoveMessagePromise = firstValueFrom( + mediatorAgent.events.observable(AgentEventTypes.AgentMessageProcessed).pipe( + filter((event) => event.payload.message.type === KeylistUpdateMessage.type.messageTypeUri), + map((event) => event.payload.message as KeylistUpdateMessage), + timeout(5000) + ) + ) + + await aliceAgent.oob.deleteById(outOfBandRecord.id) + + const keyRemoveMessage = await keyRemoveMessagePromise + expect(keyRemoveMessage.updates.length).toEqual(1) + expect( + keyRemoveMessage.updates.map((update) => ({ + action: update.action, + recipientKey: didKeyToVerkey(update.recipientKey), + }))[0] + ).toEqual({ + action: KeylistUpdateAction.remove, + recipientKey: didKeyToVerkey((outOfBandInvitation.getServices()[0] as OutOfBandDidCommService).recipientKeys[0]), + }) + }) }) From 9352fa5eea1e01d29acd0757298398aac45fcab2 Mon Sep 17 00:00:00 2001 From: Pritam Singh <43764373+Zzocker@users.noreply.github.com> Date: Wed, 14 Dec 2022 08:12:54 +0530 Subject: [PATCH 092/125] feat(oob): receive Invitation with timeout (#1156) Signed-off-by: Pritam Singh --- packages/core/src/modules/oob/OutOfBandApi.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts index 0936c60539..7448674d4e 100644 --- a/packages/core/src/modules/oob/OutOfBandApi.ts +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -75,6 +75,7 @@ export interface ReceiveOutOfBandInvitationConfig { autoAcceptConnection?: boolean reuseConnection?: boolean routing?: Routing + acceptInvitationTimeoutMs?: number } @injectable() @@ -388,6 +389,7 @@ export class OutOfBandApi { autoAcceptConnection, reuseConnection, routing, + timeoutMs: config.acceptInvitationTimeoutMs, }) } @@ -417,6 +419,7 @@ export class OutOfBandApi { alias?: string imageUrl?: string routing?: Routing + timeoutMs?: number } ) { const outOfBandRecord = await this.outOfBandService.getById(this.agentContext, outOfBandId) @@ -426,6 +429,7 @@ export class OutOfBandApi { const { handshakeProtocols } = outOfBandInvitation const services = outOfBandInvitation.getServices() const messages = outOfBandInvitation.getRequests() + const timeoutMs = config.timeoutMs ?? 20000 const existingConnection = await this.findExistingConnection(outOfBandInvitation) @@ -483,7 +487,7 @@ export class OutOfBandApi { } else { // Wait until the connection is ready and then pass the messages to the agent for further processing this.connectionsApi - .returnWhenIsConnected(connectionRecord.id) + .returnWhenIsConnected(connectionRecord.id, { timeoutMs }) .then((connectionRecord) => this.emitWithConnection(connectionRecord, messages)) .catch((error) => { if (error instanceof EmptyError) { From c75246147ffc6be3c815c66b0a7ad66e48996568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Przytu=C5=82a?= Date: Wed, 14 Dec 2022 13:51:14 +0100 Subject: [PATCH 093/125] feat(proofs): proof negotiation (#1131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Łukasz Przytuła --- packages/core/src/modules/proofs/ProofsApi.ts | 97 +++- .../src/modules/proofs/ProofsApiOptions.ts | 17 + .../__tests__/indy-proof-negotiation.test.ts | 355 ++++++++++++++ .../proofs/protocol/v2/V2ProofService.ts | 2 + .../__tests__/indy-proof-negotiation.test.ts | 444 ++++++++++++++++++ packages/core/tests/helpers.ts | 4 +- 6 files changed, 917 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-negotiation.test.ts diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index 1bec0aec43..bd07ac199b 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -11,6 +11,8 @@ import type { ProposeProofOptions, RequestProofOptions, ProofServiceMap, + NegotiateRequestOptions, + NegotiateProposalOptions, } from './ProofsApiOptions' import type { ProofFormat } from './formats/ProofFormat' import type { IndyProofFormat } from './formats/indy/IndyProofFormat' @@ -28,6 +30,7 @@ import type { FormatRetrievedCredentialOptions, DeleteProofOptions, GetFormatDataReturn, + CreateProposalAsResponseOptions, } from './models/ProofServiceOptions' import type { ProofExchangeRecord } from './repository/ProofExchangeRecord' @@ -57,11 +60,13 @@ export interface ProofsApi): Promise acceptProposal(options: AcceptProofProposalOptions): Promise + negotiateProposal(options: NegotiateProposalOptions): Promise // Request methods requestProof(options: RequestProofOptions): Promise acceptRequest(options: AcceptProofPresentationOptions): Promise declineRequest(proofRecordId: string): Promise + negotiateRequest(options: NegotiateRequestOptions): Promise // Present acceptPresentation(proofRecordId: string): Promise @@ -204,13 +209,14 @@ export class ProofsApi< */ public async acceptProposal(options: AcceptProofProposalOptions): Promise { const { proofRecordId } = options + const proofRecord = await this.getById(proofRecordId) const service = this.getService(proofRecord.protocolVersion) if (!proofRecord.connectionId) { throw new AriesFrameworkError( - `No connectionId found for credential record '${proofRecord.id}'. Connection-less issuance does not support presentation proposal or negotiation.` + `No connectionId found for proof record '${proofRecord.id}'. Connection-less issuance does not support presentation proposal or negotiation.` ) } @@ -248,6 +254,50 @@ export class ProofsApi< return proofRecord } + /** + * Answer with a new presentation request in response to received presentation proposal message + * to the connection associated with the proof record. + * + * @param options multiple properties like proof record id, proof formats to accept requested credentials object + * specifying which credentials to use for the proof + * @returns Proof record associated with the sent request message + */ + public async negotiateProposal(options: NegotiateProposalOptions): Promise { + const { proofRecordId } = options + + const proofRecord = await this.getById(proofRecordId) + + const service = this.getService(proofRecord.protocolVersion) + + if (!proofRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for proof record '${proofRecord.id}'. Connection-less issuance does not support negotiation.` + ) + } + + const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + + // Assert + connection.assertReady() + + const requestOptions: CreateRequestAsResponseOptions = { + proofRecord, + proofFormats: options.proofFormats, + autoAcceptProof: options.autoAcceptProof, + comment: options.comment, + } + const { message } = await service.createRequestAsResponse(this.agentContext, requestOptions) + + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: proofRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) + + return proofRecord + } + /** * Initiate a new presentation exchange as verifier by sending a presentation request message * to the connection with the specified connection id @@ -398,6 +448,51 @@ export class ProofsApi< return proofRecord } + /** + * Answer with a new presentation proposal in response to received presentation request message + * to the connection associated with the proof record. + * + * @param options multiple properties like proof record id, proof format (indy/ presentation exchange) + * to include in the message + * @returns Proof record associated with the sent proposal message + */ + public async negotiateRequest(options: NegotiateRequestOptions): Promise { + const { proofRecordId } = options + const proofRecord = await this.getById(proofRecordId) + + const service = this.getService(proofRecord.protocolVersion) + + if (!proofRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for proof record '${proofRecord.id}'. Connection-less issuance does not support presentation proposal or negotiation.` + ) + } + + const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + + // Assert + connection.assertReady() + + const proposalOptions: CreateProposalAsResponseOptions = { + proofRecord, + proofFormats: options.proofFormats, + autoAcceptProof: options.autoAcceptProof, + goalCode: options.goalCode, + comment: options.comment, + } + + const { message } = await service.createProposalAsResponse(this.agentContext, proposalOptions) + + const outboundMessageContext = new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection, + associatedRecord: proofRecord, + }) + await this.messageSender.sendMessage(outboundMessageContext) + + return proofRecord + } + /** * Accept a presentation as prover (by sending a presentation acknowledgement message) to the connection * associated with the proof record. diff --git a/packages/core/src/modules/proofs/ProofsApiOptions.ts b/packages/core/src/modules/proofs/ProofsApiOptions.ts index 8e89ec5121..5d23a7b131 100644 --- a/packages/core/src/modules/proofs/ProofsApiOptions.ts +++ b/packages/core/src/modules/proofs/ProofsApiOptions.ts @@ -42,6 +42,15 @@ export interface ProposeProofOptions< autoAcceptProof?: AutoAcceptProof parentThreadId?: string } + +export interface NegotiateRequestOptions { + proofRecordId: string + proofFormats: ProofFormatPayload + comment?: string + goalCode?: string + autoAcceptProof?: AutoAcceptProof +} + export interface AcceptProofPresentationOptions { proofRecordId: string comment?: string @@ -68,6 +77,14 @@ export interface RequestProofOptions< parentThreadId?: string } +export interface NegotiateProposalOptions { + proofRecordId: string + proofFormats: ProofFormatPayload + comment?: string + autoAcceptProof?: AutoAcceptProof + parentThreadId?: string +} + export interface CreateProofRequestOptions< PFs extends ProofFormat[] = ProofFormat[], PSs extends ProofService[] = ProofService[] diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts new file mode 100644 index 0000000000..19134854a8 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts @@ -0,0 +1,355 @@ +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' +import type { AcceptProofProposalOptions, NegotiateProposalOptions } from '../../../ProofsApiOptions' +import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' +import type { PresentationPreview } from '../models/V1PresentationPreview' +import type { CredDefId } from 'indy-sdk' + +import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { DidCommMessageRepository } from '../../../../../storage/didcomm' +import { AttributeFilter } from '../../../formats/indy/models/AttributeFilter' +import { PredicateType } from '../../../formats/indy/models/PredicateType' +import { ProofAttributeInfo } from '../../../formats/indy/models/ProofAttributeInfo' +import { ProofPredicateInfo } from '../../../formats/indy/models/ProofPredicateInfo' +import { ProofState } from '../../../models/ProofState' +import { V1ProposePresentationMessage, V1RequestPresentationMessage } from '../messages' + +describe('Present Proof', () => { + let faberAgent: Agent + let aliceAgent: Agent + let credDefId: CredDefId + let aliceConnection: ConnectionRecord + let presentationPreview: PresentationPreview + let faberProofExchangeRecord: ProofExchangeRecord + let aliceProofExchangeRecord: ProofExchangeRecord + let didCommMessageRepository: DidCommMessageRepository + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ faberAgent, aliceAgent, credDefId, aliceConnection, presentationPreview } = await setupProofsTest( + 'Faber agent', + 'Alice agent' + )) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test(`Proof negotiation between Alice and Faber`, async () => { + testLogger.test('Alice sends proof proposal to Faber') + + let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnection.id, + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'proof-request', + nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + version: '1.0', + attributes: presentationPreview.attributes.filter((attribute) => attribute.name !== 'name'), + predicates: presentationPreview.predicates, + }, + }, + comment: 'V1 propose proof test 1', + }) + + testLogger.test('Faber waits for presentation from Alice') + faberProofExchangeRecord = await faberProofExchangeRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + let proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofExchangeRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/propose-presentation', + id: expect.any(String), + comment: 'V1 propose proof test 1', + presentationProposal: { + type: 'https://didcomm.org/present-proof/1.0/presentation-preview', + attributes: [ + { + name: 'image_0', + credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + predicate: '>=', + threshold: 50, + }, + ], + }, + }) + expect(faberProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v1', + }) + + // Negotiate Proposal + const attributes = { + name: new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + image_0: new ProofAttributeInfo({ + name: 'image_0', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const requestProofAsResponseOptions: NegotiateProposalOptions = { + proofRecordId: faberProofExchangeRecord.id, + proofFormats: { + indy: { + name: 'proof-request', + nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + version: '1.0', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } + + let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + }) + + testLogger.test('Faber sends new proof request to Alice') + faberProofExchangeRecord = await faberAgent.proofs.negotiateProposal(requestProofAsResponseOptions) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + let request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofExchangeRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/request-presentation', + id: expect.any(String), + requestPresentationAttachments: [ + { + id: 'libindy-request-presentation-0', + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + thread: { + threadId: faberProofExchangeRecord.threadId, + }, + }) + expect(aliceProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: 'v1', + }) + + testLogger.test('Alice sends proof proposal to Faber') + + faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofExchangeRecord = await aliceAgent.proofs.negotiateRequest({ + proofRecordId: aliceProofExchangeRecord.id, + proofFormats: { + indy: { + name: 'proof-request', + nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + version: '1.0', + attributes: presentationPreview.attributes.filter((attribute) => attribute.name === 'name'), + predicates: presentationPreview.predicates, + }, + }, + comment: 'V1 propose proof test 2', + }) + + testLogger.test('Faber waits for presentation from Alice') + faberProofExchangeRecord = await faberProofExchangeRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofExchangeRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/propose-presentation', + id: expect.any(String), + comment: 'V1 propose proof test 2', + presentationProposal: { + type: 'https://didcomm.org/present-proof/1.0/presentation-preview', + attributes: [ + { + name: 'name', + credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + value: 'John', + referent: '0', + }, + ], + predicates: [ + { + name: 'age', + credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + predicate: '>=', + threshold: 50, + }, + ], + }, + }) + expect(faberProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v1', + }) + + // Accept Proposal + const acceptProposalOptions: AcceptProofProposalOptions = { + proofRecordId: faberProofExchangeRecord.id, + } + + aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + }) + + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofExchangeRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/request-presentation', + id: expect.any(String), + requestPresentationAttachments: [ + { + id: 'libindy-request-presentation-0', + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + thread: { + threadId: faberProofExchangeRecord.threadId, + }, + }) + expect(aliceProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: 'v1', + }) + + const presentationProposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofExchangeRecord.id) + + expect(presentationProposalMessage).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/propose-presentation', + id: expect.any(String), + comment: 'V1 propose proof test 2', + presentationProposal: { + type: 'https://didcomm.org/present-proof/1.0/presentation-preview', + attributes: [ + { + name: 'name', + credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + value: 'John', + referent: '0', + }, + ], + predicates: [ + { + name: 'age', + credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + predicate: '>=', + threshold: 50, + }, + ], + }, + }) + + const proofRequestMessage = (await aliceAgent.proofs.findRequestMessage( + aliceProofExchangeRecord.id + )) as V1RequestPresentationMessage + + const predicateKey = proofRequestMessage.indyProofRequest?.requestedPredicates?.keys().next().value + const predicate = Object.values(predicates)[0] + + expect(proofRequestMessage.indyProofRequest).toMatchObject({ + name: 'Proof Request', + version: '1.0', + requestedAttributes: new Map( + Object.entries({ + '0': new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + }) + ), + requestedPredicates: new Map( + Object.entries({ + [predicateKey]: predicate, + }) + ), + }) + }) +}) diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts index 7ef492d4bb..0bd358728d 100644 --- a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts @@ -152,6 +152,8 @@ export class V2ProofService extends P willConfirm: options.willConfirm, }) + proposalMessage.setThread({ threadId: options.proofRecord.threadId }) + await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: proposalMessage, role: DidCommMessageRole.Sender, diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-negotiation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-negotiation.test.ts new file mode 100644 index 0000000000..7ebe83cb32 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-negotiation.test.ts @@ -0,0 +1,444 @@ +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' +import type { AcceptProofProposalOptions, NegotiateProposalOptions } from '../../../ProofsApiOptions' +import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' +import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' +import type { CredDefId } from 'indy-sdk' + +import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { DidCommMessageRepository } from '../../../../../storage/didcomm' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST } from '../../../formats/ProofFormatConstants' +import { AttributeFilter } from '../../../formats/indy/models/AttributeFilter' +import { PredicateType } from '../../../formats/indy/models/PredicateType' +import { ProofAttributeInfo } from '../../../formats/indy/models/ProofAttributeInfo' +import { ProofPredicateInfo } from '../../../formats/indy/models/ProofPredicateInfo' +import { ProofRequest } from '../../../formats/indy/models/ProofRequest' +import { ProofState } from '../../../models/ProofState' +import { V2ProposalPresentationMessage, V2RequestPresentationMessage } from '../messages' + +describe('Present Proof', () => { + let faberAgent: Agent + let aliceAgent: Agent + let credDefId: CredDefId + let aliceConnection: ConnectionRecord + let presentationPreview: PresentationPreview + let faberProofExchangeRecord: ProofExchangeRecord + let aliceProofExchangeRecord: ProofExchangeRecord + let didCommMessageRepository: DidCommMessageRepository + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ faberAgent, aliceAgent, credDefId, aliceConnection, presentationPreview } = await setupProofsTest( + 'Faber agent', + 'Alice agent' + )) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test(`Proof negotiation between Alice and Faber`, async () => { + testLogger.test('Alice sends proof proposal to Faber') + + let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnection.id, + protocolVersion: 'v2', + proofFormats: { + indy: { + name: 'proof-request', + nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + version: '1.0', + attributes: presentationPreview.attributes.filter((attribute) => attribute.name !== 'name'), + predicates: presentationPreview.predicates, + }, + }, + comment: 'V2 propose proof test 1', + }) + + testLogger.test('Faber waits for presentation from Alice') + faberProofExchangeRecord = await faberProofExchangeRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + let proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofExchangeRecord.id, + messageClass: V2ProposalPresentationMessage, + }) + + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/propose-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION_PROPOSAL, + }, + ], + proposalsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + comment: 'V2 propose proof test 1', + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let proposalAttach = proposal?.proposalsAttach[0].getDataAsJson() as any + let attributesGroup = Object.keys(proposalAttach.requested_attributes ?? {})[0] + let predicatesGroup = Object.keys(proposalAttach.requested_predicates ?? {})[0] + expect(proposalAttach).toMatchObject({ + requested_attributes: { + [attributesGroup]: { + name: 'image_0', + restrictions: [ + { + cred_def_id: presentationPreview.attributes[1].credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + [predicatesGroup]: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: presentationPreview.predicates[0].credentialDefinitionId, + }, + ], + }, + }, + }) + expect(faberProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v2', + }) + + // Negotiate Proposal + const attributes = { + name: new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + image_0: new ProofAttributeInfo({ + name: 'image_0', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const requestProofAsResponseOptions: NegotiateProposalOptions = { + proofRecordId: faberProofExchangeRecord.id, + proofFormats: { + indy: { + name: 'proof-request', + nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + version: '1.0', + requestedAttributes: attributes, + requestedPredicates: predicates, + }, + }, + } + + let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + }) + + testLogger.test('Faber sends new proof request to Alice') + faberProofExchangeRecord = await faberAgent.proofs.negotiateProposal(requestProofAsResponseOptions) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + let request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofExchangeRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/request-presentation', + id: expect.any(String), + requestPresentationsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + thread: { + threadId: faberProofExchangeRecord.threadId, + }, + }) + expect(aliceProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: 'v2', + }) + + testLogger.test('Alice sends proof proposal to Faber') + + faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofExchangeRecord = await aliceAgent.proofs.negotiateRequest({ + proofRecordId: aliceProofExchangeRecord.id, + proofFormats: { + indy: { + name: 'proof-request', + nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + version: '1.0', + attributes: presentationPreview.attributes.filter((attribute) => attribute.name === 'name'), + predicates: presentationPreview.predicates, + }, + }, + comment: 'V2 propose proof test 2', + }) + + testLogger.test('Faber waits for presentation from Alice') + faberProofExchangeRecord = await faberProofExchangeRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofExchangeRecord.id, + messageClass: V2ProposalPresentationMessage, + }) + + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/propose-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION_PROPOSAL, + }, + ], + proposalsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + comment: 'V2 propose proof test 2', + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + proposalAttach = proposal?.proposalsAttach[0].getDataAsJson() as any + attributesGroup = Object.keys(proposalAttach.requested_attributes ?? {})[0] + predicatesGroup = Object.keys(proposalAttach.requested_predicates ?? {})[0] + expect(proposalAttach).toMatchObject({ + requested_attributes: { + [attributesGroup]: { + name: 'name', + restrictions: [ + { + cred_def_id: presentationPreview.attributes[1].credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + [predicatesGroup]: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: presentationPreview.predicates[0].credentialDefinitionId, + }, + ], + }, + }, + }) + expect(faberProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v2', + }) + + // Accept Proposal + const acceptProposalOptions: AcceptProofProposalOptions = { + proofRecordId: faberProofExchangeRecord.id, + } + + aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + }) + + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { + associatedRecordId: faberProofExchangeRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/request-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION_REQUEST, + }, + ], + requestPresentationsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + thread: { + threadId: faberProofExchangeRecord.threadId, + }, + }) + expect(aliceProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: 'v2', + }) + + const presentationProposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofExchangeRecord.id) + + expect(presentationProposalMessage).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/propose-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: V2_INDY_PRESENTATION_PROPOSAL, + }, + ], + proposalsAttach: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + comment: 'V2 propose proof test 2', + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + proposalAttach = proposal?.proposalsAttach[0].getDataAsJson() as any + attributesGroup = Object.keys(proposalAttach.requested_attributes ?? {})[0] + predicatesGroup = Object.keys(proposalAttach.requested_predicates ?? {})[0] + expect(proposalAttach).toMatchObject({ + requested_attributes: { + [attributesGroup]: { + name: 'name', + restrictions: [ + { + cred_def_id: presentationPreview.attributes[1].credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + [predicatesGroup]: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: presentationPreview.predicates[0].credentialDefinitionId, + }, + ], + }, + }, + }) + + const proofRequestMessage = (await aliceAgent.proofs.findRequestMessage( + aliceProofExchangeRecord.id + )) as V2RequestPresentationMessage + + const proofRequest = JsonTransformer.fromJSON( + proofRequestMessage.requestPresentationsAttach[0].getDataAsJson(), + ProofRequest + ) + const predicateKey = proofRequest.requestedPredicates?.keys().next().value + const predicate = Object.values(predicates)[0] + + expect(proofRequest).toMatchObject({ + name: 'proof-request', + nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + version: '1.0', + requestedAttributes: new Map( + Object.entries({ + '0': new ProofAttributeInfo({ + name: 'name', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + }) + ), + requestedPredicates: new Map( + Object.entries({ + [predicateKey]: predicate, + }) + ), + }) + }) +}) diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index a8b89bfaec..b236690458 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -71,6 +71,8 @@ export const genesisPath = process.env.GENESIS_TXN_PATH : path.join(__dirname, '../../../network/genesis/local-genesis.txn') export const publicDidSeed = process.env.TEST_AGENT_PUBLIC_DID_SEED ?? '000000000000000000000000Trustee9' +const taaVersion = (process.env.TEST_AGENT_TAA_VERSION ?? '1') as `${number}.${number}` | `${number}` +const taaAcceptanceMechanism = process.env.TEST_AGENT_TAA_ACCEPTANCE_MECHANISM ?? 'accept' export { agentDependencies } export function getAgentOptions( @@ -93,7 +95,7 @@ export function getAgentOptions( isProduction: false, genesisPath, indyNamespace: `pool:localtest`, - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, }, ], // TODO: determine the log level based on an environment variable. This will make it From 0e89e6c9f4a3cdbf98c5d85de2e015becdc3e1fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Przytu=C5=82a?= Date: Wed, 14 Dec 2022 15:25:35 +0100 Subject: [PATCH 094/125] fix: credential values encoding (#1157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Łukasz Przytuła --- .../credentials/formats/indy/IndyCredentialUtils.ts | 12 +++++++++++- .../indy/__tests__/IndyCredentialUtils.test.ts | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialUtils.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialUtils.ts index 0badb0c735..34042333e8 100644 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialUtils.ts +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialUtils.ts @@ -156,7 +156,13 @@ export class IndyCredentialUtils { } // If value is an int32 number string return as number string - if (isString(value) && !isEmpty(value) && !isNaN(Number(value)) && this.isInt32(Number(value))) { + if ( + isString(value) && + !isEmpty(value) && + !isNaN(Number(value)) && + this.isNumeric(value) && + this.isInt32(Number(value)) + ) { return Number(value).toString() } @@ -194,4 +200,8 @@ export class IndyCredentialUtils { // Check if number is integer and in range of int32 return Number.isInteger(number) && number >= minI32 && number <= maxI32 } + + private static isNumeric(value: string) { + return /^-?\d+$/.test(value) + } } diff --git a/packages/core/src/modules/credentials/formats/indy/__tests__/IndyCredentialUtils.test.ts b/packages/core/src/modules/credentials/formats/indy/__tests__/IndyCredentialUtils.test.ts index 7d629d2f66..e89a849001 100644 --- a/packages/core/src/modules/credentials/formats/indy/__tests__/IndyCredentialUtils.test.ts +++ b/packages/core/src/modules/credentials/formats/indy/__tests__/IndyCredentialUtils.test.ts @@ -74,6 +74,14 @@ const testEncodings: { [key: string]: { raw: string | number | boolean | null; e raw: '0.1', encoded: '9382477430624249591204401974786823110077201914483282671737639310288175260432', }, + 'str 1.0': { + raw: '1.0', + encoded: '94532235908853478633102631881008651863941875830027892478278578250784387892726', + }, + 'str 1': { + raw: '1', + encoded: '1', + }, 'leading zero number string': { raw: '012345', encoded: '12345', From e4e5ca12d377064f1d21e808663298f6c67ca135 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 15 Dec 2022 21:09:42 +0800 Subject: [PATCH 095/125] refactor(action-menu): clearly mark public api (#1163) Signed-off-by: Timo Glastra --- packages/action-menu/src/ActionMenuApi.ts | 3 +++ packages/action-menu/src/ActionMenuApiOptions.ts | 15 +++++++++++++++ packages/action-menu/src/ActionMenuEvents.ts | 7 +++++++ packages/action-menu/src/ActionMenuModule.ts | 3 +++ packages/action-menu/src/ActionMenuRole.ts | 1 + packages/action-menu/src/ActionMenuState.ts | 1 + .../src/__tests__/ActionMenuModule.test.ts | 12 +++++------- .../src/errors/ActionMenuProblemReportError.ts | 7 +++++++ .../src/errors/ActionMenuProblemReportReason.ts | 1 + .../handlers/ActionMenuProblemReportHandler.ts | 3 +++ .../src/handlers/MenuMessageHandler.ts | 3 +++ .../src/handlers/MenuRequestMessageHandler.ts | 3 +++ .../src/handlers/PerformMessageHandler.ts | 3 +++ packages/action-menu/src/index.ts | 4 +--- .../messages/ActionMenuProblemReportMessage.ts | 1 + packages/action-menu/src/messages/MenuMessage.ts | 6 ++++++ .../src/messages/MenuRequestMessage.ts | 6 ++++++ .../action-menu/src/messages/PerformMessage.ts | 6 ++++++ packages/action-menu/src/models/ActionMenu.ts | 6 ++++++ .../action-menu/src/models/ActionMenuOption.ts | 6 ++++++ .../src/models/ActionMenuOptionForm.ts | 6 ++++++ .../src/models/ActionMenuOptionFormParameter.ts | 9 +++++++++ .../action-menu/src/models/ActionMenuSelection.ts | 6 ++++++ .../src/repository/ActionMenuRecord.ts | 12 ++++++++++++ .../src/repository/ActionMenuRepository.ts | 3 +++ .../action-menu/src/services/ActionMenuService.ts | 5 ++++- .../src/services/ActionMenuServiceOptions.ts | 15 +++++++++++++++ 27 files changed, 142 insertions(+), 11 deletions(-) diff --git a/packages/action-menu/src/ActionMenuApi.ts b/packages/action-menu/src/ActionMenuApi.ts index c8569894c7..af91cba0b4 100644 --- a/packages/action-menu/src/ActionMenuApi.ts +++ b/packages/action-menu/src/ActionMenuApi.ts @@ -25,6 +25,9 @@ import { } from './handlers' import { ActionMenuService } from './services' +/** + * @public + */ @injectable() export class ActionMenuApi { private connectionService: ConnectionService diff --git a/packages/action-menu/src/ActionMenuApiOptions.ts b/packages/action-menu/src/ActionMenuApiOptions.ts index b4aea64a57..877e5fab74 100644 --- a/packages/action-menu/src/ActionMenuApiOptions.ts +++ b/packages/action-menu/src/ActionMenuApiOptions.ts @@ -1,25 +1,40 @@ import type { ActionMenuRole } from './ActionMenuRole' import type { ActionMenu, ActionMenuSelection } from './models' +/** + * @public + */ export interface FindActiveMenuOptions { connectionId: string role: ActionMenuRole } +/** + * @public + */ export interface ClearActiveMenuOptions { connectionId: string role: ActionMenuRole } +/** + * @public + */ export interface RequestMenuOptions { connectionId: string } +/** + * @public + */ export interface SendMenuOptions { connectionId: string menu: ActionMenu } +/** + * @public + */ export interface PerformActionOptions { connectionId: string performedAction: ActionMenuSelection diff --git a/packages/action-menu/src/ActionMenuEvents.ts b/packages/action-menu/src/ActionMenuEvents.ts index e0a052987b..e191e84f53 100644 --- a/packages/action-menu/src/ActionMenuEvents.ts +++ b/packages/action-menu/src/ActionMenuEvents.ts @@ -2,9 +2,16 @@ import type { ActionMenuState } from './ActionMenuState' import type { ActionMenuRecord } from './repository' import type { BaseEvent } from '@aries-framework/core' +/** + * @public + */ export enum ActionMenuEventTypes { ActionMenuStateChanged = 'ActionMenuStateChanged', } + +/** + * @public + */ export interface ActionMenuStateChangedEvent extends BaseEvent { type: typeof ActionMenuEventTypes.ActionMenuStateChanged payload: { diff --git a/packages/action-menu/src/ActionMenuModule.ts b/packages/action-menu/src/ActionMenuModule.ts index 09b98d4dbb..11e209a670 100644 --- a/packages/action-menu/src/ActionMenuModule.ts +++ b/packages/action-menu/src/ActionMenuModule.ts @@ -7,6 +7,9 @@ import { ActionMenuRole } from './ActionMenuRole' import { ActionMenuRepository } from './repository' import { ActionMenuService } from './services' +/** + * @public + */ export class ActionMenuModule implements Module { public readonly api = ActionMenuApi diff --git a/packages/action-menu/src/ActionMenuRole.ts b/packages/action-menu/src/ActionMenuRole.ts index f4ef73f56c..a73d9351aa 100644 --- a/packages/action-menu/src/ActionMenuRole.ts +++ b/packages/action-menu/src/ActionMenuRole.ts @@ -2,6 +2,7 @@ * Action Menu roles based on the flow defined in RFC 0509. * * @see https://github.com/hyperledger/aries-rfcs/tree/main/features/0509-action-menu#roles + * @public */ export enum ActionMenuRole { Requester = 'requester', diff --git a/packages/action-menu/src/ActionMenuState.ts b/packages/action-menu/src/ActionMenuState.ts index bf158c9b26..da19f40686 100644 --- a/packages/action-menu/src/ActionMenuState.ts +++ b/packages/action-menu/src/ActionMenuState.ts @@ -2,6 +2,7 @@ * Action Menu states based on the flow defined in RFC 0509. * * @see https://github.com/hyperledger/aries-rfcs/tree/main/features/0509-action-menu#states + * @public */ export enum ActionMenuState { Null = 'null', diff --git a/packages/action-menu/src/__tests__/ActionMenuModule.test.ts b/packages/action-menu/src/__tests__/ActionMenuModule.test.ts index ff53ca1221..b95df86d28 100644 --- a/packages/action-menu/src/__tests__/ActionMenuModule.test.ts +++ b/packages/action-menu/src/__tests__/ActionMenuModule.test.ts @@ -2,13 +2,11 @@ import type { DependencyManager, FeatureRegistry } from '@aries-framework/core' import { Protocol } from '@aries-framework/core' -import { - ActionMenuApi, - ActionMenuModule, - ActionMenuRepository, - ActionMenuRole, - ActionMenuService, -} from '@aries-framework/action-menu' +import { ActionMenuApi } from '../ActionMenuApi' +import { ActionMenuModule } from '../ActionMenuModule' +import { ActionMenuRole } from '../ActionMenuRole' +import { ActionMenuRepository } from '../repository' +import { ActionMenuService } from '../services' const dependencyManager = { registerInstance: jest.fn(), diff --git a/packages/action-menu/src/errors/ActionMenuProblemReportError.ts b/packages/action-menu/src/errors/ActionMenuProblemReportError.ts index 23a5058cd3..70e418f3c7 100644 --- a/packages/action-menu/src/errors/ActionMenuProblemReportError.ts +++ b/packages/action-menu/src/errors/ActionMenuProblemReportError.ts @@ -5,9 +5,16 @@ import { ProblemReportError } from '@aries-framework/core' import { ActionMenuProblemReportMessage } from '../messages' +/** + * @internal + */ interface ActionMenuProblemReportErrorOptions extends ProblemReportErrorOptions { problemCode: ActionMenuProblemReportReason } + +/** + * @internal + */ export class ActionMenuProblemReportError extends ProblemReportError { public problemReport: ActionMenuProblemReportMessage diff --git a/packages/action-menu/src/errors/ActionMenuProblemReportReason.ts b/packages/action-menu/src/errors/ActionMenuProblemReportReason.ts index 97e18b9245..c015c5507f 100644 --- a/packages/action-menu/src/errors/ActionMenuProblemReportReason.ts +++ b/packages/action-menu/src/errors/ActionMenuProblemReportReason.ts @@ -2,6 +2,7 @@ * Action Menu errors discussed in RFC 0509. * * @see https://github.com/hyperledger/aries-rfcs/tree/main/features/0509-action-menu#unresolved-questions + * @internal */ export enum ActionMenuProblemReportReason { Timeout = 'timeout', diff --git a/packages/action-menu/src/handlers/ActionMenuProblemReportHandler.ts b/packages/action-menu/src/handlers/ActionMenuProblemReportHandler.ts index 6f8d410365..0687184110 100644 --- a/packages/action-menu/src/handlers/ActionMenuProblemReportHandler.ts +++ b/packages/action-menu/src/handlers/ActionMenuProblemReportHandler.ts @@ -3,6 +3,9 @@ import type { Handler, HandlerInboundMessage } from '@aries-framework/core' import { ActionMenuProblemReportMessage } from '../messages' +/** + * @internal + */ export class ActionMenuProblemReportHandler implements Handler { private actionMenuService: ActionMenuService public supportedMessages = [ActionMenuProblemReportMessage] diff --git a/packages/action-menu/src/handlers/MenuMessageHandler.ts b/packages/action-menu/src/handlers/MenuMessageHandler.ts index 73e596f65d..e3436d3491 100644 --- a/packages/action-menu/src/handlers/MenuMessageHandler.ts +++ b/packages/action-menu/src/handlers/MenuMessageHandler.ts @@ -3,6 +3,9 @@ import type { Handler, HandlerInboundMessage } from '@aries-framework/core' import { MenuMessage } from '../messages' +/** + * @internal + */ export class MenuMessageHandler implements Handler { private actionMenuService: ActionMenuService public supportedMessages = [MenuMessage] diff --git a/packages/action-menu/src/handlers/MenuRequestMessageHandler.ts b/packages/action-menu/src/handlers/MenuRequestMessageHandler.ts index 7e10aed8f5..07febb2026 100644 --- a/packages/action-menu/src/handlers/MenuRequestMessageHandler.ts +++ b/packages/action-menu/src/handlers/MenuRequestMessageHandler.ts @@ -3,6 +3,9 @@ import type { Handler, HandlerInboundMessage } from '@aries-framework/core' import { MenuRequestMessage } from '../messages' +/** + * @internal + */ export class MenuRequestMessageHandler implements Handler { private actionMenuService: ActionMenuService public supportedMessages = [MenuRequestMessage] diff --git a/packages/action-menu/src/handlers/PerformMessageHandler.ts b/packages/action-menu/src/handlers/PerformMessageHandler.ts index fae36cb189..65144b3538 100644 --- a/packages/action-menu/src/handlers/PerformMessageHandler.ts +++ b/packages/action-menu/src/handlers/PerformMessageHandler.ts @@ -3,6 +3,9 @@ import type { Handler, HandlerInboundMessage } from '@aries-framework/core' import { PerformMessage } from '../messages' +/** + * @internal + */ export class PerformMessageHandler implements Handler { private actionMenuService: ActionMenuService public supportedMessages = [PerformMessage] diff --git a/packages/action-menu/src/index.ts b/packages/action-menu/src/index.ts index 3183ffd412..204d9dc359 100644 --- a/packages/action-menu/src/index.ts +++ b/packages/action-menu/src/index.ts @@ -4,7 +4,5 @@ export * from './ActionMenuModule' export * from './ActionMenuEvents' export * from './ActionMenuRole' export * from './ActionMenuState' -export * from './messages' export * from './models' -export * from './repository' -export * from './services' +export * from './repository/ActionMenuRecord' diff --git a/packages/action-menu/src/messages/ActionMenuProblemReportMessage.ts b/packages/action-menu/src/messages/ActionMenuProblemReportMessage.ts index 099f7172c1..aea7a60b0d 100644 --- a/packages/action-menu/src/messages/ActionMenuProblemReportMessage.ts +++ b/packages/action-menu/src/messages/ActionMenuProblemReportMessage.ts @@ -6,6 +6,7 @@ export type ActionMenuProblemReportMessageOptions = ProblemReportMessageOptions /** * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md + * @internal */ export class ActionMenuProblemReportMessage extends ProblemReportMessage { /** diff --git a/packages/action-menu/src/messages/MenuMessage.ts b/packages/action-menu/src/messages/MenuMessage.ts index 74d8b11c1f..77f8cbfd55 100644 --- a/packages/action-menu/src/messages/MenuMessage.ts +++ b/packages/action-menu/src/messages/MenuMessage.ts @@ -6,6 +6,9 @@ import { IsInstance, IsOptional, IsString } from 'class-validator' import { ActionMenuOption } from '../models' +/** + * @internal + */ export interface MenuMessageOptions { id?: string title: string @@ -15,6 +18,9 @@ export interface MenuMessageOptions { threadId?: string } +/** + * @internal + */ export class MenuMessage extends AgentMessage { public constructor(options: MenuMessageOptions) { super() diff --git a/packages/action-menu/src/messages/MenuRequestMessage.ts b/packages/action-menu/src/messages/MenuRequestMessage.ts index 4eede7e578..461304de98 100644 --- a/packages/action-menu/src/messages/MenuRequestMessage.ts +++ b/packages/action-menu/src/messages/MenuRequestMessage.ts @@ -1,9 +1,15 @@ import { AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' +/** + * @internal + */ export interface MenuRequestMessageOptions { id?: string } +/** + * @internal + */ export class MenuRequestMessage extends AgentMessage { public constructor(options: MenuRequestMessageOptions) { super() diff --git a/packages/action-menu/src/messages/PerformMessage.ts b/packages/action-menu/src/messages/PerformMessage.ts index 6e9b081df8..af8d2f14f3 100644 --- a/packages/action-menu/src/messages/PerformMessage.ts +++ b/packages/action-menu/src/messages/PerformMessage.ts @@ -1,6 +1,9 @@ import { AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { IsOptional, IsString } from 'class-validator' +/** + * @internal + */ export interface PerformMessageOptions { id?: string name: string @@ -8,6 +11,9 @@ export interface PerformMessageOptions { threadId: string } +/** + * @internal + */ export class PerformMessage extends AgentMessage { public constructor(options: PerformMessageOptions) { super() diff --git a/packages/action-menu/src/models/ActionMenu.ts b/packages/action-menu/src/models/ActionMenu.ts index 1123394796..9e241693e3 100644 --- a/packages/action-menu/src/models/ActionMenu.ts +++ b/packages/action-menu/src/models/ActionMenu.ts @@ -5,12 +5,18 @@ import { IsInstance, IsString } from 'class-validator' import { ActionMenuOption } from './ActionMenuOption' +/** + * @public + */ export interface ActionMenuOptions { title: string description: string options: ActionMenuOptionOptions[] } +/** + * @public + */ export class ActionMenu { public constructor(options: ActionMenuOptions) { if (options) { diff --git a/packages/action-menu/src/models/ActionMenuOption.ts b/packages/action-menu/src/models/ActionMenuOption.ts index 1418c61e6c..ce283c9473 100644 --- a/packages/action-menu/src/models/ActionMenuOption.ts +++ b/packages/action-menu/src/models/ActionMenuOption.ts @@ -5,6 +5,9 @@ import { IsBoolean, IsInstance, IsOptional, IsString } from 'class-validator' import { ActionMenuForm } from './ActionMenuOptionForm' +/** + * @public + */ export interface ActionMenuOptionOptions { name: string title: string @@ -13,6 +16,9 @@ export interface ActionMenuOptionOptions { form?: ActionMenuFormOptions } +/** + * @public + */ export class ActionMenuOption { public constructor(options: ActionMenuOptionOptions) { if (options) { diff --git a/packages/action-menu/src/models/ActionMenuOptionForm.ts b/packages/action-menu/src/models/ActionMenuOptionForm.ts index 07a027a0a1..3c9f4e1e08 100644 --- a/packages/action-menu/src/models/ActionMenuOptionForm.ts +++ b/packages/action-menu/src/models/ActionMenuOptionForm.ts @@ -5,12 +5,18 @@ import { IsInstance, IsString } from 'class-validator' import { ActionMenuFormParameter } from './ActionMenuOptionFormParameter' +/** + * @public + */ export interface ActionMenuFormOptions { description: string params: ActionMenuFormParameterOptions[] submitLabel: string } +/** + * @public + */ export class ActionMenuForm { public constructor(options: ActionMenuFormOptions) { if (options) { diff --git a/packages/action-menu/src/models/ActionMenuOptionFormParameter.ts b/packages/action-menu/src/models/ActionMenuOptionFormParameter.ts index 2c66ac39dc..dfcce82848 100644 --- a/packages/action-menu/src/models/ActionMenuOptionFormParameter.ts +++ b/packages/action-menu/src/models/ActionMenuOptionFormParameter.ts @@ -1,9 +1,15 @@ import { IsBoolean, IsEnum, IsOptional, IsString } from 'class-validator' +/** + * @public + */ export enum ActionMenuFormInputType { Text = 'text', } +/** + * @public + */ export interface ActionMenuFormParameterOptions { name: string title: string @@ -13,6 +19,9 @@ export interface ActionMenuFormParameterOptions { type?: ActionMenuFormInputType } +/** + * @public + */ export class ActionMenuFormParameter { public constructor(options: ActionMenuFormParameterOptions) { if (options) { diff --git a/packages/action-menu/src/models/ActionMenuSelection.ts b/packages/action-menu/src/models/ActionMenuSelection.ts index ff4299da6d..f25c361b41 100644 --- a/packages/action-menu/src/models/ActionMenuSelection.ts +++ b/packages/action-menu/src/models/ActionMenuSelection.ts @@ -1,10 +1,16 @@ import { IsOptional, IsString } from 'class-validator' +/** + * @public + */ export interface ActionMenuSelectionOptions { name: string params?: Record } +/** + * @public + */ export class ActionMenuSelection { public constructor(options: ActionMenuSelectionOptions) { if (options) { diff --git a/packages/action-menu/src/repository/ActionMenuRecord.ts b/packages/action-menu/src/repository/ActionMenuRecord.ts index 0560ef4559..da906e3524 100644 --- a/packages/action-menu/src/repository/ActionMenuRecord.ts +++ b/packages/action-menu/src/repository/ActionMenuRecord.ts @@ -7,6 +7,9 @@ import { Type } from 'class-transformer' import { ActionMenuSelection, ActionMenu } from '../models' +/** + * @public + */ export interface ActionMenuRecordProps { id?: string state: ActionMenuState @@ -19,14 +22,23 @@ export interface ActionMenuRecordProps { tags?: CustomActionMenuTags } +/** + * @public + */ export type CustomActionMenuTags = TagsBase +/** + * @public + */ export type DefaultActionMenuTags = { role: ActionMenuRole connectionId: string threadId: string } +/** + * @public + */ export class ActionMenuRecord extends BaseRecord implements ActionMenuRecordProps diff --git a/packages/action-menu/src/repository/ActionMenuRepository.ts b/packages/action-menu/src/repository/ActionMenuRepository.ts index 2337fd12c6..47dd52d172 100644 --- a/packages/action-menu/src/repository/ActionMenuRepository.ts +++ b/packages/action-menu/src/repository/ActionMenuRepository.ts @@ -2,6 +2,9 @@ import { EventEmitter, InjectionSymbols, inject, injectable, Repository, Storage import { ActionMenuRecord } from './ActionMenuRecord' +/** + * @internal + */ @injectable() export class ActionMenuRepository extends Repository { public constructor( diff --git a/packages/action-menu/src/services/ActionMenuService.ts b/packages/action-menu/src/services/ActionMenuService.ts index c2561f4617..89c27f54a4 100644 --- a/packages/action-menu/src/services/ActionMenuService.ts +++ b/packages/action-menu/src/services/ActionMenuService.ts @@ -20,6 +20,9 @@ import { PerformMessage, MenuMessage, MenuRequestMessage } from '../messages' import { ActionMenuSelection, ActionMenu } from '../models' import { ActionMenuRepository, ActionMenuRecord } from '../repository' +/** + * @internal + */ @injectable() export class ActionMenuService { private actionMenuRepository: ActionMenuRepository @@ -39,7 +42,7 @@ export class ActionMenuService { // Create message const menuRequestMessage = new MenuRequestMessage({}) - // Create record if not existant for connection/role + // Create record if not existent for connection/role let actionMenuRecord = await this.find(agentContext, { connectionId: options.connection.id, role: ActionMenuRole.Requester, diff --git a/packages/action-menu/src/services/ActionMenuServiceOptions.ts b/packages/action-menu/src/services/ActionMenuServiceOptions.ts index 3a7faa0fd3..0cac1efeac 100644 --- a/packages/action-menu/src/services/ActionMenuServiceOptions.ts +++ b/packages/action-menu/src/services/ActionMenuServiceOptions.ts @@ -4,24 +4,39 @@ import type { ActionMenu } from '../models/ActionMenu' import type { ActionMenuRecord } from '../repository' import type { ConnectionRecord } from '@aries-framework/core' +/** + * @internal + */ export interface CreateRequestOptions { connection: ConnectionRecord } +/** + * @internal + */ export interface CreateMenuOptions { connection: ConnectionRecord menu: ActionMenu } +/** + * @internal + */ export interface CreatePerformOptions { actionMenuRecord: ActionMenuRecord performedAction: ActionMenuSelection } +/** + * @internal + */ export interface ClearMenuOptions { actionMenuRecord: ActionMenuRecord } +/** + * @internal + */ export interface FindMenuOptions { connectionId: string role: ActionMenuRole From 5e48696ec16d88321f225628e6cffab243718b4c Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 16 Dec 2022 15:45:27 +0800 Subject: [PATCH 096/125] refactor!: rename Handler to MessageHandler (#1161) Signed-off-by: Timo Glastra BREAKING CHANGE: Handler has been renamed to MessageHandler to be more descriptive, along with related types and methods. This means: Handler is now MessageHandler HandlerInboundMessage is now MessageHandlerInboundMessage Dispatcher.registerHandler is now Dispatcher.registerMessageHandlers --- packages/action-menu/src/ActionMenuApi.ts | 12 ++++---- .../ActionMenuProblemReportHandler.ts | 6 ++-- .../src/handlers/MenuMessageHandler.ts | 6 ++-- .../src/handlers/MenuRequestMessageHandler.ts | 6 ++-- .../src/handlers/PerformMessageHandler.ts | 6 ++-- packages/core/src/agent/Dispatcher.ts | 28 +++++++++---------- .../agent/{Handler.ts => MessageHandler.ts} | 6 ++-- .../src/agent/__tests__/Dispatcher.test.ts | 16 +++++------ packages/core/src/index.ts | 2 +- .../basic-messages/BasicMessagesApi.ts | 6 ++-- .../handlers/BasicMessageHandler.ts | 6 ++-- .../src/modules/connections/ConnectionsApi.ts | 20 ++++++------- .../connections/handlers/AckMessageHandler.ts | 6 ++-- .../ConnectionProblemReportHandler.ts | 6 ++-- .../handlers/ConnectionRequestHandler.ts | 6 ++-- .../handlers/ConnectionResponseHandler.ts | 6 ++-- .../handlers/DidExchangeCompleteHandler.ts | 6 ++-- .../handlers/DidExchangeRequestHandler.ts | 6 ++-- .../handlers/DidExchangeResponseHandler.ts | 6 ++-- .../handlers/TrustPingMessageHandler.ts | 6 ++-- .../TrustPingResponseMessageHandler.ts | 6 ++-- .../V1RevocationNotificationHandler.ts | 6 ++-- .../V2RevocationNotificationHandler.ts | 6 ++-- .../services/RevocationNotificationService.ts | 8 +++--- .../protocol/v1/V1CredentialService.ts | 20 +++++++------ .../v1/handlers/V1CredentialAckHandler.ts | 6 ++-- .../V1CredentialProblemReportHandler.ts | 6 ++-- .../v1/handlers/V1IssueCredentialHandler.ts | 8 +++--- .../v1/handlers/V1OfferCredentialHandler.ts | 8 +++--- .../v1/handlers/V1ProposeCredentialHandler.ts | 8 +++--- .../v1/handlers/V1RequestCredentialHandler.ts | 8 +++--- .../protocol/v2/V2CredentialService.ts | 24 +++++++++------- .../v2/handlers/V2CredentialAckHandler.ts | 6 ++-- .../V2CredentialProblemReportHandler.ts | 6 ++-- .../v2/handlers/V2IssueCredentialHandler.ts | 6 ++-- .../v2/handlers/V2OfferCredentialHandler.ts | 6 ++-- .../v2/handlers/V2ProposeCredentialHandler.ts | 6 ++-- .../v2/handlers/V2RequestCredentialHandler.ts | 4 +-- .../protocol/v1/V1DiscoverFeaturesService.ts | 8 +++--- .../v1/handlers/V1DiscloseMessageHandler.ts | 6 ++-- .../v1/handlers/V1QueryMessageHandler.ts | 6 ++-- .../protocol/v2/V2DiscoverFeaturesService.ts | 8 +++--- .../handlers/V2DisclosuresMessageHandler.ts | 6 ++-- .../v2/handlers/V2QueriesMessageHandler.ts | 6 ++-- packages/core/src/modules/oob/OutOfBandApi.ts | 8 +++--- .../handlers/HandshakeReuseAcceptedHandler.ts | 4 +-- .../oob/handlers/HandshakeReuseHandler.ts | 4 +-- .../core/src/modules/proofs/ProofService.ts | 2 +- packages/core/src/modules/proofs/ProofsApi.ts | 6 ++-- .../proofs/protocol/v1/V1ProofService.ts | 12 ++++---- .../v1/handlers/V1PresentationAckHandler.ts | 6 ++-- .../v1/handlers/V1PresentationHandler.ts | 11 +++++--- .../V1PresentationProblemReportHandler.ts | 6 ++-- .../handlers/V1ProposePresentationHandler.ts | 8 +++--- .../handlers/V1RequestPresentationHandler.ts | 8 +++--- .../proofs/protocol/v2/V2ProofService.ts | 12 ++++---- .../v2/handlers/V2PresentationAckHandler.ts | 6 ++-- .../v2/handlers/V2PresentationHandler.ts | 11 +++++--- .../V2PresentationProblemReportHandler.ts | 6 ++-- .../handlers/V2ProposePresentationHandler.ts | 8 +++--- .../handlers/V2RequestPresentationHandler.ts | 8 +++--- .../core/src/modules/routing/MediatorApi.ts | 16 ++++++----- .../core/src/modules/routing/RecipientApi.ts | 16 +++++------ .../routing/handlers/ForwardHandler.ts | 6 ++-- .../routing/handlers/KeylistUpdateHandler.ts | 6 ++-- .../handlers/KeylistUpdateResponseHandler.ts | 6 ++-- .../routing/handlers/MediationDenyHandler.ts | 6 ++-- .../routing/handlers/MediationGrantHandler.ts | 6 ++-- .../handlers/MediationRequestHandler.ts | 6 ++-- .../pickup/v1/MessagePickupService.ts | 8 +++--- .../pickup/v1/handlers/BatchHandler.ts | 6 ++-- .../pickup/v1/handlers/BatchPickupHandler.ts | 6 ++-- .../pickup/v2/V2MessagePickupService.ts | 14 +++++----- .../v2/handlers/DeliveryRequestHandler.ts | 4 +-- .../v2/handlers/MessageDeliveryHandler.ts | 4 +-- .../v2/handlers/MessagesReceivedHandler.ts | 4 +-- .../pickup/v2/handlers/StatusHandler.ts | 4 +-- .../v2/handlers/StatusRequestHandler.ts | 4 +-- .../core/tests/multi-protocol-version.test.ts | 2 +- .../question-answer/src/QuestionAnswerApi.ts | 8 +++--- .../src/handlers/AnswerMessageHandler.ts | 6 ++-- .../src/handlers/QuestionMessageHandler.ts | 6 ++-- samples/extension-module/README.md | 2 +- samples/extension-module/dummy/DummyApi.ts | 8 +++--- .../dummy/handlers/DummyRequestHandler.ts | 6 ++-- .../dummy/handlers/DummyResponseHandler.ts | 6 ++-- 86 files changed, 332 insertions(+), 316 deletions(-) rename packages/core/src/agent/{Handler.ts => MessageHandler.ts} (71%) diff --git a/packages/action-menu/src/ActionMenuApi.ts b/packages/action-menu/src/ActionMenuApi.ts index af91cba0b4..bb6f3cd4f3 100644 --- a/packages/action-menu/src/ActionMenuApi.ts +++ b/packages/action-menu/src/ActionMenuApi.ts @@ -46,7 +46,7 @@ export class ActionMenuApi { this.messageSender = messageSender this.actionMenuService = actionMenuService this.agentContext = agentContext - this.registerHandlers(dispatcher) + this.registerMessageHandlers(dispatcher) } /** @@ -161,10 +161,10 @@ export class ActionMenuApi { return actionMenuRecord ? await this.actionMenuService.clearMenu(this.agentContext, { actionMenuRecord }) : null } - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new ActionMenuProblemReportHandler(this.actionMenuService)) - dispatcher.registerHandler(new MenuMessageHandler(this.actionMenuService)) - dispatcher.registerHandler(new MenuRequestMessageHandler(this.actionMenuService)) - dispatcher.registerHandler(new PerformMessageHandler(this.actionMenuService)) + private registerMessageHandlers(dispatcher: Dispatcher) { + dispatcher.registerMessageHandler(new ActionMenuProblemReportHandler(this.actionMenuService)) + dispatcher.registerMessageHandler(new MenuMessageHandler(this.actionMenuService)) + dispatcher.registerMessageHandler(new MenuRequestMessageHandler(this.actionMenuService)) + dispatcher.registerMessageHandler(new PerformMessageHandler(this.actionMenuService)) } } diff --git a/packages/action-menu/src/handlers/ActionMenuProblemReportHandler.ts b/packages/action-menu/src/handlers/ActionMenuProblemReportHandler.ts index 0687184110..e5d111f899 100644 --- a/packages/action-menu/src/handlers/ActionMenuProblemReportHandler.ts +++ b/packages/action-menu/src/handlers/ActionMenuProblemReportHandler.ts @@ -1,12 +1,12 @@ import type { ActionMenuService } from '../services' -import type { Handler, HandlerInboundMessage } from '@aries-framework/core' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { ActionMenuProblemReportMessage } from '../messages' /** * @internal */ -export class ActionMenuProblemReportHandler implements Handler { +export class ActionMenuProblemReportHandler implements MessageHandler { private actionMenuService: ActionMenuService public supportedMessages = [ActionMenuProblemReportMessage] @@ -14,7 +14,7 @@ export class ActionMenuProblemReportHandler implements Handler { this.actionMenuService = actionMenuService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.actionMenuService.processProblemReport(messageContext) } } diff --git a/packages/action-menu/src/handlers/MenuMessageHandler.ts b/packages/action-menu/src/handlers/MenuMessageHandler.ts index e3436d3491..972f717c75 100644 --- a/packages/action-menu/src/handlers/MenuMessageHandler.ts +++ b/packages/action-menu/src/handlers/MenuMessageHandler.ts @@ -1,12 +1,12 @@ import type { ActionMenuService } from '../services' -import type { Handler, HandlerInboundMessage } from '@aries-framework/core' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { MenuMessage } from '../messages' /** * @internal */ -export class MenuMessageHandler implements Handler { +export class MenuMessageHandler implements MessageHandler { private actionMenuService: ActionMenuService public supportedMessages = [MenuMessage] @@ -14,7 +14,7 @@ export class MenuMessageHandler implements Handler { this.actionMenuService = actionMenuService } - public async handle(inboundMessage: HandlerInboundMessage) { + public async handle(inboundMessage: MessageHandlerInboundMessage) { inboundMessage.assertReadyConnection() await this.actionMenuService.processMenu(inboundMessage) diff --git a/packages/action-menu/src/handlers/MenuRequestMessageHandler.ts b/packages/action-menu/src/handlers/MenuRequestMessageHandler.ts index 07febb2026..9186def3c2 100644 --- a/packages/action-menu/src/handlers/MenuRequestMessageHandler.ts +++ b/packages/action-menu/src/handlers/MenuRequestMessageHandler.ts @@ -1,12 +1,12 @@ import type { ActionMenuService } from '../services' -import type { Handler, HandlerInboundMessage } from '@aries-framework/core' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { MenuRequestMessage } from '../messages' /** * @internal */ -export class MenuRequestMessageHandler implements Handler { +export class MenuRequestMessageHandler implements MessageHandler { private actionMenuService: ActionMenuService public supportedMessages = [MenuRequestMessage] @@ -14,7 +14,7 @@ export class MenuRequestMessageHandler implements Handler { this.actionMenuService = actionMenuService } - public async handle(inboundMessage: HandlerInboundMessage) { + public async handle(inboundMessage: MessageHandlerInboundMessage) { inboundMessage.assertReadyConnection() await this.actionMenuService.processRequest(inboundMessage) diff --git a/packages/action-menu/src/handlers/PerformMessageHandler.ts b/packages/action-menu/src/handlers/PerformMessageHandler.ts index 65144b3538..c0dc74a011 100644 --- a/packages/action-menu/src/handlers/PerformMessageHandler.ts +++ b/packages/action-menu/src/handlers/PerformMessageHandler.ts @@ -1,12 +1,12 @@ import type { ActionMenuService } from '../services' -import type { Handler, HandlerInboundMessage } from '@aries-framework/core' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { PerformMessage } from '../messages' /** * @internal */ -export class PerformMessageHandler implements Handler { +export class PerformMessageHandler implements MessageHandler { private actionMenuService: ActionMenuService public supportedMessages = [PerformMessage] @@ -14,7 +14,7 @@ export class PerformMessageHandler implements Handler { this.actionMenuService = actionMenuService } - public async handle(inboundMessage: HandlerInboundMessage) { + public async handle(inboundMessage: MessageHandlerInboundMessage) { inboundMessage.assertReadyConnection() await this.actionMenuService.processPerform(inboundMessage) diff --git a/packages/core/src/agent/Dispatcher.ts b/packages/core/src/agent/Dispatcher.ts index acc6bd9a05..c9c4f2c0bc 100644 --- a/packages/core/src/agent/Dispatcher.ts +++ b/packages/core/src/agent/Dispatcher.ts @@ -1,6 +1,6 @@ import type { AgentMessage } from './AgentMessage' import type { AgentMessageProcessedEvent } from './Events' -import type { Handler } from './Handler' +import type { MessageHandler } from './MessageHandler' import type { InboundMessageContext } from './models/InboundMessageContext' import { InjectionSymbols } from '../constants' @@ -17,7 +17,7 @@ import { OutboundMessageContext } from './models' @injectable() class Dispatcher { - private handlers: Handler[] = [] + private messageHandlers: MessageHandler[] = [] private messageSender: MessageSender private eventEmitter: EventEmitter private logger: Logger @@ -32,22 +32,22 @@ class Dispatcher { this.logger = logger } - public registerHandler(handler: Handler) { - this.handlers.push(handler) + public registerMessageHandler(handler: MessageHandler) { + this.messageHandlers.push(handler) } public async dispatch(messageContext: InboundMessageContext): Promise { const { agentContext, connection, senderKey, recipientKey, message } = messageContext - const handler = this.getHandlerForType(message.type) + const messageHandler = this.getMessageHandlerForType(message.type) - if (!handler) { + if (!messageHandler) { throw new AriesFrameworkError(`No handler for message type "${message.type}" found`) } let outboundMessage: OutboundMessageContext | void try { - outboundMessage = await handler.handle(messageContext) + outboundMessage = await messageHandler.handle(messageContext) } catch (error) { const problemReportMessage = error.problemReport @@ -90,12 +90,12 @@ class Dispatcher { }) } - private getHandlerForType(messageType: string): Handler | undefined { + private getMessageHandlerForType(messageType: string): MessageHandler | undefined { const incomingMessageType = parseMessageType(messageType) - for (const handler of this.handlers) { - for (const MessageClass of handler.supportedMessages) { - if (canHandleMessageType(MessageClass, incomingMessageType)) return handler + for (const messageHandler of this.messageHandlers) { + for (const MessageClass of messageHandler.supportedMessages) { + if (canHandleMessageType(MessageClass, incomingMessageType)) return messageHandler } } } @@ -103,8 +103,8 @@ class Dispatcher { public getMessageClassForType(messageType: string): typeof AgentMessage | undefined { const incomingMessageType = parseMessageType(messageType) - for (const handler of this.handlers) { - for (const MessageClass of handler.supportedMessages) { + for (const messageHandler of this.messageHandlers) { + for (const MessageClass of messageHandler.supportedMessages) { if (canHandleMessageType(MessageClass, incomingMessageType)) return MessageClass } } @@ -115,7 +115,7 @@ class Dispatcher { * Message type format is MTURI specified at https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0003-protocols/README.md#mturi. */ public get supportedMessageTypes() { - return this.handlers + return this.messageHandlers .reduce((all, cur) => [...all, ...cur.supportedMessages], []) .map((m) => m.type) } diff --git a/packages/core/src/agent/Handler.ts b/packages/core/src/agent/MessageHandler.ts similarity index 71% rename from packages/core/src/agent/Handler.ts rename to packages/core/src/agent/MessageHandler.ts index e736aad3da..692cea5cc5 100644 --- a/packages/core/src/agent/Handler.ts +++ b/packages/core/src/agent/MessageHandler.ts @@ -1,7 +1,7 @@ import type { ConstructableAgentMessage } from './AgentMessage' import type { InboundMessageContext, OutboundMessageContext } from './models' -export interface Handler { +export interface MessageHandler { readonly supportedMessages: readonly ConstructableAgentMessage[] handle(messageContext: InboundMessageContext): Promise @@ -12,8 +12,8 @@ export interface Handler { * of a handler. It takes all possible types from `supportedMessageTypes` * * @example - * async handle(messageContext: HandlerInboundMessage) {} + * async handle(messageContext: MessageHandlerInboundMessage) {} */ -export type HandlerInboundMessage = InboundMessageContext< +export type MessageHandlerInboundMessage = InboundMessageContext< InstanceType > diff --git a/packages/core/src/agent/__tests__/Dispatcher.test.ts b/packages/core/src/agent/__tests__/Dispatcher.test.ts index 5a735449c6..30f4cfefef 100644 --- a/packages/core/src/agent/__tests__/Dispatcher.test.ts +++ b/packages/core/src/agent/__tests__/Dispatcher.test.ts @@ -1,4 +1,4 @@ -import type { Handler } from '../Handler' +import type { MessageHandler } from '../MessageHandler' import { Subject } from 'rxjs' @@ -34,7 +34,7 @@ class CustomProtocolMessage extends AgentMessage { public static readonly type = parseMessageType('https://didcomm.org/fake-protocol/1.5/message') } -class TestHandler implements Handler { +class TestHandler implements MessageHandler { // We want to pass various classes to test various behaviours so we dont need to strictly type it. // eslint-disable-next-line @typescript-eslint/no-explicit-any public constructor(classes: any[]) { @@ -62,10 +62,10 @@ describe('Dispatcher', () => { const dispatcher = new Dispatcher(new MessageSenderMock(), eventEmitter, agentConfig.logger) - dispatcher.registerHandler(connectionHandler) - dispatcher.registerHandler(new TestHandler([NotificationAckTestMessage])) - dispatcher.registerHandler(new TestHandler([CredentialProposalTestMessage])) - dispatcher.registerHandler(fakeProtocolHandler) + dispatcher.registerMessageHandler(connectionHandler) + dispatcher.registerMessageHandler(new TestHandler([NotificationAckTestMessage])) + dispatcher.registerMessageHandler(new TestHandler([CredentialProposalTestMessage])) + dispatcher.registerMessageHandler(fakeProtocolHandler) describe('supportedMessageTypes', () => { test('return all supported message types URIs', async () => { @@ -146,7 +146,7 @@ describe('Dispatcher', () => { const inboundMessageContext = new InboundMessageContext(customProtocolMessage, { agentContext }) const mockHandle = jest.fn() - dispatcher.registerHandler({ supportedMessages: [CustomProtocolMessage], handle: mockHandle }) + dispatcher.registerMessageHandler({ supportedMessages: [CustomProtocolMessage], handle: mockHandle }) await dispatcher.dispatch(inboundMessageContext) @@ -159,7 +159,7 @@ describe('Dispatcher', () => { const inboundMessageContext = new InboundMessageContext(customProtocolMessage, { agentContext }) const mockHandle = jest.fn() - dispatcher.registerHandler({ supportedMessages: [], handle: mockHandle }) + dispatcher.registerMessageHandler({ supportedMessages: [], handle: mockHandle }) await expect(dispatcher.dispatch(inboundMessageContext)).rejects.toThrow( 'No handler for message type "https://didcomm.org/fake-protocol/1.5/message" found' diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index bc90ed89bc..2090bd358f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -8,7 +8,7 @@ export * from './agent' export type { ModulesMap, DefaultAgentModules, EmptyModuleMap } from './agent/AgentModules' export { EventEmitter } from './agent/EventEmitter' export { FeatureRegistry } from './agent/FeatureRegistry' -export { Handler, HandlerInboundMessage } from './agent/Handler' +export { MessageHandler, MessageHandlerInboundMessage } from './agent/MessageHandler' export * from './agent/models' export { AgentConfig } from './agent/AgentConfig' export { AgentMessage } from './agent/AgentMessage' diff --git a/packages/core/src/modules/basic-messages/BasicMessagesApi.ts b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts index 17bb32d080..816340429d 100644 --- a/packages/core/src/modules/basic-messages/BasicMessagesApi.ts +++ b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts @@ -29,7 +29,7 @@ export class BasicMessagesApi { this.messageSender = messageSender this.connectionService = connectionService this.agentContext = agentContext - this.registerHandlers(dispatcher) + this.registerMessageHandlers(dispatcher) } /** @@ -91,7 +91,7 @@ export class BasicMessagesApi { await this.basicMessageService.deleteById(this.agentContext, basicMessageRecordId) } - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new BasicMessageHandler(this.basicMessageService)) + private registerMessageHandlers(dispatcher: Dispatcher) { + dispatcher.registerMessageHandler(new BasicMessageHandler(this.basicMessageService)) } } diff --git a/packages/core/src/modules/basic-messages/handlers/BasicMessageHandler.ts b/packages/core/src/modules/basic-messages/handlers/BasicMessageHandler.ts index 3e10a3cb0c..cec6931983 100644 --- a/packages/core/src/modules/basic-messages/handlers/BasicMessageHandler.ts +++ b/packages/core/src/modules/basic-messages/handlers/BasicMessageHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { BasicMessageService } from '../services/BasicMessageService' import { BasicMessage } from '../messages' -export class BasicMessageHandler implements Handler { +export class BasicMessageHandler implements MessageHandler { private basicMessageService: BasicMessageService public supportedMessages = [BasicMessage] @@ -11,7 +11,7 @@ export class BasicMessageHandler implements Handler { this.basicMessageService = basicMessageService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const connection = messageContext.assertReadyConnection() await this.basicMessageService.save(messageContext, connection) } diff --git a/packages/core/src/modules/connections/ConnectionsApi.ts b/packages/core/src/modules/connections/ConnectionsApi.ts index 03729dec8d..9b12210091 100644 --- a/packages/core/src/modules/connections/ConnectionsApi.ts +++ b/packages/core/src/modules/connections/ConnectionsApi.ts @@ -73,7 +73,7 @@ export class ConnectionsApi { this.agentContext = agentContext this.config = connectionsModuleConfig - this.registerHandlers(dispatcher) + this.registerMessageHandlers(dispatcher) } public async acceptOutOfBandInvitation( @@ -361,8 +361,8 @@ export class ConnectionsApi { return this.connectionService.findByInvitationDid(this.agentContext, invitationDid) } - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler( + private registerMessageHandlers(dispatcher: Dispatcher) { + dispatcher.registerMessageHandler( new ConnectionRequestHandler( this.connectionService, this.outOfBandService, @@ -371,14 +371,14 @@ export class ConnectionsApi { this.config ) ) - dispatcher.registerHandler( + dispatcher.registerMessageHandler( new ConnectionResponseHandler(this.connectionService, this.outOfBandService, this.didResolverService, this.config) ) - dispatcher.registerHandler(new AckMessageHandler(this.connectionService)) - dispatcher.registerHandler(new TrustPingMessageHandler(this.trustPingService, this.connectionService)) - dispatcher.registerHandler(new TrustPingResponseMessageHandler(this.trustPingService)) + dispatcher.registerMessageHandler(new AckMessageHandler(this.connectionService)) + dispatcher.registerMessageHandler(new TrustPingMessageHandler(this.trustPingService, this.connectionService)) + dispatcher.registerMessageHandler(new TrustPingResponseMessageHandler(this.trustPingService)) - dispatcher.registerHandler( + dispatcher.registerMessageHandler( new DidExchangeRequestHandler( this.didExchangeProtocol, this.outOfBandService, @@ -388,7 +388,7 @@ export class ConnectionsApi { ) ) - dispatcher.registerHandler( + dispatcher.registerMessageHandler( new DidExchangeResponseHandler( this.didExchangeProtocol, this.outOfBandService, @@ -397,6 +397,6 @@ export class ConnectionsApi { this.config ) ) - dispatcher.registerHandler(new DidExchangeCompleteHandler(this.didExchangeProtocol, this.outOfBandService)) + dispatcher.registerMessageHandler(new DidExchangeCompleteHandler(this.didExchangeProtocol, this.outOfBandService)) } } diff --git a/packages/core/src/modules/connections/handlers/AckMessageHandler.ts b/packages/core/src/modules/connections/handlers/AckMessageHandler.ts index 369d2d6793..e9c8fafc38 100644 --- a/packages/core/src/modules/connections/handlers/AckMessageHandler.ts +++ b/packages/core/src/modules/connections/handlers/AckMessageHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { ConnectionService } from '../services/ConnectionService' import { AckMessage } from '../../common' -export class AckMessageHandler implements Handler { +export class AckMessageHandler implements MessageHandler { private connectionService: ConnectionService public supportedMessages = [AckMessage] @@ -11,7 +11,7 @@ export class AckMessageHandler implements Handler { this.connectionService = connectionService } - public async handle(inboundMessage: HandlerInboundMessage) { + public async handle(inboundMessage: MessageHandlerInboundMessage) { await this.connectionService.processAck(inboundMessage) } } diff --git a/packages/core/src/modules/connections/handlers/ConnectionProblemReportHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionProblemReportHandler.ts index b1b5896017..5f464a531c 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionProblemReportHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionProblemReportHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { ConnectionService } from '../services' import { ConnectionProblemReportMessage } from '../messages' -export class ConnectionProblemReportHandler implements Handler { +export class ConnectionProblemReportHandler implements MessageHandler { private connectionService: ConnectionService public supportedMessages = [ConnectionProblemReportMessage] @@ -11,7 +11,7 @@ export class ConnectionProblemReportHandler implements Handler { this.connectionService = connectionService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.connectionService.processProblemReport(messageContext) } } diff --git a/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts index 77056ed0fd..25268c1e9f 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts @@ -1,4 +1,4 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { DidRepository } from '../../dids/repository' import type { OutOfBandService } from '../../oob/OutOfBandService' import type { RoutingService } from '../../routing/services/RoutingService' @@ -9,7 +9,7 @@ import { OutboundMessageContext } from '../../../agent/models' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' import { ConnectionRequestMessage } from '../messages' -export class ConnectionRequestHandler implements Handler { +export class ConnectionRequestHandler implements MessageHandler { private connectionService: ConnectionService private outOfBandService: OutOfBandService private routingService: RoutingService @@ -31,7 +31,7 @@ export class ConnectionRequestHandler implements Handler { this.connectionsModuleConfig = connectionsModuleConfig } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const { connection, recipientKey, senderKey } = messageContext if (!recipientKey || !senderKey) { diff --git a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts index 28794676c6..6b37020c15 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts @@ -1,4 +1,4 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { DidResolverService } from '../../dids' import type { OutOfBandService } from '../../oob/OutOfBandService' import type { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' @@ -9,7 +9,7 @@ import { ReturnRouteTypes } from '../../../decorators/transport/TransportDecorat import { AriesFrameworkError } from '../../../error' import { ConnectionResponseMessage } from '../messages' -export class ConnectionResponseHandler implements Handler { +export class ConnectionResponseHandler implements MessageHandler { private connectionService: ConnectionService private outOfBandService: OutOfBandService private didResolverService: DidResolverService @@ -29,7 +29,7 @@ export class ConnectionResponseHandler implements Handler { this.connectionsModuleConfig = connectionsModuleConfig } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const { recipientKey, senderKey, message } = messageContext if (!recipientKey || !senderKey) { diff --git a/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts index e138dfc49e..d8e45b71df 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts @@ -1,4 +1,4 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { OutOfBandService } from '../../oob/OutOfBandService' import type { DidExchangeProtocol } from '../DidExchangeProtocol' @@ -7,7 +7,7 @@ import { OutOfBandState } from '../../oob/domain/OutOfBandState' import { DidExchangeCompleteMessage } from '../messages' import { HandshakeProtocol } from '../models' -export class DidExchangeCompleteHandler implements Handler { +export class DidExchangeCompleteHandler implements MessageHandler { private didExchangeProtocol: DidExchangeProtocol private outOfBandService: OutOfBandService public supportedMessages = [DidExchangeCompleteMessage] @@ -17,7 +17,7 @@ export class DidExchangeCompleteHandler implements Handler { this.outOfBandService = outOfBandService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const { connection: connectionRecord } = messageContext if (!connectionRecord) { diff --git a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts index 309d351726..f5f2e0393f 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts @@ -1,4 +1,4 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { DidRepository } from '../../dids/repository' import type { OutOfBandService } from '../../oob/OutOfBandService' import type { RoutingService } from '../../routing/services/RoutingService' @@ -10,7 +10,7 @@ import { AriesFrameworkError } from '../../../error/AriesFrameworkError' import { OutOfBandState } from '../../oob/domain/OutOfBandState' import { DidExchangeRequestMessage } from '../messages' -export class DidExchangeRequestHandler implements Handler { +export class DidExchangeRequestHandler implements MessageHandler { private didExchangeProtocol: DidExchangeProtocol private outOfBandService: OutOfBandService private routingService: RoutingService @@ -32,7 +32,7 @@ export class DidExchangeRequestHandler implements Handler { this.connectionsModuleConfig = connectionsModuleConfig } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const { recipientKey, senderKey, message, connection } = messageContext if (!recipientKey || !senderKey) { diff --git a/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts index 3f71f85251..b997fcde45 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts @@ -1,4 +1,4 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { DidResolverService } from '../../dids' import type { OutOfBandService } from '../../oob/OutOfBandService' import type { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' @@ -12,7 +12,7 @@ import { OutOfBandState } from '../../oob/domain/OutOfBandState' import { DidExchangeResponseMessage } from '../messages' import { HandshakeProtocol } from '../models' -export class DidExchangeResponseHandler implements Handler { +export class DidExchangeResponseHandler implements MessageHandler { private didExchangeProtocol: DidExchangeProtocol private outOfBandService: OutOfBandService private connectionService: ConnectionService @@ -34,7 +34,7 @@ export class DidExchangeResponseHandler implements Handler { this.connectionsModuleConfig = connectionsModuleConfig } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const { agentContext, recipientKey, senderKey, message } = messageContext if (!recipientKey || !senderKey) { diff --git a/packages/core/src/modules/connections/handlers/TrustPingMessageHandler.ts b/packages/core/src/modules/connections/handlers/TrustPingMessageHandler.ts index aec2f74ea5..52da1423df 100644 --- a/packages/core/src/modules/connections/handlers/TrustPingMessageHandler.ts +++ b/packages/core/src/modules/connections/handlers/TrustPingMessageHandler.ts @@ -1,4 +1,4 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { ConnectionService } from '../services/ConnectionService' import type { TrustPingService } from '../services/TrustPingService' @@ -6,7 +6,7 @@ import { AriesFrameworkError } from '../../../error' import { TrustPingMessage } from '../messages' import { DidExchangeState } from '../models' -export class TrustPingMessageHandler implements Handler { +export class TrustPingMessageHandler implements MessageHandler { private trustPingService: TrustPingService private connectionService: ConnectionService public supportedMessages = [TrustPingMessage] @@ -16,7 +16,7 @@ export class TrustPingMessageHandler implements Handler { this.connectionService = connectionService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const { connection, recipientKey } = messageContext if (!connection) { throw new AriesFrameworkError(`Connection for verkey ${recipientKey?.fingerprint} not found!`) diff --git a/packages/core/src/modules/connections/handlers/TrustPingResponseMessageHandler.ts b/packages/core/src/modules/connections/handlers/TrustPingResponseMessageHandler.ts index e9ff25761b..27e0bff533 100644 --- a/packages/core/src/modules/connections/handlers/TrustPingResponseMessageHandler.ts +++ b/packages/core/src/modules/connections/handlers/TrustPingResponseMessageHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { TrustPingService } from '../services/TrustPingService' import { TrustPingResponseMessage } from '../messages' -export class TrustPingResponseMessageHandler implements Handler { +export class TrustPingResponseMessageHandler implements MessageHandler { private trustPingService: TrustPingService public supportedMessages = [TrustPingResponseMessage] @@ -11,7 +11,7 @@ export class TrustPingResponseMessageHandler implements Handler { this.trustPingService = trustPingService } - public async handle(inboundMessage: HandlerInboundMessage) { + public async handle(inboundMessage: MessageHandlerInboundMessage) { return this.trustPingService.processPingResponse(inboundMessage) } } diff --git a/packages/core/src/modules/credentials/protocol/revocation-notification/handlers/V1RevocationNotificationHandler.ts b/packages/core/src/modules/credentials/protocol/revocation-notification/handlers/V1RevocationNotificationHandler.ts index b780dba59c..36a41caf16 100644 --- a/packages/core/src/modules/credentials/protocol/revocation-notification/handlers/V1RevocationNotificationHandler.ts +++ b/packages/core/src/modules/credentials/protocol/revocation-notification/handlers/V1RevocationNotificationHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { RevocationNotificationService } from '../services' import { V1RevocationNotificationMessage } from '../messages/V1RevocationNotificationMessage' -export class V1RevocationNotificationHandler implements Handler { +export class V1RevocationNotificationHandler implements MessageHandler { private revocationService: RevocationNotificationService public supportedMessages = [V1RevocationNotificationMessage] @@ -11,7 +11,7 @@ export class V1RevocationNotificationHandler implements Handler { this.revocationService = revocationService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.revocationService.v1ProcessRevocationNotification(messageContext) } } diff --git a/packages/core/src/modules/credentials/protocol/revocation-notification/handlers/V2RevocationNotificationHandler.ts b/packages/core/src/modules/credentials/protocol/revocation-notification/handlers/V2RevocationNotificationHandler.ts index ff0056ba80..2057a49d14 100644 --- a/packages/core/src/modules/credentials/protocol/revocation-notification/handlers/V2RevocationNotificationHandler.ts +++ b/packages/core/src/modules/credentials/protocol/revocation-notification/handlers/V2RevocationNotificationHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { RevocationNotificationService } from '../services' import { V2RevocationNotificationMessage } from '../messages/V2RevocationNotificationMessage' -export class V2RevocationNotificationHandler implements Handler { +export class V2RevocationNotificationHandler implements MessageHandler { private revocationService: RevocationNotificationService public supportedMessages = [V2RevocationNotificationMessage] @@ -11,7 +11,7 @@ export class V2RevocationNotificationHandler implements Handler { this.revocationService = revocationService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.revocationService.v2ProcessRevocationNotification(messageContext) } } diff --git a/packages/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts b/packages/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts index 6f285a2272..f3636c689a 100644 --- a/packages/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts +++ b/packages/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts @@ -36,7 +36,7 @@ export class RevocationNotificationService { this.dispatcher = dispatcher this.logger = logger - this.registerHandlers() + this.registerMessageHandlers() } private async processRevocationNotification( @@ -146,8 +146,8 @@ export class RevocationNotificationService { } } - private registerHandlers() { - this.dispatcher.registerHandler(new V1RevocationNotificationHandler(this)) - this.dispatcher.registerHandler(new V2RevocationNotificationHandler(this)) + private registerMessageHandlers() { + this.dispatcher.registerMessageHandler(new V1RevocationNotificationHandler(this)) + this.dispatcher.registerMessageHandler(new V2RevocationNotificationHandler(this)) } } diff --git a/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts b/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts index c3598ab3bb..dad931ab97 100644 --- a/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts +++ b/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts @@ -88,7 +88,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat this.routingService = routingService this.credentialsModuleConfig = credentialsModuleConfig - this.registerHandlers() + this.registerMessageHandlers() } /** @@ -1171,15 +1171,19 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat } } - protected registerHandlers() { - this.dispatcher.registerHandler(new V1ProposeCredentialHandler(this, this.logger)) - this.dispatcher.registerHandler( + protected registerMessageHandlers() { + this.dispatcher.registerMessageHandler(new V1ProposeCredentialHandler(this, this.logger)) + this.dispatcher.registerMessageHandler( new V1OfferCredentialHandler(this, this.routingService, this.didCommMessageRepository, this.logger) ) - this.dispatcher.registerHandler(new V1RequestCredentialHandler(this, this.didCommMessageRepository, this.logger)) - this.dispatcher.registerHandler(new V1IssueCredentialHandler(this, this.didCommMessageRepository, this.logger)) - this.dispatcher.registerHandler(new V1CredentialAckHandler(this)) - this.dispatcher.registerHandler(new V1CredentialProblemReportHandler(this)) + this.dispatcher.registerMessageHandler( + new V1RequestCredentialHandler(this, this.didCommMessageRepository, this.logger) + ) + this.dispatcher.registerMessageHandler( + new V1IssueCredentialHandler(this, this.didCommMessageRepository, this.logger) + ) + this.dispatcher.registerMessageHandler(new V1CredentialAckHandler(this)) + this.dispatcher.registerMessageHandler(new V1CredentialProblemReportHandler(this)) } private rfc0592ProposalFromV1ProposeMessage(proposalMessage: V1ProposeCredentialMessage) { diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts index 9bcd934ad9..b649857234 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1CredentialService } from '../V1CredentialService' import { V1CredentialAckMessage } from '../messages' -export class V1CredentialAckHandler implements Handler { +export class V1CredentialAckHandler implements MessageHandler { private credentialService: V1CredentialService public supportedMessages = [V1CredentialAckMessage] @@ -11,7 +11,7 @@ export class V1CredentialAckHandler implements Handler { this.credentialService = credentialService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.credentialService.processAck(messageContext) } } diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts index 184be10163..fe515b29ca 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1CredentialService } from '../V1CredentialService' import { V1CredentialProblemReportMessage } from '../messages' -export class V1CredentialProblemReportHandler implements Handler { +export class V1CredentialProblemReportHandler implements MessageHandler { private credentialService: V1CredentialService public supportedMessages = [V1CredentialProblemReportMessage] @@ -11,7 +11,7 @@ export class V1CredentialProblemReportHandler implements Handler { this.credentialService = credentialService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.credentialService.processProblemReport(messageContext) } } diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts index e30f82e101..505832a7dd 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts @@ -1,4 +1,4 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { Logger } from '../../../../../logger' import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' @@ -7,7 +7,7 @@ import type { V1CredentialService } from '../V1CredentialService' import { OutboundMessageContext } from '../../../../../agent/models' import { V1IssueCredentialMessage, V1RequestCredentialMessage } from '../messages' -export class V1IssueCredentialHandler implements Handler { +export class V1IssueCredentialHandler implements MessageHandler { private credentialService: V1CredentialService private didCommMessageRepository: DidCommMessageRepository private logger: Logger @@ -23,7 +23,7 @@ export class V1IssueCredentialHandler implements Handler { this.logger = logger } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const credentialRecord = await this.credentialService.processCredential(messageContext) const shouldAutoRespond = await this.credentialService.shouldAutoRespondToCredential(messageContext.agentContext, { @@ -38,7 +38,7 @@ export class V1IssueCredentialHandler implements Handler { private async acceptCredential( credentialRecord: CredentialExchangeRecord, - messageContext: HandlerInboundMessage + messageContext: MessageHandlerInboundMessage ) { this.logger.info(`Automatically sending acknowledgement with autoAccept`) const { message } = await this.credentialService.acceptCredential(messageContext.agentContext, { diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts index bb85fd199a..2f2890d4bd 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts @@ -1,4 +1,4 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { Logger } from '../../../../../logger' import type { DidCommMessageRepository } from '../../../../../storage' import type { RoutingService } from '../../../../routing/services/RoutingService' @@ -10,7 +10,7 @@ import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecor import { DidCommMessageRole } from '../../../../../storage' import { V1OfferCredentialMessage } from '../messages' -export class V1OfferCredentialHandler implements Handler { +export class V1OfferCredentialHandler implements MessageHandler { private credentialService: V1CredentialService private routingService: RoutingService private didCommMessageRepository: DidCommMessageRepository @@ -29,7 +29,7 @@ export class V1OfferCredentialHandler implements Handler { this.logger = logger } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const credentialRecord = await this.credentialService.processOffer(messageContext) const shouldAutoRespond = await this.credentialService.shouldAutoRespondToOffer(messageContext.agentContext, { @@ -44,7 +44,7 @@ export class V1OfferCredentialHandler implements Handler { private async acceptOffer( credentialRecord: CredentialExchangeRecord, - messageContext: HandlerInboundMessage + messageContext: MessageHandlerInboundMessage ) { this.logger.info(`Automatically sending request with autoAccept`) if (messageContext.connection) { diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts index f427a97670..6867832638 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts @@ -1,4 +1,4 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { Logger } from '../../../../../logger' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialService } from '../V1CredentialService' @@ -6,7 +6,7 @@ import type { V1CredentialService } from '../V1CredentialService' import { OutboundMessageContext } from '../../../../../agent/models' import { V1ProposeCredentialMessage } from '../messages' -export class V1ProposeCredentialHandler implements Handler { +export class V1ProposeCredentialHandler implements MessageHandler { private credentialService: V1CredentialService private logger: Logger public supportedMessages = [V1ProposeCredentialMessage] @@ -16,7 +16,7 @@ export class V1ProposeCredentialHandler implements Handler { this.logger = logger } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const credentialRecord = await this.credentialService.processProposal(messageContext) const shouldAutoAcceptProposal = await this.credentialService.shouldAutoRespondToProposal( @@ -34,7 +34,7 @@ export class V1ProposeCredentialHandler implements Handler { private async acceptProposal( credentialRecord: CredentialExchangeRecord, - messageContext: HandlerInboundMessage + messageContext: MessageHandlerInboundMessage ) { this.logger.info(`Automatically sending offer with autoAccept`) diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts index ba85e05ae8..00e475102f 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts @@ -1,4 +1,4 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { Logger } from '../../../../../logger' import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' @@ -8,7 +8,7 @@ import { OutboundMessageContext } from '../../../../../agent/models' import { DidCommMessageRole } from '../../../../../storage' import { V1RequestCredentialMessage } from '../messages' -export class V1RequestCredentialHandler implements Handler { +export class V1RequestCredentialHandler implements MessageHandler { private credentialService: V1CredentialService private didCommMessageRepository: DidCommMessageRepository private logger: Logger @@ -24,7 +24,7 @@ export class V1RequestCredentialHandler implements Handler { this.didCommMessageRepository = didCommMessageRepository } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const credentialRecord = await this.credentialService.processRequest(messageContext) const shouldAutoRespond = await this.credentialService.shouldAutoRespondToRequest(messageContext.agentContext, { @@ -39,7 +39,7 @@ export class V1RequestCredentialHandler implements Handler { private async acceptRequest( credentialRecord: CredentialExchangeRecord, - messageContext: HandlerInboundMessage + messageContext: MessageHandlerInboundMessage ) { this.logger.info(`Automatically sending credential with autoAccept`) diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts index 390dac4e1d..e3d4e85c38 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts @@ -1,6 +1,6 @@ import type { AgentContext } from '../../../../agent' import type { AgentMessage } from '../../../../agent/AgentMessage' -import type { HandlerInboundMessage } from '../../../../agent/Handler' +import type { MessageHandlerInboundMessage } from '../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' import type { ProblemReportMessage } from '../../../problem-reports' import type { @@ -100,7 +100,7 @@ export class V2CredentialService - this.registerHandlers() + this.registerMessageHandlers() } /** @@ -371,7 +371,7 @@ export class V2CredentialService + messageContext: MessageHandlerInboundMessage ): Promise { const { message: offerMessage, connection } = messageContext @@ -1166,20 +1166,24 @@ export class V2CredentialService) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.credentialService.processAck(messageContext) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialProblemReportHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialProblemReportHandler.ts index 914c902691..4801080fed 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialProblemReportHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialProblemReportHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V2CredentialService } from '../V2CredentialService' import { V2CredentialProblemReportMessage } from '../messages/V2CredentialProblemReportMessage' -export class V2CredentialProblemReportHandler implements Handler { +export class V2CredentialProblemReportHandler implements MessageHandler { private credentialService: V2CredentialService public supportedMessages = [V2CredentialProblemReportMessage] @@ -11,7 +11,7 @@ export class V2CredentialProblemReportHandler implements Handler { this.credentialService = credentialService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.credentialService.processProblemReport(messageContext) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts index 308be12bc1..9c27dc09cc 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts @@ -1,4 +1,4 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' import type { Logger } from '../../../../../logger' import type { DidCommMessageRepository } from '../../../../../storage' @@ -9,7 +9,7 @@ import { OutboundMessageContext } from '../../../../../agent/models' import { V2IssueCredentialMessage } from '../messages/V2IssueCredentialMessage' import { V2RequestCredentialMessage } from '../messages/V2RequestCredentialMessage' -export class V2IssueCredentialHandler implements Handler { +export class V2IssueCredentialHandler implements MessageHandler { private credentialService: V2CredentialService private didCommMessageRepository: DidCommMessageRepository private logger: Logger @@ -41,7 +41,7 @@ export class V2IssueCredentialHandler implements Handler { private async acceptCredential( credentialRecord: CredentialExchangeRecord, - messageContext: HandlerInboundMessage + messageContext: MessageHandlerInboundMessage ) { this.logger.info(`Automatically sending acknowledgement with autoAccept`) diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts index d1d4248661..83c29f8069 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts @@ -1,4 +1,4 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' import type { Logger } from '../../../../../logger' import type { DidCommMessageRepository } from '../../../../../storage' @@ -11,7 +11,7 @@ import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecor import { DidCommMessageRole } from '../../../../../storage' import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' -export class V2OfferCredentialHandler implements Handler { +export class V2OfferCredentialHandler implements MessageHandler { private credentialService: V2CredentialService private routingService: RoutingService private logger: Logger @@ -45,7 +45,7 @@ export class V2OfferCredentialHandler implements Handler { private async acceptOffer( credentialRecord: CredentialExchangeRecord, - messageContext: HandlerInboundMessage, + messageContext: MessageHandlerInboundMessage, offerMessage?: V2OfferCredentialMessage ) { this.logger.info(`Automatically sending request with autoAccept`) diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts index d536303a33..5825fb368f 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts @@ -1,4 +1,4 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' import type { Logger } from '../../../../../logger' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' @@ -7,7 +7,7 @@ import type { V2CredentialService } from '../V2CredentialService' import { OutboundMessageContext } from '../../../../../agent/models' import { V2ProposeCredentialMessage } from '../messages/V2ProposeCredentialMessage' -export class V2ProposeCredentialHandler implements Handler { +export class V2ProposeCredentialHandler implements MessageHandler { private credentialService: V2CredentialService private logger: Logger @@ -33,7 +33,7 @@ export class V2ProposeCredentialHandler implements Handler { private async acceptProposal( credentialRecord: CredentialExchangeRecord, - messageContext: HandlerInboundMessage + messageContext: MessageHandlerInboundMessage ) { this.logger.info(`Automatically sending offer with autoAccept`) diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts index 72b436174d..18cab7c34c 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts @@ -1,4 +1,4 @@ -import type { Handler } from '../../../../../agent/Handler' +import type { MessageHandler } from '../../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' import type { Logger } from '../../../../../logger/Logger' import type { DidCommMessageRepository } from '../../../../../storage' @@ -10,7 +10,7 @@ import { DidCommMessageRole } from '../../../../../storage' import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' import { V2RequestCredentialMessage } from '../messages/V2RequestCredentialMessage' -export class V2RequestCredentialHandler implements Handler { +export class V2RequestCredentialHandler implements MessageHandler { private credentialService: V2CredentialService private didCommMessageRepository: DidCommMessageRepository private logger: Logger diff --git a/packages/core/src/modules/discover-features/protocol/v1/V1DiscoverFeaturesService.ts b/packages/core/src/modules/discover-features/protocol/v1/V1DiscoverFeaturesService.ts index e60a9e7d0e..39a694ccd1 100644 --- a/packages/core/src/modules/discover-features/protocol/v1/V1DiscoverFeaturesService.ts +++ b/packages/core/src/modules/discover-features/protocol/v1/V1DiscoverFeaturesService.ts @@ -36,7 +36,7 @@ export class V1DiscoverFeaturesService extends DiscoverFeaturesService { ) { super(featureRegistry, eventEmitter, dispatcher, logger, discoverFeaturesConfig) - this.registerHandlers(dispatcher) + this.registerMessageHandlers(dispatcher) } /** @@ -44,9 +44,9 @@ export class V1DiscoverFeaturesService extends DiscoverFeaturesService { */ public readonly version = 'v1' - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new V1DiscloseMessageHandler(this)) - dispatcher.registerHandler(new V1QueryMessageHandler(this)) + private registerMessageHandlers(dispatcher: Dispatcher) { + dispatcher.registerMessageHandler(new V1DiscloseMessageHandler(this)) + dispatcher.registerMessageHandler(new V1QueryMessageHandler(this)) } public async createQuery( diff --git a/packages/core/src/modules/discover-features/protocol/v1/handlers/V1DiscloseMessageHandler.ts b/packages/core/src/modules/discover-features/protocol/v1/handlers/V1DiscloseMessageHandler.ts index e7a47da870..5a66a4a527 100644 --- a/packages/core/src/modules/discover-features/protocol/v1/handlers/V1DiscloseMessageHandler.ts +++ b/packages/core/src/modules/discover-features/protocol/v1/handlers/V1DiscloseMessageHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1DiscoverFeaturesService } from '../V1DiscoverFeaturesService' import { V1DiscloseMessage } from '../messages' -export class V1DiscloseMessageHandler implements Handler { +export class V1DiscloseMessageHandler implements MessageHandler { public supportedMessages = [V1DiscloseMessage] private discoverFeaturesService: V1DiscoverFeaturesService @@ -11,7 +11,7 @@ export class V1DiscloseMessageHandler implements Handler { this.discoverFeaturesService = discoverFeaturesService } - public async handle(inboundMessage: HandlerInboundMessage) { + public async handle(inboundMessage: MessageHandlerInboundMessage) { await this.discoverFeaturesService.processDisclosure(inboundMessage) } } diff --git a/packages/core/src/modules/discover-features/protocol/v1/handlers/V1QueryMessageHandler.ts b/packages/core/src/modules/discover-features/protocol/v1/handlers/V1QueryMessageHandler.ts index 9a5bacf74c..cd1db2d885 100644 --- a/packages/core/src/modules/discover-features/protocol/v1/handlers/V1QueryMessageHandler.ts +++ b/packages/core/src/modules/discover-features/protocol/v1/handlers/V1QueryMessageHandler.ts @@ -1,10 +1,10 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1DiscoverFeaturesService } from '../V1DiscoverFeaturesService' import { OutboundMessageContext } from '../../../../../agent/models' import { V1QueryMessage } from '../messages' -export class V1QueryMessageHandler implements Handler { +export class V1QueryMessageHandler implements MessageHandler { private discoverFeaturesService: V1DiscoverFeaturesService public supportedMessages = [V1QueryMessage] @@ -12,7 +12,7 @@ export class V1QueryMessageHandler implements Handler { this.discoverFeaturesService = discoverFeaturesService } - public async handle(inboundMessage: HandlerInboundMessage) { + public async handle(inboundMessage: MessageHandlerInboundMessage) { const connection = inboundMessage.assertReadyConnection() const discloseMessage = await this.discoverFeaturesService.processQuery(inboundMessage) diff --git a/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts b/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts index 99ca5a948b..2e007ae142 100644 --- a/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts +++ b/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts @@ -32,7 +32,7 @@ export class V2DiscoverFeaturesService extends DiscoverFeaturesService { discoverFeaturesModuleConfig: DiscoverFeaturesModuleConfig ) { super(featureRegistry, eventEmitter, dispatcher, logger, discoverFeaturesModuleConfig) - this.registerHandlers(dispatcher) + this.registerMessageHandlers(dispatcher) } /** @@ -40,9 +40,9 @@ export class V2DiscoverFeaturesService extends DiscoverFeaturesService { */ public readonly version = 'v2' - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new V2DisclosuresMessageHandler(this)) - dispatcher.registerHandler(new V2QueriesMessageHandler(this)) + private registerMessageHandlers(dispatcher: Dispatcher) { + dispatcher.registerMessageHandler(new V2DisclosuresMessageHandler(this)) + dispatcher.registerMessageHandler(new V2QueriesMessageHandler(this)) } public async createQuery( diff --git a/packages/core/src/modules/discover-features/protocol/v2/handlers/V2DisclosuresMessageHandler.ts b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2DisclosuresMessageHandler.ts index 7bf631f92c..1691e7a5a8 100644 --- a/packages/core/src/modules/discover-features/protocol/v2/handlers/V2DisclosuresMessageHandler.ts +++ b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2DisclosuresMessageHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V2DiscoverFeaturesService } from '../V2DiscoverFeaturesService' import { V2DisclosuresMessage } from '../messages' -export class V2DisclosuresMessageHandler implements Handler { +export class V2DisclosuresMessageHandler implements MessageHandler { private discoverFeaturesService: V2DiscoverFeaturesService public supportedMessages = [V2DisclosuresMessage] @@ -11,7 +11,7 @@ export class V2DisclosuresMessageHandler implements Handler { this.discoverFeaturesService = discoverFeaturesService } - public async handle(inboundMessage: HandlerInboundMessage) { + public async handle(inboundMessage: MessageHandlerInboundMessage) { await this.discoverFeaturesService.processDisclosure(inboundMessage) } } diff --git a/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts index 8664dd2240..45798397be 100644 --- a/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts +++ b/packages/core/src/modules/discover-features/protocol/v2/handlers/V2QueriesMessageHandler.ts @@ -1,10 +1,10 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V2DiscoverFeaturesService } from '../V2DiscoverFeaturesService' import { OutboundMessageContext } from '../../../../../agent/models' import { V2QueriesMessage } from '../messages' -export class V2QueriesMessageHandler implements Handler { +export class V2QueriesMessageHandler implements MessageHandler { private discoverFeaturesService: V2DiscoverFeaturesService public supportedMessages = [V2QueriesMessage] @@ -12,7 +12,7 @@ export class V2QueriesMessageHandler implements Handler { this.discoverFeaturesService = discoverFeaturesService } - public async handle(inboundMessage: HandlerInboundMessage) { + public async handle(inboundMessage: MessageHandlerInboundMessage) { const connection = inboundMessage.assertReadyConnection() const discloseMessage = await this.discoverFeaturesService.processQuery(inboundMessage) diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts index 7448674d4e..1f3e3f2794 100644 --- a/packages/core/src/modules/oob/OutOfBandApi.ts +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -113,7 +113,7 @@ export class OutOfBandApi { this.didCommMessageRepository = didCommMessageRepository this.messageSender = messageSender this.eventEmitter = eventEmitter - this.registerHandlers(dispatcher) + this.registerMessageHandlers(dispatcher) } /** @@ -766,8 +766,8 @@ export class OutOfBandApi { return reuseAcceptedEventPromise } - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new HandshakeReuseHandler(this.outOfBandService)) - dispatcher.registerHandler(new HandshakeReuseAcceptedHandler(this.outOfBandService)) + private registerMessageHandlers(dispatcher: Dispatcher) { + dispatcher.registerMessageHandler(new HandshakeReuseHandler(this.outOfBandService)) + dispatcher.registerMessageHandler(new HandshakeReuseAcceptedHandler(this.outOfBandService)) } } diff --git a/packages/core/src/modules/oob/handlers/HandshakeReuseAcceptedHandler.ts b/packages/core/src/modules/oob/handlers/HandshakeReuseAcceptedHandler.ts index 41b616b443..07fe48259a 100644 --- a/packages/core/src/modules/oob/handlers/HandshakeReuseAcceptedHandler.ts +++ b/packages/core/src/modules/oob/handlers/HandshakeReuseAcceptedHandler.ts @@ -1,10 +1,10 @@ -import type { Handler } from '../../../agent/Handler' +import type { MessageHandler } from '../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { OutOfBandService } from '../OutOfBandService' import { HandshakeReuseAcceptedMessage } from '../messages/HandshakeReuseAcceptedMessage' -export class HandshakeReuseAcceptedHandler implements Handler { +export class HandshakeReuseAcceptedHandler implements MessageHandler { public supportedMessages = [HandshakeReuseAcceptedMessage] private outOfBandService: OutOfBandService diff --git a/packages/core/src/modules/oob/handlers/HandshakeReuseHandler.ts b/packages/core/src/modules/oob/handlers/HandshakeReuseHandler.ts index 43ec159d2e..c4db9cdaf4 100644 --- a/packages/core/src/modules/oob/handlers/HandshakeReuseHandler.ts +++ b/packages/core/src/modules/oob/handlers/HandshakeReuseHandler.ts @@ -1,11 +1,11 @@ -import type { Handler } from '../../../agent/Handler' +import type { MessageHandler } from '../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { OutOfBandService } from '../OutOfBandService' import { OutboundMessageContext } from '../../../agent/models' import { HandshakeReuseMessage } from '../messages/HandshakeReuseMessage' -export class HandshakeReuseHandler implements Handler { +export class HandshakeReuseHandler implements MessageHandler { public supportedMessages = [HandshakeReuseMessage] private outOfBandService: OutOfBandService diff --git a/packages/core/src/modules/proofs/ProofService.ts b/packages/core/src/modules/proofs/ProofService.ts index c0735a9718..dca7cea41f 100644 --- a/packages/core/src/modules/proofs/ProofService.ts +++ b/packages/core/src/modules/proofs/ProofService.ts @@ -192,7 +192,7 @@ export abstract class ProofService { proofRecord: ProofExchangeRecord ): Promise - public abstract registerHandlers( + public abstract registerMessageHandlers( dispatcher: Dispatcher, agentConfig: AgentConfig, proofResponseCoordinator: ProofResponseCoordinator, diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index bd07ac199b..af54ebe234 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -150,7 +150,7 @@ export class ProofsApi< this.logger.debug(`Initializing Proofs Module for agent ${this.agentContext.config.label}`) - this.registerHandlers(dispatcher, mediationRecipientService) + this.registerMessageHandlers(dispatcher, mediationRecipientService) } public getService(protocolVersion: PVT): ProofService { @@ -743,10 +743,10 @@ export class ProofsApi< return service.findPresentationMessage(this.agentContext, proofRecordId) } - private registerHandlers(dispatcher: Dispatcher, mediationRecipientService: MediationRecipientService) { + private registerMessageHandlers(dispatcher: Dispatcher, mediationRecipientService: MediationRecipientService) { for (const service of Object.values(this.serviceMap)) { const proofService = service as ProofService - proofService.registerHandlers( + proofService.registerMessageHandlers( dispatcher, this.agentConfig, new ProofResponseCoordinator(proofService), diff --git a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts index 97f49bb572..22b43cdc93 100644 --- a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts @@ -946,18 +946,18 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { return await this.indyProofFormatService.autoSelectCredentialsForProofRequest(options) } - public registerHandlers( + public registerMessageHandlers( dispatcher: Dispatcher, agentConfig: AgentConfig, proofResponseCoordinator: ProofResponseCoordinator, mediationRecipientService: MediationRecipientService, routingService: RoutingService ): void { - dispatcher.registerHandler( + dispatcher.registerMessageHandler( new V1ProposePresentationHandler(this, agentConfig, proofResponseCoordinator, this.didCommMessageRepository) ) - dispatcher.registerHandler( + dispatcher.registerMessageHandler( new V1RequestPresentationHandler( this, agentConfig, @@ -968,11 +968,11 @@ export class V1ProofService extends ProofService<[IndyProofFormat]> { ) ) - dispatcher.registerHandler( + dispatcher.registerMessageHandler( new V1PresentationHandler(this, agentConfig, proofResponseCoordinator, this.didCommMessageRepository) ) - dispatcher.registerHandler(new V1PresentationAckHandler(this)) - dispatcher.registerHandler(new V1PresentationProblemReportHandler(this)) + dispatcher.registerMessageHandler(new V1PresentationAckHandler(this)) + dispatcher.registerMessageHandler(new V1PresentationProblemReportHandler(this)) } public async findRequestMessage( diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts index aa0c050c82..cdc9f6d797 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1ProofService } from '../V1ProofService' import { V1PresentationAckMessage } from '../messages' -export class V1PresentationAckHandler implements Handler { +export class V1PresentationAckHandler implements MessageHandler { private proofService: V1ProofService public supportedMessages = [V1PresentationAckMessage] @@ -11,7 +11,7 @@ export class V1PresentationAckHandler implements Handler { this.proofService = proofService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.proofService.processAck(messageContext) } } diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts index 7ecd43d6ee..6918979829 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts @@ -1,5 +1,5 @@ import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { DidCommMessageRepository } from '../../../../../storage' import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' import type { ProofExchangeRecord } from '../../../repository' @@ -8,7 +8,7 @@ import type { V1ProofService } from '../V1ProofService' import { OutboundMessageContext } from '../../../../../agent/models' import { V1PresentationMessage, V1RequestPresentationMessage } from '../messages' -export class V1PresentationHandler implements Handler { +export class V1PresentationHandler implements MessageHandler { private proofService: V1ProofService private agentConfig: AgentConfig private proofResponseCoordinator: ProofResponseCoordinator @@ -27,7 +27,7 @@ export class V1PresentationHandler implements Handler { this.didCommMessageRepository = didCommMessageRepository } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const proofRecord = await this.proofService.processPresentation(messageContext) const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToPresentation( @@ -40,7 +40,10 @@ export class V1PresentationHandler implements Handler { } } - private async createAck(record: ProofExchangeRecord, messageContext: HandlerInboundMessage) { + private async createAck( + record: ProofExchangeRecord, + messageContext: MessageHandlerInboundMessage + ) { this.agentConfig.logger.info( `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptProofs}` ) diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts index da2af78c18..e3c7f97410 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1ProofService } from '../V1ProofService' import { V1PresentationProblemReportMessage } from '../messages/V1PresentationProblemReportMessage' -export class V1PresentationProblemReportHandler implements Handler { +export class V1PresentationProblemReportHandler implements MessageHandler { private proofService: V1ProofService public supportedMessages = [V1PresentationProblemReportMessage] @@ -11,7 +11,7 @@ export class V1PresentationProblemReportHandler implements Handler { this.proofService = proofService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.proofService.processProblemReport(messageContext) } } diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts index a3c220ed44..e69764d3d5 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts @@ -1,5 +1,5 @@ import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' @@ -12,7 +12,7 @@ import { OutboundMessageContext } from '../../../../../agent/models' import { AriesFrameworkError } from '../../../../../error' import { V1ProposePresentationMessage } from '../messages' -export class V1ProposePresentationHandler implements Handler { +export class V1ProposePresentationHandler implements MessageHandler { private proofService: V1ProofService private agentConfig: AgentConfig private didCommMessageRepository: DidCommMessageRepository @@ -31,7 +31,7 @@ export class V1ProposePresentationHandler implements Handler { this.didCommMessageRepository = didCommMessageRepository } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const proofRecord = await this.proofService.processProposal(messageContext) const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToProposal( @@ -46,7 +46,7 @@ export class V1ProposePresentationHandler implements Handler { private async createRequest( proofRecord: ProofExchangeRecord, - messageContext: HandlerInboundMessage + messageContext: MessageHandlerInboundMessage ) { this.agentConfig.logger.info( `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptProofs}` diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts index addfdb3a6c..d460bbe122 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts @@ -1,5 +1,5 @@ import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' import type { MediationRecipientService, RoutingService } from '../../../../routing' import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' @@ -17,7 +17,7 @@ import { AriesFrameworkError } from '../../../../../error' import { DidCommMessageRole } from '../../../../../storage' import { V1RequestPresentationMessage } from '../messages' -export class V1RequestPresentationHandler implements Handler { +export class V1RequestPresentationHandler implements MessageHandler { private proofService: V1ProofService private agentConfig: AgentConfig private proofResponseCoordinator: ProofResponseCoordinator @@ -42,7 +42,7 @@ export class V1RequestPresentationHandler implements Handler { this.routingService = routingService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const proofRecord = await this.proofService.processRequest(messageContext) const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToRequest( @@ -57,7 +57,7 @@ export class V1RequestPresentationHandler implements Handler { private async createPresentation( record: ProofExchangeRecord, - messageContext: HandlerInboundMessage + messageContext: MessageHandlerInboundMessage ) { const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { associatedRecordId: record.id, diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts index 0bd358728d..1596e55e03 100644 --- a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts @@ -913,18 +913,18 @@ export class V2ProofService extends P return returnValue } - public registerHandlers( + public registerMessageHandlers( dispatcher: Dispatcher, agentConfig: AgentConfig, proofResponseCoordinator: ProofResponseCoordinator, mediationRecipientService: MediationRecipientService, routingService: RoutingService ): void { - dispatcher.registerHandler( + dispatcher.registerMessageHandler( new V2ProposePresentationHandler(this, agentConfig, this.didCommMessageRepository, proofResponseCoordinator) ) - dispatcher.registerHandler( + dispatcher.registerMessageHandler( new V2RequestPresentationHandler( this, agentConfig, @@ -935,11 +935,11 @@ export class V2ProofService extends P ) ) - dispatcher.registerHandler( + dispatcher.registerMessageHandler( new V2PresentationHandler(this, agentConfig, proofResponseCoordinator, this.didCommMessageRepository) ) - dispatcher.registerHandler(new V2PresentationAckHandler(this)) - dispatcher.registerHandler(new V2PresentationProblemReportHandler(this)) + dispatcher.registerMessageHandler(new V2PresentationAckHandler(this)) + dispatcher.registerMessageHandler(new V2PresentationProblemReportHandler(this)) } private getFormatServiceForFormat(format: ProofFormatSpec) { diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts index 9ffa2f32cc..3d28970a6e 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { ProofService } from '../../../ProofService' import { V2PresentationAckMessage } from '../messages' -export class V2PresentationAckHandler implements Handler { +export class V2PresentationAckHandler implements MessageHandler { private proofService: ProofService public supportedMessages = [V2PresentationAckMessage] @@ -11,7 +11,7 @@ export class V2PresentationAckHandler implements Handler { this.proofService = proofService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.proofService.processAck(messageContext) } } diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts index a0f2b3e5d1..9f2d074d67 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts @@ -1,5 +1,5 @@ import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { DidCommMessageRepository } from '../../../../../storage' import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' import type { ProofExchangeRecord } from '../../../repository' @@ -8,7 +8,7 @@ import type { V2ProofService } from '../V2ProofService' import { OutboundMessageContext } from '../../../../../agent/models' import { V2PresentationMessage, V2RequestPresentationMessage } from '../messages' -export class V2PresentationHandler implements Handler { +export class V2PresentationHandler implements MessageHandler { private proofService: V2ProofService private agentConfig: AgentConfig private proofResponseCoordinator: ProofResponseCoordinator @@ -27,7 +27,7 @@ export class V2PresentationHandler implements Handler { this.didCommMessageRepository = didCommMessageRepository } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const proofRecord = await this.proofService.processPresentation(messageContext) const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToPresentation( @@ -40,7 +40,10 @@ export class V2PresentationHandler implements Handler { } } - private async createAck(record: ProofExchangeRecord, messageContext: HandlerInboundMessage) { + private async createAck( + record: ProofExchangeRecord, + messageContext: MessageHandlerInboundMessage + ) { this.agentConfig.logger.info( `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptProofs}` ) diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts index 77bdab2160..947a8c6c44 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V2ProofService } from '../V2ProofService' import { V2PresentationProblemReportMessage } from '../messages' -export class V2PresentationProblemReportHandler implements Handler { +export class V2PresentationProblemReportHandler implements MessageHandler { private proofService: V2ProofService public supportedMessages = [V2PresentationProblemReportMessage] @@ -11,7 +11,7 @@ export class V2PresentationProblemReportHandler implements Handler { this.proofService = proofService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.proofService.processProblemReport(messageContext) } } diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts index bbbf3f2b38..9432a3ca56 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts @@ -1,5 +1,5 @@ import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { DidCommMessageRepository } from '../../../../../storage' import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' import type { ProofFormat } from '../../../formats/ProofFormat' @@ -15,7 +15,7 @@ import { OutboundMessageContext } from '../../../../../agent/models' import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' -export class V2ProposePresentationHandler implements Handler { +export class V2ProposePresentationHandler implements MessageHandler { private proofService: V2ProofService private agentConfig: AgentConfig private didCommMessageRepository: DidCommMessageRepository @@ -34,7 +34,7 @@ export class V2ProposePresentationHandler) { + public async handle(messageContext: MessageHandlerInboundMessage) { const proofRecord = await this.proofService.processProposal(messageContext) const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToProposal( @@ -49,7 +49,7 @@ export class V2ProposePresentationHandler + messageContext: MessageHandlerInboundMessage ) { this.agentConfig.logger.info( `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptProofs}` diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts index fb8c7767d4..65edcd85c5 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts @@ -1,5 +1,5 @@ import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' import type { MediationRecipientService, RoutingService } from '../../../../routing' import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' @@ -16,7 +16,7 @@ import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecor import { DidCommMessageRole } from '../../../../../storage' import { V2RequestPresentationMessage } from '../messages/V2RequestPresentationMessage' -export class V2RequestPresentationHandler implements Handler { +export class V2RequestPresentationHandler implements MessageHandler { private proofService: V2ProofService private agentConfig: AgentConfig private proofResponseCoordinator: ProofResponseCoordinator @@ -41,7 +41,7 @@ export class V2RequestPresentationHandler) { + public async handle(messageContext: MessageHandlerInboundMessage) { const proofRecord = await this.proofService.processRequest(messageContext) const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToRequest( @@ -56,7 +56,7 @@ export class V2RequestPresentationHandler + messageContext: MessageHandlerInboundMessage ) { const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { associatedRecordId: record.id, diff --git a/packages/core/src/modules/routing/MediatorApi.ts b/packages/core/src/modules/routing/MediatorApi.ts index 3128ece119..bc366e0015 100644 --- a/packages/core/src/modules/routing/MediatorApi.ts +++ b/packages/core/src/modules/routing/MediatorApi.ts @@ -46,7 +46,7 @@ export class MediatorApi { this.connectionService = connectionService this.agentContext = agentContext this.config = config - this.registerHandlers(dispatcher) + this.registerMessageHandlers(dispatcher) } public async initialize() { @@ -85,11 +85,13 @@ export class MediatorApi { return this.messagePickupService.queueMessage(connectionId, message) } - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new KeylistUpdateHandler(this.mediatorService)) - dispatcher.registerHandler(new ForwardHandler(this.mediatorService, this.connectionService, this.messageSender)) - dispatcher.registerHandler(new BatchPickupHandler(this.messagePickupService)) - dispatcher.registerHandler(new BatchHandler(this.eventEmitter)) - dispatcher.registerHandler(new MediationRequestHandler(this.mediatorService, this.config)) + private registerMessageHandlers(dispatcher: Dispatcher) { + dispatcher.registerMessageHandler(new KeylistUpdateHandler(this.mediatorService)) + dispatcher.registerMessageHandler( + new ForwardHandler(this.mediatorService, this.connectionService, this.messageSender) + ) + dispatcher.registerMessageHandler(new BatchPickupHandler(this.messagePickupService)) + dispatcher.registerMessageHandler(new BatchHandler(this.eventEmitter)) + dispatcher.registerMessageHandler(new MediationRequestHandler(this.mediatorService, this.config)) } } diff --git a/packages/core/src/modules/routing/RecipientApi.ts b/packages/core/src/modules/routing/RecipientApi.ts index c58ec32521..49c09365eb 100644 --- a/packages/core/src/modules/routing/RecipientApi.ts +++ b/packages/core/src/modules/routing/RecipientApi.ts @@ -84,7 +84,7 @@ export class RecipientApi { this.agentContext = agentContext this.stop$ = stop$ this.config = recipientModuleConfig - this.registerHandlers(dispatcher) + this.registerMessageHandlers(dispatcher) } public async initialize() { @@ -484,12 +484,12 @@ export class RecipientApi { } // Register handlers for the several messages for the mediator. - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new KeylistUpdateResponseHandler(this.mediationRecipientService)) - dispatcher.registerHandler(new MediationGrantHandler(this.mediationRecipientService)) - dispatcher.registerHandler(new MediationDenyHandler(this.mediationRecipientService)) - dispatcher.registerHandler(new StatusHandler(this.mediationRecipientService)) - dispatcher.registerHandler(new MessageDeliveryHandler(this.mediationRecipientService)) - //dispatcher.registerHandler(new KeylistListHandler(this.mediationRecipientService)) // TODO: write this + private registerMessageHandlers(dispatcher: Dispatcher) { + dispatcher.registerMessageHandler(new KeylistUpdateResponseHandler(this.mediationRecipientService)) + dispatcher.registerMessageHandler(new MediationGrantHandler(this.mediationRecipientService)) + dispatcher.registerMessageHandler(new MediationDenyHandler(this.mediationRecipientService)) + dispatcher.registerMessageHandler(new StatusHandler(this.mediationRecipientService)) + dispatcher.registerMessageHandler(new MessageDeliveryHandler(this.mediationRecipientService)) + //dispatcher.registerMessageHandler(new KeylistListHandler(this.mediationRecipientService)) // TODO: write this } } diff --git a/packages/core/src/modules/routing/handlers/ForwardHandler.ts b/packages/core/src/modules/routing/handlers/ForwardHandler.ts index 4407480175..960237fc45 100644 --- a/packages/core/src/modules/routing/handlers/ForwardHandler.ts +++ b/packages/core/src/modules/routing/handlers/ForwardHandler.ts @@ -1,11 +1,11 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { MessageSender } from '../../../agent/MessageSender' import type { ConnectionService } from '../../connections/services' import type { MediatorService } from '../services' import { ForwardMessage } from '../messages' -export class ForwardHandler implements Handler { +export class ForwardHandler implements MessageHandler { private mediatorService: MediatorService private connectionService: ConnectionService private messageSender: MessageSender @@ -22,7 +22,7 @@ export class ForwardHandler implements Handler { this.messageSender = messageSender } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const { encryptedMessage, mediationRecord } = await this.mediatorService.processForwardMessage(messageContext) const connectionRecord = await this.connectionService.getById( diff --git a/packages/core/src/modules/routing/handlers/KeylistUpdateHandler.ts b/packages/core/src/modules/routing/handlers/KeylistUpdateHandler.ts index 3e5357ef75..fcc8609512 100644 --- a/packages/core/src/modules/routing/handlers/KeylistUpdateHandler.ts +++ b/packages/core/src/modules/routing/handlers/KeylistUpdateHandler.ts @@ -1,10 +1,10 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { MediatorService } from '../services/MediatorService' import { OutboundMessageContext } from '../../../agent/models' import { KeylistUpdateMessage } from '../messages' -export class KeylistUpdateHandler implements Handler { +export class KeylistUpdateHandler implements MessageHandler { private mediatorService: MediatorService public supportedMessages = [KeylistUpdateMessage] @@ -12,7 +12,7 @@ export class KeylistUpdateHandler implements Handler { this.mediatorService = mediatorService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const connection = messageContext.assertReadyConnection() const response = await this.mediatorService.processKeylistUpdateRequest(messageContext) diff --git a/packages/core/src/modules/routing/handlers/KeylistUpdateResponseHandler.ts b/packages/core/src/modules/routing/handlers/KeylistUpdateResponseHandler.ts index 1b637dbd6f..58fdf301d8 100644 --- a/packages/core/src/modules/routing/handlers/KeylistUpdateResponseHandler.ts +++ b/packages/core/src/modules/routing/handlers/KeylistUpdateResponseHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { MediationRecipientService } from '../services' import { KeylistUpdateResponseMessage } from '../messages' -export class KeylistUpdateResponseHandler implements Handler { +export class KeylistUpdateResponseHandler implements MessageHandler { public mediationRecipientService: MediationRecipientService public supportedMessages = [KeylistUpdateResponseMessage] @@ -11,7 +11,7 @@ export class KeylistUpdateResponseHandler implements Handler { this.mediationRecipientService = mediationRecipientService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { messageContext.assertReadyConnection() return await this.mediationRecipientService.processKeylistUpdateResults(messageContext) diff --git a/packages/core/src/modules/routing/handlers/MediationDenyHandler.ts b/packages/core/src/modules/routing/handlers/MediationDenyHandler.ts index ca6e163c11..83b90da990 100644 --- a/packages/core/src/modules/routing/handlers/MediationDenyHandler.ts +++ b/packages/core/src/modules/routing/handlers/MediationDenyHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { MediationRecipientService } from '../services' import { MediationDenyMessage } from '../messages' -export class MediationDenyHandler implements Handler { +export class MediationDenyHandler implements MessageHandler { private mediationRecipientService: MediationRecipientService public supportedMessages = [MediationDenyMessage] @@ -11,7 +11,7 @@ export class MediationDenyHandler implements Handler { this.mediationRecipientService = mediationRecipientService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { messageContext.assertReadyConnection() await this.mediationRecipientService.processMediationDeny(messageContext) diff --git a/packages/core/src/modules/routing/handlers/MediationGrantHandler.ts b/packages/core/src/modules/routing/handlers/MediationGrantHandler.ts index 5ac69e7c3f..f9b290c934 100644 --- a/packages/core/src/modules/routing/handlers/MediationGrantHandler.ts +++ b/packages/core/src/modules/routing/handlers/MediationGrantHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { MediationRecipientService } from '../services/MediationRecipientService' import { MediationGrantMessage } from '../messages' -export class MediationGrantHandler implements Handler { +export class MediationGrantHandler implements MessageHandler { private mediationRecipientService: MediationRecipientService public supportedMessages = [MediationGrantMessage] @@ -11,7 +11,7 @@ export class MediationGrantHandler implements Handler { this.mediationRecipientService = mediationRecipientService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { messageContext.assertReadyConnection() await this.mediationRecipientService.processMediationGrant(messageContext) diff --git a/packages/core/src/modules/routing/handlers/MediationRequestHandler.ts b/packages/core/src/modules/routing/handlers/MediationRequestHandler.ts index 7d58dc28e2..f020c6f9bd 100644 --- a/packages/core/src/modules/routing/handlers/MediationRequestHandler.ts +++ b/packages/core/src/modules/routing/handlers/MediationRequestHandler.ts @@ -1,11 +1,11 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../agent/MessageHandler' import type { MediatorModuleConfig } from '../MediatorModuleConfig' import type { MediatorService } from '../services/MediatorService' import { OutboundMessageContext } from '../../../agent/models' import { MediationRequestMessage } from '../messages/MediationRequestMessage' -export class MediationRequestHandler implements Handler { +export class MediationRequestHandler implements MessageHandler { private mediatorService: MediatorService private mediatorModuleConfig: MediatorModuleConfig public supportedMessages = [MediationRequestMessage] @@ -15,7 +15,7 @@ export class MediationRequestHandler implements Handler { this.mediatorModuleConfig = mediatorModuleConfig } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const connection = messageContext.assertReadyConnection() const mediationRecord = await this.mediatorService.processMediationRequest(messageContext) diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/MessagePickupService.ts b/packages/core/src/modules/routing/protocol/pickup/v1/MessagePickupService.ts index 0669ca0cd4..7c6624af68 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v1/MessagePickupService.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v1/MessagePickupService.ts @@ -27,7 +27,7 @@ export class MessagePickupService { this.dispatcher = dispatcher this.eventEmitter = eventEmitter - this.registerHandlers() + this.registerMessageHandlers() } public async batch(messageContext: InboundMessageContext) { @@ -57,8 +57,8 @@ export class MessagePickupService { await this.messageRepository.add(connectionId, message) } - protected registerHandlers() { - this.dispatcher.registerHandler(new BatchPickupHandler(this)) - this.dispatcher.registerHandler(new BatchHandler(this.eventEmitter)) + protected registerMessageHandlers() { + this.dispatcher.registerMessageHandler(new BatchPickupHandler(this)) + this.dispatcher.registerMessageHandler(new BatchHandler(this.eventEmitter)) } } diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts index b67bf8b1bb..30d8e5263f 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts @@ -1,11 +1,11 @@ import type { EventEmitter } from '../../../../../../agent/EventEmitter' import type { AgentMessageReceivedEvent } from '../../../../../../agent/Events' -import type { Handler, HandlerInboundMessage } from '../../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../../agent/MessageHandler' import { AgentEventTypes } from '../../../../../../agent/Events' import { BatchMessage } from '../messages' -export class BatchHandler implements Handler { +export class BatchHandler implements MessageHandler { private eventEmitter: EventEmitter public supportedMessages = [BatchMessage] @@ -13,7 +13,7 @@ export class BatchHandler implements Handler { this.eventEmitter = eventEmitter } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { const { message } = messageContext messageContext.assertReadyConnection() diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchPickupHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchPickupHandler.ts index e854d23f42..e47fb6c2f5 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchPickupHandler.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchPickupHandler.ts @@ -1,9 +1,9 @@ -import type { Handler, HandlerInboundMessage } from '../../../../../../agent/Handler' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../../agent/MessageHandler' import type { MessagePickupService } from '../MessagePickupService' import { BatchPickupMessage } from '../messages' -export class BatchPickupHandler implements Handler { +export class BatchPickupHandler implements MessageHandler { private messagePickupService: MessagePickupService public supportedMessages = [BatchPickupMessage] @@ -11,7 +11,7 @@ export class BatchPickupHandler implements Handler { this.messagePickupService = messagePickupService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { messageContext.assertReadyConnection() return this.messagePickupService.batch(messageContext) diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/V2MessagePickupService.ts b/packages/core/src/modules/routing/protocol/pickup/v2/V2MessagePickupService.ts index 599011cbd6..77d23c2e69 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/V2MessagePickupService.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v2/V2MessagePickupService.ts @@ -35,7 +35,7 @@ export class V2MessagePickupService { this.dispatcher = dispatcher this.mediationRecipientService = mediationRecipientService - this.registerHandlers() + this.registerMessageHandlers() } public async processStatusRequest(messageContext: InboundMessageContext) { @@ -116,11 +116,11 @@ export class V2MessagePickupService { return new OutboundMessageContext(statusMessage, { agentContext: messageContext.agentContext, connection }) } - protected registerHandlers() { - this.dispatcher.registerHandler(new StatusRequestHandler(this)) - this.dispatcher.registerHandler(new DeliveryRequestHandler(this)) - this.dispatcher.registerHandler(new MessagesReceivedHandler(this)) - this.dispatcher.registerHandler(new StatusHandler(this.mediationRecipientService)) - this.dispatcher.registerHandler(new MessageDeliveryHandler(this.mediationRecipientService)) + protected registerMessageHandlers() { + this.dispatcher.registerMessageHandler(new StatusRequestHandler(this)) + this.dispatcher.registerMessageHandler(new DeliveryRequestHandler(this)) + this.dispatcher.registerMessageHandler(new MessagesReceivedHandler(this)) + this.dispatcher.registerMessageHandler(new StatusHandler(this.mediationRecipientService)) + this.dispatcher.registerMessageHandler(new MessageDeliveryHandler(this.mediationRecipientService)) } } diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/DeliveryRequestHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/DeliveryRequestHandler.ts index a992cc990d..78334b5448 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/DeliveryRequestHandler.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/DeliveryRequestHandler.ts @@ -1,10 +1,10 @@ -import type { Handler } from '../../../../../../agent/Handler' +import type { MessageHandler } from '../../../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../../../agent/models/InboundMessageContext' import type { V2MessagePickupService } from '../V2MessagePickupService' import { DeliveryRequestMessage } from '../messages' -export class DeliveryRequestHandler implements Handler { +export class DeliveryRequestHandler implements MessageHandler { public supportedMessages = [DeliveryRequestMessage] private messagePickupService: V2MessagePickupService diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessageDeliveryHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessageDeliveryHandler.ts index fb9db1d25b..606647edb9 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessageDeliveryHandler.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessageDeliveryHandler.ts @@ -1,11 +1,11 @@ -import type { Handler } from '../../../../../../agent/Handler' +import type { MessageHandler } from '../../../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../../../agent/models/InboundMessageContext' import type { MediationRecipientService } from '../../../../services' import { OutboundMessageContext } from '../../../../../../agent/models' import { MessageDeliveryMessage } from '../messages/MessageDeliveryMessage' -export class MessageDeliveryHandler implements Handler { +export class MessageDeliveryHandler implements MessageHandler { public supportedMessages = [MessageDeliveryMessage] private mediationRecipientService: MediationRecipientService diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessagesReceivedHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessagesReceivedHandler.ts index 079762881e..7ddf4b6d75 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessagesReceivedHandler.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessagesReceivedHandler.ts @@ -1,10 +1,10 @@ -import type { Handler } from '../../../../../../agent/Handler' +import type { MessageHandler } from '../../../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../../../agent/models/InboundMessageContext' import type { V2MessagePickupService } from '../V2MessagePickupService' import { MessagesReceivedMessage } from '../messages' -export class MessagesReceivedHandler implements Handler { +export class MessagesReceivedHandler implements MessageHandler { public supportedMessages = [MessagesReceivedMessage] private messagePickupService: V2MessagePickupService diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusHandler.ts index 163f1a9c4f..7fedcc381f 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusHandler.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusHandler.ts @@ -1,11 +1,11 @@ -import type { Handler } from '../../../../../../agent/Handler' +import type { MessageHandler } from '../../../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../../../agent/models/InboundMessageContext' import type { MediationRecipientService } from '../../../../services' import { OutboundMessageContext } from '../../../../../../agent/models' import { StatusMessage } from '../messages' -export class StatusHandler implements Handler { +export class StatusHandler implements MessageHandler { public supportedMessages = [StatusMessage] private mediatorRecipientService: MediationRecipientService diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusRequestHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusRequestHandler.ts index 7d069ddcd2..1e7c67ae1d 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusRequestHandler.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusRequestHandler.ts @@ -1,10 +1,10 @@ -import type { Handler } from '../../../../../../agent/Handler' +import type { MessageHandler } from '../../../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../../../agent/models/InboundMessageContext' import type { V2MessagePickupService } from '../V2MessagePickupService' import { StatusRequestMessage } from '../messages' -export class StatusRequestHandler implements Handler { +export class StatusRequestHandler implements MessageHandler { public supportedMessages = [StatusRequestMessage] private messagePickupService: V2MessagePickupService diff --git a/packages/core/tests/multi-protocol-version.test.ts b/packages/core/tests/multi-protocol-version.test.ts index 07b7b057f0..c7197ca36f 100644 --- a/packages/core/tests/multi-protocol-version.test.ts +++ b/packages/core/tests/multi-protocol-version.test.ts @@ -47,7 +47,7 @@ describe('multi version protocols', () => { // Register the test handler with the v1.3 version of the message const dispatcher = aliceAgent.dependencyManager.resolve(Dispatcher) - dispatcher.registerHandler({ supportedMessages: [TestMessageV13], handle: mockHandle }) + dispatcher.registerMessageHandler({ supportedMessages: [TestMessageV13], handle: mockHandle }) await aliceAgent.initialize() diff --git a/packages/question-answer/src/QuestionAnswerApi.ts b/packages/question-answer/src/QuestionAnswerApi.ts index 777e3ff12f..6fed567575 100644 --- a/packages/question-answer/src/QuestionAnswerApi.ts +++ b/packages/question-answer/src/QuestionAnswerApi.ts @@ -32,7 +32,7 @@ export class QuestionAnswerApi { this.messageSender = messageSender this.connectionService = connectionService this.agentContext = agentContext - this.registerHandlers(dispatcher) + this.registerMessageHandlers(dispatcher) } /** @@ -132,8 +132,8 @@ export class QuestionAnswerApi { return this.questionAnswerService.findById(this.agentContext, questionAnswerId) } - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new QuestionMessageHandler(this.questionAnswerService)) - dispatcher.registerHandler(new AnswerMessageHandler(this.questionAnswerService)) + private registerMessageHandlers(dispatcher: Dispatcher) { + dispatcher.registerMessageHandler(new QuestionMessageHandler(this.questionAnswerService)) + dispatcher.registerMessageHandler(new AnswerMessageHandler(this.questionAnswerService)) } } diff --git a/packages/question-answer/src/handlers/AnswerMessageHandler.ts b/packages/question-answer/src/handlers/AnswerMessageHandler.ts index 5310aeb345..6615e6c9e7 100644 --- a/packages/question-answer/src/handlers/AnswerMessageHandler.ts +++ b/packages/question-answer/src/handlers/AnswerMessageHandler.ts @@ -1,9 +1,9 @@ import type { QuestionAnswerService } from '../services' -import type { Handler, HandlerInboundMessage } from '@aries-framework/core' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { AnswerMessage } from '../messages' -export class AnswerMessageHandler implements Handler { +export class AnswerMessageHandler implements MessageHandler { private questionAnswerService: QuestionAnswerService public supportedMessages = [AnswerMessage] @@ -11,7 +11,7 @@ export class AnswerMessageHandler implements Handler { this.questionAnswerService = questionAnswerService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.questionAnswerService.receiveAnswer(messageContext) } } diff --git a/packages/question-answer/src/handlers/QuestionMessageHandler.ts b/packages/question-answer/src/handlers/QuestionMessageHandler.ts index 7dbfbdc440..8da1caa222 100644 --- a/packages/question-answer/src/handlers/QuestionMessageHandler.ts +++ b/packages/question-answer/src/handlers/QuestionMessageHandler.ts @@ -1,9 +1,9 @@ import type { QuestionAnswerService } from '../services' -import type { Handler, HandlerInboundMessage } from '@aries-framework/core' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { QuestionMessage } from '../messages' -export class QuestionMessageHandler implements Handler { +export class QuestionMessageHandler implements MessageHandler { private questionAnswerService: QuestionAnswerService public supportedMessages = [QuestionMessage] @@ -11,7 +11,7 @@ export class QuestionMessageHandler implements Handler { this.questionAnswerService = questionAnswerService } - public async handle(messageContext: HandlerInboundMessage) { + public async handle(messageContext: MessageHandlerInboundMessage) { await this.questionAnswerService.processReceiveQuestion(messageContext) } } diff --git a/samples/extension-module/README.md b/samples/extension-module/README.md index c57acf74f1..309d5cb0ef 100644 --- a/samples/extension-module/README.md +++ b/samples/extension-module/README.md @@ -11,7 +11,7 @@ An extension module could be used for different purposes, such as storing data i This example consists of a module that implements a very simple request-response protocol called Dummy. In order to do so and be able to be injected into an AFJ instance, some steps were followed: - Define Dummy protocol message classes (inherited from `AgentMessage`) -- Create handlers for those messages (inherited from `Handler`) +- Create handlers for those messages (inherited from `MessageHandler`) - Define records (inherited from `BaseRecord`) and a singleton repository (inherited from `Repository`) for state persistance - Define events (inherited from `BaseEvent`) - Create a singleton service class that manages records and repository, and also trigger events using Agent's `EventEmitter` diff --git a/samples/extension-module/dummy/DummyApi.ts b/samples/extension-module/dummy/DummyApi.ts index dded85085f..1bb998336c 100644 --- a/samples/extension-module/dummy/DummyApi.ts +++ b/samples/extension-module/dummy/DummyApi.ts @@ -33,7 +33,7 @@ export class DummyApi { this.connectionService = connectionService this.agentContext = agentContext - this.registerHandlers(dispatcher) + this.registerMessageHandlers(dispatcher) } /** @@ -94,8 +94,8 @@ export class DummyApi { return this.dummyService.findAllByQuery(this.agentContext, query) } - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler(new DummyRequestHandler(this.dummyService)) - dispatcher.registerHandler(new DummyResponseHandler(this.dummyService)) + private registerMessageHandlers(dispatcher: Dispatcher) { + dispatcher.registerMessageHandler(new DummyRequestHandler(this.dummyService)) + dispatcher.registerMessageHandler(new DummyResponseHandler(this.dummyService)) } } diff --git a/samples/extension-module/dummy/handlers/DummyRequestHandler.ts b/samples/extension-module/dummy/handlers/DummyRequestHandler.ts index 7b0de4ea72..b19394239f 100644 --- a/samples/extension-module/dummy/handlers/DummyRequestHandler.ts +++ b/samples/extension-module/dummy/handlers/DummyRequestHandler.ts @@ -1,11 +1,11 @@ import type { DummyService } from '../services' -import type { Handler, HandlerInboundMessage } from '@aries-framework/core' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { OutboundMessageContext } from '@aries-framework/core' import { DummyRequestMessage } from '../messages' -export class DummyRequestHandler implements Handler { +export class DummyRequestHandler implements MessageHandler { public supportedMessages = [DummyRequestMessage] private dummyService: DummyService @@ -13,7 +13,7 @@ export class DummyRequestHandler implements Handler { this.dummyService = dummyService } - public async handle(inboundMessage: HandlerInboundMessage) { + public async handle(inboundMessage: MessageHandlerInboundMessage) { const connection = inboundMessage.assertReadyConnection() const responseMessage = await this.dummyService.processRequest(inboundMessage) diff --git a/samples/extension-module/dummy/handlers/DummyResponseHandler.ts b/samples/extension-module/dummy/handlers/DummyResponseHandler.ts index faca594166..f7acbdd0fb 100644 --- a/samples/extension-module/dummy/handlers/DummyResponseHandler.ts +++ b/samples/extension-module/dummy/handlers/DummyResponseHandler.ts @@ -1,9 +1,9 @@ import type { DummyService } from '../services' -import type { Handler, HandlerInboundMessage } from '@aries-framework/core' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { DummyResponseMessage } from '../messages' -export class DummyResponseHandler implements Handler { +export class DummyResponseHandler implements MessageHandler { public supportedMessages = [DummyResponseMessage] private dummyService: DummyService @@ -11,7 +11,7 @@ export class DummyResponseHandler implements Handler { this.dummyService = dummyService } - public async handle(inboundMessage: HandlerInboundMessage) { + public async handle(inboundMessage: MessageHandlerInboundMessage) { inboundMessage.assertReadyConnection() await this.dummyService.processResponse(inboundMessage) From ff6abdfc4e8ca64dd5a3b9859474bfc09e1a6c21 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 16 Dec 2022 19:55:32 +0800 Subject: [PATCH 097/125] feat(w3c): add custom document loader option (#1159) Signed-off-by: Timo Glastra --- .../tests/bbs-signatures.e2e.test.ts | 11 ++- ...ldproof.connectionless-credentials.test.ts | 32 ++++++-- .../src/modules/vc/W3cCredentialService.ts | 77 ++++++------------- packages/core/src/modules/vc/W3cVcModule.ts | 11 +++ .../core/src/modules/vc/W3cVcModuleConfig.ts | 46 +++++++++++ .../vc/__tests__/W3cCredentialService.test.ts | 16 ++-- .../modules/vc/__tests__/W3cVcModule.test.ts | 9 ++- .../vc/__tests__/W3cVcModuleConfig.test.ts | 19 +++++ .../modules/vc/__tests__/documentLoader.ts | 7 +- .../modules/vc/libraries/documentLoader.ts | 50 +++++++++++- ...tive.ts => nativeDocumentLoader.native.ts} | 2 +- .../vc/libraries/nativeDocumentLoader.ts | 8 ++ packages/core/tests/connections.test.ts | 1 - packages/core/tests/helpers.ts | 7 ++ 14 files changed, 213 insertions(+), 83 deletions(-) create mode 100644 packages/core/src/modules/vc/W3cVcModuleConfig.ts create mode 100644 packages/core/src/modules/vc/__tests__/W3cVcModuleConfig.test.ts rename packages/core/src/modules/vc/libraries/{documentLoader.native.ts => nativeDocumentLoader.native.ts} (78%) create mode 100644 packages/core/src/modules/vc/libraries/nativeDocumentLoader.ts diff --git a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts index 72a76fa517..8cd087c20e 100644 --- a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts @@ -5,7 +5,6 @@ import { VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, KeyType, JsonTransformer, - DidResolverService, DidKey, Key, SigningProviderRegistry, @@ -24,6 +23,7 @@ import { } from '@aries-framework/core' import { SignatureSuiteRegistry } from '../../core/src/modules/vc/SignatureSuiteRegistry' +import { W3cVcModuleConfig } from '../../core/src/modules/vc/W3cVcModuleConfig' import { customDocumentLoader } from '../../core/src/modules/vc/__tests__/documentLoader' import { getAgentConfig, getAgentContext } from '../../core/tests/helpers' import { BbsBlsSignature2020, BbsBlsSignatureProof2020, Bls12381g2SigningProvider } from '../src' @@ -62,7 +62,6 @@ const agentConfig = getAgentConfig('BbsSignaturesE2eTest') describeSkipNode17And18('BBS W3cCredentialService', () => { let wallet: IndyWallet let agentContext: AgentContext - let didResolverService: DidResolverService let w3cCredentialService: W3cCredentialService const seed = 'testseed000000000000000000000001' @@ -74,13 +73,13 @@ describeSkipNode17And18('BBS W3cCredentialService', () => { agentConfig, wallet, }) - didResolverService = new DidResolverService(agentConfig.logger, []) w3cCredentialService = new W3cCredentialService( {} as unknown as W3cCredentialRepository, - didResolverService, - signatureSuiteRegistry + signatureSuiteRegistry, + new W3cVcModuleConfig({ + documentLoader: customDocumentLoader, + }) ) - w3cCredentialService.documentLoaderWithContext = () => customDocumentLoader }) afterAll(async () => { diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts index 5d9d85f596..c5d06d2723 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts @@ -14,18 +14,36 @@ import testLogger from '../../../../../../tests/logger' import { Agent } from '../../../../../agent/Agent' import { InjectionSymbols } from '../../../../../constants' import { Ed25519Signature2018Fixtures } from '../../../../../modules/vc/__tests__/fixtures' +import { W3cVcModule } from '../../../../vc' +import { customDocumentLoader } from '../../../../vc/__tests__/documentLoader' import { W3cCredential } from '../../../../vc/models/' import { CredentialEventTypes } from '../../../CredentialEvents' import { CredentialState } from '../../../models' import { CredentialExchangeRecord } from '../../../repository' -const faberAgentOptions = getAgentOptions('Faber LD connection-less Credentials V2', { - endpoints: ['rxjs:faber'], -}) - -const aliceAgentOptions = getAgentOptions('Alice LD connection-less Credentials V2', { - endpoints: ['rxjs:alice'], -}) +const faberAgentOptions = getAgentOptions( + 'Faber LD connection-less Credentials V2', + { + endpoints: ['rxjs:faber'], + }, + { + w3cVc: new W3cVcModule({ + documentLoader: customDocumentLoader, + }), + } +) + +const aliceAgentOptions = getAgentOptions( + 'Alice LD connection-less Credentials V2', + { + endpoints: ['rxjs:alice'], + }, + { + w3cVc: new W3cVcModule({ + documentLoader: customDocumentLoader, + }), + } +) let wallet let credential: W3cCredential diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 039e3544c5..46ac773401 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -1,7 +1,6 @@ import type { AgentContext } from '../../agent/context' import type { Key } from '../../crypto/Key' import type { Query } from '../../storage/StorageService' -import type { DocumentLoader } from './jsonldUtil' import type { W3cVerifyCredentialResult } from './models' import type { CreatePresentationOptions, @@ -18,13 +17,13 @@ import { createWalletKeyPairClass } from '../../crypto/WalletKeyPair' import { AriesFrameworkError } from '../../error' import { injectable } from '../../plugins' import { JsonTransformer } from '../../utils' -import { DidResolverService, VerificationMethod } from '../dids' +import { VerificationMethod } from '../dids' import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type' import { SignatureSuiteRegistry } from './SignatureSuiteRegistry' +import { W3cVcModuleConfig } from './W3cVcModuleConfig' import { deriveProof } from './deriveProof' import { orArrayToArray, w3cDate } from './jsonldUtil' -import { getDocumentLoader } from './libraries/documentLoader' import jsonld from './libraries/jsonld' import vc from './libraries/vc' import { W3cVerifiableCredential } from './models' @@ -35,17 +34,17 @@ import { W3cCredentialRecord, W3cCredentialRepository } from './repository' @injectable() export class W3cCredentialService { private w3cCredentialRepository: W3cCredentialRepository - private didResolver: DidResolverService - private suiteRegistry: SignatureSuiteRegistry + private signatureSuiteRegistry: SignatureSuiteRegistry + private w3cVcModuleConfig: W3cVcModuleConfig public constructor( w3cCredentialRepository: W3cCredentialRepository, - didResolver: DidResolverService, - suiteRegistry: SignatureSuiteRegistry + signatureSuiteRegistry: SignatureSuiteRegistry, + w3cVcModuleConfig: W3cVcModuleConfig ) { this.w3cCredentialRepository = w3cCredentialRepository - this.didResolver = didResolver - this.suiteRegistry = suiteRegistry + this.signatureSuiteRegistry = signatureSuiteRegistry + this.w3cVcModuleConfig = w3cVcModuleConfig } /** @@ -61,7 +60,7 @@ export class W3cCredentialService { const WalletKeyPair = createWalletKeyPairClass(agentContext.wallet) const signingKey = await this.getPublicKeyFromVerificationMethod(agentContext, options.verificationMethod) - const suiteInfo = this.suiteRegistry.getByProofType(options.proofType) + const suiteInfo = this.signatureSuiteRegistry.getByProofType(options.proofType) if (!suiteInfo.keyTypes.includes(signingKey.keyType)) { throw new AriesFrameworkError('The key type of the verification method does not match the suite') @@ -90,7 +89,7 @@ export class W3cCredentialService { credential: JsonTransformer.toJSON(options.credential), suite: suite, purpose: options.proofPurpose, - documentLoader: this.documentLoaderWithContext(agentContext), + documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext), }) return JsonTransformer.fromJSON(result, W3cVerifiableCredential) @@ -111,7 +110,7 @@ export class W3cCredentialService { const verifyOptions: Record = { credential: JsonTransformer.toJSON(options.credential), suite: suites, - documentLoader: this.documentLoaderWithContext(agentContext), + documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext), } // this is a hack because vcjs throws if purpose is passed as undefined or null @@ -161,7 +160,7 @@ export class W3cCredentialService { // create keyPair const WalletKeyPair = createWalletKeyPairClass(agentContext.wallet) - const suiteInfo = this.suiteRegistry.getByProofType(options.signatureType) + const suiteInfo = this.signatureSuiteRegistry.getByProofType(options.signatureType) if (!suiteInfo) { throw new AriesFrameworkError(`The requested proofType ${options.signatureType} is not supported`) @@ -173,7 +172,7 @@ export class W3cCredentialService { throw new AriesFrameworkError('The key type of the verification method does not match the suite') } - const documentLoader = this.documentLoaderWithContext(agentContext) + const documentLoader = this.w3cVcModuleConfig.documentLoader(agentContext) const verificationMethodObject = (await documentLoader(options.verificationMethod)).document as Record< string, unknown @@ -200,7 +199,7 @@ export class W3cCredentialService { presentation: JsonTransformer.toJSON(options.presentation), suite: suite, challenge: options.challenge, - documentLoader: this.documentLoaderWithContext(agentContext), + documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext), }) return JsonTransformer.fromJSON(result, W3cVerifiablePresentation) @@ -229,7 +228,7 @@ export class W3cCredentialService { } const presentationSuites = proofs.map((proof) => { - const SuiteClass = this.suiteRegistry.getByProofType(proof.type).suiteClass + const SuiteClass = this.signatureSuiteRegistry.getByProofType(proof.type).suiteClass return new SuiteClass({ LDKeyClass: WalletKeyPair, proof: { @@ -253,7 +252,7 @@ export class W3cCredentialService { presentation: JsonTransformer.toJSON(options.presentation), suite: allSuites, challenge: options.challenge, - documentLoader: this.documentLoaderWithContext(agentContext), + documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext), } // this is a hack because vcjs throws if purpose is passed as undefined or null @@ -268,54 +267,24 @@ export class W3cCredentialService { public async deriveProof(agentContext: AgentContext, options: DeriveProofOptions): Promise { // TODO: make suite dynamic - const suiteInfo = this.suiteRegistry.getByProofType('BbsBlsSignatureProof2020') + const suiteInfo = this.signatureSuiteRegistry.getByProofType('BbsBlsSignatureProof2020') const SuiteClass = suiteInfo.suiteClass const suite = new SuiteClass() const proof = await deriveProof(JsonTransformer.toJSON(options.credential), options.revealDocument, { suite: suite, - documentLoader: this.documentLoaderWithContext(agentContext), + documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext), }) return proof } - public documentLoaderWithContext = (agentContext: AgentContext): DocumentLoader => { - return async (url: string) => { - if (url.startsWith('did:')) { - const result = await this.didResolver.resolve(agentContext, url) - - if (result.didResolutionMetadata.error || !result.didDocument) { - throw new AriesFrameworkError(`Unable to resolve DID: ${url}`) - } - - const framed = await jsonld.frame(result.didDocument.toJSON(), { - '@context': result.didDocument.context, - '@embed': '@never', - id: url, - }) - - return { - contextUrl: null, - documentUrl: url, - document: framed, - } - } - - // fetches the documentLoader from documentLoader.ts or documentLoader.native.ts depending on the platform at bundle time - const platformLoader = getDocumentLoader() - const loader = platformLoader.apply(jsonld, []) - - return await loader(url) - } - } - private async getPublicKeyFromVerificationMethod( agentContext: AgentContext, verificationMethod: string ): Promise { - const documentLoader = this.documentLoaderWithContext(agentContext) + const documentLoader = this.w3cVcModuleConfig.documentLoader(agentContext) const verificationMethodObject = await documentLoader(verificationMethod) const verificationMethodClass = JsonTransformer.fromJSON(verificationMethodObject.document, VerificationMethod) @@ -337,7 +306,7 @@ export class W3cCredentialService { // Get the expanded types const expandedTypes = ( await jsonld.expand(JsonTransformer.toJSON(options.credential), { - documentLoader: this.documentLoaderWithContext(agentContext), + documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext), }) )[0]['@type'] @@ -375,11 +344,11 @@ export class W3cCredentialService { } public getVerificationMethodTypesByProofType(proofType: string): string[] { - return this.suiteRegistry.getByProofType(proofType).verificationMethodTypes + return this.signatureSuiteRegistry.getByProofType(proofType).verificationMethodTypes } public getKeyTypesByProofType(proofType: string): string[] { - return this.suiteRegistry.getByProofType(proofType).keyTypes + return this.signatureSuiteRegistry.getByProofType(proofType).keyTypes } public async findCredentialRecordByQuery( @@ -400,7 +369,7 @@ export class W3cCredentialService { } return proofs.map((proof) => { - const SuiteClass = this.suiteRegistry.getByProofType(proof.type)?.suiteClass + const SuiteClass = this.signatureSuiteRegistry.getByProofType(proof.type)?.suiteClass if (SuiteClass) { return new SuiteClass({ LDKeyClass: WalletKeyPair, diff --git a/packages/core/src/modules/vc/W3cVcModule.ts b/packages/core/src/modules/vc/W3cVcModule.ts index b8191cc70c..96231aa168 100644 --- a/packages/core/src/modules/vc/W3cVcModule.ts +++ b/packages/core/src/modules/vc/W3cVcModule.ts @@ -1,4 +1,5 @@ import type { DependencyManager, Module } from '../../plugins' +import type { W3cVcModuleConfigOptions } from './W3cVcModuleConfig' import { KeyType } from '../../crypto' import { @@ -8,16 +9,26 @@ import { import { SignatureSuiteRegistry, SignatureSuiteToken } from './SignatureSuiteRegistry' import { W3cCredentialService } from './W3cCredentialService' +import { W3cVcModuleConfig } from './W3cVcModuleConfig' import { W3cCredentialRepository } from './repository/W3cCredentialRepository' import { Ed25519Signature2018 } from './signature-suites' export class W3cVcModule implements Module { + public readonly config: W3cVcModuleConfig + + public constructor(config?: W3cVcModuleConfigOptions) { + this.config = new W3cVcModuleConfig(config) + } + public register(dependencyManager: DependencyManager) { dependencyManager.registerSingleton(W3cCredentialService) dependencyManager.registerSingleton(W3cCredentialRepository) dependencyManager.registerSingleton(SignatureSuiteRegistry) + // Register the config + dependencyManager.registerInstance(W3cVcModuleConfig, this.config) + // Always register ed25519 signature suite dependencyManager.registerInstance(SignatureSuiteToken, { suiteClass: Ed25519Signature2018, diff --git a/packages/core/src/modules/vc/W3cVcModuleConfig.ts b/packages/core/src/modules/vc/W3cVcModuleConfig.ts new file mode 100644 index 0000000000..2c05a446a9 --- /dev/null +++ b/packages/core/src/modules/vc/W3cVcModuleConfig.ts @@ -0,0 +1,46 @@ +import type { DocumentLoaderWithContext } from './libraries/documentLoader' + +import { defaultDocumentLoader } from './libraries/documentLoader' + +/** + * W3cVcModuleConfigOptions defines the interface for the options of the W3cVcModuleConfig class. + * This can contain optional parameters that have default values in the config class itself. + */ +export interface W3cVcModuleConfigOptions { + /** + * Document loader to use for resolving JSON-LD objects. Takes a {@link AgentContext} as parameter, + * and must return a {@link DocumentLoader} function. + * + * @example + * ``` + * const myDocumentLoader = (agentContext: AgentContext) => { + * return async (url) => { + * if (url !== 'https://example.org') throw new Error("I don't know how to load this document") + * + * return { + * contextUrl: null, + * documentUrl: url, + * document: null + * } + * } + * } + * ``` + * + * + * @default {@link defaultDocumentLoader} + */ + documentLoader?: DocumentLoaderWithContext +} + +export class W3cVcModuleConfig { + private options: W3cVcModuleConfigOptions + + public constructor(options?: W3cVcModuleConfigOptions) { + this.options = options ?? {} + } + + /** See {@link W3cVcModuleConfigOptions.documentLoader} */ + public get documentLoader() { + return this.options.documentLoader ?? defaultDocumentLoader + } +} diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index 9971c97c3f..955ad1d827 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -7,13 +7,14 @@ import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { JsonTransformer } from '../../../utils/JsonTransformer' import { IndyWallet } from '../../../wallet/IndyWallet' import { WalletError } from '../../../wallet/error' -import { DidKey, DidResolverService } from '../../dids' +import { DidKey } from '../../dids' import { VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020, } from '../../dids/domain/key-type/ed25519' import { SignatureSuiteRegistry } from '../SignatureSuiteRegistry' import { W3cCredentialService } from '../W3cCredentialService' +import { W3cVcModuleConfig } from '../W3cVcModuleConfig' import { orArrayToArray } from '../jsonldUtil' import jsonld from '../libraries/jsonld' import { W3cCredential, W3cVerifiableCredential } from '../models' @@ -52,7 +53,7 @@ const agentConfig = getAgentConfig('W3cCredentialServiceTest') // Helper func const credentialRecordFactory = async (credential: W3cVerifiableCredential) => { const expandedTypes = ( - await jsonld.expand(JsonTransformer.toJSON(credential), { documentLoader: customDocumentLoader }) + await jsonld.expand(JsonTransformer.toJSON(credential), { documentLoader: customDocumentLoader() }) )[0]['@type'] // Create an instance of the w3cCredentialRecord @@ -65,7 +66,6 @@ const credentialRecordFactory = async (credential: W3cVerifiableCredential) => { describe('W3cCredentialService', () => { let wallet: IndyWallet let agentContext: AgentContext - let didResolverService: DidResolverService let w3cCredentialService: W3cCredentialService let w3cCredentialRepository: W3cCredentialRepository const seed = 'testseed000000000000000000000001' @@ -78,10 +78,14 @@ describe('W3cCredentialService', () => { agentConfig, wallet, }) - didResolverService = new DidResolverService(agentConfig.logger, []) w3cCredentialRepository = new W3cCredentialRepositoryMock() - w3cCredentialService = new W3cCredentialService(w3cCredentialRepository, didResolverService, signatureSuiteRegistry) - w3cCredentialService.documentLoaderWithContext = () => customDocumentLoader + w3cCredentialService = new W3cCredentialService( + w3cCredentialRepository, + signatureSuiteRegistry, + new W3cVcModuleConfig({ + documentLoader: customDocumentLoader, + }) + ) }) afterAll(async () => { diff --git a/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts b/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts index 2e98f11f63..873c8274e6 100644 --- a/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts @@ -3,6 +3,7 @@ import { DependencyManager } from '../../../plugins/DependencyManager' import { SignatureSuiteRegistry, SignatureSuiteToken } from '../SignatureSuiteRegistry' import { W3cCredentialService } from '../W3cCredentialService' import { W3cVcModule } from '../W3cVcModule' +import { W3cVcModuleConfig } from '../W3cVcModuleConfig' import { W3cCredentialRepository } from '../repository' import { Ed25519Signature2018 } from '../signature-suites' @@ -13,14 +14,18 @@ const dependencyManager = new DependencyManagerMock() describe('W3cVcModule', () => { test('registers dependencies on the dependency manager', () => { - new W3cVcModule().register(dependencyManager) + const module = new W3cVcModule() + + module.register(dependencyManager) expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(3) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(W3cCredentialService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(W3cCredentialRepository) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(SignatureSuiteRegistry) - expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(2) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(W3cVcModuleConfig, module.config) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(SignatureSuiteToken, { suiteClass: Ed25519Signature2018, verificationMethodTypes: ['Ed25519VerificationKey2018', 'Ed25519VerificationKey2020'], diff --git a/packages/core/src/modules/vc/__tests__/W3cVcModuleConfig.test.ts b/packages/core/src/modules/vc/__tests__/W3cVcModuleConfig.test.ts new file mode 100644 index 0000000000..e97d51389d --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/W3cVcModuleConfig.test.ts @@ -0,0 +1,19 @@ +import { W3cVcModuleConfig } from '../W3cVcModuleConfig' +import { defaultDocumentLoader } from '../libraries/documentLoader' + +describe('W3cVcModuleConfig', () => { + test('sets default values', () => { + const config = new W3cVcModuleConfig() + + expect(config.documentLoader).toBe(defaultDocumentLoader) + }) + + test('sets values', () => { + const documentLoader = jest.fn() + const config = new W3cVcModuleConfig({ + documentLoader, + }) + + expect(config.documentLoader).toBe(documentLoader) + }) +}) diff --git a/packages/core/src/modules/vc/__tests__/documentLoader.ts b/packages/core/src/modules/vc/__tests__/documentLoader.ts index ce0781ffac..29c47d7d91 100644 --- a/packages/core/src/modules/vc/__tests__/documentLoader.ts +++ b/packages/core/src/modules/vc/__tests__/documentLoader.ts @@ -1,5 +1,6 @@ +import type { AgentContext } from '../../../agent/context/AgentContext' import type { JsonObject } from '../../../types' -import type { DocumentLoaderResult } from '../jsonldUtil' +import type { DocumentLoaderResult } from '../libraries/jsonld' import jsonld from '../libraries/jsonld' @@ -103,6 +104,7 @@ async function _customDocumentLoader(url: string): Promise '@embed': '@never', id: url, }, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore { documentLoader: this } ) @@ -115,4 +117,5 @@ async function _customDocumentLoader(url: string): Promise } } -export const customDocumentLoader = _customDocumentLoader.bind(_customDocumentLoader) +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const customDocumentLoader = (agentContext?: AgentContext) => _customDocumentLoader.bind(_customDocumentLoader) diff --git a/packages/core/src/modules/vc/libraries/documentLoader.ts b/packages/core/src/modules/vc/libraries/documentLoader.ts index e7424258ee..d8679884d8 100644 --- a/packages/core/src/modules/vc/libraries/documentLoader.ts +++ b/packages/core/src/modules/vc/libraries/documentLoader.ts @@ -1,8 +1,50 @@ +import type { AgentContext } from '../../../agent/context/AgentContext' import type { DocumentLoader } from './jsonld' -export function getDocumentLoader(): () => DocumentLoader { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const loader = require('@digitalcredentials/jsonld/lib/documentLoaders/node') +import { AriesFrameworkError } from '../../../error/AriesFrameworkError' +import { DidResolverService } from '../../dids' - return loader as () => DocumentLoader +import jsonld from './jsonld' +import { getNativeDocumentLoader } from './nativeDocumentLoader' + +export type DocumentLoaderWithContext = (agentContext: AgentContext) => DocumentLoader + +export function defaultDocumentLoader(agentContext: AgentContext): DocumentLoader { + const didResolver = agentContext.dependencyManager.resolve(DidResolverService) + + async function loader(url: string) { + if (url.startsWith('did:')) { + const result = await didResolver.resolve(agentContext, url) + + if (result.didResolutionMetadata.error || !result.didDocument) { + throw new AriesFrameworkError(`Unable to resolve DID: ${url}`) + } + + const framed = await jsonld.frame( + result.didDocument.toJSON(), + { + '@context': result.didDocument.context, + '@embed': '@never', + id: url, + }, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + { documentLoader: this } + ) + + return { + contextUrl: null, + documentUrl: url, + document: framed, + } + } + + // fetches the documentLoader from documentLoader.ts or documentLoader.native.ts depending on the platform at bundle time + const platformLoader = getNativeDocumentLoader() + const nativeLoader = platformLoader.apply(jsonld, []) + + return await nativeLoader(url) + } + + return loader.bind(loader) } diff --git a/packages/core/src/modules/vc/libraries/documentLoader.native.ts b/packages/core/src/modules/vc/libraries/nativeDocumentLoader.native.ts similarity index 78% rename from packages/core/src/modules/vc/libraries/documentLoader.native.ts rename to packages/core/src/modules/vc/libraries/nativeDocumentLoader.native.ts index 11dcbd0826..79b502872f 100644 --- a/packages/core/src/modules/vc/libraries/documentLoader.native.ts +++ b/packages/core/src/modules/vc/libraries/nativeDocumentLoader.native.ts @@ -1,6 +1,6 @@ import type { DocumentLoader } from './jsonld' -export function getDocumentLoader(): () => DocumentLoader { +export function getNativeDocumentLoader(): () => DocumentLoader { // eslint-disable-next-line @typescript-eslint/no-var-requires const loader = require('@digitalcredentials/jsonld/lib/documentLoaders/xhr') diff --git a/packages/core/src/modules/vc/libraries/nativeDocumentLoader.ts b/packages/core/src/modules/vc/libraries/nativeDocumentLoader.ts new file mode 100644 index 0000000000..9ad2e61701 --- /dev/null +++ b/packages/core/src/modules/vc/libraries/nativeDocumentLoader.ts @@ -0,0 +1,8 @@ +import type { DocumentLoader } from './jsonld' + +export function getNativeDocumentLoader(): () => DocumentLoader { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const loader = require('@digitalcredentials/jsonld/lib/documentLoaders/node') + + return loader as () => DocumentLoader +} diff --git a/packages/core/tests/connections.test.ts b/packages/core/tests/connections.test.ts index 20ed36613e..60389f872d 100644 --- a/packages/core/tests/connections.test.ts +++ b/packages/core/tests/connections.test.ts @@ -10,7 +10,6 @@ import { Key, AgentEventTypes, KeylistUpdateMessage, - MediatorPickupStrategy, DidExchangeState, HandshakeProtocol, KeylistUpdateAction, diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index b236690458..f149b25f0b 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -28,6 +28,7 @@ import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutbou import { BbsModule } from '../../bbs-signatures/src/BbsModule' import { agentDependencies, WalletScheme } from '../../node/src' import { + W3cVcModule, Agent, AgentConfig, AgentContext, @@ -61,6 +62,7 @@ import { PresentationPreviewAttribute, PresentationPreviewPredicate, } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' +import { customDocumentLoader } from '../src/modules/vc/__tests__/documentLoader' import { LinkedAttachment } from '../src/utils/LinkedAttachment' import { uuid } from '../src/utils/uuid' @@ -672,6 +674,11 @@ export async function setupCredentialTests( // TODO remove the dependency on BbsModule const modules = { bbs: new BbsModule(), + + // Register custom w3cVc module so we can define the test document loader + w3cVc: new W3cVcModule({ + documentLoader: customDocumentLoader, + }), } const faberAgentOptions = getAgentOptions( faberName, From c9acef3a7eb8b51ba293b2bfab0fd68cde9924ce Mon Sep 17 00:00:00 2001 From: NB-MikeRichardson <93971245+NB-MikeRichardson@users.noreply.github.com> Date: Fri, 16 Dec 2022 16:18:35 +0200 Subject: [PATCH 098/125] refactor(credentials): credential format service improvements (#1140) Signed-off-by: Mike Richardson --- ...proof.credentials.propose-offerBbs.test.ts | 76 +++++------- .../JsonLdCredentialFormatService.test.ts | 45 +++---- .../formats/jsonld/JsonLdCredentialFormat.ts | 66 +++++++++-- .../jsonld/JsonLdCredentialFormatService.ts | 112 +++++++++--------- ...ldproof.connectionless-credentials.test.ts | 52 ++++++-- ...v2.ldproof.credentials-auto-accept.test.ts | 33 ++++-- ...f.credentials.propose-offerED25519.test.ts | 42 +++---- .../tests/v2-connectionless-proofs.test.ts | 1 + 8 files changed, 247 insertions(+), 180 deletions(-) diff --git a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts index 2ca92faa8d..94f100990a 100644 --- a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts +++ b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts @@ -1,6 +1,6 @@ import type { Agent } from '../../core/src/agent/Agent' import type { ConnectionRecord } from '../../core/src/modules/connections' -import type { SignCredentialOptionsRFC0593 } from '../../core/src/modules/credentials/formats/jsonld' +import type { JsonCredential, JsonLdSignCredentialFormat } from '../../core/src/modules/credentials/formats/jsonld' import type { Wallet } from '../../core/src/wallet' import { InjectionSymbols } from '../../core/src/constants' @@ -11,7 +11,6 @@ import { V2OfferCredentialMessage } from '../../core/src/modules/credentials/pro import { CredentialExchangeRecord } from '../../core/src/modules/credentials/repository/CredentialExchangeRecord' import { DidKey } from '../../core/src/modules/dids' import { CREDENTIALS_CONTEXT_V1_URL, SECURITY_CONTEXT_BBS_URL } from '../../core/src/modules/vc' -import { W3cCredential } from '../../core/src/modules/vc/models/credential/W3cCredential' import { DidCommMessageRepository } from '../../core/src/storage' import { JsonTransformer } from '../../core/src/utils/JsonTransformer' import { setupCredentialTests, waitForCredentialRecord } from '../../core/tests/helpers' @@ -25,37 +24,11 @@ let aliceConnection: ConnectionRecord let aliceCredentialRecord: CredentialExchangeRecord let faberCredentialRecord: CredentialExchangeRecord -const TEST_LD_DOCUMENT = { - '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], - id: 'https://issuer.oidp.uscis.gov/credentials/83627465', - type: ['VerifiableCredential', 'PermanentResidentCard'], - issuer: '', - identifier: '83627465', - name: 'Permanent Resident Card', - description: 'Government of Example Permanent Resident Card.', - issuanceDate: '2019-12-03T12:19:52Z', - expirationDate: '2029-12-03T12:19:52Z', - credentialSubject: { - id: 'did:example:b34ca6cd37bbf23', - type: ['PermanentResident', 'Person'], - givenName: 'JOHN', - familyName: 'SMITH', - gender: 'Male', - image: 'data:image/png;base64,iVBORw0KGgokJggg==', - residentSince: '2015-01-01', - lprCategory: 'C09', - lprNumber: '999-999-999', - commuterClassification: 'C1', - birthCountry: 'Bahamas', - birthDate: '1958-07-17', - }, -} - describeSkipNode17And18('credentials, BBS+ signature', () => { let wallet let issuerDidKey: DidKey let didCommMessageRepository: DidCommMessageRepository - let signCredentialOptions: SignCredentialOptionsRFC0593 + let signCredentialOptions: JsonLdSignCredentialFormat const seed = 'testseed000000000000000000000001' beforeAll(async () => { ;({ faberAgent, aliceAgent, aliceConnection } = await setupCredentialTests( @@ -68,7 +41,6 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { issuerDidKey = new DidKey(key) }) - afterAll(async () => { await faberAgent.shutdown() await faberAgent.wallet.delete() @@ -80,13 +52,33 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { testLogger.test('Alice sends (v2 jsonld) credential proposal to Faber') // set the propose options - const credentialJson = TEST_LD_DOCUMENT - credentialJson.issuer = issuerDidKey.did - - const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) - + const TEST_LD_DOCUMENT: JsonCredential = { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: issuerDidKey.did, + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + identifier: '83627465', + name: 'Permanent Resident Card', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + description: 'Government of Example Permanent Resident Card.', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + } signCredentialOptions = { - credential, + credential: TEST_LD_DOCUMENT, options: { proofType: 'BbsBlsSignature2020', proofPurpose: 'assertionMethod', @@ -118,9 +110,6 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { await faberAgent.credentials.acceptProposal({ credentialRecordId: faberCredentialRecord.id, comment: 'V2 W3C Offer', - credentialFormats: { - jsonld: signCredentialOptions, - }, }) testLogger.test('Alice waits for credential offer from Faber') @@ -200,9 +189,6 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { await faberAgent.credentials.acceptRequest({ credentialRecordId: faberCredentialRecord.id, comment: 'V2 W3C Offer', - credentialFormats: { - jsonld: signCredentialOptions, - }, }) testLogger.test('Alice waits for credential from Faber') @@ -245,11 +231,10 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { type: ['VerifiableCredential', 'PermanentResidentCard'], issuer: 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', - identifier: '83627465', - name: 'Permanent Resident Card', - description: 'Government of Example Permanent Resident Card.', issuanceDate: '2019-12-03T12:19:52Z', expirationDate: '2029-12-03T12:19:52Z', + identifier: '83627465', + name: 'Permanent Resident Card', credentialSubject: { id: 'did:example:b34ca6cd37bbf23', type: ['PermanentResident', 'Person'], @@ -258,6 +243,7 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { gender: 'Male', image: 'data:image/png;base64,iVBORw0KGgokJggg==', residentSince: '2015-01-01', + description: 'Government of Example Permanent Resident Card.', lprCategory: 'C09', lprNumber: '999-999-999', commuterClassification: 'C1', diff --git a/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts index a66b8abe65..d06f3bf3be 100644 --- a/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts +++ b/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts @@ -1,9 +1,9 @@ import type { AgentContext } from '../../../../agent' import type { CredentialFormatService } from '../../formats' import type { - JsonLdAcceptRequestOptions, + JsonCredential, JsonLdCredentialFormat, - SignCredentialOptionsRFC0593, + JsonLdSignCredentialFormat, } from '../../formats/jsonld/JsonLdCredentialFormat' import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' import type { V2OfferCredentialMessageOptions } from '../../protocol/v2/messages/V2OfferCredentialMessage' @@ -18,7 +18,6 @@ import { W3cCredentialRecord, W3cCredentialService } from '../../../vc' import { Ed25519Signature2018Fixtures } from '../../../vc/__tests__/fixtures' import { CREDENTIALS_CONTEXT_V1_URL } from '../../../vc/constants' import { W3cVerifiableCredential } from '../../../vc/models' -import { W3cCredential } from '../../../vc/models/credential/W3cCredential' import { JsonLdCredentialFormatService } from '../../formats/jsonld/JsonLdCredentialFormatService' import { CredentialState } from '../../models' import { INDY_CREDENTIAL_OFFER_ATTACHMENT_ID } from '../../protocol/v1/messages' @@ -89,7 +88,7 @@ const offerAttachment = new Attachment({ const credentialAttachment = new Attachment({ mimeType: 'application/json', data: new AttachmentData({ - base64: JsonEncoder.toBase64(vc), + base64: JsonEncoder.toBase64(vcJson), }), }) @@ -137,7 +136,7 @@ const mockCredentialRecord = ({ return credentialRecord } -const inputDoc = { +const inputDocAsJson: JsonCredential = { '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], type: ['VerifiableCredential', 'UniversityDegreeCredential'], issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', @@ -151,10 +150,9 @@ const inputDoc = { }, } const verificationMethod = `8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K#8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K` -const credential = JsonTransformer.fromJSON(inputDoc, W3cCredential) -const signCredentialOptions: SignCredentialOptionsRFC0593 = { - credential, +const signCredentialOptions: JsonLdSignCredentialFormat = { + credential: inputDocAsJson, options: { proofPurpose: 'assertionMethod', proofType: 'Ed25519Signature2018', @@ -200,7 +198,7 @@ describe('JsonLd CredentialFormatService', () => { byteCount: undefined, data: { base64: - 'eyJjcmVkZW50aWFsIjp7ImNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZ2czNDJZY3B1azI2M1I5ZDhBcTZNVWF4UG4xRERlSHlHbzM4RWVmWG1nREwiLCJpc3N1YW5jZURhdGUiOiIyMDE3LTEwLTIyVDEyOjIzOjQ4WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjaGVsb3Igb2YgU2NpZW5jZSBhbmQgQXJ0cyJ9LCJhbHVtbmlPZiI6Im9vcHMifX0sIm9wdGlvbnMiOnsicHJvb2ZQdXJwb3NlIjoiYXNzZXJ0aW9uTWV0aG9kIiwicHJvb2ZUeXBlIjoiRWQyNTUxOVNpZ25hdHVyZTIwMTgifX0=', + 'eyJjcmVkZW50aWFsIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWVDcmVkZW50aWFsIl0sImlzc3VlciI6ImRpZDprZXk6ejZNa2dnMzQyWWNwdWsyNjNSOWQ4QXE2TVVheFBuMUREZUh5R28zOEVlZlhtZ0RMIiwiaXNzdWFuY2VEYXRlIjoiMjAxNy0xMC0yMlQxMjoyMzo0OFoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifSwiYWx1bW5pT2YiOiJvb3BzIn19LCJvcHRpb25zIjp7InByb29mUHVycG9zZSI6ImFzc2VydGlvbk1ldGhvZCIsInByb29mVHlwZSI6IkVkMjU1MTlTaWduYXR1cmUyMDE4In19', json: undefined, links: undefined, jws: undefined, @@ -233,7 +231,7 @@ describe('JsonLd CredentialFormatService', () => { byteCount: undefined, data: { base64: - 'eyJjcmVkZW50aWFsIjp7ImNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rZ2czNDJZY3B1azI2M1I5ZDhBcTZNVWF4UG4xRERlSHlHbzM4RWVmWG1nREwiLCJpc3N1YW5jZURhdGUiOiIyMDE3LTEwLTIyVDEyOjIzOjQ4WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjaGVsb3Igb2YgU2NpZW5jZSBhbmQgQXJ0cyJ9LCJhbHVtbmlPZiI6Im9vcHMifX0sIm9wdGlvbnMiOnsicHJvb2ZQdXJwb3NlIjoiYXNzZXJ0aW9uTWV0aG9kIiwicHJvb2ZUeXBlIjoiRWQyNTUxOVNpZ25hdHVyZTIwMTgifX0=', + 'eyJjcmVkZW50aWFsIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlVuaXZlcnNpdHlEZWdyZWVDcmVkZW50aWFsIl0sImlzc3VlciI6ImRpZDprZXk6ejZNa2dnMzQyWWNwdWsyNjNSOWQ4QXE2TVVheFBuMUREZUh5R28zOEVlZlhtZ0RMIiwiaXNzdWFuY2VEYXRlIjoiMjAxNy0xMC0yMlQxMjoyMzo0OFoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifSwiYWx1bW5pT2YiOiJvb3BzIn19LCJvcHRpb25zIjp7InByb29mUHVycG9zZSI6ImFzc2VydGlvbk1ldGhvZCIsInByb29mVHlwZSI6IkVkMjU1MTlTaWduYXR1cmUyMDE4In19', json: undefined, links: undefined, jws: undefined, @@ -293,13 +291,14 @@ describe('JsonLd CredentialFormatService', () => { const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' test('Derive Verification Method', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any mockFunction(didResolver.resolveDidDocument).mockReturnValue(Promise.resolve(didDocument as any)) mockFunction(w3cCredentialService.getVerificationMethodTypesByProofType).mockReturnValue([ 'Ed25519VerificationKey2018', ]) const service = jsonldFormatService as JsonLdCredentialFormatService - const credentialRequest = requestAttachment.getDataAsJson() + const credentialRequest = requestAttachment.getDataAsJson() // calls private method in the format service const verificationMethod = await service['deriveVerificationMethod']( @@ -322,15 +321,12 @@ describe('JsonLd CredentialFormatService', () => { connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - const acceptRequestOptions: JsonLdAcceptRequestOptions = { - ...signCredentialOptions, - verificationMethod, - } - const { format, attachment } = await jsonldFormatService.acceptRequest(agentContext, { credentialRecord, credentialFormats: { - jsonld: acceptRequestOptions, + jsonld: { + verificationMethod, + }, }, requestAttachment, offerAttachment, @@ -365,7 +361,7 @@ describe('JsonLd CredentialFormatService', () => { state: CredentialState.RequestSent, }) let w3c: W3cCredentialRecord - let signCredentialOptionsWithProperty: SignCredentialOptionsRFC0593 + let signCredentialOptionsWithProperty: JsonLdSignCredentialFormat beforeEach(async () => { signCredentialOptionsWithProperty = signCredentialOptions signCredentialOptionsWithProperty.options = { @@ -387,6 +383,7 @@ describe('JsonLd CredentialFormatService', () => { }) test('finds credential record by thread ID and saves credential attachment into the wallet', async () => { // given + // eslint-disable-next-line @typescript-eslint/no-explicit-any mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) // when @@ -412,15 +409,15 @@ describe('JsonLd CredentialFormatService', () => { }, } - const vc = JsonTransformer.fromJSON(vcJson, W3cVerifiableCredential) const credentialAttachment = new Attachment({ mimeType: 'application/json', data: new AttachmentData({ - base64: JsonEncoder.toBase64(vc), + base64: JsonEncoder.toBase64(vcJson), }), }) // given + // eslint-disable-next-line @typescript-eslint/no-explicit-any mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) // when/then @@ -442,6 +439,7 @@ describe('JsonLd CredentialFormatService', () => { }), }) // given + // eslint-disable-next-line @typescript-eslint/no-explicit-any mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) // when/then @@ -464,6 +462,7 @@ describe('JsonLd CredentialFormatService', () => { }) // given + // eslint-disable-next-line @typescript-eslint/no-explicit-any mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) // when/then @@ -486,6 +485,7 @@ describe('JsonLd CredentialFormatService', () => { }) // given + // eslint-disable-next-line @typescript-eslint/no-explicit-any mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) // when/then @@ -508,6 +508,7 @@ describe('JsonLd CredentialFormatService', () => { }) // given + // eslint-disable-next-line @typescript-eslint/no-explicit-any mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) // when/then @@ -525,7 +526,7 @@ describe('JsonLd CredentialFormatService', () => { id: 'cdb0669b-7cd6-46bc-b1c7-7034f86083ac', mimeType: 'application/json', data: new AttachmentData({ - base64: JsonEncoder.toBase64(inputDoc), + base64: JsonEncoder.toBase64(inputDocAsJson), }), }) @@ -533,7 +534,7 @@ describe('JsonLd CredentialFormatService', () => { id: '9a8ff4fb-ac86-478f-b7f9-fbf3f9cc60e6', mimeType: 'application/json', data: new AttachmentData({ - base64: JsonEncoder.toBase64(inputDoc), + base64: JsonEncoder.toBase64(inputDocAsJson), }), }) diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts index 9855b49d45..e4d0fb111c 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts @@ -1,25 +1,71 @@ -import type { W3cCredential } from '../../../vc/models/credential/W3cCredential' +import type { JsonObject } from '../../../../types' +import type { SingleOrArray } from '../../../../utils' +import type { IssuerOptions } from '../../../vc/models/credential/Issuer' import type { CredentialFormat } from '../CredentialFormat' import type { JsonLdOptionsRFC0593 } from './JsonLdOptionsRFC0593' -export interface JsonLdAcceptRequestOptions extends SignCredentialOptionsRFC0593 { - verificationMethod?: string +export interface JsonCredential { + '@context': Array | JsonObject + id?: string + type: Array + issuer: string | IssuerOptions + issuanceDate: string + expirationDate?: string + credentialSubject: SingleOrArray + [key: string]: unknown +} + +// this is the API interface (only) +export interface JsonLdSignCredentialFormat { + credential: JsonCredential + options: JsonLdOptionsRFC0593 } +// use this interface internally as the above may diverge in future export interface SignCredentialOptionsRFC0593 { - credential: W3cCredential + credential: JsonCredential options: JsonLdOptionsRFC0593 } +export interface JsonVerifiableCredential extends JsonLdSignCredentialFormat { + proof: { + type: string + proofPurpose: string + verificationMethod: string + created: string + domain?: string + challenge?: string + jws?: string + proofValue?: string + nonce?: string + [key: string]: unknown + } +} + +// use empty object in the acceptXXX jsonld format interface so we indicate that +// the jsonld format service needs to be invoked +type EmptyObject = Record + +// it is an option to provide the verification method in acceptRequest +export interface JsonLdCreateRequestFormat { + verificationMethod?: string +} + export interface JsonLdCredentialFormat extends CredentialFormat { formatKey: 'jsonld' credentialRecordType: 'w3c' credentialFormats: { - createProposal: SignCredentialOptionsRFC0593 - acceptProposal: SignCredentialOptionsRFC0593 - createOffer: SignCredentialOptionsRFC0593 - acceptOffer: SignCredentialOptionsRFC0593 - createRequest: SignCredentialOptionsRFC0593 - acceptRequest: JsonLdAcceptRequestOptions + createProposal: JsonLdSignCredentialFormat + acceptProposal: EmptyObject + createOffer: JsonLdSignCredentialFormat + acceptOffer: EmptyObject + createRequest: JsonLdSignCredentialFormat + acceptRequest: JsonLdCreateRequestFormat + } + formatData: { + proposal: JsonLdSignCredentialFormat + offer: JsonLdSignCredentialFormat + request: JsonLdSignCredentialFormat + credential: JsonVerifiableCredential } } diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts index e8fdd4f329..2a654063c9 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts @@ -18,15 +18,18 @@ import type { FormatProcessCredentialOptions, FormatProcessOptions, } from '../CredentialFormatServiceOptions' -import type { JsonLdCredentialFormat, SignCredentialOptionsRFC0593 } from './JsonLdCredentialFormat' -import type { JsonLdOptionsRFC0593 } from './JsonLdOptionsRFC0593' +import type { + JsonLdCredentialFormat, + JsonLdSignCredentialFormat, + JsonCredential, + SignCredentialOptionsRFC0593, +} from './JsonLdCredentialFormat' import { injectable } from 'tsyringe' import { AriesFrameworkError } from '../../../../error' import { objectEquality } from '../../../../utils' import { JsonTransformer } from '../../../../utils/JsonTransformer' -import { MessageValidator } from '../../../../utils/MessageValidator' import { findVerificationMethodByKeyType } from '../../../dids/domain/DidDocument' import { DidResolverService } from '../../../dids/services/DidResolverService' import { W3cCredentialService } from '../../../vc' @@ -73,8 +76,8 @@ export class JsonLdCredentialFormatService extends CredentialFormatService + { attachId, proposalAttachment }: FormatAcceptProposalOptions ): Promise { // if the offer has an attachment Id use that, otherwise the generated id of the formats object const format = new CredentialFormatSpec({ @@ -106,18 +109,10 @@ export class JsonLdCredentialFormatService extends CredentialFormatService() - const jsonLdCredentialProposal = new JsonLdCredentialDetail(credentialProposal) - MessageValidator.validateSync(jsonLdCredentialProposal) + JsonTransformer.fromJSON(credentialProposal, JsonLdCredentialDetail) - const offerData = jsonLdFormat ?? credentialProposal + const offerData = credentialProposal const attachment = this.getFormatData(offerData, format.attachId) @@ -146,9 +141,9 @@ export class JsonLdCredentialFormatService extends CredentialFormatService + { attachId, offerAttachment }: FormatAcceptOfferOptions ): Promise { - const jsonLdFormat = credentialFormats?.jsonld - const credentialOffer = offerAttachment.getDataAsJson() - const requestData = jsonLdFormat ?? credentialOffer - const jsonLdCredential = new JsonLdCredentialDetail(requestData) - MessageValidator.validateSync(jsonLdCredential) + // validate + JsonTransformer.fromJSON(credentialOffer, JsonLdCredentialDetail) const format = new CredentialFormatSpec({ attachId, format: JSONLD_VC_DETAIL, }) - const attachment = this.getFormatData(requestData, format.attachId) + const attachment = this.getFormatData(credentialOffer, format.attachId) return { format, attachment } } @@ -207,8 +198,8 @@ export class JsonLdCredentialFormatService extends CredentialFormatService ): Promise { - const jsonLdFormat = credentialFormats?.jsonld - // sign credential here. credential to be signed is received as the request attachment // (attachment in the request message from holder to issuer) const credentialRequest = requestAttachment.getDataAsJson() - const credentialData = jsonLdFormat ?? credentialRequest - const jsonLdCredential = new JsonLdCredentialDetail(credentialData) - MessageValidator.validateSync(jsonLdCredential) - const verificationMethod = credentialFormats?.jsonld?.verificationMethod ?? - (await this.deriveVerificationMethod(agentContext, credentialData.credential, credentialRequest)) + (await this.deriveVerificationMethod(agentContext, credentialRequest.credential, credentialRequest)) if (!verificationMethod) { throw new AriesFrameworkError('Missing verification method in credential data') @@ -252,7 +237,7 @@ export class JsonLdCredentialFormatService extends CredentialFormatService { + const credential = JsonTransformer.fromJSON(credentialAsJson, W3cCredential) + // extract issuer from vc (can be string or Issuer) let issuerDid = credential.issuer @@ -317,22 +306,24 @@ export class JsonLdCredentialFormatService extends CredentialFormatService { const credentialAsJson = attachment.getDataAsJson() const credential = JsonTransformer.fromJSON(credentialAsJson, W3cVerifiableCredential) - MessageValidator.validateSync(credential) // compare stuff in the proof object of the credential and request...based on aca-py const requestAsJson = requestAttachment.getDataAsJson() - const request = JsonTransformer.fromJSON(requestAsJson, JsonLdCredentialDetail) if (Array.isArray(credential.proof)) { - // question: what do we compare here, each element of the proof array with the request??? throw new AriesFrameworkError('Credential arrays are not supported') } else { // do checks here - this.compareCredentialSubject(credential, request.credential) - this.compareProofs(credential.proof, request.options) + this.compareCredentialSubject(credential, requestAsJson) + this.compareProofs(credential.proof, requestAsJson) } + // verify signatures of the credential + const result = await this.w3cCredentialService.verifyCredential(agentContext, { credential }) + if (result && !result.verified) { + throw new AriesFrameworkError(`Failed to validate credential, error = ${result.error}`) + } const verifiableCredential = await this.w3cCredentialService.storeCredential(agentContext, { credential: credential, }) @@ -343,13 +334,22 @@ export class JsonLdCredentialFormatService extends CredentialFormatService() - const request = JsonTransformer.fromJSON(requestAsJson, JsonLdCredentialDetail) - this.compareCredentialSubject(credential, request.credential) - this.compareProofs(credential.proof, request.options) + + this.compareCredentialSubject(credential, requestAsJson) + this.compareProofs(credential.proof, requestAsJson) return true } catch (error) { return false diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts index c5d06d2723..7e81510800 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts @@ -2,21 +2,21 @@ import type { SubjectMessage } from '../../../../../../../../tests/transport/Sub import type { Wallet } from '../../../../../wallet' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' import type { CreateOfferOptions } from '../../../CredentialsApiOptions' -import type { SignCredentialOptionsRFC0593 } from '../../../formats/jsonld/JsonLdCredentialFormat' +import type { JsonCredential, JsonLdSignCredentialFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' +import type { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' import { ReplaySubject, Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' -import { JsonTransformer } from '../../../../../../src/utils' import { getAgentOptions, prepareForIssuance, waitForCredentialRecordSubject } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { Agent } from '../../../../../agent/Agent' import { InjectionSymbols } from '../../../../../constants' -import { Ed25519Signature2018Fixtures } from '../../../../../modules/vc/__tests__/fixtures' +import { JsonEncoder } from '../../../../../utils/JsonEncoder' import { W3cVcModule } from '../../../../vc' import { customDocumentLoader } from '../../../../vc/__tests__/documentLoader' -import { W3cCredential } from '../../../../vc/models/' +import { CREDENTIALS_CONTEXT_V1_URL } from '../../../../vc/constants' import { CredentialEventTypes } from '../../../CredentialEvents' import { CredentialState } from '../../../models' import { CredentialExchangeRecord } from '../../../repository' @@ -46,8 +46,7 @@ const aliceAgentOptions = getAgentOptions( ) let wallet -let credential: W3cCredential -let signCredentialOptions: SignCredentialOptionsRFC0593 +let signCredentialOptions: JsonLdSignCredentialFormat describe('credentials', () => { let faberAgent: Agent @@ -55,7 +54,18 @@ describe('credentials', () => { let faberReplay: ReplaySubject let aliceReplay: ReplaySubject const seed = 'testseed000000000000000000000001' - + const TEST_LD_DOCUMENT: JsonCredential = { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, + }, + } beforeEach(async () => { const faberMessages = new Subject() const aliceMessages = new Subject() @@ -88,10 +98,8 @@ describe('credentials', () => { wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) await wallet.createDid({ seed }) - credential = JsonTransformer.fromJSON(Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT, W3cCredential) - signCredentialOptions = { - credential, + credential: TEST_LD_DOCUMENT, options: { proofType: 'Ed25519Signature2018', proofPurpose: 'assertionMethod', @@ -119,6 +127,30 @@ describe('credentials', () => { // eslint-disable-next-line prefer-const let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer(offerOptions) + const offerMsg = message as V2OfferCredentialMessage + const attachment = offerMsg?.offerAttachments[0] + + if (attachment.data.base64) { + expect(JsonEncoder.fromBase64(attachment.data.base64)).toMatchObject({ + credential: { + '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + name: 'Bachelor of Science and Arts', + type: 'BachelorDegree', + }, + }, + }, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, + }) + } + const { message: offerMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ recordId: faberCredentialRecord.id, message, diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts index d91a5afb01..80bc4dd2ef 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts @@ -3,8 +3,9 @@ import type { Agent } from '../../../../../agent/Agent' import type { Wallet } from '../../../../../wallet' import type { ConnectionRecord } from '../../../../connections' import type { + JsonCredential, JsonLdCredentialFormat, - SignCredentialOptionsRFC0593, + JsonLdSignCredentialFormat, } from '../../../formats/jsonld/JsonLdCredentialFormat' import type { V2CredentialService } from '../V2CredentialService' @@ -12,20 +13,30 @@ import { setupCredentialTests, waitForCredentialRecord } from '../../../../../.. import testLogger from '../../../../../../tests/logger' import { InjectionSymbols } from '../../../../../constants' import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' -import { Ed25519Signature2018Fixtures } from '../../../../../modules/vc/__tests__/fixtures' -import { W3cCredential } from '../../../../../modules/vc/models' -import { JsonTransformer } from '../../../../../utils' +import { CREDENTIALS_CONTEXT_V1_URL } from '../../../../vc/constants' import { AutoAcceptCredential, CredentialState } from '../../../models' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +const TEST_LD_DOCUMENT: JsonCredential = { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, + }, +} + describe('credentials', () => { let faberAgent: Agent let aliceAgent: Agent let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord let aliceCredentialRecord: CredentialExchangeRecord - let credential: W3cCredential - let signCredentialOptions: SignCredentialOptionsRFC0593 + let signCredentialOptions: JsonLdSignCredentialFormat let wallet const seed = 'testseed000000000000000000000001' @@ -37,11 +48,10 @@ describe('credentials', () => { AutoAcceptCredential.Always )) - credential = JsonTransformer.fromJSON(Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT, W3cCredential) wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) await wallet.createDid({ seed }) signCredentialOptions = { - credential, + credential: TEST_LD_DOCUMENT, options: { proofType: 'Ed25519Signature2018', proofPurpose: 'assertionMethod', @@ -72,6 +82,7 @@ describe('credentials', () => { const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(options) testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { threadId: aliceCredentialExchangeRecord.threadId, state: CredentialState.CredentialReceived, @@ -140,11 +151,10 @@ describe('credentials', () => { 'alice agent: content-approved v2 jsonld', AutoAcceptCredential.ContentApproved )) - credential = JsonTransformer.fromJSON(Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT, W3cCredential) wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) await wallet.createDid({ seed }) signCredentialOptions = { - credential, + credential: TEST_LD_DOCUMENT, options: { proofType: 'Ed25519Signature2018', proofPurpose: 'assertionMethod', @@ -180,9 +190,6 @@ describe('credentials', () => { const faberCredentialExchangeRecord = await faberAgent.credentials.acceptProposal({ credentialRecordId: faberCredentialRecord.id, comment: 'V2 JsonLd Offer', - credentialFormats: { - jsonld: signCredentialOptions, - }, }) testLogger.test('Alice waits for credential from Faber') diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts index f712bfa43e..cc71ebec34 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -1,14 +1,13 @@ import type { Agent } from '../../../../../agent/Agent' import type { Wallet } from '../../../../../wallet' import type { ConnectionRecord } from '../../../../connections' -import type { SignCredentialOptionsRFC0593 } from '../../../formats/jsonld/JsonLdCredentialFormat' +import type { JsonCredential, JsonLdSignCredentialFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { InjectionSymbols } from '../../../../../constants' import { DidCommMessageRepository } from '../../../../../storage' import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { W3cCredential } from '../../../../vc/models/credential/W3cCredential' import { CredentialState } from '../../../models' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { V2CredentialPreview } from '../messages' @@ -24,7 +23,7 @@ describe('credentials', () => { let didCommMessageRepository: DidCommMessageRepository - const inputDoc = { + const inputDocAsJson: JsonCredential = { '@context': [ 'https://www.w3.org/2018/credentials/v1', 'https://w3id.org/citizenship/v1', @@ -33,11 +32,10 @@ describe('credentials', () => { id: 'https://issuer.oidp.uscis.gov/credentials/83627465', type: ['VerifiableCredential', 'PermanentResidentCard'], issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - identifier: '83627465', - name: 'Permanent Resident Card', - description: 'Government of Example Permanent Resident Card.', issuanceDate: '2019-12-03T12:19:52Z', expirationDate: '2029-12-03T12:19:52Z', + identifier: '83627465', + name: 'Permanent Resident Card', credentialSubject: { id: 'did:example:b34ca6cd37bbf23', type: ['PermanentResident', 'Person'], @@ -46,6 +44,7 @@ describe('credentials', () => { gender: 'Male', image: 'data:image/png;base64,iVBORw0KGgokJggg==', residentSince: '2015-01-01', + description: 'Government of Example Permanent Resident Card.', lprCategory: 'C09', lprNumber: '999-999-999', commuterClassification: 'C1', @@ -54,9 +53,7 @@ describe('credentials', () => { }, } - const credential = JsonTransformer.fromJSON(inputDoc, W3cCredential) - - let signCredentialOptions: SignCredentialOptionsRFC0593 + let signCredentialOptions: JsonLdSignCredentialFormat let wallet const seed = 'testseed000000000000000000000001' @@ -70,7 +67,7 @@ describe('credentials', () => { wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) await wallet.createDid({ seed }) signCredentialOptions = { - credential, + credential: inputDocAsJson, options: { proofType: 'Ed25519Signature2018', proofPurpose: 'assertionMethod', @@ -112,9 +109,6 @@ describe('credentials', () => { await faberAgent.credentials.acceptProposal({ credentialRecordId: faberCredentialRecord.id, comment: 'V2 W3C Offer', - credentialFormats: { - jsonld: signCredentialOptions, - }, }) testLogger.test('Alice waits for credential offer from Faber') @@ -171,7 +165,7 @@ describe('credentials', () => { const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ credentialRecordId: aliceCredentialRecord.id, credentialFormats: { - jsonld: undefined, + jsonld: {}, }, }) @@ -308,7 +302,7 @@ describe('credentials', () => { credentialDefinitionId: credDefId, attributes: credentialPreview.attributes, }, - jsonld: signCredentialOptions, + jsonld: {}, // this is to ensure both services are formatted }, }) @@ -325,11 +319,11 @@ describe('credentials', () => { messageClass: V2OfferCredentialMessage, }) - const credOfferJson = offerMessage?.offerAttachments[1].getDataAsJson() + const credOfferJson = offerMessage?.offerAttachments[1].getDataAsJson() expect(credOfferJson).toMatchObject({ credential: { - context: [ + '@context': [ 'https://www.w3.org/2018/credentials/v1', 'https://w3id.org/citizenship/v1', 'https://w3id.org/security/bbs/v1', @@ -337,18 +331,18 @@ describe('credentials', () => { id: 'https://issuer.oidp.uscis.gov/credentials/83627465', type: ['VerifiableCredential', 'PermanentResidentCard'], issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - identifier: '83627465', - name: 'Permanent Resident Card', - description: 'Government of Example Permanent Resident Card.', issuanceDate: '2019-12-03T12:19:52Z', expirationDate: '2029-12-03T12:19:52Z', + identifier: '83627465', + name: 'Permanent Resident Card', credentialSubject: { id: 'did:example:b34ca6cd37bbf23', - // type: [Array], + type: expect.any(Array), givenName: 'JOHN', familyName: 'SMITH', gender: 'Male', image: 'data:image/png;base64,iVBORw0KGgokJggg==', + description: 'Government of Example Permanent Resident Card.', residentSince: '2015-01-01', lprCategory: 'C09', lprNumber: '999-999-999', @@ -475,11 +469,10 @@ describe('credentials', () => { id: 'https://issuer.oidp.uscis.gov/credentials/83627465', type: ['VerifiableCredential', 'PermanentResidentCard'], issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - identifier: '83627465', - name: 'Permanent Resident Card', - description: 'Government of Example Permanent Resident Card.', issuanceDate: '2019-12-03T12:19:52Z', expirationDate: '2029-12-03T12:19:52Z', + identifier: '83627465', + name: 'Permanent Resident Card', credentialSubject: { id: 'did:example:b34ca6cd37bbf23', type: ['PermanentResident', 'Person'], @@ -488,6 +481,7 @@ describe('credentials', () => { gender: 'Male', image: 'data:image/png;base64,iVBORw0KGgokJggg==', residentSince: '2015-01-01', + description: 'Government of Example Permanent Resident Card.', lprCategory: 'C09', lprNumber: '999-999-999', commuterClassification: 'C1', diff --git a/packages/core/tests/v2-connectionless-proofs.test.ts b/packages/core/tests/v2-connectionless-proofs.test.ts index 29263d692e..921f6c3127 100644 --- a/packages/core/tests/v2-connectionless-proofs.test.ts +++ b/packages/core/tests/v2-connectionless-proofs.test.ts @@ -285,6 +285,7 @@ describe('Present Proof', () => { expect(faberConnection.isReady).toBe(true) expect(aliceConnection.isReady).toBe(true) + // issue credential with two linked attachments await issueCredential({ issuerAgent: faberAgent, issuerConnectionId: faberConnection.id, From ff6293cf13e0ca3b1ec1d234e65dcb68b742eba1 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Mon, 19 Dec 2022 14:18:57 +0800 Subject: [PATCH 099/125] feat(credentials)!: custom registration of credential protocols (#1158) Signed-off-by: Timo Glastra --- packages/core/src/agent/Agent.ts | 8 +- packages/core/src/agent/AgentModules.ts | 28 +- packages/core/src/agent/BaseAgent.ts | 10 +- packages/core/src/agent/Dispatcher.ts | 59 +-- .../core/src/agent/MessageHandlerRegistry.ts | 59 +++ packages/core/src/agent/MessageReceiver.ts | 6 +- .../src/agent/__tests__/Dispatcher.test.ts | 135 +------ .../__tests__/MessageHandlerRegistry.test.ts | 139 +++++++ ...ptions.ts => CredentialProtocolOptions.ts} | 81 +++- .../src/modules/credentials/CredentialsApi.ts | 137 +++---- .../credentials/CredentialsApiOptions.ts | 84 ++-- .../modules/credentials/CredentialsModule.ts | 73 +++- .../credentials/CredentialsModuleConfig.ts | 30 +- .../__tests__/CredentialsModule.test.ts | 56 ++- .../__tests__/CredentialsModuleConfig.test.ts | 10 +- .../formats/CredentialFormatService.ts | 73 +--- .../formats/CredentialFormatServiceOptions.ts | 27 +- .../IndyCredentialFormatService.test.ts | 24 +- .../JsonLdCredentialFormatService.test.ts | 128 +++--- .../indy/IndyCredentialFormatService.ts | 103 ++--- .../jsonld/JsonLdCredentialFormatService.ts | 57 ++- .../BaseCredentialProtocol.ts} | 111 ++--- .../protocol/CredentialProtocol.ts | 130 ++++++ ...tialService.ts => V1CredentialProtocol.ts} | 382 +++++++++++------- ...st.ts => V1CredentialProtocolCred.test.ts} | 97 ++--- ... V1CredentialProtocolProposeOffer.test.ts} | 71 ++-- .../v1-connectionless-credentials.e2e.test.ts | 20 +- .../v1/handlers/V1CredentialAckHandler.ts | 10 +- .../V1CredentialProblemReportHandler.ts | 10 +- .../v1/handlers/V1IssueCredentialHandler.ts | 33 +- .../v1/handlers/V1OfferCredentialHandler.ts | 44 +- .../v1/handlers/V1ProposeCredentialHandler.ts | 21 +- .../v1/handlers/V1RequestCredentialHandler.ts | 38 +- .../modules/credentials/protocol/v1/index.ts | 2 +- .../v2/CredentialFormatCoordinator.ts | 72 ++-- ...tialService.ts => V2CredentialProtocol.ts} | 352 ++++++++-------- ...st.ts => V2CredentialProtocolCred.test.ts} | 105 ++--- ...t.ts => V2CredentialProtocolOffer.test.ts} | 58 ++- .../v2-connectionless-credentials.e2e.test.ts | 20 +- ...ldproof.connectionless-credentials.test.ts | 21 +- ...v2.ldproof.credentials-auto-accept.test.ts | 16 +- ...f.credentials.propose-offerED25519.test.ts | 6 +- .../v2/handlers/V2CredentialAckHandler.ts | 10 +- .../V2CredentialProblemReportHandler.ts | 10 +- .../v2/handlers/V2IssueCredentialHandler.ts | 34 +- .../v2/handlers/V2OfferCredentialHandler.ts | 43 +- .../v2/handlers/V2ProposeCredentialHandler.ts | 21 +- .../v2/handlers/V2RequestCredentialHandler.ts | 35 +- .../modules/credentials/protocol/v2/index.ts | 2 +- .../src/modules/credentials/services/index.ts | 1 - packages/core/src/modules/oob/OutOfBandApi.ts | 25 +- .../core/src/plugins/DependencyManager.ts | 10 + packages/core/src/plugins/Module.ts | 8 +- .../src/storage/migration/UpdateAssistant.ts | 3 +- .../core/src/transport/InboundTransport.ts | 3 +- .../core/src/transport/OutboundTransport.ts | 3 +- packages/core/src/types.ts | 111 ++++- packages/core/tests/helpers.ts | 33 +- packages/core/tests/oob.test.ts | 6 +- 59 files changed, 1921 insertions(+), 1383 deletions(-) create mode 100644 packages/core/src/agent/MessageHandlerRegistry.ts create mode 100644 packages/core/src/agent/__tests__/MessageHandlerRegistry.test.ts rename packages/core/src/modules/credentials/{CredentialServiceOptions.ts => CredentialProtocolOptions.ts} (59%) rename packages/core/src/modules/credentials/{services/CredentialService.ts => protocol/BaseCredentialProtocol.ts} (72%) create mode 100644 packages/core/src/modules/credentials/protocol/CredentialProtocol.ts rename packages/core/src/modules/credentials/protocol/v1/{V1CredentialService.ts => V1CredentialProtocol.ts} (72%) rename packages/core/src/modules/credentials/protocol/v1/__tests__/{V1CredentialServiceCred.test.ts => V1CredentialProtocolCred.test.ts} (90%) rename packages/core/src/modules/credentials/protocol/v1/__tests__/{V1CredentialServiceProposeOffer.test.ts => V1CredentialProtocolProposeOffer.test.ts} (88%) rename packages/core/src/modules/credentials/protocol/v2/{V2CredentialService.ts => V2CredentialProtocol.ts} (77%) rename packages/core/src/modules/credentials/protocol/v2/__tests__/{V2CredentialServiceCred.test.ts => V2CredentialProtocolCred.test.ts} (90%) rename packages/core/src/modules/credentials/protocol/v2/__tests__/{V2CredentialServiceOffer.test.ts => V2CredentialProtocolOffer.test.ts} (87%) delete mode 100644 packages/core/src/modules/credentials/services/index.ts diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 133029bc81..c005d53e6d 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -2,7 +2,7 @@ import type { InboundTransport } from '../transport/InboundTransport' import type { OutboundTransport } from '../transport/OutboundTransport' import type { InitConfig } from '../types' import type { AgentDependencies } from './AgentDependencies' -import type { AgentModulesInput, ModulesMap } from './AgentModules' +import type { AgentModulesInput } from './AgentModules' import type { AgentMessageReceivedEvent } from './Events' import type { Subscription } from 'rxjs' @@ -28,6 +28,7 @@ import { EnvelopeService } from './EnvelopeService' import { EventEmitter } from './EventEmitter' import { AgentEventTypes } from './Events' import { FeatureRegistry } from './FeatureRegistry' +import { MessageHandlerRegistry } from './MessageHandlerRegistry' import { MessageReceiver } from './MessageReceiver' import { MessageSender } from './MessageSender' import { TransportService } from './TransportService' @@ -39,7 +40,9 @@ interface AgentOptions { dependencies: AgentDependencies } -export class Agent extends BaseAgent { +// Any makes sure you can use Agent as a type without always needing to specify the exact generics for the agent +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export class Agent extends BaseAgent { public messageSubscription: Subscription public constructor(options: AgentOptions, dependencyManager = new DependencyManager()) { @@ -47,6 +50,7 @@ export class Agent extends const modulesWithDefaultModules = extendModulesWithDefaultModules(agentConfig, options.modules) // Register internal dependencies + dependencyManager.registerSingleton(MessageHandlerRegistry) dependencyManager.registerSingleton(EventEmitter) dependencyManager.registerSingleton(MessageSender) dependencyManager.registerSingleton(MessageReceiver) diff --git a/packages/core/src/agent/AgentModules.ts b/packages/core/src/agent/AgentModules.ts index 6bf2de802e..9b0ec0448c 100644 --- a/packages/core/src/agent/AgentModules.ts +++ b/packages/core/src/agent/AgentModules.ts @@ -1,4 +1,4 @@ -import type { Module, DependencyManager } from '../plugins' +import type { Module, DependencyManager, ApiModule } from '../plugins' import type { Constructor } from '../utils/mixins' import type { AgentConfig } from './AgentConfig' @@ -28,7 +28,16 @@ export type EmptyModuleMap = {} * Default modules can be optionally defined to provide custom configuration. This type makes it so that it is not * possible to use a different key for the default modules */ -export type AgentModulesInput = Partial & ModulesMap +export type AgentModulesInput = Partial & ModulesMap + +/** + * Defines the input type for the default agent modules. This is overwritten as we + * want the input type to allow for generics to be passed in for the credentials module. + */ +export type DefaultAgentModulesInput = Omit & { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + credentials: CredentialsModule +} /** * Type that represents the default agent modules. This is the {@link ModulesMap} variant for the default modules in the framework. @@ -83,6 +92,21 @@ export type AgentApi = { : never]: Modules[moduleKey]['api'] extends Constructor ? InstanceType : never } +/** + * Returns the `api` type from the CustomModuleType if the module is an ApiModule. If the module is not defined + * which is the case if you don't configure a default agent module (e.g. credentials module), it will use the default + * module type and use that for the typing. This will contain the default typing, and thus provide the correct agent api + * interface + */ +export type CustomOrDefaultApi< + CustomModuleType, + DefaultModuleType extends ApiModule +> = CustomModuleType extends ApiModule + ? InstanceType + : CustomModuleType extends Module + ? never + : InstanceType + /** * Method to get the default agent modules to be registered on any agent instance. * diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index 00477a6b5c..ef6b917463 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -1,7 +1,8 @@ import type { Logger } from '../logger' +import type { CredentialsModule } from '../modules/credentials' import type { DependencyManager } from '../plugins' import type { AgentConfig } from './AgentConfig' -import type { AgentApi, EmptyModuleMap, ModulesMap, WithoutDefaultModules } from './AgentModules' +import type { AgentApi, EmptyModuleMap, ModulesMap, WithoutDefaultModules, CustomOrDefaultApi } from './AgentModules' import type { TransportSession } from './TransportService' import { AriesFrameworkError } from '../error' @@ -42,7 +43,7 @@ export abstract class BaseAgent public readonly proofs: ProofsApi public readonly mediator: MediatorApi public readonly mediationRecipient: RecipientApi @@ -83,7 +84,10 @@ export abstract class BaseAgent this.proofs = this.dependencyManager.resolve(ProofsApi) this.mediator = this.dependencyManager.resolve(MediatorApi) this.mediationRecipient = this.dependencyManager.resolve(RecipientApi) diff --git a/packages/core/src/agent/Dispatcher.ts b/packages/core/src/agent/Dispatcher.ts index c9c4f2c0bc..ee04bc0e3c 100644 --- a/packages/core/src/agent/Dispatcher.ts +++ b/packages/core/src/agent/Dispatcher.ts @@ -7,17 +7,17 @@ import { InjectionSymbols } from '../constants' import { AriesFrameworkError } from '../error/AriesFrameworkError' import { Logger } from '../logger' import { injectable, inject } from '../plugins' -import { canHandleMessageType, parseMessageType } from '../utils/messageType' import { ProblemReportMessage } from './../modules/problem-reports/messages/ProblemReportMessage' import { EventEmitter } from './EventEmitter' import { AgentEventTypes } from './Events' +import { MessageHandlerRegistry } from './MessageHandlerRegistry' import { MessageSender } from './MessageSender' import { OutboundMessageContext } from './models' @injectable() class Dispatcher { - private messageHandlers: MessageHandler[] = [] + private messageHandlerRegistry: MessageHandlerRegistry private messageSender: MessageSender private eventEmitter: EventEmitter private logger: Logger @@ -25,20 +25,25 @@ class Dispatcher { public constructor( messageSender: MessageSender, eventEmitter: EventEmitter, + messageHandlerRegistry: MessageHandlerRegistry, @inject(InjectionSymbols.Logger) logger: Logger ) { this.messageSender = messageSender this.eventEmitter = eventEmitter + this.messageHandlerRegistry = messageHandlerRegistry this.logger = logger } - public registerMessageHandler(handler: MessageHandler) { - this.messageHandlers.push(handler) + /** + * @deprecated Use {@link MessageHandlerRegistry.registerMessageHandler} directly + */ + public registerMessageHandler(messageHandler: MessageHandler) { + this.messageHandlerRegistry.registerMessageHandler(messageHandler) } public async dispatch(messageContext: InboundMessageContext): Promise { const { agentContext, connection, senderKey, recipientKey, message } = messageContext - const messageHandler = this.getMessageHandlerForType(message.type) + const messageHandler = this.messageHandlerRegistry.getHandlerForMessageType(message.type) if (!messageHandler) { throw new AriesFrameworkError(`No handler for message type "${message.type}" found`) @@ -89,50 +94,6 @@ class Dispatcher { }, }) } - - private getMessageHandlerForType(messageType: string): MessageHandler | undefined { - const incomingMessageType = parseMessageType(messageType) - - for (const messageHandler of this.messageHandlers) { - for (const MessageClass of messageHandler.supportedMessages) { - if (canHandleMessageType(MessageClass, incomingMessageType)) return messageHandler - } - } - } - - public getMessageClassForType(messageType: string): typeof AgentMessage | undefined { - const incomingMessageType = parseMessageType(messageType) - - for (const messageHandler of this.messageHandlers) { - for (const MessageClass of messageHandler.supportedMessages) { - if (canHandleMessageType(MessageClass, incomingMessageType)) return MessageClass - } - } - } - - /** - * Returns array of message types that dispatcher is able to handle. - * Message type format is MTURI specified at https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0003-protocols/README.md#mturi. - */ - public get supportedMessageTypes() { - return this.messageHandlers - .reduce((all, cur) => [...all, ...cur.supportedMessages], []) - .map((m) => m.type) - } - - /** - * Returns array of protocol IDs that dispatcher is able to handle. - * Protocol ID format is PIURI specified at https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0003-protocols/README.md#piuri. - */ - public get supportedProtocols() { - return Array.from(new Set(this.supportedMessageTypes.map((m) => m.protocolUri))) - } - - public filterSupportedProtocolsByMessageFamilies(messageFamilies: string[]) { - return this.supportedProtocols.filter((protocolId) => - messageFamilies.find((messageFamily) => protocolId.startsWith(messageFamily)) - ) - } } export { Dispatcher } diff --git a/packages/core/src/agent/MessageHandlerRegistry.ts b/packages/core/src/agent/MessageHandlerRegistry.ts new file mode 100644 index 0000000000..3942c43c55 --- /dev/null +++ b/packages/core/src/agent/MessageHandlerRegistry.ts @@ -0,0 +1,59 @@ +import type { AgentMessage } from './AgentMessage' +import type { MessageHandler } from './MessageHandler' + +import { injectable } from 'tsyringe' + +import { canHandleMessageType, parseMessageType } from '../utils/messageType' + +@injectable() +export class MessageHandlerRegistry { + private messageHandlers: MessageHandler[] = [] + + public registerMessageHandler(messageHandler: MessageHandler) { + this.messageHandlers.push(messageHandler) + } + + public getHandlerForMessageType(messageType: string): MessageHandler | undefined { + const incomingMessageType = parseMessageType(messageType) + + for (const handler of this.messageHandlers) { + for (const MessageClass of handler.supportedMessages) { + if (canHandleMessageType(MessageClass, incomingMessageType)) return handler + } + } + } + + public getMessageClassForMessageType(messageType: string): typeof AgentMessage | undefined { + const incomingMessageType = parseMessageType(messageType) + + for (const handler of this.messageHandlers) { + for (const MessageClass of handler.supportedMessages) { + if (canHandleMessageType(MessageClass, incomingMessageType)) return MessageClass + } + } + } + + /** + * Returns array of message types that dispatcher is able to handle. + * Message type format is MTURI specified at https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0003-protocols/README.md#mturi. + */ + public get supportedMessageTypes() { + return this.messageHandlers + .reduce((all, cur) => [...all, ...cur.supportedMessages], []) + .map((m) => m.type) + } + + /** + * Returns array of protocol IDs that dispatcher is able to handle. + * Protocol ID format is PIURI specified at https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0003-protocols/README.md#piuri. + */ + public get supportedProtocols() { + return Array.from(new Set(this.supportedMessageTypes.map((m) => m.protocolUri))) + } + + public filterSupportedProtocolsByMessageFamilies(messageFamilies: string[]) { + return this.supportedProtocols.filter((protocolId) => + messageFamilies.find((messageFamily) => protocolId.startsWith(messageFamily)) + ) + } +} diff --git a/packages/core/src/agent/MessageReceiver.ts b/packages/core/src/agent/MessageReceiver.ts index 7cf8d79308..a87b42b7e2 100644 --- a/packages/core/src/agent/MessageReceiver.ts +++ b/packages/core/src/agent/MessageReceiver.ts @@ -18,6 +18,7 @@ import { canHandleMessageType, parseMessageType, replaceLegacyDidSovPrefixOnMess import { Dispatcher } from './Dispatcher' import { EnvelopeService } from './EnvelopeService' +import { MessageHandlerRegistry } from './MessageHandlerRegistry' import { MessageSender } from './MessageSender' import { TransportService } from './TransportService' import { AgentContextProvider } from './context' @@ -31,6 +32,7 @@ export class MessageReceiver { private dispatcher: Dispatcher private logger: Logger private connectionService: ConnectionService + private messageHandlerRegistry: MessageHandlerRegistry private agentContextProvider: AgentContextProvider public readonly inboundTransports: InboundTransport[] = [] @@ -40,6 +42,7 @@ export class MessageReceiver { messageSender: MessageSender, connectionService: ConnectionService, dispatcher: Dispatcher, + messageHandlerRegistry: MessageHandlerRegistry, @inject(InjectionSymbols.AgentContextProvider) agentContextProvider: AgentContextProvider, @inject(InjectionSymbols.Logger) logger: Logger ) { @@ -48,6 +51,7 @@ export class MessageReceiver { this.messageSender = messageSender this.connectionService = connectionService this.dispatcher = dispatcher + this.messageHandlerRegistry = messageHandlerRegistry this.agentContextProvider = agentContextProvider this.logger = logger } @@ -227,7 +231,7 @@ export class MessageReceiver { replaceLegacyDidSovPrefixOnMessage(message) const messageType = message['@type'] - const MessageClass = this.dispatcher.getMessageClassForType(messageType) + const MessageClass = this.messageHandlerRegistry.getMessageClassForMessageType(messageType) if (!MessageClass) { throw new ProblemReportError(`No message class found for message type "${messageType}"`, { diff --git a/packages/core/src/agent/__tests__/Dispatcher.test.ts b/packages/core/src/agent/__tests__/Dispatcher.test.ts index 30f4cfefef..3c91825d2d 100644 --- a/packages/core/src/agent/__tests__/Dispatcher.test.ts +++ b/packages/core/src/agent/__tests__/Dispatcher.test.ts @@ -1,5 +1,3 @@ -import type { MessageHandler } from '../MessageHandler' - import { Subject } from 'rxjs' import { getAgentConfig, getAgentContext } from '../../../tests/helpers' @@ -7,141 +5,29 @@ import { parseMessageType } from '../../utils/messageType' import { AgentMessage } from '../AgentMessage' import { Dispatcher } from '../Dispatcher' import { EventEmitter } from '../EventEmitter' +import { MessageHandlerRegistry } from '../MessageHandlerRegistry' import { MessageSender } from '../MessageSender' import { InboundMessageContext } from '../models/InboundMessageContext' -class ConnectionInvitationTestMessage extends AgentMessage { - public static readonly type = parseMessageType('https://didcomm.org/connections/1.0/invitation') -} -class ConnectionRequestTestMessage extends AgentMessage { - public static readonly type = parseMessageType('https://didcomm.org/connections/1.0/request') -} - -class ConnectionResponseTestMessage extends AgentMessage { - public static readonly type = parseMessageType('https://didcomm.org/connections/1.0/response') -} - -class NotificationAckTestMessage extends AgentMessage { - public static readonly type = parseMessageType('https://didcomm.org/notification/1.0/ack') -} -class CredentialProposalTestMessage extends AgentMessage { - public readonly type = CredentialProposalTestMessage.type.messageTypeUri - public static readonly type = parseMessageType('https://didcomm.org/issue-credential/1.0/credential-proposal') -} - class CustomProtocolMessage extends AgentMessage { public readonly type = CustomProtocolMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/fake-protocol/1.5/message') } -class TestHandler implements MessageHandler { - // We want to pass various classes to test various behaviours so we dont need to strictly type it. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public constructor(classes: any[]) { - this.supportedMessages = classes - } - - public supportedMessages - - // We don't need an implementation in test handler so we can disable lint. - // eslint-disable-next-line @typescript-eslint/no-empty-function - public async handle() {} -} - describe('Dispatcher', () => { const agentConfig = getAgentConfig('DispatcherTest') const agentContext = getAgentContext() const MessageSenderMock = MessageSender as jest.Mock const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) - const fakeProtocolHandler = new TestHandler([CustomProtocolMessage]) - const connectionHandler = new TestHandler([ - ConnectionInvitationTestMessage, - ConnectionRequestTestMessage, - ConnectionResponseTestMessage, - ]) - - const dispatcher = new Dispatcher(new MessageSenderMock(), eventEmitter, agentConfig.logger) - - dispatcher.registerMessageHandler(connectionHandler) - dispatcher.registerMessageHandler(new TestHandler([NotificationAckTestMessage])) - dispatcher.registerMessageHandler(new TestHandler([CredentialProposalTestMessage])) - dispatcher.registerMessageHandler(fakeProtocolHandler) - - describe('supportedMessageTypes', () => { - test('return all supported message types URIs', async () => { - const messageTypes = dispatcher.supportedMessageTypes - - expect(messageTypes).toMatchObject([ - { messageTypeUri: 'https://didcomm.org/connections/1.0/invitation' }, - { messageTypeUri: 'https://didcomm.org/connections/1.0/request' }, - { messageTypeUri: 'https://didcomm.org/connections/1.0/response' }, - { messageTypeUri: 'https://didcomm.org/notification/1.0/ack' }, - { messageTypeUri: 'https://didcomm.org/issue-credential/1.0/credential-proposal' }, - { messageTypeUri: 'https://didcomm.org/fake-protocol/1.5/message' }, - ]) - }) - }) - - describe('supportedProtocols', () => { - test('return all supported message protocols URIs', async () => { - const messageTypes = dispatcher.supportedProtocols - - expect(messageTypes).toEqual([ - 'https://didcomm.org/connections/1.0', - 'https://didcomm.org/notification/1.0', - 'https://didcomm.org/issue-credential/1.0', - 'https://didcomm.org/fake-protocol/1.5', - ]) - }) - }) - - describe('filterSupportedProtocolsByMessageFamilies', () => { - it('should return empty array when input is empty array', async () => { - const supportedProtocols = dispatcher.filterSupportedProtocolsByMessageFamilies([]) - expect(supportedProtocols).toEqual([]) - }) - - it('should return empty array when input contains only unsupported protocol', async () => { - const supportedProtocols = dispatcher.filterSupportedProtocolsByMessageFamilies([ - 'https://didcomm.org/unsupported-protocol/1.0', - ]) - expect(supportedProtocols).toEqual([]) - }) - - it('should return array with only supported protocol when input contains supported and unsupported protocol', async () => { - const supportedProtocols = dispatcher.filterSupportedProtocolsByMessageFamilies([ - 'https://didcomm.org/connections', - 'https://didcomm.org/didexchange', - ]) - expect(supportedProtocols).toEqual(['https://didcomm.org/connections/1.0']) - }) - }) - - describe('getMessageClassForType()', () => { - it('should return the correct message class for a registered message type', () => { - const messageClass = dispatcher.getMessageClassForType('https://didcomm.org/connections/1.0/invitation') - expect(messageClass).toBe(ConnectionInvitationTestMessage) - }) - - it('should return undefined if no message class is registered for the message type', () => { - const messageClass = dispatcher.getMessageClassForType('https://didcomm.org/non-existing/1.0/invitation') - expect(messageClass).toBeUndefined() - }) - - it('should return the message class with a higher minor version for the message type', () => { - const messageClass = dispatcher.getMessageClassForType('https://didcomm.org/fake-protocol/1.0/message') - expect(messageClass).toBe(CustomProtocolMessage) - }) - - it('should not return the message class with a different major version', () => { - const messageClass = dispatcher.getMessageClassForType('https://didcomm.org/fake-protocol/2.0/message') - expect(messageClass).toBeUndefined() - }) - }) describe('dispatch()', () => { it('calls the handle method of the handler', async () => { - const dispatcher = new Dispatcher(new MessageSenderMock(), eventEmitter, agentConfig.logger) + const dispatcher = new Dispatcher( + new MessageSenderMock(), + eventEmitter, + new MessageHandlerRegistry(), + agentConfig.logger + ) const customProtocolMessage = new CustomProtocolMessage() const inboundMessageContext = new InboundMessageContext(customProtocolMessage, { agentContext }) @@ -154,7 +40,12 @@ describe('Dispatcher', () => { }) it('throws an error if no handler for the message could be found', async () => { - const dispatcher = new Dispatcher(new MessageSenderMock(), eventEmitter, agentConfig.logger) + const dispatcher = new Dispatcher( + new MessageSenderMock(), + eventEmitter, + new MessageHandlerRegistry(), + agentConfig.logger + ) const customProtocolMessage = new CustomProtocolMessage() const inboundMessageContext = new InboundMessageContext(customProtocolMessage, { agentContext }) diff --git a/packages/core/src/agent/__tests__/MessageHandlerRegistry.test.ts b/packages/core/src/agent/__tests__/MessageHandlerRegistry.test.ts new file mode 100644 index 0000000000..64b3946b62 --- /dev/null +++ b/packages/core/src/agent/__tests__/MessageHandlerRegistry.test.ts @@ -0,0 +1,139 @@ +import type { MessageHandler } from '../MessageHandler' + +import { parseMessageType } from '../../utils/messageType' +import { AgentMessage } from '../AgentMessage' +import { MessageHandlerRegistry } from '../MessageHandlerRegistry' + +class ConnectionInvitationTestMessage extends AgentMessage { + public static readonly type = parseMessageType('https://didcomm.org/connections/1.0/invitation') +} +class ConnectionRequestTestMessage extends AgentMessage { + public static readonly type = parseMessageType('https://didcomm.org/connections/1.0/request') +} + +class ConnectionResponseTestMessage extends AgentMessage { + public static readonly type = parseMessageType('https://didcomm.org/connections/1.0/response') +} + +class NotificationAckTestMessage extends AgentMessage { + public static readonly type = parseMessageType('https://didcomm.org/notification/1.0/ack') +} +class CredentialProposalTestMessage extends AgentMessage { + public readonly type = CredentialProposalTestMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/issue-credential/1.0/credential-proposal') +} + +class CustomProtocolMessage extends AgentMessage { + public readonly type = CustomProtocolMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/fake-protocol/1.5/message') +} + +class TestHandler implements MessageHandler { + // We want to pass various classes to test various behaviours so we dont need to strictly type it. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public constructor(classes: any[]) { + this.supportedMessages = classes + } + + public supportedMessages + + // We don't need an implementation in test handler so we can disable lint. + // eslint-disable-next-line @typescript-eslint/no-empty-function + public async handle() {} +} + +describe('MessageHandlerRegistry', () => { + const fakeProtocolHandler = new TestHandler([CustomProtocolMessage]) + const connectionHandler = new TestHandler([ + ConnectionInvitationTestMessage, + ConnectionRequestTestMessage, + ConnectionResponseTestMessage, + ]) + + const messageHandlerRegistry = new MessageHandlerRegistry() + + messageHandlerRegistry.registerMessageHandler(connectionHandler) + messageHandlerRegistry.registerMessageHandler(new TestHandler([NotificationAckTestMessage])) + messageHandlerRegistry.registerMessageHandler(new TestHandler([CredentialProposalTestMessage])) + messageHandlerRegistry.registerMessageHandler(fakeProtocolHandler) + + describe('supportedMessageTypes', () => { + test('return all supported message types URIs', async () => { + const messageTypes = messageHandlerRegistry.supportedMessageTypes + + expect(messageTypes).toMatchObject([ + { messageTypeUri: 'https://didcomm.org/connections/1.0/invitation' }, + { messageTypeUri: 'https://didcomm.org/connections/1.0/request' }, + { messageTypeUri: 'https://didcomm.org/connections/1.0/response' }, + { messageTypeUri: 'https://didcomm.org/notification/1.0/ack' }, + { messageTypeUri: 'https://didcomm.org/issue-credential/1.0/credential-proposal' }, + { messageTypeUri: 'https://didcomm.org/fake-protocol/1.5/message' }, + ]) + }) + }) + + describe('supportedProtocols', () => { + test('return all supported message protocols URIs', async () => { + const messageTypes = messageHandlerRegistry.supportedProtocols + + expect(messageTypes).toEqual([ + 'https://didcomm.org/connections/1.0', + 'https://didcomm.org/notification/1.0', + 'https://didcomm.org/issue-credential/1.0', + 'https://didcomm.org/fake-protocol/1.5', + ]) + }) + }) + + describe('filterSupportedProtocolsByMessageFamilies', () => { + it('should return empty array when input is empty array', async () => { + const supportedProtocols = messageHandlerRegistry.filterSupportedProtocolsByMessageFamilies([]) + expect(supportedProtocols).toEqual([]) + }) + + it('should return empty array when input contains only unsupported protocol', async () => { + const supportedProtocols = messageHandlerRegistry.filterSupportedProtocolsByMessageFamilies([ + 'https://didcomm.org/unsupported-protocol/1.0', + ]) + expect(supportedProtocols).toEqual([]) + }) + + it('should return array with only supported protocol when input contains supported and unsupported protocol', async () => { + const supportedProtocols = messageHandlerRegistry.filterSupportedProtocolsByMessageFamilies([ + 'https://didcomm.org/connections', + 'https://didcomm.org/didexchange', + ]) + expect(supportedProtocols).toEqual(['https://didcomm.org/connections/1.0']) + }) + }) + + describe('getMessageClassForMessageType()', () => { + it('should return the correct message class for a registered message type', () => { + const messageClass = messageHandlerRegistry.getMessageClassForMessageType( + 'https://didcomm.org/connections/1.0/invitation' + ) + expect(messageClass).toBe(ConnectionInvitationTestMessage) + }) + + it('should return undefined if no message class is registered for the message type', () => { + const messageClass = messageHandlerRegistry.getMessageClassForMessageType( + 'https://didcomm.org/non-existing/1.0/invitation' + ) + expect(messageClass).toBeUndefined() + }) + + it('should return the message class with a higher minor version for the message type', () => { + const messageClass = messageHandlerRegistry.getMessageClassForMessageType( + 'https://didcomm.org/fake-protocol/1.0/message' + ) + expect(messageClass).toBe(CustomProtocolMessage) + }) + + it('should not return the message class with a different major version', () => { + const messageClass = messageHandlerRegistry.getMessageClassForMessageType( + 'https://didcomm.org/fake-protocol/2.0/message' + ) + expect(messageClass).toBeUndefined() + }) + }) +}) diff --git a/packages/core/src/modules/credentials/CredentialServiceOptions.ts b/packages/core/src/modules/credentials/CredentialProtocolOptions.ts similarity index 59% rename from packages/core/src/modules/credentials/CredentialServiceOptions.ts rename to packages/core/src/modules/credentials/CredentialProtocolOptions.ts index 49d8f623d8..a26a0e6861 100644 --- a/packages/core/src/modules/credentials/CredentialServiceOptions.ts +++ b/packages/core/src/modules/credentials/CredentialProtocolOptions.ts @@ -1,8 +1,15 @@ import type { AgentMessage } from '../../agent/AgentMessage' +import type { FlatArray } from '../../types' import type { ConnectionRecord } from '../connections/repository/ConnectionRecord' -import type { CredentialFormat, CredentialFormatPayload } from './formats' +import type { + CredentialFormat, + CredentialFormatPayload, + CredentialFormatService, + ExtractCredentialFormats, +} from './formats' import type { CredentialPreviewAttributeOptions } from './models' import type { AutoAcceptCredential } from './models/CredentialAutoAcceptType' +import type { CredentialProtocol } from './protocol/CredentialProtocol' import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' /** @@ -31,9 +38,47 @@ export type FormatDataMessagePayload< CFs extends CredentialFormat[] = CredentialFormat[], M extends keyof CredentialFormat['formatData'] = keyof CredentialFormat['formatData'] > = { - [CredentialFormat in CFs[number] as CredentialFormat['formatKey']]?: CredentialFormat['formatData'][M] + [Service in CFs[number] as Service['formatKey']]?: Service['formatData'][M] } +/** + * Infer an array of {@link CredentialFormatService} types based on a {@link CredentialProtocol}. + * + * It does this by extracting the `CredentialFormatServices` generic from the `CredentialProtocol`. + * + * @example + * ``` + * // TheCredentialFormatServices is now equal to [IndyCredentialFormatService] + * type TheCredentialFormatServices = ExtractCredentialFormatServices + * ``` + * + * Because the `V1CredentialProtocol` is defined as follows: + * ``` + * class V1CredentialProtocol implements CredentialProtocol<[IndyCredentialFormatService]> { + * } + * ``` + */ +export type ExtractCredentialFormatServices = Type extends CredentialProtocol + ? CredentialFormatServices + : never + +/** + * Infer an array of {@link CredentialFormat} types based on an array of {@link CredentialProtocol} types. + * + * This is based on {@link ExtractCredentialFormatServices}, but allows to handle arrays. + */ +export type CFsFromCPs = _CFsFromCPs extends CredentialFormat[] + ? _CFsFromCPs + : [] + +/** + * Utility type for {@link ExtractCredentialFormatServicesFromCredentialProtocols} to reduce duplication. + * Should not be used directly. + */ +type _CFsFromCPs = FlatArray<{ + [CP in keyof CPs]: ExtractCredentialFormats> +}>[] + /** * Get format data return value. Each key holds a mapping of credential format key to format data. * @@ -57,59 +102,59 @@ export type GetFormatDataReturn } -export interface CreateProposalOptions { +export interface CreateProposalOptions { connection: ConnectionRecord - credentialFormats: CredentialFormatPayload + credentialFormats: CredentialFormatPayload, 'createProposal'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface AcceptProposalOptions { +export interface AcceptProposalOptions { credentialRecord: CredentialExchangeRecord - credentialFormats?: CredentialFormatPayload + credentialFormats?: CredentialFormatPayload, 'acceptProposal'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface NegotiateProposalOptions { +export interface NegotiateProposalOptions { credentialRecord: CredentialExchangeRecord - credentialFormats: CredentialFormatPayload + credentialFormats: CredentialFormatPayload, 'createOffer'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface CreateOfferOptions { +export interface CreateOfferOptions { // Create offer can also be used for connection-less, so connection is optional connection?: ConnectionRecord - credentialFormats: CredentialFormatPayload + credentialFormats: CredentialFormatPayload, 'createOffer'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface AcceptOfferOptions { +export interface AcceptOfferOptions { credentialRecord: CredentialExchangeRecord - credentialFormats?: CredentialFormatPayload + credentialFormats?: CredentialFormatPayload, 'acceptOffer'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface NegotiateOfferOptions { +export interface NegotiateOfferOptions { credentialRecord: CredentialExchangeRecord - credentialFormats: CredentialFormatPayload + credentialFormats: CredentialFormatPayload, 'createProposal'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface CreateRequestOptions { +export interface CreateRequestOptions { connection: ConnectionRecord - credentialFormats: CredentialFormatPayload + credentialFormats: CredentialFormatPayload, 'createRequest'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface AcceptRequestOptions { +export interface AcceptRequestOptions { credentialRecord: CredentialExchangeRecord - credentialFormats?: CredentialFormatPayload + credentialFormats?: CredentialFormatPayload, 'acceptRequest'> autoAcceptCredential?: AutoAcceptCredential comment?: string } diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index 3eefc4857a..10520e4c2e 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -1,6 +1,6 @@ import type { AgentMessage } from '../../agent/AgentMessage' import type { Query } from '../../storage/StorageService' -import type { DeleteCredentialOptions } from './CredentialServiceOptions' +import type { CFsFromCPs, DeleteCredentialOptions } from './CredentialProtocolOptions' import type { AcceptCredentialOptions, AcceptCredentialOfferOptions, @@ -17,13 +17,10 @@ import type { OfferCredentialOptions, ProposeCredentialOptions, SendCredentialProblemReportOptions, - CredentialServiceMap, + CredentialProtocolMap, } from './CredentialsApiOptions' -import type { CredentialFormat } from './formats' -import type { IndyCredentialFormat } from './formats/indy/IndyCredentialFormat' -import type { JsonLdCredentialFormat } from './formats/jsonld/JsonLdCredentialFormat' +import type { CredentialProtocol } from './protocol/CredentialProtocol' import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' -import type { CredentialService } from './services/CredentialService' import { AgentContext } from '../../agent' import { MessageSender } from '../../agent/MessageSender' @@ -41,34 +38,32 @@ import { RoutingService } from '../routing/services/RoutingService' import { CredentialsModuleConfig } from './CredentialsModuleConfig' import { CredentialState } from './models/CredentialState' import { RevocationNotificationService } from './protocol/revocation-notification/services' -import { V1CredentialService } from './protocol/v1/V1CredentialService' -import { V2CredentialService } from './protocol/v2/V2CredentialService' import { CredentialRepository } from './repository/CredentialRepository' -export interface CredentialsApi[]> { +export interface CredentialsApi { // Propose Credential methods - proposeCredential(options: ProposeCredentialOptions): Promise - acceptProposal(options: AcceptCredentialProposalOptions): Promise - negotiateProposal(options: NegotiateCredentialProposalOptions): Promise + proposeCredential(options: ProposeCredentialOptions): Promise + acceptProposal(options: AcceptCredentialProposalOptions): Promise + negotiateProposal(options: NegotiateCredentialProposalOptions): Promise // Offer Credential Methods - offerCredential(options: OfferCredentialOptions): Promise - acceptOffer(options: AcceptCredentialOfferOptions): Promise + offerCredential(options: OfferCredentialOptions): Promise + acceptOffer(options: AcceptCredentialOfferOptions): Promise declineOffer(credentialRecordId: string): Promise - negotiateOffer(options: NegotiateCredentialOfferOptions): Promise + negotiateOffer(options: NegotiateCredentialOfferOptions): Promise // Request Credential Methods // This is for beginning the exchange with a request (no proposal or offer). Only possible // (currently) with W3C. We will not implement this in phase I // when the issuer accepts the request he issues the credential to the holder - acceptRequest(options: AcceptCredentialRequestOptions): Promise + acceptRequest(options: AcceptCredentialRequestOptions): Promise // Issue Credential Methods acceptCredential(options: AcceptCredentialOptions): Promise // out of band - createOffer(options: CreateOfferOptions): Promise<{ + createOffer(options: CreateOfferOptions): Promise<{ message: AgentMessage credentialRecord: CredentialExchangeRecord }> @@ -82,25 +77,21 @@ export interface CredentialsApi deleteById(credentialRecordId: string, options?: DeleteCredentialOptions): Promise update(credentialRecord: CredentialExchangeRecord): Promise - getFormatData(credentialRecordId: string): Promise> + getFormatData(credentialRecordId: string): Promise>> // DidComm Message Records - findProposalMessage(credentialExchangeId: string): Promise> - findOfferMessage(credentialExchangeId: string): Promise> - findRequestMessage(credentialExchangeId: string): Promise> - findCredentialMessage(credentialExchangeId: string): Promise> + findProposalMessage(credentialExchangeId: string): Promise> + findOfferMessage(credentialExchangeId: string): Promise> + findRequestMessage(credentialExchangeId: string): Promise> + findCredentialMessage(credentialExchangeId: string): Promise> } @injectable() -export class CredentialsApi< - CFs extends CredentialFormat[] = [IndyCredentialFormat, JsonLdCredentialFormat], - CSs extends CredentialService[] = [V1CredentialService, V2CredentialService] -> implements CredentialsApi -{ +export class CredentialsApi implements CredentialsApi { /** * Configuration for the connections module */ - public readonly config: CredentialsModuleConfig + public readonly config: CredentialsModuleConfig private connectionService: ConnectionService private messageSender: MessageSender @@ -109,7 +100,7 @@ export class CredentialsApi< private didCommMessageRepository: DidCommMessageRepository private routingService: RoutingService private logger: Logger - private serviceMap: CredentialServiceMap + private credentialProtocolMap: CredentialProtocolMap public constructor( messageSender: MessageSender, @@ -119,12 +110,10 @@ export class CredentialsApi< credentialRepository: CredentialRepository, mediationRecipientService: RoutingService, didCommMessageRepository: DidCommMessageRepository, - v1Service: V1CredentialService, - v2Service: V2CredentialService, // only injected so the handlers will be registered // eslint-disable-next-line @typescript-eslint/no-unused-vars _revocationNotificationService: RevocationNotificationService, - config: CredentialsModuleConfig + config: CredentialsModuleConfig ) { this.messageSender = messageSender this.connectionService = connectionService @@ -136,23 +125,21 @@ export class CredentialsApi< this.config = config // Dynamically build service map. This will be extracted once services are registered dynamically - this.serviceMap = [v1Service, v2Service].reduce( - (serviceMap, service) => ({ - ...serviceMap, + this.credentialProtocolMap = config.credentialProtocols.reduce( + (protocolMap, service) => ({ + ...protocolMap, [service.version]: service, }), {} - ) as CredentialServiceMap - - this.logger.debug(`Initializing Credentials Module for agent ${this.agentContext.config.label}`) + ) as CredentialProtocolMap } - public getService(protocolVersion: PVT): CredentialService { - if (!this.serviceMap[protocolVersion]) { - throw new AriesFrameworkError(`No credential service registered for protocol version ${protocolVersion}`) + private getProtocol>(protocolVersion: PVT) { + if (!this.credentialProtocolMap[protocolVersion]) { + throw new AriesFrameworkError(`No credential protocol registered for protocol version ${protocolVersion}`) } - return this.serviceMap[protocolVersion] + return this.credentialProtocolMap[protocolVersion] } /** @@ -163,10 +150,10 @@ export class CredentialsApi< * @returns Credential exchange record associated with the sent proposal message */ - public async proposeCredential(options: ProposeCredentialOptions): Promise { - const service = this.getService(options.protocolVersion) + public async proposeCredential(options: ProposeCredentialOptions): Promise { + const service = this.getProtocol(options.protocolVersion) - this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) + this.logger.debug(`Got a credentialProtocol object for version ${options.protocolVersion}`) const connection = await this.connectionService.getById(this.agentContext, options.connectionId) @@ -200,7 +187,7 @@ export class CredentialsApi< * @returns Credential exchange record associated with the credential offer * */ - public async acceptProposal(options: AcceptCredentialProposalOptions): Promise { + public async acceptProposal(options: AcceptCredentialProposalOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) if (!credentialRecord.connectionId) { @@ -210,7 +197,7 @@ export class CredentialsApi< } // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) + const service = this.getProtocol(credentialRecord.protocolVersion) // will get back a credential record -> map to Credential Exchange Record const { message } = await service.acceptProposal(this.agentContext, { @@ -240,7 +227,7 @@ export class CredentialsApi< * @returns Credential exchange record associated with the credential offer * */ - public async negotiateProposal(options: NegotiateCredentialProposalOptions): Promise { + public async negotiateProposal(options: NegotiateCredentialProposalOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) if (!credentialRecord.connectionId) { @@ -250,7 +237,7 @@ export class CredentialsApi< } // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) + const service = this.getProtocol(credentialRecord.protocolVersion) const { message } = await service.negotiateProposal(this.agentContext, { credentialRecord, @@ -277,11 +264,11 @@ export class CredentialsApi< * @param options config options for the credential offer * @returns Credential exchange record associated with the sent credential offer message */ - public async offerCredential(options: OfferCredentialOptions): Promise { + public async offerCredential(options: OfferCredentialOptions): Promise { const connection = await this.connectionService.getById(this.agentContext, options.connectionId) - const service = this.getService(options.protocolVersion) + const service = this.getProtocol(options.protocolVersion) - this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) + this.logger.debug(`Got a credentialProtocol object for version ${options.protocolVersion}`) const { message, credentialRecord } = await service.createOffer(this.agentContext, { credentialFormats: options.credentialFormats, @@ -308,12 +295,12 @@ export class CredentialsApi< * @param options The object containing config options of the offer to be accepted * @returns Object containing offer associated credential record */ - public async acceptOffer(options: AcceptCredentialOfferOptions): Promise { + public async acceptOffer(options: AcceptCredentialOfferOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) - const service = this.getService(credentialRecord.protocolVersion) + const service = this.getProtocol(credentialRecord.protocolVersion) - this.logger.debug(`Got a CredentialService object for this version; version = ${service.version}`) + this.logger.debug(`Got a credentialProtocol object for this version; version = ${service.version}`) const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) // Use connection if present @@ -388,16 +375,16 @@ export class CredentialsApi< credentialRecord.assertState(CredentialState.OfferReceived) // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) + const service = this.getProtocol(credentialRecord.protocolVersion) await service.updateState(this.agentContext, credentialRecord, CredentialState.Declined) return credentialRecord } - public async negotiateOffer(options: NegotiateCredentialOfferOptions): Promise { + public async negotiateOffer(options: NegotiateCredentialOfferOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) - const service = this.getService(credentialRecord.protocolVersion) + const service = this.getProtocol(credentialRecord.protocolVersion) const { message } = await service.negotiateOffer(this.agentContext, { credentialFormats: options.credentialFormats, credentialRecord, @@ -428,13 +415,13 @@ export class CredentialsApi< * @param options The credential options to use for the offer * @returns The credential record and credential offer message */ - public async createOffer(options: CreateOfferOptions): Promise<{ + public async createOffer(options: CreateOfferOptions): Promise<{ message: AgentMessage credentialRecord: CredentialExchangeRecord }> { - const service = this.getService(options.protocolVersion) + const service = this.getProtocol(options.protocolVersion) - this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) + this.logger.debug(`Got a credentialProtocol object for version ${options.protocolVersion}`) const { message, credentialRecord } = await service.createOffer(this.agentContext, { credentialFormats: options.credentialFormats, comment: options.comment, @@ -453,13 +440,13 @@ export class CredentialsApi< * @param options The object containing config options of the request * @returns CredentialExchangeRecord updated with information pertaining to this request */ - public async acceptRequest(options: AcceptCredentialRequestOptions): Promise { + public async acceptRequest(options: AcceptCredentialRequestOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) + const service = this.getProtocol(credentialRecord.protocolVersion) - this.logger.debug(`Got a CredentialService object for version ${credentialRecord.protocolVersion}`) + this.logger.debug(`Got a credentialProtocol object for version ${credentialRecord.protocolVersion}`) const { message } = await service.acceptRequest(this.agentContext, { credentialRecord, @@ -529,9 +516,9 @@ export class CredentialsApi< const credentialRecord = await this.getById(options.credentialRecordId) // with version we can get the Service - const service = this.getService(credentialRecord.protocolVersion) + const service = this.getProtocol(credentialRecord.protocolVersion) - this.logger.debug(`Got a CredentialService object for version ${credentialRecord.protocolVersion}`) + this.logger.debug(`Got a credentialProtocol object for version ${credentialRecord.protocolVersion}`) const { message } = await service.acceptCredential(this.agentContext, { credentialRecord, @@ -591,7 +578,7 @@ export class CredentialsApi< } const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const service = this.getService(credentialRecord.protocolVersion) + const service = this.getProtocol(credentialRecord.protocolVersion) const problemReportMessage = service.createProblemReport(this.agentContext, { message: options.message }) problemReportMessage.setThread({ threadId: credentialRecord.threadId, @@ -606,9 +593,9 @@ export class CredentialsApi< return credentialRecord } - public async getFormatData(credentialRecordId: string): Promise> { + public async getFormatData(credentialRecordId: string): Promise>> { const credentialRecord = await this.getById(credentialRecordId) - const service = this.getService(credentialRecord.protocolVersion) + const service = this.getProtocol(credentialRecord.protocolVersion) return service.getFormatData(this.agentContext, credentialRecordId) } @@ -661,7 +648,7 @@ export class CredentialsApi< */ public async deleteById(credentialId: string, options?: DeleteCredentialOptions) { const credentialRecord = await this.getById(credentialId) - const service = this.getService(credentialRecord.protocolVersion) + const service = this.getProtocol(credentialRecord.protocolVersion) return service.delete(this.agentContext, credentialRecord, options) } @@ -674,25 +661,25 @@ export class CredentialsApi< await this.credentialRepository.update(this.agentContext, credentialRecord) } - public async findProposalMessage(credentialExchangeId: string): Promise> { + public async findProposalMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) return service.findProposalMessage(this.agentContext, credentialExchangeId) } - public async findOfferMessage(credentialExchangeId: string): Promise> { + public async findOfferMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) return service.findOfferMessage(this.agentContext, credentialExchangeId) } - public async findRequestMessage(credentialExchangeId: string): Promise> { + public async findRequestMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) return service.findRequestMessage(this.agentContext, credentialExchangeId) } - public async findCredentialMessage(credentialExchangeId: string): Promise> { + public async findCredentialMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) return service.findCredentialMessage(this.agentContext, credentialExchangeId) @@ -701,6 +688,6 @@ export class CredentialsApi< private async getServiceForCredentialExchangeId(credentialExchangeId: string) { const credentialExchangeRecord = await this.getById(credentialExchangeId) - return this.getService(credentialExchangeRecord.protocolVersion) + return this.getProtocol(credentialExchangeRecord.protocolVersion) } } diff --git a/packages/core/src/modules/credentials/CredentialsApiOptions.ts b/packages/core/src/modules/credentials/CredentialsApiOptions.ts index 86059ca443..7eb6c7488f 100644 --- a/packages/core/src/modules/credentials/CredentialsApiOptions.ts +++ b/packages/core/src/modules/credentials/CredentialsApiOptions.ts @@ -1,44 +1,45 @@ -import type { GetFormatDataReturn } from './CredentialServiceOptions' -import type { CredentialFormat, CredentialFormatPayload } from './formats' +import type { CFsFromCPs, GetFormatDataReturn } from './CredentialProtocolOptions' +import type { CredentialFormatPayload } from './formats' import type { AutoAcceptCredential } from './models/CredentialAutoAcceptType' -import type { CredentialService } from './services' +import type { CredentialProtocol } from './protocol/CredentialProtocol' // re-export GetFormatDataReturn type from service, as it is also used in the module export type { GetFormatDataReturn } -export type FindCredentialProposalMessageReturn = ReturnType< - CSs[number]['findProposalMessage'] +export type FindCredentialProposalMessageReturn = ReturnType< + CPs[number]['findProposalMessage'] > -export type FindCredentialOfferMessageReturn = ReturnType< - CSs[number]['findOfferMessage'] +export type FindCredentialOfferMessageReturn = ReturnType< + CPs[number]['findOfferMessage'] > -export type FindCredentialRequestMessageReturn = ReturnType< - CSs[number]['findRequestMessage'] +export type FindCredentialRequestMessageReturn = ReturnType< + CPs[number]['findRequestMessage'] > -export type FindCredentialMessageReturn = ReturnType< - CSs[number]['findCredentialMessage'] +export type FindCredentialMessageReturn = ReturnType< + CPs[number]['findCredentialMessage'] > /** - * Get the supported protocol versions based on the provided credential services. + * Get the supported protocol versions based on the provided credential protocols. */ -export type CredentialProtocolVersionType = CSs[number]['version'] +export type CredentialProtocolVersionType = + CPs[number]['version'] /** * Get the service map for usage in the credentials module. Will return a type mapping of protocol version to service. * * @example * ``` - * type ServiceMap = CredentialServiceMap<[IndyCredentialFormat], [V1CredentialService]> + * type ProtocolMap = CredentialProtocolMap<[IndyCredentialFormatService], [V1CredentialProtocol]> * * // equal to - * type ServiceMap = { - * v1: V1CredentialService + * type ProtocolMap = { + * v1: V1CredentialProtocol * } * ``` */ -export type CredentialServiceMap[]> = { - [CS in CSs[number] as CS['version']]: CredentialService +export type CredentialProtocolMap = { + [CP in CPs[number] as CP['version']]: CredentialProtocol } interface BaseOptions { @@ -49,13 +50,10 @@ interface BaseOptions { /** * Interface for CredentialsApi.proposeCredential. Will send a proposal. */ -export interface ProposeCredentialOptions< - CFs extends CredentialFormat[] = CredentialFormat[], - CSs extends CredentialService[] = CredentialService[] -> extends BaseOptions { +export interface ProposeCredentialOptions extends BaseOptions { connectionId: string - protocolVersion: CredentialProtocolVersionType - credentialFormats: CredentialFormatPayload + protocolVersion: CredentialProtocolVersionType + credentialFormats: CredentialFormatPayload, 'createProposal'> } /** @@ -63,40 +61,35 @@ export interface ProposeCredentialOptions< * * credentialFormats is optional because this is an accept method */ -export interface AcceptCredentialProposalOptions +export interface AcceptCredentialProposalOptions extends BaseOptions { credentialRecordId: string - credentialFormats?: CredentialFormatPayload + credentialFormats?: CredentialFormatPayload, 'acceptProposal'> } /** * Interface for CredentialsApi.negotiateProposal. Will send an offer */ -export interface NegotiateCredentialProposalOptions +export interface NegotiateCredentialProposalOptions extends BaseOptions { credentialRecordId: string - credentialFormats: CredentialFormatPayload + credentialFormats: CredentialFormatPayload, 'createOffer'> } /** * Interface for CredentialsApi.createOffer. Will create an out of band offer */ -export interface CreateOfferOptions< - CFs extends CredentialFormat[] = CredentialFormat[], - CSs extends CredentialService[] = CredentialService[] -> extends BaseOptions { - protocolVersion: CredentialProtocolVersionType - credentialFormats: CredentialFormatPayload +export interface CreateOfferOptions extends BaseOptions { + protocolVersion: CredentialProtocolVersionType + credentialFormats: CredentialFormatPayload, 'createOffer'> } /** * Interface for CredentialsApi.offerCredentials. Extends CreateOfferOptions, will send an offer */ -export interface OfferCredentialOptions< - CFs extends CredentialFormat[] = CredentialFormat[], - CSs extends CredentialService[] = CredentialService[] -> extends BaseOptions, - CreateOfferOptions { +export interface OfferCredentialOptions + extends BaseOptions, + CreateOfferOptions { connectionId: string } @@ -105,18 +98,19 @@ export interface OfferCredentialOptions< * * credentialFormats is optional because this is an accept method */ -export interface AcceptCredentialOfferOptions extends BaseOptions { +export interface AcceptCredentialOfferOptions + extends BaseOptions { credentialRecordId: string - credentialFormats?: CredentialFormatPayload + credentialFormats?: CredentialFormatPayload, 'acceptOffer'> } /** * Interface for CredentialsApi.negotiateOffer. Will send a proposal. */ -export interface NegotiateCredentialOfferOptions +export interface NegotiateCredentialOfferOptions extends BaseOptions { credentialRecordId: string - credentialFormats: CredentialFormatPayload + credentialFormats: CredentialFormatPayload, 'createProposal'> } /** @@ -124,10 +118,10 @@ export interface NegotiateCredentialOfferOptions +export interface AcceptCredentialRequestOptions extends BaseOptions { credentialRecordId: string - credentialFormats?: CredentialFormatPayload + credentialFormats?: CredentialFormatPayload, 'acceptRequest'> autoAcceptCredential?: AutoAcceptCredential comment?: string } diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index 8e9926ff87..a581f5e551 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -1,24 +1,62 @@ import type { FeatureRegistry } from '../../agent/FeatureRegistry' -import type { DependencyManager, Module } from '../../plugins' +import type { ApiModule, DependencyManager } from '../../plugins' +import type { Constructor } from '../../utils/mixins' +import type { Optional } from '../../utils/type' import type { CredentialsModuleConfigOptions } from './CredentialsModuleConfig' +import type { CredentialProtocol } from './protocol/CredentialProtocol' import { Protocol } from '../../agent/models' import { CredentialsApi } from './CredentialsApi' import { CredentialsModuleConfig } from './CredentialsModuleConfig' import { IndyCredentialFormatService } from './formats/indy' -import { JsonLdCredentialFormatService } from './formats/jsonld/JsonLdCredentialFormatService' import { RevocationNotificationService } from './protocol/revocation-notification/services' -import { V1CredentialService } from './protocol/v1' -import { V2CredentialService } from './protocol/v2' +import { V1CredentialProtocol } from './protocol/v1' +import { V2CredentialProtocol } from './protocol/v2' import { CredentialRepository } from './repository' -export class CredentialsModule implements Module { - public readonly config: CredentialsModuleConfig - public readonly api = CredentialsApi +/** + * Default credentialProtocols that will be registered if the `credentialProtocols` property is not configured. + */ +export type DefaultCredentialProtocols = [V1CredentialProtocol, V2CredentialProtocol] - public constructor(config?: CredentialsModuleConfigOptions) { - this.config = new CredentialsModuleConfig(config) +// CredentialModuleOptions makes the credentialProtocols property optional from the config, as it will set it when not provided. +export type CredentialsModuleOptions = Optional< + CredentialsModuleConfigOptions, + 'credentialProtocols' +> + +export class CredentialsModule + implements ApiModule +{ + public readonly config: CredentialsModuleConfig + + // Infer Api type from the config + public readonly api: Constructor> = CredentialsApi + + public constructor(config?: CredentialsModuleOptions) { + this.config = new CredentialsModuleConfig({ + ...config, + // NOTE: the credentialProtocols defaults are set in the CredentialsModule rather than the CredentialsModuleConfig to + // void dependency cycles. + credentialProtocols: config?.credentialProtocols ?? this.getDefaultCredentialProtocols(), + }) as CredentialsModuleConfig + } + + /** + * Get the default credential protocols that will be registered if the `credentialProtocols` property is not configured. + */ + private getDefaultCredentialProtocols(): DefaultCredentialProtocols { + // Instantiate credential formats + const indyCredentialFormat = new IndyCredentialFormatService() + + // Instantiate credential protocols + const v1CredentialProtocol = new V1CredentialProtocol({ indyCredentialFormat }) + const v2CredentialProtocol = new V2CredentialProtocol({ + credentialFormats: [indyCredentialFormat], + }) + + return [v1CredentialProtocol, v2CredentialProtocol] } /** @@ -32,23 +70,13 @@ export class CredentialsModule implements Module { dependencyManager.registerInstance(CredentialsModuleConfig, this.config) // Services - dependencyManager.registerSingleton(V1CredentialService) dependencyManager.registerSingleton(RevocationNotificationService) - dependencyManager.registerSingleton(V2CredentialService) // Repositories dependencyManager.registerSingleton(CredentialRepository) // Features featureRegistry.register( - new Protocol({ - id: 'https://didcomm.org/issue-credential/1.0', - roles: ['holder', 'issuer'], - }), - new Protocol({ - id: 'https://didcomm.org/issue-credential/2.0', - roles: ['holder', 'issuer'], - }), new Protocol({ id: 'https://didcomm.org/revocation_notification/1.0', roles: ['holder'], @@ -59,8 +87,9 @@ export class CredentialsModule implements Module { }) ) - // Credential Formats - dependencyManager.registerSingleton(IndyCredentialFormatService) - dependencyManager.registerSingleton(JsonLdCredentialFormatService) + // Protocol needs to register feature registry items and handlers + for (const credentialProtocol of this.config.credentialProtocols) { + credentialProtocol.register(dependencyManager, featureRegistry) + } } } diff --git a/packages/core/src/modules/credentials/CredentialsModuleConfig.ts b/packages/core/src/modules/credentials/CredentialsModuleConfig.ts index 7bce4095f4..34c20f50e7 100644 --- a/packages/core/src/modules/credentials/CredentialsModuleConfig.ts +++ b/packages/core/src/modules/credentials/CredentialsModuleConfig.ts @@ -1,27 +1,47 @@ +import type { CredentialProtocol } from './protocol/CredentialProtocol' + import { AutoAcceptCredential } from './models' /** * CredentialsModuleConfigOptions defines the interface for the options of the CredentialsModuleConfig class. * This can contain optional parameters that have default values in the config class itself. */ -export interface CredentialsModuleConfigOptions { +export interface CredentialsModuleConfigOptions { /** * Whether to automatically accept credential messages. Applies to all issue credential protocol versions. * * @default {@link AutoAcceptCredential.Never} */ autoAcceptCredentials?: AutoAcceptCredential + + /** + * Credential protocols to make available to the credentials module. Only one credential protocol should be registered for each credential + * protocol version. + * + * When not provided, the `V1CredentialProtocol` and `V2CredentialProtocol` are registered by default. + * + * @default + * ``` + * [V1CredentialProtocol, V2CredentialProtocol] + * ``` + */ + credentialProtocols: CredentialProtocols } -export class CredentialsModuleConfig { - private options: CredentialsModuleConfigOptions +export class CredentialsModuleConfig { + private options: CredentialsModuleConfigOptions - public constructor(options?: CredentialsModuleConfigOptions) { - this.options = options ?? {} + public constructor(options: CredentialsModuleConfigOptions) { + this.options = options } /** See {@link CredentialsModuleConfigOptions.autoAcceptCredentials} */ public get autoAcceptCredentials() { return this.options.autoAcceptCredentials ?? AutoAcceptCredential.Never } + + /** See {@link CredentialsModuleConfigOptions.credentialProtocols} */ + public get credentialProtocols() { + return this.options.credentialProtocols + } } diff --git a/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts index 9aec292944..f51328bede 100644 --- a/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts +++ b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts @@ -1,11 +1,12 @@ +import type { CredentialProtocol } from '../protocol/CredentialProtocol' + import { FeatureRegistry } from '../../../agent/FeatureRegistry' +import { Protocol } from '../../../agent/models/features/Protocol' import { DependencyManager } from '../../../plugins/DependencyManager' import { CredentialsApi } from '../CredentialsApi' import { CredentialsModule } from '../CredentialsModule' import { CredentialsModuleConfig } from '../CredentialsModuleConfig' -import { IndyCredentialFormatService } from '../formats' -import { JsonLdCredentialFormatService } from '../formats/jsonld/JsonLdCredentialFormatService' -import { V1CredentialService, V2CredentialService } from '../protocol' +import { V1CredentialProtocol, V2CredentialProtocol } from '../protocol' import { RevocationNotificationService } from '../protocol/revocation-notification/services' import { CredentialRepository } from '../repository' @@ -21,7 +22,9 @@ const featureRegistry = new FeatureRegistryMock() describe('CredentialsModule', () => { test('registers dependencies on the dependency manager', () => { - const credentialsModule = new CredentialsModule() + const credentialsModule = new CredentialsModule({ + credentialProtocols: [], + }) credentialsModule.register(dependencyManager, featureRegistry) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) @@ -30,13 +33,48 @@ describe('CredentialsModule', () => { expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) expect(dependencyManager.registerInstance).toHaveBeenCalledWith(CredentialsModuleConfig, credentialsModule.config) - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(6) + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(2) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V1CredentialService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V2CredentialService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(RevocationNotificationService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(CredentialRepository) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyCredentialFormatService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(JsonLdCredentialFormatService) + + expect(featureRegistry.register).toHaveBeenCalledTimes(1) + expect(featureRegistry.register).toHaveBeenCalledWith( + new Protocol({ + id: 'https://didcomm.org/revocation_notification/1.0', + roles: ['holder'], + }), + new Protocol({ + id: 'https://didcomm.org/revocation_notification/2.0', + roles: ['holder'], + }) + ) + }) + + test('registers V1CredentialProtocol and V2CredentialProtocol if no credentialProtocols are configured', () => { + const credentialsModule = new CredentialsModule() + + expect(credentialsModule.config.credentialProtocols).toEqual([ + expect.any(V1CredentialProtocol), + expect.any(V2CredentialProtocol), + ]) + }) + + test('calls register on the provided CredentialProtocols', () => { + const registerMock = jest.fn() + const credentialProtocol = { + register: registerMock, + } as unknown as CredentialProtocol + + const credentialsModule = new CredentialsModule({ + credentialProtocols: [credentialProtocol], + }) + + expect(credentialsModule.config.credentialProtocols).toEqual([credentialProtocol]) + + credentialsModule.register(dependencyManager, featureRegistry) + + expect(registerMock).toHaveBeenCalledTimes(1) + expect(registerMock).toHaveBeenCalledWith(dependencyManager, featureRegistry) }) }) diff --git a/packages/core/src/modules/credentials/__tests__/CredentialsModuleConfig.test.ts b/packages/core/src/modules/credentials/__tests__/CredentialsModuleConfig.test.ts index 5725cd7378..23a0e1c6e6 100644 --- a/packages/core/src/modules/credentials/__tests__/CredentialsModuleConfig.test.ts +++ b/packages/core/src/modules/credentials/__tests__/CredentialsModuleConfig.test.ts @@ -1,18 +1,26 @@ +import type { CredentialProtocol } from '../protocol/CredentialProtocol' + import { CredentialsModuleConfig } from '../CredentialsModuleConfig' import { AutoAcceptCredential } from '../models' describe('CredentialsModuleConfig', () => { test('sets default values', () => { - const config = new CredentialsModuleConfig() + const config = new CredentialsModuleConfig({ + credentialProtocols: [], + }) expect(config.autoAcceptCredentials).toBe(AutoAcceptCredential.Never) + expect(config.credentialProtocols).toEqual([]) }) test('sets values', () => { + const credentialProtocol = jest.fn() as unknown as CredentialProtocol const config = new CredentialsModuleConfig({ autoAcceptCredentials: AutoAcceptCredential.Always, + credentialProtocols: [credentialProtocol], }) expect(config.autoAcceptCredentials).toBe(AutoAcceptCredential.Always) + expect(config.credentialProtocols).toEqual([credentialProtocol]) }) }) diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts index f16edfb147..4e8e1e4102 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts @@ -1,4 +1,5 @@ import type { AgentContext } from '../../../agent' +import type { Attachment } from '../../../decorators/attachment/Attachment' import type { CredentialFormat } from './CredentialFormat' import type { FormatCreateProposalOptions, @@ -18,78 +19,46 @@ import type { FormatProcessCredentialOptions, } from './CredentialFormatServiceOptions' -import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' -import { JsonEncoder } from '../../../utils/JsonEncoder' - -export abstract class CredentialFormatService { - abstract readonly formatKey: CF['formatKey'] - abstract readonly credentialRecordType: CF['credentialRecordType'] +export interface CredentialFormatService { + formatKey: CF['formatKey'] + credentialRecordType: CF['credentialRecordType'] // proposal methods - abstract createProposal( + createProposal( agentContext: AgentContext, options: FormatCreateProposalOptions ): Promise - abstract processProposal(agentContext: AgentContext, options: FormatProcessOptions): Promise - abstract acceptProposal( - agentContext: AgentContext, - options: FormatAcceptProposalOptions - ): Promise + processProposal(agentContext: AgentContext, options: FormatProcessOptions): Promise + acceptProposal(agentContext: AgentContext, options: FormatAcceptProposalOptions): Promise // offer methods - abstract createOffer( - agentContext: AgentContext, - options: FormatCreateOfferOptions - ): Promise - abstract processOffer(agentContext: AgentContext, options: FormatProcessOptions): Promise - abstract acceptOffer( - agentContext: AgentContext, - options: FormatAcceptOfferOptions - ): Promise + createOffer(agentContext: AgentContext, options: FormatCreateOfferOptions): Promise + processOffer(agentContext: AgentContext, options: FormatProcessOptions): Promise + acceptOffer(agentContext: AgentContext, options: FormatAcceptOfferOptions): Promise // request methods - abstract createRequest( + createRequest( agentContext: AgentContext, options: FormatCreateRequestOptions ): Promise - abstract processRequest(agentContext: AgentContext, options: FormatProcessOptions): Promise - abstract acceptRequest( + processRequest(agentContext: AgentContext, options: FormatProcessOptions): Promise + acceptRequest( agentContext: AgentContext, options: FormatAcceptRequestOptions ): Promise // credential methods - abstract processCredential(agentContext: AgentContext, options: FormatProcessCredentialOptions): Promise + processCredential(agentContext: AgentContext, options: FormatProcessCredentialOptions): Promise // auto accept methods - abstract shouldAutoRespondToProposal(agentContext: AgentContext, options: FormatAutoRespondProposalOptions): boolean - abstract shouldAutoRespondToOffer(agentContext: AgentContext, options: FormatAutoRespondOfferOptions): boolean - abstract shouldAutoRespondToRequest(agentContext: AgentContext, options: FormatAutoRespondRequestOptions): boolean - abstract shouldAutoRespondToCredential( - agentContext: AgentContext, - options: FormatAutoRespondCredentialOptions - ): boolean - - abstract deleteCredentialById(agentContext: AgentContext, credentialId: string): Promise + shouldAutoRespondToProposal(agentContext: AgentContext, options: FormatAutoRespondProposalOptions): boolean + shouldAutoRespondToOffer(agentContext: AgentContext, options: FormatAutoRespondOfferOptions): boolean + shouldAutoRespondToRequest(agentContext: AgentContext, options: FormatAutoRespondRequestOptions): boolean + shouldAutoRespondToCredential(agentContext: AgentContext, options: FormatAutoRespondCredentialOptions): boolean - abstract supportsFormat(format: string): boolean + deleteCredentialById(agentContext: AgentContext, credentialId: string): Promise - /** - * Returns an object of type {@link Attachment} for use in credential exchange messages. - * It looks up the correct format identifier and encodes the data as a base64 attachment. - * - * @param data The data to include in the attach object - * @param id the attach id from the formats component of the message - */ - protected getFormatData(data: unknown, id: string): Attachment { - const attachment = new Attachment({ - id, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(data), - }), - }) + supportsFormat(format: string): boolean - return attachment - } + getFormatData(data: unknown, id: string): Attachment } diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts index 0c4d6044af..eb2430498f 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts @@ -6,20 +6,33 @@ import type { CredentialFormat, CredentialFormatPayload } from './CredentialForm import type { CredentialFormatService } from './CredentialFormatService' /** - * Get the service map for usage in the credentials module. Will return a type mapping of protocol version to service. + * Infer the {@link CredentialFormat} based on a {@link CredentialFormatService}. + * + * It does this by extracting the `CredentialFormat` generic from the `CredentialFormatService`. * * @example * ``` - * type FormatServiceMap = CredentialFormatServiceMap<[IndyCredentialFormat]> + * // TheCredentialFormat is now equal to IndyCredentialFormat + * type TheCredentialFormat = ExtractCredentialFormat + * ``` * - * // equal to - * type FormatServiceMap = { - * indy: CredentialFormatService + * Because the `IndyCredentialFormatService` is defined as follows: + * ``` + * class IndyCredentialFormatService implements CredentialFormatService { * } * ``` */ -export type CredentialFormatServiceMap = { - [CF in CFs[number] as CF['formatKey']]: CredentialFormatService +export type ExtractCredentialFormat = Type extends CredentialFormatService + ? CredentialFormat + : never + +/** + * Infer an array of {@link CredentialFormat} types based on an array of {@link CredentialFormatService} types. + * + * This is based on {@link ExtractCredentialFormat}, but allows to handle arrays. + */ +export type ExtractCredentialFormats = { + [CF in keyof CFs]: ExtractCredentialFormat } /** diff --git a/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts index fee7df817f..ebc32c4785 100644 --- a/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts +++ b/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts @@ -171,23 +171,25 @@ let credentialRecord: CredentialExchangeRecord describe('Indy CredentialFormatService', () => { let agentContext: AgentContext beforeEach(async () => { - agentContext = getAgentContext() - agentConfig = getAgentConfig('CredentialServiceTest') - indyIssuerService = new IndyIssuerServiceMock() indyHolderService = new IndyHolderServiceMock() indyLedgerService = new IndyLedgerServiceMock() didResolverService = new DidResolverServiceMock() connectionService = new ConnectionServiceMock() - indyFormatService = new IndyCredentialFormatService( - indyIssuerService, - indyLedgerService, - indyHolderService, - connectionService, - didResolverService, - agentConfig.logger - ) + agentConfig = getAgentConfig('IndyCredentialFormatServiceTest') + agentContext = getAgentContext({ + registerInstances: [ + [IndyIssuerService, indyIssuerService], + [IndyHolderService, indyHolderService], + [IndyLedgerService, indyLedgerService], + [DidResolverService, didResolverService], + [ConnectionService, connectionService], + ], + agentConfig, + }) + + indyFormatService = new IndyCredentialFormatService() mockFunction(indyLedgerService.getSchema).mockReturnValue(Promise.resolve(schema)) }) diff --git a/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts index d06f3bf3be..9e3b15105f 100644 --- a/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts +++ b/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts @@ -9,10 +9,11 @@ import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewA import type { V2OfferCredentialMessageOptions } from '../../protocol/v2/messages/V2OfferCredentialMessage' import type { CustomCredentialTags } from '../../repository/CredentialExchangeRecord' -import { getAgentContext, mockFunction } from '../../../../../tests/helpers' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' import { JsonTransformer } from '../../../../utils' import { JsonEncoder } from '../../../../utils/JsonEncoder' +import { DidDocument } from '../../../dids' import { DidResolverService } from '../../../dids/services/DidResolverService' import { W3cCredentialRecord, W3cCredentialService } from '../../../vc' import { Ed25519Signature2018Fixtures } from '../../../vc/__tests__/fixtures' @@ -31,36 +32,39 @@ jest.mock('../../../dids/services/DidResolverService') const W3cCredentialServiceMock = W3cCredentialService as jest.Mock const DidResolverServiceMock = DidResolverService as jest.Mock -const didDocument = { - context: [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - ], - id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - verificationMethod: [ - { - id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - type: 'Ed25519VerificationKey2018', - controller: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - publicKeyBase58: '3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx', - }, - ], - authentication: [ - 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - ], - assertionMethod: [ - 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - ], - keyAgreement: [ - { - id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6LSbkodSr6SU2trs8VUgnrnWtSm7BAPG245ggrBmSrxbv1R', - type: 'X25519KeyAgreementKey2019', - controller: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - publicKeyBase58: '5dTvYHaNaB7mk7iA9LqCJEHG2dGZQsvoi8WGzDRtYEf', - }, - ], -} +const didDocument = JsonTransformer.fromJSON( + { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + verificationMethod: [ + { + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + type: 'Ed25519VerificationKey2018', + controller: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + publicKeyBase58: '3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx', + }, + ], + authentication: [ + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + ], + assertionMethod: [ + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + ], + keyAgreement: [ + { + id: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6LSbkodSr6SU2trs8VUgnrnWtSm7BAPG245ggrBmSrxbv1R', + type: 'X25519KeyAgreementKey2019', + controller: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + publicKeyBase58: '5dTvYHaNaB7mk7iA9LqCJEHG2dGZQsvoi8WGzDRtYEf', + }, + ], + }, + DidDocument +) const vcJson = { ...Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, @@ -165,23 +169,32 @@ const requestAttachment = new Attachment({ base64: JsonEncoder.toBase64(signCredentialOptions), }), }) -let jsonldFormatService: CredentialFormatService +let jsonLdFormatService: CredentialFormatService let w3cCredentialService: W3cCredentialService let didResolver: DidResolverService describe('JsonLd CredentialFormatService', () => { let agentContext: AgentContext beforeEach(async () => { - agentContext = getAgentContext() w3cCredentialService = new W3cCredentialServiceMock() didResolver = new DidResolverServiceMock() - jsonldFormatService = new JsonLdCredentialFormatService(w3cCredentialService, didResolver) + + const agentConfig = getAgentConfig('JsonLdCredentialFormatServiceTest') + agentContext = getAgentContext({ + registerInstances: [ + [DidResolverService, didResolver], + [W3cCredentialService, w3cCredentialService], + ], + agentConfig, + }) + + jsonLdFormatService = new JsonLdCredentialFormatService() }) describe('Create JsonLd Credential Proposal / Offer', () => { test(`Creates JsonLd Credential Proposal`, async () => { // when - const { attachment, format } = await jsonldFormatService.createProposal(agentContext, { + const { attachment, format } = await jsonLdFormatService.createProposal(agentContext, { credentialRecord: mockCredentialRecord(), credentialFormats: { jsonld: signCredentialOptions, @@ -214,7 +227,7 @@ describe('JsonLd CredentialFormatService', () => { test(`Creates JsonLd Credential Offer`, async () => { // when - const { attachment, previewAttributes, format } = await jsonldFormatService.createOffer(agentContext, { + const { attachment, previewAttributes, format } = await jsonLdFormatService.createOffer(agentContext, { credentialFormats: { jsonld: signCredentialOptions, }, @@ -251,7 +264,7 @@ describe('JsonLd CredentialFormatService', () => { describe('Accept Credential Offer', () => { test('returns credential request message base on existing credential offer message', async () => { // when - const { attachment, format } = await jsonldFormatService.acceptOffer(agentContext, { + const { attachment, format } = await jsonLdFormatService.acceptOffer(agentContext, { credentialFormats: { jsonld: undefined, }, @@ -291,13 +304,12 @@ describe('JsonLd CredentialFormatService', () => { const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' test('Derive Verification Method', async () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockFunction(didResolver.resolveDidDocument).mockReturnValue(Promise.resolve(didDocument as any)) + mockFunction(didResolver.resolveDidDocument).mockReturnValue(Promise.resolve(didDocument)) mockFunction(w3cCredentialService.getVerificationMethodTypesByProofType).mockReturnValue([ 'Ed25519VerificationKey2018', ]) - const service = jsonldFormatService as JsonLdCredentialFormatService + const service = jsonLdFormatService as JsonLdCredentialFormatService const credentialRequest = requestAttachment.getDataAsJson() // calls private method in the format service @@ -321,7 +333,7 @@ describe('JsonLd CredentialFormatService', () => { connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - const { format, attachment } = await jsonldFormatService.acceptRequest(agentContext, { + const { format, attachment } = await jsonLdFormatService.acceptRequest(agentContext, { credentialRecord, credentialFormats: { jsonld: { @@ -383,11 +395,10 @@ describe('JsonLd CredentialFormatService', () => { }) test('finds credential record by thread ID and saves credential attachment into the wallet', async () => { // given - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c)) // when - await jsonldFormatService.processCredential(agentContext, { + await jsonLdFormatService.processCredential(agentContext, { attachment: credentialAttachment, requestAttachment: requestAttachment, credentialRecord, @@ -417,12 +428,11 @@ describe('JsonLd CredentialFormatService', () => { }) // given - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c)) // when/then await expect( - jsonldFormatService.processCredential(agentContext, { + jsonLdFormatService.processCredential(agentContext, { attachment: credentialAttachment, requestAttachment: requestAttachment, credentialRecord, @@ -439,12 +449,11 @@ describe('JsonLd CredentialFormatService', () => { }), }) // given - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c)) // when/then await expect( - jsonldFormatService.processCredential(agentContext, { + jsonLdFormatService.processCredential(agentContext, { attachment: credentialAttachment, requestAttachment: requestAttachmentWithDomain, credentialRecord, @@ -462,12 +471,11 @@ describe('JsonLd CredentialFormatService', () => { }) // given - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c)) // when/then await expect( - jsonldFormatService.processCredential(agentContext, { + jsonLdFormatService.processCredential(agentContext, { attachment: credentialAttachment, requestAttachment: requestAttachmentWithChallenge, credentialRecord, @@ -485,12 +493,11 @@ describe('JsonLd CredentialFormatService', () => { }) // given - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c)) // when/then await expect( - jsonldFormatService.processCredential(agentContext, { + jsonLdFormatService.processCredential(agentContext, { attachment: credentialAttachment, requestAttachment: requestAttachmentWithProofType, credentialRecord, @@ -508,12 +515,11 @@ describe('JsonLd CredentialFormatService', () => { }) // given - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c as any)) + mockFunction(w3cCredentialService.storeCredential).mockReturnValue(Promise.resolve(w3c)) // when/then await expect( - jsonldFormatService.processCredential(agentContext, { + jsonLdFormatService.processCredential(agentContext, { attachment: credentialAttachment, requestAttachment: requestAttachmentWithProofPurpose, credentialRecord, @@ -539,7 +545,7 @@ describe('JsonLd CredentialFormatService', () => { }) // indirectly test areCredentialsEqual as black box rather than expose that method in the API - let areCredentialsEqual = jsonldFormatService.shouldAutoRespondToProposal(agentContext, { + let areCredentialsEqual = jsonLdFormatService.shouldAutoRespondToProposal(agentContext, { credentialRecord, proposalAttachment: message1, offerAttachment: message2, @@ -557,7 +563,7 @@ describe('JsonLd CredentialFormatService', () => { base64: JsonEncoder.toBase64(inputDoc2), }) - areCredentialsEqual = jsonldFormatService.shouldAutoRespondToProposal(agentContext, { + areCredentialsEqual = jsonLdFormatService.shouldAutoRespondToProposal(agentContext, { credentialRecord, proposalAttachment: message1, offerAttachment: message2, diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts index c97dc188cb..cf9beaeb9f 100644 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts @@ -1,8 +1,8 @@ import type { AgentContext } from '../../../../agent' -import type { Attachment } from '../../../../decorators/attachment/Attachment' import type { LinkedAttachment } from '../../../../utils/LinkedAttachment' import type { CredentialPreviewAttributeOptions } from '../../models/CredentialPreviewAttribute' import type { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' +import type { CredentialFormatService } from '../CredentialFormatService' import type { FormatAcceptOfferOptions, FormatAcceptProposalOptions, @@ -17,27 +17,27 @@ import type { FormatCreateProposalReturn, CredentialFormatCreateReturn, FormatProcessOptions, + FormatProcessCredentialOptions, } from '../CredentialFormatServiceOptions' import type { IndyCredentialFormat } from './IndyCredentialFormat' import type * as Indy from 'indy-sdk' -import { InjectionSymbols } from '../../../../constants' +import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../../error' -import { Logger } from '../../../../logger' -import { inject, injectable } from '../../../../plugins' +import { JsonEncoder } from '../../../../utils/JsonEncoder' import { JsonTransformer } from '../../../../utils/JsonTransformer' import { MessageValidator } from '../../../../utils/MessageValidator' import { getIndyDidFromVerificationMethod } from '../../../../utils/did' import { uuid } from '../../../../utils/uuid' import { ConnectionService } from '../../../connections' import { DidResolverService, findVerificationMethodByKeyType } from '../../../dids' -import { IndyHolderService, IndyIssuerService } from '../../../indy' +import { IndyHolderService } from '../../../indy/services/IndyHolderService' +import { IndyIssuerService } from '../../../indy/services/IndyIssuerService' import { IndyLedgerService } from '../../../ledger' import { CredentialProblemReportError, CredentialProblemReportReason } from '../../errors' import { CredentialFormatSpec } from '../../models/CredentialFormatSpec' import { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' import { CredentialMetadataKeys } from '../../repository/CredentialMetadataTypes' -import { CredentialFormatService } from '../CredentialFormatService' import { IndyCredentialUtils } from './IndyCredentialUtils' import { IndyCredPropose } from './models/IndyCredPropose' @@ -47,32 +47,7 @@ const INDY_CRED_REQUEST = 'hlindy/cred-req@v2.0' const INDY_CRED_FILTER = 'hlindy/cred-filter@v2.0' const INDY_CRED = 'hlindy/cred@v2.0' -@injectable() -export class IndyCredentialFormatService extends CredentialFormatService { - private indyIssuerService: IndyIssuerService - private indyLedgerService: IndyLedgerService - private indyHolderService: IndyHolderService - private connectionService: ConnectionService - private didResolver: DidResolverService - private logger: Logger - - public constructor( - indyIssuerService: IndyIssuerService, - indyLedgerService: IndyLedgerService, - indyHolderService: IndyHolderService, - connectionService: ConnectionService, - didResolver: DidResolverService, - @inject(InjectionSymbols.Logger) logger: Logger - ) { - super() - this.indyIssuerService = indyIssuerService - this.indyLedgerService = indyLedgerService - this.indyHolderService = indyHolderService - this.connectionService = connectionService - this.didResolver = didResolver - this.logger = logger - } - +export class IndyCredentialFormatService implements CredentialFormatService { public readonly formatKey = 'indy' as const public readonly credentialRecordType = 'indy' as const @@ -198,7 +173,7 @@ export class IndyCredentialFormatService extends CredentialFormatService() @@ -215,15 +190,18 @@ export class IndyCredentialFormatService extends CredentialFormatService { const indyFormat = credentialFormats?.indy + const indyLedgerService = agentContext.dependencyManager.resolve(IndyLedgerService) + const indyHolderService = agentContext.dependencyManager.resolve(IndyHolderService) + const holderDid = indyFormat?.holderDid ?? (await this.getIndyHolderDid(agentContext, credentialRecord)) const credentialOffer = offerAttachment.getDataAsJson() - const credentialDefinition = await this.indyLedgerService.getCredentialDefinition( + const credentialDefinition = await indyLedgerService.getCredentialDefinition( agentContext, credentialOffer.cred_def_id ) - const [credentialRequest, credentialRequestMetadata] = await this.indyHolderService.createCredentialRequest( + const [credentialRequest, credentialRequestMetadata] = await indyHolderService.createCredentialRequest( agentContext, { holderDid, @@ -275,6 +253,8 @@ export class IndyCredentialFormatService extends CredentialFormatService() const credentialRequest = requestAttachment.getDataAsJson() @@ -282,7 +262,7 @@ export class IndyCredentialFormatService extends CredentialFormatService { const credentialRequestMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyRequest) + const indyLedgerService = agentContext.dependencyManager.resolve(IndyLedgerService) + const indyHolderService = agentContext.dependencyManager.resolve(IndyHolderService) + if (!credentialRequestMetadata) { throw new CredentialProblemReportError( `Missing required request metadata for credential with id ${credentialRecord.id}`, @@ -323,12 +306,12 @@ export class IndyCredentialFormatService extends CredentialFormatService() - const credentialDefinition = await this.indyLedgerService.getCredentialDefinition( + const credentialDefinition = await indyLedgerService.getCredentialDefinition( agentContext, indyCredential.cred_def_id ) const revocationRegistry = indyCredential.rev_reg_id - ? await this.indyLedgerService.getRevocationRegistryDefinition(agentContext, indyCredential.rev_reg_id) + ? await indyLedgerService.getRevocationRegistryDefinition(agentContext, indyCredential.rev_reg_id) : null if (!credentialRecord.credentialAttributes) { @@ -341,7 +324,7 @@ export class IndyCredentialFormatService extends CredentialFormatService { - await this.indyHolderService.deleteCredential(agentContext, credentialRecordId) + const indyHolderService = agentContext.dependencyManager.resolve(IndyHolderService) + + await indyHolderService.deleteCredential(agentContext, credentialRecordId) } public shouldAutoRespondToProposal( @@ -466,13 +451,15 @@ export class IndyCredentialFormatService extends CredentialFormatService { + const indyIssuerService = agentContext.dependencyManager.resolve(IndyIssuerService) + // if the proposal has an attachment Id use that, otherwise the generated id of the formats object const format = new CredentialFormatSpec({ attachId: attachId, format: INDY_CRED_ABSTRACT, }) - const offer = await this.indyIssuerService.createCredentialOffer(agentContext, credentialDefinitionId) + const offer = await indyIssuerService.createCredentialOffer(agentContext, credentialDefinitionId) const { previewAttributes } = this.getCredentialLinkedAttachments(attributes, linkedAttachments) if (!previewAttributes) { @@ -496,19 +483,24 @@ export class IndyCredentialFormatService extends CredentialFormatService { - const schema = await this.indyLedgerService.getSchema(agentContext, offer.schema_id) + const indyLedgerService = agentContext.dependencyManager.resolve(IndyLedgerService) + + const schema = await indyLedgerService.getSchema(agentContext, offer.schema_id) IndyCredentialUtils.checkAttributesMatch(schema, attributes) } private async getIndyHolderDid(agentContext: AgentContext, credentialRecord: CredentialExchangeRecord) { + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + const didResolver = agentContext.dependencyManager.resolve(DidResolverService) + // If we have a connection id we try to extract the did from the connection did document. if (credentialRecord.connectionId) { - const connection = await this.connectionService.getById(agentContext, credentialRecord.connectionId) + const connection = await connectionService.getById(agentContext, credentialRecord.connectionId) if (!connection.did) { throw new AriesFrameworkError(`Connection record ${connection.id} has no 'did'`) } - const resolved = await this.didResolver.resolve(agentContext, connection.did) + const resolved = await didResolver.resolve(agentContext, connection.did) if (resolved.didDocument) { const verificationMethod = await findVerificationMethodByKeyType( @@ -560,4 +552,23 @@ export class IndyCredentialFormatService extends CredentialFormatService { - private w3cCredentialService: W3cCredentialService - private didResolver: DidResolverService - - public constructor(w3cCredentialService: W3cCredentialService, didResolver: DidResolverService) { - super() - this.w3cCredentialService = w3cCredentialService - this.didResolver = didResolver - } - +export class JsonLdCredentialFormatService implements CredentialFormatService { public readonly formatKey = 'jsonld' as const public readonly credentialRecordType = 'w3c' as const @@ -221,6 +209,8 @@ export class JsonLdCredentialFormatService extends CredentialFormatService ): Promise { + const w3cCredentialService = agentContext.dependencyManager.resolve(W3cCredentialService) + // sign credential here. credential to be signed is received as the request attachment // (attachment in the request message from holder to issuer) const credentialRequest = requestAttachment.getDataAsJson() @@ -247,7 +237,7 @@ export class JsonLdCredentialFormatService extends CredentialFormatService { + const didResolver = agentContext.dependencyManager.resolve(DidResolverService) + const w3cCredentialService = agentContext.dependencyManager.resolve(W3cCredentialService) + const credential = JsonTransformer.fromJSON(credentialAsJson, W3cCredential) // extract issuer from vc (can be string or Issuer) @@ -276,13 +269,13 @@ export class JsonLdCredentialFormatService extends CredentialFormatService { + const w3cCredentialService = agentContext.dependencyManager.resolve(W3cCredentialService) + const credentialAsJson = attachment.getDataAsJson() const credential = JsonTransformer.fromJSON(credentialAsJson, W3cVerifiableCredential) @@ -320,11 +315,12 @@ export class JsonLdCredentialFormatService extends CredentialFormatService { - protected credentialRepository: CredentialRepository - protected didCommMessageRepository: DidCommMessageRepository - protected eventEmitter: EventEmitter - protected dispatcher: Dispatcher - protected logger: Logger - - public constructor( - credentialRepository: CredentialRepository, - didCommMessageRepository: DidCommMessageRepository, - eventEmitter: EventEmitter, - dispatcher: Dispatcher, - logger: Logger - ) { - this.credentialRepository = credentialRepository - this.didCommMessageRepository = didCommMessageRepository - this.eventEmitter = eventEmitter - this.dispatcher = dispatcher - this.logger = logger - } - +import { CredentialRepository } from '../repository' + +/** + * Base implementation of the CredentialProtocol that can be used as a foundation for implementing + * the CredentialProtocol interface. + */ +export abstract class BaseCredentialProtocol + implements CredentialProtocol +{ abstract readonly version: string - abstract getFormatServiceForRecordType( - credentialRecordType: CFs[number]['credentialRecordType'] - ): CredentialFormatService + protected abstract getFormatServiceForRecordType(credentialRecordType: string): CFs[number] // methods for proposal abstract createProposal( @@ -116,7 +101,12 @@ export abstract class CredentialService abstract findRequestMessage(agentContext: AgentContext, credentialExchangeId: string): Promise abstract findCredentialMessage(agentContext: AgentContext, credentialExchangeId: string): Promise - abstract getFormatData(agentContext: AgentContext, credentialExchangeId: string): Promise> + abstract getFormatData( + agentContext: AgentContext, + credentialExchangeId: string + ): Promise>> + + abstract register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void /** * Decline a credential offer @@ -142,11 +132,11 @@ export abstract class CredentialService ): Promise { - const { message: credentialProblemReportMessage } = messageContext + const { message: credentialProblemReportMessage, agentContext } = messageContext const connection = messageContext.assertReadyConnection() - this.logger.debug(`Processing problem report with id ${credentialProblemReportMessage.id}`) + agentContext.config.logger.debug(`Processing problem report with id ${credentialProblemReportMessage.id}`) const credentialRecord = await this.getByThreadAndConnectionId( messageContext.agentContext, @@ -173,13 +163,15 @@ export abstract class CredentialService(agentContext, { + eventEmitter.emit(agentContext, { type: CredentialEventTypes.CredentialStateChanged, payload: { credentialRecord: clonedCredential, @@ -209,7 +203,9 @@ export abstract class CredentialService { - return this.credentialRepository.getById(agentContext, credentialRecordId) + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + + return credentialRepository.getById(agentContext, credentialRecordId) } /** @@ -218,14 +214,18 @@ export abstract class CredentialService { - return this.credentialRepository.getAll(agentContext) + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + + return credentialRepository.getAll(agentContext) } public async findAllByQuery( agentContext: AgentContext, query: Query ): Promise { - return this.credentialRepository.findByQuery(agentContext, query) + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + + return credentialRepository.findByQuery(agentContext, query) } /** @@ -235,7 +235,9 @@ export abstract class CredentialService { - return this.credentialRepository.findById(agentContext, connectionId) + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + + return credentialRepository.findById(agentContext, connectionId) } public async delete( @@ -243,7 +245,10 @@ export abstract class CredentialService { - await this.credentialRepository.delete(agentContext, credentialRecord) + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + await credentialRepository.delete(agentContext, credentialRecord) const deleteAssociatedCredentials = options?.deleteAssociatedCredentials ?? true const deleteAssociatedDidCommMessages = options?.deleteAssociatedDidCommMessages ?? true @@ -256,11 +261,11 @@ export abstract class CredentialService { - return this.credentialRepository.getSingleByQuery(agentContext, { + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + + return credentialRepository.getSingleByQuery(agentContext, { connectionId, threadId, }) @@ -297,13 +304,17 @@ export abstract class CredentialService { - return this.credentialRepository.findSingleByQuery(agentContext, { + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + + return credentialRepository.findSingleByQuery(agentContext, { connectionId, threadId, }) } public async update(agentContext: AgentContext, credentialRecord: CredentialExchangeRecord) { - return await this.credentialRepository.update(agentContext, credentialRecord) + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + + return await credentialRepository.update(agentContext, credentialRecord) } } diff --git a/packages/core/src/modules/credentials/protocol/CredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/CredentialProtocol.ts new file mode 100644 index 0000000000..77665a8236 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/CredentialProtocol.ts @@ -0,0 +1,130 @@ +import type { AgentContext } from '../../../agent' +import type { AgentMessage } from '../../../agent/AgentMessage' +import type { FeatureRegistry } from '../../../agent/FeatureRegistry' +import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { DependencyManager } from '../../../plugins' +import type { Query } from '../../../storage/StorageService' +import type { ProblemReportMessage } from '../../problem-reports' +import type { + CreateProposalOptions, + CredentialProtocolMsgReturnType, + DeleteCredentialOptions, + AcceptProposalOptions, + NegotiateProposalOptions, + CreateOfferOptions, + NegotiateOfferOptions, + CreateRequestOptions, + AcceptOfferOptions, + AcceptRequestOptions, + AcceptCredentialOptions, + GetFormatDataReturn, + CreateProblemReportOptions, +} from '../CredentialProtocolOptions' +import type { CredentialFormatService, ExtractCredentialFormats } from '../formats' +import type { CredentialState } from '../models/CredentialState' +import type { CredentialExchangeRecord } from '../repository' + +export interface CredentialProtocol { + readonly version: string + + // methods for proposal + createProposal( + agentContext: AgentContext, + options: CreateProposalOptions + ): Promise> + processProposal(messageContext: InboundMessageContext): Promise + acceptProposal( + agentContext: AgentContext, + options: AcceptProposalOptions + ): Promise> + negotiateProposal( + agentContext: AgentContext, + options: NegotiateProposalOptions + ): Promise> + + // methods for offer + createOffer( + agentContext: AgentContext, + options: CreateOfferOptions + ): Promise> + processOffer(messageContext: InboundMessageContext): Promise + acceptOffer( + agentContext: AgentContext, + options: AcceptOfferOptions + ): Promise> + negotiateOffer( + agentContext: AgentContext, + options: NegotiateOfferOptions + ): Promise> + + // methods for request + createRequest( + agentContext: AgentContext, + options: CreateRequestOptions + ): Promise> + processRequest(messageContext: InboundMessageContext): Promise + acceptRequest( + agentContext: AgentContext, + options: AcceptRequestOptions + ): Promise> + + // methods for issue + processCredential(messageContext: InboundMessageContext): Promise + acceptCredential( + agentContext: AgentContext, + options: AcceptCredentialOptions + ): Promise> + + // methods for ack + processAck(messageContext: InboundMessageContext): Promise + + // methods for problem-report + createProblemReport(agentContext: AgentContext, options: CreateProblemReportOptions): ProblemReportMessage + + findProposalMessage(agentContext: AgentContext, credentialExchangeId: string): Promise + findOfferMessage(agentContext: AgentContext, credentialExchangeId: string): Promise + findRequestMessage(agentContext: AgentContext, credentialExchangeId: string): Promise + findCredentialMessage(agentContext: AgentContext, credentialExchangeId: string): Promise + getFormatData( + agentContext: AgentContext, + credentialExchangeId: string + ): Promise>> + + declineOffer( + agentContext: AgentContext, + credentialRecord: CredentialExchangeRecord + ): Promise + processProblemReport(messageContext: InboundMessageContext): Promise + + // Repository methods + updateState( + agentContext: AgentContext, + credentialRecord: CredentialExchangeRecord, + newState: CredentialState + ): Promise + getById(agentContext: AgentContext, credentialRecordId: string): Promise + getAll(agentContext: AgentContext): Promise + findAllByQuery( + agentContext: AgentContext, + query: Query + ): Promise + findById(agentContext: AgentContext, connectionId: string): Promise + delete( + agentContext: AgentContext, + credentialRecord: CredentialExchangeRecord, + options?: DeleteCredentialOptions + ): Promise + getByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise + findByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise + update(agentContext: AgentContext, credentialRecord: CredentialExchangeRecord): Promise + + register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void +} diff --git a/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts b/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts similarity index 72% rename from packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts rename to packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts index dad931ab97..879ef75fd3 100644 --- a/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts +++ b/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts @@ -1,6 +1,8 @@ import type { AgentContext } from '../../../../agent' import type { AgentMessage } from '../../../../agent/AgentMessage' +import type { FeatureRegistry } from '../../../../agent/FeatureRegistry' import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { DependencyManager } from '../../../../plugins' import type { ProblemReportMessage } from '../../../problem-reports' import type { AcceptCredentialOptions, @@ -13,35 +15,29 @@ import type { CredentialProtocolMsgReturnType, NegotiateOfferOptions, NegotiateProposalOptions, -} from '../../CredentialServiceOptions' +} from '../../CredentialProtocolOptions' import type { GetFormatDataReturn } from '../../CredentialsApiOptions' -import type { CredentialFormat } from '../../formats' -import type { IndyCredentialFormat } from '../../formats/indy/IndyCredentialFormat' +import type { CredentialFormatService, ExtractCredentialFormats, IndyCredentialFormat } from '../../formats' -import { Dispatcher } from '../../../../agent/Dispatcher' -import { EventEmitter } from '../../../../agent/EventEmitter' -import { InjectionSymbols } from '../../../../constants' +import { Protocol } from '../../../../agent/models/features' import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../../error' -import { Logger } from '../../../../logger' -import { inject, injectable } from '../../../../plugins' +import { injectable } from '../../../../plugins' import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' import { JsonTransformer } from '../../../../utils' import { isLinkedAttachment } from '../../../../utils/attachment' import { uuid } from '../../../../utils/uuid' import { AckStatus } from '../../../common' import { ConnectionService } from '../../../connections/services' -import { RoutingService } from '../../../routing/services/RoutingService' import { CredentialsModuleConfig } from '../../CredentialsModuleConfig' import { CredentialProblemReportReason } from '../../errors' -import { IndyCredentialFormatService } from '../../formats/indy/IndyCredentialFormatService' import { IndyCredPropose } from '../../formats/indy/models' import { AutoAcceptCredential } from '../../models/CredentialAutoAcceptType' import { CredentialState } from '../../models/CredentialState' import { CredentialExchangeRecord, CredentialRepository } from '../../repository' -import { CredentialService } from '../../services' import { composeAutoAccept } from '../../util/composeAutoAccept' import { arePreviewAttributesEqual } from '../../util/previewAttributes' +import { BaseCredentialProtocol } from '../BaseCredentialProtocol' import { V1CredentialAckHandler, @@ -64,31 +60,20 @@ import { } from './messages' import { V1CredentialPreview } from './messages/V1CredentialPreview' +export interface V1CredentialProtocolConfig { + // indyCredentialFormat must be a service that implements the `IndyCredentialFormat` interface, however it doesn't + // have to be the IndyCredentialFormatService implementation per se. + indyCredentialFormat: CredentialFormatService +} + @injectable() -export class V1CredentialService extends CredentialService<[IndyCredentialFormat]> { - private connectionService: ConnectionService - private formatService: IndyCredentialFormatService - private routingService: RoutingService - private credentialsModuleConfig: CredentialsModuleConfig - - public constructor( - connectionService: ConnectionService, - didCommMessageRepository: DidCommMessageRepository, - @inject(InjectionSymbols.Logger) logger: Logger, - routingService: RoutingService, - dispatcher: Dispatcher, - eventEmitter: EventEmitter, - credentialRepository: CredentialRepository, - formatService: IndyCredentialFormatService, - credentialsModuleConfig: CredentialsModuleConfig - ) { - super(credentialRepository, didCommMessageRepository, eventEmitter, dispatcher, logger) - this.connectionService = connectionService - this.formatService = formatService - this.routingService = routingService - this.credentialsModuleConfig = credentialsModuleConfig +export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialFormatService]> { + private indyCredentialFormat: CredentialFormatService - this.registerMessageHandlers() + public constructor({ indyCredentialFormat }: V1CredentialProtocolConfig) { + super() + + this.indyCredentialFormat = indyCredentialFormat } /** @@ -96,14 +81,27 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat */ public readonly version = 'v1' - public getFormatServiceForRecordType(credentialRecordType: string) { - if (credentialRecordType !== this.formatService.credentialRecordType) { - throw new AriesFrameworkError( - `Unsupported credential record type ${credentialRecordType} for v1 issue credential protocol (need ${this.formatService.credentialRecordType})` - ) - } + /** + * Registers the protocol implementation (handlers, feature registry) on the agent. + */ + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { + // Register message handlers for the Issue Credential V1 Protocol + dependencyManager.registerMessageHandlers([ + new V1ProposeCredentialHandler(this), + new V1OfferCredentialHandler(this), + new V1RequestCredentialHandler(this), + new V1IssueCredentialHandler(this), + new V1CredentialAckHandler(this), + new V1CredentialProblemReportHandler(this), + ]) - return this.formatService + // Register Issue Credential V1 in feature registry, with supported roles + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/issue-credential/1.0', + roles: ['holder', 'issuer'], + }) + ) } /** @@ -116,10 +114,18 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat */ public async createProposal( agentContext: AgentContext, - { connection, credentialFormats, comment, autoAcceptCredential }: CreateProposalOptions<[IndyCredentialFormat]> + { + connection, + credentialFormats, + comment, + autoAcceptCredential, + }: CreateProposalOptions<[CredentialFormatService]> ): Promise> { this.assertOnlyIndyFormat(credentialFormats) + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + if (!credentialFormats.indy) { throw new AriesFrameworkError('Missing indy credential format in v1 create proposal call.') } @@ -139,7 +145,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat }) // call create proposal for validation of the proposal and addition of linked attachments - const { previewAttributes, attachment } = await this.formatService.createProposal(agentContext, { + const { previewAttributes, attachment } = await this.indyCredentialFormat.createProposal(agentContext, { credentialFormats, credentialRecord, }) @@ -161,14 +167,14 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat comment, }) - await this.didCommMessageRepository.saveAgentMessage(agentContext, { + await didCommMessageRepository.saveAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, }) credentialRecord.credentialAttributes = previewAttributes - await this.credentialRepository.save(agentContext, credentialRecord) + await credentialRepository.save(agentContext, credentialRecord) this.emitStateChangedEvent(agentContext, credentialRecord, null) return { credentialRecord, message } @@ -187,9 +193,16 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat public async processProposal( messageContext: InboundMessageContext ): Promise { - const { message: proposalMessage, connection } = messageContext + const { message: proposalMessage, connection, agentContext } = messageContext + + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) - this.logger.debug(`Processing credential proposal with message id ${proposalMessage.id}`) + // TODO: with this method, we should update the credential protocol to use the ConnectionApi, so it + // only depends on the public api, rather than the internal API (this helps with breaking changes) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + agentContext.config.logger.debug(`Processing credential proposal with message id ${proposalMessage.id}`) let credentialRecord = await this.findByThreadAndConnectionId( messageContext.agentContext, @@ -199,30 +212,27 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat // Credential record already exists, this is a response to an earlier message sent by us if (credentialRecord) { - this.logger.debug('Credential record already exists for incoming proposal') + agentContext.config.logger.debug('Credential record already exists for incoming proposal') // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferSent) - const proposalCredentialMessage = await this.didCommMessageRepository.findAgentMessage( - messageContext.agentContext, - { - associatedRecordId: credentialRecord.id, - messageClass: V1ProposeCredentialMessage, - } - ) - const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const proposalCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: credentialRecord.id, + messageClass: V1ProposeCredentialMessage, + }) + const offerCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + connectionService.assertConnectionOrServiceDecorator(messageContext, { previousReceivedMessage: proposalCredentialMessage ?? undefined, previousSentMessage: offerCredentialMessage ?? undefined, }) - await this.formatService.processProposal(messageContext.agentContext, { + await this.indyCredentialFormat.processProposal(messageContext.agentContext, { credentialRecord, attachment: new Attachment({ data: new AttachmentData({ @@ -233,13 +243,13 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat // Update record await this.updateState(messageContext.agentContext, credentialRecord, CredentialState.ProposalReceived) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: proposalMessage, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, }) } else { - this.logger.debug('Credential record does not exists yet for incoming proposal') + agentContext.config.logger.debug('Credential record does not exists yet for incoming proposal') // No credential record exists with thread id credentialRecord = new CredentialExchangeRecord({ @@ -250,13 +260,13 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat }) // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) + connectionService.assertConnectionOrServiceDecorator(messageContext) // Save record - await this.credentialRepository.save(messageContext.agentContext, credentialRecord) + await credentialRepository.save(messageContext.agentContext, credentialRecord) this.emitStateChangedEvent(messageContext.agentContext, credentialRecord, null) - await this.didCommMessageRepository.saveAgentMessage(messageContext.agentContext, { + await didCommMessageRepository.saveAgentMessage(messageContext.agentContext, { agentMessage: proposalMessage, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, @@ -277,14 +287,16 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat credentialFormats, comment, autoAcceptCredential, - }: AcceptProposalOptions<[IndyCredentialFormat]> + }: AcceptProposalOptions<[CredentialFormatService]> ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalReceived) if (credentialFormats) this.assertOnlyIndyFormat(credentialFormats) - const proposalMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const proposalMessage = await didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1ProposeCredentialMessage, }) @@ -294,7 +306,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat // if the user provided other attributes in the credentialFormats array. credentialRecord.credentialAttributes = proposalMessage.credentialPreview?.attributes - const { attachment, previewAttributes } = await this.formatService.acceptProposal(agentContext, { + const { attachment, previewAttributes } = await this.indyCredentialFormat.acceptProposal(agentContext, { attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, credentialFormats, credentialRecord, @@ -324,7 +336,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential await this.updateState(agentContext, credentialRecord, CredentialState.OfferSent) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -348,14 +360,16 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat credentialRecord, comment, autoAcceptCredential, - }: NegotiateProposalOptions<[IndyCredentialFormat]> + }: NegotiateProposalOptions<[CredentialFormatService]> ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalReceived) if (credentialFormats) this.assertOnlyIndyFormat(credentialFormats) - const { attachment, previewAttributes } = await this.formatService.createOffer(agentContext, { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const { attachment, previewAttributes } = await this.indyCredentialFormat.createOffer(agentContext, { attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, credentialFormats, credentialRecord, @@ -379,7 +393,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential await this.updateState(agentContext, credentialRecord, CredentialState.OfferSent) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -390,7 +404,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat /** * Create a {@link OfferCredentialMessage} not bound to an existing credential exchange. - * To create an offer as response to an existing credential exchange, use {@link V1CredentialService#createOfferAsResponse}. + * To create an offer as response to an existing credential exchange, use {@link V1CredentialProtocol#createOfferAsResponse}. * * @param options The options containing config params for creating the credential offer * @returns Object containing offer message and associated credential record @@ -398,11 +412,19 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat */ public async createOffer( agentContext: AgentContext, - { credentialFormats, autoAcceptCredential, comment, connection }: CreateOfferOptions<[IndyCredentialFormat]> + { + credentialFormats, + autoAcceptCredential, + comment, + connection, + }: CreateOfferOptions<[CredentialFormatService]> ): Promise> { // Assert if (credentialFormats) this.assertOnlyIndyFormat(credentialFormats) + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + if (!credentialFormats.indy) { throw new AriesFrameworkError('Missing indy credential format data for v1 create offer') } @@ -419,7 +441,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat protocolVersion: 'v1', }) - const { attachment, previewAttributes } = await this.formatService.createOffer(agentContext, { + const { attachment, previewAttributes } = await this.indyCredentialFormat.createOffer(agentContext, { attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, credentialFormats, credentialRecord, @@ -440,14 +462,14 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat attachments: credentialFormats.indy.linkedAttachments?.map((linkedAttachments) => linkedAttachments.attachment), }) - await this.didCommMessageRepository.saveAgentMessage(agentContext, { + await didCommMessageRepository.saveAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, agentMessage: message, role: DidCommMessageRole.Sender, }) credentialRecord.credentialAttributes = previewAttributes - await this.credentialRepository.save(agentContext, credentialRecord) + await credentialRepository.save(agentContext, credentialRecord) this.emitStateChangedEvent(agentContext, credentialRecord, null) return { message, credentialRecord } @@ -466,9 +488,16 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat public async processOffer( messageContext: InboundMessageContext ): Promise { - const { message: offerMessage, connection } = messageContext + const { message: offerMessage, connection, agentContext } = messageContext + + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // TODO: with this method, we should update the credential protocol to use the ConnectionApi, so it + // only depends on the public api, rather than the internal API (this helps with breaking changes) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) - this.logger.debug(`Processing credential offer with id ${offerMessage.id}`) + agentContext.config.logger.debug(`Processing credential offer with id ${offerMessage.id}`) let credentialRecord = await this.findByThreadAndConnectionId( messageContext.agentContext, @@ -484,14 +513,11 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat } if (credentialRecord) { - const proposalCredentialMessage = await this.didCommMessageRepository.findAgentMessage( - messageContext.agentContext, - { - associatedRecordId: credentialRecord.id, - messageClass: V1ProposeCredentialMessage, - } - ) - const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const proposalCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: credentialRecord.id, + messageClass: V1ProposeCredentialMessage, + }) + const offerCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) @@ -499,17 +525,17 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + connectionService.assertConnectionOrServiceDecorator(messageContext, { previousReceivedMessage: offerCredentialMessage ?? undefined, previousSentMessage: proposalCredentialMessage ?? undefined, }) - await this.formatService.processOffer(messageContext.agentContext, { + await this.indyCredentialFormat.processOffer(messageContext.agentContext, { credentialRecord, attachment: offerAttachment, }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: offerMessage, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, @@ -527,20 +553,20 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat }) // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) + connectionService.assertConnectionOrServiceDecorator(messageContext) - await this.formatService.processOffer(messageContext.agentContext, { + await this.indyCredentialFormat.processOffer(messageContext.agentContext, { credentialRecord, attachment: offerAttachment, }) // Save in repository - await this.didCommMessageRepository.saveAgentMessage(messageContext.agentContext, { + await didCommMessageRepository.saveAgentMessage(messageContext.agentContext, { agentMessage: offerMessage, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, }) - await this.credentialRepository.save(messageContext.agentContext, credentialRecord) + await credentialRepository.save(messageContext.agentContext, credentialRecord) this.emitStateChangedEvent(messageContext.agentContext, credentialRecord, null) return credentialRecord @@ -556,13 +582,20 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat */ public async acceptOffer( agentContext: AgentContext, - { credentialRecord, credentialFormats, comment, autoAcceptCredential }: AcceptOfferOptions<[IndyCredentialFormat]> + { + credentialRecord, + credentialFormats, + comment, + autoAcceptCredential, + }: AcceptOfferOptions<[CredentialFormatService]> ): Promise> { // Assert credential credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferReceived) - const offerMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const offerMessage = await didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) @@ -574,7 +607,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat ) } - const { attachment } = await this.formatService.acceptOffer(agentContext, { + const { attachment } = await this.indyCredentialFormat.acceptOffer(agentContext, { credentialRecord, credentialFormats, attachId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, @@ -594,7 +627,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat isLinkedAttachment(attachment) ) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: requestMessage, associatedRecordId: credentialRecord.id, role: DidCommMessageRole.Sender, @@ -621,12 +654,15 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat credentialRecord, autoAcceptCredential, comment, - }: NegotiateOfferOptions<[IndyCredentialFormat]> + }: NegotiateOfferOptions<[CredentialFormatService]> ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferReceived) this.assertOnlyIndyFormat(credentialFormats) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + if (!credentialRecord.connectionId) { throw new AriesFrameworkError( `No connectionId found for credential record '${credentialRecord.id}'. Connection-less issuance does not support negotiation.` @@ -641,7 +677,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat // call create proposal for validation of the proposal and addition of linked attachments // As the format is different for v1 of the issue credential protocol we won't be using the attachment - const { previewAttributes, attachment } = await this.formatService.createProposal(agentContext, { + const { previewAttributes, attachment } = await this.indyCredentialFormat.createProposal(agentContext, { credentialFormats, credentialRecord, }) @@ -664,7 +700,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat message.setThread({ threadId: credentialRecord.threadId }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -701,22 +737,28 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat public async processRequest( messageContext: InboundMessageContext ): Promise { - const { message: requestMessage, connection } = messageContext + const { message: requestMessage, connection, agentContext } = messageContext + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // TODO: with this method, we should update the credential protocol to use the ConnectionApi, so it + // only depends on the public api, rather than the internal API (this helps with breaking changes) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) - this.logger.debug(`Processing credential request with id ${requestMessage.id}`) + agentContext.config.logger.debug(`Processing credential request with id ${requestMessage.id}`) const credentialRecord = await this.getByThreadAndConnectionId( messageContext.agentContext, requestMessage.threadId, connection?.id ) - this.logger.trace('Credential record found when processing credential request', credentialRecord) + agentContext.config.logger.trace('Credential record found when processing credential request', credentialRecord) - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const proposalMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1ProposeCredentialMessage, }) - const offerMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const offerMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) @@ -724,7 +766,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + connectionService.assertConnectionOrServiceDecorator(messageContext, { previousReceivedMessage: proposalMessage ?? undefined, previousSentMessage: offerMessage ?? undefined, }) @@ -737,12 +779,12 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat ) } - await this.formatService.processRequest(messageContext.agentContext, { + await this.indyCredentialFormat.processRequest(messageContext.agentContext, { credentialRecord, attachment: requestAttachment, }) - await this.didCommMessageRepository.saveAgentMessage(messageContext.agentContext, { + await didCommMessageRepository.saveAgentMessage(messageContext.agentContext, { agentMessage: requestMessage, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, @@ -761,17 +803,24 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat */ public async acceptRequest( agentContext: AgentContext, - { credentialRecord, credentialFormats, comment, autoAcceptCredential }: AcceptRequestOptions<[IndyCredentialFormat]> + { + credentialRecord, + credentialFormats, + comment, + autoAcceptCredential, + }: AcceptRequestOptions<[CredentialFormatService]> ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.RequestReceived) - const offerMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const offerMessage = await didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) - const requestMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1RequestCredentialMessage, }) @@ -785,7 +834,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat ) } - const { attachment: credentialsAttach } = await this.formatService.acceptRequest(agentContext, { + const { attachment: credentialsAttach } = await this.indyCredentialFormat.acceptRequest(agentContext, { credentialRecord, requestAttachment, offerAttachment, @@ -802,7 +851,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat issueMessage.setThread({ threadId: credentialRecord.threadId }) issueMessage.setPleaseAck() - await this.didCommMessageRepository.saveAgentMessage(agentContext, { + await didCommMessageRepository.saveAgentMessage(agentContext, { agentMessage: issueMessage, associatedRecordId: credentialRecord.id, role: DidCommMessageRole.Sender, @@ -824,9 +873,15 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat public async processCredential( messageContext: InboundMessageContext ): Promise { - const { message: issueMessage, connection } = messageContext + const { message: issueMessage, connection, agentContext } = messageContext + + agentContext.config.logger.debug(`Processing credential with id ${issueMessage.id}`) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) - this.logger.debug(`Processing credential with id ${issueMessage.id}`) + // TODO: with this method, we should update the credential protocol to use the ConnectionApi, so it + // only depends on the public api, rather than the internal API (this helps with breaking changes) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) const credentialRecord = await this.getByThreadAndConnectionId( messageContext.agentContext, @@ -834,11 +889,11 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat connection?.id ) - const requestCredentialMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const requestCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1RequestCredentialMessage, }) - const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const offerCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) @@ -846,7 +901,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.RequestSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + connectionService.assertConnectionOrServiceDecorator(messageContext, { previousReceivedMessage: offerCredentialMessage ?? undefined, previousSentMessage: requestCredentialMessage ?? undefined, }) @@ -856,12 +911,18 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat throw new AriesFrameworkError('Missing indy credential attachment in processCredential') } - await this.formatService.processCredential(messageContext.agentContext, { + const requestAttachment = requestCredentialMessage?.getRequestAttachmentById(INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID) + if (!requestAttachment) { + throw new AriesFrameworkError('Missing indy credential request attachment in processCredential') + } + + await this.indyCredentialFormat.processCredential(messageContext.agentContext, { attachment: issueAttachment, credentialRecord, + requestAttachment, }) - await this.didCommMessageRepository.saveAgentMessage(messageContext.agentContext, { + await didCommMessageRepository.saveAgentMessage(messageContext.agentContext, { agentMessage: issueMessage, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, @@ -907,9 +968,15 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat public async processAck( messageContext: InboundMessageContext ): Promise { - const { message: ackMessage, connection } = messageContext + const { message: ackMessage, connection, agentContext } = messageContext + + agentContext.config.logger.debug(`Processing credential ack with id ${ackMessage.id}`) - this.logger.debug(`Processing credential ack with id ${ackMessage.id}`) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // TODO: with this method, we should update the credential protocol to use the ConnectionApi, so it + // only depends on the public api, rather than the internal API (this helps with breaking changes) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) const credentialRecord = await this.getByThreadAndConnectionId( messageContext.agentContext, @@ -917,11 +984,11 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat connection?.id ) - const requestCredentialMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + const requestCredentialMessage = await didCommMessageRepository.getAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1RequestCredentialMessage, }) - const issueCredentialMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + const issueCredentialMessage = await didCommMessageRepository.getAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1IssueCredentialMessage, }) @@ -929,7 +996,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.CredentialIssued) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + connectionService.assertConnectionOrServiceDecorator(messageContext, { previousReceivedMessage: requestCredentialMessage, previousSentMessage: issueCredentialMessage, }) @@ -965,9 +1032,12 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat } ) { const { credentialRecord, proposalMessage } = options + + const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) + const autoAccept = composeAutoAccept( credentialRecord.autoAcceptCredential, - this.credentialsModuleConfig.autoAcceptCredentials + credentialsModuleConfig.autoAcceptCredentials ) // Handle always / never cases @@ -1001,9 +1071,12 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat } ) { const { credentialRecord, offerMessage } = options + + const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) + const autoAccept = composeAutoAccept( credentialRecord.autoAcceptCredential, - this.credentialsModuleConfig.autoAcceptCredentials + credentialsModuleConfig.autoAcceptCredentials ) // Handle always / never cases @@ -1037,9 +1110,12 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat } ) { const { credentialRecord, requestMessage } = options + + const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) + const autoAccept = composeAutoAccept( credentialRecord.autoAcceptCredential, - this.credentialsModuleConfig.autoAcceptCredentials + credentialsModuleConfig.autoAcceptCredentials ) // Handle always / never cases @@ -1054,7 +1130,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat if (!offerAttachment || !requestAttachment) return false - return this.formatService.shouldAutoRespondToRequest(agentContext, { + return this.indyCredentialFormat.shouldAutoRespondToRequest(agentContext, { credentialRecord, offerAttachment, requestAttachment, @@ -1069,9 +1145,12 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat } ) { const { credentialRecord, credentialMessage } = options + + const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) + const autoAccept = composeAutoAccept( credentialRecord.autoAcceptCredential, - this.credentialsModuleConfig.autoAcceptCredentials + credentialsModuleConfig.autoAcceptCredentials ) // Handle always / never cases @@ -1089,7 +1168,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat const offerAttachment = offerMessage?.getOfferAttachmentById(INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) - return this.formatService.shouldAutoRespondToCredential(agentContext, { + return this.indyCredentialFormat.shouldAutoRespondToCredential(agentContext, { credentialRecord, credentialAttachment, requestAttachment, @@ -1098,28 +1177,36 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat } public async findProposalMessage(agentContext: AgentContext, credentialExchangeId: string) { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + return await didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: credentialExchangeId, messageClass: V1ProposeCredentialMessage, }) } public async findOfferMessage(agentContext: AgentContext, credentialExchangeId: string) { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + return await didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: credentialExchangeId, messageClass: V1OfferCredentialMessage, }) } public async findRequestMessage(agentContext: AgentContext, credentialExchangeId: string) { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + return await didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: credentialExchangeId, messageClass: V1RequestCredentialMessage, }) } public async findCredentialMessage(agentContext: AgentContext, credentialExchangeId: string) { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + return await didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: credentialExchangeId, messageClass: V1IssueCredentialMessage, }) @@ -1128,7 +1215,7 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat public async getFormatData( agentContext: AgentContext, credentialExchangeId: string - ): Promise> { + ): Promise]>>> { // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. const [proposalMessage, offerMessage, requestMessage, credentialMessage] = await Promise.all([ this.findProposalMessage(agentContext, credentialExchangeId), @@ -1171,21 +1258,6 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat } } - protected registerMessageHandlers() { - this.dispatcher.registerMessageHandler(new V1ProposeCredentialHandler(this, this.logger)) - this.dispatcher.registerMessageHandler( - new V1OfferCredentialHandler(this, this.routingService, this.didCommMessageRepository, this.logger) - ) - this.dispatcher.registerMessageHandler( - new V1RequestCredentialHandler(this, this.didCommMessageRepository, this.logger) - ) - this.dispatcher.registerMessageHandler( - new V1IssueCredentialHandler(this, this.didCommMessageRepository, this.logger) - ) - this.dispatcher.registerMessageHandler(new V1CredentialAckHandler(this)) - this.dispatcher.registerMessageHandler(new V1CredentialProblemReportHandler(this)) - } - private rfc0592ProposalFromV1ProposeMessage(proposalMessage: V1ProposeCredentialMessage) { const indyCredentialProposal = new IndyCredPropose({ credentialDefinitionId: proposalMessage.credentialDefinitionId, @@ -1209,4 +1281,14 @@ export class V1CredentialService extends CredentialService<[IndyCredentialFormat throw new AriesFrameworkError('Only indy credential format is supported for issue credential v1 protocol') } } + + public getFormatServiceForRecordType(credentialRecordType: string) { + if (credentialRecordType !== this.indyCredentialFormat.credentialRecordType) { + throw new AriesFrameworkError( + `Unsupported credential record type ${credentialRecordType} for v1 issue credential protocol (need ${this.indyCredentialFormat.credentialRecordType})` + ) + } + + return this.indyCredentialFormat + } } diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts similarity index 90% rename from packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts rename to packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts index 6c627e0fb6..f6ba0a6c22 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts @@ -24,7 +24,6 @@ import { DidExchangeState } from '../../../../connections' import { ConnectionService } from '../../../../connections/services/ConnectionService' import { RoutingService } from '../../../../routing/services/RoutingService' import { CredentialEventTypes } from '../../../CredentialEvents' -import { CredentialsModuleConfig } from '../../../CredentialsModuleConfig' import { credDef, credReq } from '../../../__tests__/fixtures' import { CredentialProblemReportReason } from '../../../errors/CredentialProblemReportReason' import { IndyCredentialFormatService } from '../../../formats/indy/IndyCredentialFormatService' @@ -35,7 +34,7 @@ import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { CredentialMetadataKeys } from '../../../repository/CredentialMetadataTypes' import { CredentialRepository } from '../../../repository/CredentialRepository' -import { V1CredentialService } from '../V1CredentialService' +import { V1CredentialProtocol } from '../V1CredentialProtocol' import { INDY_CREDENTIAL_ATTACHMENT_ID, INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, @@ -221,18 +220,29 @@ const mockCredentialRecord = ({ return credentialRecord } -describe('V1CredentialService', () => { +describe('V1CredentialProtocol', () => { let eventEmitter: EventEmitter let agentConfig: AgentConfig let agentContext: AgentContext - let credentialService: V1CredentialService + let credentialProtocol: V1CredentialProtocol beforeEach(async () => { // real objects - agentConfig = getAgentConfig('V1CredentialServiceCredTest') - agentContext = getAgentContext() + agentConfig = getAgentConfig('V1CredentialProtocolCredTest') eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) + agentContext = getAgentContext({ + registerInstances: [ + [CredentialRepository, credentialRepository], + [DidCommMessageRepository, didCommMessageRepository], + [RoutingService, routingService], + [Dispatcher, dispatcher], + [ConnectionService, connectionService], + [EventEmitter, eventEmitter], + ], + agentConfig, + }) + // mock function implementations mockFunction(connectionService.getById).mockResolvedValue(connection) mockFunction(didCommMessageRepository.findAgentMessage).mockImplementation(getAgentMessageMock) @@ -243,17 +253,7 @@ describe('V1CredentialService', () => { didCommMessageRecord, ]) - credentialService = new V1CredentialService( - connectionService, - didCommMessageRepository, - agentConfig.logger, - routingService, - dispatcher, - eventEmitter, - credentialRepository, - indyCredentialFormatService, - new CredentialsModuleConfig() - ) + credentialProtocol = new V1CredentialProtocol({ indyCredentialFormat: indyCredentialFormatService }) }) afterEach(() => { @@ -285,7 +285,7 @@ describe('V1CredentialService', () => { }) // when - const { message } = await credentialService.acceptOffer(agentContext, { + const { message } = await credentialProtocol.acceptOffer(agentContext, { comment: 'hello', autoAcceptCredential: AutoAcceptCredential.Never, credentialRecord, @@ -330,7 +330,7 @@ describe('V1CredentialService', () => { state: CredentialState.OfferReceived, }) - const updateStateSpy = jest.spyOn(credentialService, 'updateState') + const updateStateSpy = jest.spyOn(credentialProtocol, 'updateState') // mock resolved format call mockFunction(indyCredentialFormatService.acceptOffer).mockResolvedValue({ @@ -342,7 +342,7 @@ describe('V1CredentialService', () => { }) // when - await credentialService.acceptOffer(agentContext, { + await credentialProtocol.acceptOffer(agentContext, { credentialRecord, }) @@ -356,7 +356,7 @@ describe('V1CredentialService', () => { await Promise.all( invalidCredentialStates.map(async (state) => { await expect( - credentialService.acceptOffer(agentContext, { credentialRecord: mockCredentialRecord({ state }) }) + credentialProtocol.acceptOffer(agentContext, { credentialRecord: mockCredentialRecord({ state }) }) ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) }) ) @@ -387,7 +387,7 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) // when - const returnedCredentialRecord = await credentialService.processRequest(messageContext) + const returnedCredentialRecord = await credentialProtocol.processRequest(messageContext) // then expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { @@ -404,7 +404,7 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) // mock offer so that the request works - const returnedCredentialRecord = await credentialService.processRequest(messageContext) + const returnedCredentialRecord = await credentialProtocol.processRequest(messageContext) // then expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { @@ -422,7 +422,7 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getSingleByQuery).mockReturnValue( Promise.resolve(mockCredentialRecord({ state })) ) - await expect(credentialService.processRequest(messageContext)).rejects.toThrowError( + await expect(credentialProtocol.processRequest(messageContext)).rejects.toThrowError( `Credential record is in invalid state ${state}. Valid states are: ${validState}.` ) }) @@ -448,7 +448,7 @@ describe('V1CredentialService', () => { }) // when - await credentialService.acceptRequest(agentContext, { credentialRecord }) + await credentialProtocol.acceptRequest(agentContext, { credentialRecord }) // then expect(credentialRepository.update).toHaveBeenCalledWith( @@ -481,7 +481,7 @@ describe('V1CredentialService', () => { eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // when - await credentialService.acceptRequest(agentContext, { credentialRecord }) + await credentialProtocol.acceptRequest(agentContext, { credentialRecord }) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -518,7 +518,7 @@ describe('V1CredentialService', () => { }) // when - const { message } = await credentialService.acceptRequest(agentContext, { credentialRecord, comment }) + const { message } = await credentialProtocol.acceptRequest(agentContext, { credentialRecord, comment }) // then expect(message.toJSON()).toMatchObject({ @@ -557,7 +557,7 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getSingleByQuery).mockResolvedValue(credentialRecord) // when - await credentialService.processCredential(messageContext) + await credentialProtocol.processCredential(messageContext) // then expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { @@ -574,6 +574,7 @@ describe('V1CredentialService', () => { expect(indyCredentialFormatService.processCredential).toHaveBeenNthCalledWith(1, agentContext, { attachment: credentialAttachment, credentialRecord, + requestAttachment: expect.any(Attachment), }) }) }) @@ -595,7 +596,7 @@ describe('V1CredentialService', () => { const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') // when - await credentialService.acceptCredential(agentContext, { credentialRecord: credential }) + await credentialProtocol.acceptCredential(agentContext, { credentialRecord: credential }) // then expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) @@ -610,7 +611,7 @@ describe('V1CredentialService', () => { eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // when - await credentialService.acceptCredential(agentContext, { credentialRecord: credential }) + await credentialProtocol.acceptCredential(agentContext, { credentialRecord: credential }) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -632,7 +633,7 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) // when - const { message: ackMessage } = await credentialService.acceptCredential(agentContext, { + const { message: ackMessage } = await credentialProtocol.acceptCredential(agentContext, { credentialRecord: credential, }) @@ -652,7 +653,7 @@ describe('V1CredentialService', () => { await Promise.all( invalidCredentialStates.map(async (state) => { await expect( - credentialService.acceptCredential(agentContext, { + credentialProtocol.acceptCredential(agentContext, { credentialRecord: mockCredentialRecord({ state, threadId, @@ -688,7 +689,7 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) // when - const returnedCredentialRecord = await credentialService.processAck(messageContext) + const returnedCredentialRecord = await credentialProtocol.processAck(messageContext) // then const expectedCredentialRecord = { @@ -723,7 +724,7 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) // when - const credentialProblemReportMessage = credentialService.createProblemReport(agentContext, { message }) + const credentialProblemReportMessage = credentialProtocol.createProblemReport(agentContext, { message }) credentialProblemReportMessage.setThread({ threadId }) // then @@ -767,7 +768,7 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) // when - const returnedCredentialRecord = await credentialService.processProblemReport(messageContext) + const returnedCredentialRecord = await credentialProtocol.processProblemReport(messageContext) // then const expectedCredentialRecord = { @@ -788,7 +789,7 @@ describe('V1CredentialService', () => { it('getById should return value from credentialRepository.getById', async () => { const expected = mockCredentialRecord() mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.getById(agentContext, expected.id) + const result = await credentialProtocol.getById(agentContext, expected.id) expect(credentialRepository.getById).toBeCalledWith(agentContext, expected.id) expect(result).toBe(expected) @@ -797,7 +798,7 @@ describe('V1CredentialService', () => { it('getById should return value from credentialRepository.getSingleByQuery', async () => { const expected = mockCredentialRecord() mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.getByThreadAndConnectionId(agentContext, 'threadId', 'connectionId') + const result = await credentialProtocol.getByThreadAndConnectionId(agentContext, 'threadId', 'connectionId') expect(credentialRepository.getSingleByQuery).toBeCalledWith(agentContext, { threadId: 'threadId', connectionId: 'connectionId', @@ -809,7 +810,7 @@ describe('V1CredentialService', () => { it('findById should return value from credentialRepository.findById', async () => { const expected = mockCredentialRecord() mockFunction(credentialRepository.findById).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.findById(agentContext, expected.id) + const result = await credentialProtocol.findById(agentContext, expected.id) expect(credentialRepository.findById).toBeCalledWith(agentContext, expected.id) expect(result).toBe(expected) @@ -819,7 +820,7 @@ describe('V1CredentialService', () => { const expected = [mockCredentialRecord(), mockCredentialRecord()] mockFunction(credentialRepository.getAll).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.getAll(agentContext) + const result = await credentialProtocol.getAll(agentContext) expect(credentialRepository.getAll).toBeCalledWith(agentContext) expect(result).toEqual(expect.arrayContaining(expected)) @@ -829,7 +830,7 @@ describe('V1CredentialService', () => { const expected = [mockCredentialRecord(), mockCredentialRecord()] mockFunction(credentialRepository.findByQuery).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.findAllByQuery(agentContext, { state: CredentialState.OfferSent }) + const result = await credentialProtocol.findAllByQuery(agentContext, { state: CredentialState.OfferSent }) expect(credentialRepository.findByQuery).toBeCalledWith(agentContext, { state: CredentialState.OfferSent }) expect(result).toEqual(expect.arrayContaining(expected)) @@ -842,7 +843,7 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credentialRecord)) const repositoryDeleteSpy = jest.spyOn(credentialRepository, 'delete') - await credentialService.delete(agentContext, credentialRecord) + await credentialProtocol.delete(agentContext, credentialRecord) expect(repositoryDeleteSpy).toHaveBeenNthCalledWith(1, agentContext, credentialRecord) }) @@ -852,7 +853,7 @@ describe('V1CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(agentContext, credentialRecord, { + await credentialProtocol.delete(agentContext, credentialRecord, { deleteAssociatedCredentials: true, deleteAssociatedDidCommMessages: false, }) @@ -870,7 +871,7 @@ describe('V1CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(agentContext, credentialRecord, { + await credentialProtocol.delete(agentContext, credentialRecord, { deleteAssociatedCredentials: false, deleteAssociatedDidCommMessages: false, }) @@ -884,7 +885,7 @@ describe('V1CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(agentContext, credentialRecord) + await credentialProtocol.delete(agentContext, credentialRecord) expect(deleteCredentialMock).toHaveBeenNthCalledWith( 1, @@ -898,7 +899,7 @@ describe('V1CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(agentContext, credentialRecord) + await credentialProtocol.delete(agentContext, credentialRecord) expect(deleteCredentialMock).toHaveBeenNthCalledWith( 1, @@ -925,7 +926,7 @@ describe('V1CredentialService', () => { const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') // when - await credentialService.declineOffer(agentContext, credential) + await credentialProtocol.declineOffer(agentContext, credential) // then const expectedCredentialState = { @@ -947,7 +948,7 @@ describe('V1CredentialService', () => { mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) // when - await credentialService.declineOffer(agentContext, credential) + await credentialProtocol.declineOffer(agentContext, credential) // then expect(eventListenerMock).toHaveBeenCalledTimes(1) @@ -972,7 +973,7 @@ describe('V1CredentialService', () => { await Promise.all( invalidCredentialStates.map(async (state) => { await expect( - credentialService.declineOffer(agentContext, mockCredentialRecord({ state, tags: { threadId } })) + credentialProtocol.declineOffer(agentContext, mockCredentialRecord({ state, tags: { threadId } })) ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) }) ) diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts similarity index 88% rename from packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts rename to packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts index 3d22169e93..78841df7fe 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialServiceProposeOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts @@ -1,6 +1,5 @@ import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { CreateOfferOptions, CreateProposalOptions } from '../../../CredentialServiceOptions' -import type { IndyCredentialFormat } from '../../../formats/indy/IndyCredentialFormat' +import type { CreateOfferOptions, CreateProposalOptions } from '../../../CredentialProtocolOptions' import { Subject } from 'rxjs' @@ -16,14 +15,13 @@ import { ConnectionService } from '../../../../connections/services/ConnectionSe import { IndyLedgerService } from '../../../../ledger/services' import { RoutingService } from '../../../../routing/services/RoutingService' import { CredentialEventTypes } from '../../../CredentialEvents' -import { CredentialsModuleConfig } from '../../../CredentialsModuleConfig' import { schema, credDef } from '../../../__tests__/fixtures' import { IndyCredentialFormatService } from '../../../formats' import { CredentialFormatSpec } from '../../../models' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { CredentialRepository } from '../../../repository/CredentialRepository' -import { V1CredentialService } from '../V1CredentialService' +import { V1CredentialProtocol } from '../V1CredentialProtocol' import { INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, V1OfferCredentialMessage } from '../messages' import { V1CredentialPreview } from '../messages/V1CredentialPreview' @@ -53,8 +51,20 @@ const indyCredentialFormatService = new IndyCredentialFormatServiceMock() const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() -const agentConfig = getAgentConfig('V1CredentialServiceProposeOfferTest') -const agentContext = getAgentContext() +const agentConfig = getAgentConfig('V1CredentialProtocolProposeOfferTest') +const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) + +const agentContext = getAgentContext({ + registerInstances: [ + [CredentialRepository, credentialRepository], + [DidCommMessageRepository, didCommMessageRepository], + [RoutingService, routingService], + [Dispatcher, dispatcher], + [ConnectionService, connectionService], + [EventEmitter, eventEmitter], + ], + agentConfig, +}) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -92,32 +102,18 @@ const proposalAttachment = new Attachment({ }), }) -describe('V1CredentialServiceProposeOffer', () => { - let eventEmitter: EventEmitter - - let credentialService: V1CredentialService +describe('V1CredentialProtocolProposeOffer', () => { + let credentialProtocol: V1CredentialProtocol beforeEach(async () => { - eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) - // mock function implementations mockFunction(connectionService.getById).mockResolvedValue(connection) mockFunction(indyLedgerService.getCredentialDefinition).mockResolvedValue(credDef) mockFunction(indyLedgerService.getSchema).mockResolvedValue(schema) - // mockFunction(didCommMessageRepository.findAgentMessage).mockImplementation(getAgentMessageMock) - // mockFunction(didCommMessageRepository.getAgentMessage).mockImplementation(getAgentMessageMock) - - credentialService = new V1CredentialService( - connectionService, - didCommMessageRepository, - agentConfig.logger, - routingService, - dispatcher, - eventEmitter, - credentialRepository, - indyCredentialFormatService, - new CredentialsModuleConfig() - ) + + credentialProtocol = new V1CredentialProtocol({ + indyCredentialFormat: indyCredentialFormatService, + }) }) afterEach(() => { @@ -125,7 +121,7 @@ describe('V1CredentialServiceProposeOffer', () => { }) describe('createProposal', () => { - const proposeOptions: CreateProposalOptions<[IndyCredentialFormat]> = { + const proposeOptions: CreateProposalOptions<[IndyCredentialFormatService]> = { connection, credentialFormats: { indy: { @@ -140,7 +136,6 @@ describe('V1CredentialServiceProposeOffer', () => { }, comment: 'v1 propose credential test', } - test(`creates credential record in ${CredentialState.OfferSent} state with offer, thread id`, async () => { const repositorySaveSpy = jest.spyOn(credentialRepository, 'save') @@ -152,7 +147,7 @@ describe('V1CredentialServiceProposeOffer', () => { }), }) - await credentialService.createProposal(agentContext, proposeOptions) + await credentialProtocol.createProposal(agentContext, proposeOptions) // then expect(repositorySaveSpy).toHaveBeenNthCalledWith( @@ -180,7 +175,7 @@ describe('V1CredentialServiceProposeOffer', () => { }), }) - await credentialService.createProposal(agentContext, proposeOptions) + await credentialProtocol.createProposal(agentContext, proposeOptions) expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', @@ -206,7 +201,7 @@ describe('V1CredentialServiceProposeOffer', () => { previewAttributes: credentialPreview.attributes, }) - const { message } = await credentialService.createProposal(agentContext, proposeOptions) + const { message } = await credentialProtocol.createProposal(agentContext, proposeOptions) expect(message.toJSON()).toMatchObject({ '@id': expect.any(String), @@ -238,7 +233,7 @@ describe('V1CredentialServiceProposeOffer', () => { }) describe('createOffer', () => { - const offerOptions: CreateOfferOptions<[IndyCredentialFormat]> = { + const offerOptions: CreateOfferOptions<[IndyCredentialFormatService]> = { comment: 'some comment', connection, credentialFormats: { @@ -261,7 +256,7 @@ describe('V1CredentialServiceProposeOffer', () => { const repositorySaveSpy = jest.spyOn(credentialRepository, 'save') - await credentialService.createOffer(agentContext, offerOptions) + await credentialProtocol.createOffer(agentContext, offerOptions) // then expect(repositorySaveSpy).toHaveBeenCalledTimes(1) @@ -290,7 +285,7 @@ describe('V1CredentialServiceProposeOffer', () => { previewAttributes: credentialPreview.attributes, }) - await credentialService.createOffer(agentContext, offerOptions) + await credentialProtocol.createOffer(agentContext, offerOptions) expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', @@ -315,7 +310,7 @@ describe('V1CredentialServiceProposeOffer', () => { }), }) - await expect(credentialService.createOffer(agentContext, offerOptions)).rejects.toThrowError( + await expect(credentialProtocol.createOffer(agentContext, offerOptions)).rejects.toThrowError( 'Missing required credential preview from indy format service' ) }) @@ -330,7 +325,7 @@ describe('V1CredentialServiceProposeOffer', () => { previewAttributes: credentialPreview.attributes, }) - const { message: credentialOffer } = await credentialService.createOffer(agentContext, offerOptions) + const { message: credentialOffer } = await credentialProtocol.createOffer(agentContext, offerOptions) expect(credentialOffer.toJSON()).toMatchObject({ '@id': expect.any(String), '@type': 'https://didcomm.org/issue-credential/1.0/offer-credential', @@ -365,7 +360,7 @@ describe('V1CredentialServiceProposeOffer', () => { test(`creates and return credential record in ${CredentialState.OfferReceived} state with offer, thread ID`, async () => { // when - await credentialService.processOffer(messageContext) + await credentialProtocol.processOffer(messageContext) // then expect(credentialRepository.save).toHaveBeenNthCalledWith( @@ -388,7 +383,7 @@ describe('V1CredentialServiceProposeOffer', () => { eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // when - await credentialService.processOffer(messageContext) + await credentialProtocol.processOffer(messageContext) // then expect(eventListenerMock).toHaveBeenCalledWith({ diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts index b51d3cb45c..a3fff6612e 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts @@ -1,10 +1,6 @@ import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { - AcceptCredentialOfferOptions, - AcceptCredentialRequestOptions, - CreateOfferOptions, -} from '../../../CredentialsApiOptions' +import type { AcceptCredentialOfferOptions, AcceptCredentialRequestOptions } from '../../../CredentialsApiOptions' import { ReplaySubject, Subject } from 'rxjs' @@ -81,7 +77,8 @@ describe('V1 Connectionless Credentials', () => { test('Faber starts with connection-less credential offer to Alice', async () => { testLogger.test('Faber sends credential offer to Alice') - const offerOptions: CreateOfferOptions = { + // eslint-disable-next-line prefer-const + let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer({ comment: 'V1 Out of Band offer', credentialFormats: { indy: { @@ -90,9 +87,7 @@ describe('V1 Connectionless Credentials', () => { }, }, protocolVersion: 'v1', - } - // eslint-disable-next-line prefer-const - let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer(offerOptions) + }) const { message: offerMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ recordId: faberCredentialRecord.id, @@ -181,7 +176,8 @@ describe('V1 Connectionless Credentials', () => { }) test('Faber starts with connection-less credential offer to Alice with auto-accept enabled', async () => { - const offerOptions: CreateOfferOptions = { + // eslint-disable-next-line prefer-const + let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer({ comment: 'V1 Out of Band offer', credentialFormats: { indy: { @@ -191,9 +187,7 @@ describe('V1 Connectionless Credentials', () => { }, protocolVersion: 'v1', autoAcceptCredential: AutoAcceptCredential.ContentApproved, - } - // eslint-disable-next-line prefer-const - let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer(offerOptions) + }) const { message: offerMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ recordId: faberCredentialRecord.id, diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts index b649857234..e34a95d2bb 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts @@ -1,17 +1,17 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { V1CredentialService } from '../V1CredentialService' +import type { V1CredentialProtocol } from '../V1CredentialProtocol' import { V1CredentialAckMessage } from '../messages' export class V1CredentialAckHandler implements MessageHandler { - private credentialService: V1CredentialService + private credentialProtocol: V1CredentialProtocol public supportedMessages = [V1CredentialAckMessage] - public constructor(credentialService: V1CredentialService) { - this.credentialService = credentialService + public constructor(credentialProtocol: V1CredentialProtocol) { + this.credentialProtocol = credentialProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - await this.credentialService.processAck(messageContext) + await this.credentialProtocol.processAck(messageContext) } } diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts index fe515b29ca..06769cf1bb 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts @@ -1,17 +1,17 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { V1CredentialService } from '../V1CredentialService' +import type { V1CredentialProtocol } from '../V1CredentialProtocol' import { V1CredentialProblemReportMessage } from '../messages' export class V1CredentialProblemReportHandler implements MessageHandler { - private credentialService: V1CredentialService + private credentialProtocol: V1CredentialProtocol public supportedMessages = [V1CredentialProblemReportMessage] - public constructor(credentialService: V1CredentialService) { - this.credentialService = credentialService + public constructor(credentialProtocol: V1CredentialProtocol) { + this.credentialProtocol = credentialProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - await this.credentialService.processProblemReport(messageContext) + await this.credentialProtocol.processProblemReport(messageContext) } } diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts index 505832a7dd..82e7dea44a 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts @@ -1,32 +1,24 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { Logger } from '../../../../../logger' -import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import type { V1CredentialService } from '../V1CredentialService' +import type { V1CredentialProtocol } from '../V1CredentialProtocol' import { OutboundMessageContext } from '../../../../../agent/models' +import { DidCommMessageRepository } from '../../../../../storage' import { V1IssueCredentialMessage, V1RequestCredentialMessage } from '../messages' export class V1IssueCredentialHandler implements MessageHandler { - private credentialService: V1CredentialService - private didCommMessageRepository: DidCommMessageRepository - private logger: Logger + private credentialProtocol: V1CredentialProtocol + public supportedMessages = [V1IssueCredentialMessage] - public constructor( - credentialService: V1CredentialService, - didCommMessageRepository: DidCommMessageRepository, - logger: Logger - ) { - this.credentialService = credentialService - this.didCommMessageRepository = didCommMessageRepository - this.logger = logger + public constructor(credentialProtocol: V1CredentialProtocol) { + this.credentialProtocol = credentialProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - const credentialRecord = await this.credentialService.processCredential(messageContext) + const credentialRecord = await this.credentialProtocol.processCredential(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToCredential(messageContext.agentContext, { + const shouldAutoRespond = await this.credentialProtocol.shouldAutoRespondToCredential(messageContext.agentContext, { credentialRecord, credentialMessage: messageContext.message, }) @@ -40,12 +32,13 @@ export class V1IssueCredentialHandler implements MessageHandler { credentialRecord: CredentialExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - this.logger.info(`Automatically sending acknowledgement with autoAccept`) - const { message } = await this.credentialService.acceptCredential(messageContext.agentContext, { + messageContext.agentContext.config.logger.info(`Automatically sending acknowledgement with autoAccept`) + const { message } = await this.credentialProtocol.acceptCredential(messageContext.agentContext, { credentialRecord, }) - const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) + const requestMessage = await didCommMessageRepository.getAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1RequestCredentialMessage, }) @@ -69,6 +62,6 @@ export class V1IssueCredentialHandler implements MessageHandler { }) } - this.logger.error(`Could not automatically create credential ack`) + messageContext.agentContext.config.logger.error(`Could not automatically create credential ack`) } } diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts index 2f2890d4bd..5e7731b72f 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts @@ -1,38 +1,25 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { Logger } from '../../../../../logger' -import type { DidCommMessageRepository } from '../../../../../storage' -import type { RoutingService } from '../../../../routing/services/RoutingService' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import type { V1CredentialService } from '../V1CredentialService' +import type { V1CredentialProtocol } from '../V1CredentialProtocol' import { OutboundMessageContext } from '../../../../../agent/models' import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' -import { DidCommMessageRole } from '../../../../../storage' +import { DidCommMessageRepository, DidCommMessageRole } from '../../../../../storage' +import { RoutingService } from '../../../../routing/services/RoutingService' import { V1OfferCredentialMessage } from '../messages' export class V1OfferCredentialHandler implements MessageHandler { - private credentialService: V1CredentialService - private routingService: RoutingService - private didCommMessageRepository: DidCommMessageRepository - private logger: Logger + private credentialProtocol: V1CredentialProtocol public supportedMessages = [V1OfferCredentialMessage] - public constructor( - credentialService: V1CredentialService, - routingService: RoutingService, - didCommMessageRepository: DidCommMessageRepository, - logger: Logger - ) { - this.credentialService = credentialService - this.routingService = routingService - this.didCommMessageRepository = didCommMessageRepository - this.logger = logger + public constructor(credentialProtocol: V1CredentialProtocol) { + this.credentialProtocol = credentialProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - const credentialRecord = await this.credentialService.processOffer(messageContext) + const credentialRecord = await this.credentialProtocol.processOffer(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToOffer(messageContext.agentContext, { + const shouldAutoRespond = await this.credentialProtocol.shouldAutoRespondToOffer(messageContext.agentContext, { credentialRecord, offerMessage: messageContext.message, }) @@ -46,9 +33,9 @@ export class V1OfferCredentialHandler implements MessageHandler { credentialRecord: CredentialExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - this.logger.info(`Automatically sending request with autoAccept`) + messageContext.agentContext.config.logger.info(`Automatically sending request with autoAccept`) if (messageContext.connection) { - const { message } = await this.credentialService.acceptOffer(messageContext.agentContext, { credentialRecord }) + const { message } = await this.credentialProtocol.acceptOffer(messageContext.agentContext, { credentialRecord }) return new OutboundMessageContext(message, { agentContext: messageContext.agentContext, @@ -56,7 +43,8 @@ export class V1OfferCredentialHandler implements MessageHandler { associatedRecord: credentialRecord, }) } else if (messageContext.message.service) { - const routing = await this.routingService.getRouting(messageContext.agentContext) + const routingService = messageContext.agentContext.dependencyManager.resolve(RoutingService) + const routing = await routingService.getRouting(messageContext.agentContext) const ourService = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], @@ -64,7 +52,7 @@ export class V1OfferCredentialHandler implements MessageHandler { }) const recipientService = messageContext.message.service - const { message } = await this.credentialService.acceptOffer(messageContext.agentContext, { + const { message } = await this.credentialProtocol.acceptOffer(messageContext.agentContext, { credentialRecord, credentialFormats: { indy: { @@ -75,7 +63,9 @@ export class V1OfferCredentialHandler implements MessageHandler { // Set and save ~service decorator to record (to remember our verkey) message.service = ourService - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) + await didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -90,6 +80,6 @@ export class V1OfferCredentialHandler implements MessageHandler { }) } - this.logger.error(`Could not automatically create credential request`) + messageContext.agentContext.config.logger.error(`Could not automatically create credential request`) } } diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts index 6867832638..998d3940fc 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts @@ -1,25 +1,22 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { Logger } from '../../../../../logger' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import type { V1CredentialService } from '../V1CredentialService' +import type { V1CredentialProtocol } from '../V1CredentialProtocol' import { OutboundMessageContext } from '../../../../../agent/models' import { V1ProposeCredentialMessage } from '../messages' export class V1ProposeCredentialHandler implements MessageHandler { - private credentialService: V1CredentialService - private logger: Logger + private credentialProtocol: V1CredentialProtocol public supportedMessages = [V1ProposeCredentialMessage] - public constructor(credentialService: V1CredentialService, logger: Logger) { - this.credentialService = credentialService - this.logger = logger + public constructor(credentialProtocol: V1CredentialProtocol) { + this.credentialProtocol = credentialProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - const credentialRecord = await this.credentialService.processProposal(messageContext) + const credentialRecord = await this.credentialProtocol.processProposal(messageContext) - const shouldAutoAcceptProposal = await this.credentialService.shouldAutoRespondToProposal( + const shouldAutoAcceptProposal = await this.credentialProtocol.shouldAutoRespondToProposal( messageContext.agentContext, { credentialRecord, @@ -36,14 +33,14 @@ export class V1ProposeCredentialHandler implements MessageHandler { credentialRecord: CredentialExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - this.logger.info(`Automatically sending offer with autoAccept`) + messageContext.agentContext.config.logger.info(`Automatically sending offer with autoAccept`) if (!messageContext.connection) { - this.logger.error('No connection on the messageContext, aborting auto accept') + messageContext.agentContext.config.logger.error('No connection on the messageContext, aborting auto accept') return } - const { message } = await this.credentialService.acceptProposal(messageContext.agentContext, { + const { message } = await this.credentialProtocol.acceptProposal(messageContext.agentContext, { credentialRecord, }) diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts index 00e475102f..2b831566b2 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts @@ -1,33 +1,23 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { Logger } from '../../../../../logger' -import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import type { V1CredentialService } from '../V1CredentialService' +import type { V1CredentialProtocol } from '../V1CredentialProtocol' import { OutboundMessageContext } from '../../../../../agent/models' -import { DidCommMessageRole } from '../../../../../storage' +import { DidCommMessageRepository, DidCommMessageRole } from '../../../../../storage' import { V1RequestCredentialMessage } from '../messages' export class V1RequestCredentialHandler implements MessageHandler { - private credentialService: V1CredentialService - private didCommMessageRepository: DidCommMessageRepository - private logger: Logger + private credentialProtocol: V1CredentialProtocol public supportedMessages = [V1RequestCredentialMessage] - public constructor( - credentialService: V1CredentialService, - didCommMessageRepository: DidCommMessageRepository, - logger: Logger - ) { - this.credentialService = credentialService - this.logger = logger - this.didCommMessageRepository = didCommMessageRepository + public constructor(credentialProtocol: V1CredentialProtocol) { + this.credentialProtocol = credentialProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - const credentialRecord = await this.credentialService.processRequest(messageContext) + const credentialRecord = await this.credentialProtocol.processRequest(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToRequest(messageContext.agentContext, { + const shouldAutoRespond = await this.credentialProtocol.shouldAutoRespondToRequest(messageContext.agentContext, { credentialRecord, requestMessage: messageContext.message, }) @@ -41,11 +31,14 @@ export class V1RequestCredentialHandler implements MessageHandler { credentialRecord: CredentialExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - this.logger.info(`Automatically sending credential with autoAccept`) + messageContext.agentContext.config.logger.info(`Automatically sending credential with autoAccept`) - const offerMessage = await this.credentialService.findOfferMessage(messageContext.agentContext, credentialRecord.id) + const offerMessage = await this.credentialProtocol.findOfferMessage( + messageContext.agentContext, + credentialRecord.id + ) - const { message } = await this.credentialService.acceptRequest(messageContext.agentContext, { + const { message } = await this.credentialProtocol.acceptRequest(messageContext.agentContext, { credentialRecord, }) @@ -62,7 +55,8 @@ export class V1RequestCredentialHandler implements MessageHandler { // Set ~service, update message in record (for later use) message.setService(ourService) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) + await didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -77,6 +71,6 @@ export class V1RequestCredentialHandler implements MessageHandler { }) } - this.logger.error(`Could not automatically create credential request`) + messageContext.agentContext.config.logger.error(`Could not automatically create credential request`) } } diff --git a/packages/core/src/modules/credentials/protocol/v1/index.ts b/packages/core/src/modules/credentials/protocol/v1/index.ts index a104ea7564..4529b2ab71 100644 --- a/packages/core/src/modules/credentials/protocol/v1/index.ts +++ b/packages/core/src/modules/credentials/protocol/v1/index.ts @@ -1,2 +1,2 @@ -export * from './V1CredentialService' +export * from './V1CredentialProtocol' export * from './messages' diff --git a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts index 1a6777bd63..97e5489378 100644 --- a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts +++ b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts @@ -1,12 +1,11 @@ import type { AgentContext } from '../../../../agent' import type { Attachment } from '../../../../decorators/attachment/Attachment' -import type { DidCommMessageRepository } from '../../../../storage' -import type { CredentialFormat, CredentialFormatPayload, CredentialFormatService } from '../../formats' +import type { CredentialFormatPayload, CredentialFormatService, ExtractCredentialFormats } from '../../formats' import type { CredentialFormatSpec } from '../../models' import type { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' -import { DidCommMessageRole } from '../../../../storage' +import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' import { V2IssueCredentialMessage, @@ -16,12 +15,7 @@ import { V2CredentialPreview, } from './messages' -export class CredentialFormatCoordinator { - private didCommMessageRepository: DidCommMessageRepository - public constructor(didCommMessageRepository: DidCommMessageRepository) { - this.didCommMessageRepository = didCommMessageRepository - } - +export class CredentialFormatCoordinator { /** * Create a {@link V2ProposeCredentialMessage}. * @@ -38,11 +32,13 @@ export class CredentialFormatCoordinator { comment, }: { formatServices: CredentialFormatService[] - credentialFormats: CredentialFormatPayload + credentialFormats: CredentialFormatPayload, 'createProposal'> credentialRecord: CredentialExchangeRecord comment?: string } ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + // create message. there are two arrays in each message, one for formats the other for attachments const formats: CredentialFormatSpec[] = [] const proposalAttachments: Attachment[] = [] @@ -76,7 +72,7 @@ export class CredentialFormatCoordinator { message.setThread({ threadId: credentialRecord.threadId }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -97,6 +93,8 @@ export class CredentialFormatCoordinator { formatServices: CredentialFormatService[] } ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + for (const formatService of formatServices) { const attachment = this.getAttachmentForService(formatService, message.formats, message.proposalAttachments) @@ -106,7 +104,7 @@ export class CredentialFormatCoordinator { }) } - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, @@ -122,17 +120,19 @@ export class CredentialFormatCoordinator { comment, }: { credentialRecord: CredentialExchangeRecord - credentialFormats?: CredentialFormatPayload + credentialFormats?: CredentialFormatPayload, 'acceptProposal'> formatServices: CredentialFormatService[] comment?: string } ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + // create message. there are two arrays in each message, one for formats the other for attachments const formats: CredentialFormatSpec[] = [] const offerAttachments: Attachment[] = [] let credentialPreview: V2CredentialPreview | undefined - const proposalMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { + const proposalMessage = await didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V2ProposeCredentialMessage, }) @@ -184,7 +184,7 @@ export class CredentialFormatCoordinator { message.setThread({ threadId: credentialRecord.threadId }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, associatedRecordId: credentialRecord.id, role: DidCommMessageRole.Sender, @@ -209,11 +209,13 @@ export class CredentialFormatCoordinator { comment, }: { formatServices: CredentialFormatService[] - credentialFormats: CredentialFormatPayload + credentialFormats: CredentialFormatPayload, 'createOffer'> credentialRecord: CredentialExchangeRecord comment?: string } ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + // create message. there are two arrays in each message, one for formats the other for attachments const formats: CredentialFormatSpec[] = [] const offerAttachments: Attachment[] = [] @@ -254,7 +256,7 @@ export class CredentialFormatCoordinator { message.setThread({ threadId: credentialRecord.threadId }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -275,6 +277,8 @@ export class CredentialFormatCoordinator { formatServices: CredentialFormatService[] } ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + for (const formatService of formatServices) { const attachment = this.getAttachmentForService(formatService, message.formats, message.offerAttachments) @@ -284,7 +288,7 @@ export class CredentialFormatCoordinator { }) } - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, @@ -300,12 +304,14 @@ export class CredentialFormatCoordinator { comment, }: { credentialRecord: CredentialExchangeRecord - credentialFormats?: CredentialFormatPayload + credentialFormats?: CredentialFormatPayload, 'acceptOffer'> formatServices: CredentialFormatService[] comment?: string } ) { - const offerMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const offerMessage = await didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V2OfferCredentialMessage, }) @@ -341,7 +347,7 @@ export class CredentialFormatCoordinator { message.setThread({ threadId: credentialRecord.threadId }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, associatedRecordId: credentialRecord.id, role: DidCommMessageRole.Sender, @@ -366,11 +372,13 @@ export class CredentialFormatCoordinator { comment, }: { formatServices: CredentialFormatService[] - credentialFormats: CredentialFormatPayload + credentialFormats: CredentialFormatPayload, 'createRequest'> credentialRecord: CredentialExchangeRecord comment?: string } ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + // create message. there are two arrays in each message, one for formats the other for attachments const formats: CredentialFormatSpec[] = [] const requestAttachments: Attachment[] = [] @@ -393,7 +401,7 @@ export class CredentialFormatCoordinator { message.setThread({ threadId: credentialRecord.threadId }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -414,6 +422,8 @@ export class CredentialFormatCoordinator { formatServices: CredentialFormatService[] } ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + for (const formatService of formatServices) { const attachment = this.getAttachmentForService(formatService, message.formats, message.requestAttachments) @@ -423,7 +433,7 @@ export class CredentialFormatCoordinator { }) } - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, @@ -439,17 +449,19 @@ export class CredentialFormatCoordinator { comment, }: { credentialRecord: CredentialExchangeRecord - credentialFormats?: CredentialFormatPayload + credentialFormats?: CredentialFormatPayload, 'acceptRequest'> formatServices: CredentialFormatService[] comment?: string } ) { - const requestMessage = await this.didCommMessageRepository.getAgentMessage(agentContext, { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V2RequestCredentialMessage, }) - const offerMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { + const offerMessage = await didCommMessageRepository.findAgentMessage(agentContext, { associatedRecordId: credentialRecord.id, messageClass: V2OfferCredentialMessage, }) @@ -489,7 +501,7 @@ export class CredentialFormatCoordinator { message.setThread({ threadId: credentialRecord.threadId }) message.setPleaseAck() - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, associatedRecordId: credentialRecord.id, role: DidCommMessageRole.Sender, @@ -512,6 +524,8 @@ export class CredentialFormatCoordinator { formatServices: CredentialFormatService[] } ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + for (const formatService of formatServices) { const attachment = this.getAttachmentForService(formatService, message.formats, message.credentialAttachments) const requestAttachment = this.getAttachmentForService( @@ -527,7 +541,7 @@ export class CredentialFormatCoordinator { }) } - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { agentMessage: message, role: DidCommMessageRole.Receiver, associatedRecordId: credentialRecord.id, diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts similarity index 77% rename from packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts rename to packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts index e3d4e85c38..7fac43d7e0 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts @@ -1,7 +1,9 @@ import type { AgentContext } from '../../../../agent' import type { AgentMessage } from '../../../../agent/AgentMessage' +import type { FeatureRegistry } from '../../../../agent/FeatureRegistry' import type { MessageHandlerInboundMessage } from '../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { DependencyManager } from '../../../../plugins' import type { ProblemReportMessage } from '../../../problem-reports' import type { AcceptCredentialOptions, @@ -17,41 +19,34 @@ import type { GetFormatDataReturn, NegotiateOfferOptions, NegotiateProposalOptions, -} from '../../CredentialServiceOptions' +} from '../../CredentialProtocolOptions' import type { CredentialFormat, CredentialFormatPayload, CredentialFormatService, - CredentialFormatServiceMap, + ExtractCredentialFormats, } from '../../formats' import type { CredentialFormatSpec } from '../../models/CredentialFormatSpec' -import { Dispatcher } from '../../../../agent/Dispatcher' -import { EventEmitter } from '../../../../agent/EventEmitter' -import { InjectionSymbols } from '../../../../constants' +import { Protocol } from '../../../../agent/models/features/Protocol' import { AriesFrameworkError } from '../../../../error' -import { Logger } from '../../../../logger' -import { injectable, inject } from '../../../../plugins' import { DidCommMessageRepository } from '../../../../storage' import { uuid } from '../../../../utils/uuid' import { AckStatus } from '../../../common' import { ConnectionService } from '../../../connections' -import { RoutingService } from '../../../routing/services/RoutingService' import { CredentialsModuleConfig } from '../../CredentialsModuleConfig' import { CredentialProblemReportReason } from '../../errors' -import { IndyCredentialFormatService } from '../../formats/indy/IndyCredentialFormatService' -import { JsonLdCredentialFormatService } from '../../formats/jsonld/JsonLdCredentialFormatService' import { AutoAcceptCredential, CredentialState } from '../../models' import { CredentialExchangeRecord, CredentialRepository } from '../../repository' -import { CredentialService } from '../../services/CredentialService' import { composeAutoAccept } from '../../util/composeAutoAccept' import { arePreviewAttributesEqual } from '../../util/previewAttributes' +import { BaseCredentialProtocol } from '../BaseCredentialProtocol' import { CredentialFormatCoordinator } from './CredentialFormatCoordinator' import { + V2OfferCredentialHandler, V2CredentialAckHandler, V2IssueCredentialHandler, - V2OfferCredentialHandler, V2ProposeCredentialHandler, V2RequestCredentialHandler, } from './handlers' @@ -65,42 +60,20 @@ import { V2RequestCredentialMessage, } from './messages' -@injectable() -export class V2CredentialService extends CredentialService { - private connectionService: ConnectionService - private credentialFormatCoordinator: CredentialFormatCoordinator - private routingService: RoutingService - private credentialsModuleConfig: CredentialsModuleConfig - private formatServiceMap: { [key: string]: CredentialFormatService } - - public constructor( - connectionService: ConnectionService, - didCommMessageRepository: DidCommMessageRepository, - routingService: RoutingService, - dispatcher: Dispatcher, - eventEmitter: EventEmitter, - credentialRepository: CredentialRepository, - indyCredentialFormatService: IndyCredentialFormatService, - jsonLdCredentialFormatService: JsonLdCredentialFormatService, - @inject(InjectionSymbols.Logger) logger: Logger, - credentialsModuleConfig: CredentialsModuleConfig - ) { - super(credentialRepository, didCommMessageRepository, eventEmitter, dispatcher, logger) - this.connectionService = connectionService - this.routingService = routingService - this.credentialFormatCoordinator = new CredentialFormatCoordinator(didCommMessageRepository) - this.credentialsModuleConfig = credentialsModuleConfig - - // Dynamically build format service map. This will be extracted once services are registered dynamically - this.formatServiceMap = [indyCredentialFormatService, jsonLdCredentialFormatService].reduce( - (formatServiceMap, formatService) => ({ - ...formatServiceMap, - [formatService.formatKey]: formatService, - }), - {} - ) as CredentialFormatServiceMap - - this.registerMessageHandlers() +export interface V2CredentialProtocolConfig { + credentialFormats: CredentialFormatServices +} + +export class V2CredentialProtocol< + CFs extends CredentialFormatService[] = CredentialFormatService[] +> extends BaseCredentialProtocol { + private credentialFormatCoordinator = new CredentialFormatCoordinator() + private credentialFormats: CFs + + public constructor({ credentialFormats }: V2CredentialProtocolConfig) { + super() + + this.credentialFormats = credentialFormats } /** @@ -108,16 +81,27 @@ export class V2CredentialService ): Promise> { - this.logger.debug('Get the Format Service and Create Proposal Message') + agentContext.config.logger.debug('Get the Format Service and Create Proposal Message') + + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) const formatServices = this.getFormatServices(credentialFormats) if (formatServices.length === 0) { @@ -153,8 +139,8 @@ export class V2CredentialService ): Promise { - const { message: proposalMessage, connection } = messageContext + const { message: proposalMessage, connection, agentContext } = messageContext + + agentContext.config.logger.debug(`Processing credential proposal with id ${proposalMessage.id}`) - this.logger.debug(`Processing credential proposal with id ${proposalMessage.id}`) + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) let credentialRecord = await this.findByThreadAndConnectionId( messageContext.agentContext, @@ -179,21 +169,18 @@ export class V2CredentialService ): Promise> { + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + const formatServices = this.getFormatServices(credentialFormats) if (formatServices.length === 0) { throw new AriesFrameworkError(`Unable to create offer. No supported formats`) @@ -357,8 +348,10 @@ export class V2CredentialService ): Promise { - const { message: offerMessage, connection } = messageContext + const { message: offerMessage, connection, agentContext } = messageContext - this.logger.debug(`Processing credential offer with id ${offerMessage.id}`) + agentContext.config.logger.debug(`Processing credential offer with id ${offerMessage.id}`) + + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) let credentialRecord = await this.findByThreadAndConnectionId( messageContext.agentContext, @@ -383,28 +380,25 @@ export class V2CredentialService ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + // Assert credentialRecord.assertProtocolVersion('v2') credentialRecord.assertState(CredentialState.OfferReceived) @@ -459,12 +455,12 @@ export class V2CredentialService ): Promise> { + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + const formatServices = this.getFormatServices(credentialFormats) if (formatServices.length === 0) { throw new AriesFrameworkError(`Unable to create request. No supported formats`) @@ -557,8 +555,10 @@ export class V2CredentialService ): Promise { - const { message: requestMessage, connection } = messageContext + const { message: requestMessage, connection, agentContext } = messageContext - this.logger.debug(`Processing credential request with id ${requestMessage.id}`) + const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + agentContext.config.logger.debug(`Processing credential request with id ${requestMessage.id}`) let credentialRecord = await this.findByThreadAndConnectionId( messageContext.agentContext, @@ -587,19 +591,19 @@ export class V2CredentialService ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + // Assert credentialRecord.assertProtocolVersion('v2') credentialRecord.assertState(CredentialState.RequestReceived) @@ -662,12 +668,12 @@ export class V2CredentialService ): Promise { - const { message: credentialMessage, connection } = messageContext + const { message: credentialMessage, connection, agentContext } = messageContext + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) - this.logger.debug(`Processing credential with id ${credentialMessage.id}`) + agentContext.config.logger.debug(`Processing credential with id ${credentialMessage.id}`) const credentialRecord = await this.getByThreadAndConnectionId( messageContext.agentContext, @@ -714,11 +723,11 @@ export class V2CredentialService ): Promise { - const { message: ackMessage, connection } = messageContext + const { message: ackMessage, connection, agentContext } = messageContext - this.logger.debug(`Processing credential ack with id ${ackMessage.id}`) + agentContext.config.logger.debug(`Processing credential ack with id ${ackMessage.id}`) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) const credentialRecord = await this.getByThreadAndConnectionId( messageContext.agentContext, @@ -794,12 +806,12 @@ export class V2CredentialService { const { credentialRecord, proposalMessage } = options + const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) + const autoAccept = composeAutoAccept( credentialRecord.autoAcceptCredential, - this.credentialsModuleConfig.autoAcceptCredentials + credentialsModuleConfig.autoAcceptCredentials ) // Handle always / never cases @@ -858,7 +872,7 @@ export class V2CredentialService { const { credentialRecord, offerMessage } = options + const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) + const autoAccept = composeAutoAccept( credentialRecord.autoAcceptCredential, - this.credentialsModuleConfig.autoAcceptCredentials + credentialsModuleConfig.autoAcceptCredentials ) // Handle always / never cases @@ -921,7 +937,7 @@ export class V2CredentialService { const { credentialRecord, requestMessage } = options + const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) + const autoAccept = composeAutoAccept( credentialRecord.autoAcceptCredential, - this.credentialsModuleConfig.autoAcceptCredentials + credentialsModuleConfig.autoAcceptCredentials ) // Handle always / never cases @@ -983,7 +1001,7 @@ export class V2CredentialService { const { credentialRecord, credentialMessage } = options + const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) + const autoAccept = composeAutoAccept( credentialRecord.autoAcceptCredential, - this.credentialsModuleConfig.autoAcceptCredentials + credentialsModuleConfig.autoAcceptCredentials ) // Handle always / never cases @@ -1046,7 +1066,7 @@ export class V2CredentialService { + public async getFormatData( + agentContext: AgentContext, + credentialExchangeId: string + ): Promise>> { // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. const [proposalMessage, offerMessage, requestMessage, credentialMessage] = await Promise.all([ this.findProposalMessage(agentContext, credentialExchangeId), @@ -1148,7 +1179,7 @@ export class V2CredentialService() for (const msg of messageFormats) { @@ -1208,7 +1222,7 @@ export class V2CredentialService( - credentialFormats: CredentialFormatPayload + credentialFormats: CredentialFormatPayload, M> ): CredentialFormatService[] { const formats = new Set() @@ -1222,18 +1236,28 @@ export class V2CredentialService credentialFormat.formatKey === formatKey) - return null + return formatService ?? null } private getFormatServiceForFormat(format: string): CredentialFormatService | null { - for (const service of Object.values(this.formatServiceMap)) { - if (service.supportsFormat(format)) return service + const formatService = this.credentialFormats.find((credentialFormat) => credentialFormat.supportsFormat(format)) + + return formatService ?? null + } + + protected getFormatServiceForRecordType(credentialRecordType: string) { + const formatService = this.credentialFormats.find( + (credentialFormat) => credentialFormat.credentialRecordType === credentialRecordType + ) + + if (!formatService) { + throw new AriesFrameworkError( + `No format service found for credential record type ${credentialRecordType} in v2 credential protocol` + ) } - return null + return formatService } } diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts similarity index 90% rename from packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts rename to packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts index e6160aa6fa..5939cb70a5 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts @@ -21,7 +21,6 @@ import { DidExchangeState } from '../../../../connections' import { ConnectionService } from '../../../../connections/services/ConnectionService' import { RoutingService } from '../../../../routing/services/RoutingService' import { CredentialEventTypes } from '../../../CredentialEvents' -import { CredentialsModuleConfig } from '../../../CredentialsModuleConfig' import { credReq } from '../../../__tests__/fixtures' import { CredentialProblemReportReason } from '../../../errors/CredentialProblemReportReason' import { IndyCredentialFormatService } from '../../../formats' @@ -32,7 +31,7 @@ import { CredentialExchangeRecord } from '../../../repository/CredentialExchange import { CredentialMetadataKeys } from '../../../repository/CredentialMetadataTypes' import { CredentialRepository } from '../../../repository/CredentialRepository' import { V1CredentialPreview } from '../../v1/messages/V1CredentialPreview' -import { V2CredentialService } from '../V2CredentialService' +import { V2CredentialProtocol } from '../V2CredentialProtocol' import { V2ProposeCredentialMessage } from '../messages' import { V2CredentialAckMessage } from '../messages/V2CredentialAckMessage' import { V2CredentialProblemReportMessage } from '../messages/V2CredentialProblemReportMessage' @@ -69,13 +68,28 @@ const connectionService = new ConnectionServiceMock() // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore indyCredentialFormatService.formatKey = 'indy' +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +indyCredentialFormatService.credentialRecordType = 'indy' // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore jsonLdCredentialFormatService.formatKey = 'jsonld' -const agentConfig = getAgentConfig('V2CredentialServiceCredTest') -const agentContext = getAgentContext() +const agentConfig = getAgentConfig('V2CredentialProtocolCredTest') +const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) + +const agentContext = getAgentContext({ + registerInstances: [ + [CredentialRepository, credentialRepository], + [DidCommMessageRepository, didCommMessageRepository], + [RoutingService, routingService], + [Dispatcher, dispatcher], + [ConnectionService, connectionService], + [EventEmitter, eventEmitter], + ], + agentConfig, +}) const connection = getMockConnection({ id: '123', @@ -243,14 +257,10 @@ const mockCredentialRecord = ({ return credentialRecord } -describe('CredentialService', () => { - let eventEmitter: EventEmitter - - let credentialService: V2CredentialService +describe('credentialProtocol', () => { + let credentialProtocol: V2CredentialProtocol beforeEach(async () => { - eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) - // mock function implementations mockFunction(connectionService.getById).mockResolvedValue(connection) mockFunction(didCommMessageRepository.findAgentMessage).mockImplementation(getAgentMessageMock) @@ -261,18 +271,9 @@ describe('CredentialService', () => { didCommMessageRecord, ]) - credentialService = new V2CredentialService( - connectionService, - didCommMessageRepository, - routingService, - dispatcher, - eventEmitter, - credentialRepository, - indyCredentialFormatService, - jsonLdCredentialFormatService, - agentConfig.logger, - new CredentialsModuleConfig() - ) + credentialProtocol = new V2CredentialProtocol({ + credentialFormats: [indyCredentialFormatService, jsonLdCredentialFormatService], + }) }) afterEach(() => { @@ -294,7 +295,7 @@ describe('CredentialService', () => { }) // when - await credentialService.acceptOffer(agentContext, { + await credentialProtocol.acceptOffer(agentContext, { credentialRecord, credentialFormats: { indy: { @@ -331,7 +332,7 @@ describe('CredentialService', () => { }) // when - const { message: credentialRequest } = await credentialService.acceptOffer(agentContext, { + const { message: credentialRequest } = await credentialProtocol.acceptOffer(agentContext, { credentialRecord, comment, }) @@ -355,7 +356,7 @@ describe('CredentialService', () => { await Promise.all( invalidCredentialStates.map(async (state) => { await expect( - credentialService.acceptOffer(agentContext, { credentialRecord: mockCredentialRecord({ state }) }) + credentialProtocol.acceptOffer(agentContext, { credentialRecord: mockCredentialRecord({ state }) }) ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) }) ) @@ -376,7 +377,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.findSingleByQuery).mockResolvedValue(credentialRecord) // when - const returnedCredentialRecord = await credentialService.processRequest(messageContext) + const returnedCredentialRecord = await credentialProtocol.processRequest(messageContext) // then expect(credentialRepository.findSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { @@ -401,7 +402,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.findSingleByQuery).mockResolvedValue(credentialRecord) - const returnedCredentialRecord = await credentialService.processRequest(messageContext) + const returnedCredentialRecord = await credentialProtocol.processRequest(messageContext) // then expect(credentialRepository.findSingleByQuery).toHaveBeenNthCalledWith( @@ -432,7 +433,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.findSingleByQuery).mockReturnValue( Promise.resolve(mockCredentialRecord({ state })) ) - await expect(credentialService.processRequest(messageContext)).rejects.toThrowError( + await expect(credentialProtocol.processRequest(messageContext)).rejects.toThrowError( `Credential record is in invalid state ${state}. Valid states are: ${validState}.` ) }) @@ -453,7 +454,7 @@ describe('CredentialService', () => { connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - await credentialService.acceptRequest(agentContext, { + await credentialProtocol.acceptRequest(agentContext, { credentialRecord, comment: 'credential response comment', }) @@ -487,7 +488,7 @@ describe('CredentialService', () => { eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // when - await credentialService.acceptRequest(agentContext, { + await credentialProtocol.acceptRequest(agentContext, { credentialRecord, comment: 'credential response comment', }) @@ -524,7 +525,7 @@ describe('CredentialService', () => { const comment = 'credential response comment' // when - const { message: credentialResponse } = await credentialService.acceptRequest(agentContext, { + const { message: credentialResponse } = await credentialProtocol.acceptRequest(agentContext, { comment: 'credential response comment', credentialRecord, }) @@ -561,7 +562,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.getSingleByQuery).mockResolvedValue(credentialRecord) // when - const record = await credentialService.processCredential(messageContext) + const record = await credentialProtocol.processCredential(messageContext) expect(record.credentialAttributes?.length).toBe(2) }) @@ -576,7 +577,7 @@ describe('CredentialService', () => { }) // when - await credentialService.acceptCredential(agentContext, { credentialRecord }) + await credentialProtocol.acceptCredential(agentContext, { credentialRecord }) // then expect(credentialRepository.update).toHaveBeenNthCalledWith( @@ -599,7 +600,7 @@ describe('CredentialService', () => { eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // when - await credentialService.acceptCredential(agentContext, { credentialRecord }) + await credentialProtocol.acceptCredential(agentContext, { credentialRecord }) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -627,7 +628,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) // when - const { message: ackMessage } = await credentialService.acceptCredential(agentContext, { credentialRecord }) + const { message: ackMessage } = await credentialProtocol.acceptCredential(agentContext, { credentialRecord }) // then expect(ackMessage.toJSON()).toMatchObject({ @@ -645,7 +646,7 @@ describe('CredentialService', () => { await Promise.all( invalidCredentialStates.map(async (state) => { await expect( - credentialService.acceptCredential(agentContext, { + credentialProtocol.acceptCredential(agentContext, { credentialRecord: mockCredentialRecord({ state, threadId: 'somethreadid', @@ -674,7 +675,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.getSingleByQuery).mockResolvedValue(credentialRecord) // when - const returnedCredentialRecord = await credentialService.processAck(messageContext) + const returnedCredentialRecord = await credentialProtocol.processAck(messageContext) expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, agentContext, { threadId: 'somethreadid', @@ -697,7 +698,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) // when - const credentialProblemReportMessage = credentialService.createProblemReport(agentContext, { message }) + const credentialProblemReportMessage = credentialProtocol.createProblemReport(agentContext, { message }) credentialProblemReportMessage.setThread({ threadId: 'somethreadid' }) // then @@ -737,7 +738,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.getSingleByQuery).mockResolvedValue(credentialRecord) // when - const returnedCredentialRecord = await credentialService.processProblemReport(messageContext) + const returnedCredentialRecord = await credentialProtocol.processProblemReport(messageContext) // then @@ -754,7 +755,7 @@ describe('CredentialService', () => { it('getById should return value from credentialRepository.getById', async () => { const expected = mockCredentialRecord() mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.getById(agentContext, expected.id) + const result = await credentialProtocol.getById(agentContext, expected.id) expect(credentialRepository.getById).toBeCalledWith(agentContext, expected.id) expect(result).toBe(expected) @@ -763,7 +764,7 @@ describe('CredentialService', () => { it('getById should return value from credentialRepository.getSingleByQuery', async () => { const expected = mockCredentialRecord() mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.getByThreadAndConnectionId(agentContext, 'threadId', 'connectionId') + const result = await credentialProtocol.getByThreadAndConnectionId(agentContext, 'threadId', 'connectionId') expect(credentialRepository.getSingleByQuery).toBeCalledWith(agentContext, { threadId: 'threadId', connectionId: 'connectionId', @@ -775,7 +776,7 @@ describe('CredentialService', () => { it('findById should return value from credentialRepository.findById', async () => { const expected = mockCredentialRecord() mockFunction(credentialRepository.findById).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.findById(agentContext, expected.id) + const result = await credentialProtocol.findById(agentContext, expected.id) expect(credentialRepository.findById).toBeCalledWith(agentContext, expected.id) expect(result).toBe(expected) @@ -785,7 +786,7 @@ describe('CredentialService', () => { const expected = [mockCredentialRecord(), mockCredentialRecord()] mockFunction(credentialRepository.getAll).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.getAll(agentContext) + const result = await credentialProtocol.getAll(agentContext) expect(credentialRepository.getAll).toBeCalledWith(agentContext) expect(result).toEqual(expect.arrayContaining(expected)) @@ -795,7 +796,7 @@ describe('CredentialService', () => { const expected = [mockCredentialRecord(), mockCredentialRecord()] mockFunction(credentialRepository.findByQuery).mockReturnValue(Promise.resolve(expected)) - const result = await credentialService.findAllByQuery(agentContext, { state: CredentialState.OfferSent }) + const result = await credentialProtocol.findAllByQuery(agentContext, { state: CredentialState.OfferSent }) expect(credentialRepository.findByQuery).toBeCalledWith(agentContext, { state: CredentialState.OfferSent }) expect(result).toEqual(expect.arrayContaining(expected)) @@ -808,7 +809,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credentialRecord)) const repositoryDeleteSpy = jest.spyOn(credentialRepository, 'delete') - await credentialService.delete(agentContext, credentialRecord) + await credentialProtocol.delete(agentContext, credentialRecord) expect(repositoryDeleteSpy).toHaveBeenNthCalledWith(1, agentContext, credentialRecord) }) @@ -818,7 +819,7 @@ describe('CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(agentContext, credentialRecord, { + await credentialProtocol.delete(agentContext, credentialRecord, { deleteAssociatedCredentials: true, deleteAssociatedDidCommMessages: false, }) @@ -836,7 +837,7 @@ describe('CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(agentContext, credentialRecord, { + await credentialProtocol.delete(agentContext, credentialRecord, { deleteAssociatedCredentials: false, deleteAssociatedDidCommMessages: false, }) @@ -850,7 +851,7 @@ describe('CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(agentContext, credentialRecord) + await credentialProtocol.delete(agentContext, credentialRecord) expect(deleteCredentialMock).toHaveBeenNthCalledWith( 1, @@ -864,7 +865,7 @@ describe('CredentialService', () => { const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - await credentialService.delete(agentContext, credentialRecord) + await credentialProtocol.delete(agentContext, credentialRecord) expect(deleteCredentialMock).toHaveBeenNthCalledWith( 1, @@ -882,7 +883,7 @@ describe('CredentialService', () => { }) // when - await credentialService.declineOffer(agentContext, credentialRecord) + await credentialProtocol.declineOffer(agentContext, credentialRecord) // then @@ -907,7 +908,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.getSingleByQuery).mockResolvedValue(credentialRecord) // when - await credentialService.declineOffer(agentContext, credentialRecord) + await credentialProtocol.declineOffer(agentContext, credentialRecord) // then expect(eventListenerMock).toHaveBeenCalledTimes(1) @@ -932,7 +933,7 @@ describe('CredentialService', () => { await Promise.all( invalidCredentialStates.map(async (state) => { await expect( - credentialService.declineOffer(agentContext, mockCredentialRecord({ state })) + credentialProtocol.declineOffer(agentContext, mockCredentialRecord({ state })) ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) }) ) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts similarity index 87% rename from packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts rename to packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts index a580005723..7b76103178 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialServiceOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts @@ -1,6 +1,5 @@ import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { CreateOfferOptions } from '../../../CredentialServiceOptions' -import type { IndyCredentialFormat } from '../../../formats/indy/IndyCredentialFormat' +import type { CreateOfferOptions } from '../../../CredentialProtocolOptions' import { Subject } from 'rxjs' @@ -16,7 +15,6 @@ import { ConnectionService } from '../../../../connections/services/ConnectionSe import { IndyLedgerService } from '../../../../ledger/services' import { RoutingService } from '../../../../routing/services/RoutingService' import { CredentialEventTypes } from '../../../CredentialEvents' -import { CredentialsModuleConfig } from '../../../CredentialsModuleConfig' import { credDef, schema } from '../../../__tests__/fixtures' import { IndyCredentialFormatService } from '../../../formats/indy/IndyCredentialFormatService' import { JsonLdCredentialFormatService } from '../../../formats/jsonld/JsonLdCredentialFormatService' @@ -25,7 +23,7 @@ import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { CredentialRepository } from '../../../repository/CredentialRepository' import { V1CredentialPreview } from '../../v1/messages/V1CredentialPreview' -import { V2CredentialService } from '../V2CredentialService' +import { V2CredentialProtocol } from '../V2CredentialProtocol' import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' // Mock classes @@ -64,8 +62,21 @@ indyCredentialFormatService.formatKey = 'indy' // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore jsonLdCredentialFormatService.formatKey = 'jsonld' -const agentConfig = getAgentConfig('V2CredentialServiceOfferTest') -const agentContext = getAgentContext() +const agentConfig = getAgentConfig('V2CredentialProtocolOfferTest') +const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) + +const agentContext = getAgentContext({ + registerInstances: [ + [CredentialRepository, credentialRepository], + [DidCommMessageRepository, didCommMessageRepository], + [RoutingService, routingService], + [IndyLedgerService, indyLedgerService], + [Dispatcher, dispatcher], + [ConnectionService, connectionService], + [EventEmitter, eventEmitter], + ], + agentConfig, +}) const connection = getMockConnection({ id: '123', @@ -90,31 +101,18 @@ const offerAttachment = new Attachment({ }), }) -describe('V2CredentialServiceOffer', () => { - let eventEmitter: EventEmitter - let credentialService: V2CredentialService +describe('V2CredentialProtocolOffer', () => { + let credentialProtocol: V2CredentialProtocol beforeEach(async () => { - // real objects - eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) - // mock function implementations mockFunction(connectionService.getById).mockResolvedValue(connection) mockFunction(indyLedgerService.getCredentialDefinition).mockResolvedValue(credDef) mockFunction(indyLedgerService.getSchema).mockResolvedValue(schema) - credentialService = new V2CredentialService( - connectionService, - didCommMessageRepository, - routingService, - dispatcher, - eventEmitter, - credentialRepository, - indyCredentialFormatService, - jsonLdCredentialFormatService, - agentConfig.logger, - new CredentialsModuleConfig() - ) + credentialProtocol = new V2CredentialProtocol({ + credentialFormats: [indyCredentialFormatService, jsonLdCredentialFormatService], + }) }) afterEach(() => { @@ -122,7 +120,7 @@ describe('V2CredentialServiceOffer', () => { }) describe('createOffer', () => { - const offerOptions: CreateOfferOptions<[IndyCredentialFormat]> = { + const offerOptions: CreateOfferOptions<[IndyCredentialFormatService]> = { comment: 'some comment', connection, credentialFormats: { @@ -141,7 +139,7 @@ describe('V2CredentialServiceOffer', () => { }) // when - await credentialService.createOffer(agentContext, offerOptions) + await credentialProtocol.createOffer(agentContext, offerOptions) // then expect(credentialRepository.save).toHaveBeenNthCalledWith( @@ -167,7 +165,7 @@ describe('V2CredentialServiceOffer', () => { const eventListenerMock = jest.fn() eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) - await credentialService.createOffer(agentContext, offerOptions) + await credentialProtocol.createOffer(agentContext, offerOptions) expect(eventListenerMock).toHaveBeenCalledWith({ type: 'CredentialStateChanged', @@ -191,7 +189,7 @@ describe('V2CredentialServiceOffer', () => { previewAttributes: credentialPreview.attributes, }) - const { message: credentialOffer } = await credentialService.createOffer(agentContext, offerOptions) + const { message: credentialOffer } = await credentialProtocol.createOffer(agentContext, offerOptions) expect(credentialOffer.toJSON()).toMatchObject({ '@id': expect.any(String), @@ -232,7 +230,7 @@ describe('V2CredentialServiceOffer', () => { mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) // when - await credentialService.processOffer(messageContext) + await credentialProtocol.processOffer(messageContext) // then expect(credentialRepository.save).toHaveBeenNthCalledWith( @@ -256,7 +254,7 @@ describe('V2CredentialServiceOffer', () => { eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // when - await credentialService.processOffer(messageContext) + await credentialProtocol.processOffer(messageContext) // then expect(eventListenerMock).toHaveBeenCalledWith({ diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts index ad2c6431d2..0c62263961 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts @@ -1,10 +1,6 @@ import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { - AcceptCredentialOfferOptions, - AcceptCredentialRequestOptions, - CreateOfferOptions, -} from '../../../CredentialsApiOptions' +import type { AcceptCredentialOfferOptions, AcceptCredentialRequestOptions } from '../../../CredentialsApiOptions' import { ReplaySubject, Subject } from 'rxjs' @@ -81,7 +77,8 @@ describe('V2 Connectionless Credentials', () => { test('Faber starts with connection-less credential offer to Alice', async () => { testLogger.test('Faber sends credential offer to Alice') - const offerOptions: CreateOfferOptions = { + // eslint-disable-next-line prefer-const + let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer({ comment: 'V2 Out of Band offer', credentialFormats: { indy: { @@ -90,9 +87,7 @@ describe('V2 Connectionless Credentials', () => { }, }, protocolVersion: 'v2', - } - // eslint-disable-next-line prefer-const - let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer(offerOptions) + }) const { message: offerMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ recordId: faberCredentialRecord.id, @@ -181,7 +176,8 @@ describe('V2 Connectionless Credentials', () => { }) test('Faber starts with connection-less credential offer to Alice with auto-accept enabled', async () => { - const offerOptions: CreateOfferOptions = { + // eslint-disable-next-line prefer-const + let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer({ comment: 'V2 Out of Band offer', credentialFormats: { indy: { @@ -191,9 +187,7 @@ describe('V2 Connectionless Credentials', () => { }, protocolVersion: 'v2', autoAcceptCredential: AutoAcceptCredential.ContentApproved, - } - // eslint-disable-next-line prefer-const - let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer(offerOptions) + }) const { message: offerMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ recordId: faberCredentialRecord.id, diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts index 7e81510800..501c079de4 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts @@ -1,7 +1,6 @@ import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' import type { Wallet } from '../../../../../wallet' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { CreateOfferOptions } from '../../../CredentialsApiOptions' import type { JsonCredential, JsonLdSignCredentialFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' import type { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' @@ -18,8 +17,11 @@ import { W3cVcModule } from '../../../../vc' import { customDocumentLoader } from '../../../../vc/__tests__/documentLoader' import { CREDENTIALS_CONTEXT_V1_URL } from '../../../../vc/constants' import { CredentialEventTypes } from '../../../CredentialEvents' +import { CredentialsModule } from '../../../CredentialsModule' +import { JsonLdCredentialFormatService } from '../../../formats' import { CredentialState } from '../../../models' import { CredentialExchangeRecord } from '../../../repository' +import { V2CredentialProtocol } from '../V2CredentialProtocol' const faberAgentOptions = getAgentOptions( 'Faber LD connection-less Credentials V2', @@ -27,6 +29,9 @@ const faberAgentOptions = getAgentOptions( endpoints: ['rxjs:faber'], }, { + credentials: new CredentialsModule({ + credentialProtocols: [new V2CredentialProtocol({ credentialFormats: [new JsonLdCredentialFormatService()] })], + }), w3cVc: new W3cVcModule({ documentLoader: customDocumentLoader, }), @@ -39,6 +44,9 @@ const aliceAgentOptions = getAgentOptions( endpoints: ['rxjs:alice'], }, { + credentials: new CredentialsModule({ + credentialProtocols: [new V2CredentialProtocol({ credentialFormats: [new JsonLdCredentialFormatService()] })], + }), w3cVc: new W3cVcModule({ documentLoader: customDocumentLoader, }), @@ -115,17 +123,16 @@ describe('credentials', () => { }) test('Faber starts with V2 W3C connection-less credential offer to Alice', async () => { - const offerOptions: CreateOfferOptions = { + testLogger.test('Faber sends credential offer to Alice') + + // eslint-disable-next-line prefer-const + let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer({ comment: 'V2 Out of Band offer (W3C)', credentialFormats: { jsonld: signCredentialOptions, }, protocolVersion: 'v2', - } - testLogger.test('Faber sends credential offer to Alice') - - // eslint-disable-next-line prefer-const - let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer(offerOptions) + }) const offerMsg = message as V2OfferCredentialMessage const attachment = offerMsg?.offerAttachments[0] diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts index 80bc4dd2ef..80766e53c8 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts @@ -1,13 +1,7 @@ -import type { ProposeCredentialOptions } from '../../..' import type { Agent } from '../../../../../agent/Agent' import type { Wallet } from '../../../../../wallet' import type { ConnectionRecord } from '../../../../connections' -import type { - JsonCredential, - JsonLdCredentialFormat, - JsonLdSignCredentialFormat, -} from '../../../formats/jsonld/JsonLdCredentialFormat' -import type { V2CredentialService } from '../V2CredentialService' +import type { JsonCredential, JsonLdSignCredentialFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' @@ -68,18 +62,14 @@ describe('credentials', () => { test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { testLogger.test('Alice sends credential proposal to Faber') - const options: ProposeCredentialOptions< - [JsonLdCredentialFormat], - [V2CredentialService<[JsonLdCredentialFormat]>] - > = { + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ connectionId: aliceConnection.id, protocolVersion: 'v2', credentialFormats: { jsonld: signCredentialOptions, }, comment: 'v2 propose credential test', - } - const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(options) + }) testLogger.test('Alice waits for credential from Faber') diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts index cc71ebec34..ff4ff1b326 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -1,4 +1,4 @@ -import type { Agent } from '../../../../../agent/Agent' +import type { Awaited } from '../../../../../types' import type { Wallet } from '../../../../../wallet' import type { ConnectionRecord } from '../../../../connections' import type { JsonCredential, JsonLdSignCredentialFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' @@ -15,8 +15,8 @@ import { V2IssueCredentialMessage } from '../messages/V2IssueCredentialMessage' import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' describe('credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent + let faberAgent: Awaited>['faberAgent'] + let aliceAgent: Awaited>['aliceAgent'] let aliceConnection: ConnectionRecord let aliceCredentialRecord: CredentialExchangeRecord let faberCredentialRecord: CredentialExchangeRecord diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialAckHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialAckHandler.ts index e859a04e22..8fdf0b2a40 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialAckHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialAckHandler.ts @@ -1,17 +1,17 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { V2CredentialService } from '../V2CredentialService' +import type { V2CredentialProtocol } from '../V2CredentialProtocol' import { V2CredentialAckMessage } from '../messages/V2CredentialAckMessage' export class V2CredentialAckHandler implements MessageHandler { - private credentialService: V2CredentialService + private credentialProtocol: V2CredentialProtocol public supportedMessages = [V2CredentialAckMessage] - public constructor(credentialService: V2CredentialService) { - this.credentialService = credentialService + public constructor(credentialProtocol: V2CredentialProtocol) { + this.credentialProtocol = credentialProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - await this.credentialService.processAck(messageContext) + await this.credentialProtocol.processAck(messageContext) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialProblemReportHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialProblemReportHandler.ts index 4801080fed..f3b7b60bf0 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialProblemReportHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialProblemReportHandler.ts @@ -1,17 +1,17 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { V2CredentialService } from '../V2CredentialService' +import type { V2CredentialProtocol } from '../V2CredentialProtocol' import { V2CredentialProblemReportMessage } from '../messages/V2CredentialProblemReportMessage' export class V2CredentialProblemReportHandler implements MessageHandler { - private credentialService: V2CredentialService + private credentialProtocol: V2CredentialProtocol public supportedMessages = [V2CredentialProblemReportMessage] - public constructor(credentialService: V2CredentialService) { - this.credentialService = credentialService + public constructor(credentialProtocol: V2CredentialProtocol) { + this.credentialProtocol = credentialProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - await this.credentialService.processProblemReport(messageContext) + await this.credentialProtocol.processProblemReport(messageContext) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts index 9c27dc09cc..c6815be71c 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts @@ -1,35 +1,25 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' -import type { Logger } from '../../../../../logger' -import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import type { V2CredentialService } from '../V2CredentialService' +import type { V2CredentialProtocol } from '../V2CredentialProtocol' import { OutboundMessageContext } from '../../../../../agent/models' +import { DidCommMessageRepository } from '../../../../../storage' import { V2IssueCredentialMessage } from '../messages/V2IssueCredentialMessage' import { V2RequestCredentialMessage } from '../messages/V2RequestCredentialMessage' export class V2IssueCredentialHandler implements MessageHandler { - private credentialService: V2CredentialService - private didCommMessageRepository: DidCommMessageRepository - private logger: Logger - + private credentialProtocol: V2CredentialProtocol public supportedMessages = [V2IssueCredentialMessage] - public constructor( - credentialService: V2CredentialService, - didCommMessageRepository: DidCommMessageRepository, - logger: Logger - ) { - this.credentialService = credentialService - this.didCommMessageRepository = didCommMessageRepository - this.logger = logger + public constructor(credentialProtocol: V2CredentialProtocol) { + this.credentialProtocol = credentialProtocol } public async handle(messageContext: InboundMessageContext) { - const credentialRecord = await this.credentialService.processCredential(messageContext) + const credentialRecord = await this.credentialProtocol.processCredential(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToCredential(messageContext.agentContext, { + const shouldAutoRespond = await this.credentialProtocol.shouldAutoRespondToCredential(messageContext.agentContext, { credentialRecord, credentialMessage: messageContext.message, }) @@ -43,14 +33,16 @@ export class V2IssueCredentialHandler implements MessageHandler { credentialRecord: CredentialExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - this.logger.info(`Automatically sending acknowledgement with autoAccept`) + messageContext.agentContext.config.logger.info(`Automatically sending acknowledgement with autoAccept`) + + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const requestMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V2RequestCredentialMessage, }) - const { message } = await this.credentialService.acceptCredential(messageContext.agentContext, { + const { message } = await this.credentialProtocol.acceptCredential(messageContext.agentContext, { credentialRecord, }) if (messageContext.connection) { @@ -72,6 +64,6 @@ export class V2IssueCredentialHandler implements MessageHandler { }) } - this.logger.error(`Could not automatically create credential ack`) + messageContext.agentContext.config.logger.error(`Could not automatically create credential ack`) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts index 83c29f8069..f23f53d2c0 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts @@ -1,40 +1,27 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' -import type { Logger } from '../../../../../logger' -import type { DidCommMessageRepository } from '../../../../../storage' -import type { RoutingService } from '../../../../routing/services/RoutingService' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import type { V2CredentialService } from '../V2CredentialService' +import type { V2CredentialProtocol } from '../V2CredentialProtocol' import { OutboundMessageContext } from '../../../../../agent/models' import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' -import { DidCommMessageRole } from '../../../../../storage' +import { DidCommMessageRepository, DidCommMessageRole } from '../../../../../storage' +import { RoutingService } from '../../../../routing/services/RoutingService' import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' export class V2OfferCredentialHandler implements MessageHandler { - private credentialService: V2CredentialService - private routingService: RoutingService - private logger: Logger + private credentialProtocol: V2CredentialProtocol public supportedMessages = [V2OfferCredentialMessage] - private didCommMessageRepository: DidCommMessageRepository - public constructor( - credentialService: V2CredentialService, - routingService: RoutingService, - didCommMessageRepository: DidCommMessageRepository, - logger: Logger - ) { - this.credentialService = credentialService - this.routingService = routingService - this.didCommMessageRepository = didCommMessageRepository - this.logger = logger + public constructor(credentialProtocol: V2CredentialProtocol) { + this.credentialProtocol = credentialProtocol } public async handle(messageContext: InboundMessageContext) { - const credentialRecord = await this.credentialService.processOffer(messageContext) + const credentialRecord = await this.credentialProtocol.processOffer(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToOffer(messageContext.agentContext, { + const shouldAutoRespond = await this.credentialProtocol.shouldAutoRespondToOffer(messageContext.agentContext, { credentialRecord, offerMessage: messageContext.message, }) @@ -48,10 +35,10 @@ export class V2OfferCredentialHandler implements MessageHandler { messageContext: MessageHandlerInboundMessage, offerMessage?: V2OfferCredentialMessage ) { - this.logger.info(`Automatically sending request with autoAccept`) + messageContext.agentContext.config.logger.info(`Automatically sending request with autoAccept`) if (messageContext.connection) { - const { message } = await this.credentialService.acceptOffer(messageContext.agentContext, { + const { message } = await this.credentialProtocol.acceptOffer(messageContext.agentContext, { credentialRecord, }) return new OutboundMessageContext(message, { @@ -60,7 +47,8 @@ export class V2OfferCredentialHandler implements MessageHandler { associatedRecord: credentialRecord, }) } else if (offerMessage?.service) { - const routing = await this.routingService.getRouting(messageContext.agentContext) + const routingService = messageContext.agentContext.dependencyManager.resolve(RoutingService) + const routing = await routingService.getRouting(messageContext.agentContext) const ourService = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], @@ -68,14 +56,15 @@ export class V2OfferCredentialHandler implements MessageHandler { }) const recipientService = offerMessage.service - const { message } = await this.credentialService.acceptOffer(messageContext.agentContext, { + const { message } = await this.credentialProtocol.acceptOffer(messageContext.agentContext, { credentialRecord, }) // Set and save ~service decorator to record (to remember our verkey) message.service = ourService - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) + await didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: message, role: DidCommMessageRole.Sender, associatedRecordId: credentialRecord.id, @@ -90,6 +79,6 @@ export class V2OfferCredentialHandler implements MessageHandler { }) } - this.logger.error(`Could not automatically create credential request`) + messageContext.agentContext.config.logger.error(`Could not automatically create credential request`) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts index 5825fb368f..c28a77c608 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts @@ -1,27 +1,24 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' -import type { Logger } from '../../../../../logger' import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import type { V2CredentialService } from '../V2CredentialService' +import type { V2CredentialProtocol } from '../V2CredentialProtocol' import { OutboundMessageContext } from '../../../../../agent/models' import { V2ProposeCredentialMessage } from '../messages/V2ProposeCredentialMessage' export class V2ProposeCredentialHandler implements MessageHandler { - private credentialService: V2CredentialService - private logger: Logger + private credentialProtocol: V2CredentialProtocol public supportedMessages = [V2ProposeCredentialMessage] - public constructor(credentialService: V2CredentialService, logger: Logger) { - this.credentialService = credentialService - this.logger = logger + public constructor(credentialProtocol: V2CredentialProtocol) { + this.credentialProtocol = credentialProtocol } public async handle(messageContext: InboundMessageContext) { - const credentialRecord = await this.credentialService.processProposal(messageContext) + const credentialRecord = await this.credentialProtocol.processProposal(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToProposal(messageContext.agentContext, { + const shouldAutoRespond = await this.credentialProtocol.shouldAutoRespondToProposal(messageContext.agentContext, { credentialRecord, proposalMessage: messageContext.message, }) @@ -35,14 +32,14 @@ export class V2ProposeCredentialHandler implements MessageHandler { credentialRecord: CredentialExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - this.logger.info(`Automatically sending offer with autoAccept`) + messageContext.agentContext.config.logger.info(`Automatically sending offer with autoAccept`) if (!messageContext.connection) { - this.logger.error('No connection on the messageContext, aborting auto accept') + messageContext.agentContext.config.logger.error('No connection on the messageContext, aborting auto accept') return } - const { message } = await this.credentialService.acceptProposal(messageContext.agentContext, { credentialRecord }) + const { message } = await this.credentialProtocol.acceptProposal(messageContext.agentContext, { credentialRecord }) return new OutboundMessageContext(message, { agentContext: messageContext.agentContext, diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts index 18cab7c34c..244485182f 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts @@ -1,36 +1,26 @@ import type { MessageHandler } from '../../../../../agent/MessageHandler' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' -import type { Logger } from '../../../../../logger/Logger' -import type { DidCommMessageRepository } from '../../../../../storage' import type { CredentialExchangeRecord } from '../../../repository' -import type { V2CredentialService } from '../V2CredentialService' +import type { V2CredentialProtocol } from '../V2CredentialProtocol' import { OutboundMessageContext } from '../../../../../agent/models' -import { DidCommMessageRole } from '../../../../../storage' +import { DidCommMessageRepository, DidCommMessageRole } from '../../../../../storage' import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' import { V2RequestCredentialMessage } from '../messages/V2RequestCredentialMessage' export class V2RequestCredentialHandler implements MessageHandler { - private credentialService: V2CredentialService - private didCommMessageRepository: DidCommMessageRepository - private logger: Logger + private credentialProtocol: V2CredentialProtocol public supportedMessages = [V2RequestCredentialMessage] - public constructor( - credentialService: V2CredentialService, - didCommMessageRepository: DidCommMessageRepository, - logger: Logger - ) { - this.credentialService = credentialService - this.didCommMessageRepository = didCommMessageRepository - this.logger = logger + public constructor(credentialProtocol: V2CredentialProtocol) { + this.credentialProtocol = credentialProtocol } public async handle(messageContext: InboundMessageContext) { - const credentialRecord = await this.credentialService.processRequest(messageContext) + const credentialRecord = await this.credentialProtocol.processRequest(messageContext) - const shouldAutoRespond = await this.credentialService.shouldAutoRespondToRequest(messageContext.agentContext, { + const shouldAutoRespond = await this.credentialProtocol.shouldAutoRespondToRequest(messageContext.agentContext, { credentialRecord, requestMessage: messageContext.message, }) @@ -44,14 +34,15 @@ export class V2RequestCredentialHandler implements MessageHandler { credentialRecord: CredentialExchangeRecord, messageContext: InboundMessageContext ) { - this.logger.info(`Automatically sending credential with autoAccept`) + messageContext.agentContext.config.logger.info(`Automatically sending credential with autoAccept`) + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) - const offerMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const offerMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V2OfferCredentialMessage, }) - const { message } = await this.credentialService.acceptRequest(messageContext.agentContext, { + const { message } = await this.credentialProtocol.acceptRequest(messageContext.agentContext, { credentialRecord, }) @@ -67,7 +58,7 @@ export class V2RequestCredentialHandler implements MessageHandler { // Set ~service, update message in record (for later use) message.setService(ourService) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: message, associatedRecordId: credentialRecord.id, role: DidCommMessageRole.Sender, @@ -82,6 +73,6 @@ export class V2RequestCredentialHandler implements MessageHandler { }) } - this.logger.error(`Could not automatically issue credential`) + messageContext.agentContext.config.logger.error(`Could not automatically issue credential`) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/index.ts b/packages/core/src/modules/credentials/protocol/v2/index.ts index 6b3febbf46..c6d6213662 100644 --- a/packages/core/src/modules/credentials/protocol/v2/index.ts +++ b/packages/core/src/modules/credentials/protocol/v2/index.ts @@ -1,2 +1,2 @@ -export * from './V2CredentialService' +export * from './V2CredentialProtocol' export * from './messages' diff --git a/packages/core/src/modules/credentials/services/index.ts b/packages/core/src/modules/credentials/services/index.ts deleted file mode 100644 index 3ef45ad8eb..0000000000 --- a/packages/core/src/modules/credentials/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './CredentialService' diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts index 1f3e3f2794..2b2493c41a 100644 --- a/packages/core/src/modules/oob/OutOfBandApi.ts +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -9,9 +9,9 @@ import type { HandshakeReusedEvent } from './domain/OutOfBandEvents' import { catchError, EmptyError, first, firstValueFrom, map, of, timeout } from 'rxjs' import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' import { EventEmitter } from '../../agent/EventEmitter' import { filterContextCorrelationId, AgentEventTypes } from '../../agent/Events' +import { MessageHandlerRegistry } from '../../agent/MessageHandlerRegistry' import { MessageSender } from '../../agent/MessageSender' import { OutboundMessageContext } from '../../agent/models' import { InjectionSymbols } from '../../constants' @@ -84,7 +84,7 @@ export class OutOfBandApi { private routingService: RoutingService private connectionsApi: ConnectionsApi private didCommMessageRepository: DidCommMessageRepository - private dispatcher: Dispatcher + private messageHandlerRegistry: MessageHandlerRegistry private didCommDocumentService: DidCommDocumentService private messageSender: MessageSender private eventEmitter: EventEmitter @@ -92,7 +92,7 @@ export class OutOfBandApi { private logger: Logger public constructor( - dispatcher: Dispatcher, + messageHandlerRegistry: MessageHandlerRegistry, didCommDocumentService: DidCommDocumentService, outOfBandService: OutOfBandService, routingService: RoutingService, @@ -103,7 +103,7 @@ export class OutOfBandApi { @inject(InjectionSymbols.Logger) logger: Logger, agentContext: AgentContext ) { - this.dispatcher = dispatcher + this.messageHandlerRegistry = messageHandlerRegistry this.didCommDocumentService = didCommDocumentService this.agentContext = agentContext this.logger = logger @@ -113,7 +113,7 @@ export class OutOfBandApi { this.didCommMessageRepository = didCommMessageRepository this.messageSender = messageSender this.eventEmitter = eventEmitter - this.registerMessageHandlers(dispatcher) + this.registerMessageHandlers(messageHandlerRegistry) } /** @@ -602,8 +602,10 @@ export class OutOfBandApi { } private getSupportedHandshakeProtocols(): HandshakeProtocol[] { + // TODO: update to featureRegistry const handshakeMessageFamilies = ['https://didcomm.org/didexchange', 'https://didcomm.org/connections'] - const handshakeProtocols = this.dispatcher.filterSupportedProtocolsByMessageFamilies(handshakeMessageFamilies) + const handshakeProtocols = + this.messageHandlerRegistry.filterSupportedProtocolsByMessageFamilies(handshakeMessageFamilies) if (handshakeProtocols.length === 0) { throw new AriesFrameworkError('There is no handshake protocol supported. Agent can not create a connection.') @@ -650,7 +652,7 @@ export class OutOfBandApi { } private async emitWithConnection(connectionRecord: ConnectionRecord, messages: PlaintextMessage[]) { - const supportedMessageTypes = this.dispatcher.supportedMessageTypes + const supportedMessageTypes = this.messageHandlerRegistry.supportedMessageTypes const plaintextMessage = messages.find((message) => { const parsedMessageType = parseMessageType(message['@type']) return supportedMessageTypes.find((type) => supportsIncomingMessageType(parsedMessageType, type)) @@ -677,7 +679,7 @@ export class OutOfBandApi { throw new AriesFrameworkError(`There are no services. We can not emit messages`) } - const supportedMessageTypes = this.dispatcher.supportedMessageTypes + const supportedMessageTypes = this.messageHandlerRegistry.supportedMessageTypes const plaintextMessage = messages.find((message) => { const parsedMessageType = parseMessageType(message['@type']) return supportedMessageTypes.find((type) => supportsIncomingMessageType(parsedMessageType, type)) @@ -766,8 +768,9 @@ export class OutOfBandApi { return reuseAcceptedEventPromise } - private registerMessageHandlers(dispatcher: Dispatcher) { - dispatcher.registerMessageHandler(new HandshakeReuseHandler(this.outOfBandService)) - dispatcher.registerMessageHandler(new HandshakeReuseAcceptedHandler(this.outOfBandService)) + // TODO: we should probably move these to the out of band module and register the handler there + private registerMessageHandlers(messageHandlerRegistry: MessageHandlerRegistry) { + messageHandlerRegistry.registerMessageHandler(new HandshakeReuseHandler(this.outOfBandService)) + messageHandlerRegistry.registerMessageHandler(new HandshakeReuseAcceptedHandler(this.outOfBandService)) } } diff --git a/packages/core/src/plugins/DependencyManager.ts b/packages/core/src/plugins/DependencyManager.ts index 734a43ce81..e836ec895b 100644 --- a/packages/core/src/plugins/DependencyManager.ts +++ b/packages/core/src/plugins/DependencyManager.ts @@ -1,10 +1,12 @@ import type { ModulesMap } from '../agent/AgentModules' +import type { MessageHandler } from '../agent/MessageHandler' import type { Constructor } from '../utils/mixins' import type { DependencyContainer } from 'tsyringe' import { container as rootContainer, InjectionToken, Lifecycle } from 'tsyringe' import { FeatureRegistry } from '../agent/FeatureRegistry' +import { MessageHandlerRegistry } from '../agent/MessageHandlerRegistry' import { AriesFrameworkError } from '../error' export { InjectionToken } @@ -36,6 +38,14 @@ export class DependencyManager { } } + public registerMessageHandlers(messageHandlers: MessageHandler[]) { + const messageHandlerRegistry = this.resolve(MessageHandlerRegistry) + + for (const messageHandler of messageHandlers) { + messageHandlerRegistry.registerMessageHandler(messageHandler) + } + } + public registerSingleton(from: InjectionToken, to: InjectionToken): void public registerSingleton(token: Constructor): void // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/core/src/plugins/Module.ts b/packages/core/src/plugins/Module.ts index f76b5329d1..8170b159de 100644 --- a/packages/core/src/plugins/Module.ts +++ b/packages/core/src/plugins/Module.ts @@ -7,10 +7,6 @@ export interface Module { register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void } -/** - * Decorator that marks the class as a module. Will enforce the required interface for a module (with static methods) - * on the class declaration. - */ -export function module() { - return >(constructor: U) => constructor +export interface ApiModule extends Module { + api: Constructor } diff --git a/packages/core/src/storage/migration/UpdateAssistant.ts b/packages/core/src/storage/migration/UpdateAssistant.ts index b5ce6f4e89..756da0e093 100644 --- a/packages/core/src/storage/migration/UpdateAssistant.ts +++ b/packages/core/src/storage/migration/UpdateAssistant.ts @@ -12,7 +12,8 @@ import { StorageUpdateService } from './StorageUpdateService' import { StorageUpdateError } from './error/StorageUpdateError' import { CURRENT_FRAMEWORK_STORAGE_VERSION, supportedUpdates } from './updates' -export class UpdateAssistant { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export class UpdateAssistant = BaseAgent> { private agent: Agent private storageUpdateService: StorageUpdateService private updateConfig: UpdateConfig diff --git a/packages/core/src/transport/InboundTransport.ts b/packages/core/src/transport/InboundTransport.ts index 61ec5ac188..fd744bfcfa 100644 --- a/packages/core/src/transport/InboundTransport.ts +++ b/packages/core/src/transport/InboundTransport.ts @@ -1,6 +1,7 @@ import type { Agent } from '../agent/Agent' export interface InboundTransport { - start(agent: Agent): Promise + // eslint-disable-next-line @typescript-eslint/no-explicit-any + start(agent: Agent): Promise stop(): Promise } diff --git a/packages/core/src/transport/OutboundTransport.ts b/packages/core/src/transport/OutboundTransport.ts index aa3ff65ac0..0fa33bbe61 100644 --- a/packages/core/src/transport/OutboundTransport.ts +++ b/packages/core/src/transport/OutboundTransport.ts @@ -6,6 +6,7 @@ export interface OutboundTransport { sendMessage(outboundPackage: OutboundPackage): Promise - start(agent: Agent): Promise + // eslint-disable-next-line @typescript-eslint/no-explicit-any + start(agent: Agent): Promise stop(): Promise } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 66b1e0856a..fcc0945cce 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -53,32 +53,107 @@ export interface InitConfig { endpoints?: string[] label: string publicDidSeed?: string - mediatorRecordId?: string walletConfig?: WalletConfig + logger?: Logger + didCommMimeType?: DidCommMimeType + useDidKeyInProtocols?: boolean + useLegacyDidSovPrefix?: boolean + connectionImageUrl?: string + autoUpdateStorageOnStartup?: boolean + + /** + * @deprecated configure `autoAcceptConnections` on the `ConnectionsModule` class + * @note This setting will be ignored if the `ConnectionsModule` is manually configured as + * a module + */ autoAcceptConnections?: boolean + + /** + * @deprecated configure `autoAcceptProofs` on the `ProofModule` class + * @note This setting will be ignored if the `ProofsModule` is manually configured as + * a module + */ autoAcceptProofs?: AutoAcceptProof + + /** + * @deprecated configure `autoAcceptCredentials` on the `CredentialsModule` class + * @note This setting will be ignored if the `CredentialsModule` is manually configured as + * a module + */ autoAcceptCredentials?: AutoAcceptCredential - logger?: Logger - didCommMimeType?: DidCommMimeType + /** + * @deprecated configure `indyLedgers` on the `LedgerModule` class + * @note This setting will be ignored if the `LedgerModule` is manually configured as + * a module + */ indyLedgers?: IndyPoolConfig[] + + /** + * @deprecated configure `connectToIndyLedgersOnStartup` on the `LedgerModule` class + * @note This setting will be ignored if the `LedgerModule` is manually configured as + * a module + */ connectToIndyLedgersOnStartup?: boolean + /** + * @deprecated configure `autoAcceptMediationRequests` on the `RecipientModule` class + * @note This setting will be ignored if the `RecipientModule` is manually configured as + * a module + */ autoAcceptMediationRequests?: boolean + + /** + * @deprecated configure `mediatorConnectionsInvite` on the `RecipientModule` class + * @note This setting will be ignored if the `RecipientModule` is manually configured as + * a module + */ mediatorConnectionsInvite?: string + + /** + * @deprecated you can use `RecipientApi.setDefaultMediator` to set the default mediator. + */ defaultMediatorId?: string + + /** + * @deprecated you can set the `default` tag to `false` (or remove it completely) to clear the default mediator. + */ clearDefaultMediator?: boolean + + /** + * @deprecated configure `mediatorPollingInterval` on the `RecipientModule` class + * @note This setting will be ignored if the `RecipientModule` is manually configured as + * a module + */ mediatorPollingInterval?: number + + /** + * @deprecated configure `mediatorPickupStrategy` on the `RecipientModule` class + * @note This setting will be ignored if the `RecipientModule` is manually configured as + * a module + */ mediatorPickupStrategy?: MediatorPickupStrategy + + /** + * @deprecated configure `maximumMessagePickup` on the `RecipientModule` class + * @note This setting will be ignored if the `RecipientModule` is manually configured as + * a module + */ maximumMessagePickup?: number - baseMediatorReconnectionIntervalMs?: number - maximumMediatorReconnectionIntervalMs?: number - useDidKeyInProtocols?: boolean - useLegacyDidSovPrefix?: boolean - connectionImageUrl?: string + /** + * @deprecated configure `baseMediatorReconnectionIntervalMs` on the `RecipientModule` class + * @note This setting will be ignored if the `RecipientModule` is manually configured as + * a module + */ + baseMediatorReconnectionIntervalMs?: number - autoUpdateStorageOnStartup?: boolean + /** + * @deprecated configure `maximumMediatorReconnectionIntervalMs` on the `RecipientModule` class + * @note This setting will be ignored if the `RecipientModule` is manually configured as + * a module + */ + maximumMediatorReconnectionIntervalMs?: number } export type ProtocolVersion = `${number}.${number}` @@ -100,3 +175,21 @@ export type JsonArray = Array export interface JsonObject { [property: string]: JsonValue } + +// Flatten an array of arrays +/** + * Flatten an array of arrays + * @example + * ``` + * type Flattened = FlatArray<[[1], [2]]> + * + * // is the same as + * type Flattened = 1 | 2 + * ``` + */ +export type FlatArray = Arr extends ReadonlyArray ? FlatArray : Arr + +/** + * Get the awaited (resolved promise) type of Promise type. + */ +export type Awaited = T extends Promise ? U : never diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index f149b25f0b..7c518c25cf 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -8,6 +8,7 @@ import type { CredentialDefinitionTemplate, CredentialStateChangedEvent, InitConfig, + InjectionToken, ProofStateChangedEvent, SchemaTemplate, Wallet, @@ -28,6 +29,11 @@ import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutbou import { BbsModule } from '../../bbs-signatures/src/BbsModule' import { agentDependencies, WalletScheme } from '../../node/src' import { + CredentialsModule, + IndyCredentialFormatService, + JsonLdCredentialFormatService, + V1CredentialProtocol, + V2CredentialProtocol, W3cVcModule, Agent, AgentConfig, @@ -156,14 +162,24 @@ export function getAgentContext({ wallet, agentConfig, contextCorrelationId = 'mock', + registerInstances = [], }: { dependencyManager?: DependencyManager wallet?: Wallet agentConfig?: AgentConfig contextCorrelationId?: string + // Must be an array of arrays as objects can't have injection tokens + // as keys (it must be number, string or symbol) + registerInstances?: Array<[InjectionToken, unknown]> } = {}) { if (wallet) dependencyManager.registerInstance(InjectionSymbols.Wallet, wallet) if (agentConfig) dependencyManager.registerInstance(AgentConfig, agentConfig) + + // Register custom instances on the dependency manager + for (const [token, instance] of registerInstances.values()) { + dependencyManager.registerInstance(token, instance) + } + return new AgentContext({ dependencyManager, contextCorrelationId }) } @@ -227,7 +243,7 @@ export function waitForCredentialRecordSubject( threadId, state, previousState, - timeoutMs = 15000, // sign and store credential in W3c credential service take several seconds + timeoutMs = 15000, // sign and store credential in W3c credential protocols take several seconds }: { threadId?: string state?: CredentialState @@ -671,10 +687,23 @@ export async function setupCredentialTests( 'rxjs:alice': aliceMessages, } + const indyCredentialFormat = new IndyCredentialFormatService() + const jsonLdCredentialFormat = new JsonLdCredentialFormatService() + // TODO remove the dependency on BbsModule const modules = { bbs: new BbsModule(), + // Initialize custom credentials module (with jsonLdCredentialFormat enabled) + credentials: new CredentialsModule({ + autoAcceptCredentials, + credentialProtocols: [ + new V1CredentialProtocol({ indyCredentialFormat }), + new V2CredentialProtocol({ + credentialFormats: [indyCredentialFormat, jsonLdCredentialFormat], + }), + ], + }), // Register custom w3cVc module so we can define the test document loader w3cVc: new W3cVcModule({ documentLoader: customDocumentLoader, @@ -684,7 +713,6 @@ export async function setupCredentialTests( faberName, { endpoints: ['rxjs:faber'], - autoAcceptCredentials, }, modules ) @@ -693,7 +721,6 @@ export async function setupCredentialTests( aliceName, { endpoints: ['rxjs:alice'], - autoAcceptCredentials, }, modules ) diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index 7a454bf2a4..e6a9ba3464 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' -import type { CreateOfferOptions } from '../src/modules/credentials' -import type { IndyCredentialFormat } from '../src/modules/credentials/formats/indy/IndyCredentialFormat' +import type { CreateOfferOptions, V1CredentialProtocol } from '../src/modules/credentials' import type { AgentMessage, AgentMessageReceivedEvent } from '@aries-framework/core' import { Subject } from 'rxjs' @@ -58,7 +57,7 @@ describe('out of band', () => { let faberAgent: Agent let aliceAgent: Agent - let credentialTemplate: CreateOfferOptions<[IndyCredentialFormat]> + let credentialTemplate: CreateOfferOptions<[V1CredentialProtocol]> beforeAll(async () => { const faberMessages = new Subject() @@ -69,6 +68,7 @@ describe('out of band', () => { } faberAgent = new Agent(faberAgentOptions) + faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() From c0569b88c27ee7785cf150ee14a5f9ebcc99898b Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 20 Dec 2022 10:26:24 +0800 Subject: [PATCH 100/125] fix(connections): use new did for each connection from reusable invitation (#1174) Signed-off-by: Timo Glastra --- .../src/modules/connections/ConnectionsApi.ts | 10 +- .../__tests__/connection-manual.e2e.test.ts | 142 ++++++++++++++++++ 2 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts diff --git a/packages/core/src/modules/connections/ConnectionsApi.ts b/packages/core/src/modules/connections/ConnectionsApi.ts index 9b12210091..f767cd1041 100644 --- a/packages/core/src/modules/connections/ConnectionsApi.ts +++ b/packages/core/src/modules/connections/ConnectionsApi.ts @@ -144,12 +144,17 @@ export class ConnectionsApi { throw new AriesFrameworkError(`Out-of-band record ${connectionRecord.outOfBandId} not found.`) } + // If the outOfBandRecord is reusable we need to use new routing keys for the connection, otherwise + // all connections will use the same routing keys + const routing = outOfBandRecord.reusable ? await this.routingService.getRouting(this.agentContext) : undefined + let outboundMessageContext if (connectionRecord.protocol === HandshakeProtocol.DidExchange) { const message = await this.didExchangeProtocol.createResponse( this.agentContext, connectionRecord, - outOfBandRecord + outOfBandRecord, + routing ) outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, @@ -159,7 +164,8 @@ export class ConnectionsApi { const { message } = await this.connectionService.createResponse( this.agentContext, connectionRecord, - outOfBandRecord + outOfBandRecord, + routing ) outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, diff --git a/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts b/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts new file mode 100644 index 0000000000..dc07e9639f --- /dev/null +++ b/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts @@ -0,0 +1,142 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { ConnectionStateChangedEvent } from '../ConnectionEvents' + +import { firstValueFrom } from 'rxjs' +import { filter, first, map, timeout } from 'rxjs/operators' + +import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getAgentOptions } from '../../../../tests/helpers' +import { Agent } from '../../../agent/Agent' +import { ConnectionEventTypes } from '../ConnectionEvents' +import { DidExchangeState } from '../models' + +function waitForRequest(agent: Agent, theirLabel: string) { + return firstValueFrom( + agent.events.observable(ConnectionEventTypes.ConnectionStateChanged).pipe( + map((event) => event.payload.connectionRecord), + // Wait for request received + filter( + (connectionRecord) => + connectionRecord.state === DidExchangeState.RequestReceived && connectionRecord.theirLabel === theirLabel + ), + first(), + timeout(5000) + ) + ) +} + +function waitForResponse(agent: Agent, connectionId: string) { + return firstValueFrom( + agent.events.observable(ConnectionEventTypes.ConnectionStateChanged).pipe( + // Wait for response received + map((event) => event.payload.connectionRecord), + filter( + (connectionRecord) => + connectionRecord.state === DidExchangeState.ResponseReceived && connectionRecord.id === connectionId + ), + first(), + timeout(5000) + ) + ) +} + +describe('Manual Connection Flow', () => { + // This test was added to reproduce a bug where all connections based on a reusable invitation would use the same keys + // This was only present in the manual flow, which is almost never used. + it('can connect multiple times using the same reusable invitation without manually using the connections api', async () => { + const aliceInboundTransport = new SubjectInboundTransport() + const bobInboundTransport = new SubjectInboundTransport() + const faberInboundTransport = new SubjectInboundTransport() + + const subjectMap = { + 'rxjs:faber': faberInboundTransport.ourSubject, + 'rxjs:alice': aliceInboundTransport.ourSubject, + 'rxjs:bob': bobInboundTransport.ourSubject, + } + const aliceAgentOptions = getAgentOptions('Manual Connection Flow Alice', { + label: 'alice', + autoAcceptConnections: false, + endpoints: ['rxjs:alice'], + }) + const bobAgentOptions = getAgentOptions('Manual Connection Flow Bob', { + label: 'bob', + autoAcceptConnections: false, + endpoints: ['rxjs:bob'], + }) + const faberAgentOptions = getAgentOptions('Manual Connection Flow Faber', { + autoAcceptConnections: false, + endpoints: ['rxjs:faber'], + }) + + const aliceAgent = new Agent(aliceAgentOptions) + aliceAgent.registerInboundTransport(aliceInboundTransport) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + + const bobAgent = new Agent(bobAgentOptions) + bobAgent.registerInboundTransport(bobInboundTransport) + bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + + const faberAgent = new Agent(faberAgentOptions) + faberAgent.registerInboundTransport(faberInboundTransport) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + + await aliceAgent.initialize() + await bobAgent.initialize() + await faberAgent.initialize() + + const faberOutOfBandRecord = await faberAgent.oob.createInvitation({ + autoAcceptConnection: false, + multiUseInvitation: true, + }) + + const waitForAliceRequest = waitForRequest(faberAgent, 'alice') + const waitForBobRequest = waitForRequest(faberAgent, 'bob') + + let { connectionRecord: aliceConnectionRecord } = await aliceAgent.oob.receiveInvitation( + faberOutOfBandRecord.outOfBandInvitation, + { + autoAcceptInvitation: true, + autoAcceptConnection: false, + } + ) + + let { connectionRecord: bobConnectionRecord } = await bobAgent.oob.receiveInvitation( + faberOutOfBandRecord.outOfBandInvitation, + { + autoAcceptInvitation: true, + autoAcceptConnection: false, + } + ) + + let faberAliceConnectionRecord = await waitForAliceRequest + let faberBobConnectionRecord = await waitForBobRequest + + const waitForAliceResponse = waitForResponse(aliceAgent, aliceConnectionRecord!.id) + const waitForBobResponse = waitForResponse(bobAgent, bobConnectionRecord!.id) + + await faberAgent.connections.acceptRequest(faberAliceConnectionRecord.id) + await faberAgent.connections.acceptRequest(faberBobConnectionRecord.id) + + aliceConnectionRecord = await waitForAliceResponse + await aliceAgent.connections.acceptResponse(aliceConnectionRecord!.id) + + bobConnectionRecord = await waitForBobResponse + await bobAgent.connections.acceptResponse(bobConnectionRecord!.id) + + aliceConnectionRecord = await aliceAgent.connections.returnWhenIsConnected(aliceConnectionRecord!.id) + bobConnectionRecord = await bobAgent.connections.returnWhenIsConnected(bobConnectionRecord!.id) + faberAliceConnectionRecord = await faberAgent.connections.returnWhenIsConnected(faberAliceConnectionRecord!.id) + faberBobConnectionRecord = await faberAgent.connections.returnWhenIsConnected(faberBobConnectionRecord!.id) + + expect(aliceConnectionRecord).toBeConnectedWith(faberAliceConnectionRecord) + expect(bobConnectionRecord).toBeConnectedWith(faberBobConnectionRecord) + + await aliceAgent.wallet.delete() + await aliceAgent.shutdown() + await bobAgent.wallet.delete() + await bobAgent.shutdown() + await faberAgent.wallet.delete() + await faberAgent.shutdown() + }) +}) From 7781a55f6699529c3d6af3c6cf6b5cfd9e58bab7 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 20 Dec 2022 13:51:52 +0800 Subject: [PATCH 101/125] feat!: allow to connect with self (#1173) Signed-off-by: Timo Glastra --- packages/core/src/agent/Agent.ts | 2 +- .../connections/DidExchangeProtocol.ts | 6 +- .../__tests__/ConnectionService.test.ts | 2 +- .../handlers/ConnectionRequestHandler.ts | 14 ++- .../handlers/ConnectionResponseHandler.ts | 8 +- .../handlers/DidExchangeCompleteHandler.ts | 2 +- .../handlers/DidExchangeRequestHandler.ts | 11 ++- .../handlers/DidExchangeResponseHandler.ts | 8 +- .../repository/ConnectionRepository.ts | 9 ++ .../connections/services/ConnectionService.ts | 32 ++++--- .../modules/dids/__tests__/peer-did.test.ts | 4 +- .../dids/methods/key/KeyDidRegistrar.ts | 2 +- .../key/__tests__/KeyDidRegistrar.test.ts | 2 +- .../dids/methods/peer/PeerDidRegistrar.ts | 2 +- .../dids/methods/peer/PeerDidResolver.ts | 10 ++- .../peer/__tests__/PeerDidRegistrar.test.ts | 6 +- .../methods/sov/IndySdkSovDidRegistrar.ts | 2 +- .../__tests__/IndySdkSovDidRegistrar.test.ts | 2 +- .../src/modules/dids/repository/DidRecord.ts | 15 +++- .../modules/dids/repository/DidRepository.ts | 30 ++++++- .../repository/__tests__/DidRecord.test.ts | 4 +- packages/core/src/modules/oob/OutOfBandApi.ts | 17 ++-- .../core/src/modules/oob/OutOfBandService.ts | 21 +++-- .../oob/__tests__/connect-to-self.e2e.test.ts | 86 +++++++++++++++++++ .../__tests__/__snapshots__/0.1.test.ts.snap | 39 +++++++++ .../0.1-0.2/__tests__/connection.test.ts | 3 + .../migration/updates/0.1-0.2/connection.ts | 69 ++++++++------- .../updates/0.2-0.3/__tests__/did.test.ts | 74 ++++++++++++++++ .../storage/migration/updates/0.2-0.3/did.ts | 42 +++++++++ .../tests/oob-mediation-provision.test.ts | 2 +- packages/core/tests/oob.test.ts | 2 +- 31 files changed, 440 insertions(+), 88 deletions(-) create mode 100644 packages/core/src/modules/oob/__tests__/connect-to-self.e2e.test.ts create mode 100644 packages/core/src/storage/migration/updates/0.2-0.3/__tests__/did.test.ts create mode 100644 packages/core/src/storage/migration/updates/0.2-0.3/did.ts diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index c005d53e6d..c4c5609387 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -216,7 +216,7 @@ export class Agent extends BaseAge protected async getMediationConnection(mediatorInvitationUrl: string) { const outOfBandInvitation = await this.oob.parseInvitation(mediatorInvitationUrl) - const outOfBandRecord = await this.oob.findByInvitationId(outOfBandInvitation.id) + const outOfBandRecord = await this.oob.findByReceivedInvitationId(outOfBandInvitation.id) const [connection] = outOfBandRecord ? await this.connections.findAllByOutOfBandId(outOfBandRecord.id) : [] if (!connection) { diff --git a/packages/core/src/modules/connections/DidExchangeProtocol.ts b/packages/core/src/modules/connections/DidExchangeProtocol.ts index eed6f3fb49..23965bbebc 100644 --- a/packages/core/src/modules/connections/DidExchangeProtocol.ts +++ b/packages/core/src/modules/connections/DidExchangeProtocol.ts @@ -177,7 +177,7 @@ export class DidExchangeProtocol { // This can be called from both the did exchange and the connection protocol. const didDocument = await this.extractDidDocument(messageContext.agentContext, message) const didRecord = new DidRecord({ - id: message.did, + did: message.did, role: DidDocumentRole.Received, // It is important to take the did document from the PeerDid class // as it will have the id property @@ -191,6 +191,7 @@ export class DidExchangeProtocol { this.logger.debug('Saving DID record', { id: didRecord.id, + did: didRecord.did, role: didRecord.role, tags: didRecord.getTags(), didDocument: 'omitted...', @@ -321,7 +322,7 @@ export class DidExchangeProtocol { .recipientKeyFingerprints.map((fingerprint) => Key.fromFingerprint(fingerprint).publicKeyBase58) ) const didRecord = new DidRecord({ - id: message.did, + did: message.did, role: DidDocumentRole.Received, didDocument, tags: { @@ -333,6 +334,7 @@ export class DidExchangeProtocol { this.logger.debug('Saving DID record', { id: didRecord.id, + did: didRecord.did, role: didRecord.role, tags: didRecord.getTags(), didDocument: 'omitted...', diff --git a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index e00e7d4522..f9ad1b1003 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -110,7 +110,7 @@ describe('ConnectionService', () => { mockFunction(didRepository.getById).mockResolvedValue( new DidRecord({ - id: 'did:peer:123', + did: 'did:peer:123', role: DidDocumentRole.Created, }) ) diff --git a/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts index 25268c1e9f..fdb6799028 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts @@ -38,7 +38,10 @@ export class ConnectionRequestHandler implements MessageHandler { throw new AriesFrameworkError('Unable to process connection request without senderVerkey or recipientKey') } - const outOfBandRecord = await this.outOfBandService.findByRecipientKey(messageContext.agentContext, recipientKey) + const outOfBandRecord = await this.outOfBandService.findCreatedByRecipientKey( + messageContext.agentContext, + recipientKey + ) if (!outOfBandRecord) { throw new AriesFrameworkError(`Out-of-band record for recipient key ${recipientKey.fingerprint} was not found.`) @@ -50,9 +53,12 @@ export class ConnectionRequestHandler implements MessageHandler { ) } - const didRecord = await this.didRepository.findByRecipientKey(messageContext.agentContext, senderKey) - if (didRecord) { - throw new AriesFrameworkError(`Did record for sender key ${senderKey.fingerprint} already exists.`) + const receivedDidRecord = await this.didRepository.findReceivedDidByRecipientKey( + messageContext.agentContext, + senderKey + ) + if (receivedDidRecord) { + throw new AriesFrameworkError(`A received did record for sender key ${senderKey.fingerprint} already exists.`) } const connectionRecord = await this.connectionService.processRequest(messageContext, outOfBandRecord) diff --git a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts index 6b37020c15..cdecad6c95 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionResponseHandler.ts @@ -8,6 +8,7 @@ import { OutboundMessageContext } from '../../../agent/models' import { ReturnRouteTypes } from '../../../decorators/transport/TransportDecorator' import { AriesFrameworkError } from '../../../error' import { ConnectionResponseMessage } from '../messages' +import { DidExchangeRole } from '../models' export class ConnectionResponseHandler implements MessageHandler { private connectionService: ConnectionService @@ -36,7 +37,12 @@ export class ConnectionResponseHandler implements MessageHandler { throw new AriesFrameworkError('Unable to process connection response without senderKey or recipientKey') } - const connectionRecord = await this.connectionService.getByThreadId(messageContext.agentContext, message.threadId) + // Query by both role and thread id to allow connecting to self + const connectionRecord = await this.connectionService.getByRoleAndThreadId( + messageContext.agentContext, + DidExchangeRole.Requester, + message.threadId + ) if (!connectionRecord) { throw new AriesFrameworkError(`Connection for thread ID ${message.threadId} not found!`) } diff --git a/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts index d8e45b71df..76f885e82b 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts @@ -35,7 +35,7 @@ export class DidExchangeCompleteHandler implements MessageHandler { if (!message.thread?.parentThreadId) { throw new AriesFrameworkError(`Message does not contain pthid attribute`) } - const outOfBandRecord = await this.outOfBandService.findByInvitationId( + const outOfBandRecord = await this.outOfBandService.findByCreatedInvitationId( messageContext.agentContext, message.thread?.parentThreadId ) diff --git a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts index f5f2e0393f..2e3bcb740d 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts @@ -42,7 +42,7 @@ export class DidExchangeRequestHandler implements MessageHandler { if (!message.thread?.parentThreadId) { throw new AriesFrameworkError(`Message does not contain 'pthid' attribute`) } - const outOfBandRecord = await this.outOfBandService.findByInvitationId( + const outOfBandRecord = await this.outOfBandService.findByCreatedInvitationId( messageContext.agentContext, message.thread.parentThreadId ) @@ -57,9 +57,12 @@ export class DidExchangeRequestHandler implements MessageHandler { ) } - const didRecord = await this.didRepository.findByRecipientKey(messageContext.agentContext, senderKey) - if (didRecord) { - throw new AriesFrameworkError(`Did record for sender key ${senderKey.fingerprint} already exists.`) + const receivedDidRecord = await this.didRepository.findReceivedDidByRecipientKey( + messageContext.agentContext, + senderKey + ) + if (receivedDidRecord) { + throw new AriesFrameworkError(`A received did record for sender key ${senderKey.fingerprint} already exists.`) } // TODO Shouldn't we check also if the keys match the keys from oob invitation services? diff --git a/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts index b997fcde45..743a0f1720 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeResponseHandler.ts @@ -10,7 +10,7 @@ import { ReturnRouteTypes } from '../../../decorators/transport/TransportDecorat import { AriesFrameworkError } from '../../../error' import { OutOfBandState } from '../../oob/domain/OutOfBandState' import { DidExchangeResponseMessage } from '../messages' -import { HandshakeProtocol } from '../models' +import { DidExchangeRole, HandshakeProtocol } from '../models' export class DidExchangeResponseHandler implements MessageHandler { private didExchangeProtocol: DidExchangeProtocol @@ -41,7 +41,11 @@ export class DidExchangeResponseHandler implements MessageHandler { throw new AriesFrameworkError('Unable to process connection response without sender key or recipient key') } - const connectionRecord = await this.connectionService.getByThreadId(agentContext, message.threadId) + const connectionRecord = await this.connectionService.getByRoleAndThreadId( + agentContext, + DidExchangeRole.Requester, + message.threadId + ) if (!connectionRecord) { throw new AriesFrameworkError(`Connection for thread ID ${message.threadId} not found!`) } diff --git a/packages/core/src/modules/connections/repository/ConnectionRepository.ts b/packages/core/src/modules/connections/repository/ConnectionRepository.ts index 504b9ea655..071d7e90cb 100644 --- a/packages/core/src/modules/connections/repository/ConnectionRepository.ts +++ b/packages/core/src/modules/connections/repository/ConnectionRepository.ts @@ -1,4 +1,5 @@ import type { AgentContext } from '../../../agent' +import type { DidExchangeRole } from '../models' import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' @@ -27,4 +28,12 @@ export class ConnectionRepository extends Repository { public getByThreadId(agentContext: AgentContext, threadId: string): Promise { return this.getSingleByQuery(agentContext, { threadId }) } + + public getByRoleAndThreadId( + agentContext: AgentContext, + role: DidExchangeRole, + threadId: string + ): Promise { + return this.getSingleByQuery(agentContext, { threadId, role }) + } } diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index 32dbf95fed..b51d1292d3 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -399,12 +399,17 @@ export class ConnectionService { throw new AriesFrameworkError('Unable to process connection problem report without recipientKey') } - let connectionRecord - const ourDidRecords = await this.didRepository.findAllByRecipientKey(messageContext.agentContext, recipientKey) - for (const ourDidRecord of ourDidRecords) { - connectionRecord = await this.findByOurDid(messageContext.agentContext, ourDidRecord.id) + const ourDidRecord = await this.didRepository.findCreatedDidByRecipientKey( + messageContext.agentContext, + recipientKey + ) + if (!ourDidRecord) { + throw new AriesFrameworkError( + `Unable to process connection problem report: created did record for recipient key ${recipientKey.fingerprint} not found` + ) } + const connectionRecord = await this.findByOurDid(messageContext.agentContext, ourDidRecord.did) if (!connectionRecord) { throw new AriesFrameworkError( `Unable to process connection problem report: connection for recipient key ${recipientKey.fingerprint} not found` @@ -413,9 +418,9 @@ export class ConnectionService { const theirDidRecord = connectionRecord.theirDid && - (await this.didRepository.findById(messageContext.agentContext, connectionRecord.theirDid)) + (await this.didRepository.findReceivedDid(messageContext.agentContext, connectionRecord.theirDid)) if (!theirDidRecord) { - throw new AriesFrameworkError(`Did record with id ${connectionRecord.theirDid} not found.`) + throw new AriesFrameworkError(`Received did record for did ${connectionRecord.theirDid} not found.`) } if (senderKey) { @@ -589,6 +594,10 @@ export class ConnectionService { return this.connectionRepository.getByThreadId(agentContext, threadId) } + public async getByRoleAndThreadId(agentContext: AgentContext, role: DidExchangeRole, threadId: string) { + return this.connectionRepository.getByRoleAndThreadId(agentContext, role, threadId) + } + public async findByTheirDid(agentContext: AgentContext, theirDid: string): Promise { return this.connectionRepository.findSingleByQuery(agentContext, { theirDid }) } @@ -613,13 +622,13 @@ export class ConnectionService { agentContext: AgentContext, { senderKey, recipientKey }: { senderKey: Key; recipientKey: Key } ) { - const theirDidRecord = await this.didRepository.findByRecipientKey(agentContext, senderKey) + const theirDidRecord = await this.didRepository.findReceivedDidByRecipientKey(agentContext, senderKey) if (theirDidRecord) { - const ourDidRecord = await this.didRepository.findByRecipientKey(agentContext, recipientKey) + const ourDidRecord = await this.didRepository.findCreatedDidByRecipientKey(agentContext, recipientKey) if (ourDidRecord) { const connectionRecord = await this.findByDids(agentContext, { - ourDid: ourDidRecord.id, - theirDid: theirDidRecord.id, + ourDid: ourDidRecord.did, + theirDid: theirDidRecord.did, }) if (connectionRecord && connectionRecord.isReady) return connectionRecord } @@ -669,7 +678,7 @@ export class ConnectionService { const peerDid = didDocumentJsonToNumAlgo1Did(didDocument.toJSON()) didDocument.id = peerDid const didRecord = new DidRecord({ - id: peerDid, + did: peerDid, role, didDocument, tags: { @@ -688,6 +697,7 @@ export class ConnectionService { this.logger.debug('Saving DID record', { id: didRecord.id, + did: didRecord.did, role: didRecord.role, tags: didRecord.getTags(), didDocument: 'omitted...', diff --git a/packages/core/src/modules/dids/__tests__/peer-did.test.ts b/packages/core/src/modules/dids/__tests__/peer-did.test.ts index 14eac24fdd..1c8bb1d9ab 100644 --- a/packages/core/src/modules/dids/__tests__/peer-did.test.ts +++ b/packages/core/src/modules/dids/__tests__/peer-did.test.ts @@ -118,7 +118,7 @@ describe('peer dids', () => { // Save the record to storage const didDocumentRecord = new DidRecord({ - id: didPeer1zQmY.id, + did: didPeer1zQmY.id, role: DidDocumentRole.Created, // It is important to take the did document from the PeerDid class // as it will have the id property @@ -155,7 +155,7 @@ describe('peer dids', () => { } const didDocumentRecord = new DidRecord({ - id: did, + did: did, role: DidDocumentRole.Received, // If the method is a genesis doc (did:peer:1) we should store the document // Otherwise we only need to store the did itself (as the did can be generated) diff --git a/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts b/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts index 7fcd557e84..b38073b02e 100644 --- a/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts +++ b/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts @@ -54,7 +54,7 @@ export class KeyDidRegistrar implements DidRegistrar { // Save the did so we know we created it and can issue with it const didRecord = new DidRecord({ - id: didKey.did, + did: didKey.did, role: DidDocumentRole.Created, }) await this.didRepository.save(agentContext, didRecord) diff --git a/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts index 19bc6bf29e..d9e89bcb72 100644 --- a/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts +++ b/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts @@ -118,7 +118,7 @@ describe('DidRegistrar', () => { const [, didRecord] = mockFunction(didRepositoryMock.save).mock.calls[0] expect(didRecord).toMatchObject({ - id: did, + did, role: DidDocumentRole.Created, didDocument: undefined, }) diff --git a/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts b/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts index d194c4cbf1..de8bd36676 100644 --- a/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts +++ b/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts @@ -87,7 +87,7 @@ export class PeerDidRegistrar implements DidRegistrar { // Save the did so we know we created it and can use it for didcomm const didRecord = new DidRecord({ - id: didDocument.id, + did: didDocument.id, role: DidDocumentRole.Created, didDocument: isPeerDidNumAlgo1CreateOptions(options) ? didDocument : undefined, tags: { diff --git a/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts index 3ee2c1b473..4cf77abe40 100644 --- a/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts +++ b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts @@ -39,7 +39,15 @@ export class PeerDidResolver implements DidResolver { } // For Method 1, retrieve from storage else if (numAlgo === PeerDidNumAlgo.GenesisDoc) { - const didDocumentRecord = await this.didRepository.getById(agentContext, did) + // We can have multiple did document records stored for a single did (one created and one received). In this case it + // doesn't matter which one we use, and they should be identical. So we just take the first one. + const [didDocumentRecord] = await this.didRepository.findByQuery(agentContext, { + did, + }) + + if (!didDocumentRecord) { + throw new AriesFrameworkError(`No did record found for peer did ${did}.`) + } if (!didDocumentRecord.didDocument) { throw new AriesFrameworkError(`Found did record for method 1 peer did (${did}), but no did document.`) diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts index c6843609a3..1edfeb31df 100644 --- a/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts +++ b/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts @@ -120,7 +120,7 @@ describe('DidRegistrar', () => { const [, didRecord] = mockFunction(didRepositoryMock.save).mock.calls[0] expect(didRecord).toMatchObject({ - id: did, + did: did, role: DidDocumentRole.Created, _tags: { recipientKeyFingerprints: [], @@ -214,7 +214,7 @@ describe('DidRegistrar', () => { const [, didRecord] = mockFunction(didRepositoryMock.save).mock.calls[0] expect(didRecord).toMatchObject({ - id: did, + did: did, didDocument: didState.didDocument, role: DidDocumentRole.Created, _tags: { @@ -306,7 +306,7 @@ describe('DidRegistrar', () => { const [, didRecord] = mockFunction(didRepositoryMock.save).mock.calls[0] expect(didRecord).toMatchObject({ - id: did, + did: did, role: DidDocumentRole.Created, _tags: { recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint), diff --git a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts index 265975327b..2efa3d585f 100644 --- a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts +++ b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts @@ -104,7 +104,7 @@ export class IndySdkSovDidRegistrar implements DidRegistrar { // Save the did so we know we created it and can issue with it const didRecord = new DidRecord({ - id: qualifiedSovDid, + did: qualifiedSovDid, role: DidDocumentRole.Created, tags: { recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint), diff --git a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts index 0c0275897c..b619de81cd 100644 --- a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts +++ b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts @@ -330,7 +330,7 @@ describe('DidRegistrar', () => { const [, didRecord] = mockFunction(didRepositoryMock.save).mock.calls[0] expect(didRecord).toMatchObject({ - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', role: DidDocumentRole.Created, _tags: { recipientKeyFingerprints: ['z6LSrH6AdsQeZuKKmG6Ehx7abEQZsVg2psR2VU536gigUoAe'], diff --git a/packages/core/src/modules/dids/repository/DidRecord.ts b/packages/core/src/modules/dids/repository/DidRecord.ts index 675cd41b32..752088323b 100644 --- a/packages/core/src/modules/dids/repository/DidRecord.ts +++ b/packages/core/src/modules/dids/repository/DidRecord.ts @@ -5,6 +5,7 @@ import { Type } from 'class-transformer' import { IsEnum, ValidateNested } from 'class-validator' import { BaseRecord } from '../../../storage/BaseRecord' +import { uuid } from '../../../utils/uuid' import { DidDocument } from '../domain' import { DidDocumentRole } from '../domain/DidDocumentRole' import { parseDid } from '../domain/parse' @@ -12,7 +13,8 @@ import { parseDid } from '../domain/parse' import { DidRecordMetadataKeys } from './didRecordMetadataTypes' export interface DidRecordProps { - id: string + id?: string + did: string role: DidDocumentRole didDocument?: DidDocument createdAt?: Date @@ -27,6 +29,8 @@ type DefaultDidTags = { role: DidDocumentRole method: string legacyUnqualifiedDid?: string + methodSpecificIdentifier: string + did: string } export class DidRecord extends BaseRecord implements DidRecordProps { @@ -34,6 +38,8 @@ export class DidRecord extends BaseRecord { super(DidRecord, storageService, eventEmitter) } - public findByRecipientKey(agentContext: AgentContext, recipientKey: Key) { - return this.findSingleByQuery(agentContext, { recipientKeyFingerprints: [recipientKey.fingerprint] }) + /** + * Finds a {@link DidRecord}, containing the specified recipientKey that was received by this agent. + * To find a {@link DidRecord} that was created by this agent, use {@link DidRepository.findCreatedDidByRecipientKey}. + */ + public findReceivedDidByRecipientKey(agentContext: AgentContext, recipientKey: Key) { + return this.findSingleByQuery(agentContext, { + recipientKeyFingerprints: [recipientKey.fingerprint], + role: DidDocumentRole.Received, + }) + } + + /** + * Finds a {@link DidRecord}, containing the specified recipientKey that was created by this agent. + * To find a {@link DidRecord} that was received by this agent, use {@link DidRepository.findReceivedDidByRecipientKey}. + */ + public findCreatedDidByRecipientKey(agentContext: AgentContext, recipientKey: Key) { + return this.findSingleByQuery(agentContext, { + recipientKeyFingerprints: [recipientKey.fingerprint], + role: DidDocumentRole.Created, + }) } public findAllByRecipientKey(agentContext: AgentContext, recipientKey: Key) { return this.findByQuery(agentContext, { recipientKeyFingerprints: [recipientKey.fingerprint] }) } + public findReceivedDid(agentContext: AgentContext, receivedDid: string) { + return this.findSingleByQuery(agentContext, { did: receivedDid, role: DidDocumentRole.Received }) + } + + public findCreatedDid(agentContext: AgentContext, createdDid: string) { + return this.findSingleByQuery(agentContext, { did: createdDid, role: DidDocumentRole.Created }) + } + public getCreatedDids(agentContext: AgentContext, { method }: { method?: string }) { return this.findByQuery(agentContext, { role: DidDocumentRole.Created, diff --git a/packages/core/src/modules/dids/repository/__tests__/DidRecord.test.ts b/packages/core/src/modules/dids/repository/__tests__/DidRecord.test.ts index dcb5cfe44b..981b070720 100644 --- a/packages/core/src/modules/dids/repository/__tests__/DidRecord.test.ts +++ b/packages/core/src/modules/dids/repository/__tests__/DidRecord.test.ts @@ -6,7 +6,7 @@ describe('DidRecord', () => { describe('getTags', () => { it('should return default tags', () => { const didRecord = new DidRecord({ - id: 'did:example:123456789abcdefghi', + did: 'did:example:123456789abcdefghi', role: DidDocumentRole.Created, }) @@ -19,6 +19,8 @@ describe('DidRecord', () => { role: DidDocumentRole.Created, method: 'example', legacyUnqualifiedDid: 'unqualifiedDid', + did: 'did:example:123456789abcdefghi', + methodSpecificIdentifier: '123456789abcdefghi', }) }) }) diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts index 2b2493c41a..5936baac7f 100644 --- a/packages/core/src/modules/oob/OutOfBandApi.ts +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -343,11 +343,14 @@ export class OutOfBandApi { ) } - // Make sure we haven't processed this invitation before. - let outOfBandRecord = await this.findByInvitationId(outOfBandInvitation.id) + // Make sure we haven't received this invitation before. (it's fine if we created it, that means we're connecting with ourselves + let [outOfBandRecord] = await this.outOfBandService.findAllByQuery(this.agentContext, { + invitationId: outOfBandInvitation.id, + role: OutOfBandRole.Receiver, + }) if (outOfBandRecord) { throw new AriesFrameworkError( - `An out of band record with invitation ${outOfBandInvitation.id} already exists. Invitations should have a unique id.` + `An out of band record with invitation ${outOfBandInvitation.id} has already been received. Invitations should have a unique id.` ) } @@ -514,12 +517,12 @@ export class OutOfBandApi { return { outOfBandRecord } } - public async findByRecipientKey(recipientKey: Key) { - return this.outOfBandService.findByRecipientKey(this.agentContext, recipientKey) + public async findByReceivedInvitationId(receivedInvitationId: string) { + return this.outOfBandService.findByReceivedInvitationId(this.agentContext, receivedInvitationId) } - public async findByInvitationId(invitationId: string) { - return this.outOfBandService.findByInvitationId(this.agentContext, invitationId) + public async findByCreatedInvitationId(createdInvitationId: string) { + return this.outOfBandService.findByCreatedInvitationId(this.agentContext, createdInvitationId) } /** diff --git a/packages/core/src/modules/oob/OutOfBandService.ts b/packages/core/src/modules/oob/OutOfBandService.ts index 031052d2f3..f1a77c9bd5 100644 --- a/packages/core/src/modules/oob/OutOfBandService.ts +++ b/packages/core/src/modules/oob/OutOfBandService.ts @@ -36,7 +36,7 @@ export class OutOfBandService { throw new AriesFrameworkError('handshake-reuse message must have a parent thread id') } - const outOfBandRecord = await this.findByInvitationId(messageContext.agentContext, parentThreadId) + const outOfBandRecord = await this.findByCreatedInvitationId(messageContext.agentContext, parentThreadId) if (!outOfBandRecord) { throw new AriesFrameworkError('No out of band record found for handshake-reuse message') } @@ -81,7 +81,7 @@ export class OutOfBandService { throw new AriesFrameworkError('handshake-reuse-accepted message must have a parent thread id') } - const outOfBandRecord = await this.findByInvitationId(messageContext.agentContext, parentThreadId) + const outOfBandRecord = await this.findByReceivedInvitationId(messageContext.agentContext, parentThreadId) if (!outOfBandRecord) { throw new AriesFrameworkError('No out of band record found for handshake-reuse-accepted message') } @@ -165,13 +165,24 @@ export class OutOfBandService { return this.outOfBandRepository.getById(agentContext, outOfBandRecordId) } - public async findByInvitationId(agentContext: AgentContext, invitationId: string) { - return this.outOfBandRepository.findSingleByQuery(agentContext, { invitationId }) + public async findByReceivedInvitationId(agentContext: AgentContext, receivedInvitationId: string) { + return this.outOfBandRepository.findSingleByQuery(agentContext, { + invitationId: receivedInvitationId, + role: OutOfBandRole.Receiver, + }) + } + + public async findByCreatedInvitationId(agentContext: AgentContext, createdInvitationId: string) { + return this.outOfBandRepository.findSingleByQuery(agentContext, { + invitationId: createdInvitationId, + role: OutOfBandRole.Sender, + }) } - public async findByRecipientKey(agentContext: AgentContext, recipientKey: Key) { + public async findCreatedByRecipientKey(agentContext: AgentContext, recipientKey: Key) { return this.outOfBandRepository.findSingleByQuery(agentContext, { recipientKeyFingerprints: [recipientKey.fingerprint], + role: OutOfBandRole.Sender, }) } diff --git a/packages/core/src/modules/oob/__tests__/connect-to-self.e2e.test.ts b/packages/core/src/modules/oob/__tests__/connect-to-self.e2e.test.ts new file mode 100644 index 0000000000..d135c0e9ea --- /dev/null +++ b/packages/core/src/modules/oob/__tests__/connect-to-self.e2e.test.ts @@ -0,0 +1,86 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' + +import { Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getAgentOptions } from '../../../../tests/helpers' +import { HandshakeProtocol, DidExchangeState } from '../../connections' +import { OutOfBandState } from '../domain/OutOfBandState' + +import { Agent } from '@aries-framework/core' + +const faberAgentOptions = getAgentOptions('Faber Agent OOB Connect to Self', { + endpoints: ['rxjs:faber'], +}) + +describe('out of band', () => { + let faberAgent: Agent + + beforeEach(async () => { + const faberMessages = new Subject() + const subjectMap = { + 'rxjs:faber': faberMessages, + } + + faberAgent = new Agent(faberAgentOptions) + + faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await faberAgent.initialize() + }) + + afterEach(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + }) + + describe('connect with self', () => { + test(`make a connection with self using ${HandshakeProtocol.DidExchange} protocol`, async () => { + const outOfBandRecord = await faberAgent.oob.createInvitation() + const { outOfBandInvitation } = outOfBandRecord + const urlMessage = outOfBandInvitation.toUrl({ domain: 'http://example.com' }) + + // eslint-disable-next-line prefer-const + let { outOfBandRecord: receivedOutOfBandRecord, connectionRecord: receiverSenderConnection } = + await faberAgent.oob.receiveInvitationFromUrl(urlMessage) + expect(receivedOutOfBandRecord.state).toBe(OutOfBandState.PrepareResponse) + + receiverSenderConnection = await faberAgent.connections.returnWhenIsConnected(receiverSenderConnection!.id) + expect(receiverSenderConnection.state).toBe(DidExchangeState.Completed) + + let [senderReceiverConnection] = await faberAgent.connections.findAllByOutOfBandId(outOfBandRecord.id) + senderReceiverConnection = await faberAgent.connections.returnWhenIsConnected(senderReceiverConnection.id) + expect(senderReceiverConnection.state).toBe(DidExchangeState.Completed) + expect(senderReceiverConnection.protocol).toBe(HandshakeProtocol.DidExchange) + + expect(receiverSenderConnection).toBeConnectedWith(senderReceiverConnection!) + expect(senderReceiverConnection).toBeConnectedWith(receiverSenderConnection) + }) + + test(`make a connection with self using ${HandshakeProtocol.Connections} protocol`, async () => { + const outOfBandRecord = await faberAgent.oob.createInvitation({ + handshakeProtocols: [HandshakeProtocol.Connections], + }) + const { outOfBandInvitation } = outOfBandRecord + const urlMessage = outOfBandInvitation.toUrl({ domain: 'http://example.com' }) + + // eslint-disable-next-line prefer-const + let { outOfBandRecord: receivedOutOfBandRecord, connectionRecord: receiverSenderConnection } = + await faberAgent.oob.receiveInvitationFromUrl(urlMessage) + expect(receivedOutOfBandRecord.state).toBe(OutOfBandState.PrepareResponse) + + receiverSenderConnection = await faberAgent.connections.returnWhenIsConnected(receiverSenderConnection!.id) + expect(receiverSenderConnection.state).toBe(DidExchangeState.Completed) + + let [senderReceiverConnection] = await faberAgent.connections.findAllByOutOfBandId(outOfBandRecord.id) + senderReceiverConnection = await faberAgent.connections.returnWhenIsConnected(senderReceiverConnection.id) + expect(senderReceiverConnection.state).toBe(DidExchangeState.Completed) + expect(senderReceiverConnection.protocol).toBe(HandshakeProtocol.Connections) + + expect(receiverSenderConnection).toBeConnectedWith(senderReceiverConnection!) + expect(senderReceiverConnection).toBeConnectedWith(receiverSenderConnection) + }) + }) +}) diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap index d71efef325..98d562950b 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap @@ -1377,8 +1377,10 @@ Object { "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT": Object { "id": "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", "tags": Object { + "did": "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", "legacyUnqualifiedDid": "SDqTzbVuCowusqGBNbNDjH", "method": "peer", + "methodSpecificIdentifier": "1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", "recipientKeyFingerprints": Array [ "z6MktCZAQNGvWb4WHAjwBqPtXhZdDYorbSJkGW9vj1uhw1HD", ], @@ -1392,6 +1394,7 @@ Object { ], }, "createdAt": "2022-04-30T13:02:21.608Z", + "did": "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", "didDocument": Object { "@context": Array [ "https://w3id.org/did/v1", @@ -1447,8 +1450,10 @@ Object { "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56": Object { "id": "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", "tags": Object { + "did": "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", "legacyUnqualifiedDid": "GkEeb96MGT94K1HyQQzpj1", "method": "peer", + "methodSpecificIdentifier": "1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", "recipientKeyFingerprints": Array [ "z6Mko31DNE3gqMRZj1JNhv2BHb1caQshcd9njgKkEQXsgFRp", ], @@ -1462,6 +1467,7 @@ Object { ], }, "createdAt": "2022-04-30T13:02:21.646Z", + "did": "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", "didDocument": Object { "@context": Array [ "https://w3id.org/did/v1", @@ -1517,8 +1523,10 @@ Object { "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ": Object { "id": "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", "tags": Object { + "did": "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", "legacyUnqualifiedDid": "XajWZZmHGAWUvYCi7CApaG", "method": "peer", + "methodSpecificIdentifier": "1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", "recipientKeyFingerprints": Array [ "z6Mkw81EsWQioXYC9YJ7uKHCRh6LTN7sfD9sJbSPBGXmUpzC", ], @@ -1532,6 +1540,7 @@ Object { ], }, "createdAt": "2022-04-30T13:02:21.577Z", + "did": "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", "didDocument": Object { "@context": Array [ "https://w3id.org/did/v1", @@ -1587,8 +1596,10 @@ Object { "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb": Object { "id": "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", "tags": Object { + "did": "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", "legacyUnqualifiedDid": "RtH4qxVPL1Dpmdv7GytjBv", "method": "peer", + "methodSpecificIdentifier": "1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", "recipientKeyFingerprints": Array [ "z6Mkt1tsp15cnDD7wBCFgehiR2SxHX1aPxt4sueE24twH9Bd", ], @@ -1602,6 +1613,7 @@ Object { ], }, "createdAt": "2022-04-30T13:02:21.628Z", + "did": "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", "didDocument": Object { "@context": Array [ "https://w3id.org/did/v1", @@ -1657,8 +1669,10 @@ Object { "did:peer:1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU": Object { "id": "did:peer:1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU", "tags": Object { + "did": "did:peer:1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU", "legacyUnqualifiedDid": "YUH4t3KMkEJiXgmqsncrY9", "method": "peer", + "methodSpecificIdentifier": "1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU", "recipientKeyFingerprints": Array [ "z6Mkwc6efk75y4Y1agRx4NGpvtrpKxtKvMfgBEdQkHBwU8Xu", ], @@ -1672,6 +1686,7 @@ Object { ], }, "createdAt": "2022-04-30T13:02:21.608Z", + "did": "did:peer:1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU", "didDocument": Object { "@context": Array [ "https://w3id.org/did/v1", @@ -1727,8 +1742,10 @@ Object { "did:peer:1zQmZ2tdw35SaLncSHhf9zBv3e9QmJmLErZRSLsDdYowPHXy": Object { "id": "did:peer:1zQmZ2tdw35SaLncSHhf9zBv3e9QmJmLErZRSLsDdYowPHXy", "tags": Object { + "did": "did:peer:1zQmZ2tdw35SaLncSHhf9zBv3e9QmJmLErZRSLsDdYowPHXy", "legacyUnqualifiedDid": "WSwJQMBHGZbQsq9LDBTWjX", "method": "peer", + "methodSpecificIdentifier": "1zQmZ2tdw35SaLncSHhf9zBv3e9QmJmLErZRSLsDdYowPHXy", "recipientKeyFingerprints": Array [ "z6MkvW9GxjjUdL9qpaj2qQW6YBhCjZY7Zkzrks3cgpJaRjxR", ], @@ -1742,6 +1759,7 @@ Object { ], }, "createdAt": "2022-04-20T13:02:21.646Z", + "did": "did:peer:1zQmZ2tdw35SaLncSHhf9zBv3e9QmJmLErZRSLsDdYowPHXy", "didDocument": Object { "@context": Array [ "https://w3id.org/did/v1", @@ -1797,8 +1815,10 @@ Object { "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf": Object { "id": "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", "tags": Object { + "did": "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", "legacyUnqualifiedDid": "TMnQftvJJJwoYogYkQgVjg", "method": "peer", + "methodSpecificIdentifier": "1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", "recipientKeyFingerprints": Array [ "z6MktpVtPC5j91aycGPT5pceiu8EGKDzM5RLwqAZBuCgxw4V", ], @@ -1812,6 +1832,7 @@ Object { ], }, "createdAt": "2022-04-30T13:02:21.641Z", + "did": "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", "didDocument": Object { "@context": Array [ "https://w3id.org/did/v1", @@ -1867,8 +1888,10 @@ Object { "did:peer:1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga": Object { "id": "did:peer:1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga", "tags": Object { + "did": "did:peer:1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga", "legacyUnqualifiedDid": "YKc7qhYN1TckZAMUf7jgwc", "method": "peer", + "methodSpecificIdentifier": "1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga", "recipientKeyFingerprints": Array [ "z6MkwXNXTehVH7YijDmN1PtaXaSaCniTyaVepmY1EJgS15xq", ], @@ -1882,6 +1905,7 @@ Object { ], }, "createdAt": "2022-04-30T13:02:21.646Z", + "did": "did:peer:1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga", "didDocument": Object { "@context": Array [ "https://w3id.org/did/v1", @@ -1937,8 +1961,10 @@ Object { "did:peer:1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ": Object { "id": "did:peer:1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ", "tags": Object { + "did": "did:peer:1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ", "legacyUnqualifiedDid": "Ak15GBhMYpdS8XX3QDMv31", "method": "peer", + "methodSpecificIdentifier": "1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ", "recipientKeyFingerprints": Array [ "z6MkjmCrDWJVf8H2pCHcu11UDs4jb6FVu8nn5yQW24rrgez6", ], @@ -1952,6 +1978,7 @@ Object { ], }, "createdAt": "2022-04-30T13:02:21.628Z", + "did": "did:peer:1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ", "didDocument": Object { "@context": Array [ "https://w3id.org/did/v1", @@ -2007,8 +2034,10 @@ Object { "did:peer:1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui": Object { "id": "did:peer:1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui", "tags": Object { + "did": "did:peer:1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui", "legacyUnqualifiedDid": "9jTqUnV4k5ucxbyxumAaV7", "method": "peer", + "methodSpecificIdentifier": "1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui", "recipientKeyFingerprints": Array [ "z6MkjDJL4X7YGoH6gjamhZR2NzowPZqtJfX5kPuNuWiVdjMr", ], @@ -2022,6 +2051,7 @@ Object { ], }, "createdAt": "2022-04-30T13:02:21.641Z", + "did": "did:peer:1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui", "didDocument": Object { "@context": Array [ "https://w3id.org/did/v1", @@ -2077,8 +2107,10 @@ Object { "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw": Object { "id": "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", "tags": Object { + "did": "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", "legacyUnqualifiedDid": "WewvCdyBi4HL8ogyGviYVS", "method": "peer", + "methodSpecificIdentifier": "1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", "recipientKeyFingerprints": Array [ "z6MkvcgxQSsX5WA8vcBokLZ46znnhRBH6aKAGYnonEUfUnQV", ], @@ -2092,6 +2124,7 @@ Object { ], }, "createdAt": "2022-04-30T13:02:21.635Z", + "did": "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", "didDocument": Object { "@context": Array [ "https://w3id.org/did/v1", @@ -2147,8 +2180,10 @@ Object { "did:peer:1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX": Object { "id": "did:peer:1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX", "tags": Object { + "did": "did:peer:1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX", "legacyUnqualifiedDid": "3KAjJWF5NjiDTUm6JpPBQD", "method": "peer", + "methodSpecificIdentifier": "1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX", "recipientKeyFingerprints": Array [ "z6MkfiPMPxCQeSDZGMkCvm1Y2rBoPsmw4ZHMv71jXtcWRRiM", ], @@ -2162,6 +2197,7 @@ Object { ], }, "createdAt": "2022-04-30T13:02:21.577Z", + "did": "did:peer:1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX", "didDocument": Object { "@context": Array [ "https://w3id.org/did/v1", @@ -2217,8 +2253,10 @@ Object { "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv": Object { "id": "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", "tags": Object { + "did": "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", "legacyUnqualifiedDid": "Ud6AWCk6WrwfYKZUw5tJmt", "method": "peer", + "methodSpecificIdentifier": "1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", "recipientKeyFingerprints": Array [ "z6MkuWTEmH1mUo6W96zSWyH612hFHowRzNEscPYBL2CCMyC2", ], @@ -2232,6 +2270,7 @@ Object { ], }, "createdAt": "2022-04-30T13:02:21.653Z", + "did": "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", "didDocument": Object { "@context": Array [ "https://w3id.org/did/v1", diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts index fe68a1d5a1..dbc3f215d7 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts @@ -424,6 +424,7 @@ describe('0.1-0.2 | Connection', () => { expect(outOfBandRepository.findByQuery).toHaveBeenNthCalledWith(1, agentContext, { invitationId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', recipientKeyFingerprints: ['z6MksYU4MHtfmNhNm1uGMvANr9j4CBv2FymjiJtRgA36bSVH'], + role: OutOfBandRole.Sender, }) // Expect the out of band record to be created @@ -474,6 +475,7 @@ describe('0.1-0.2 | Connection', () => { expect(outOfBandRepository.findByQuery).toHaveBeenNthCalledWith(1, agentContext, { invitationId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', recipientKeyFingerprints: ['z6MksYU4MHtfmNhNm1uGMvANr9j4CBv2FymjiJtRgA36bSVH'], + role: OutOfBandRole.Sender, }) expect(outOfBandRepository.save).not.toHaveBeenCalled() @@ -540,6 +542,7 @@ describe('0.1-0.2 | Connection', () => { expect(outOfBandRepository.findByQuery).toHaveBeenNthCalledWith(1, agentContext, { invitationId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', recipientKeyFingerprints: ['z6MksYU4MHtfmNhNm1uGMvANr9j4CBv2FymjiJtRgA36bSVH'], + role: OutOfBandRole.Sender, }) expect(outOfBandRepository.save).not.toHaveBeenCalled() expect(outOfBandRepository.update).toHaveBeenCalledWith(agentContext, outOfBandRecord) diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts b/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts index ff88c5e156..6fabfbae0d 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/connection.ts @@ -152,49 +152,54 @@ export async function extractDidDocument(agent: Agent, const didRepository = agent.dependencyManager.resolve(DidRepository) const untypedConnectionRecord = connectionRecord as unknown as JsonObject - const oldDidDocJson = untypedConnectionRecord.didDoc as JsonObject | undefined + const oldOurDidDocJson = untypedConnectionRecord.didDoc as JsonObject | undefined const oldTheirDidDocJson = untypedConnectionRecord.theirDidDoc as JsonObject | undefined - if (oldDidDocJson) { - const oldDidDoc = JsonTransformer.fromJSON(oldDidDocJson, DidDoc) + if (oldOurDidDocJson) { + const oldOurDidDoc = JsonTransformer.fromJSON(oldOurDidDocJson, DidDoc) agent.config.logger.debug( - `Found a legacy did document for did ${oldDidDoc.id} in connection record didDoc. Converting it to a peer did document.` + `Found a legacy did document for did ${oldOurDidDoc.id} in connection record didDoc. Converting it to a peer did document.` ) - const newDidDocument = convertToNewDidDocument(oldDidDoc) + const newOurDidDocument = convertToNewDidDocument(oldOurDidDoc) // Maybe we already have a record for this did because the migration failed previously - let didRecord = await didRepository.findById(agent.context, newDidDocument.id) - - if (!didRecord) { - agent.config.logger.debug(`Creating did record for did ${newDidDocument.id}`) - didRecord = new DidRecord({ - id: newDidDocument.id, + // NOTE: in 0.3.0 the id property was updated to be a uuid, and a new did property was added. As this is the update from 0.1 to 0.2, + // the `id` property of the record is still the did here. + let ourDidRecord = await didRepository.findById(agent.context, newOurDidDocument.id) + + if (!ourDidRecord) { + agent.config.logger.debug(`Creating did record for our did ${newOurDidDocument.id}`) + ourDidRecord = new DidRecord({ + // NOTE: in 0.3.0 the id property was updated to be a uuid, and a new did property was added. Here we make the id and did property both the did. + // In the 0.3.0 update the `id` property will be updated to an uuid. + id: newOurDidDocument.id, + did: newOurDidDocument.id, role: DidDocumentRole.Created, - didDocument: newDidDocument, + didDocument: newOurDidDocument, createdAt: connectionRecord.createdAt, tags: { - recipientKeyFingerprints: newDidDocument.recipientKeys.map((key) => key.fingerprint), + recipientKeyFingerprints: newOurDidDocument.recipientKeys.map((key) => key.fingerprint), }, }) - didRecord.metadata.set(DidRecordMetadataKeys.LegacyDid, { - unqualifiedDid: oldDidDoc.id, - didDocumentString: JsonEncoder.toString(oldDidDocJson), + ourDidRecord.metadata.set(DidRecordMetadataKeys.LegacyDid, { + unqualifiedDid: oldOurDidDoc.id, + didDocumentString: JsonEncoder.toString(oldOurDidDocJson), }) - await didRepository.save(agent.context, didRecord) + await didRepository.save(agent.context, ourDidRecord) - agent.config.logger.debug(`Successfully saved did record for did ${newDidDocument.id}`) + agent.config.logger.debug(`Successfully saved did record for did ${newOurDidDocument.id}`) } else { - agent.config.logger.debug(`Found existing did record for did ${newDidDocument.id}, not creating did record.`) + agent.config.logger.debug(`Found existing did record for did ${newOurDidDocument.id}, not creating did record.`) } agent.config.logger.debug(`Deleting old did document from connection record and storing new did:peer did`) // Remove didDoc and assign the new did:peer did to did delete untypedConnectionRecord.didDoc - connectionRecord.did = newDidDocument.id + connectionRecord.did = newOurDidDocument.id } else { agent.config.logger.debug( `Did not find a did document in connection record didDoc. Not converting it to a peer did document.` @@ -211,13 +216,18 @@ export async function extractDidDocument(agent: Agent, const newTheirDidDocument = convertToNewDidDocument(oldTheirDidDoc) // Maybe we already have a record for this did because the migration failed previously - let didRecord = await didRepository.findById(agent.context, newTheirDidDocument.id) + // NOTE: in 0.3.0 the id property was updated to be a uuid, and a new did property was added. As this is the update from 0.1 to 0.2, + // the `id` property of the record is still the did here. + let theirDidRecord = await didRepository.findById(agent.context, newTheirDidDocument.id) - if (!didRecord) { + if (!theirDidRecord) { agent.config.logger.debug(`Creating did record for theirDid ${newTheirDidDocument.id}`) - didRecord = new DidRecord({ + theirDidRecord = new DidRecord({ + // NOTE: in 0.3.0 the id property was updated to be a uuid, and a new did property was added. Here we make the id and did property both the did. + // In the 0.3.0 update the `id` property will be updated to an uuid. id: newTheirDidDocument.id, + did: newTheirDidDocument.id, role: DidDocumentRole.Received, didDocument: newTheirDidDocument, createdAt: connectionRecord.createdAt, @@ -226,12 +236,12 @@ export async function extractDidDocument(agent: Agent, }, }) - didRecord.metadata.set(DidRecordMetadataKeys.LegacyDid, { + theirDidRecord.metadata.set(DidRecordMetadataKeys.LegacyDid, { unqualifiedDid: oldTheirDidDoc.id, didDocumentString: JsonEncoder.toString(oldTheirDidDocJson), }) - await didRepository.save(agent.context, didRecord) + await didRepository.save(agent.context, theirDidRecord) agent.config.logger.debug(`Successfully saved did record for theirDid ${newTheirDidDocument.id}`) } else { @@ -313,16 +323,18 @@ export async function migrateToOobRecord( const outOfBandInvitation = convertToNewInvitation(oldInvitation) - // If both the recipientKeys and the @id match we assume the connection was created using the same invitation. + // If both the recipientKeys, the @id and the role match we assume the connection was created using the same invitation. const recipientKeyFingerprints = outOfBandInvitation .getInlineServices() .map((s) => s.recipientKeys) .reduce((acc, curr) => [...acc, ...curr], []) .map((didKey) => DidKey.fromDid(didKey).key.fingerprint) + const oobRole = connectionRecord.role === DidExchangeRole.Responder ? OutOfBandRole.Sender : OutOfBandRole.Receiver const oobRecords = await oobRepository.findByQuery(agent.context, { invitationId: oldInvitation.id, recipientKeyFingerprints, + role: oobRole, }) let oobRecord: OutOfBandRecord | undefined = oobRecords[0] @@ -330,9 +342,6 @@ export async function migrateToOobRecord( if (!oobRecord) { agent.config.logger.debug(`Create out of band record.`) - const oobRole = - connectionRecord.role === DidExchangeRole.Responder ? OutOfBandRole.Sender : OutOfBandRole.Receiver - const connectionRole = connectionRecord.role as DidExchangeRole const connectionState = connectionRecord.state as DidExchangeState const oobState = oobStateFromDidExchangeRoleAndState(connectionRole, connectionState) @@ -368,7 +377,7 @@ export async function migrateToOobRecord( await oobRepository.update(agent.context, oobRecord) await connectionRepository.delete(agent.context, connectionRecord) agent.config.logger.debug( - `Set reusable=true for out of band record with invitation @id ${oobRecord.outOfBandInvitation.id}.` + `Set reusable=true for out of band record with invitation @id ${oobRecord.outOfBandInvitation.id} and role ${oobRole}.` ) return diff --git a/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/did.test.ts b/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/did.test.ts new file mode 100644 index 0000000000..2f4bd97710 --- /dev/null +++ b/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/did.test.ts @@ -0,0 +1,74 @@ +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../../tests/helpers' +import { AgentContext } from '../../../../../agent' +import { Agent } from '../../../../../agent/Agent' +import { DidDocumentRole, DidRecord } from '../../../../../modules/dids' +import { DidRepository } from '../../../../../modules/dids/repository/DidRepository' +import { JsonTransformer } from '../../../../../utils' +import { Metadata } from '../../../../Metadata' +import * as testModule from '../did' + +const agentConfig = getAgentConfig('Migration DidRecord 0.2-0.3') +const agentContext = getAgentContext() + +jest.mock('../../../../../modules/dids/repository/DidRepository') +const DidRepositoryMock = DidRepository as jest.Mock +const didRepository = new DidRepositoryMock() + +jest.mock('../../../../../agent/Agent', () => { + return { + Agent: jest.fn(() => ({ + config: agentConfig, + context: agentContext, + dependencyManager: { + resolve: jest.fn(() => didRepository), + }, + })), + } +}) + +// Mock typed object +const AgentMock = Agent as jest.Mock + +describe('0.2-0.3 | Did', () => { + let agent: Agent + + beforeEach(() => { + agent = new AgentMock() + }) + + describe('migrateDidRecordToV0_3()', () => { + it('should fetch all records and apply the needed updates ', async () => { + const records: DidRecord[] = [getDid({ id: 'did:peer:123' })] + + mockFunction(didRepository.getAll).mockResolvedValue(records) + + await testModule.migrateDidRecordToV0_3(agent) + + expect(didRepository.getAll).toHaveBeenCalledTimes(1) + expect(didRepository.save).toHaveBeenCalledTimes(1) + + const [, didRecord] = mockFunction(didRepository.save).mock.calls[0] + expect(didRecord).toEqual({ + type: 'DidRecord', + id: expect.any(String), + did: 'did:peer:123', + metadata: expect.any(Metadata), + role: DidDocumentRole.Created, + _tags: {}, + }) + + expect(didRepository.deleteById).toHaveBeenCalledTimes(1) + expect(didRepository.deleteById).toHaveBeenCalledWith(expect.any(AgentContext), 'did:peer:123') + }) + }) +}) + +function getDid({ id }: { id: string }) { + return JsonTransformer.fromJSON( + { + role: DidDocumentRole.Created, + id, + }, + DidRecord + ) +} diff --git a/packages/core/src/storage/migration/updates/0.2-0.3/did.ts b/packages/core/src/storage/migration/updates/0.2-0.3/did.ts new file mode 100644 index 0000000000..f051b0e339 --- /dev/null +++ b/packages/core/src/storage/migration/updates/0.2-0.3/did.ts @@ -0,0 +1,42 @@ +import type { BaseAgent } from '../../../../agent/BaseAgent' + +import { DidRepository } from '../../../../modules/dids' +import { uuid } from '../../../../utils/uuid' + +/** + * Migrates the {@link DidRecord} to 0.3 compatible format. It fetches all records from storage + * and applies the needed updates to the records. After a record has been transformed, it is updated + * in storage and the next record will be transformed. + * + * The following transformations are applied: + * - {@link extractDidAsSeparateProperty} + */ +export async function migrateDidRecordToV0_3(agent: Agent) { + agent.config.logger.info('Migrating did records to storage version 0.3') + const didRepository = agent.dependencyManager.resolve(DidRepository) + + agent.config.logger.debug(`Fetching all did records from storage`) + const allDids = await didRepository.getAll(agent.context) + + agent.config.logger.debug(`Found a total of ${allDids.length} did records to update.`) + for (const didRecord of allDids) { + agent.config.logger.debug(`Migrating did record with id ${didRecord.id} to storage version 0.3`) + + const newId = uuid() + + agent.config.logger.debug(`Updating id ${didRecord.id} to ${newId} for did record`) + // The id of the didRecord was previously the did itself. This prevented us from connecting to ourselves + didRecord.did = didRecord.id + didRecord.id = newId + + // Save new did record + await didRepository.save(agent.context, didRecord) + + // Delete old did record + await didRepository.deleteById(agent.context, didRecord.did) + + agent.config.logger.debug( + `Successfully migrated did record with old id ${didRecord.did} to new id ${didRecord.id} to storage version 0.3` + ) + } +} diff --git a/packages/core/tests/oob-mediation-provision.test.ts b/packages/core/tests/oob-mediation-provision.test.ts index 3a95ee0c85..abfebc9f14 100644 --- a/packages/core/tests/oob-mediation-provision.test.ts +++ b/packages/core/tests/oob-mediation-provision.test.ts @@ -110,7 +110,7 @@ describe('out of band with mediation set up with provision method', () => { expect(basicMessage.content).toBe('hello') // Test if we can call provision for the same out-of-band record, respectively connection - const reusedOutOfBandRecord = await aliceAgent.oob.findByInvitationId(mediatorOutOfBandInvitation.id) + const reusedOutOfBandRecord = await aliceAgent.oob.findByReceivedInvitationId(mediatorOutOfBandInvitation.id) const [reusedAliceMediatorConnection] = reusedOutOfBandRecord ? await aliceAgent.connections.findAllByOutOfBandId(reusedOutOfBandRecord.id) : [] diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index e6a9ba3464..96f7715bd6 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -588,7 +588,7 @@ describe('out of band', () => { // Try to receive the invitation again await expect(aliceAgent.oob.receiveInvitation(outOfBandInvitation)).rejects.toThrow( new AriesFrameworkError( - `An out of band record with invitation ${outOfBandInvitation.id} already exists. Invitations should have a unique id.` + `An out of band record with invitation ${outOfBandInvitation.id} has already been received. Invitations should have a unique id.` ) ) }) From bc912c37f445ccd85aac7b496fd06fda45a50234 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 20 Dec 2022 23:43:51 +0800 Subject: [PATCH 102/125] refactor: jsonld credential format improvements (#1178) Signed-off-by: Timo Glastra --- jest.config.ts | 2 +- ...proof.credentials.propose-offerBbs.test.ts | 4 +- .../JsonLdCredentialFormatService.test.ts | 17 ++- ...alOptions.ts => JsonLdCredentialDetail.ts} | 12 +- ...93.ts => JsonLdCredentialDetailOptions.ts} | 16 +-- .../formats/jsonld/JsonLdCredentialFormat.ts | 94 +++++++++----- .../jsonld/JsonLdCredentialFormatService.ts | 116 +++++++++--------- .../credentials/formats/jsonld/index.ts | 4 +- ...ldproof.connectionless-credentials.test.ts | 4 +- ...v2.ldproof.credentials-auto-accept.test.ts | 4 +- ...f.credentials.propose-offerED25519.test.ts | 6 +- packages/core/src/utils/deepEquality.ts | 4 +- packages/core/src/utils/objectEquality.ts | 6 +- 13 files changed, 161 insertions(+), 128 deletions(-) rename packages/core/src/modules/credentials/formats/jsonld/{JsonLdCredentialOptions.ts => JsonLdCredentialDetail.ts} (60%) rename packages/core/src/modules/credentials/formats/jsonld/{JsonLdOptionsRFC0593.ts => JsonLdCredentialDetailOptions.ts} (64%) diff --git a/jest.config.ts b/jest.config.ts index d2f8f320db..c4f6c766dc 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -6,7 +6,7 @@ const config: Config.InitialOptions = { ...base, roots: [''], projects: [ - '/packages/*', + '/packages/*/jest.config.ts', '/tests/jest.config.ts', '/samples/extension-module/jest.config.ts', ], diff --git a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts index 94f100990a..32074932a8 100644 --- a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts +++ b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts @@ -1,6 +1,6 @@ import type { Agent } from '../../core/src/agent/Agent' import type { ConnectionRecord } from '../../core/src/modules/connections' -import type { JsonCredential, JsonLdSignCredentialFormat } from '../../core/src/modules/credentials/formats/jsonld' +import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../core/src/modules/credentials/formats/jsonld' import type { Wallet } from '../../core/src/wallet' import { InjectionSymbols } from '../../core/src/constants' @@ -28,7 +28,7 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { let wallet let issuerDidKey: DidKey let didCommMessageRepository: DidCommMessageRepository - let signCredentialOptions: JsonLdSignCredentialFormat + let signCredentialOptions: JsonLdCredentialDetailFormat const seed = 'testseed000000000000000000000001' beforeAll(async () => { ;({ faberAgent, aliceAgent, aliceConnection } = await setupCredentialTests( diff --git a/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts index 9e3b15105f..b94e44651b 100644 --- a/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts +++ b/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts @@ -3,7 +3,7 @@ import type { CredentialFormatService } from '../../formats' import type { JsonCredential, JsonLdCredentialFormat, - JsonLdSignCredentialFormat, + JsonLdCredentialDetailFormat, } from '../../formats/jsonld/JsonLdCredentialFormat' import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' import type { V2OfferCredentialMessageOptions } from '../../protocol/v2/messages/V2OfferCredentialMessage' @@ -155,7 +155,7 @@ const inputDocAsJson: JsonCredential = { } const verificationMethod = `8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K#8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K` -const signCredentialOptions: JsonLdSignCredentialFormat = { +const signCredentialOptions: JsonLdCredentialDetailFormat = { credential: inputDocAsJson, options: { proofPurpose: 'assertionMethod', @@ -310,7 +310,7 @@ describe('JsonLd CredentialFormatService', () => { ]) const service = jsonLdFormatService as JsonLdCredentialFormatService - const credentialRequest = requestAttachment.getDataAsJson() + const credentialRequest = requestAttachment.getDataAsJson() // calls private method in the format service const verificationMethod = await service['deriveVerificationMethod']( @@ -373,7 +373,7 @@ describe('JsonLd CredentialFormatService', () => { state: CredentialState.RequestSent, }) let w3c: W3cCredentialRecord - let signCredentialOptionsWithProperty: JsonLdSignCredentialFormat + let signCredentialOptionsWithProperty: JsonLdCredentialDetailFormat beforeEach(async () => { signCredentialOptionsWithProperty = signCredentialOptions signCredentialOptionsWithProperty.options = { @@ -437,10 +437,13 @@ describe('JsonLd CredentialFormatService', () => { requestAttachment: requestAttachment, credentialRecord, }) - ).rejects.toThrow('Received credential subject does not match subject from credential request') + ).rejects.toThrow('Received credential does not match credential request') }) test('throws error if credential domain not equal to request domain', async () => { + // this property is not supported yet by us, but could be in the credential we received + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error signCredentialOptionsWithProperty.options.domain = 'https://test.com' const requestAttachmentWithDomain = new Attachment({ mimeType: 'application/json', @@ -462,7 +465,11 @@ describe('JsonLd CredentialFormatService', () => { }) test('throws error if credential challenge not equal to request challenge', async () => { + // this property is not supported yet by us, but could be in the credential we received + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error signCredentialOptionsWithProperty.options.challenge = '7bf32d0b-39d4-41f3-96b6-45de52988e4c' + const requestAttachmentWithChallenge = new Attachment({ mimeType: 'application/json', data: new AttachmentData({ diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialOptions.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialDetail.ts similarity index 60% rename from packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialOptions.ts rename to packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialDetail.ts index 410a190902..734fc6366c 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialOptions.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialDetail.ts @@ -2,11 +2,11 @@ import { Expose, Type } from 'class-transformer' import { W3cCredential } from '../../../vc/models/credential/W3cCredential' -import { JsonLdOptionsRFC0593 } from './JsonLdOptionsRFC0593' +import { JsonLdCredentialDetailOptions } from './JsonLdCredentialDetailOptions' -export interface JsonLdCredentialDetailOptions { +export interface JsonLdCredentialDetailInputOptions { credential: W3cCredential - options: JsonLdOptionsRFC0593 + options: JsonLdCredentialDetailOptions } /** @@ -14,7 +14,7 @@ export interface JsonLdCredentialDetailOptions { * */ export class JsonLdCredentialDetail { - public constructor(options: JsonLdCredentialDetailOptions) { + public constructor(options: JsonLdCredentialDetailInputOptions) { if (options) { this.credential = options.credential this.options = options.options @@ -25,6 +25,6 @@ export class JsonLdCredentialDetail { public credential!: W3cCredential @Expose({ name: 'options' }) - @Type(() => JsonLdOptionsRFC0593) - public options!: JsonLdOptionsRFC0593 + @Type(() => JsonLdCredentialDetailOptions) + public options!: JsonLdCredentialDetailOptions } diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdOptionsRFC0593.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialDetailOptions.ts similarity index 64% rename from packages/core/src/modules/credentials/formats/jsonld/JsonLdOptionsRFC0593.ts rename to packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialDetailOptions.ts index 77eff33a6c..bbc33c06c6 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdOptionsRFC0593.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialDetailOptions.ts @@ -1,11 +1,11 @@ import { IsObject, IsOptional, IsString } from 'class-validator' -export interface JsonLdOptionsCredentialStatusOptions { +export interface JsonLdCredentialDetailCredentialStatusOptions { type: string } -export class JsonLdOptionsCredentialStatus { - public constructor(options: JsonLdOptionsCredentialStatusOptions) { +export class JsonLdCredentialDetailCredentialStatus { + public constructor(options: JsonLdCredentialDetailCredentialStatusOptions) { if (options) { this.type = options.type } @@ -14,17 +14,17 @@ export class JsonLdOptionsCredentialStatus { public type!: string } -export interface JsonLdOptionsRFC0593Options { +export interface JsonLdCredentialDetailOptionsOptions { proofPurpose: string created?: string domain?: string challenge?: string - credentialStatus?: JsonLdOptionsCredentialStatus + credentialStatus?: JsonLdCredentialDetailCredentialStatus proofType: string } -export class JsonLdOptionsRFC0593 { - public constructor(options: JsonLdOptionsRFC0593Options) { +export class JsonLdCredentialDetailOptions { + public constructor(options: JsonLdCredentialDetailOptionsOptions) { if (options) { this.proofPurpose = options.proofPurpose this.created = options.created @@ -55,5 +55,5 @@ export class JsonLdOptionsRFC0593 { @IsOptional() @IsObject() - public credentialStatus?: JsonLdOptionsCredentialStatus + public credentialStatus?: JsonLdCredentialDetailCredentialStatus } diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts index e4d0fb111c..990ea4a86e 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts @@ -2,7 +2,6 @@ import type { JsonObject } from '../../../../types' import type { SingleOrArray } from '../../../../utils' import type { IssuerOptions } from '../../../vc/models/credential/Issuer' import type { CredentialFormat } from '../CredentialFormat' -import type { JsonLdOptionsRFC0593 } from './JsonLdOptionsRFC0593' export interface JsonCredential { '@context': Array | JsonObject @@ -15,19 +14,53 @@ export interface JsonCredential { [key: string]: unknown } -// this is the API interface (only) -export interface JsonLdSignCredentialFormat { +/** + * Format for creating a jsonld proposal, offer or request. + */ +export interface JsonLdCredentialDetailFormat { credential: JsonCredential - options: JsonLdOptionsRFC0593 + options: { + proofPurpose: string + proofType: string + } } -// use this interface internally as the above may diverge in future -export interface SignCredentialOptionsRFC0593 { - credential: JsonCredential - options: JsonLdOptionsRFC0593 +// use empty object in the acceptXXX jsonld format interface so we indicate that +// the jsonld format service needs to be invoked +type EmptyObject = Record + +/** + * Format for accepting a jsonld credential request. Optionally allows the verification + * method to use to sign the credential. + */ +export interface JsonLdAcceptRequestFormat { + verificationMethod?: string } -export interface JsonVerifiableCredential extends JsonLdSignCredentialFormat { +export interface JsonLdCredentialFormat extends CredentialFormat { + formatKey: 'jsonld' + credentialRecordType: 'w3c' + credentialFormats: { + createProposal: JsonLdCredentialDetailFormat + acceptProposal: EmptyObject + createOffer: JsonLdCredentialDetailFormat + acceptOffer: EmptyObject + createRequest: JsonLdCredentialDetailFormat + acceptRequest: JsonLdAcceptRequestFormat + } + formatData: { + proposal: JsonLdFormatDataCredentialDetail + offer: JsonLdFormatDataCredentialDetail + request: JsonLdFormatDataCredentialDetail + credential: JsonLdFormatDataVerifiableCredential + } +} + +/** + * Represents a signed verifiable credential. Only meant to be used for credential + * format data interfaces. + */ +export interface JsonLdFormatDataVerifiableCredential extends JsonCredential { proof: { type: string proofPurpose: string @@ -42,30 +75,27 @@ export interface JsonVerifiableCredential extends JsonLdSignCredentialFormat { } } -// use empty object in the acceptXXX jsonld format interface so we indicate that -// the jsonld format service needs to be invoked -type EmptyObject = Record - -// it is an option to provide the verification method in acceptRequest -export interface JsonLdCreateRequestFormat { - verificationMethod?: string +/** + * Represents the jsonld credential detail. Only meant to be used for credential + * format data interfaces. + */ +export interface JsonLdFormatDataCredentialDetail { + credential: JsonCredential + options: JsonLdFormatDataCredentialDetailOptions } -export interface JsonLdCredentialFormat extends CredentialFormat { - formatKey: 'jsonld' - credentialRecordType: 'w3c' - credentialFormats: { - createProposal: JsonLdSignCredentialFormat - acceptProposal: EmptyObject - createOffer: JsonLdSignCredentialFormat - acceptOffer: EmptyObject - createRequest: JsonLdSignCredentialFormat - acceptRequest: JsonLdCreateRequestFormat - } - formatData: { - proposal: JsonLdSignCredentialFormat - offer: JsonLdSignCredentialFormat - request: JsonLdSignCredentialFormat - credential: JsonVerifiableCredential +/** + * Represents the jsonld credential detail options. Only meant to be used for credential + * format data interfaces. + */ +export interface JsonLdFormatDataCredentialDetailOptions { + proofPurpose: string + proofType: string + created?: string + domain?: string + challenge?: string + credentialStatus?: { + type: string + [key: string]: unknown } } diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts index e72143812e..a5df1d86e6 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts @@ -1,11 +1,9 @@ import type { AgentContext } from '../../../../agent' -import type { LinkedDataProof } from '../../../vc/models/LinkedDataProof' import type { CredentialFormatService } from '../CredentialFormatService' import type { FormatAcceptOfferOptions, FormatAcceptProposalOptions, FormatAcceptRequestOptions, - FormatAutoRespondCredentialOptions, FormatAutoRespondOfferOptions, FormatAutoRespondProposalOptions, FormatAutoRespondRequestOptions, @@ -17,17 +15,18 @@ import type { CredentialFormatCreateReturn, FormatProcessCredentialOptions, FormatProcessOptions, + FormatAutoRespondCredentialOptions, } from '../CredentialFormatServiceOptions' import type { JsonLdCredentialFormat, - JsonLdSignCredentialFormat, JsonCredential, - SignCredentialOptionsRFC0593, + JsonLdFormatDataCredentialDetail, + JsonLdFormatDataVerifiableCredential, } from './JsonLdCredentialFormat' import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../../error' -import { JsonEncoder, objectEquality } from '../../../../utils' +import { JsonEncoder, areObjectsEqual } from '../../../../utils' import { JsonTransformer } from '../../../../utils/JsonTransformer' import { findVerificationMethodByKeyType } from '../../../dids/domain/DidDocument' import { DidResolverService } from '../../../dids/services/DidResolverService' @@ -35,7 +34,7 @@ import { W3cCredentialService } from '../../../vc' import { W3cCredential, W3cVerifiableCredential } from '../../../vc/models' import { CredentialFormatSpec } from '../../models/CredentialFormatSpec' -import { JsonLdCredentialDetail } from './JsonLdCredentialOptions' +import { JsonLdCredentialDetail } from './JsonLdCredentialDetail' const JSONLD_VC_DETAIL = 'aries/ld-proof-vc-detail@v1.0' const JSONLD_VC = 'aries/ld-proof-vc@1.0' @@ -67,7 +66,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService { - const credProposalJson = attachment.getDataAsJson() + const credProposalJson = attachment.getDataAsJson() if (!credProposalJson) { throw new AriesFrameworkError('Missing jsonld credential proposal data payload') @@ -97,7 +96,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService() + const credentialProposal = proposalAttachment.getDataAsJson() JsonTransformer.fromJSON(credentialProposal, JsonLdCredentialDetail) const offerData = credentialProposal @@ -138,7 +137,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService() + const credentialOfferJson = attachment.getDataAsJson() if (!credentialOfferJson) { throw new AriesFrameworkError('Missing jsonld credential offer data payload') @@ -151,7 +150,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService ): Promise { - const credentialOffer = offerAttachment.getDataAsJson() + const credentialOffer = offerAttachment.getDataAsJson() // validate JsonTransformer.fromJSON(credentialOffer, JsonLdCredentialDetail) @@ -195,7 +194,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService { - const requestJson = attachment.getDataAsJson() + const requestJson = attachment.getDataAsJson() if (!requestJson) { throw new AriesFrameworkError('Missing jsonld credential request data payload') @@ -213,7 +212,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService() + const credentialRequest = requestAttachment.getDataAsJson() const verificationMethod = credentialFormats?.jsonld?.verificationMethod ?? @@ -229,9 +228,13 @@ export class JsonLdCredentialFormatService implements CredentialFormatService options[field] !== undefined) + + if (foundFields.length > 0) { throw new AriesFrameworkError( - 'The fields challenge, domain and credentialStatus not currently supported in credential options ' + `Some fields are not currently supported in credential options: ${foundFields.join(', ')}` ) } @@ -255,7 +258,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService { const didResolver = agentContext.dependencyManager.resolve(DidResolverService) const w3cCredentialService = agentContext.dependencyManager.resolve(W3cCredentialService) @@ -301,18 +304,10 @@ export class JsonLdCredentialFormatService implements CredentialFormatService() const credential = JsonTransformer.fromJSON(credentialAsJson, W3cVerifiableCredential) + const requestAsJson = requestAttachment.getDataAsJson() - // compare stuff in the proof object of the credential and request...based on aca-py - - const requestAsJson = requestAttachment.getDataAsJson() - - if (Array.isArray(credential.proof)) { - throw new AriesFrameworkError('Credential arrays are not supported') - } else { - // do checks here - this.compareCredentialSubject(credential, requestAsJson) - this.compareProofs(credential.proof, requestAsJson) - } + // Verify the credential request matches the credential + this.verifyReceivedCredentialMatchesRequest(credential, requestAsJson) // verify signatures of the credential const result = await w3cCredentialService.verifyCredential(agentContext, { credential }) @@ -330,41 +325,47 @@ export class JsonLdCredentialFormatService implements CredentialFormatService { - // FIXME: this implementation doesn't make sense. We can't loop over stringified objects... const obj1 = message1.getDataAsJson() const obj2 = message2.getDataAsJson() - return objectEquality(obj1, obj2) + return areObjectsEqual(obj1, obj2) } public shouldAutoRespondToProposal( @@ -408,24 +408,20 @@ export class JsonLdCredentialFormatService implements CredentialFormatService() - const credential = JsonTransformer.fromJSON(credentialAsJson, W3cVerifiableCredential) - - if (Array.isArray(credential.proof)) { - throw new AriesFrameworkError('Credential arrays are not supported') - } else { - // do checks here - try { - const requestAsJson = requestAttachment.getDataAsJson() - - this.compareCredentialSubject(credential, requestAsJson) - this.compareProofs(credential.proof, requestAsJson) - return true - } catch (error) { - return false - } + const credentialJson = credentialAttachment.getDataAsJson() + const w3cCredential = JsonTransformer.fromJSON(credentialJson, W3cVerifiableCredential) + const request = requestAttachment.getDataAsJson() + + try { + // This check is also done in the processCredential method, but we do it here as well + // to be certain we don't skip the check + this.verifyReceivedCredentialMatchesRequest(w3cCredential, request) + + return true + } catch (error) { + return false } } diff --git a/packages/core/src/modules/credentials/formats/jsonld/index.ts b/packages/core/src/modules/credentials/formats/jsonld/index.ts index bce1b302d7..f672c3e342 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/index.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/index.ts @@ -1,4 +1,4 @@ export * from './JsonLdCredentialFormatService' export * from './JsonLdCredentialFormat' -export * from './JsonLdCredentialOptions' -export * from './JsonLdOptionsRFC0593' +export * from './JsonLdCredentialDetail' +export * from './JsonLdCredentialDetailOptions' diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts index 501c079de4..8b1ed1cc1e 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts @@ -1,7 +1,7 @@ import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' import type { Wallet } from '../../../../../wallet' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { JsonCredential, JsonLdSignCredentialFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' +import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' import type { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' import { ReplaySubject, Subject } from 'rxjs' @@ -54,7 +54,7 @@ const aliceAgentOptions = getAgentOptions( ) let wallet -let signCredentialOptions: JsonLdSignCredentialFormat +let signCredentialOptions: JsonLdCredentialDetailFormat describe('credentials', () => { let faberAgent: Agent diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts index 80766e53c8..ce0526ffc2 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts @@ -1,7 +1,7 @@ import type { Agent } from '../../../../../agent/Agent' import type { Wallet } from '../../../../../wallet' import type { ConnectionRecord } from '../../../../connections' -import type { JsonCredential, JsonLdSignCredentialFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' +import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' @@ -30,7 +30,7 @@ describe('credentials', () => { let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord let aliceCredentialRecord: CredentialExchangeRecord - let signCredentialOptions: JsonLdSignCredentialFormat + let signCredentialOptions: JsonLdCredentialDetailFormat let wallet const seed = 'testseed000000000000000000000001' diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts index ff4ff1b326..d53b4b961e 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -1,7 +1,7 @@ import type { Awaited } from '../../../../../types' import type { Wallet } from '../../../../../wallet' import type { ConnectionRecord } from '../../../../connections' -import type { JsonCredential, JsonLdSignCredentialFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' +import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' @@ -53,7 +53,7 @@ describe('credentials', () => { }, } - let signCredentialOptions: JsonLdSignCredentialFormat + let signCredentialOptions: JsonLdCredentialDetailFormat let wallet const seed = 'testseed000000000000000000000001' @@ -319,7 +319,7 @@ describe('credentials', () => { messageClass: V2OfferCredentialMessage, }) - const credOfferJson = offerMessage?.offerAttachments[1].getDataAsJson() + const credOfferJson = offerMessage?.offerAttachments[1].getDataAsJson() expect(credOfferJson).toMatchObject({ credential: { diff --git a/packages/core/src/utils/deepEquality.ts b/packages/core/src/utils/deepEquality.ts index 0ebfae7971..a8bb286d73 100644 --- a/packages/core/src/utils/deepEquality.ts +++ b/packages/core/src/utils/deepEquality.ts @@ -1,4 +1,4 @@ -import { objectEquality } from './objectEquality' +import { areObjectsEqual } from './objectEquality' // eslint-disable-next-line @typescript-eslint/no-explicit-any export function deepEquality(x: any, y: any): boolean { @@ -7,7 +7,7 @@ export function deepEquality(x: any, y: any): boolean { const isXSimpleEqualY = simpleEqual(x, y) if (isXSimpleEqualY !== undefined) return isXSimpleEqualY - if (!(x instanceof Map) || !(y instanceof Map)) return objectEquality(x, y) + if (!(x instanceof Map) || !(y instanceof Map)) return areObjectsEqual(x, y) const xMap = x as Map const yMap = y as Map diff --git a/packages/core/src/utils/objectEquality.ts b/packages/core/src/utils/objectEquality.ts index 9d0a18e0b3..5288d1a52d 100644 --- a/packages/core/src/utils/objectEquality.ts +++ b/packages/core/src/utils/objectEquality.ts @@ -1,14 +1,14 @@ // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function objectEquality(a: any, b: any): boolean { +export function areObjectsEqual(a: any, b: any): boolean { if (typeof a == 'object' && a != null && typeof b == 'object' && b != null) { if (Object.keys(a).length !== Object.keys(b).length) return false for (const key in a) { - if (!(key in b) || !objectEquality(a[key], b[key])) { + if (!(key in b) || !areObjectsEqual(a[key], b[key])) { return false } } for (const key in b) { - if (!(key in a) || !objectEquality(b[key], a[key])) { + if (!(key in a) || !areObjectsEqual(b[key], a[key])) { return false } } From d5c28bc2764aa48d124fe18307f0233e7b59153a Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 21 Dec 2022 13:19:12 +0800 Subject: [PATCH 103/125] refactor(dids): use class instances in module config (#1175) Signed-off-by: Timo Glastra --- packages/core/src/modules/dids/DidsModule.ts | 11 ---- .../core/src/modules/dids/DidsModuleConfig.ts | 64 ++++++++++++++----- .../modules/dids/__tests__/DidsModule.test.ts | 37 +---------- .../dids/__tests__/DidsModuleConfig.test.ts | 44 ++++++++++--- .../modules/dids/__tests__/peer-did.test.ts | 22 +++++-- .../src/modules/dids/domain/DidRegistrar.ts | 2 - .../src/modules/dids/domain/DidResolver.ts | 2 - .../dids/methods/key/KeyDidRegistrar.ts | 11 +--- .../dids/methods/key/KeyDidResolver.ts | 3 - .../key/__tests__/KeyDidRegistrar.test.ts | 16 ++--- .../dids/methods/peer/PeerDidRegistrar.ts | 10 +-- .../dids/methods/peer/PeerDidResolver.ts | 14 +--- .../peer/__tests__/PeerDidRegistrar.test.ts | 17 +++-- .../methods/sov/IndySdkSovDidRegistrar.ts | 57 +++++++---------- .../dids/methods/sov/IndySdkSovDidResolver.ts | 52 ++++++--------- .../__tests__/IndySdkSovDidRegistrar.test.ts | 26 ++++---- .../__tests__/IndySdkSovDidResolver.test.ts | 20 ++---- .../dids/methods/web/WebDidResolver.ts | 2 - .../modules/dids/repository/DidRepository.ts | 4 ++ .../dids/services/DidRegistrarService.ts | 15 ++--- .../dids/services/DidResolverService.ts | 15 ++--- .../__tests__/DidRegistrarService.test.ts | 8 ++- .../__tests__/DidResolverService.test.ts | 6 +- 23 files changed, 220 insertions(+), 238 deletions(-) diff --git a/packages/core/src/modules/dids/DidsModule.ts b/packages/core/src/modules/dids/DidsModule.ts index dbf46d7cad..cf438e3ae8 100644 --- a/packages/core/src/modules/dids/DidsModule.ts +++ b/packages/core/src/modules/dids/DidsModule.ts @@ -3,7 +3,6 @@ import type { DidsModuleConfigOptions } from './DidsModuleConfig' import { DidsApi } from './DidsApi' import { DidsModuleConfig } from './DidsModuleConfig' -import { DidResolverToken, DidRegistrarToken } from './domain' import { DidRepository } from './repository' import { DidResolverService, DidRegistrarService } from './services' @@ -30,15 +29,5 @@ export class DidsModule implements Module { dependencyManager.registerSingleton(DidResolverService) dependencyManager.registerSingleton(DidRegistrarService) dependencyManager.registerSingleton(DidRepository) - - // Register all did resolvers - for (const Resolver of this.config.resolvers) { - dependencyManager.registerSingleton(DidResolverToken, Resolver) - } - - // Register all did registrars - for (const Registrar of this.config.registrars) { - dependencyManager.registerSingleton(DidRegistrarToken, Registrar) - } } } diff --git a/packages/core/src/modules/dids/DidsModuleConfig.ts b/packages/core/src/modules/dids/DidsModuleConfig.ts index d4d71fa90b..24acbf38bf 100644 --- a/packages/core/src/modules/dids/DidsModuleConfig.ts +++ b/packages/core/src/modules/dids/DidsModuleConfig.ts @@ -1,4 +1,3 @@ -import type { Constructor } from '../../utils/mixins' import type { DidRegistrar, DidResolver } from './domain' import { @@ -17,9 +16,8 @@ import { */ export interface DidsModuleConfigOptions { /** - * List of did registrars that should be registered on the dids module. The registrar must - * follow the {@link DidRegistrar} interface, and must be constructable. The registrar will be injected - * into the `DidRegistrarService` and should be decorated with the `@injectable` decorator. + * List of did registrars that should be used by the dids module. The registrar must + * be an instance of the {@link DidRegistrar} interface. * * If no registrars are provided, the default registrars will be used. The `PeerDidRegistrar` will ALWAYS be * registered, as it is needed for the connections and out of band module to function. Other did methods can be @@ -27,12 +25,11 @@ export interface DidsModuleConfigOptions { * * @default [KeyDidRegistrar, IndySdkSovDidRegistrar, PeerDidRegistrar] */ - registrars?: Constructor[] + registrars?: DidRegistrar[] /** - * List of did resolvers that should be registered on the dids module. The resolver must - * follow the {@link DidResolver} interface, and must be constructable. The resolver will be injected - * into the `DidResolverService` and should be decorated with the `@injectable` decorator. + * List of did resolvers that should be used by the dids module. The resolver must + * be an instance of the {@link DidResolver} interface. * * If no resolvers are provided, the default resolvers will be used. The `PeerDidResolver` will ALWAYS be * registered, as it is needed for the connections and out of band module to function. Other did methods can be @@ -40,11 +37,13 @@ export interface DidsModuleConfigOptions { * * @default [IndySdkSovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver] */ - resolvers?: Constructor[] + resolvers?: DidResolver[] } export class DidsModuleConfig { private options: DidsModuleConfigOptions + private _registrars: DidRegistrar[] | undefined + private _resolvers: DidResolver[] | undefined public constructor(options?: DidsModuleConfigOptions) { this.options = options ?? {} @@ -52,17 +51,52 @@ export class DidsModuleConfig { /** See {@link DidsModuleConfigOptions.registrars} */ public get registrars() { - const registrars = this.options.registrars ?? [KeyDidRegistrar, IndySdkSovDidRegistrar, PeerDidRegistrar] + // This prevents creating new instances every time this property is accessed + if (this._registrars) return this._registrars - // If the peer did registrar is not included yet, add it - return registrars.includes(PeerDidRegistrar) ? registrars : [...registrars, PeerDidRegistrar] + let registrars = this.options.registrars ?? [ + new KeyDidRegistrar(), + new IndySdkSovDidRegistrar(), + new PeerDidRegistrar(), + ] + + // Add peer did registrar if it is not included yet + if (!registrars.find((registrar) => registrar instanceof PeerDidRegistrar)) { + // Do not modify original options array + registrars = [...registrars, new PeerDidRegistrar()] + } + + this._registrars = registrars + return registrars + } + + public addRegistrar(registrar: DidRegistrar) { + this.registrars.push(registrar) } /** See {@link DidsModuleConfigOptions.resolvers} */ public get resolvers() { - const resolvers = this.options.resolvers ?? [IndySdkSovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver] + // This prevents creating new instances every time this property is accessed + if (this._resolvers) return this._resolvers + + let resolvers = this.options.resolvers ?? [ + new IndySdkSovDidResolver(), + new WebDidResolver(), + new KeyDidResolver(), + new PeerDidResolver(), + ] + + // Add peer did resolver if it is not included yet + if (!resolvers.find((resolver) => resolver instanceof PeerDidResolver)) { + // Do not modify original options array + resolvers = [...resolvers, new PeerDidResolver()] + } + + this._resolvers = resolvers + return resolvers + } - // If the peer did resolver is not included yet, add it - return resolvers.includes(PeerDidResolver) ? resolvers : [...resolvers, PeerDidResolver] + public addResolver(resolver: DidResolver) { + this.resolvers.push(resolver) } } diff --git a/packages/core/src/modules/dids/__tests__/DidsModule.test.ts b/packages/core/src/modules/dids/__tests__/DidsModule.test.ts index 5e9ca00503..bddd4a3d53 100644 --- a/packages/core/src/modules/dids/__tests__/DidsModule.test.ts +++ b/packages/core/src/modules/dids/__tests__/DidsModule.test.ts @@ -1,20 +1,7 @@ -import type { Constructor } from '../../../utils/mixins' -import type { DidRegistrar, DidResolver } from '../domain' - import { DependencyManager } from '../../../plugins/DependencyManager' import { DidsApi } from '../DidsApi' import { DidsModule } from '../DidsModule' import { DidsModuleConfig } from '../DidsModuleConfig' -import { DidRegistrarToken, DidResolverToken } from '../domain' -import { - KeyDidRegistrar, - KeyDidResolver, - PeerDidRegistrar, - PeerDidResolver, - IndySdkSovDidRegistrar, - IndySdkSovDidResolver, - WebDidResolver, -} from '../methods' import { DidRepository } from '../repository' import { DidRegistrarService, DidResolverService } from '../services' @@ -34,31 +21,9 @@ describe('DidsModule', () => { expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) expect(dependencyManager.registerInstance).toHaveBeenCalledWith(DidsModuleConfig, didsModule.config) - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(10) + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(3) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRepository) - - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, IndySdkSovDidResolver) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, WebDidResolver) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, KeyDidResolver) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, PeerDidResolver) - - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarToken, KeyDidRegistrar) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarToken, IndySdkSovDidRegistrar) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarToken, PeerDidRegistrar) - }) - - test('takes the values from the dids config', () => { - const registrar = {} as Constructor - const resolver = {} as Constructor - - new DidsModule({ - registrars: [registrar], - resolvers: [resolver], - }).register(dependencyManager) - - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidResolverToken, resolver) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(DidRegistrarToken, registrar) }) }) diff --git a/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts b/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts index af8fb61448..53e5ed3203 100644 --- a/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts +++ b/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts @@ -1,4 +1,3 @@ -import type { Constructor } from '../../../utils/mixins' import type { DidRegistrar, DidResolver } from '../domain' import { @@ -16,13 +15,22 @@ describe('DidsModuleConfig', () => { test('sets default values', () => { const config = new DidsModuleConfig() - expect(config.registrars).toEqual([KeyDidRegistrar, IndySdkSovDidRegistrar, PeerDidRegistrar]) - expect(config.resolvers).toEqual([IndySdkSovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver]) + expect(config.registrars).toEqual([ + expect.any(KeyDidRegistrar), + expect.any(IndySdkSovDidRegistrar), + expect.any(PeerDidRegistrar), + ]) + expect(config.resolvers).toEqual([ + expect.any(IndySdkSovDidResolver), + expect.any(WebDidResolver), + expect.any(KeyDidResolver), + expect.any(PeerDidResolver), + ]) }) test('sets values', () => { - const registrars = [PeerDidRegistrar, {} as Constructor] - const resolvers = [PeerDidResolver, {} as Constructor] + const registrars = [new PeerDidRegistrar(), {} as DidRegistrar] + const resolvers = [new PeerDidResolver(), {} as DidResolver] const config = new DidsModuleConfig({ registrars, resolvers, @@ -33,14 +41,32 @@ describe('DidsModuleConfig', () => { }) test('adds peer did resolver and registrar if not provided in config', () => { - const registrar = {} as Constructor - const resolver = {} as Constructor + const registrar = {} as DidRegistrar + const resolver = {} as DidResolver const config = new DidsModuleConfig({ registrars: [registrar], resolvers: [resolver], }) - expect(config.registrars).toEqual([registrar, PeerDidRegistrar]) - expect(config.resolvers).toEqual([resolver, PeerDidResolver]) + expect(config.registrars).toEqual([registrar, expect.any(PeerDidRegistrar)]) + expect(config.resolvers).toEqual([resolver, expect.any(PeerDidResolver)]) + }) + + test('add resolver and registrar after creation', () => { + const registrar = {} as DidRegistrar + const resolver = {} as DidResolver + const config = new DidsModuleConfig({ + resolvers: [], + registrars: [], + }) + + expect(config.registrars).not.toContain(registrar) + expect(config.resolvers).not.toContain(resolver) + + config.addRegistrar(registrar) + config.addResolver(resolver) + + expect(config.registrars).toContain(registrar) + expect(config.resolvers).toContain(resolver) }) }) diff --git a/packages/core/src/modules/dids/__tests__/peer-did.test.ts b/packages/core/src/modules/dids/__tests__/peer-did.test.ts index 1c8bb1d9ab..1fadc6f327 100644 --- a/packages/core/src/modules/dids/__tests__/peer-did.test.ts +++ b/packages/core/src/modules/dids/__tests__/peer-did.test.ts @@ -4,11 +4,13 @@ import { Subject } from 'rxjs' import { getAgentConfig, getAgentContext } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../constants' import { Key, KeyType } from '../../../crypto' import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { IndyStorageService } from '../../../storage/IndyStorageService' import { JsonTransformer } from '../../../utils' import { IndyWallet } from '../../../wallet/IndyWallet' +import { DidsModuleConfig } from '../DidsModuleConfig' import { DidCommV1Service, DidDocument, DidDocumentBuilder } from '../domain' import { DidDocumentRole } from '../domain/DidDocumentRole' import { convertPublicKeyToX25519, getEd25519VerificationMethod } from '../domain/key-type/ed25519' @@ -33,16 +35,24 @@ describe('peer dids', () => { beforeEach(async () => { wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([])) - agentContext = getAgentContext({ wallet }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(config.walletConfig!) - const storageService = new IndyStorageService(config.agentDependencies) eventEmitter = new EventEmitter(config.agentDependencies, new Subject()) didRepository = new DidRepository(storageService, eventEmitter) - // Mocking IndyLedgerService as we're only interested in the did:peer resolver - didResolverService = new DidResolverService(config.logger, [new PeerDidResolver(didRepository)]) + agentContext = getAgentContext({ + wallet, + registerInstances: [ + [DidRepository, didRepository], + [InjectionSymbols.StorageService, storageService], + ], + }) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await wallet.createAndOpen(config.walletConfig!) + + didResolverService = new DidResolverService( + config.logger, + new DidsModuleConfig({ resolvers: [new PeerDidResolver()] }) + ) }) afterEach(async () => { diff --git a/packages/core/src/modules/dids/domain/DidRegistrar.ts b/packages/core/src/modules/dids/domain/DidRegistrar.ts index 200cda6ab6..fabca7bc5d 100644 --- a/packages/core/src/modules/dids/domain/DidRegistrar.ts +++ b/packages/core/src/modules/dids/domain/DidRegistrar.ts @@ -8,8 +8,6 @@ import type { DidDeactivateResult, } from '../types' -export const DidRegistrarToken = Symbol('DidRegistrar') - export interface DidRegistrar { readonly supportedMethods: string[] diff --git a/packages/core/src/modules/dids/domain/DidResolver.ts b/packages/core/src/modules/dids/domain/DidResolver.ts index e4512a1e57..050ea2cd97 100644 --- a/packages/core/src/modules/dids/domain/DidResolver.ts +++ b/packages/core/src/modules/dids/domain/DidResolver.ts @@ -1,8 +1,6 @@ import type { AgentContext } from '../../../agent' import type { ParsedDid, DidResolutionResult, DidResolutionOptions } from '../types' -export const DidResolverToken = Symbol('DidResolver') - export interface DidResolver { readonly supportedMethods: string[] resolve( diff --git a/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts b/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts index b38073b02e..6a4def3cd2 100644 --- a/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts +++ b/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts @@ -3,22 +3,17 @@ import type { KeyType } from '../../../../crypto' import type { DidRegistrar } from '../../domain/DidRegistrar' import type { DidCreateOptions, DidCreateResult, DidDeactivateResult, DidUpdateResult } from '../../types' -import { injectable } from '../../../../plugins' import { DidDocumentRole } from '../../domain/DidDocumentRole' import { DidRepository, DidRecord } from '../../repository' import { DidKey } from './DidKey' -@injectable() export class KeyDidRegistrar implements DidRegistrar { public readonly supportedMethods = ['key'] - private didRepository: DidRepository - - public constructor(didRepository: DidRepository) { - this.didRepository = didRepository - } public async create(agentContext: AgentContext, options: KeyDidCreateOptions): Promise { + const didRepository = agentContext.dependencyManager.resolve(DidRepository) + const keyType = options.options.keyType const seed = options.secret?.seed @@ -57,7 +52,7 @@ export class KeyDidRegistrar implements DidRegistrar { did: didKey.did, role: DidDocumentRole.Created, }) - await this.didRepository.save(agentContext, didRecord) + await didRepository.save(agentContext, didRecord) return { didDocumentMetadata: {}, diff --git a/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts b/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts index 4929e76fec..41f4a0e221 100644 --- a/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts +++ b/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts @@ -2,11 +2,8 @@ import type { AgentContext } from '../../../../agent' import type { DidResolver } from '../../domain/DidResolver' import type { DidResolutionResult } from '../../types' -import { injectable } from '../../../../plugins' - import { DidKey } from './DidKey' -@injectable() export class KeyDidResolver implements DidResolver { public readonly supportedMethods = ['key'] diff --git a/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts index d9e89bcb72..e859dcf795 100644 --- a/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts +++ b/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts @@ -17,20 +17,20 @@ const walletMock = { createKey: jest.fn(() => Key.fromFingerprint('z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU')), } as unknown as Wallet +const didRepositoryMock = new DidRepositoryMock() +const keyDidRegistrar = new KeyDidRegistrar() + const agentContext = getAgentContext({ wallet: walletMock, + registerInstances: [[DidRepository, didRepositoryMock]], }) describe('DidRegistrar', () => { - describe('KeyDidRegistrar', () => { - let keyDidRegistrar: KeyDidRegistrar - let didRepositoryMock: DidRepository - - beforeEach(() => { - didRepositoryMock = new DidRepositoryMock() - keyDidRegistrar = new KeyDidRegistrar(didRepositoryMock) - }) + afterEach(() => { + jest.clearAllMocks() + }) + describe('KeyDidRegistrar', () => { it('should correctly create a did:key document using Ed25519 key type', async () => { const seed = '96213c3d7fc8d4d6754c712fd969598e' diff --git a/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts b/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts index de8bd36676..057ed96c9d 100644 --- a/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts +++ b/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts @@ -3,7 +3,6 @@ import type { KeyType } from '../../../../crypto' import type { DidRegistrar } from '../../domain/DidRegistrar' import type { DidCreateOptions, DidCreateResult, DidDeactivateResult, DidUpdateResult } from '../../types' -import { injectable } from '../../../../plugins' import { JsonTransformer } from '../../../../utils' import { DidDocument } from '../../domain' import { DidDocumentRole } from '../../domain/DidDocumentRole' @@ -14,19 +13,14 @@ import { keyToNumAlgo0DidDocument } from './peerDidNumAlgo0' import { didDocumentJsonToNumAlgo1Did } from './peerDidNumAlgo1' import { didDocumentToNumAlgo2Did } from './peerDidNumAlgo2' -@injectable() export class PeerDidRegistrar implements DidRegistrar { public readonly supportedMethods = ['peer'] - private didRepository: DidRepository - - public constructor(didRepository: DidRepository) { - this.didRepository = didRepository - } public async create( agentContext: AgentContext, options: PeerDidNumAlgo0CreateOptions | PeerDidNumAlgo1CreateOptions | PeerDidNumAlgo2CreateOptions ): Promise { + const didRepository = agentContext.dependencyManager.resolve(DidRepository) let didDocument: DidDocument try { @@ -96,7 +90,7 @@ export class PeerDidRegistrar implements DidRegistrar { recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint), }, }) - await this.didRepository.save(agentContext, didRecord) + await didRepository.save(agentContext, didRecord) return { didDocumentMetadata: {}, diff --git a/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts index 4cf77abe40..fa3f2ed9d2 100644 --- a/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts +++ b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts @@ -4,24 +4,18 @@ import type { DidResolver } from '../../domain/DidResolver' import type { DidResolutionResult } from '../../types' import { AriesFrameworkError } from '../../../../error' -import { injectable } from '../../../../plugins' import { DidRepository } from '../../repository' import { getNumAlgoFromPeerDid, isValidPeerDid, PeerDidNumAlgo } from './didPeer' import { didToNumAlgo0DidDocument } from './peerDidNumAlgo0' import { didToNumAlgo2DidDocument } from './peerDidNumAlgo2' -@injectable() export class PeerDidResolver implements DidResolver { public readonly supportedMethods = ['peer'] - private didRepository: DidRepository - - public constructor(didRepository: DidRepository) { - this.didRepository = didRepository - } - public async resolve(agentContext: AgentContext, did: string): Promise { + const didRepository = agentContext.dependencyManager.resolve(DidRepository) + const didDocumentMetadata = {} try { @@ -41,9 +35,7 @@ export class PeerDidResolver implements DidResolver { else if (numAlgo === PeerDidNumAlgo.GenesisDoc) { // We can have multiple did document records stored for a single did (one created and one received). In this case it // doesn't matter which one we use, and they should be identical. So we just take the first one. - const [didDocumentRecord] = await this.didRepository.findByQuery(agentContext, { - did, - }) + const [didDocumentRecord] = await didRepository.findAllByDid(agentContext, did) if (!didDocumentRecord) { throw new AriesFrameworkError(`No did record found for peer did ${did}.`) diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts index 1edfeb31df..f20079cd4f 100644 --- a/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts +++ b/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts @@ -19,18 +19,17 @@ const DidRepositoryMock = DidRepository as jest.Mock const walletMock = { createKey: jest.fn(() => Key.fromFingerprint('z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU')), } as unknown as Wallet -const agentContext = getAgentContext({ wallet: walletMock }) +const didRepositoryMock = new DidRepositoryMock() -describe('DidRegistrar', () => { - describe('PeerDidRegistrar', () => { - let peerDidRegistrar: PeerDidRegistrar - let didRepositoryMock: DidRepository +const agentContext = getAgentContext({ wallet: walletMock, registerInstances: [[DidRepository, didRepositoryMock]] }) +const peerDidRegistrar = new PeerDidRegistrar() - beforeEach(() => { - didRepositoryMock = new DidRepositoryMock() - peerDidRegistrar = new PeerDidRegistrar(didRepositoryMock) - }) +describe('DidRegistrar', () => { + afterEach(() => { + jest.clearAllMocks() + }) + describe('PeerDidRegistrar', () => { describe('did:peer:0', () => { it('should correctly create a did:peer:0 document using Ed25519 key type', async () => { const seed = '96213c3d7fc8d4d6754c712fd969598e' diff --git a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts index 2efa3d585f..763f97b622 100644 --- a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts +++ b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts @@ -4,11 +4,8 @@ import type { DidRegistrar } from '../../domain/DidRegistrar' import type { DidCreateOptions, DidCreateResult, DidDeactivateResult, DidUpdateResult } from '../../types' import type * as Indy from 'indy-sdk' -import { AgentDependencies } from '../../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../../constants' import { IndySdkError } from '../../../../error' -import { Logger } from '../../../../logger' -import { inject, injectable } from '../../../../plugins' +import { injectable } from '../../../../plugins' import { isIndyError } from '../../../../utils/indyError' import { assertIndyWallet } from '../../../../wallet/util/assertIndyWallet' import { IndyPoolService } from '../../../ledger' @@ -20,24 +17,12 @@ import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './util' @injectable() export class IndySdkSovDidRegistrar implements DidRegistrar { public readonly supportedMethods = ['sov'] - private didRepository: DidRepository - private indy: typeof Indy - private indyPoolService: IndyPoolService - private logger: Logger - - public constructor( - didRepository: DidRepository, - indyPoolService: IndyPoolService, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, - @inject(InjectionSymbols.Logger) logger: Logger - ) { - this.didRepository = didRepository - this.indy = agentDependencies.indy - this.indyPoolService = indyPoolService - this.logger = logger - } public async create(agentContext: AgentContext, options: SovDidCreateOptions): Promise { + const indy = agentContext.config.agentDependencies.indy + const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) + const didRepository = agentContext.dependencyManager.resolve(DidRepository) + const { alias, role, submitterDid, indyNamespace } = options.options const seed = options.secret?.seed @@ -70,7 +55,7 @@ export class IndySdkSovDidRegistrar implements DidRegistrar { // to rely directly on the indy SDK, as we don't want to expose a createDid method just for. // FIXME: once askar/indy-vdr is supported we need to adjust this to work with both indy-sdk and askar assertIndyWallet(agentContext.wallet) - const [unqualifiedIndyDid, verkey] = await this.indy.createAndStoreMyDid(agentContext.wallet.handle, { + const [unqualifiedIndyDid, verkey] = await indy.createAndStoreMyDid(agentContext.wallet.handle, { seed, }) @@ -79,7 +64,7 @@ export class IndySdkSovDidRegistrar implements DidRegistrar { // TODO: it should be possible to pass the pool used for writing to the indy ledger service. // The easiest way to do this would be to make the submitterDid a fully qualified did, including the indy namespace. - const pool = this.indyPoolService.getPoolForNamespace(indyNamespace) + const pool = indyPoolService.getPoolForNamespace(indyNamespace) await this.registerPublicDid(agentContext, unqualifiedSubmitterDid, unqualifiedIndyDid, verkey, alias, pool, role) // Create did document @@ -111,7 +96,7 @@ export class IndySdkSovDidRegistrar implements DidRegistrar { qualifiedIndyDid, }, }) - await this.didRepository.save(agentContext, didRecord) + await didRepository.save(agentContext, didRecord) return { didDocumentMetadata: { @@ -177,20 +162,23 @@ export class IndySdkSovDidRegistrar implements DidRegistrar { pool: IndyPool, role?: Indy.NymRole ) { + const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) + const indy = agentContext.config.agentDependencies.indy + try { - this.logger.debug(`Register public did '${targetDid}' on ledger '${pool.id}'`) + agentContext.config.logger.debug(`Register public did '${targetDid}' on ledger '${pool.id}'`) - const request = await this.indy.buildNymRequest(submitterDid, targetDid, verkey, alias, role || null) + const request = await indy.buildNymRequest(submitterDid, targetDid, verkey, alias, role || null) - const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, submitterDid) + const response = await indyPoolService.submitWriteRequest(agentContext, pool, request, submitterDid) - this.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.id}'`, { + agentContext.config.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.id}'`, { response, }) return targetDid } catch (error) { - this.logger.error(`Error registering public did '${targetDid}' on ledger '${pool.id}'`, { + agentContext.config.logger.error(`Error registering public did '${targetDid}' on ledger '${pool.id}'`, { error, submitterDid, targetDid, @@ -210,18 +198,21 @@ export class IndySdkSovDidRegistrar implements DidRegistrar { endpoints: IndyEndpointAttrib, pool: IndyPool ): Promise { + const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) + const indy = agentContext.config.agentDependencies.indy + try { - this.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.id}'`, endpoints) + agentContext.config.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.id}'`, endpoints) - const request = await this.indy.buildAttribRequest(did, did, null, { endpoint: endpoints }, null) + const request = await indy.buildAttribRequest(did, did, null, { endpoint: endpoints }, null) - const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, did) - this.logger.debug(`Successfully set endpoints for did '${did}' on ledger '${pool.id}'`, { + const response = await indyPoolService.submitWriteRequest(agentContext, pool, request, did) + agentContext.config.logger.debug(`Successfully set endpoints for did '${did}' on ledger '${pool.id}'`, { response, endpoints, }) } catch (error) { - this.logger.error(`Error setting endpoints for did '${did}' on ledger '${pool.id}'`, { + agentContext.config.logger.error(`Error setting endpoints for did '${did}' on ledger '${pool.id}'`, { error, did, endpoints, diff --git a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts index ecf324e644..bae98c5587 100644 --- a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts +++ b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts @@ -2,34 +2,16 @@ import type { AgentContext } from '../../../../agent' import type { IndyEndpointAttrib } from '../../../ledger' import type { DidResolver } from '../../domain/DidResolver' import type { DidResolutionResult, ParsedDid } from '../../types' -import type * as Indy from 'indy-sdk' -import { isIndyError } from '../../../..//utils/indyError' -import { AgentDependencies } from '../../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../../constants' import { IndySdkError } from '../../../../error' -import { Logger } from '../../../../logger' -import { inject, injectable } from '../../../../plugins' +import { injectable } from '../../../../plugins' +import { isIndyError } from '../../../../utils/indyError' import { IndyPoolService } from '../../../ledger' import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './util' @injectable() export class IndySdkSovDidResolver implements DidResolver { - private indy: typeof Indy - private indyPoolService: IndyPoolService - private logger: Logger - - public constructor( - indyPoolService: IndyPoolService, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, - @inject(InjectionSymbols.Logger) logger: Logger - ) { - this.indy = agentDependencies.indy - this.indyPoolService = indyPoolService - this.logger = logger - } - public readonly supportedMethods = ['sov'] public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { @@ -61,34 +43,42 @@ export class IndySdkSovDidResolver implements DidResolver { } private async getPublicDid(agentContext: AgentContext, did: string) { + const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) + // Getting the pool for a did also retrieves the DID. We can just use that - const { did: didResponse } = await this.indyPoolService.getPoolForDid(agentContext, did) + const { did: didResponse } = await indyPoolService.getPoolForDid(agentContext, did) return didResponse } private async getEndpointsForDid(agentContext: AgentContext, did: string) { - const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) + const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) + const indy = agentContext.config.agentDependencies.indy + + const { pool } = await indyPoolService.getPoolForDid(agentContext, did) try { - this.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.id}'`) + agentContext.config.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.id}'`) - const request = await this.indy.buildGetAttribRequest(null, did, 'endpoint', null, null) + const request = await indy.buildGetAttribRequest(null, did, 'endpoint', null, null) - this.logger.debug(`Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.id}'`) - const response = await this.indyPoolService.submitReadRequest(pool, request) + agentContext.config.logger.debug(`Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.id}'`) + const response = await indyPoolService.submitReadRequest(pool, request) if (!response.result.data) return {} const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib - this.logger.debug(`Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.id}'`, { - response, - endpoints, - }) + agentContext.config.logger.debug( + `Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.id}'`, + { + response, + endpoints, + } + ) return endpoints ?? {} } catch (error) { - this.logger.error(`Error retrieving endpoints for did '${did}' from ledger '${pool.id}'`, { + agentContext.config.logger.error(`Error retrieving endpoints for did '${did}' from ledger '${pool.id}'`, { error, }) diff --git a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts index b619de81cd..761a2956b6 100644 --- a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts +++ b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts @@ -1,3 +1,4 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { Wallet } from '../../../../../wallet' import type { IndyPool } from '../../../../ledger' import type * as Indy from 'indy-sdk' @@ -22,26 +23,28 @@ mockFunction(indyPoolServiceMock.getPoolForNamespace).mockReturnValue({ } as IndyPool) const agentConfig = getAgentConfig('IndySdkSovDidRegistrar') - const createDidMock = jest.fn(async () => ['R1xKJw17sUoXhejEpugMYJ', 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu']) const wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) mockProperty(wallet, 'handle', 10) +const didRepositoryMock = new DidRepositoryMock() const agentContext = getAgentContext({ wallet, + registerInstances: [ + [DidRepository, didRepositoryMock], + [IndyPoolService, indyPoolServiceMock], + ], + agentConfig: { + ...agentConfig, + agentDependencies: { + ...agentConfig.agentDependencies, + indy: { createAndStoreMyDid: createDidMock } as unknown as typeof Indy, + }, + } as AgentConfig, }) -const didRepositoryMock = new DidRepositoryMock() -const indySdkSovDidRegistrar = new IndySdkSovDidRegistrar( - didRepositoryMock, - indyPoolServiceMock, - { - ...agentConfig.agentDependencies, - indy: { createAndStoreMyDid: createDidMock } as unknown as typeof Indy, - }, - agentConfig.logger -) +const indySdkSovDidRegistrar = new IndySdkSovDidRegistrar() describe('DidRegistrar', () => { describe('IndySdkSovDidRegistrar', () => { @@ -75,6 +78,7 @@ describe('DidRegistrar', () => { it('should return an error state if the wallet is not an indy wallet', async () => { const agentContext = getAgentContext({ wallet: {} as unknown as Wallet, + agentConfig, }) const result = await indySdkSovDidRegistrar.create(agentContext, { diff --git a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts index 2ca3c3f82b..6d082792f0 100644 --- a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts +++ b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts @@ -1,4 +1,3 @@ -import type { AgentContext } from '../../../../../agent' import type { IndyPool } from '../../../../ledger' import type { IndyEndpointAttrib } from '../../../../ledger/services/IndyLedgerService' import type { GetNymResponse } from 'indy-sdk' @@ -25,20 +24,15 @@ const agentConfig = getAgentConfig('IndySdkSovDidResolver') const wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) mockProperty(wallet, 'handle', 10) +const agentContext = getAgentContext({ + agentConfig, + registerInstances: [[IndyPoolService, indyPoolServiceMock]], +}) + +const indySdkSovDidResolver = new IndySdkSovDidResolver() + describe('DidResolver', () => { describe('IndySdkSovDidResolver', () => { - let indySdkSovDidResolver: IndySdkSovDidResolver - let agentContext: AgentContext - - beforeEach(() => { - indySdkSovDidResolver = new IndySdkSovDidResolver( - indyPoolServiceMock, - agentConfig.agentDependencies, - agentConfig.logger - ) - agentContext = getAgentContext() - }) - it('should correctly resolve a did:sov document', async () => { const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' diff --git a/packages/core/src/modules/dids/methods/web/WebDidResolver.ts b/packages/core/src/modules/dids/methods/web/WebDidResolver.ts index 84cac28e59..77d9b1e295 100644 --- a/packages/core/src/modules/dids/methods/web/WebDidResolver.ts +++ b/packages/core/src/modules/dids/methods/web/WebDidResolver.ts @@ -5,11 +5,9 @@ import type { ParsedDid, DidResolutionResult, DidResolutionOptions } from '../.. import { Resolver } from 'did-resolver' import * as didWeb from 'web-did-resolver' -import { injectable } from '../../../../plugins' import { JsonTransformer } from '../../../../utils/JsonTransformer' import { DidDocument } from '../../domain' -@injectable() export class WebDidResolver implements DidResolver { public readonly supportedMethods diff --git a/packages/core/src/modules/dids/repository/DidRepository.ts b/packages/core/src/modules/dids/repository/DidRepository.ts index bea0df17c9..80fad9cf52 100644 --- a/packages/core/src/modules/dids/repository/DidRepository.ts +++ b/packages/core/src/modules/dids/repository/DidRepository.ts @@ -45,6 +45,10 @@ export class DidRepository extends Repository { return this.findByQuery(agentContext, { recipientKeyFingerprints: [recipientKey.fingerprint] }) } + public findAllByDid(agentContext: AgentContext, did: string) { + return this.findByQuery(agentContext, { did }) + } + public findReceivedDid(agentContext: AgentContext, receivedDid: string) { return this.findSingleByQuery(agentContext, { did: receivedDid, role: DidDocumentRole.Received }) } diff --git a/packages/core/src/modules/dids/services/DidRegistrarService.ts b/packages/core/src/modules/dids/services/DidRegistrarService.ts index 43a77c8c25..cb59457aa0 100644 --- a/packages/core/src/modules/dids/services/DidRegistrarService.ts +++ b/packages/core/src/modules/dids/services/DidRegistrarService.ts @@ -11,21 +11,18 @@ import type { import { InjectionSymbols } from '../../../constants' import { Logger } from '../../../logger' -import { inject, injectable, injectAll } from '../../../plugins' -import { DidRegistrarToken } from '../domain/DidRegistrar' +import { inject, injectable } from '../../../plugins' +import { DidsModuleConfig } from '../DidsModuleConfig' import { tryParseDid } from '../domain/parse' @injectable() export class DidRegistrarService { private logger: Logger - private registrars: DidRegistrar[] + private didsModuleConfig: DidsModuleConfig - public constructor( - @inject(InjectionSymbols.Logger) logger: Logger, - @injectAll(DidRegistrarToken) registrars: DidRegistrar[] - ) { + public constructor(@inject(InjectionSymbols.Logger) logger: Logger, didsModuleConfig: DidsModuleConfig) { this.logger = logger - this.registrars = registrars + this.didsModuleConfig = didsModuleConfig } public async create( @@ -154,6 +151,6 @@ export class DidRegistrarService { } private findRegistrarForMethod(method: string): DidRegistrar | null { - return this.registrars.find((r) => r.supportedMethods.includes(method)) ?? null + return this.didsModuleConfig.registrars.find((r) => r.supportedMethods.includes(method)) ?? null } } diff --git a/packages/core/src/modules/dids/services/DidResolverService.ts b/packages/core/src/modules/dids/services/DidResolverService.ts index a365706dc4..e23206cc9a 100644 --- a/packages/core/src/modules/dids/services/DidResolverService.ts +++ b/packages/core/src/modules/dids/services/DidResolverService.ts @@ -5,21 +5,18 @@ import type { DidResolutionOptions, DidResolutionResult, ParsedDid } from '../ty import { InjectionSymbols } from '../../../constants' import { AriesFrameworkError } from '../../../error' import { Logger } from '../../../logger' -import { injectable, inject, injectAll } from '../../../plugins' -import { DidResolverToken } from '../domain/DidResolver' +import { injectable, inject } from '../../../plugins' +import { DidsModuleConfig } from '../DidsModuleConfig' import { parseDid } from '../domain/parse' @injectable() export class DidResolverService { private logger: Logger - private resolvers: DidResolver[] + private didsModuleConfig: DidsModuleConfig - public constructor( - @inject(InjectionSymbols.Logger) logger: Logger, - @injectAll(DidResolverToken) resolvers: DidResolver[] - ) { + public constructor(@inject(InjectionSymbols.Logger) logger: Logger, didsModuleConfig: DidsModuleConfig) { this.logger = logger - this.resolvers = resolvers + this.didsModuleConfig = didsModuleConfig } public async resolve( @@ -69,6 +66,6 @@ export class DidResolverService { } private findResolver(parsed: ParsedDid): DidResolver | null { - return this.resolvers.find((r) => r.supportedMethods.includes(parsed.method)) ?? null + return this.didsModuleConfig.resolvers.find((r) => r.supportedMethods.includes(parsed.method)) ?? null } } diff --git a/packages/core/src/modules/dids/services/__tests__/DidRegistrarService.test.ts b/packages/core/src/modules/dids/services/__tests__/DidRegistrarService.test.ts index b92659ebe4..a00e781557 100644 --- a/packages/core/src/modules/dids/services/__tests__/DidRegistrarService.test.ts +++ b/packages/core/src/modules/dids/services/__tests__/DidRegistrarService.test.ts @@ -1,6 +1,7 @@ import type { DidDocument, DidRegistrar } from '../../domain' import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' +import { DidsModuleConfig } from '../../DidsModuleConfig' import { DidRegistrarService } from '../DidRegistrarService' const agentConfig = getAgentConfig('DidResolverService') @@ -13,7 +14,12 @@ const didRegistrarMock = { deactivate: jest.fn(), } as DidRegistrar -const didRegistrarService = new DidRegistrarService(agentConfig.logger, [didRegistrarMock]) +const didRegistrarService = new DidRegistrarService( + agentConfig.logger, + new DidsModuleConfig({ + registrars: [didRegistrarMock], + }) +) describe('DidResolverService', () => { afterEach(() => { diff --git a/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts b/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts index c472ec8899..81f250f294 100644 --- a/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts +++ b/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts @@ -2,6 +2,7 @@ import type { DidResolver } from '../../domain' import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' import { JsonTransformer } from '../../../../utils/JsonTransformer' +import { DidsModuleConfig } from '../../DidsModuleConfig' import didKeyEd25519Fixture from '../../__tests__/__fixtures__/didKeyEd25519.json' import { DidDocument } from '../../domain' import { parseDid } from '../../domain/parse' @@ -16,7 +17,10 @@ const agentConfig = getAgentConfig('DidResolverService') const agentContext = getAgentContext() describe('DidResolverService', () => { - const didResolverService = new DidResolverService(agentConfig.logger, [didResolverMock]) + const didResolverService = new DidResolverService( + agentConfig.logger, + new DidsModuleConfig({ resolvers: [didResolverMock] }) + ) it('should correctly find and call the correct resolver for a specified did', async () => { const returnValue = { From a0b66028ea658259902a29d0be04773f0740695e Mon Sep 17 00:00:00 2001 From: Akiff Manji Date: Wed, 21 Dec 2022 08:06:04 -0800 Subject: [PATCH 104/125] chore(migrations)!: connections 0.3.0 migration script and tests (#1177) Signed-off-by: Akiff Manji --- .../src/modules/connections/ConnectionsApi.ts | 6 +- .../__tests__/ConnectionService.test.ts | 8 +- .../repository/ConnectionRecord.ts | 7 +- .../__tests__/ConnectionRecord.test.ts | 1 + .../connections/services/ConnectionService.ts | 17 +- .../__tests__/__snapshots__/0.1.test.ts.snap | 14 ++ .../0.1-0.2/__tests__/connection.test.ts | 7 + .../0.2-0.3/__tests__/connection.test.ts | 188 ++++++++++++++++++ .../migration/updates/0.2-0.3/connection.ts | 67 +++++++ .../migration/updates/0.2-0.3/index.ts | 2 + packages/core/tests/connections.test.ts | 14 +- 11 files changed, 304 insertions(+), 27 deletions(-) create mode 100644 packages/core/src/storage/migration/updates/0.2-0.3/__tests__/connection.test.ts create mode 100644 packages/core/src/storage/migration/updates/0.2-0.3/connection.ts diff --git a/packages/core/src/modules/connections/ConnectionsApi.ts b/packages/core/src/modules/connections/ConnectionsApi.ts index f767cd1041..b437c46b4e 100644 --- a/packages/core/src/modules/connections/ConnectionsApi.ts +++ b/packages/core/src/modules/connections/ConnectionsApi.ts @@ -251,7 +251,7 @@ export class ConnectionsApi { /** * Allows for the addition of connectionType to the record. - * Either updates or creates an array of string conection types + * Either updates or creates an array of string connection types * @param connectionId * @param type * @throws {RecordNotFoundError} If no record is found @@ -295,8 +295,8 @@ export class ConnectionsApi { * @param connectionTypes An array of connection types to query for a match for * @returns a promise of ab array of connection records */ - public async findAllByConnectionType(connectionTypes: Array) { - return this.connectionService.findAllByConnectionType(this.agentContext, connectionTypes) + public async findAllByConnectionTypes(connectionTypes: Array) { + return this.connectionService.findAllByConnectionTypes(this.agentContext, connectionTypes) } /** diff --git a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index f9ad1b1003..6030b5877e 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -986,10 +986,9 @@ describe('ConnectionService', () => { it('removeConnectionType - existing type', async () => { const connection = getMockConnection() - - connection.setTag('connectionType', ['type-1', 'type-2', 'type-3']) + connection.connectionTypes = ['type-1', 'type-2', 'type-3'] let connectionTypes = await connectionService.getConnectionTypes(connection) - expect(connectionTypes).toMatchObject(['type-1', 'type-2', 'type-3']) + expect(connectionTypes.sort()).toMatchObject(['type-1', 'type-2', 'type-3'].sort()) await connectionService.removeConnectionType(agentContext, connection, 'type-2') connectionTypes = await connectionService.getConnectionTypes(connection) @@ -998,8 +997,7 @@ describe('ConnectionService', () => { it('removeConnectionType - type not existent', async () => { const connection = getMockConnection() - - connection.setTag('connectionType', ['type-1', 'type-2', 'type-3']) + connection.connectionTypes = ['type-1', 'type-2', 'type-3'] let connectionTypes = await connectionService.getConnectionTypes(connection) expect(connectionTypes).toMatchObject(['type-1', 'type-2', 'type-3']) diff --git a/packages/core/src/modules/connections/repository/ConnectionRecord.ts b/packages/core/src/modules/connections/repository/ConnectionRecord.ts index 4cdaca805d..382b0f0eac 100644 --- a/packages/core/src/modules/connections/repository/ConnectionRecord.ts +++ b/packages/core/src/modules/connections/repository/ConnectionRecord.ts @@ -26,6 +26,7 @@ export interface ConnectionRecordProps { protocol?: HandshakeProtocol outOfBandId?: string invitationDid?: string + connectionTypes?: Array } export type CustomConnectionTags = TagsBase @@ -38,7 +39,7 @@ export type DefaultConnectionTags = { theirDid?: string outOfBandId?: string invitationDid?: string - connectionType?: Array + connectionTypes?: Array } export class ConnectionRecord @@ -64,6 +65,8 @@ export class ConnectionRecord public outOfBandId?: string public invitationDid?: string + public connectionTypes: string[] = [] + public static readonly type = 'ConnectionRecord' public readonly type = ConnectionRecord.type @@ -88,6 +91,7 @@ export class ConnectionRecord this.errorMessage = props.errorMessage this.protocol = props.protocol this.outOfBandId = props.outOfBandId + this.connectionTypes = props.connectionTypes ?? [] } } @@ -102,6 +106,7 @@ export class ConnectionRecord theirDid: this.theirDid, outOfBandId: this.outOfBandId, invitationDid: this.invitationDid, + connectionTypes: this.connectionTypes, } } diff --git a/packages/core/src/modules/connections/repository/__tests__/ConnectionRecord.test.ts b/packages/core/src/modules/connections/repository/__tests__/ConnectionRecord.test.ts index aefc5a88cb..229a99d1e6 100644 --- a/packages/core/src/modules/connections/repository/__tests__/ConnectionRecord.test.ts +++ b/packages/core/src/modules/connections/repository/__tests__/ConnectionRecord.test.ts @@ -24,6 +24,7 @@ describe('ConnectionRecord', () => { theirDid: 'a-their-did', outOfBandId: 'a-out-of-band-id', invitationDid: 'a-invitation-did', + connectionTypes: [], }) }) }) diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index b51d1292d3..f228f4fa64 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -610,8 +610,8 @@ export class ConnectionService { return this.connectionRepository.findByQuery(agentContext, { outOfBandId }) } - public async findAllByConnectionType(agentContext: AgentContext, connectionTypes: Array) { - return this.connectionRepository.findByQuery(agentContext, { connectionType: connectionTypes }) + public async findAllByConnectionTypes(agentContext: AgentContext, connectionTypes: Array) { + return this.connectionRepository.findByQuery(agentContext, { connectionTypes }) } public async findByInvitationDid(agentContext: AgentContext, invitationDid: string) { @@ -652,23 +652,18 @@ export class ConnectionService { } public async addConnectionType(agentContext: AgentContext, connectionRecord: ConnectionRecord, type: string) { - const tags = connectionRecord.getTags().connectionType || [] - connectionRecord.setTag('connectionType', [type, ...tags]) + const connectionTypes = connectionRecord.connectionTypes || [] + connectionRecord.connectionTypes = [type, ...connectionTypes] await this.update(agentContext, connectionRecord) } public async removeConnectionType(agentContext: AgentContext, connectionRecord: ConnectionRecord, type: string) { - const tags = connectionRecord.getTags().connectionType || [] - - const newTags = tags.filter((value: string) => value !== type) - connectionRecord.setTag('connectionType', [...newTags]) - + connectionRecord.connectionTypes = connectionRecord.connectionTypes.filter((value) => value !== type) await this.update(agentContext, connectionRecord) } public async getConnectionTypes(connectionRecord: ConnectionRecord) { - const tags = connectionRecord.getTags().connectionType - return tags || [] + return connectionRecord.connectionTypes || [] } private async createDid(agentContext: AgentContext, { role, didDoc }: { role: DidDocumentRole; didDoc: DidDoc }) { diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap index 98d562950b..241611490a 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap @@ -1216,6 +1216,7 @@ Object { "7781341d-be29-441b-9b79-4a957d8c6d37": Object { "id": "7781341d-be29-441b-9b79-4a957d8c6d37", "tags": Object { + "connectionTypes": Array [], "did": "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", "invitationDid": "did:peer:2.SeyJzIjoicnhqczphbGljZSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa3QxdHNwMTVjbkREN3dCQ0ZnZWhpUjJTeEhYMWFQeHQ0c3VlRTI0dHdIOUJkI3o2TWt0MXRzcDE1Y25ERDd3QkNGZ2VoaVIyU3hIWDFhUHh0NHN1ZUUyNHR3SDlCZCJdLCJyIjpbXX0", "invitationKey": "EZdqDkqBSfiepgMZ15jsZvtxTwjiz5diBtjJBnvvMvQF", @@ -1231,6 +1232,7 @@ Object { "type": "ConnectionRecord", "value": Object { "autoAcceptConnection": false, + "connectionTypes": Array [], "createdAt": "2022-04-30T13:02:21.628Z", "did": "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", "id": "7781341d-be29-441b-9b79-4a957d8c6d37", @@ -1247,6 +1249,7 @@ Object { "8f4908ee-15ad-4058-9106-eda26eae735c": Object { "id": "8f4908ee-15ad-4058-9106-eda26eae735c", "tags": Object { + "connectionTypes": Array [], "did": "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", "invitationDid": "did:peer:2.SeyJzIjoicnhqczpmYWJlciIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa2ZpUE1QeENRZVNEWkdNa0N2bTFZMnJCb1BzbXc0WkhNdjcxalh0Y1dSUmlNI3o2TWtmaVBNUHhDUWVTRFpHTWtDdm0xWTJyQm9Qc213NFpITXY3MWpYdGNXUlJpTSJdLCJyIjpbXX0", "invitationKey": "2G8JohwyJtj69ruWFC3hBkdoaJW5eg31E66ohceVWCvy", @@ -1262,6 +1265,7 @@ Object { "type": "ConnectionRecord", "value": Object { "alias": "connection alias", + "connectionTypes": Array [], "createdAt": "2022-04-30T13:02:21.577Z", "did": "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", "id": "8f4908ee-15ad-4058-9106-eda26eae735c", @@ -1278,6 +1282,7 @@ Object { "9383d8e5-c002-4aae-8300-4a21384c919e": Object { "id": "9383d8e5-c002-4aae-8300-4a21384c919e", "tags": Object { + "connectionTypes": Array [], "did": "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", "invitationDid": "did:peer:2.SeyJzIjoicnhqczphbGljZSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa3RDWkFRTkd2V2I0V0hBandCcVB0WGhaZERZb3JiU0prR1c5dmoxdWh3MUhEI3o2TWt0Q1pBUU5HdldiNFdIQWp3QnFQdFhoWmREWW9yYlNKa0dXOXZqMXVodzFIRCJdLCJyIjpbXX0", "invitationKey": "EkJ7p82VB3a3AfuEWGS3gc1dPyY1BZ4PaVEztjwh1nVq", @@ -1292,6 +1297,7 @@ Object { }, "type": "ConnectionRecord", "value": Object { + "connectionTypes": Array [], "createdAt": "2022-04-30T13:02:21.608Z", "did": "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", "id": "9383d8e5-c002-4aae-8300-4a21384c919e", @@ -1319,6 +1325,7 @@ Object { "b65c2ccd-277c-4140-9d87-c8dd30e7a98c": Object { "id": "b65c2ccd-277c-4140-9d87-c8dd30e7a98c", "tags": Object { + "connectionTypes": Array [], "did": "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", "invitationDid": "did:peer:2.SeyJzIjoicnhqczphbGljZSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa3VXVEVtSDFtVW82Vzk2elNXeUg2MTJoRkhvd1J6TkVzY1BZQkwyQ0NNeUMyI3o2TWt1V1RFbUgxbVVvNlc5NnpTV3lINjEyaEZIb3dSek5Fc2NQWUJMMkNDTXlDMiJdLCJyIjpbXX0", "invitationKey": "G4CCB2mL9Fc32c9jqQKF9w9FUEfaaUzWvNdFVkEBSkQe", @@ -1333,6 +1340,7 @@ Object { "type": "ConnectionRecord", "value": Object { "autoAcceptConnection": true, + "connectionTypes": Array [], "createdAt": "2022-04-30T13:02:21.653Z", "did": "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", "id": "b65c2ccd-277c-4140-9d87-c8dd30e7a98c", @@ -1346,6 +1354,7 @@ Object { "da518433-0e55-4b74-a05b-aa75c1095a99": Object { "id": "da518433-0e55-4b74-a05b-aa75c1095a99", "tags": Object { + "connectionTypes": Array [], "did": "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", "invitationDid": "did:peer:2.SeyJzIjoicnhqczphbGljZSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa28zMURORTNncU1SWmoxSk5odjJCSGIxY2FRc2hjZDluamdLa0VRWHNnRlJwI3o2TWtvMzFETkUzZ3FNUlpqMUpOaHYyQkhiMWNhUXNoY2Q5bmpnS2tFUVhzZ0ZScCJdLCJyIjpbXX0", "invitationKey": "9akAmyoFVow6cWTg2M4LSVTckqbrCjuS3fQpQ8Zrm2eS", @@ -1361,6 +1370,7 @@ Object { "type": "ConnectionRecord", "value": Object { "autoAcceptConnection": true, + "connectionTypes": Array [], "createdAt": "2022-04-30T13:02:21.646Z", "did": "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", "id": "da518433-0e55-4b74-a05b-aa75c1095a99", @@ -2326,6 +2336,7 @@ Object { "e3f9bc2b-f0a1-4a2c-ab81-2f0a3488c199": Object { "id": "e3f9bc2b-f0a1-4a2c-ab81-2f0a3488c199", "tags": Object { + "connectionTypes": Array [], "did": "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", "invitationDid": "did:peer:2.SeyJzIjoicnhqczpmYWJlciIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa2pESkw0WDdZR29INmdqYW1oWlIyTnpvd1BacXRKZlg1a1B1TnVXaVZkak1yI3o2TWtqREpMNFg3WUdvSDZnamFtaFpSMk56b3dQWnF0SmZYNWtQdU51V2lWZGpNciJdLCJyIjpbXX0", "invitationKey": "5m3HUGs6wFndaEk51zTBXuFwZza2tnGj4NzT5EkUiWaU", @@ -2341,6 +2352,7 @@ Object { "type": "ConnectionRecord", "value": Object { "autoAcceptConnection": false, + "connectionTypes": Array [], "createdAt": "2022-04-30T13:02:21.641Z", "did": "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", "id": "e3f9bc2b-f0a1-4a2c-ab81-2f0a3488c199", @@ -2357,6 +2369,7 @@ Object { "ee88e2e1-e27e-46a6-a910-f87690109e32": Object { "id": "ee88e2e1-e27e-46a6-a910-f87690109e32", "tags": Object { + "connectionTypes": Array [], "did": "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", "invitationDid": "did:peer:2.SeyJzIjoicnhqczpmYWJlciIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa21vZDh2cDJuVVJWa3RWQzVjZVFleXIyVlV6MjZpdTJaQU5MTlZnOXBNYXdhI3o2TWttb2Q4dnAyblVSVmt0VkM1Y2VRZXlyMlZVejI2aXUyWkFOTE5WZzlwTWF3YSJdLCJyIjpbXX0", "invitationKey": "8MN6LZnM8t1HmzMNw5Sp8kUVfQkFK1nCUMRSfQBoSNAC", @@ -2370,6 +2383,7 @@ Object { }, "type": "ConnectionRecord", "value": Object { + "connectionTypes": Array [], "createdAt": "2022-04-30T13:02:21.635Z", "did": "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", "id": "ee88e2e1-e27e-46a6-a910-f87690109e32", diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts index dbc3f215d7..70c72c9c2a 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/connection.test.ts @@ -141,6 +141,7 @@ describe('0.1-0.2 | Connection', () => { 'did:peer:2.SeyJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa3NZVTRNSHRmbU5oTm0xdUdNdkFOcjlqNENCdjJGeW1qaUp0UmdBMzZiU1ZII3o2TWtzWVU0TUh0Zm1OaE5tMXVHTXZBTnI5ajRDQnYyRnltamlKdFJnQTM2YlNWSCJdfQ', theirDid: didPeer4kgVt6CidfKgo1MoWMqsQX.id, outOfBandId: expect.any(String), + connectionTypes: [], }) }) }) @@ -171,6 +172,7 @@ describe('0.1-0.2 | Connection', () => { serviceEndpoint: 'https://example.com', label: 'test', }, + connectionTypes: [], }) }) }) @@ -199,6 +201,7 @@ describe('0.1-0.2 | Connection', () => { serviceEndpoint: 'https://example.com', label: 'test', }, + connectionTypes: [], }) }) @@ -271,6 +274,7 @@ describe('0.1-0.2 | Connection', () => { theirDidDoc: undefined, metadata: {}, _tags: {}, + connectionTypes: [], }) }) @@ -341,6 +345,7 @@ describe('0.1-0.2 | Connection', () => { serviceEndpoint: 'https://example.com', label: 'test', }, + connectionTypes: [], }) }) }) @@ -367,6 +372,7 @@ describe('0.1-0.2 | Connection', () => { outOfBandId: expect.any(String), autoAcceptConnection: true, mediatorId: 'a-mediator-id', + connectionTypes: [], }) }) @@ -492,6 +498,7 @@ describe('0.1-0.2 | Connection', () => { autoAcceptConnection: true, mediatorId: 'a-mediator-id', outOfBandId: outOfBandRecord.id, + connectionTypes: [], }) }) diff --git a/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/connection.test.ts b/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/connection.test.ts new file mode 100644 index 0000000000..0e397e3419 --- /dev/null +++ b/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/connection.test.ts @@ -0,0 +1,188 @@ +import type { ConnectionRecordProps, CustomConnectionTags } from '../../../../../modules/connections' +import type { MediationRecordProps } from '../../../../../modules/routing' + +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../../tests/helpers' +import { Agent } from '../../../../../agent/Agent' +import { + ConnectionRecord, + ConnectionRepository, + ConnectionType, + DidExchangeRole, + DidExchangeState, +} from '../../../../../modules/connections' +import { MediationRecord, MediationState, MediationRepository, MediationRole } from '../../../../../modules/routing' +import { JsonTransformer } from '../../../../../utils' +import * as testModule from '../connection' + +const agentConfig = getAgentConfig('Migration ConnectionRecord 0.2-0.3') +const agentContext = getAgentContext() + +jest.mock('../../../../../modules/connections/repository/ConnectionRepository') +const ConnectionRepositoryMock = ConnectionRepository as jest.Mock +const connectionRepository = new ConnectionRepositoryMock() + +jest.mock('../../../../../modules/routing/repository/MediationRepository') +const MediationRepositoryMock = MediationRepository as jest.Mock +const mediationRepository = new MediationRepositoryMock() + +jest.mock('../../../../../agent/Agent', () => { + return { + Agent: jest.fn(() => ({ + config: agentConfig, + context: agentContext, + dependencyManager: { + resolve: jest.fn((token) => (token === ConnectionRepositoryMock ? connectionRepository : mediationRepository)), + }, + })), + } +}) + +const AgentMock = Agent as jest.Mock + +describe('0.2-0.3 | Connection', () => { + let agent: Agent + + beforeEach(() => { + agent = AgentMock() + }) + + describe('migrateConnectionRecordToV0_3', () => { + it('should fetch all records and apply the needed updates', async () => { + const connectionRecordsProps = [ + getConnection({ + state: DidExchangeState.Completed, + role: DidExchangeRole.Responder, + id: 'theConnectionId', + }), + getConnection({ + state: DidExchangeState.Completed, + role: DidExchangeRole.Responder, + id: 'theConnectionId2', + }), + ] + + const mediationRecordsProps = [ + getMediator({ + state: MediationState.Granted, + role: MediationRole.Recipient, + connectionId: 'theConnectionId', + threadId: 'theThreadId', + }), + ] + + const connectionRecords: ConnectionRecord[] = connectionRecordsProps + + mockFunction(connectionRepository.getAll).mockResolvedValue(connectionRecords) + + const mediationRecords: MediationRecord[] = mediationRecordsProps + + mockFunction(mediationRepository.getAll).mockResolvedValue(mediationRecords) + + await testModule.migrateConnectionRecordToV0_3(agent) + + expect(connectionRepository.getAll).toBeCalledTimes(1) + expect(mediationRepository.getAll).toBeCalledTimes(1) + expect(connectionRepository.update).toBeCalledTimes(connectionRecords.length) + }) + }) + + describe('migrateConnectionRecordMediatorTags', () => { + it('should set the mediator connection type on the record, connection type tags should be undefined', async () => { + const connectionRecordProps = { + state: DidExchangeState.Completed, + role: DidExchangeRole.Responder, + id: 'theConnectionId', + } + + const connectionRecord = getConnection(connectionRecordProps) + + await testModule.migrateConnectionRecordTags(agent, connectionRecord, new Set(['theConnectionId'])) + + expect(connectionRecord.toJSON()).toEqual({ + ...connectionRecordProps, + connectionTypes: [ConnectionType.Mediator], + _tags: { + connectionType: undefined, + }, + metadata: {}, + }) + }) + + it('should add the mediator connection type to existing types on the record, connection type tags should be undefined', async () => { + const connectionRecordProps = { + state: DidExchangeState.Completed, + role: DidExchangeRole.Responder, + id: 'theConnectionId', + _tags: { + connectionType: ['theConnectionType'], + }, + } + + const connectionRecord = getConnection(connectionRecordProps) + + await testModule.migrateConnectionRecordTags(agent, connectionRecord, new Set(['theConnectionId'])) + + expect(connectionRecord.toJSON()).toEqual({ + ...connectionRecordProps, + connectionTypes: ['theConnectionType', ConnectionType.Mediator], + _tags: { + connectionType: undefined, + }, + metadata: {}, + }) + }) + + it('should not set the mediator connection type on the record, connection type tags should be undefined', async () => { + const connectionRecordProps = { + state: DidExchangeState.Completed, + role: DidExchangeRole.Responder, + id: 'theConnectionId', + } + + const connectionRecord = getConnection(connectionRecordProps) + + await testModule.migrateConnectionRecordTags(agent, connectionRecord) + + expect(connectionRecord.toJSON()).toEqual({ + ...connectionRecordProps, + connectionTypes: [], + _tags: { + connectionType: undefined, + }, + metadata: {}, + }) + }) + + it('should not add the mediator connection type to existing types on the record, connection type tags should be undefined', async () => { + const connectionRecordProps = { + state: DidExchangeState.Completed, + role: DidExchangeRole.Responder, + id: 'theConnectionId', + _tags: { + connectionType: ['theConnectionType'], + }, + } + + const connectionRecord = getConnection(connectionRecordProps) + + await testModule.migrateConnectionRecordTags(agent, connectionRecord) + + expect(connectionRecord.toJSON()).toEqual({ + ...connectionRecordProps, + connectionTypes: ['theConnectionType'], + _tags: { + connectionType: undefined, + }, + metadata: {}, + }) + }) + }) +}) + +function getConnection({ state, role, id, _tags }: ConnectionRecordProps & { _tags?: CustomConnectionTags }) { + return JsonTransformer.fromJSON({ state, role, id, _tags }, ConnectionRecord) +} + +function getMediator({ state, role, connectionId, threadId }: MediationRecordProps) { + return JsonTransformer.fromJSON({ state, role, connectionId, threadId }, MediationRecord) +} diff --git a/packages/core/src/storage/migration/updates/0.2-0.3/connection.ts b/packages/core/src/storage/migration/updates/0.2-0.3/connection.ts new file mode 100644 index 0000000000..80ec2fcfae --- /dev/null +++ b/packages/core/src/storage/migration/updates/0.2-0.3/connection.ts @@ -0,0 +1,67 @@ +import type { BaseAgent } from '../../../../agent/BaseAgent' +import type { ConnectionRecord } from '../../../../modules/connections' + +import { ConnectionType, ConnectionRepository } from '../../../../modules/connections' +import { MediationRepository } from '../../../../modules/routing' + +/** + * Migrate the {@link ConnectionRecord} to a 0.3 compatible format. + * + * @param agent + */ +export async function migrateConnectionRecordToV0_3(agent: Agent) { + agent.config.logger.info('Migrating connection records to storage version 0.3') + const connectionRepository = agent.dependencyManager.resolve(ConnectionRepository) + const mediationRepository = agent.dependencyManager.resolve(MediationRepository) + + agent.config.logger.debug('Fetching all connection records from storage') + const allConnections = await connectionRepository.getAll(agent.context) + agent.config.logger.debug(`Found a total of ${allConnections.length} connection records to update`) + + agent.config.logger.debug('Fetching all mediation records from storage') + const allMediators = await mediationRepository.getAll(agent.context) + agent.config.logger.debug(`Found a total of ${allMediators.length} mediation records`) + + const mediatorConnectionIds = new Set(allMediators.map((mediator) => mediator.connectionId)) + + for (const connectionRecord of allConnections) { + agent.config.logger.debug(`Migrating connection record with id ${connectionRecord.id} to storage version 0.3`) + + await migrateConnectionRecordTags(agent, connectionRecord, mediatorConnectionIds) + await connectionRepository.update(agent.context, connectionRecord) + + agent.config.logger.debug( + `Successfully migrated connection record with id ${connectionRecord.id} to storage version 0.3` + ) + } +} + +/** + * + * @param agent + * @param connectionRecord + */ +export async function migrateConnectionRecordTags( + agent: Agent, + connectionRecord: ConnectionRecord, + mediatorConnectionIds: Set = new Set() +) { + agent.config.logger.debug( + `Migrating internal connection record type tags ${connectionRecord.id} to storage version 0.3` + ) + + // Old connection records will have tags set in the 'connectionType' property + const connectionTypeTags = (connectionRecord.getTags().connectionType || []) as [string] + const connectionTypes = [...connectionTypeTags] + + if (mediatorConnectionIds.has(connectionRecord.id) && !connectionTypes.includes(ConnectionType.Mediator)) { + connectionTypes.push(ConnectionType.Mediator) + } + + connectionRecord.connectionTypes = connectionTypes + connectionRecord.setTag('connectionType', undefined) + + agent.config.logger.debug( + `Successfully migrated internal connection record type tags ${connectionRecord.id} to storage version 0.3` + ) +} diff --git a/packages/core/src/storage/migration/updates/0.2-0.3/index.ts b/packages/core/src/storage/migration/updates/0.2-0.3/index.ts index a47fa4f328..60a56fa546 100644 --- a/packages/core/src/storage/migration/updates/0.2-0.3/index.ts +++ b/packages/core/src/storage/migration/updates/0.2-0.3/index.ts @@ -1,7 +1,9 @@ import type { BaseAgent } from '../../../../agent/BaseAgent' +import { migrateConnectionRecordToV0_3 } from './connection' import { migrateProofExchangeRecordToV0_3 } from './proof' export async function updateV0_2ToV0_3(agent: Agent): Promise { await migrateProofExchangeRecordToV0_3(agent) + await migrateConnectionRecordToV0_3(agent) } diff --git a/packages/core/tests/connections.test.ts b/packages/core/tests/connections.test.ts index 60389f872d..598a6d8fe0 100644 --- a/packages/core/tests/connections.test.ts +++ b/packages/core/tests/connections.test.ts @@ -140,23 +140,23 @@ describe('connections', () => { aliceFaberConnection = await aliceAgent.connections.addConnectionType(aliceFaberConnection.id, 'alice-faber-3') // Now search for them - let connectionsFound = await aliceAgent.connections.findAllByConnectionType(['alice-faber-4']) + let connectionsFound = await aliceAgent.connections.findAllByConnectionTypes(['alice-faber-4']) expect(connectionsFound).toEqual([]) - connectionsFound = await aliceAgent.connections.findAllByConnectionType(['alice-faber-1']) + connectionsFound = await aliceAgent.connections.findAllByConnectionTypes(['alice-faber-1']) expect(connectionsFound.map((item) => item.id)).toMatchObject([aliceFaberConnection.id]) - connectionsFound = await aliceAgent.connections.findAllByConnectionType(['alice-faber-2']) + connectionsFound = await aliceAgent.connections.findAllByConnectionTypes(['alice-faber-2']) expect(connectionsFound.map((item) => item.id)).toMatchObject([aliceFaberConnection.id]) - connectionsFound = await aliceAgent.connections.findAllByConnectionType(['alice-faber-3']) + connectionsFound = await aliceAgent.connections.findAllByConnectionTypes(['alice-faber-3']) expect(connectionsFound.map((item) => item.id)).toMatchObject([aliceFaberConnection.id]) - connectionsFound = await aliceAgent.connections.findAllByConnectionType(['alice-faber-1', 'alice-faber-3']) + connectionsFound = await aliceAgent.connections.findAllByConnectionTypes(['alice-faber-1', 'alice-faber-3']) expect(connectionsFound.map((item) => item.id)).toMatchObject([aliceFaberConnection.id]) - connectionsFound = await aliceAgent.connections.findAllByConnectionType([ + connectionsFound = await aliceAgent.connections.findAllByConnectionTypes([ 'alice-faber-1', 'alice-faber-2', 'alice-faber-3', ]) expect(connectionsFound.map((item) => item.id)).toMatchObject([aliceFaberConnection.id]) - connectionsFound = await aliceAgent.connections.findAllByConnectionType(['alice-faber-1', 'alice-faber-4']) + connectionsFound = await aliceAgent.connections.findAllByConnectionTypes(['alice-faber-1', 'alice-faber-4']) expect(connectionsFound).toEqual([]) }) From 5a6e40a82b935b6bcdc604c6851bfa12774a9013 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 22 Dec 2022 09:24:59 +0800 Subject: [PATCH 105/125] refactor(wallet)!: remove wallet.createDid method (#1180) * chore: deprecate agent public did --- .../tests/bbs-signatures.e2e.test.ts | 3 +- .../tests/bbs-signing-provider.e2e.test.ts | 2 +- ...proof.credentials.propose-offerBbs.test.ts | 2 +- packages/core/src/agent/AgentConfig.ts | 5 +- packages/core/src/agent/BaseAgent.ts | 6 +++ .../src/crypto/__tests__/JwsService.test.ts | 7 ++- .../signature/SignatureDecoratorUtils.test.ts | 5 +- .../__tests__/ConnectionService.test.ts | 48 ++++++++++-------- .../indy/IndyCredentialFormatService.ts | 5 +- ...ldproof.connectionless-credentials.test.ts | 4 +- ...v2.ldproof.credentials-auto-accept.test.ts | 5 +- ...f.credentials.propose-offerED25519.test.ts | 3 +- .../dids/__tests__/dids-resolver.e2e.test.ts | 50 +++++++++---------- .../modules/dids/__tests__/peer-did.test.ts | 11 ++-- .../routing/services/MediatorService.ts | 8 ++- .../routing/services/RoutingService.ts | 7 ++- .../services/__tests__/RoutingService.test.ts | 5 +- .../vc/__tests__/W3cCredentialService.test.ts | 6 +-- packages/core/src/wallet/IndyWallet.test.ts | 8 --- packages/core/src/wallet/IndyWallet.ts | 26 ++++++---- packages/core/src/wallet/Wallet.ts | 15 +++++- packages/core/tests/ledger.test.ts | 16 ++++-- packages/core/tests/mocks/MockWallet.ts | 3 -- 23 files changed, 144 insertions(+), 106 deletions(-) diff --git a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts index 8cd087c20e..8e579225fe 100644 --- a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts @@ -6,7 +6,6 @@ import { KeyType, JsonTransformer, DidKey, - Key, SigningProviderRegistry, W3cVerifiableCredential, W3cCredentialService, @@ -220,7 +219,7 @@ describeSkipNode17And18('BBS W3cCredentialService', () => { describe('signPresentation', () => { it('should sign the presentation successfully', async () => { - const signingKey = Key.fromPublicKeyBase58((await wallet.createDid({ seed })).verkey, KeyType.Ed25519) + const signingKey = await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) const signingDidKey = new DidKey(signingKey) const verificationMethod = `${signingDidKey.did}#${signingDidKey.key.fingerprint}` const presentation = JsonTransformer.fromJSON(BbsBlsSignature2020Fixtures.TEST_VP_DOCUMENT, W3cPresentation) diff --git a/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts index b5a90765af..db67e0c5a1 100644 --- a/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts @@ -18,7 +18,7 @@ import { describeSkipNode17And18 } from './util' // use raw key derivation method to speed up wallet creating / opening / closing between tests const walletConfig: WalletConfig = { - id: 'Wallet: IndyWalletTest', + id: 'Wallet: BBS Signing Provider', // generated using indy.generateWalletKey key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', keyDerivationMethod: KeyDerivationMethod.Raw, diff --git a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts index 32074932a8..a1819304b6 100644 --- a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts +++ b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts @@ -36,7 +36,7 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { 'Alice Agent Credentials LD BBS+' )) wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) - await wallet.createDid({ seed }) + await wallet.createKey({ keyType: KeyType.Ed25519, seed }) const key = await wallet.createKey({ keyType: KeyType.Bls12381g2, seed }) issuerDidKey = new DidKey(key) diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index 29ee8e8add..cceef0e271 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -39,7 +39,10 @@ export class AgentConfig { } /** - * @todo remove once did registrar module is available + * @deprecated The public did functionality of the wallet has been deprecated in favour of the DidsModule, which can be + * used to create and resolve dids. Currently the global agent public did functionality is still used by the `LedgerModule`, but + * will be removed once the `LedgerModule` has been deprecated. Do not use this property for new functionality, but rather + * use the `DidsModule`. */ public get publicDidSeed() { return this.initConfig.publicDidSeed diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index ef6b917463..b0abda3209 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -170,6 +170,12 @@ export abstract class BaseAgent { describe('createJws', () => { it('creates a jws for the payload with the key associated with the verkey', async () => { - const { verkey } = await wallet.createDid({ seed: didJwsz6Mkf.SEED }) + const key = await wallet.createKey({ seed: didJwsz6Mkf.SEED, keyType: KeyType.Ed25519 }) const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON) - const key = Key.fromPublicKeyBase58(verkey, KeyType.Ed25519) const kid = new DidKey(key).did const jws = await jwsService.createJws(agentContext, { payload, - verkey, + // FIXME: update to use key instance instead of verkey + verkey: key.publicKeyBase58, header: { kid }, }) diff --git a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts index ec6c906818..894520edaf 100644 --- a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts +++ b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts @@ -1,4 +1,5 @@ import { getAgentConfig } from '../../../tests/helpers' +import { KeyType } from '../../crypto' import { SigningProviderRegistry } from '../../crypto/signing-provider' import { IndyWallet } from '../../wallet/IndyWallet' @@ -53,9 +54,9 @@ describe('Decorators | Signature | SignatureDecoratorUtils', () => { test('signData signs json object and returns SignatureDecorator', async () => { const seed1 = '00000000000000000000000000000My1' - const { verkey } = await wallet.createDid({ seed: seed1 }) + const key = await wallet.createKey({ seed: seed1, keyType: KeyType.Ed25519 }) - const result = await signData(data, wallet, verkey) + const result = await signData(data, wallet, key.publicKeyBase58) expect(result).toEqual(signedData) }) diff --git a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index 6030b5877e..9cc403ecba 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -19,6 +19,7 @@ import { Key, KeyType } from '../../../crypto' import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { signData, unpackAndVerifySignatureDecorator } from '../../../decorators/signature/SignatureDecoratorUtils' import { JsonTransformer } from '../../../utils/JsonTransformer' +import { indyDidFromPublicKeyBase58 } from '../../../utils/did' import { uuid } from '../../../utils/uuid' import { IndyWallet } from '../../../wallet/IndyWallet' import { AckMessage, AckStatus } from '../../common' @@ -388,8 +389,10 @@ describe('ConnectionService', () => { it('returns a connection response message containing the information from the connection record', async () => { expect.assertions(2) + const key = await wallet.createKey({ keyType: KeyType.Ed25519 }) + const did = indyDidFromPublicKeyBase58(key.publicKeyBase58) + // Needed for signing connection~sig - const { did, verkey } = await wallet.createDid() const mockConnection = getMockConnection({ state: DidExchangeState.RequestReceived, role: DidExchangeRole.Responder, @@ -398,13 +401,13 @@ describe('ConnectionService', () => { }, }) - const recipientKeys = [new DidKey(Key.fromPublicKeyBase58(verkey, KeyType.Ed25519))] + const recipientKeys = [new DidKey(key)] const outOfBand = getMockOutOfBand({ recipientKeys: recipientKeys.map((did) => did.did) }) const publicKey = new Ed25119Sig2018({ id: `${did}#1`, controller: did, - publicKeyBase58: verkey, + publicKeyBase58: key.publicKeyBase58, }) const mockDidDoc = new DidDoc({ id: did, @@ -477,8 +480,11 @@ describe('ConnectionService', () => { it('returns a connection record containing the information from the connection response', async () => { expect.assertions(2) - const { did, verkey } = await wallet.createDid() - const { did: theirDid, verkey: theirVerkey } = await wallet.createDid() + const key = await wallet.createKey({ keyType: KeyType.Ed25519 }) + const did = indyDidFromPublicKeyBase58(key.publicKeyBase58) + + const theirKey = await wallet.createKey({ keyType: KeyType.Ed25519 }) + const theirDid = indyDidFromPublicKeyBase58(key.publicKeyBase58) const connectionRecord = getMockConnection({ did, @@ -486,8 +492,6 @@ describe('ConnectionService', () => { role: DidExchangeRole.Requester, }) - const theirKey = Key.fromPublicKeyBase58(theirVerkey, KeyType.Ed25519) - const otherPartyConnection = new Connection({ did: theirDid, didDoc: new DidDoc({ @@ -513,7 +517,7 @@ describe('ConnectionService', () => { }) const plainConnection = JsonTransformer.toJSON(otherPartyConnection) - const connectionSig = await signData(plainConnection, wallet, theirVerkey) + const connectionSig = await signData(plainConnection, wallet, theirKey.publicKeyBase58) const connectionResponse = new ConnectionResponseMessage({ threadId: uuid(), @@ -527,7 +531,7 @@ describe('ConnectionService', () => { agentContext, connection: connectionRecord, senderKey: theirKey, - recipientKey: Key.fromPublicKeyBase58(verkey, KeyType.Ed25519), + recipientKey: key, }) const processedConnection = await connectionService.processResponse(messageContext, outOfBandRecord) @@ -562,16 +566,17 @@ describe('ConnectionService', () => { it('throws an error when the connection sig is not signed with the same key as the recipient key from the invitation', async () => { expect.assertions(1) - const { did, verkey } = await wallet.createDid() - const { did: theirDid, verkey: theirVerkey } = await wallet.createDid() + const key = await wallet.createKey({ keyType: KeyType.Ed25519 }) + const did = indyDidFromPublicKeyBase58(key.publicKeyBase58) + + const theirKey = await wallet.createKey({ keyType: KeyType.Ed25519 }) + const theirDid = indyDidFromPublicKeyBase58(key.publicKeyBase58) const connectionRecord = getMockConnection({ did, role: DidExchangeRole.Requester, state: DidExchangeState.RequestSent, }) - const theirKey = Key.fromPublicKeyBase58(theirVerkey, KeyType.Ed25519) - const otherPartyConnection = new Connection({ did: theirDid, didDoc: new DidDoc({ @@ -596,7 +601,7 @@ describe('ConnectionService', () => { }), }) const plainConnection = JsonTransformer.toJSON(otherPartyConnection) - const connectionSig = await signData(plainConnection, wallet, theirVerkey) + const connectionSig = await signData(plainConnection, wallet, theirKey.publicKeyBase58) const connectionResponse = new ConnectionResponseMessage({ threadId: uuid(), @@ -606,13 +611,13 @@ describe('ConnectionService', () => { // Recipient key `verkey` is not the same as theirVerkey which was used to sign message, // therefore it should cause a failure. const outOfBandRecord = getMockOutOfBand({ - recipientKeys: [new DidKey(Key.fromPublicKeyBase58(verkey, KeyType.Ed25519)).did], + recipientKeys: [new DidKey(key).did], }) const messageContext = new InboundMessageContext(connectionResponse, { agentContext, connection: connectionRecord, senderKey: theirKey, - recipientKey: Key.fromPublicKeyBase58(verkey, KeyType.Ed25519), + recipientKey: key, }) return expect(connectionService.processResponse(messageContext, outOfBandRecord)).rejects.toThrowError( @@ -625,19 +630,20 @@ describe('ConnectionService', () => { it('throws an error when the message does not contain a DID Document', async () => { expect.assertions(1) - const { did } = await wallet.createDid() - const { did: theirDid, verkey: theirVerkey } = await wallet.createDid() + const key = await wallet.createKey({ keyType: KeyType.Ed25519 }) + const did = indyDidFromPublicKeyBase58(key.publicKeyBase58) + + const theirKey = await wallet.createKey({ keyType: KeyType.Ed25519 }) + const theirDid = indyDidFromPublicKeyBase58(key.publicKeyBase58) const connectionRecord = getMockConnection({ did, state: DidExchangeState.RequestSent, theirDid: undefined, }) - const theirKey = Key.fromPublicKeyBase58(theirVerkey, KeyType.Ed25519) - const otherPartyConnection = new Connection({ did: theirDid }) const plainConnection = JsonTransformer.toJSON(otherPartyConnection) - const connectionSig = await signData(plainConnection, wallet, theirVerkey) + const connectionSig = await signData(plainConnection, wallet, theirKey.publicKeyBase58) const connectionResponse = new ConnectionResponseMessage({ threadId: uuid(), connectionSig }) diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts index cf9beaeb9f..cf14a6c4db 100644 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts @@ -22,11 +22,13 @@ import type { import type { IndyCredentialFormat } from './IndyCredentialFormat' import type * as Indy from 'indy-sdk' +import { KeyType } from '../../../../crypto' import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../../error' import { JsonEncoder } from '../../../../utils/JsonEncoder' import { JsonTransformer } from '../../../../utils/JsonTransformer' import { MessageValidator } from '../../../../utils/MessageValidator' +import { TypedArrayEncoder } from '../../../../utils/TypedArrayEncoder' import { getIndyDidFromVerificationMethod } from '../../../../utils/did' import { uuid } from '../../../../utils/uuid' import { ConnectionService } from '../../../connections' @@ -517,7 +519,8 @@ export class IndyCredentialFormatService implements CredentialFormatService { .observable(CredentialEventTypes.CredentialStateChanged) .subscribe(aliceReplay) wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) - await wallet.createDid({ seed }) + + await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) signCredentialOptions = { credential: TEST_LD_DOCUMENT, diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts index ce0526ffc2..2223ac9132 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts @@ -6,6 +6,7 @@ import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../../form import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { InjectionSymbols } from '../../../../../constants' +import { KeyType } from '../../../../../crypto' import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' import { CREDENTIALS_CONTEXT_V1_URL } from '../../../../vc/constants' import { AutoAcceptCredential, CredentialState } from '../../../models' @@ -43,7 +44,7 @@ describe('credentials', () => { )) wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) - await wallet.createDid({ seed }) + await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) signCredentialOptions = { credential: TEST_LD_DOCUMENT, options: { @@ -142,7 +143,7 @@ describe('credentials', () => { AutoAcceptCredential.ContentApproved )) wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) - await wallet.createDid({ seed }) + await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) signCredentialOptions = { credential: TEST_LD_DOCUMENT, options: { diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts index d53b4b961e..3d4d757554 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -6,6 +6,7 @@ import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../../form import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { InjectionSymbols } from '../../../../../constants' +import { KeyType } from '../../../../../crypto' import { DidCommMessageRepository } from '../../../../../storage' import { JsonTransformer } from '../../../../../utils/JsonTransformer' import { CredentialState } from '../../../models' @@ -65,7 +66,7 @@ describe('credentials', () => { 'Alice Agent Credentials LD' )) wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) - await wallet.createDid({ seed }) + await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) signCredentialOptions = { credential: inputDocAsJson, options: { diff --git a/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts index f10a16ead7..09d64b2b70 100644 --- a/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts +++ b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts @@ -1,11 +1,8 @@ -import type { Wallet } from '../../../wallet' - -import { convertPublicKeyToX25519 } from '@stablelib/ed25519' +import type { SovDidCreateOptions } from '../methods' import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' -import { InjectionSymbols } from '../../../constants' -import { Key, KeyType } from '../../../crypto' +import { AriesFrameworkError } from '../../../error' import { JsonTransformer } from '../../../utils' import { sleep } from '../../../utils/sleep' @@ -23,21 +20,24 @@ describe('dids', () => { }) it('should resolve a did:sov did', async () => { - const wallet = agent.injectionContainer.resolve(InjectionSymbols.Wallet) - const { did: unqualifiedDid, verkey: publicKeyBase58 } = await wallet.createDid() + const publicDid = agent.publicDid?.did - await agent.ledger.registerPublicDid(unqualifiedDid, publicKeyBase58, 'Alias', 'TRUSTEE') + if (!publicDid) throw new Error('Agent has no public did') + + const createResult = await agent.dids.create({ + method: 'sov', + options: { + submitterDid: `did:sov:${publicDid}`, + alias: 'Alias', + role: 'TRUSTEE', + }, + }) // Terrible, but the did can't be immediately resolved, so we need to wait a bit await sleep(1000) - const did = `did:sov:${unqualifiedDid}` - const didResult = await agent.dids.resolve(did) - - const x25519PublicKey = convertPublicKeyToX25519( - Key.fromPublicKeyBase58(publicKeyBase58, KeyType.Ed25519).publicKey - ) - const x25519PublicKeyBase58 = Key.fromPublicKey(x25519PublicKey, KeyType.X25519).publicKeyBase58 + if (!createResult.didState.did) throw new AriesFrameworkError('Unable to register did') + const didResult = await agent.dids.resolve(createResult.didState.did) expect(JsonTransformer.toJSON(didResult)).toMatchObject({ didDocument: { @@ -46,28 +46,28 @@ describe('dids', () => { 'https://w3id.org/security/suites/ed25519-2018/v1', 'https://w3id.org/security/suites/x25519-2019/v1', ], - id: did, + id: createResult.didState.did, alsoKnownAs: undefined, controller: undefined, verificationMethod: [ { type: 'Ed25519VerificationKey2018', - controller: did, - id: `${did}#key-1`, - publicKeyBase58, + controller: createResult.didState.did, + id: `${createResult.didState.did}#key-1`, + publicKeyBase58: expect.any(String), }, { - controller: did, + controller: createResult.didState.did, type: 'X25519KeyAgreementKey2019', - id: `${did}#key-agreement-1`, - publicKeyBase58: x25519PublicKeyBase58, + id: `${createResult.didState.did}#key-agreement-1`, + publicKeyBase58: expect.any(String), }, ], capabilityDelegation: undefined, capabilityInvocation: undefined, - authentication: [`${did}#key-1`], - assertionMethod: [`${did}#key-1`], - keyAgreement: [`${did}#key-agreement-1`], + authentication: [`${createResult.didState.did}#key-1`], + assertionMethod: [`${createResult.didState.did}#key-1`], + keyAgreement: [`${createResult.didState.did}#key-agreement-1`], service: undefined, }, didDocumentMetadata: {}, diff --git a/packages/core/src/modules/dids/__tests__/peer-did.test.ts b/packages/core/src/modules/dids/__tests__/peer-did.test.ts index 1fadc6f327..5467b601c9 100644 --- a/packages/core/src/modules/dids/__tests__/peer-did.test.ts +++ b/packages/core/src/modules/dids/__tests__/peer-did.test.ts @@ -62,10 +62,12 @@ describe('peer dids', () => { test('create a peer did method 1 document from ed25519 keys with a service', async () => { // The following scenario show how we could create a key and create a did document from it for DID Exchange - const { verkey: publicKeyBase58 } = await wallet.createDid({ seed: 'astringoftotalin32characterslong' }) - const { verkey: mediatorPublicKeyBase58 } = await wallet.createDid({ seed: 'anotherstringof32characterslong1' }) + const ed25519Key = await wallet.createKey({ seed: 'astringoftotalin32characterslong', keyType: KeyType.Ed25519 }) + const mediatorEd25519Key = await wallet.createKey({ + seed: 'anotherstringof32characterslong1', + keyType: KeyType.Ed25519, + }) - const ed25519Key = Key.fromPublicKeyBase58(publicKeyBase58, KeyType.Ed25519) const x25519Key = Key.fromPublicKey(convertPublicKeyToX25519(ed25519Key.publicKey), KeyType.X25519) const ed25519VerificationMethod = getEd25519VerificationMethod({ @@ -87,10 +89,9 @@ describe('peer dids', () => { controller: '#id', }) - const mediatorEd25519Key = Key.fromPublicKeyBase58(mediatorPublicKeyBase58, KeyType.Ed25519) const mediatorEd25519DidKey = new DidKey(mediatorEd25519Key) - const mediatorX25519Key = Key.fromPublicKey(convertPublicKeyToX25519(mediatorEd25519Key.publicKey), KeyType.X25519) + // Use ed25519 did:key, which also includes the x25519 key used for didcomm const mediatorRoutingKey = `${mediatorEd25519DidKey.did}#${mediatorX25519Key.fingerprint}` diff --git a/packages/core/src/modules/routing/services/MediatorService.ts b/packages/core/src/modules/routing/services/MediatorService.ts index af0c5cdb5f..8bd916e9a3 100644 --- a/packages/core/src/modules/routing/services/MediatorService.ts +++ b/packages/core/src/modules/routing/services/MediatorService.ts @@ -8,6 +8,7 @@ import type { ForwardMessage, MediationRequestMessage } from '../messages' import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' +import { KeyType } from '../../../crypto' import { AriesFrameworkError } from '../../../error' import { Logger } from '../../../logger' import { injectable, inject } from '../../../plugins' @@ -199,11 +200,14 @@ export class MediatorService { } public async createMediatorRoutingRecord(agentContext: AgentContext): Promise { - const { verkey } = await agentContext.wallet.createDid() + const routingKey = await agentContext.wallet.createKey({ + keyType: KeyType.Ed25519, + }) const routingRecord = new MediatorRoutingRecord({ id: this.mediatorRoutingRepository.MEDIATOR_ROUTING_RECORD_ID, - routingKeys: [verkey], + // FIXME: update to fingerprint to include the key type + routingKeys: [routingKey.publicKeyBase58], }) await this.mediatorRoutingRepository.save(agentContext, routingRecord) diff --git a/packages/core/src/modules/routing/services/RoutingService.ts b/packages/core/src/modules/routing/services/RoutingService.ts index 7c21b62ec4..94224c58a5 100644 --- a/packages/core/src/modules/routing/services/RoutingService.ts +++ b/packages/core/src/modules/routing/services/RoutingService.ts @@ -1,9 +1,10 @@ import type { AgentContext } from '../../../agent' +import type { Key } from '../../../crypto' import type { Routing } from '../../connections' import type { RoutingCreatedEvent } from '../RoutingEvents' import { EventEmitter } from '../../../agent/EventEmitter' -import { Key, KeyType } from '../../../crypto' +import { KeyType } from '../../../crypto' import { injectable } from '../../../plugins' import { RoutingEventTypes } from '../RoutingEvents' @@ -26,9 +27,7 @@ export class RoutingService { { mediatorId, useDefaultMediator = true }: GetRoutingOptions = {} ): Promise { // Create and store new key - const { verkey: publicKeyBase58 } = await agentContext.wallet.createDid() - - const recipientKey = Key.fromPublicKeyBase58(publicKeyBase58, KeyType.Ed25519) + const recipientKey = await agentContext.wallet.createKey({ keyType: KeyType.Ed25519 }) let routing: Routing = { endpoints: agentContext.config.endpoints, diff --git a/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts b/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts index 504da2f0b2..de6763884f 100644 --- a/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts @@ -34,10 +34,7 @@ const routing = { routingKeys: [], } mockFunction(mediationRecipientService.addMediationRouting).mockResolvedValue(routing) -mockFunction(wallet.createDid).mockResolvedValue({ - did: 'some-did', - verkey: recipientKey.publicKeyBase58, -}) +mockFunction(wallet.createKey).mockResolvedValue(recipientKey) describe('RoutingService', () => { afterEach(() => { diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index 955ad1d827..2ab30fe7e5 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -2,7 +2,6 @@ import type { AgentContext } from '../../../agent' import { getAgentConfig, getAgentContext, mockFunction } from '../../../../tests/helpers' import { KeyType } from '../../../crypto' -import { Key } from '../../../crypto/Key' import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { JsonTransformer } from '../../../utils/JsonTransformer' import { IndyWallet } from '../../../wallet/IndyWallet' @@ -116,9 +115,8 @@ describe('W3cCredentialService', () => { let issuerDidKey: DidKey let verificationMethod: string beforeAll(async () => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const issuerDidInfo = await wallet.createDid({ seed }) - const issuerKey = Key.fromPublicKeyBase58(issuerDidInfo.verkey, KeyType.Ed25519) + // TODO: update to use did registrar + const issuerKey = await wallet.createKey({ keyType: KeyType.Ed25519, seed }) issuerDidKey = new DidKey(issuerKey) verificationMethod = `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}` }) diff --git a/packages/core/src/wallet/IndyWallet.test.ts b/packages/core/src/wallet/IndyWallet.test.ts index 6ab30b6657..07c5e74978 100644 --- a/packages/core/src/wallet/IndyWallet.test.ts +++ b/packages/core/src/wallet/IndyWallet.test.ts @@ -68,14 +68,6 @@ describe('IndyWallet', () => { }) }) - test('Create DID', async () => { - const didInfo = await indyWallet.createDid({ seed: '00000000000000000000000Forward01' }) - expect(didInfo).toMatchObject({ - did: 'DtWRdd6C5dN5vpcN6XRAvu', - verkey: '82RBSn3heLgXzZd74UsMC8Q8YRfEEhQoAM7LUqE6bevJ', - }) - }) - test('Generate Nonce', async () => { await expect(indyWallet.generateNonce()).resolves.toEqual(expect.any(String)) }) diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts index b8c54a2f71..3e279ff2aa 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/core/src/wallet/IndyWallet.ts @@ -63,6 +63,12 @@ export class IndyWallet implements Wallet { return this.walletHandle !== undefined } + /** + * @deprecated The public did functionality of the wallet has been deprecated in favour of the DidsModule, which can be + * used to create and resolve dids. Currently the global agent public did functionality is still used by the `LedgerModule`, but + * will be removed once the `LedgerModule` has been deprecated. Do not use this property for new functionality, but rather + * use the `DidsModule`. + */ public get publicDid() { return this.publicDidInfo } @@ -435,19 +441,21 @@ export class IndyWallet implements Wallet { } } + /** + * @deprecated The public did functionality of the wallet has been deprecated in favour of the DidsModule, which can be + * used to create and resolve dids. Currently the global agent public did functionality is still used by the `LedgerModule`, but + * will be removed once the `LedgerModule` has been deprecated. Do not use this property for new functionality, but rather + * use the `DidsModule`. + */ public async initPublicDid(didConfig: DidConfig) { - const { did, verkey } = await this.createDid(didConfig) - this.publicDidInfo = { - did, - verkey, - } - } - - public async createDid(didConfig?: DidConfig): Promise { + // The Indy SDK cannot use a key to sign a request for the ledger. This is the only place where we need to call createDid try { const [did, verkey] = await this.indy.createAndStoreMyDid(this.handle, didConfig || {}) - return { did, verkey } + this.publicDidInfo = { + did, + verkey, + } } catch (error) { if (!isError(error)) { throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') diff --git a/packages/core/src/wallet/Wallet.ts b/packages/core/src/wallet/Wallet.ts index 9e942eff56..6c5cff6388 100644 --- a/packages/core/src/wallet/Wallet.ts +++ b/packages/core/src/wallet/Wallet.ts @@ -10,7 +10,14 @@ import type { import type { Buffer } from '../utils/buffer' export interface Wallet extends Disposable { + /** + * @deprecated The public did functionality of the wallet has been deprecated in favour of the DidsModule, which can be + * used to create and resolve dids. Currently the global agent public did functionality is still used by the `LedgerModule`, but + * will be removed once the `LedgerModule` has been deprecated. Do not use this property for new functionality, but rather + * use the `DidsModule`. + */ publicDid: DidInfo | undefined + isInitialized: boolean isProvisioned: boolean @@ -27,8 +34,14 @@ export interface Wallet extends Disposable { sign(options: WalletSignOptions): Promise verify(options: WalletVerifyOptions): Promise + /** + * @deprecated The public did functionality of the wallet has been deprecated in favour of the DidsModule, which can be + * used to create and resolve dids. Currently the global agent public did functionality is still used by the `LedgerModule`, but + * will be removed once the `LedgerModule` has been deprecated. Do not use this property for new functionality, but rather + * use the `DidsModule`. + */ initPublicDid(didConfig: DidConfig): Promise - createDid(didConfig?: DidConfig): Promise + pack(payload: Record, recipientKeys: string[], senderVerkey?: string): Promise unpack(encryptedMessage: EncryptedMessage): Promise generateNonce(): Promise diff --git a/packages/core/tests/ledger.test.ts b/packages/core/tests/ledger.test.ts index 28198b02a5..9d3411e54d 100644 --- a/packages/core/tests/ledger.test.ts +++ b/packages/core/tests/ledger.test.ts @@ -1,8 +1,15 @@ import { promises } from 'fs' import * as indy from 'indy-sdk' +import { KeyType } from '../src' import { Agent } from '../src/agent/Agent' -import { DID_IDENTIFIER_REGEX, isAbbreviatedVerkey, isFullVerkey, VERKEY_REGEX } from '../src/utils/did' +import { + DID_IDENTIFIER_REGEX, + indyDidFromPublicKeyBase58, + isAbbreviatedVerkey, + isFullVerkey, + VERKEY_REGEX, +} from '../src/utils/did' import { sleep } from '../src/utils/sleep' import { genesisPath, getAgentOptions } from './helpers' @@ -63,11 +70,12 @@ describe('ledger', () => { } const faberWallet = faberAgent.context.wallet - const didInfo = await faberWallet.createDid() + const key = await faberWallet.createKey({ keyType: KeyType.Ed25519 }) + const did = indyDidFromPublicKeyBase58(key.publicKeyBase58) - const result = await faberAgent.ledger.registerPublicDid(didInfo.did, didInfo.verkey, 'alias', 'TRUST_ANCHOR') + const result = await faberAgent.ledger.registerPublicDid(did, key.publicKeyBase58, 'alias', 'TRUST_ANCHOR') - expect(result).toEqual(didInfo.did) + expect(result).toEqual(did) }) test('register schema on ledger', async () => { diff --git a/packages/core/tests/mocks/MockWallet.ts b/packages/core/tests/mocks/MockWallet.ts index caf24a990c..7f941325b7 100644 --- a/packages/core/tests/mocks/MockWallet.ts +++ b/packages/core/tests/mocks/MockWallet.ts @@ -44,9 +44,6 @@ export class MockWallet implements Wallet { public initPublicDid(didConfig: DidConfig): Promise { throw new Error('Method not implemented.') } - public createDid(didConfig?: DidConfig): Promise { - throw new Error('Method not implemented.') - } public pack( payload: Record, recipientKeys: string[], From e299a0377b3cde2daa4a47aa4856d8f32707cf7b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Dec 2022 15:18:22 +0100 Subject: [PATCH 106/125] chore(release): v0.3.0 (#1064) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 133 ++++++++++++++++++++++++++ lerna.json | 2 +- packages/action-menu/CHANGELOG.md | 34 +++++++ packages/action-menu/package.json | 8 +- packages/bbs-signatures/package.json | 6 +- packages/core/CHANGELOG.md | 129 +++++++++++++++++++++++++ packages/core/package.json | 2 +- packages/node/CHANGELOG.md | 13 +++ packages/node/package.json | 4 +- packages/question-answer/CHANGELOG.md | 52 ++++++++++ packages/question-answer/package.json | 8 +- packages/react-native/CHANGELOG.md | 13 +++ packages/react-native/package.json | 4 +- packages/tenants/CHANGELOG.md | 8 ++ packages/tenants/package.json | 6 +- 15 files changed, 402 insertions(+), 20 deletions(-) create mode 100644 packages/action-menu/CHANGELOG.md create mode 100644 packages/question-answer/CHANGELOG.md create mode 100644 packages/tenants/CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 47323c1ec8..6a37e2a53e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,139 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) + +### Bug Fixes + +- **connections:** do not log AgentContext object ([#1085](https://github.com/hyperledger/aries-framework-javascript/issues/1085)) ([ef20f1e](https://github.com/hyperledger/aries-framework-javascript/commit/ef20f1ef420e5345825cc9e79f52ecfb191489fc)) +- **connections:** use new did for each connection from reusable invitation ([#1174](https://github.com/hyperledger/aries-framework-javascript/issues/1174)) ([c0569b8](https://github.com/hyperledger/aries-framework-javascript/commit/c0569b88c27ee7785cf150ee14a5f9ebcc99898b)) +- credential values encoding ([#1157](https://github.com/hyperledger/aries-framework-javascript/issues/1157)) ([0e89e6c](https://github.com/hyperledger/aries-framework-javascript/commit/0e89e6c9f4a3cdbf98c5d85de2e015becdc3e1fc)) +- **demo:** direct import to remove warnings ([#1094](https://github.com/hyperledger/aries-framework-javascript/issues/1094)) ([6747756](https://github.com/hyperledger/aries-framework-javascript/commit/674775692bd60b2a0d8a726fa0ed3603b4fc724e)) +- expose AttachmentData and DiscoverFeaturesEvents ([#1146](https://github.com/hyperledger/aries-framework-javascript/issues/1146)) ([e48f481](https://github.com/hyperledger/aries-framework-javascript/commit/e48f481024810a0eba17e32b995a8db0730bbcb1)) +- expose OutOfBandEvents ([#1151](https://github.com/hyperledger/aries-framework-javascript/issues/1151)) ([3c040b6](https://github.com/hyperledger/aries-framework-javascript/commit/3c040b68e0c8a7f5625df427a2ace28f0223bfbc)) +- invalid injection symbols in W3cCredService ([#786](https://github.com/hyperledger/aries-framework-javascript/issues/786)) ([38cb106](https://github.com/hyperledger/aries-framework-javascript/commit/38cb1065e6fbf46c676c7ad52e160b721cb1b4e6)) +- peer dependency for rn bbs signatures ([#785](https://github.com/hyperledger/aries-framework-javascript/issues/785)) ([c751e28](https://github.com/hyperledger/aries-framework-javascript/commit/c751e286aa11a1d2b9424ae23de5647efc5d536f)) +- **problem-report:** proper string interpolation ([#1120](https://github.com/hyperledger/aries-framework-javascript/issues/1120)) ([c4e9679](https://github.com/hyperledger/aries-framework-javascript/commit/c4e96799d8390225ba5aaecced19c79ec1f12fa8)) +- **proofs:** await shouldAutoRespond to correctly handle the check ([#1116](https://github.com/hyperledger/aries-framework-javascript/issues/1116)) ([f294129](https://github.com/hyperledger/aries-framework-javascript/commit/f294129821cd6fcb9b82d875f19cab5a63310b23)) +- **react-native:** move bbs dep to bbs package ([#1076](https://github.com/hyperledger/aries-framework-javascript/issues/1076)) ([c6762bb](https://github.com/hyperledger/aries-framework-javascript/commit/c6762bbe9d64ac5220915af3425d493e505dcc2c)) +- remove sensitive information from agent config toJSON() method ([#1112](https://github.com/hyperledger/aries-framework-javascript/issues/1112)) ([427a80f](https://github.com/hyperledger/aries-framework-javascript/commit/427a80f7759e029222119cf815a866fe9899a170)) +- **routing:** add connection type on mediation grant ([#1147](https://github.com/hyperledger/aries-framework-javascript/issues/1147)) ([979c695](https://github.com/hyperledger/aries-framework-javascript/commit/979c69506996fb1853e200b53d052d474f497bf1)) +- **routing:** async message pickup on init ([#1093](https://github.com/hyperledger/aries-framework-javascript/issues/1093)) ([15cfd91](https://github.com/hyperledger/aries-framework-javascript/commit/15cfd91d1c6ba8e3f8355db4c4941fcbd85382ac)) +- unable to resolve nodejs document loader in react native environment ([#1003](https://github.com/hyperledger/aries-framework-javascript/issues/1003)) ([5cdcfa2](https://github.com/hyperledger/aries-framework-javascript/commit/5cdcfa203e6d457f74250028678dbc3393d8eb5c)) +- use custom document loader in jsonld.frame ([#1119](https://github.com/hyperledger/aries-framework-javascript/issues/1119)) ([36d4656](https://github.com/hyperledger/aries-framework-javascript/commit/36d465669c6714b00167b17fe2924f3c53b5fa68)) +- **vc:** change pubKey input from Buffer to Uint8Array ([#935](https://github.com/hyperledger/aries-framework-javascript/issues/935)) ([80c3740](https://github.com/hyperledger/aries-framework-javascript/commit/80c3740f625328125fe8121035f2d83ce1dee6a5)) + +- refactor!: rename Handler to MessageHandler (#1161) ([5e48696](https://github.com/hyperledger/aries-framework-javascript/commit/5e48696ec16d88321f225628e6cffab243718b4c)), closes [#1161](https://github.com/hyperledger/aries-framework-javascript/issues/1161) +- feat!: use did:key in protocols by default (#1149) ([9f10da8](https://github.com/hyperledger/aries-framework-javascript/commit/9f10da85d8739f7be6c5e6624ba5f53a1d6a3116)), closes [#1149](https://github.com/hyperledger/aries-framework-javascript/issues/1149) +- feat(action-menu)!: move to separate package (#1049) ([e0df0d8](https://github.com/hyperledger/aries-framework-javascript/commit/e0df0d884b1a7816c7c638406606e45f6e169ff4)), closes [#1049](https://github.com/hyperledger/aries-framework-javascript/issues/1049) +- feat(question-answer)!: separate logic to a new module (#1040) ([97d3073](https://github.com/hyperledger/aries-framework-javascript/commit/97d3073aa9300900740c3e8aee8233d38849293d)), closes [#1040](https://github.com/hyperledger/aries-framework-javascript/issues/1040) +- feat!: agent module registration api (#955) ([82a17a3](https://github.com/hyperledger/aries-framework-javascript/commit/82a17a3a1eff61008b2e91695f6527501fe44237)), closes [#955](https://github.com/hyperledger/aries-framework-javascript/issues/955) +- feat!: Discover Features V2 (#991) ([273e353](https://github.com/hyperledger/aries-framework-javascript/commit/273e353f4b36ab5d2420356eb3a53dcfb1c59ec6)), closes [#991](https://github.com/hyperledger/aries-framework-javascript/issues/991) +- refactor!: module to api and module config (#943) ([7cbccb1](https://github.com/hyperledger/aries-framework-javascript/commit/7cbccb1ce9dae2cb1e4887220898f2f74cca8dbe)), closes [#943](https://github.com/hyperledger/aries-framework-javascript/issues/943) +- refactor!: add agent context (#920) ([b47cfcb](https://github.com/hyperledger/aries-framework-javascript/commit/b47cfcba1450cd1d6839bf8192d977bfe33f1bb0)), closes [#920](https://github.com/hyperledger/aries-framework-javascript/issues/920) + +### Features + +- add agent context provider ([#921](https://github.com/hyperledger/aries-framework-javascript/issues/921)) ([a1b1e5a](https://github.com/hyperledger/aries-framework-javascript/commit/a1b1e5a22fd4ab9ef593b5cd7b3c710afcab3142)) +- add base agent class ([#922](https://github.com/hyperledger/aries-framework-javascript/issues/922)) ([113a575](https://github.com/hyperledger/aries-framework-javascript/commit/113a5756ed1b630b3c05929d79f6afcceae4fa6a)) +- add dynamic suite and signing provider ([#949](https://github.com/hyperledger/aries-framework-javascript/issues/949)) ([ab8b8ef](https://github.com/hyperledger/aries-framework-javascript/commit/ab8b8ef1357c7a8dc338eaea16b20d93a0c92d4f)) +- add indynamespace for ledger id for anoncreds ([#965](https://github.com/hyperledger/aries-framework-javascript/issues/965)) ([df3777e](https://github.com/hyperledger/aries-framework-javascript/commit/df3777ee394211a401940bf27b3e5a9e1688f6b2)) +- add present proof v2 ([#979](https://github.com/hyperledger/aries-framework-javascript/issues/979)) ([f38ac05](https://github.com/hyperledger/aries-framework-javascript/commit/f38ac05875e38b6cc130bcb9f603e82657aabe9c)) +- bbs createKey, sign and verify ([#684](https://github.com/hyperledger/aries-framework-javascript/issues/684)) ([5f91738](https://github.com/hyperledger/aries-framework-javascript/commit/5f91738337fac1efbbb4597e7724791e542f0762)) +- **bbs:** extract bbs logic into separate module ([#1035](https://github.com/hyperledger/aries-framework-javascript/issues/1035)) ([991151b](https://github.com/hyperledger/aries-framework-javascript/commit/991151bfff829fa11cd98a1951be9b54a77385a8)) +- **dids:** add did registrar ([#953](https://github.com/hyperledger/aries-framework-javascript/issues/953)) ([93f3c93](https://github.com/hyperledger/aries-framework-javascript/commit/93f3c93310f9dae032daa04a920b7df18e2f8a65)) +- fetch verification method types by proof type ([#913](https://github.com/hyperledger/aries-framework-javascript/issues/913)) ([ed69dac](https://github.com/hyperledger/aries-framework-javascript/commit/ed69dac7784feea7abe430ad685911faa477fa11)) +- issue credentials v2 (W3C/JSON-LD) ([#1092](https://github.com/hyperledger/aries-framework-javascript/issues/1092)) ([574e6a6](https://github.com/hyperledger/aries-framework-javascript/commit/574e6a62ebbd77902c50da821afdfd1b1558abe7)) +- jsonld-credential support ([#718](https://github.com/hyperledger/aries-framework-javascript/issues/718)) ([ea34c47](https://github.com/hyperledger/aries-framework-javascript/commit/ea34c4752712efecf3367c5a5fc4b06e66c1e9d7)) +- **ledger:** smart schema and credential definition registration ([#900](https://github.com/hyperledger/aries-framework-javascript/issues/900)) ([1e708e9](https://github.com/hyperledger/aries-framework-javascript/commit/1e708e9aeeb63977a7305999a5027d9743a56f91)) +- **oob:** receive Invitation with timeout ([#1156](https://github.com/hyperledger/aries-framework-javascript/issues/1156)) ([9352fa5](https://github.com/hyperledger/aries-framework-javascript/commit/9352fa5eea1e01d29acd0757298398aac45fcab2)) +- **proofs:** add getRequestedCredentialsForProofRequest ([#1028](https://github.com/hyperledger/aries-framework-javascript/issues/1028)) ([26bb9c9](https://github.com/hyperledger/aries-framework-javascript/commit/26bb9c9989a97bf22859a7eccbeabc632521a6c2)) +- **proofs:** delete associated didcomm messages ([#1021](https://github.com/hyperledger/aries-framework-javascript/issues/1021)) ([dba46c3](https://github.com/hyperledger/aries-framework-javascript/commit/dba46c3bc3a1d6b5669f296f0c45cd03dc2294b1)) +- **proofs:** proof negotiation ([#1131](https://github.com/hyperledger/aries-framework-javascript/issues/1131)) ([c752461](https://github.com/hyperledger/aries-framework-javascript/commit/c75246147ffc6be3c815c66b0a7ad66e48996568)) +- **proofs:** proofs module migration script for 0.3.0 ([#1020](https://github.com/hyperledger/aries-framework-javascript/issues/1020)) ([5e9e0fc](https://github.com/hyperledger/aries-framework-javascript/commit/5e9e0fcc7f13b8a27e35761464c8fd970c17d28c)) +- remove keys on mediator when deleting connections ([#1143](https://github.com/hyperledger/aries-framework-javascript/issues/1143)) ([1af57fd](https://github.com/hyperledger/aries-framework-javascript/commit/1af57fde5016300e243eafbbdea5ea26bd8ef313)) +- **routing:** add reconnection parameters to RecipientModuleConfig ([#1070](https://github.com/hyperledger/aries-framework-javascript/issues/1070)) ([d4fd1ae](https://github.com/hyperledger/aries-framework-javascript/commit/d4fd1ae16dc1fd99b043835b97b33f4baece6790)) +- specify httpinboundtransport path ([#1115](https://github.com/hyperledger/aries-framework-javascript/issues/1115)) ([03cdf39](https://github.com/hyperledger/aries-framework-javascript/commit/03cdf397b61253d2eb20694049baf74843b7ed92)) +- **tenants:** initial tenants module ([#932](https://github.com/hyperledger/aries-framework-javascript/issues/932)) ([7cbd08c](https://github.com/hyperledger/aries-framework-javascript/commit/7cbd08c9bb4b14ab2db92b0546d6fcb520f5fec9)) +- **tenants:** tenant lifecycle ([#942](https://github.com/hyperledger/aries-framework-javascript/issues/942)) ([adfa65b](https://github.com/hyperledger/aries-framework-javascript/commit/adfa65b13152a980ba24b03082446e91d8ec5b37)) +- **vc:** delete w3c credential record ([#886](https://github.com/hyperledger/aries-framework-javascript/issues/886)) ([be37011](https://github.com/hyperledger/aries-framework-javascript/commit/be37011c139c5cc69fc591060319d8c373e9508b)) +- **w3c:** add custom document loader option ([#1159](https://github.com/hyperledger/aries-framework-javascript/issues/1159)) ([ff6abdf](https://github.com/hyperledger/aries-framework-javascript/commit/ff6abdfc4e8ca64dd5a3b9859474bfc09e1a6c21)) + +### BREAKING CHANGES + +- Handler has been renamed to MessageHandler to be more descriptive, along with related types and methods. This means: + +Handler is now MessageHandler +HandlerInboundMessage is now MessageHandlerInboundMessage +Dispatcher.registerHandler is now Dispatcher.registerMessageHandlers + +- `useDidKeyInProtocols` configuration parameter is now enabled by default. If your agent only interacts with modern agents (e.g. AFJ 0.2.5 and newer) this will not represent any issue. Otherwise it is safer to explicitly set it to `false`. However, keep in mind that we expect this setting to be deprecated in the future, so we encourage you to update all your agents to use did:key. +- action-menu module has been removed from the core and moved to a separate package. To integrate it in an Agent instance, it can be injected in constructor like this: + +```ts +const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, + modules: { + actionMenu: new ActionMenuModule(), + /* other custom modules */ + }, +}) +``` + +Then, module API can be accessed in `agent.modules.actionMenu`. + +- question-answer module has been removed from the core and moved to a separate package. To integrate it in an Agent instance, it can be injected in constructor like this: + +```ts +const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, + modules: { + questionAnswer: new QuestionAnswerModule(), + /* other custom modules */ + }, +}) +``` + +Then, module API can be accessed in `agent.modules.questionAnswer`. + +- custom modules have been moved to the .modules namespace. In addition the agent constructor has been updated to a single options object that contains the `config` and `dependencies` properties. Instead of constructing the agent like this: + +```ts +const agent = new Agent( + { + /* config */ + }, + agentDependencies +) +``` + +You should now construct it like this: + +```ts +const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, +}) +``` + +This allows for the new custom modules to be defined in the agent constructor. + +- - `queryFeatures` method parameters have been unified to a single `QueryFeaturesOptions` object that requires specification of Discover Features protocol to be used. + +* `isProtocolSupported` has been replaced by the more general synchronous mode of `queryFeatures`, which works when `awaitDisclosures` in options is set. Instead of returning a boolean, it returns an object with matching features +* Custom modules implementing protocols must register them in Feature Registry in order to let them be discovered by other agents (this can be done in module `register(dependencyManager, featureRegistry)` method) + +- All module api classes have been renamed from `XXXModule` to `XXXApi`. A module now represents a module plugin, and is separate from the API of a module. If you previously imported e.g. the `CredentialsModule` class, you should now import the `CredentialsApi` class +- To make AFJ multi-tenancy ready, all services and repositories have been made stateless. A new `AgentContext` is introduced that holds the current context, which is passed to each method call. The public API hasn't been affected, but due to the large impact of this change it is marked as breaking. + ## [0.2.5](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.4...v0.2.5) (2022-10-13) ### Bug Fixes diff --git a/lerna.json b/lerna.json index 22cb1ca6ea..bda0cb3f35 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.2.5", + "version": "0.3.0", "useWorkspaces": true, "npmClient": "yarn", "command": { diff --git a/packages/action-menu/CHANGELOG.md b/packages/action-menu/CHANGELOG.md new file mode 100644 index 0000000000..533e961b33 --- /dev/null +++ b/packages/action-menu/CHANGELOG.md @@ -0,0 +1,34 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) + +- refactor!: rename Handler to MessageHandler (#1161) ([5e48696](https://github.com/hyperledger/aries-framework-javascript/commit/5e48696ec16d88321f225628e6cffab243718b4c)), closes [#1161](https://github.com/hyperledger/aries-framework-javascript/issues/1161) +- feat(action-menu)!: move to separate package (#1049) ([e0df0d8](https://github.com/hyperledger/aries-framework-javascript/commit/e0df0d884b1a7816c7c638406606e45f6e169ff4)), closes [#1049](https://github.com/hyperledger/aries-framework-javascript/issues/1049) + +### BREAKING CHANGES + +- Handler has been renamed to MessageHandler to be more descriptive, along with related types and methods. This means: + +Handler is now MessageHandler +HandlerInboundMessage is now MessageHandlerInboundMessage +Dispatcher.registerHandler is now Dispatcher.registerMessageHandlers + +- action-menu module has been removed from the core and moved to a separate package. To integrate it in an Agent instance, it can be injected in constructor like this: + +```ts +const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, + modules: { + actionMenu: new ActionMenuModule(), + /* other custom modules */ + }, +}) +``` + +Then, module API can be accessed in `agent.modules.actionMenu`. diff --git a/packages/action-menu/package.json b/packages/action-menu/package.json index eb59188728..94c34249fe 100644 --- a/packages/action-menu/package.json +++ b/packages/action-menu/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/action-menu", "main": "build/index", "types": "build/index", - "version": "0.2.5", + "version": "0.3.0", "files": [ "build" ], @@ -24,15 +24,15 @@ "test": "jest" }, "dependencies": { - "rxjs": "^7.2.0", "class-transformer": "0.5.1", - "class-validator": "0.13.1" + "class-validator": "0.13.1", + "rxjs": "^7.2.0" }, "peerDependencies": { "@aries-framework/core": "0.2.5" }, "devDependencies": { - "@aries-framework/node": "0.2.5", + "@aries-framework/node": "0.3.0", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/bbs-signatures/package.json b/packages/bbs-signatures/package.json index 8d54629716..353048460f 100644 --- a/packages/bbs-signatures/package.json +++ b/packages/bbs-signatures/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/bbs-signatures", "main": "build/index", "types": "build/index", - "version": "0.2.5", + "version": "0.3.0", "private": true, "files": [ "build" @@ -25,16 +25,16 @@ "test": "jest" }, "dependencies": { + "@aries-framework/core": "*", "@mattrglobal/bbs-signatures": "^1.0.0", "@mattrglobal/bls12381-key-pair": "^1.0.0", "@stablelib/random": "^1.0.2" }, "peerDependencies": { - "@aries-framework/core": "0.2.5", "@animo-id/react-native-bbs-signatures": "^0.1.0" }, "devDependencies": { - "@aries-framework/node": "0.2.5", + "@aries-framework/node": "*", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index f30627f4af..6d26e52045 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,135 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) + +### Bug Fixes + +- **connections:** do not log AgentContext object ([#1085](https://github.com/hyperledger/aries-framework-javascript/issues/1085)) ([ef20f1e](https://github.com/hyperledger/aries-framework-javascript/commit/ef20f1ef420e5345825cc9e79f52ecfb191489fc)) +- **connections:** use new did for each connection from reusable invitation ([#1174](https://github.com/hyperledger/aries-framework-javascript/issues/1174)) ([c0569b8](https://github.com/hyperledger/aries-framework-javascript/commit/c0569b88c27ee7785cf150ee14a5f9ebcc99898b)) +- credential values encoding ([#1157](https://github.com/hyperledger/aries-framework-javascript/issues/1157)) ([0e89e6c](https://github.com/hyperledger/aries-framework-javascript/commit/0e89e6c9f4a3cdbf98c5d85de2e015becdc3e1fc)) +- expose AttachmentData and DiscoverFeaturesEvents ([#1146](https://github.com/hyperledger/aries-framework-javascript/issues/1146)) ([e48f481](https://github.com/hyperledger/aries-framework-javascript/commit/e48f481024810a0eba17e32b995a8db0730bbcb1)) +- expose OutOfBandEvents ([#1151](https://github.com/hyperledger/aries-framework-javascript/issues/1151)) ([3c040b6](https://github.com/hyperledger/aries-framework-javascript/commit/3c040b68e0c8a7f5625df427a2ace28f0223bfbc)) +- invalid injection symbols in W3cCredService ([#786](https://github.com/hyperledger/aries-framework-javascript/issues/786)) ([38cb106](https://github.com/hyperledger/aries-framework-javascript/commit/38cb1065e6fbf46c676c7ad52e160b721cb1b4e6)) +- **problem-report:** proper string interpolation ([#1120](https://github.com/hyperledger/aries-framework-javascript/issues/1120)) ([c4e9679](https://github.com/hyperledger/aries-framework-javascript/commit/c4e96799d8390225ba5aaecced19c79ec1f12fa8)) +- **proofs:** await shouldAutoRespond to correctly handle the check ([#1116](https://github.com/hyperledger/aries-framework-javascript/issues/1116)) ([f294129](https://github.com/hyperledger/aries-framework-javascript/commit/f294129821cd6fcb9b82d875f19cab5a63310b23)) +- remove sensitive information from agent config toJSON() method ([#1112](https://github.com/hyperledger/aries-framework-javascript/issues/1112)) ([427a80f](https://github.com/hyperledger/aries-framework-javascript/commit/427a80f7759e029222119cf815a866fe9899a170)) +- **routing:** add connection type on mediation grant ([#1147](https://github.com/hyperledger/aries-framework-javascript/issues/1147)) ([979c695](https://github.com/hyperledger/aries-framework-javascript/commit/979c69506996fb1853e200b53d052d474f497bf1)) +- **routing:** async message pickup on init ([#1093](https://github.com/hyperledger/aries-framework-javascript/issues/1093)) ([15cfd91](https://github.com/hyperledger/aries-framework-javascript/commit/15cfd91d1c6ba8e3f8355db4c4941fcbd85382ac)) +- unable to resolve nodejs document loader in react native environment ([#1003](https://github.com/hyperledger/aries-framework-javascript/issues/1003)) ([5cdcfa2](https://github.com/hyperledger/aries-framework-javascript/commit/5cdcfa203e6d457f74250028678dbc3393d8eb5c)) +- use custom document loader in jsonld.frame ([#1119](https://github.com/hyperledger/aries-framework-javascript/issues/1119)) ([36d4656](https://github.com/hyperledger/aries-framework-javascript/commit/36d465669c6714b00167b17fe2924f3c53b5fa68)) +- **vc:** change pubKey input from Buffer to Uint8Array ([#935](https://github.com/hyperledger/aries-framework-javascript/issues/935)) ([80c3740](https://github.com/hyperledger/aries-framework-javascript/commit/80c3740f625328125fe8121035f2d83ce1dee6a5)) + +- refactor!: rename Handler to MessageHandler (#1161) ([5e48696](https://github.com/hyperledger/aries-framework-javascript/commit/5e48696ec16d88321f225628e6cffab243718b4c)), closes [#1161](https://github.com/hyperledger/aries-framework-javascript/issues/1161) +- feat!: use did:key in protocols by default (#1149) ([9f10da8](https://github.com/hyperledger/aries-framework-javascript/commit/9f10da85d8739f7be6c5e6624ba5f53a1d6a3116)), closes [#1149](https://github.com/hyperledger/aries-framework-javascript/issues/1149) +- feat(action-menu)!: move to separate package (#1049) ([e0df0d8](https://github.com/hyperledger/aries-framework-javascript/commit/e0df0d884b1a7816c7c638406606e45f6e169ff4)), closes [#1049](https://github.com/hyperledger/aries-framework-javascript/issues/1049) +- feat(question-answer)!: separate logic to a new module (#1040) ([97d3073](https://github.com/hyperledger/aries-framework-javascript/commit/97d3073aa9300900740c3e8aee8233d38849293d)), closes [#1040](https://github.com/hyperledger/aries-framework-javascript/issues/1040) +- feat!: agent module registration api (#955) ([82a17a3](https://github.com/hyperledger/aries-framework-javascript/commit/82a17a3a1eff61008b2e91695f6527501fe44237)), closes [#955](https://github.com/hyperledger/aries-framework-javascript/issues/955) +- feat!: Discover Features V2 (#991) ([273e353](https://github.com/hyperledger/aries-framework-javascript/commit/273e353f4b36ab5d2420356eb3a53dcfb1c59ec6)), closes [#991](https://github.com/hyperledger/aries-framework-javascript/issues/991) +- refactor!: module to api and module config (#943) ([7cbccb1](https://github.com/hyperledger/aries-framework-javascript/commit/7cbccb1ce9dae2cb1e4887220898f2f74cca8dbe)), closes [#943](https://github.com/hyperledger/aries-framework-javascript/issues/943) +- refactor!: add agent context (#920) ([b47cfcb](https://github.com/hyperledger/aries-framework-javascript/commit/b47cfcba1450cd1d6839bf8192d977bfe33f1bb0)), closes [#920](https://github.com/hyperledger/aries-framework-javascript/issues/920) + +### Features + +- add agent context provider ([#921](https://github.com/hyperledger/aries-framework-javascript/issues/921)) ([a1b1e5a](https://github.com/hyperledger/aries-framework-javascript/commit/a1b1e5a22fd4ab9ef593b5cd7b3c710afcab3142)) +- add base agent class ([#922](https://github.com/hyperledger/aries-framework-javascript/issues/922)) ([113a575](https://github.com/hyperledger/aries-framework-javascript/commit/113a5756ed1b630b3c05929d79f6afcceae4fa6a)) +- add dynamic suite and signing provider ([#949](https://github.com/hyperledger/aries-framework-javascript/issues/949)) ([ab8b8ef](https://github.com/hyperledger/aries-framework-javascript/commit/ab8b8ef1357c7a8dc338eaea16b20d93a0c92d4f)) +- add indynamespace for ledger id for anoncreds ([#965](https://github.com/hyperledger/aries-framework-javascript/issues/965)) ([df3777e](https://github.com/hyperledger/aries-framework-javascript/commit/df3777ee394211a401940bf27b3e5a9e1688f6b2)) +- add present proof v2 ([#979](https://github.com/hyperledger/aries-framework-javascript/issues/979)) ([f38ac05](https://github.com/hyperledger/aries-framework-javascript/commit/f38ac05875e38b6cc130bcb9f603e82657aabe9c)) +- bbs createKey, sign and verify ([#684](https://github.com/hyperledger/aries-framework-javascript/issues/684)) ([5f91738](https://github.com/hyperledger/aries-framework-javascript/commit/5f91738337fac1efbbb4597e7724791e542f0762)) +- **bbs:** extract bbs logic into separate module ([#1035](https://github.com/hyperledger/aries-framework-javascript/issues/1035)) ([991151b](https://github.com/hyperledger/aries-framework-javascript/commit/991151bfff829fa11cd98a1951be9b54a77385a8)) +- **dids:** add did registrar ([#953](https://github.com/hyperledger/aries-framework-javascript/issues/953)) ([93f3c93](https://github.com/hyperledger/aries-framework-javascript/commit/93f3c93310f9dae032daa04a920b7df18e2f8a65)) +- fetch verification method types by proof type ([#913](https://github.com/hyperledger/aries-framework-javascript/issues/913)) ([ed69dac](https://github.com/hyperledger/aries-framework-javascript/commit/ed69dac7784feea7abe430ad685911faa477fa11)) +- issue credentials v2 (W3C/JSON-LD) ([#1092](https://github.com/hyperledger/aries-framework-javascript/issues/1092)) ([574e6a6](https://github.com/hyperledger/aries-framework-javascript/commit/574e6a62ebbd77902c50da821afdfd1b1558abe7)) +- jsonld-credential support ([#718](https://github.com/hyperledger/aries-framework-javascript/issues/718)) ([ea34c47](https://github.com/hyperledger/aries-framework-javascript/commit/ea34c4752712efecf3367c5a5fc4b06e66c1e9d7)) +- **ledger:** smart schema and credential definition registration ([#900](https://github.com/hyperledger/aries-framework-javascript/issues/900)) ([1e708e9](https://github.com/hyperledger/aries-framework-javascript/commit/1e708e9aeeb63977a7305999a5027d9743a56f91)) +- **oob:** receive Invitation with timeout ([#1156](https://github.com/hyperledger/aries-framework-javascript/issues/1156)) ([9352fa5](https://github.com/hyperledger/aries-framework-javascript/commit/9352fa5eea1e01d29acd0757298398aac45fcab2)) +- **proofs:** add getRequestedCredentialsForProofRequest ([#1028](https://github.com/hyperledger/aries-framework-javascript/issues/1028)) ([26bb9c9](https://github.com/hyperledger/aries-framework-javascript/commit/26bb9c9989a97bf22859a7eccbeabc632521a6c2)) +- **proofs:** delete associated didcomm messages ([#1021](https://github.com/hyperledger/aries-framework-javascript/issues/1021)) ([dba46c3](https://github.com/hyperledger/aries-framework-javascript/commit/dba46c3bc3a1d6b5669f296f0c45cd03dc2294b1)) +- **proofs:** proof negotiation ([#1131](https://github.com/hyperledger/aries-framework-javascript/issues/1131)) ([c752461](https://github.com/hyperledger/aries-framework-javascript/commit/c75246147ffc6be3c815c66b0a7ad66e48996568)) +- **proofs:** proofs module migration script for 0.3.0 ([#1020](https://github.com/hyperledger/aries-framework-javascript/issues/1020)) ([5e9e0fc](https://github.com/hyperledger/aries-framework-javascript/commit/5e9e0fcc7f13b8a27e35761464c8fd970c17d28c)) +- remove keys on mediator when deleting connections ([#1143](https://github.com/hyperledger/aries-framework-javascript/issues/1143)) ([1af57fd](https://github.com/hyperledger/aries-framework-javascript/commit/1af57fde5016300e243eafbbdea5ea26bd8ef313)) +- **routing:** add reconnection parameters to RecipientModuleConfig ([#1070](https://github.com/hyperledger/aries-framework-javascript/issues/1070)) ([d4fd1ae](https://github.com/hyperledger/aries-framework-javascript/commit/d4fd1ae16dc1fd99b043835b97b33f4baece6790)) +- **tenants:** initial tenants module ([#932](https://github.com/hyperledger/aries-framework-javascript/issues/932)) ([7cbd08c](https://github.com/hyperledger/aries-framework-javascript/commit/7cbd08c9bb4b14ab2db92b0546d6fcb520f5fec9)) +- **tenants:** tenant lifecycle ([#942](https://github.com/hyperledger/aries-framework-javascript/issues/942)) ([adfa65b](https://github.com/hyperledger/aries-framework-javascript/commit/adfa65b13152a980ba24b03082446e91d8ec5b37)) +- **vc:** delete w3c credential record ([#886](https://github.com/hyperledger/aries-framework-javascript/issues/886)) ([be37011](https://github.com/hyperledger/aries-framework-javascript/commit/be37011c139c5cc69fc591060319d8c373e9508b)) +- **w3c:** add custom document loader option ([#1159](https://github.com/hyperledger/aries-framework-javascript/issues/1159)) ([ff6abdf](https://github.com/hyperledger/aries-framework-javascript/commit/ff6abdfc4e8ca64dd5a3b9859474bfc09e1a6c21)) + +### BREAKING CHANGES + +- Handler has been renamed to MessageHandler to be more descriptive, along with related types and methods. This means: + +Handler is now MessageHandler +HandlerInboundMessage is now MessageHandlerInboundMessage +Dispatcher.registerHandler is now Dispatcher.registerMessageHandlers + +- `useDidKeyInProtocols` configuration parameter is now enabled by default. If your agent only interacts with modern agents (e.g. AFJ 0.2.5 and newer) this will not represent any issue. Otherwise it is safer to explicitly set it to `false`. However, keep in mind that we expect this setting to be deprecated in the future, so we encourage you to update all your agents to use did:key. +- action-menu module has been removed from the core and moved to a separate package. To integrate it in an Agent instance, it can be injected in constructor like this: + +```ts +const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, + modules: { + actionMenu: new ActionMenuModule(), + /* other custom modules */ + }, +}) +``` + +Then, module API can be accessed in `agent.modules.actionMenu`. + +- question-answer module has been removed from the core and moved to a separate package. To integrate it in an Agent instance, it can be injected in constructor like this: + +```ts +const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, + modules: { + questionAnswer: new QuestionAnswerModule(), + /* other custom modules */ + }, +}) +``` + +Then, module API can be accessed in `agent.modules.questionAnswer`. + +- custom modules have been moved to the .modules namespace. In addition the agent constructor has been updated to a single options object that contains the `config` and `dependencies` properties. Instead of constructing the agent like this: + +```ts +const agent = new Agent( + { + /* config */ + }, + agentDependencies +) +``` + +You should now construct it like this: + +```ts +const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, +}) +``` + +This allows for the new custom modules to be defined in the agent constructor. + +- - `queryFeatures` method parameters have been unified to a single `QueryFeaturesOptions` object that requires specification of Discover Features protocol to be used. + +* `isProtocolSupported` has been replaced by the more general synchronous mode of `queryFeatures`, which works when `awaitDisclosures` in options is set. Instead of returning a boolean, it returns an object with matching features +* Custom modules implementing protocols must register them in Feature Registry in order to let them be discovered by other agents (this can be done in module `register(dependencyManager, featureRegistry)` method) + +- All module api classes have been renamed from `XXXModule` to `XXXApi`. A module now represents a module plugin, and is separate from the API of a module. If you previously imported e.g. the `CredentialsModule` class, you should now import the `CredentialsApi` class +- To make AFJ multi-tenancy ready, all services and repositories have been made stateless. A new `AgentContext` is introduced that holds the current context, which is passed to each method call. The public API hasn't been affected, but due to the large impact of this change it is marked as breaking. + ## [0.2.5](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.4...v0.2.5) (2022-10-13) ### Bug Fixes diff --git a/packages/core/package.json b/packages/core/package.json index 3dc169184a..01c2ef62f7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/core", "main": "build/index", "types": "build/index", - "version": "0.2.5", + "version": "0.3.0", "files": [ "build" ], diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index 7bda831ff2..51339c3f56 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) + +- refactor!: add agent context (#920) ([b47cfcb](https://github.com/hyperledger/aries-framework-javascript/commit/b47cfcba1450cd1d6839bf8192d977bfe33f1bb0)), closes [#920](https://github.com/hyperledger/aries-framework-javascript/issues/920) + +### Features + +- add agent context provider ([#921](https://github.com/hyperledger/aries-framework-javascript/issues/921)) ([a1b1e5a](https://github.com/hyperledger/aries-framework-javascript/commit/a1b1e5a22fd4ab9ef593b5cd7b3c710afcab3142)) +- specify httpinboundtransport path ([#1115](https://github.com/hyperledger/aries-framework-javascript/issues/1115)) ([03cdf39](https://github.com/hyperledger/aries-framework-javascript/commit/03cdf397b61253d2eb20694049baf74843b7ed92)) + +### BREAKING CHANGES + +- To make AFJ multi-tenancy ready, all services and repositories have been made stateless. A new `AgentContext` is introduced that holds the current context, which is passed to each method call. The public API hasn't been affected, but due to the large impact of this change it is marked as breaking. + ## [0.2.5](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.4...v0.2.5) (2022-10-13) **Note:** Version bump only for package @aries-framework/node diff --git a/packages/node/package.json b/packages/node/package.json index 9ad1abfb3b..7a1fc24077 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/node", "main": "build/index", "types": "build/index", - "version": "0.2.5", + "version": "0.3.0", "files": [ "build", "bin" @@ -28,7 +28,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.5", + "@aries-framework/core": "0.3.0", "express": "^4.17.1", "ffi-napi": "^4.0.3", "indy-sdk": "^1.16.0-dev-1636", diff --git a/packages/question-answer/CHANGELOG.md b/packages/question-answer/CHANGELOG.md new file mode 100644 index 0000000000..92bab66277 --- /dev/null +++ b/packages/question-answer/CHANGELOG.md @@ -0,0 +1,52 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) + +- refactor!: rename Handler to MessageHandler (#1161) ([5e48696](https://github.com/hyperledger/aries-framework-javascript/commit/5e48696ec16d88321f225628e6cffab243718b4c)), closes [#1161](https://github.com/hyperledger/aries-framework-javascript/issues/1161) +- feat(action-menu)!: move to separate package (#1049) ([e0df0d8](https://github.com/hyperledger/aries-framework-javascript/commit/e0df0d884b1a7816c7c638406606e45f6e169ff4)), closes [#1049](https://github.com/hyperledger/aries-framework-javascript/issues/1049) +- feat(question-answer)!: separate logic to a new module (#1040) ([97d3073](https://github.com/hyperledger/aries-framework-javascript/commit/97d3073aa9300900740c3e8aee8233d38849293d)), closes [#1040](https://github.com/hyperledger/aries-framework-javascript/issues/1040) + +### BREAKING CHANGES + +- Handler has been renamed to MessageHandler to be more descriptive, along with related types and methods. This means: + +Handler is now MessageHandler +HandlerInboundMessage is now MessageHandlerInboundMessage +Dispatcher.registerHandler is now Dispatcher.registerMessageHandlers + +- action-menu module has been removed from the core and moved to a separate package. To integrate it in an Agent instance, it can be injected in constructor like this: + +```ts +const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, + modules: { + actionMenu: new ActionMenuModule(), + /* other custom modules */ + }, +}) +``` + +Then, module API can be accessed in `agent.modules.actionMenu`. + +- question-answer module has been removed from the core and moved to a separate package. To integrate it in an Agent instance, it can be injected in constructor like this: + +```ts +const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, + modules: { + questionAnswer: new QuestionAnswerModule(), + /* other custom modules */ + }, +}) +``` + +Then, module API can be accessed in `agent.modules.questionAnswer`. diff --git a/packages/question-answer/package.json b/packages/question-answer/package.json index ef9a010635..e3e56c9232 100644 --- a/packages/question-answer/package.json +++ b/packages/question-answer/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/question-answer", "main": "build/index", "types": "build/index", - "version": "0.2.5", + "version": "0.3.0", "files": [ "build" ], @@ -24,15 +24,15 @@ "test": "jest" }, "dependencies": { - "rxjs": "^7.2.0", "class-transformer": "0.5.1", - "class-validator": "0.13.1" + "class-validator": "0.13.1", + "rxjs": "^7.2.0" }, "peerDependencies": { "@aries-framework/core": "0.2.5" }, "devDependencies": { - "@aries-framework/node": "0.2.5", + "@aries-framework/node": "0.3.0", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/react-native/CHANGELOG.md b/packages/react-native/CHANGELOG.md index 31568ef45b..64b8138f53 100644 --- a/packages/react-native/CHANGELOG.md +++ b/packages/react-native/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) + +### Bug Fixes + +- peer dependency for rn bbs signatures ([#785](https://github.com/hyperledger/aries-framework-javascript/issues/785)) ([c751e28](https://github.com/hyperledger/aries-framework-javascript/commit/c751e286aa11a1d2b9424ae23de5647efc5d536f)) +- **react-native:** move bbs dep to bbs package ([#1076](https://github.com/hyperledger/aries-framework-javascript/issues/1076)) ([c6762bb](https://github.com/hyperledger/aries-framework-javascript/commit/c6762bbe9d64ac5220915af3425d493e505dcc2c)) + +### Features + +- bbs createKey, sign and verify ([#684](https://github.com/hyperledger/aries-framework-javascript/issues/684)) ([5f91738](https://github.com/hyperledger/aries-framework-javascript/commit/5f91738337fac1efbbb4597e7724791e542f0762)) +- **dids:** add did registrar ([#953](https://github.com/hyperledger/aries-framework-javascript/issues/953)) ([93f3c93](https://github.com/hyperledger/aries-framework-javascript/commit/93f3c93310f9dae032daa04a920b7df18e2f8a65)) +- jsonld-credential support ([#718](https://github.com/hyperledger/aries-framework-javascript/issues/718)) ([ea34c47](https://github.com/hyperledger/aries-framework-javascript/commit/ea34c4752712efecf3367c5a5fc4b06e66c1e9d7)) + ## [0.2.5](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.4...v0.2.5) (2022-10-13) **Note:** Version bump only for package @aries-framework/react-native diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 990f78d7bb..1242d6aa98 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/react-native", "main": "build/index", "types": "build/index", - "version": "0.2.5", + "version": "0.3.0", "files": [ "build" ], @@ -24,7 +24,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.5", + "@aries-framework/core": "0.3.0", "@azure/core-asynciterator-polyfill": "^1.0.0", "events": "^3.3.0" }, diff --git a/packages/tenants/CHANGELOG.md b/packages/tenants/CHANGELOG.md new file mode 100644 index 0000000000..3ba91895eb --- /dev/null +++ b/packages/tenants/CHANGELOG.md @@ -0,0 +1,8 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) + +**Note:** Version bump only for package @aries-framework/tenants diff --git a/packages/tenants/package.json b/packages/tenants/package.json index e9bbe844f4..bcac141048 100644 --- a/packages/tenants/package.json +++ b/packages/tenants/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/tenants", "main": "build/index", "types": "build/index", - "version": "0.2.5", + "version": "0.3.0", "files": [ "build" ], @@ -24,11 +24,11 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.2.5", + "@aries-framework/core": "0.3.0", "async-mutex": "^0.3.2" }, "devDependencies": { - "@aries-framework/node": "0.2.5", + "@aries-framework/node": "0.3.0", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" From 460510db43a7c63fd8dc1c3614be03fd8772f63c Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Tue, 27 Dec 2022 19:49:20 -0300 Subject: [PATCH 107/125] fix: missing migration script and exports (#1184) Signed-off-by: Ariel Gentile --- packages/core/src/index.ts | 2 + .../storage/migration/__tests__/0.2.test.ts | 59 +- .../storage/migration/__tests__/0.3.test.ts | 87 +++ .../__fixtures__/alice-8-dids-0.2.json | 417 ++++++++++++++ .../__fixtures__/alice-8-dids-0.3.json | 417 ++++++++++++++ .../__tests__/__snapshots__/0.2.test.ts.snap | 524 +++++++++++++++++- .../__tests__/__snapshots__/0.3.test.ts.snap | 517 +++++++++++++++++ packages/core/src/storage/migration/index.ts | 1 + .../core/src/storage/migration/updates.ts | 6 + .../__tests__/did.test.ts | 8 +- .../updates/{0.2-0.3 => 0.3-0.3.1}/did.ts | 8 +- .../migration/updates/0.3-0.3.1/index.ts | 7 + .../core/src/utils/__tests__/version.test.ts | 67 ++- packages/core/src/utils/version.ts | 17 +- 14 files changed, 2093 insertions(+), 44 deletions(-) create mode 100644 packages/core/src/storage/migration/__tests__/0.3.test.ts create mode 100644 packages/core/src/storage/migration/__tests__/__fixtures__/alice-8-dids-0.2.json create mode 100644 packages/core/src/storage/migration/__tests__/__fixtures__/alice-8-dids-0.3.json create mode 100644 packages/core/src/storage/migration/__tests__/__snapshots__/0.3.test.ts.snap rename packages/core/src/storage/migration/updates/{0.2-0.3 => 0.3-0.3.1}/__tests__/did.test.ts (90%) rename packages/core/src/storage/migration/updates/{0.2-0.3 => 0.3-0.3.1}/did.ts (88%) create mode 100644 packages/core/src/storage/migration/updates/0.3-0.3.1/index.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2090bd358f..f551cb8d59 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -9,6 +9,7 @@ export type { ModulesMap, DefaultAgentModules, EmptyModuleMap } from './agent/Ag export { EventEmitter } from './agent/EventEmitter' export { FeatureRegistry } from './agent/FeatureRegistry' export { MessageHandler, MessageHandlerInboundMessage } from './agent/MessageHandler' +export { MessageHandlerRegistry } from './agent/MessageHandlerRegistry' export * from './agent/models' export { AgentConfig } from './agent/AgentConfig' export { AgentMessage } from './agent/AgentMessage' @@ -31,6 +32,7 @@ export { InMemoryMessageRepository } from './storage/InMemoryMessageRepository' export { Repository } from './storage/Repository' export * from './storage/RepositoryEvents' export { StorageService, Query } from './storage/StorageService' +export * from './storage/migration' export { getDirFromFilePath } from './utils/path' export { InjectionSymbols } from './constants' export * from './wallet' diff --git a/packages/core/src/storage/migration/__tests__/0.2.test.ts b/packages/core/src/storage/migration/__tests__/0.2.test.ts index 3003a203ab..b67e361855 100644 --- a/packages/core/src/storage/migration/__tests__/0.2.test.ts +++ b/packages/core/src/storage/migration/__tests__/0.2.test.ts @@ -20,7 +20,7 @@ const walletConfig = { key: `Key: 0.2 Update`, } -describe('UpdateAssistant | v0.2 - v0.3', () => { +describe('UpdateAssistant | v0.2 - v0.3.1', () => { it(`should correctly update proof records and create didcomm records`, async () => { // We need to mock the uuid generation to make sure we generate consistent uuids for the new records created. let uuidCounter = 1 @@ -67,6 +67,11 @@ describe('UpdateAssistant | v0.2 - v0.3', () => { toVersion: '0.3', doUpdate: expect.any(Function), }, + { + fromVersion: '0.3', + toVersion: '0.3.1', + doUpdate: expect.any(Function), + }, ]) await updateAssistant.update() @@ -141,4 +146,56 @@ describe('UpdateAssistant | v0.2 - v0.3', () => { uuidSpy.mockReset() }) + + it(`should correctly update the did records`, async () => { + // We need to mock the uuid generation to make sure we generate consistent uuids for the new records created. + let uuidCounter = 1 + const uuidSpy = jest.spyOn(uuid, 'uuid').mockImplementation(() => `${uuidCounter++}-4e4f-41d9-94c4-f49351b811f1`) + + const aliceDidRecordsString = readFileSync(path.join(__dirname, '__fixtures__/alice-8-dids-0.2.json'), 'utf8') + + const dependencyManager = new DependencyManager() + const storageService = new InMemoryStorageService() + dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + + const agent = new Agent( + { + config: { + label: 'Test Agent', + walletConfig, + autoUpdateStorageOnStartup: true, + }, + dependencies: agentDependencies, + }, + dependencyManager + ) + + const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) + + // We need to manually initialize the wallet as we're using the in memory wallet service + // When we call agent.initialize() it will create the wallet and store the current framework + // version in the in memory storage service. We need to manually set the records between initializing + // the wallet and calling agent.initialize() + await agent.wallet.initialize(walletConfig) + + // Set storage after initialization. This mimics as if this wallet + // is opened as an existing wallet instead of a new wallet + storageService.records = JSON.parse(aliceDidRecordsString) + + await agent.initialize() + + // MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here + delete storageService.records.MEDIATOR_ROUTING_RECORD + + expect(storageService.records).toMatchSnapshot() + + // Need to remove backupFiles after each run so we don't get IOErrors + const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` + unlinkSync(backupPath) + + await agent.shutdown() + await agent.wallet.delete() + + uuidSpy.mockReset() + }) }) diff --git a/packages/core/src/storage/migration/__tests__/0.3.test.ts b/packages/core/src/storage/migration/__tests__/0.3.test.ts new file mode 100644 index 0000000000..a7ec0f6adb --- /dev/null +++ b/packages/core/src/storage/migration/__tests__/0.3.test.ts @@ -0,0 +1,87 @@ +import type { FileSystem } from '../../FileSystem' + +import { unlinkSync, readFileSync } from 'fs' +import path from 'path' + +import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' +import { agentDependencies } from '../../../../tests/helpers' +import { Agent } from '../../../agent/Agent' +import { InjectionSymbols } from '../../../constants' +import { DependencyManager } from '../../../plugins' +import * as uuid from '../../../utils/uuid' +import { UpdateAssistant } from '../UpdateAssistant' + +const backupDate = new Date('2022-01-21T22:50:20.522Z') +jest.useFakeTimers().setSystemTime(backupDate) +const backupIdentifier = backupDate.getTime() + +const walletConfig = { + id: `Wallet: 0.3 Update`, + key: `Key: 0.3 Update`, +} + +describe('UpdateAssistant | v0.3 - v0.3.1', () => { + it(`should correctly update the did records`, async () => { + // We need to mock the uuid generation to make sure we generate consistent uuids for the new records created. + let uuidCounter = 1 + const uuidSpy = jest.spyOn(uuid, 'uuid').mockImplementation(() => `${uuidCounter++}-4e4f-41d9-94c4-f49351b811f1`) + + const aliceDidRecordsString = readFileSync(path.join(__dirname, '__fixtures__/alice-8-dids-0.3.json'), 'utf8') + + const dependencyManager = new DependencyManager() + const storageService = new InMemoryStorageService() + dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + + const agent = new Agent( + { + config: { + label: 'Test Agent', + walletConfig, + }, + dependencies: agentDependencies, + }, + dependencyManager + ) + + const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) + + const updateAssistant = new UpdateAssistant(agent, { + v0_1ToV0_2: { + mediationRoleUpdateStrategy: 'doNotChange', + }, + }) + + await updateAssistant.initialize() + + // Set storage after initialization. This mimics as if this wallet + // is opened as an existing wallet instead of a new wallet + storageService.records = JSON.parse(aliceDidRecordsString) + + expect(await updateAssistant.isUpToDate()).toBe(false) + expect(await updateAssistant.getNeededUpdates()).toEqual([ + { + fromVersion: '0.3', + toVersion: '0.3.1', + doUpdate: expect.any(Function), + }, + ]) + + await updateAssistant.update() + + expect(await updateAssistant.isUpToDate()).toBe(true) + expect(await updateAssistant.getNeededUpdates()).toEqual([]) + + // MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here + delete storageService.records.MEDIATOR_ROUTING_RECORD + expect(storageService.records).toMatchSnapshot() + + // Need to remove backupFiles after each run so we don't get IOErrors + const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` + unlinkSync(backupPath) + + await agent.shutdown() + await agent.wallet.delete() + + uuidSpy.mockReset() + }) +}) diff --git a/packages/core/src/storage/migration/__tests__/__fixtures__/alice-8-dids-0.2.json b/packages/core/src/storage/migration/__tests__/__fixtures__/alice-8-dids-0.2.json new file mode 100644 index 0000000000..e6ee2718a1 --- /dev/null +++ b/packages/core/src/storage/migration/__tests__/__fixtures__/alice-8-dids-0.2.json @@ -0,0 +1,417 @@ +{ + "STORAGE_VERSION_RECORD_ID": { + "value": { + "metadata": {}, + "id": "STORAGE_VERSION_RECORD_ID", + "createdAt": "2022-09-08T19:35:53.872Z", + "storageVersion": "0.2" + }, + "id": "STORAGE_VERSION_RECORD_ID", + "type": "StorageVersionRecord", + "tags": {} + }, + "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd": { + "value": { + "_tags": { + "role": "created", + "method": "peer", + "recipientKeyFingerprints": ["z6Mkpg6nDPuYdLMuzNEjaa2Xprh3J2MC1WtNakWUEFqC4wvU"] + }, + "metadata": {}, + "id": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "role": "created", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "ws://ssi.mediator.com", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#57a05508-1d1c-474c-8c68-1afcf3188720"], + "routingKeys": [ + "did:key:z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop#z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop" + ] + } + ], + "authentication": [ + { + "id": "#57a05508-1d1c-474c-8c68-1afcf3188720", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "BDqjd9f7HnsSssQ2u14gym93UT5Lbde1tjbYPysB9j96" + } + ], + "keyAgreement": [ + { + "id": "#59d2ce6f-c9fc-49c6-a8f6-eab14820b028", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "avivQP6GvWj6cBbxbXSSZnZTA4tGsvQ5DB9FXm45tZt" + } + ] + }, + "createdAt": "2022-12-27T13:51:21.344Z" + }, + "id": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "type": "DidRecord", + "tags": { + "role": "created", + "method": "peer", + "recipientKeyFingerprints": ["z6Mkpg6nDPuYdLMuzNEjaa2Xprh3J2MC1WtNakWUEFqC4wvU"] + } + }, + "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W": { + "value": { + "_tags": { + "recipientKeyFingerprints": ["z6MkuLL6bQykGpposTrQhfYceQRR4JDzvdm133xpwb7Dv1oz"], + "role": "received", + "method": "peer" + }, + "metadata": {}, + "id": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "role": "received", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "http://ssi.verifier.com", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#6173438e-09a5-4b1e-895a-0563f5a169b7"], + "routingKeys": [] + } + ], + "authentication": [ + { + "id": "#6173438e-09a5-4b1e-895a-0563f5a169b7", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "Ft541AjJwHLLky1i26amoJsREix9WkWeM33u7K9Czo2c" + } + ], + "keyAgreement": [ + { + "id": "#a0448ee3-093a-4b16-bc59-8bf8559d60a5", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "JCfZJ72mtgGE9xuekJKV6yoAGzLgQCNkdHKkct8uaNKb" + } + ] + }, + "createdAt": "2022-12-27T13:51:51.414Z" + }, + "id": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "type": "DidRecord", + "tags": { + "role": "received", + "method": "peer", + "recipientKeyFingerprints": ["z6MkuLL6bQykGpposTrQhfYceQRR4JDzvdm133xpwb7Dv1oz"] + } + }, + "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q": { + "value": { + "_tags": { + "recipientKeyFingerprints": ["z6Mkv6Kd744JcBL5P9gi5Ddtehxn1gSMTgM9kbTdfQ9soB8G"], + "role": "created", + "method": "peer" + }, + "metadata": {}, + "id": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "role": "created", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "didcomm:transport/queue", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#d9d97b3e-5615-4e60-9366-a8ab1ec1a84d"], + "routingKeys": [] + } + ], + "authentication": [ + { + "id": "#d9d97b3e-5615-4e60-9366-a8ab1ec1a84d", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "Ge4aWoosGdqcGer1Peg3ocQnC7AW3o6o4aYhq8BrsxLt" + } + ], + "keyAgreement": [ + { + "id": "#c30ea91b-5f49-461f-b0bd-046f947ef668", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "HKBdBGRK8uxgCwge2QHPBzVuuayEQFhC2LM3g1fzMFGE" + } + ] + }, + "createdAt": "2022-12-27T13:50:32.815Z" + }, + "id": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "type": "DidRecord", + "tags": { + "role": "created", + "method": "peer", + "recipientKeyFingerprints": ["z6Mkv6Kd744JcBL5P9gi5Ddtehxn1gSMTgM9kbTdfQ9soB8G"] + } + }, + "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx": { + "value": { + "_tags": { + "method": "peer", + "recipientKeyFingerprints": ["z6MkvcuHjVcNRBM78E3xWy7MnVqQV9hAvTawBsPmg3bR1DaE"], + "role": "created" + }, + "metadata": {}, + "id": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "role": "created", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "ws://ssi.mediator.com", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#22219a28-b52a-4024-bc0f-62d3969131fd"], + "routingKeys": [ + "did:key:z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop#z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop" + ] + } + ], + "authentication": [ + { + "id": "#22219a28-b52a-4024-bc0f-62d3969131fd", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "HAeF9FMw5dre1jDFqQ9WwQHQfaRKWaLaVrUqqmdQ5znr" + } + ], + "keyAgreement": [ + { + "id": "#0f9535d1-9c9c-4491-be1e-1628f365b513", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "FztF8HCahTeL9gYHoHnDFo6HruwnKB19ZtbHFbLndAmE" + } + ] + }, + "createdAt": "2022-12-27T13:51:50.193Z" + }, + "id": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "type": "DidRecord", + "tags": { + "role": "created", + "method": "peer", + "recipientKeyFingerprints": ["z6MkvcuHjVcNRBM78E3xWy7MnVqQV9hAvTawBsPmg3bR1DaE"] + } + }, + "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce": { + "value": { + "_tags": { + "method": "peer", + "role": "received", + "recipientKeyFingerprints": ["z6Mkp1wyxzqoUrZ3TvnerfZBq48o1XLDSPH8ibFALkNABSgh"] + }, + "metadata": {}, + "id": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "role": "received", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "ws://ssi.mediator.com", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#075b7e8b-a7bd-41e1-9b01-044f1ccab1a6"], + "routingKeys": [] + } + ], + "authentication": [ + { + "id": "#075b7e8b-a7bd-41e1-9b01-044f1ccab1a6", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "AZgwNkbN9K4aMRwxB6bLyxaoBx4N2W2n2aLEWUQ9GDuK" + } + ], + "keyAgreement": [ + { + "id": "#bf888dc5-0f54-4e71-9058-c43bebfc7d01", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "3Q8wpgxdCVdJfznYERZR1r9eVnCy7oxpjzGVucDLF2tG" + } + ] + }, + "createdAt": "2022-12-27T13:50:44.957Z" + }, + "id": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "type": "DidRecord", + "tags": { + "role": "received", + "method": "peer", + "recipientKeyFingerprints": ["z6Mkp1wyxzqoUrZ3TvnerfZBq48o1XLDSPH8ibFALkNABSgh"] + } + }, + "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN": { + "value": { + "_tags": { + "role": "received", + "recipientKeyFingerprints": ["z6MkrQzQqPtNjJHGLGjWC7H3mjMSNA36bCBTYJeoR44JoFvH"], + "method": "peer" + }, + "metadata": {}, + "id": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "role": "received", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "ws://ssi.issuer.com", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#b6d349fb-93fb-4298-b57a-3f2fecffccd8"], + "routingKeys": [] + } + ], + "authentication": [ + { + "id": "#b6d349fb-93fb-4298-b57a-3f2fecffccd8", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "CxjNF9dwPknoDmtoWYKCvdoSYamFBJw6rHjsan6Ht38u" + } + ], + "keyAgreement": [ + { + "id": "#c0917f9f-1102-4f13-800c-ff7b522999eb", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "Eso3A6AmL5qiWr9syqHx8NgdQ8EMkYcitmrW5G7bX9uU" + } + ] + }, + "createdAt": "2022-12-27T13:50:34.057Z" + }, + "id": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "type": "DidRecord", + "tags": { + "role": "received", + "method": "peer", + "recipientKeyFingerprints": ["z6MkrQzQqPtNjJHGLGjWC7H3mjMSNA36bCBTYJeoR44JoFvH"] + } + }, + "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3": { + "value": { + "_tags": { + "method": "peer", + "recipientKeyFingerprints": ["z6Mkt4ohp4uT74HdENeGVGAyVFTerCzzuDXP8kpU3yo6SqqM"], + "role": "received" + }, + "metadata": {}, + "id": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "role": "received", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "http://ssi.verifier.com", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#5ea98568-dfcd-4614-9495-ba95ec2665d3"], + "routingKeys": [] + } + ], + "authentication": [ + { + "id": "#5ea98568-dfcd-4614-9495-ba95ec2665d3", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "EcYfDpf1mWoA7soZohD8e9uf2dj9VLH2SjuYDhq5Xd3y" + } + ], + "keyAgreement": [ + { + "id": "#506e5ead-ddbc-44ef-848b-6593a692a916", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "EeQHhb6CqWGrQR9PfWpS1L8CedsbK2mZfPdaxaHN4s8b" + } + ] + }, + "createdAt": "2022-12-27T13:51:22.817Z" + }, + "id": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "type": "DidRecord", + "tags": { + "role": "received", + "method": "peer", + "recipientKeyFingerprints": ["z6Mkt4ohp4uT74HdENeGVGAyVFTerCzzuDXP8kpU3yo6SqqM"] + } + }, + "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S": { + "value": { + "_tags": { + "method": "peer", + "recipientKeyFingerprints": ["z6MktdgEKUiH5kt5t2dKAH14WzFYhzj3JFobUf2PFFQAg2ma"], + "role": "created" + }, + "metadata": {}, + "id": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "role": "created", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "didcomm:transport/queue", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#12b8b7d4-87b9-4638-a929-f98df2f1f566"], + "routingKeys": [] + } + ], + "authentication": [ + { + "id": "#12b8b7d4-87b9-4638-a929-f98df2f1f566", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "FBRBjETqkDPcmXncUi3DfthYtRTBtNZEne7TQyS9kozC" + } + ], + "keyAgreement": [ + { + "id": "#77b9cf84-2441-419f-b295-945d06e29edc", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "EgsQArrmCUru9MxR1RNNiomnMFz6E3ia2GfjVvoCjAWY" + } + ] + }, + "createdAt": "2022-12-27T13:50:43.937Z" + }, + "id": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "type": "DidRecord", + "tags": { + "role": "created", + "method": "peer", + "recipientKeyFingerprints": ["z6MktdgEKUiH5kt5t2dKAH14WzFYhzj3JFobUf2PFFQAg2ma"] + } + } +} diff --git a/packages/core/src/storage/migration/__tests__/__fixtures__/alice-8-dids-0.3.json b/packages/core/src/storage/migration/__tests__/__fixtures__/alice-8-dids-0.3.json new file mode 100644 index 0000000000..c0a6597833 --- /dev/null +++ b/packages/core/src/storage/migration/__tests__/__fixtures__/alice-8-dids-0.3.json @@ -0,0 +1,417 @@ +{ + "STORAGE_VERSION_RECORD_ID": { + "value": { + "metadata": {}, + "id": "STORAGE_VERSION_RECORD_ID", + "createdAt": "2022-09-08T19:35:53.872Z", + "storageVersion": "0.3" + }, + "id": "STORAGE_VERSION_RECORD_ID", + "type": "StorageVersionRecord", + "tags": {} + }, + "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd": { + "value": { + "_tags": { + "role": "created", + "method": "peer", + "recipientKeyFingerprints": ["z6Mkpg6nDPuYdLMuzNEjaa2Xprh3J2MC1WtNakWUEFqC4wvU"] + }, + "metadata": {}, + "id": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "role": "created", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "ws://ssi.mediator.com", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#57a05508-1d1c-474c-8c68-1afcf3188720"], + "routingKeys": [ + "did:key:z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop#z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop" + ] + } + ], + "authentication": [ + { + "id": "#57a05508-1d1c-474c-8c68-1afcf3188720", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "BDqjd9f7HnsSssQ2u14gym93UT5Lbde1tjbYPysB9j96" + } + ], + "keyAgreement": [ + { + "id": "#59d2ce6f-c9fc-49c6-a8f6-eab14820b028", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "avivQP6GvWj6cBbxbXSSZnZTA4tGsvQ5DB9FXm45tZt" + } + ] + }, + "createdAt": "2022-12-27T13:51:21.344Z" + }, + "id": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "type": "DidRecord", + "tags": { + "role": "created", + "method": "peer", + "recipientKeyFingerprints": ["z6Mkpg6nDPuYdLMuzNEjaa2Xprh3J2MC1WtNakWUEFqC4wvU"] + } + }, + "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W": { + "value": { + "_tags": { + "recipientKeyFingerprints": ["z6MkuLL6bQykGpposTrQhfYceQRR4JDzvdm133xpwb7Dv1oz"], + "role": "received", + "method": "peer" + }, + "metadata": {}, + "id": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "role": "received", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "http://ssi.verifier.com", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#6173438e-09a5-4b1e-895a-0563f5a169b7"], + "routingKeys": [] + } + ], + "authentication": [ + { + "id": "#6173438e-09a5-4b1e-895a-0563f5a169b7", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "Ft541AjJwHLLky1i26amoJsREix9WkWeM33u7K9Czo2c" + } + ], + "keyAgreement": [ + { + "id": "#a0448ee3-093a-4b16-bc59-8bf8559d60a5", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "JCfZJ72mtgGE9xuekJKV6yoAGzLgQCNkdHKkct8uaNKb" + } + ] + }, + "createdAt": "2022-12-27T13:51:51.414Z" + }, + "id": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "type": "DidRecord", + "tags": { + "role": "received", + "method": "peer", + "recipientKeyFingerprints": ["z6MkuLL6bQykGpposTrQhfYceQRR4JDzvdm133xpwb7Dv1oz"] + } + }, + "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q": { + "value": { + "_tags": { + "recipientKeyFingerprints": ["z6Mkv6Kd744JcBL5P9gi5Ddtehxn1gSMTgM9kbTdfQ9soB8G"], + "role": "created", + "method": "peer" + }, + "metadata": {}, + "id": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "role": "created", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "didcomm:transport/queue", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#d9d97b3e-5615-4e60-9366-a8ab1ec1a84d"], + "routingKeys": [] + } + ], + "authentication": [ + { + "id": "#d9d97b3e-5615-4e60-9366-a8ab1ec1a84d", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "Ge4aWoosGdqcGer1Peg3ocQnC7AW3o6o4aYhq8BrsxLt" + } + ], + "keyAgreement": [ + { + "id": "#c30ea91b-5f49-461f-b0bd-046f947ef668", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "HKBdBGRK8uxgCwge2QHPBzVuuayEQFhC2LM3g1fzMFGE" + } + ] + }, + "createdAt": "2022-12-27T13:50:32.815Z" + }, + "id": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "type": "DidRecord", + "tags": { + "role": "created", + "method": "peer", + "recipientKeyFingerprints": ["z6Mkv6Kd744JcBL5P9gi5Ddtehxn1gSMTgM9kbTdfQ9soB8G"] + } + }, + "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx": { + "value": { + "_tags": { + "method": "peer", + "recipientKeyFingerprints": ["z6MkvcuHjVcNRBM78E3xWy7MnVqQV9hAvTawBsPmg3bR1DaE"], + "role": "created" + }, + "metadata": {}, + "id": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "role": "created", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "ws://ssi.mediator.com", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#22219a28-b52a-4024-bc0f-62d3969131fd"], + "routingKeys": [ + "did:key:z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop#z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop" + ] + } + ], + "authentication": [ + { + "id": "#22219a28-b52a-4024-bc0f-62d3969131fd", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "HAeF9FMw5dre1jDFqQ9WwQHQfaRKWaLaVrUqqmdQ5znr" + } + ], + "keyAgreement": [ + { + "id": "#0f9535d1-9c9c-4491-be1e-1628f365b513", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "FztF8HCahTeL9gYHoHnDFo6HruwnKB19ZtbHFbLndAmE" + } + ] + }, + "createdAt": "2022-12-27T13:51:50.193Z" + }, + "id": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "type": "DidRecord", + "tags": { + "role": "created", + "method": "peer", + "recipientKeyFingerprints": ["z6MkvcuHjVcNRBM78E3xWy7MnVqQV9hAvTawBsPmg3bR1DaE"] + } + }, + "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce": { + "value": { + "_tags": { + "method": "peer", + "role": "received", + "recipientKeyFingerprints": ["z6Mkp1wyxzqoUrZ3TvnerfZBq48o1XLDSPH8ibFALkNABSgh"] + }, + "metadata": {}, + "id": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "role": "received", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "ws://ssi.mediator.com", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#075b7e8b-a7bd-41e1-9b01-044f1ccab1a6"], + "routingKeys": [] + } + ], + "authentication": [ + { + "id": "#075b7e8b-a7bd-41e1-9b01-044f1ccab1a6", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "AZgwNkbN9K4aMRwxB6bLyxaoBx4N2W2n2aLEWUQ9GDuK" + } + ], + "keyAgreement": [ + { + "id": "#bf888dc5-0f54-4e71-9058-c43bebfc7d01", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "3Q8wpgxdCVdJfznYERZR1r9eVnCy7oxpjzGVucDLF2tG" + } + ] + }, + "createdAt": "2022-12-27T13:50:44.957Z" + }, + "id": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "type": "DidRecord", + "tags": { + "role": "received", + "method": "peer", + "recipientKeyFingerprints": ["z6Mkp1wyxzqoUrZ3TvnerfZBq48o1XLDSPH8ibFALkNABSgh"] + } + }, + "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN": { + "value": { + "_tags": { + "role": "received", + "recipientKeyFingerprints": ["z6MkrQzQqPtNjJHGLGjWC7H3mjMSNA36bCBTYJeoR44JoFvH"], + "method": "peer" + }, + "metadata": {}, + "id": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "role": "received", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "ws://ssi.issuer.com", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#b6d349fb-93fb-4298-b57a-3f2fecffccd8"], + "routingKeys": [] + } + ], + "authentication": [ + { + "id": "#b6d349fb-93fb-4298-b57a-3f2fecffccd8", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "CxjNF9dwPknoDmtoWYKCvdoSYamFBJw6rHjsan6Ht38u" + } + ], + "keyAgreement": [ + { + "id": "#c0917f9f-1102-4f13-800c-ff7b522999eb", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "Eso3A6AmL5qiWr9syqHx8NgdQ8EMkYcitmrW5G7bX9uU" + } + ] + }, + "createdAt": "2022-12-27T13:50:34.057Z" + }, + "id": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "type": "DidRecord", + "tags": { + "role": "received", + "method": "peer", + "recipientKeyFingerprints": ["z6MkrQzQqPtNjJHGLGjWC7H3mjMSNA36bCBTYJeoR44JoFvH"] + } + }, + "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3": { + "value": { + "_tags": { + "method": "peer", + "recipientKeyFingerprints": ["z6Mkt4ohp4uT74HdENeGVGAyVFTerCzzuDXP8kpU3yo6SqqM"], + "role": "received" + }, + "metadata": {}, + "id": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "role": "received", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "http://ssi.verifier.com", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#5ea98568-dfcd-4614-9495-ba95ec2665d3"], + "routingKeys": [] + } + ], + "authentication": [ + { + "id": "#5ea98568-dfcd-4614-9495-ba95ec2665d3", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "EcYfDpf1mWoA7soZohD8e9uf2dj9VLH2SjuYDhq5Xd3y" + } + ], + "keyAgreement": [ + { + "id": "#506e5ead-ddbc-44ef-848b-6593a692a916", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "EeQHhb6CqWGrQR9PfWpS1L8CedsbK2mZfPdaxaHN4s8b" + } + ] + }, + "createdAt": "2022-12-27T13:51:22.817Z" + }, + "id": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "type": "DidRecord", + "tags": { + "role": "received", + "method": "peer", + "recipientKeyFingerprints": ["z6Mkt4ohp4uT74HdENeGVGAyVFTerCzzuDXP8kpU3yo6SqqM"] + } + }, + "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S": { + "value": { + "_tags": { + "method": "peer", + "recipientKeyFingerprints": ["z6MktdgEKUiH5kt5t2dKAH14WzFYhzj3JFobUf2PFFQAg2ma"], + "role": "created" + }, + "metadata": {}, + "id": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "role": "created", + "didDocument": { + "@context": ["https://w3id.org/did/v1"], + "id": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "service": [ + { + "id": "#inline-0", + "serviceEndpoint": "didcomm:transport/queue", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["#12b8b7d4-87b9-4638-a929-f98df2f1f566"], + "routingKeys": [] + } + ], + "authentication": [ + { + "id": "#12b8b7d4-87b9-4638-a929-f98df2f1f566", + "type": "Ed25519VerificationKey2018", + "controller": "#id", + "publicKeyBase58": "FBRBjETqkDPcmXncUi3DfthYtRTBtNZEne7TQyS9kozC" + } + ], + "keyAgreement": [ + { + "id": "#77b9cf84-2441-419f-b295-945d06e29edc", + "type": "X25519KeyAgreementKey2019", + "controller": "#id", + "publicKeyBase58": "EgsQArrmCUru9MxR1RNNiomnMFz6E3ia2GfjVvoCjAWY" + } + ] + }, + "createdAt": "2022-12-27T13:50:43.937Z" + }, + "id": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "type": "DidRecord", + "tags": { + "role": "created", + "method": "peer", + "recipientKeyFingerprints": ["z6MktdgEKUiH5kt5t2dKAH14WzFYhzj3JFobUf2PFFQAg2ma"] + } + } +} diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap index f46399930f..a56e8065c1 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UpdateAssistant | v0.2 - v0.3 should correctly update proof records and create didcomm records 1`] = ` +exports[`UpdateAssistant | v0.2 - v0.3.1 should correctly update proof records and create didcomm records 1`] = ` Object { "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e": Object { "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", @@ -121,7 +121,7 @@ Object { "createdAt": "2022-09-08T19:35:53.872Z", "id": "STORAGE_VERSION_RECORD_ID", "metadata": Object {}, - "storageVersion": "0.3", + "storageVersion": "0.3.1", }, }, "ea840186-3c77-45f4-a2e6-349811ad8994": Object { @@ -238,7 +238,523 @@ Object { } `; -exports[`UpdateAssistant | v0.2 - v0.3 should correctly update the proofs records and create didcomm records with auto update 1`] = ` +exports[`UpdateAssistant | v0.2 - v0.3.1 should correctly update the did records 1`] = ` +Object { + "1-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "1-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "recipientKeyFingerprints": Array [ + "z6Mkpg6nDPuYdLMuzNEjaa2Xprh3J2MC1WtNakWUEFqC4wvU", + ], + "role": "created", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6Mkpg6nDPuYdLMuzNEjaa2Xprh3J2MC1WtNakWUEFqC4wvU", + ], + "role": "created", + }, + "createdAt": "2022-12-27T13:51:21.344Z", + "did": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#57a05508-1d1c-474c-8c68-1afcf3188720", + "publicKeyBase58": "BDqjd9f7HnsSssQ2u14gym93UT5Lbde1tjbYPysB9j96", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#59d2ce6f-c9fc-49c6-a8f6-eab14820b028", + "publicKeyBase58": "avivQP6GvWj6cBbxbXSSZnZTA4tGsvQ5DB9FXm45tZt", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#57a05508-1d1c-474c-8c68-1afcf3188720", + ], + "routingKeys": Array [ + "did:key:z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop#z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop", + ], + "serviceEndpoint": "ws://ssi.mediator.com", + "type": "did-communication", + }, + ], + }, + "id": "1-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "created", + }, + }, + "2-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "2-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "recipientKeyFingerprints": Array [ + "z6MkuLL6bQykGpposTrQhfYceQRR4JDzvdm133xpwb7Dv1oz", + ], + "role": "received", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6MkuLL6bQykGpposTrQhfYceQRR4JDzvdm133xpwb7Dv1oz", + ], + "role": "received", + }, + "createdAt": "2022-12-27T13:51:51.414Z", + "did": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#6173438e-09a5-4b1e-895a-0563f5a169b7", + "publicKeyBase58": "Ft541AjJwHLLky1i26amoJsREix9WkWeM33u7K9Czo2c", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#a0448ee3-093a-4b16-bc59-8bf8559d60a5", + "publicKeyBase58": "JCfZJ72mtgGE9xuekJKV6yoAGzLgQCNkdHKkct8uaNKb", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#6173438e-09a5-4b1e-895a-0563f5a169b7", + ], + "routingKeys": Array [], + "serviceEndpoint": "http://ssi.verifier.com", + "type": "did-communication", + }, + ], + }, + "id": "2-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "received", + }, + }, + "3-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "3-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "recipientKeyFingerprints": Array [ + "z6Mkv6Kd744JcBL5P9gi5Ddtehxn1gSMTgM9kbTdfQ9soB8G", + ], + "role": "created", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6Mkv6Kd744JcBL5P9gi5Ddtehxn1gSMTgM9kbTdfQ9soB8G", + ], + "role": "created", + }, + "createdAt": "2022-12-27T13:50:32.815Z", + "did": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#d9d97b3e-5615-4e60-9366-a8ab1ec1a84d", + "publicKeyBase58": "Ge4aWoosGdqcGer1Peg3ocQnC7AW3o6o4aYhq8BrsxLt", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#c30ea91b-5f49-461f-b0bd-046f947ef668", + "publicKeyBase58": "HKBdBGRK8uxgCwge2QHPBzVuuayEQFhC2LM3g1fzMFGE", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#d9d97b3e-5615-4e60-9366-a8ab1ec1a84d", + ], + "routingKeys": Array [], + "serviceEndpoint": "didcomm:transport/queue", + "type": "did-communication", + }, + ], + }, + "id": "3-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "created", + }, + }, + "4-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "4-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "recipientKeyFingerprints": Array [ + "z6MkvcuHjVcNRBM78E3xWy7MnVqQV9hAvTawBsPmg3bR1DaE", + ], + "role": "created", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6MkvcuHjVcNRBM78E3xWy7MnVqQV9hAvTawBsPmg3bR1DaE", + ], + "role": "created", + }, + "createdAt": "2022-12-27T13:51:50.193Z", + "did": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#22219a28-b52a-4024-bc0f-62d3969131fd", + "publicKeyBase58": "HAeF9FMw5dre1jDFqQ9WwQHQfaRKWaLaVrUqqmdQ5znr", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#0f9535d1-9c9c-4491-be1e-1628f365b513", + "publicKeyBase58": "FztF8HCahTeL9gYHoHnDFo6HruwnKB19ZtbHFbLndAmE", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#22219a28-b52a-4024-bc0f-62d3969131fd", + ], + "routingKeys": Array [ + "did:key:z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop#z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop", + ], + "serviceEndpoint": "ws://ssi.mediator.com", + "type": "did-communication", + }, + ], + }, + "id": "4-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "created", + }, + }, + "5-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "5-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "recipientKeyFingerprints": Array [ + "z6Mkp1wyxzqoUrZ3TvnerfZBq48o1XLDSPH8ibFALkNABSgh", + ], + "role": "received", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6Mkp1wyxzqoUrZ3TvnerfZBq48o1XLDSPH8ibFALkNABSgh", + ], + "role": "received", + }, + "createdAt": "2022-12-27T13:50:44.957Z", + "did": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#075b7e8b-a7bd-41e1-9b01-044f1ccab1a6", + "publicKeyBase58": "AZgwNkbN9K4aMRwxB6bLyxaoBx4N2W2n2aLEWUQ9GDuK", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#bf888dc5-0f54-4e71-9058-c43bebfc7d01", + "publicKeyBase58": "3Q8wpgxdCVdJfznYERZR1r9eVnCy7oxpjzGVucDLF2tG", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#075b7e8b-a7bd-41e1-9b01-044f1ccab1a6", + ], + "routingKeys": Array [], + "serviceEndpoint": "ws://ssi.mediator.com", + "type": "did-communication", + }, + ], + }, + "id": "5-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "received", + }, + }, + "6-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "6-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "recipientKeyFingerprints": Array [ + "z6MkrQzQqPtNjJHGLGjWC7H3mjMSNA36bCBTYJeoR44JoFvH", + ], + "role": "received", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6MkrQzQqPtNjJHGLGjWC7H3mjMSNA36bCBTYJeoR44JoFvH", + ], + "role": "received", + }, + "createdAt": "2022-12-27T13:50:34.057Z", + "did": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#b6d349fb-93fb-4298-b57a-3f2fecffccd8", + "publicKeyBase58": "CxjNF9dwPknoDmtoWYKCvdoSYamFBJw6rHjsan6Ht38u", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#c0917f9f-1102-4f13-800c-ff7b522999eb", + "publicKeyBase58": "Eso3A6AmL5qiWr9syqHx8NgdQ8EMkYcitmrW5G7bX9uU", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#b6d349fb-93fb-4298-b57a-3f2fecffccd8", + ], + "routingKeys": Array [], + "serviceEndpoint": "ws://ssi.issuer.com", + "type": "did-communication", + }, + ], + }, + "id": "6-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "received", + }, + }, + "7-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "7-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "recipientKeyFingerprints": Array [ + "z6Mkt4ohp4uT74HdENeGVGAyVFTerCzzuDXP8kpU3yo6SqqM", + ], + "role": "received", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6Mkt4ohp4uT74HdENeGVGAyVFTerCzzuDXP8kpU3yo6SqqM", + ], + "role": "received", + }, + "createdAt": "2022-12-27T13:51:22.817Z", + "did": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#5ea98568-dfcd-4614-9495-ba95ec2665d3", + "publicKeyBase58": "EcYfDpf1mWoA7soZohD8e9uf2dj9VLH2SjuYDhq5Xd3y", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#506e5ead-ddbc-44ef-848b-6593a692a916", + "publicKeyBase58": "EeQHhb6CqWGrQR9PfWpS1L8CedsbK2mZfPdaxaHN4s8b", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#5ea98568-dfcd-4614-9495-ba95ec2665d3", + ], + "routingKeys": Array [], + "serviceEndpoint": "http://ssi.verifier.com", + "type": "did-communication", + }, + ], + }, + "id": "7-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "received", + }, + }, + "8-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "8-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "recipientKeyFingerprints": Array [ + "z6MktdgEKUiH5kt5t2dKAH14WzFYhzj3JFobUf2PFFQAg2ma", + ], + "role": "created", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6MktdgEKUiH5kt5t2dKAH14WzFYhzj3JFobUf2PFFQAg2ma", + ], + "role": "created", + }, + "createdAt": "2022-12-27T13:50:43.937Z", + "did": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#12b8b7d4-87b9-4638-a929-f98df2f1f566", + "publicKeyBase58": "FBRBjETqkDPcmXncUi3DfthYtRTBtNZEne7TQyS9kozC", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#77b9cf84-2441-419f-b295-945d06e29edc", + "publicKeyBase58": "EgsQArrmCUru9MxR1RNNiomnMFz6E3ia2GfjVvoCjAWY", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#12b8b7d4-87b9-4638-a929-f98df2f1f566", + ], + "routingKeys": Array [], + "serviceEndpoint": "didcomm:transport/queue", + "type": "did-communication", + }, + ], + }, + "id": "8-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "created", + }, + }, + "STORAGE_VERSION_RECORD_ID": Object { + "id": "STORAGE_VERSION_RECORD_ID", + "tags": Object {}, + "type": "StorageVersionRecord", + "value": Object { + "createdAt": "2022-09-08T19:35:53.872Z", + "id": "STORAGE_VERSION_RECORD_ID", + "metadata": Object {}, + "storageVersion": "0.3.1", + }, + }, +} +`; + +exports[`UpdateAssistant | v0.2 - v0.3.1 should correctly update the proofs records and create didcomm records with auto update 1`] = ` Object { "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e": Object { "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", @@ -359,7 +875,7 @@ Object { "createdAt": "2022-09-08T19:35:53.872Z", "id": "STORAGE_VERSION_RECORD_ID", "metadata": Object {}, - "storageVersion": "0.3", + "storageVersion": "0.3.1", }, }, "ea840186-3c77-45f4-a2e6-349811ad8994": Object { diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.3.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.3.test.ts.snap new file mode 100644 index 0000000000..8169373e57 --- /dev/null +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.3.test.ts.snap @@ -0,0 +1,517 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UpdateAssistant | v0.3 - v0.3.1 should correctly update the did records 1`] = ` +Object { + "1-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "1-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "recipientKeyFingerprints": Array [ + "z6Mkpg6nDPuYdLMuzNEjaa2Xprh3J2MC1WtNakWUEFqC4wvU", + ], + "role": "created", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6Mkpg6nDPuYdLMuzNEjaa2Xprh3J2MC1WtNakWUEFqC4wvU", + ], + "role": "created", + }, + "createdAt": "2022-12-27T13:51:21.344Z", + "did": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#57a05508-1d1c-474c-8c68-1afcf3188720", + "publicKeyBase58": "BDqjd9f7HnsSssQ2u14gym93UT5Lbde1tjbYPysB9j96", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#59d2ce6f-c9fc-49c6-a8f6-eab14820b028", + "publicKeyBase58": "avivQP6GvWj6cBbxbXSSZnZTA4tGsvQ5DB9FXm45tZt", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#57a05508-1d1c-474c-8c68-1afcf3188720", + ], + "routingKeys": Array [ + "did:key:z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop#z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop", + ], + "serviceEndpoint": "ws://ssi.mediator.com", + "type": "did-communication", + }, + ], + }, + "id": "1-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "created", + }, + }, + "2-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "2-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "recipientKeyFingerprints": Array [ + "z6MkuLL6bQykGpposTrQhfYceQRR4JDzvdm133xpwb7Dv1oz", + ], + "role": "received", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6MkuLL6bQykGpposTrQhfYceQRR4JDzvdm133xpwb7Dv1oz", + ], + "role": "received", + }, + "createdAt": "2022-12-27T13:51:51.414Z", + "did": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#6173438e-09a5-4b1e-895a-0563f5a169b7", + "publicKeyBase58": "Ft541AjJwHLLky1i26amoJsREix9WkWeM33u7K9Czo2c", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#a0448ee3-093a-4b16-bc59-8bf8559d60a5", + "publicKeyBase58": "JCfZJ72mtgGE9xuekJKV6yoAGzLgQCNkdHKkct8uaNKb", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#6173438e-09a5-4b1e-895a-0563f5a169b7", + ], + "routingKeys": Array [], + "serviceEndpoint": "http://ssi.verifier.com", + "type": "did-communication", + }, + ], + }, + "id": "2-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "received", + }, + }, + "3-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "3-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "recipientKeyFingerprints": Array [ + "z6Mkv6Kd744JcBL5P9gi5Ddtehxn1gSMTgM9kbTdfQ9soB8G", + ], + "role": "created", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6Mkv6Kd744JcBL5P9gi5Ddtehxn1gSMTgM9kbTdfQ9soB8G", + ], + "role": "created", + }, + "createdAt": "2022-12-27T13:50:32.815Z", + "did": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#d9d97b3e-5615-4e60-9366-a8ab1ec1a84d", + "publicKeyBase58": "Ge4aWoosGdqcGer1Peg3ocQnC7AW3o6o4aYhq8BrsxLt", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#c30ea91b-5f49-461f-b0bd-046f947ef668", + "publicKeyBase58": "HKBdBGRK8uxgCwge2QHPBzVuuayEQFhC2LM3g1fzMFGE", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#d9d97b3e-5615-4e60-9366-a8ab1ec1a84d", + ], + "routingKeys": Array [], + "serviceEndpoint": "didcomm:transport/queue", + "type": "did-communication", + }, + ], + }, + "id": "3-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "created", + }, + }, + "4-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "4-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "recipientKeyFingerprints": Array [ + "z6MkvcuHjVcNRBM78E3xWy7MnVqQV9hAvTawBsPmg3bR1DaE", + ], + "role": "created", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6MkvcuHjVcNRBM78E3xWy7MnVqQV9hAvTawBsPmg3bR1DaE", + ], + "role": "created", + }, + "createdAt": "2022-12-27T13:51:50.193Z", + "did": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#22219a28-b52a-4024-bc0f-62d3969131fd", + "publicKeyBase58": "HAeF9FMw5dre1jDFqQ9WwQHQfaRKWaLaVrUqqmdQ5znr", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#0f9535d1-9c9c-4491-be1e-1628f365b513", + "publicKeyBase58": "FztF8HCahTeL9gYHoHnDFo6HruwnKB19ZtbHFbLndAmE", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#22219a28-b52a-4024-bc0f-62d3969131fd", + ], + "routingKeys": Array [ + "did:key:z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop#z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop", + ], + "serviceEndpoint": "ws://ssi.mediator.com", + "type": "did-communication", + }, + ], + }, + "id": "4-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "created", + }, + }, + "5-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "5-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "recipientKeyFingerprints": Array [ + "z6Mkp1wyxzqoUrZ3TvnerfZBq48o1XLDSPH8ibFALkNABSgh", + ], + "role": "received", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6Mkp1wyxzqoUrZ3TvnerfZBq48o1XLDSPH8ibFALkNABSgh", + ], + "role": "received", + }, + "createdAt": "2022-12-27T13:50:44.957Z", + "did": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#075b7e8b-a7bd-41e1-9b01-044f1ccab1a6", + "publicKeyBase58": "AZgwNkbN9K4aMRwxB6bLyxaoBx4N2W2n2aLEWUQ9GDuK", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#bf888dc5-0f54-4e71-9058-c43bebfc7d01", + "publicKeyBase58": "3Q8wpgxdCVdJfznYERZR1r9eVnCy7oxpjzGVucDLF2tG", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#075b7e8b-a7bd-41e1-9b01-044f1ccab1a6", + ], + "routingKeys": Array [], + "serviceEndpoint": "ws://ssi.mediator.com", + "type": "did-communication", + }, + ], + }, + "id": "5-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "received", + }, + }, + "6-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "6-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "recipientKeyFingerprints": Array [ + "z6MkrQzQqPtNjJHGLGjWC7H3mjMSNA36bCBTYJeoR44JoFvH", + ], + "role": "received", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6MkrQzQqPtNjJHGLGjWC7H3mjMSNA36bCBTYJeoR44JoFvH", + ], + "role": "received", + }, + "createdAt": "2022-12-27T13:50:34.057Z", + "did": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#b6d349fb-93fb-4298-b57a-3f2fecffccd8", + "publicKeyBase58": "CxjNF9dwPknoDmtoWYKCvdoSYamFBJw6rHjsan6Ht38u", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#c0917f9f-1102-4f13-800c-ff7b522999eb", + "publicKeyBase58": "Eso3A6AmL5qiWr9syqHx8NgdQ8EMkYcitmrW5G7bX9uU", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#b6d349fb-93fb-4298-b57a-3f2fecffccd8", + ], + "routingKeys": Array [], + "serviceEndpoint": "ws://ssi.issuer.com", + "type": "did-communication", + }, + ], + }, + "id": "6-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "received", + }, + }, + "7-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "7-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "recipientKeyFingerprints": Array [ + "z6Mkt4ohp4uT74HdENeGVGAyVFTerCzzuDXP8kpU3yo6SqqM", + ], + "role": "received", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6Mkt4ohp4uT74HdENeGVGAyVFTerCzzuDXP8kpU3yo6SqqM", + ], + "role": "received", + }, + "createdAt": "2022-12-27T13:51:22.817Z", + "did": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#5ea98568-dfcd-4614-9495-ba95ec2665d3", + "publicKeyBase58": "EcYfDpf1mWoA7soZohD8e9uf2dj9VLH2SjuYDhq5Xd3y", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#506e5ead-ddbc-44ef-848b-6593a692a916", + "publicKeyBase58": "EeQHhb6CqWGrQR9PfWpS1L8CedsbK2mZfPdaxaHN4s8b", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#5ea98568-dfcd-4614-9495-ba95ec2665d3", + ], + "routingKeys": Array [], + "serviceEndpoint": "http://ssi.verifier.com", + "type": "did-communication", + }, + ], + }, + "id": "7-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "received", + }, + }, + "8-4e4f-41d9-94c4-f49351b811f1": Object { + "id": "8-4e4f-41d9-94c4-f49351b811f1", + "tags": Object { + "did": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "legacyUnqualifiedDid": undefined, + "method": "peer", + "methodSpecificIdentifier": "1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "recipientKeyFingerprints": Array [ + "z6MktdgEKUiH5kt5t2dKAH14WzFYhzj3JFobUf2PFFQAg2ma", + ], + "role": "created", + }, + "type": "DidRecord", + "value": Object { + "_tags": Object { + "method": "peer", + "recipientKeyFingerprints": Array [ + "z6MktdgEKUiH5kt5t2dKAH14WzFYhzj3JFobUf2PFFQAg2ma", + ], + "role": "created", + }, + "createdAt": "2022-12-27T13:50:43.937Z", + "did": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "didDocument": Object { + "@context": Array [ + "https://w3id.org/did/v1", + ], + "authentication": Array [ + Object { + "controller": "#id", + "id": "#12b8b7d4-87b9-4638-a929-f98df2f1f566", + "publicKeyBase58": "FBRBjETqkDPcmXncUi3DfthYtRTBtNZEne7TQyS9kozC", + "type": "Ed25519VerificationKey2018", + }, + ], + "id": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", + "keyAgreement": Array [ + Object { + "controller": "#id", + "id": "#77b9cf84-2441-419f-b295-945d06e29edc", + "publicKeyBase58": "EgsQArrmCUru9MxR1RNNiomnMFz6E3ia2GfjVvoCjAWY", + "type": "X25519KeyAgreementKey2019", + }, + ], + "service": Array [ + Object { + "id": "#inline-0", + "priority": 0, + "recipientKeys": Array [ + "#12b8b7d4-87b9-4638-a929-f98df2f1f566", + ], + "routingKeys": Array [], + "serviceEndpoint": "didcomm:transport/queue", + "type": "did-communication", + }, + ], + }, + "id": "8-4e4f-41d9-94c4-f49351b811f1", + "metadata": Object {}, + "role": "created", + }, + }, + "STORAGE_VERSION_RECORD_ID": Object { + "id": "STORAGE_VERSION_RECORD_ID", + "tags": Object {}, + "type": "StorageVersionRecord", + "value": Object { + "createdAt": "2022-09-08T19:35:53.872Z", + "id": "STORAGE_VERSION_RECORD_ID", + "metadata": Object {}, + "storageVersion": "0.3.1", + }, + }, +} +`; diff --git a/packages/core/src/storage/migration/index.ts b/packages/core/src/storage/migration/index.ts index e59ac63479..c908e9655d 100644 --- a/packages/core/src/storage/migration/index.ts +++ b/packages/core/src/storage/migration/index.ts @@ -1,3 +1,4 @@ export * from './repository/StorageVersionRecord' export * from './repository/StorageVersionRepository' export * from './StorageUpdateService' +export * from './UpdateAssistant' diff --git a/packages/core/src/storage/migration/updates.ts b/packages/core/src/storage/migration/updates.ts index d079287e61..08c890fdd0 100644 --- a/packages/core/src/storage/migration/updates.ts +++ b/packages/core/src/storage/migration/updates.ts @@ -4,6 +4,7 @@ import type { V0_1ToV0_2UpdateConfig } from './updates/0.1-0.2' import { updateV0_1ToV0_2 } from './updates/0.1-0.2' import { updateV0_2ToV0_3 } from './updates/0.2-0.3' +import { updateV0_3ToV0_3_1 } from './updates/0.3-0.3.1' export const INITIAL_STORAGE_VERSION = '0.1' @@ -34,6 +35,11 @@ export const supportedUpdates = [ toVersion: '0.3', doUpdate: updateV0_2ToV0_3, }, + { + fromVersion: '0.3', + toVersion: '0.3.1', + doUpdate: updateV0_3ToV0_3_1, + }, ] as const // Current version is last toVersion from the supported updates diff --git a/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/did.test.ts b/packages/core/src/storage/migration/updates/0.3-0.3.1/__tests__/did.test.ts similarity index 90% rename from packages/core/src/storage/migration/updates/0.2-0.3/__tests__/did.test.ts rename to packages/core/src/storage/migration/updates/0.3-0.3.1/__tests__/did.test.ts index 2f4bd97710..7ce585b93a 100644 --- a/packages/core/src/storage/migration/updates/0.2-0.3/__tests__/did.test.ts +++ b/packages/core/src/storage/migration/updates/0.3-0.3.1/__tests__/did.test.ts @@ -7,7 +7,7 @@ import { JsonTransformer } from '../../../../../utils' import { Metadata } from '../../../../Metadata' import * as testModule from '../did' -const agentConfig = getAgentConfig('Migration DidRecord 0.2-0.3') +const agentConfig = getAgentConfig('Migration DidRecord 0.3-0.3.1') const agentContext = getAgentContext() jest.mock('../../../../../modules/dids/repository/DidRepository') @@ -29,20 +29,20 @@ jest.mock('../../../../../agent/Agent', () => { // Mock typed object const AgentMock = Agent as jest.Mock -describe('0.2-0.3 | Did', () => { +describe('0.3-0.3.1 | Did', () => { let agent: Agent beforeEach(() => { agent = new AgentMock() }) - describe('migrateDidRecordToV0_3()', () => { + describe('migrateDidRecordToV0_3_1()', () => { it('should fetch all records and apply the needed updates ', async () => { const records: DidRecord[] = [getDid({ id: 'did:peer:123' })] mockFunction(didRepository.getAll).mockResolvedValue(records) - await testModule.migrateDidRecordToV0_3(agent) + await testModule.migrateDidRecordToV0_3_1(agent) expect(didRepository.getAll).toHaveBeenCalledTimes(1) expect(didRepository.save).toHaveBeenCalledTimes(1) diff --git a/packages/core/src/storage/migration/updates/0.2-0.3/did.ts b/packages/core/src/storage/migration/updates/0.3-0.3.1/did.ts similarity index 88% rename from packages/core/src/storage/migration/updates/0.2-0.3/did.ts rename to packages/core/src/storage/migration/updates/0.3-0.3.1/did.ts index f051b0e339..4b8d0571f6 100644 --- a/packages/core/src/storage/migration/updates/0.2-0.3/did.ts +++ b/packages/core/src/storage/migration/updates/0.3-0.3.1/did.ts @@ -11,8 +11,8 @@ import { uuid } from '../../../../utils/uuid' * The following transformations are applied: * - {@link extractDidAsSeparateProperty} */ -export async function migrateDidRecordToV0_3(agent: Agent) { - agent.config.logger.info('Migrating did records to storage version 0.3') +export async function migrateDidRecordToV0_3_1(agent: Agent) { + agent.config.logger.info('Migrating did records to storage version 0.3.1') const didRepository = agent.dependencyManager.resolve(DidRepository) agent.config.logger.debug(`Fetching all did records from storage`) @@ -20,7 +20,7 @@ export async function migrateDidRecordToV0_3(agent: Age agent.config.logger.debug(`Found a total of ${allDids.length} did records to update.`) for (const didRecord of allDids) { - agent.config.logger.debug(`Migrating did record with id ${didRecord.id} to storage version 0.3`) + agent.config.logger.debug(`Migrating did record with id ${didRecord.id} to storage version 0.3.1`) const newId = uuid() @@ -36,7 +36,7 @@ export async function migrateDidRecordToV0_3(agent: Age await didRepository.deleteById(agent.context, didRecord.did) agent.config.logger.debug( - `Successfully migrated did record with old id ${didRecord.did} to new id ${didRecord.id} to storage version 0.3` + `Successfully migrated did record with old id ${didRecord.did} to new id ${didRecord.id} to storage version 0.3.1` ) } } diff --git a/packages/core/src/storage/migration/updates/0.3-0.3.1/index.ts b/packages/core/src/storage/migration/updates/0.3-0.3.1/index.ts new file mode 100644 index 0000000000..6d9e6b40ab --- /dev/null +++ b/packages/core/src/storage/migration/updates/0.3-0.3.1/index.ts @@ -0,0 +1,7 @@ +import type { BaseAgent } from '../../../../agent/BaseAgent' + +import { migrateDidRecordToV0_3_1 } from './did' + +export async function updateV0_3ToV0_3_1(agent: Agent): Promise { + await migrateDidRecordToV0_3_1(agent) +} diff --git a/packages/core/src/utils/__tests__/version.test.ts b/packages/core/src/utils/__tests__/version.test.ts index d9fdcdedb8..77c1bbe808 100644 --- a/packages/core/src/utils/__tests__/version.test.ts +++ b/packages/core/src/utils/__tests__/version.test.ts @@ -3,63 +3,80 @@ import { isFirstVersionEqualToSecond, isFirstVersionHigherThanSecond, parseVersi describe('version', () => { describe('parseVersionString()', () => { it('parses a version string to a tuple', () => { - expect(parseVersionString('1.0')).toStrictEqual([1, 0]) - expect(parseVersionString('2.12')).toStrictEqual([2, 12]) - expect(parseVersionString('0.0')).toStrictEqual([0, 0]) + expect(parseVersionString('1.0')).toStrictEqual([1, 0, 0]) + expect(parseVersionString('2.12')).toStrictEqual([2, 12, 0]) + expect(parseVersionString('2.3.1')).toStrictEqual([2, 3, 1]) + expect(parseVersionString('0.2.1')).toStrictEqual([0, 2, 1]) + expect(parseVersionString('0.0')).toStrictEqual([0, 0, 0]) }) }) describe('isFirstVersionHigherThanSecond()', () => { it('returns true if the major version digit of the first version is higher than the second', () => { - expect(isFirstVersionHigherThanSecond([2, 0], [1, 0])).toBe(true) - expect(isFirstVersionHigherThanSecond([2, 1], [1, 10])).toBe(true) + expect(isFirstVersionHigherThanSecond([2, 0, 0], [1, 0, 0])).toBe(true) + expect(isFirstVersionHigherThanSecond([2, 1, 0], [1, 1, 1])).toBe(true) }) it('returns false if the major version digit of the first version is lower than the second', () => { - expect(isFirstVersionHigherThanSecond([1, 0], [2, 0])).toBe(false) - expect(isFirstVersionHigherThanSecond([1, 10], [2, 1])).toBe(false) + expect(isFirstVersionHigherThanSecond([1, 0, 0], [2, 0, 0])).toBe(false) + expect(isFirstVersionHigherThanSecond([1, 10, 2], [2, 1, 0])).toBe(false) }) it('returns true if the major version digit of both versions are equal, but the minor version of the first version is higher', () => { - expect(isFirstVersionHigherThanSecond([1, 10], [1, 0])).toBe(true) - expect(isFirstVersionHigherThanSecond([2, 11], [2, 10])).toBe(true) + expect(isFirstVersionHigherThanSecond([1, 10, 0], [1, 0, 0])).toBe(true) + expect(isFirstVersionHigherThanSecond([2, 11, 0], [2, 10, 0])).toBe(true) }) it('returns false if the major version digit of both versions are equal, but the minor version of the second version is higher', () => { - expect(isFirstVersionHigherThanSecond([1, 0], [1, 10])).toBe(false) - expect(isFirstVersionHigherThanSecond([2, 10], [2, 11])).toBe(false) + expect(isFirstVersionHigherThanSecond([1, 0, 0], [1, 10, 0])).toBe(false) + expect(isFirstVersionHigherThanSecond([2, 10, 0], [2, 11, 0])).toBe(false) }) - it('returns false if the major and minor version digit of both versions are equal', () => { - expect(isFirstVersionHigherThanSecond([1, 0], [1, 0])).toBe(false) - expect(isFirstVersionHigherThanSecond([2, 10], [2, 10])).toBe(false) + it('returns false if the major, minor and patch version digit of both versions are equal', () => { + expect(isFirstVersionHigherThanSecond([1, 0, 0], [1, 0, 0])).toBe(false) + expect(isFirstVersionHigherThanSecond([2, 10, 0], [2, 10, 0])).toBe(false) + }) + + it('returns true if the major and minor version digit of both versions are equal but patch version is higher', () => { + expect(isFirstVersionHigherThanSecond([1, 0, 1], [1, 0, 0])).toBe(true) + expect(isFirstVersionHigherThanSecond([2, 10, 3], [2, 10, 2])).toBe(true) + }) + + it('returns false if the major and minor version digit of both versions are equal but patch version is lower', () => { + expect(isFirstVersionHigherThanSecond([1, 0, 0], [1, 0, 1])).toBe(false) + expect(isFirstVersionHigherThanSecond([2, 10, 2], [2, 10, 3])).toBe(false) }) }) describe('isFirstVersionEqualToSecond()', () => { it('returns false if the major version digit of the first version is lower than the second', () => { - expect(isFirstVersionEqualToSecond([2, 0], [1, 0])).toBe(false) - expect(isFirstVersionEqualToSecond([2, 1], [1, 10])).toBe(false) + expect(isFirstVersionEqualToSecond([2, 0, 0], [1, 0, 0])).toBe(false) + expect(isFirstVersionEqualToSecond([2, 1, 0], [1, 10, 0])).toBe(false) }) it('returns false if the major version digit of the first version is higher than the second', () => { - expect(isFirstVersionEqualToSecond([1, 0], [2, 0])).toBe(false) - expect(isFirstVersionEqualToSecond([1, 10], [2, 1])).toBe(false) + expect(isFirstVersionEqualToSecond([1, 0, 0], [2, 0, 0])).toBe(false) + expect(isFirstVersionEqualToSecond([1, 10, 0], [2, 1, 0])).toBe(false) }) it('returns false if the major version digit of both versions are equal, but the minor version of the first version is lower', () => { - expect(isFirstVersionEqualToSecond([1, 10], [1, 0])).toBe(false) - expect(isFirstVersionEqualToSecond([2, 11], [2, 10])).toBe(false) + expect(isFirstVersionEqualToSecond([1, 10, 0], [1, 0, 0])).toBe(false) + expect(isFirstVersionEqualToSecond([2, 11, 0], [2, 10, 0])).toBe(false) }) it('returns false if the major version digit of both versions are equal, but the minor version of the second version is lower', () => { - expect(isFirstVersionEqualToSecond([1, 0], [1, 10])).toBe(false) - expect(isFirstVersionEqualToSecond([2, 10], [2, 11])).toBe(false) + expect(isFirstVersionEqualToSecond([1, 0, 0], [1, 10, 0])).toBe(false) + expect(isFirstVersionEqualToSecond([2, 10, 0], [2, 11, 0])).toBe(false) + }) + + it('returns true if the major, minor and patch version digit of both versions are equal', () => { + expect(isFirstVersionEqualToSecond([1, 0, 0], [1, 0, 0])).toBe(true) + expect(isFirstVersionEqualToSecond([2, 10, 0], [2, 10, 0])).toBe(true) }) - it('returns true if the major and minor version digit of both versions are equal', () => { - expect(isFirstVersionEqualToSecond([1, 0], [1, 0])).toBe(true) - expect(isFirstVersionEqualToSecond([2, 10], [2, 10])).toBe(true) + it('returns false if the patch version digit of both versions are different', () => { + expect(isFirstVersionEqualToSecond([1, 0, 1], [1, 0, 0])).toBe(false) + expect(isFirstVersionEqualToSecond([2, 10, 0], [2, 10, 4])).toBe(false) }) }) }) diff --git a/packages/core/src/utils/version.ts b/packages/core/src/utils/version.ts index 33ae345f99..82a9597909 100644 --- a/packages/core/src/utils/version.ts +++ b/packages/core/src/utils/version.ts @@ -1,18 +1,23 @@ export function parseVersionString(version: VersionString): Version { - const [major, minor] = version.split('.') + const [major, minor, patch] = version.split('.') - return [Number(major), Number(minor)] + return [Number(major), Number(minor), Number(patch ?? '0')] } export function isFirstVersionHigherThanSecond(first: Version, second: Version) { - return first[0] > second[0] || (first[0] == second[0] && first[1] > second[1]) + return ( + first[0] > second[0] || + (first[0] == second[0] && first[1] > second[1]) || + (first[0] == second[0] && first[1] == second[1] && first[2] > second[2]) + ) } export function isFirstVersionEqualToSecond(first: Version, second: Version) { - return first[0] === second[0] && first[1] === second[1] + return first[0] === second[0] && first[1] === second[1] && first[2] === second[2] } -export type VersionString = `${number}.${number}` +export type VersionString = `${number}.${number}` | `${number}.${number}.${number}` export type MajorVersion = number export type MinorVersion = number -export type Version = [MajorVersion, MinorVersion] +export type PatchVersion = number +export type Version = [MajorVersion, MinorVersion, PatchVersion] From 1c6b88095f0c552c090af693787e26b4d2cd28c3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 31 Dec 2022 09:02:46 +0800 Subject: [PATCH 108/125] chore(release): v0.3.1 (#1186) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 6 ++++++ lerna.json | 2 +- packages/action-menu/CHANGELOG.md | 4 ++++ packages/action-menu/package.json | 4 ++-- packages/core/CHANGELOG.md | 6 ++++++ packages/core/package.json | 2 +- packages/node/CHANGELOG.md | 4 ++++ packages/node/package.json | 4 ++-- packages/question-answer/CHANGELOG.md | 4 ++++ packages/question-answer/package.json | 4 ++-- packages/react-native/CHANGELOG.md | 4 ++++ packages/react-native/package.json | 4 ++-- packages/tenants/CHANGELOG.md | 4 ++++ packages/tenants/package.json | 6 +++--- 14 files changed, 45 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a37e2a53e..d19e023ee6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) + +### Bug Fixes + +- missing migration script and exports ([#1184](https://github.com/hyperledger/aries-framework-javascript/issues/1184)) ([460510d](https://github.com/hyperledger/aries-framework-javascript/commit/460510db43a7c63fd8dc1c3614be03fd8772f63c)) + # [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) ### Bug Fixes diff --git a/lerna.json b/lerna.json index bda0cb3f35..880e72d3df 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.3.0", + "version": "0.3.1", "useWorkspaces": true, "npmClient": "yarn", "command": { diff --git a/packages/action-menu/CHANGELOG.md b/packages/action-menu/CHANGELOG.md index 533e961b33..03254b92a1 100644 --- a/packages/action-menu/CHANGELOG.md +++ b/packages/action-menu/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) + +**Note:** Version bump only for package @aries-framework/action-menu + # [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) - refactor!: rename Handler to MessageHandler (#1161) ([5e48696](https://github.com/hyperledger/aries-framework-javascript/commit/5e48696ec16d88321f225628e6cffab243718b4c)), closes [#1161](https://github.com/hyperledger/aries-framework-javascript/issues/1161) diff --git a/packages/action-menu/package.json b/packages/action-menu/package.json index 94c34249fe..8f0276e054 100644 --- a/packages/action-menu/package.json +++ b/packages/action-menu/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/action-menu", "main": "build/index", "types": "build/index", - "version": "0.3.0", + "version": "0.3.1", "files": [ "build" ], @@ -32,7 +32,7 @@ "@aries-framework/core": "0.2.5" }, "devDependencies": { - "@aries-framework/node": "0.3.0", + "@aries-framework/node": "0.3.1", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 6d26e52045..ca67dcc743 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) + +### Bug Fixes + +- missing migration script and exports ([#1184](https://github.com/hyperledger/aries-framework-javascript/issues/1184)) ([460510d](https://github.com/hyperledger/aries-framework-javascript/commit/460510db43a7c63fd8dc1c3614be03fd8772f63c)) + # [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) ### Bug Fixes diff --git a/packages/core/package.json b/packages/core/package.json index 01c2ef62f7..ad147f5580 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/core", "main": "build/index", "types": "build/index", - "version": "0.3.0", + "version": "0.3.1", "files": [ "build" ], diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index 51339c3f56..f34aa9b4fc 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) + +**Note:** Version bump only for package @aries-framework/node + # [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) - refactor!: add agent context (#920) ([b47cfcb](https://github.com/hyperledger/aries-framework-javascript/commit/b47cfcba1450cd1d6839bf8192d977bfe33f1bb0)), closes [#920](https://github.com/hyperledger/aries-framework-javascript/issues/920) diff --git a/packages/node/package.json b/packages/node/package.json index 7a1fc24077..a85baed7f6 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/node", "main": "build/index", "types": "build/index", - "version": "0.3.0", + "version": "0.3.1", "files": [ "build", "bin" @@ -28,7 +28,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.0", + "@aries-framework/core": "0.3.1", "express": "^4.17.1", "ffi-napi": "^4.0.3", "indy-sdk": "^1.16.0-dev-1636", diff --git a/packages/question-answer/CHANGELOG.md b/packages/question-answer/CHANGELOG.md index 92bab66277..614f6bfbcd 100644 --- a/packages/question-answer/CHANGELOG.md +++ b/packages/question-answer/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) + +**Note:** Version bump only for package @aries-framework/question-answer + # [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) - refactor!: rename Handler to MessageHandler (#1161) ([5e48696](https://github.com/hyperledger/aries-framework-javascript/commit/5e48696ec16d88321f225628e6cffab243718b4c)), closes [#1161](https://github.com/hyperledger/aries-framework-javascript/issues/1161) diff --git a/packages/question-answer/package.json b/packages/question-answer/package.json index e3e56c9232..6c34ba7687 100644 --- a/packages/question-answer/package.json +++ b/packages/question-answer/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/question-answer", "main": "build/index", "types": "build/index", - "version": "0.3.0", + "version": "0.3.1", "files": [ "build" ], @@ -32,7 +32,7 @@ "@aries-framework/core": "0.2.5" }, "devDependencies": { - "@aries-framework/node": "0.3.0", + "@aries-framework/node": "0.3.1", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/react-native/CHANGELOG.md b/packages/react-native/CHANGELOG.md index 64b8138f53..cec577df36 100644 --- a/packages/react-native/CHANGELOG.md +++ b/packages/react-native/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) + +**Note:** Version bump only for package @aries-framework/react-native + # [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) ### Bug Fixes diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 1242d6aa98..77a453de94 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/react-native", "main": "build/index", "types": "build/index", - "version": "0.3.0", + "version": "0.3.1", "files": [ "build" ], @@ -24,7 +24,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.0", + "@aries-framework/core": "0.3.1", "@azure/core-asynciterator-polyfill": "^1.0.0", "events": "^3.3.0" }, diff --git a/packages/tenants/CHANGELOG.md b/packages/tenants/CHANGELOG.md index 3ba91895eb..12cb7d1428 100644 --- a/packages/tenants/CHANGELOG.md +++ b/packages/tenants/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) + +**Note:** Version bump only for package @aries-framework/tenants + # [0.3.0](https://github.com/hyperledger/aries-framework-javascript/compare/v0.2.5...v0.3.0) (2022-12-22) **Note:** Version bump only for package @aries-framework/tenants diff --git a/packages/tenants/package.json b/packages/tenants/package.json index bcac141048..0b7cc578c4 100644 --- a/packages/tenants/package.json +++ b/packages/tenants/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/tenants", "main": "build/index", "types": "build/index", - "version": "0.3.0", + "version": "0.3.1", "files": [ "build" ], @@ -24,11 +24,11 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.0", + "@aries-framework/core": "0.3.1", "async-mutex": "^0.3.2" }, "devDependencies": { - "@aries-framework/node": "0.3.0", + "@aries-framework/node": "0.3.1", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" From 541356e866bcd3ce06c69093d8cb6100dca4d09f Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 4 Jan 2023 18:28:40 +0800 Subject: [PATCH 109/125] fix(credentials): typing if no modules provided (#1188) Fixes https://github.com/hyperledger/aries-framework-javascript/issues/1187 Signed-off-by: Timo Glastra --- .../v2.ldproof.credentials.propose-offerBbs.test.ts | 6 +++--- packages/core/src/agent/AgentModules.ts | 5 ++++- .../v2.ldproof.connectionless-credentials.test.ts | 4 ++-- .../v2.ldproof.credentials-auto-accept.test.ts | 6 +++--- packages/core/src/types.ts | 5 +++++ packages/core/tests/helpers.ts | 13 +++++++++---- packages/tenants/src/TenantsApi.ts | 4 ++-- 7 files changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts index a1819304b6..4569b44631 100644 --- a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts +++ b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts @@ -1,7 +1,7 @@ -import type { Agent } from '../../core/src/agent/Agent' import type { ConnectionRecord } from '../../core/src/modules/connections' import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../core/src/modules/credentials/formats/jsonld' import type { Wallet } from '../../core/src/wallet' +import type { CredentialTestsAgent } from '../../core/tests/helpers' import { InjectionSymbols } from '../../core/src/constants' import { KeyType } from '../../core/src/crypto' @@ -18,8 +18,8 @@ import testLogger from '../../core/tests/logger' import { describeSkipNode17And18 } from './util' -let faberAgent: Agent -let aliceAgent: Agent +let faberAgent: CredentialTestsAgent +let aliceAgent: CredentialTestsAgent let aliceConnection: ConnectionRecord let aliceCredentialRecord: CredentialExchangeRecord let faberCredentialRecord: CredentialExchangeRecord diff --git a/packages/core/src/agent/AgentModules.ts b/packages/core/src/agent/AgentModules.ts index 9b0ec0448c..ce59b48daa 100644 --- a/packages/core/src/agent/AgentModules.ts +++ b/packages/core/src/agent/AgentModules.ts @@ -1,4 +1,5 @@ import type { Module, DependencyManager, ApiModule } from '../plugins' +import type { IsAny } from '../types' import type { Constructor } from '../utils/mixins' import type { AgentConfig } from './AgentConfig' @@ -101,7 +102,9 @@ export type AgentApi = { export type CustomOrDefaultApi< CustomModuleType, DefaultModuleType extends ApiModule -> = CustomModuleType extends ApiModule +> = IsAny extends true + ? InstanceType + : CustomModuleType extends ApiModule ? InstanceType : CustomModuleType extends Module ? never diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts index 32048db8bb..e8085a05f2 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts @@ -58,8 +58,8 @@ let wallet let signCredentialOptions: JsonLdCredentialDetailFormat describe('credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent + let faberAgent: Agent + let aliceAgent: Agent let faberReplay: ReplaySubject let aliceReplay: ReplaySubject const seed = 'testseed000000000000000000000001' diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts index 2223ac9132..3870b22bb3 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts @@ -1,4 +1,4 @@ -import type { Agent } from '../../../../../agent/Agent' +import type { CredentialTestsAgent } from '../../../../../../tests/helpers' import type { Wallet } from '../../../../../wallet' import type { ConnectionRecord } from '../../../../connections' import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' @@ -26,8 +26,8 @@ const TEST_LD_DOCUMENT: JsonCredential = { } describe('credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent + let faberAgent: CredentialTestsAgent + let aliceAgent: CredentialTestsAgent let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord let aliceCredentialRecord: CredentialExchangeRecord diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index fcc0945cce..36450e0c3c 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -193,3 +193,8 @@ export type FlatArray = Arr extends ReadonlyArray ? FlatArr * Get the awaited (resolved promise) type of Promise type. */ export type Awaited = T extends Promise ? U : never + +/** + * Type util that returns `true` or `false` based on whether the input type `T` is of type `any` + */ +export type IsAny = unknown extends T ? ([keyof T] extends [never] ? false : true) : false diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 7c518c25cf..70864471ff 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -2,6 +2,7 @@ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { AcceptCredentialOfferOptions, + AgentDependencies, BasicMessage, BasicMessageStateChangedEvent, ConnectionRecordProps, @@ -13,10 +14,11 @@ import type { SchemaTemplate, Wallet, } from '../src' -import type { AgentModulesInput } from '../src/agent/AgentModules' +import type { AgentModulesInput, EmptyModuleMap } from '../src/agent/AgentModules' import type { IndyOfferCredentialFormat } from '../src/modules/credentials/formats/indy/IndyCredentialFormat' import type { ProofAttributeInfo, ProofPredicateInfo } from '../src/modules/proofs/formats/indy/models' import type { AutoAcceptProof } from '../src/modules/proofs/models/ProofAutoAcceptType' +import type { Awaited } from '../src/types' import type { CredDef, Schema } from 'indy-sdk' import type { Observable } from 'rxjs' @@ -83,11 +85,11 @@ const taaVersion = (process.env.TEST_AGENT_TAA_VERSION ?? '1') as `${number}.${n const taaAcceptanceMechanism = process.env.TEST_AGENT_TAA_ACCEPTANCE_MECHANISM ?? 'accept' export { agentDependencies } -export function getAgentOptions( +export function getAgentOptions( name: string, extraConfig: Partial = {}, modules?: AgentModules -) { +): { config: InitConfig; modules: AgentModules; dependencies: AgentDependencies } { const config: InitConfig = { label: `Agent: ${name}`, walletConfig: { @@ -111,7 +113,8 @@ export function getAgentOptions( logger: new TestLogger(LogLevel.off, name), ...extraConfig, } - return { config, modules, dependencies: agentDependencies } as const + + return { config, modules: (modules ?? {}) as AgentModules, dependencies: agentDependencies } as const } export function getPostgresAgentOptions(name: string, extraConfig: Partial = {}) { @@ -675,6 +678,8 @@ export function mockProperty(object: T, propert Object.defineProperty(object, property, { get: () => value }) } +// Helper type to get the type of the agents (with the custom modules) for the credential tests +export type CredentialTestsAgent = Awaited>['aliceAgent'] export async function setupCredentialTests( faberName: string, aliceName: string, diff --git a/packages/tenants/src/TenantsApi.ts b/packages/tenants/src/TenantsApi.ts index 430bd2634f..e29b487b14 100644 --- a/packages/tenants/src/TenantsApi.ts +++ b/packages/tenants/src/TenantsApi.ts @@ -1,5 +1,5 @@ import type { CreateTenantOptions, GetTenantAgentOptions, WithTenantAgentCallback } from './TenantsApiOptions' -import type { EmptyModuleMap, ModulesMap } from '@aries-framework/core' +import type { DefaultAgentModules, ModulesMap } from '@aries-framework/core' import { AgentContext, inject, InjectionSymbols, AgentContextProvider, injectable, Logger } from '@aries-framework/core' @@ -7,7 +7,7 @@ import { TenantAgent } from './TenantAgent' import { TenantRecordService } from './services' @injectable() -export class TenantsApi { +export class TenantsApi { private agentContext: AgentContext private tenantRecordService: TenantRecordService private agentContextProvider: AgentContextProvider From 5f98fed8ec99ec48b21ab1303cd8be9263d7f568 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Jan 2023 14:22:34 +0100 Subject: [PATCH 110/125] chore(release): v0.3.2 (#1190) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 6 ++++++ lerna.json | 2 +- packages/action-menu/CHANGELOG.md | 4 ++++ packages/action-menu/package.json | 4 ++-- packages/core/CHANGELOG.md | 6 ++++++ packages/core/package.json | 2 +- packages/node/CHANGELOG.md | 4 ++++ packages/node/package.json | 4 ++-- packages/question-answer/CHANGELOG.md | 4 ++++ packages/question-answer/package.json | 4 ++-- packages/react-native/CHANGELOG.md | 4 ++++ packages/react-native/package.json | 4 ++-- packages/tenants/CHANGELOG.md | 6 ++++++ packages/tenants/package.json | 6 +++--- 14 files changed, 47 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d19e023ee6..50748153df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) + +### Bug Fixes + +- **credentials:** typing if no modules provided ([#1188](https://github.com/hyperledger/aries-framework-javascript/issues/1188)) ([541356e](https://github.com/hyperledger/aries-framework-javascript/commit/541356e866bcd3ce06c69093d8cb6100dca4d09f)) + ## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) ### Bug Fixes diff --git a/lerna.json b/lerna.json index 880e72d3df..56d3a038a2 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.3.1", + "version": "0.3.2", "useWorkspaces": true, "npmClient": "yarn", "command": { diff --git a/packages/action-menu/CHANGELOG.md b/packages/action-menu/CHANGELOG.md index 03254b92a1..c3167eb95d 100644 --- a/packages/action-menu/CHANGELOG.md +++ b/packages/action-menu/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) + +**Note:** Version bump only for package @aries-framework/action-menu + ## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) **Note:** Version bump only for package @aries-framework/action-menu diff --git a/packages/action-menu/package.json b/packages/action-menu/package.json index 8f0276e054..cedf2db5ef 100644 --- a/packages/action-menu/package.json +++ b/packages/action-menu/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/action-menu", "main": "build/index", "types": "build/index", - "version": "0.3.1", + "version": "0.3.2", "files": [ "build" ], @@ -32,7 +32,7 @@ "@aries-framework/core": "0.2.5" }, "devDependencies": { - "@aries-framework/node": "0.3.1", + "@aries-framework/node": "0.3.2", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index ca67dcc743..341ff76014 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) + +### Bug Fixes + +- **credentials:** typing if no modules provided ([#1188](https://github.com/hyperledger/aries-framework-javascript/issues/1188)) ([541356e](https://github.com/hyperledger/aries-framework-javascript/commit/541356e866bcd3ce06c69093d8cb6100dca4d09f)) + ## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) ### Bug Fixes diff --git a/packages/core/package.json b/packages/core/package.json index ad147f5580..3325039d25 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/core", "main": "build/index", "types": "build/index", - "version": "0.3.1", + "version": "0.3.2", "files": [ "build" ], diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index f34aa9b4fc..a3f6e278ce 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) + +**Note:** Version bump only for package @aries-framework/node + ## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) **Note:** Version bump only for package @aries-framework/node diff --git a/packages/node/package.json b/packages/node/package.json index a85baed7f6..540489b8b6 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/node", "main": "build/index", "types": "build/index", - "version": "0.3.1", + "version": "0.3.2", "files": [ "build", "bin" @@ -28,7 +28,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.1", + "@aries-framework/core": "0.3.2", "express": "^4.17.1", "ffi-napi": "^4.0.3", "indy-sdk": "^1.16.0-dev-1636", diff --git a/packages/question-answer/CHANGELOG.md b/packages/question-answer/CHANGELOG.md index 614f6bfbcd..216388c0ea 100644 --- a/packages/question-answer/CHANGELOG.md +++ b/packages/question-answer/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) + +**Note:** Version bump only for package @aries-framework/question-answer + ## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) **Note:** Version bump only for package @aries-framework/question-answer diff --git a/packages/question-answer/package.json b/packages/question-answer/package.json index 6c34ba7687..8be53f94cc 100644 --- a/packages/question-answer/package.json +++ b/packages/question-answer/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/question-answer", "main": "build/index", "types": "build/index", - "version": "0.3.1", + "version": "0.3.2", "files": [ "build" ], @@ -32,7 +32,7 @@ "@aries-framework/core": "0.2.5" }, "devDependencies": { - "@aries-framework/node": "0.3.1", + "@aries-framework/node": "0.3.2", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/react-native/CHANGELOG.md b/packages/react-native/CHANGELOG.md index cec577df36..092f793c6d 100644 --- a/packages/react-native/CHANGELOG.md +++ b/packages/react-native/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) + +**Note:** Version bump only for package @aries-framework/react-native + ## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) **Note:** Version bump only for package @aries-framework/react-native diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 77a453de94..dfef41e5d2 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/react-native", "main": "build/index", "types": "build/index", - "version": "0.3.1", + "version": "0.3.2", "files": [ "build" ], @@ -24,7 +24,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.1", + "@aries-framework/core": "0.3.2", "@azure/core-asynciterator-polyfill": "^1.0.0", "events": "^3.3.0" }, diff --git a/packages/tenants/CHANGELOG.md b/packages/tenants/CHANGELOG.md index 12cb7d1428..a73651ba84 100644 --- a/packages/tenants/CHANGELOG.md +++ b/packages/tenants/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) + +### Bug Fixes + +- **credentials:** typing if no modules provided ([#1188](https://github.com/hyperledger/aries-framework-javascript/issues/1188)) ([541356e](https://github.com/hyperledger/aries-framework-javascript/commit/541356e866bcd3ce06c69093d8cb6100dca4d09f)) + ## [0.3.1](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.0...v0.3.1) (2022-12-27) **Note:** Version bump only for package @aries-framework/tenants diff --git a/packages/tenants/package.json b/packages/tenants/package.json index 0b7cc578c4..191baf900f 100644 --- a/packages/tenants/package.json +++ b/packages/tenants/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/tenants", "main": "build/index", "types": "build/index", - "version": "0.3.1", + "version": "0.3.2", "files": [ "build" ], @@ -24,11 +24,11 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.1", + "@aries-framework/core": "0.3.2", "async-mutex": "^0.3.2" }, "devDependencies": { - "@aries-framework/node": "0.3.1", + "@aries-framework/node": "0.3.2", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" From adba83d8df176288083969f2c3f975bbfc1acd9c Mon Sep 17 00:00:00 2001 From: Karim Stekelenburg Date: Mon, 9 Jan 2023 06:28:27 +0100 Subject: [PATCH 111/125] feat: add anoncreds package (#1118) Signed-off-by: Karim Stekelenburg --- packages/anoncreds/README.md | 35 ++++++ packages/anoncreds/jest.config.ts | 14 +++ packages/anoncreds/package.json | 34 ++++++ packages/anoncreds/src/index.ts | 2 + packages/anoncreds/src/models/exchange.ts | 110 ++++++++++++++++++ packages/anoncreds/src/models/index.ts | 3 + packages/anoncreds/src/models/internal.ts | 31 +++++ packages/anoncreds/src/models/registry.ts | 38 ++++++ .../src/services/AnonCredsHolderService.ts | 40 +++++++ .../services/AnonCredsHolderServiceOptions.ts | 80 +++++++++++++ .../src/services/AnonCredsIssuerService.ts | 29 +++++ .../services/AnonCredsIssuerServiceOptions.ts | 44 +++++++ .../src/services/AnonCredsRegistry.ts | 41 +++++++ .../src/services/AnonCredsRegistryOptions.ts | 47 ++++++++ .../src/services/AnonCredsVerifierService.ts | 7 ++ .../AnonCredsVerifierServiceOptions.ts | 31 +++++ packages/anoncreds/src/services/index.ts | 8 ++ packages/anoncreds/tsconfig.build.json | 7 ++ packages/anoncreds/tsconfig.json | 6 + 19 files changed, 607 insertions(+) create mode 100644 packages/anoncreds/README.md create mode 100644 packages/anoncreds/jest.config.ts create mode 100644 packages/anoncreds/package.json create mode 100644 packages/anoncreds/src/index.ts create mode 100644 packages/anoncreds/src/models/exchange.ts create mode 100644 packages/anoncreds/src/models/index.ts create mode 100644 packages/anoncreds/src/models/internal.ts create mode 100644 packages/anoncreds/src/models/registry.ts create mode 100644 packages/anoncreds/src/services/AnonCredsHolderService.ts create mode 100644 packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts create mode 100644 packages/anoncreds/src/services/AnonCredsIssuerService.ts create mode 100644 packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts create mode 100644 packages/anoncreds/src/services/AnonCredsRegistry.ts create mode 100644 packages/anoncreds/src/services/AnonCredsRegistryOptions.ts create mode 100644 packages/anoncreds/src/services/AnonCredsVerifierService.ts create mode 100644 packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts create mode 100644 packages/anoncreds/src/services/index.ts create mode 100644 packages/anoncreds/tsconfig.build.json create mode 100644 packages/anoncreds/tsconfig.json diff --git a/packages/anoncreds/README.md b/packages/anoncreds/README.md new file mode 100644 index 0000000000..5bf5e5fbb0 --- /dev/null +++ b/packages/anoncreds/README.md @@ -0,0 +1,35 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript AnonCreds Interfaces

+

+ License + typescript + @aries-framework/anoncreds version + +

+
+ +### Installation + +### Quick start + +### Example of usage diff --git a/packages/anoncreds/jest.config.ts b/packages/anoncreds/jest.config.ts new file mode 100644 index 0000000000..c7c5196637 --- /dev/null +++ b/packages/anoncreds/jest.config.ts @@ -0,0 +1,14 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + name: packageJson.name, + displayName: packageJson.name, + // setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/packages/anoncreds/package.json b/packages/anoncreds/package.json new file mode 100644 index 0000000000..29946c1b20 --- /dev/null +++ b/packages/anoncreds/package.json @@ -0,0 +1,34 @@ +{ + "name": "@aries-framework/anoncreds", + "main": "build/index", + "types": "build/index", + "version": "0.2.5", + "files": [ + "build" + ], + "private": true, + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/anoncreds", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/anoncreds" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf -rf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@aries-framework/core": "*" + }, + "peerDependencies": {}, + "devDependencies": { + "typescript": "~4.3.0" + } +} diff --git a/packages/anoncreds/src/index.ts b/packages/anoncreds/src/index.ts new file mode 100644 index 0000000000..83fdeb7877 --- /dev/null +++ b/packages/anoncreds/src/index.ts @@ -0,0 +1,2 @@ +export * from './models' +export * from './services' diff --git a/packages/anoncreds/src/models/exchange.ts b/packages/anoncreds/src/models/exchange.ts new file mode 100644 index 0000000000..420c26a158 --- /dev/null +++ b/packages/anoncreds/src/models/exchange.ts @@ -0,0 +1,110 @@ +// TODO: Maybe we can make this a bit more specific? +export type WalletQuery = Record + +export interface ReferentWalletQuery { + [key: string]: WalletQuery +} + +export interface NonRevokedInterval { + from?: number + to?: number +} + +export interface AnonCredsCredentialOffer { + schema_id: string + cred_def_id: string + nonce: string + key_correctness_proof: Record +} + +export interface AnonCredsCredentialRequest { + // TODO: Why is this needed? It is just used as context in Ursa, can be any string. Should we remove it? + // Should we not make it did related? + prover_did: string + cred_def_id: string + blinded_ms: Record + blinded_ms_correctness_proof: Record + nonce: string +} + +export interface CredValue { + raw: string + encoded: string // Raw value as number in string +} + +export interface AnonCredsCredential { + schema_id: string + cred_def_id: string + rev_reg_id?: string + values: Record + signature: unknown + signature_correctness_proof: unknown +} + +export interface AnonCredsProof { + requested_proof: { + revealed_attrs: Record< + string, + { + sub_proof_index: number + raw: string + encoded: string + } + > + revealed_attr_groups: Record< + string, + { + sub_proof_index: number + values: { + [key: string]: { + raw: string + encoded: string + } + } + } + > + unrevealed_attrs: Record< + string, + { + sub_proof_index: number + } + > + self_attested_attrs: Record + + requested_predicates: Record + } + proof: any + identifiers: Array<{ + schema_id: string + cred_def_id: string + rev_reg_id?: string + timestamp?: number + }> +} + +export interface AnonCredsProofRequest { + name: string + version: string + nonce: string + requested_attributes: Record< + string, + { + name?: string + names?: string + restrictions?: WalletQuery[] + non_revoked?: NonRevokedInterval + } + > + requested_predicates: Record< + string, + { + name: string + p_type: '>=' | '>' | '<=' | '<' + p_value: number + restrictions?: WalletQuery[] + non_revoked?: NonRevokedInterval + } + > + non_revoked?: NonRevokedInterval + ver?: '1.0' | '2.0' +} diff --git a/packages/anoncreds/src/models/index.ts b/packages/anoncreds/src/models/index.ts new file mode 100644 index 0000000000..6dd1a6e3bb --- /dev/null +++ b/packages/anoncreds/src/models/index.ts @@ -0,0 +1,3 @@ +export * from './internal' +export * from './exchange' +export * from './registry' diff --git a/packages/anoncreds/src/models/internal.ts b/packages/anoncreds/src/models/internal.ts new file mode 100644 index 0000000000..f0b64d614c --- /dev/null +++ b/packages/anoncreds/src/models/internal.ts @@ -0,0 +1,31 @@ +export interface CredentialInfo { + referent: string + attributes: { + [key: string]: string + } + schemaId: string + credentialDefinitionId: string + revocationRegistryId?: number | undefined + credentialRevocationId?: string | undefined +} + +export interface RequestedAttribute { + credentialId: string + timestamp?: number + revealed: boolean + credentialInfo?: CredentialInfo + revoked?: boolean +} + +export interface RequestedPredicate { + credentialId: string + timestamp?: number + credentialInfo?: CredentialInfo + revoked?: boolean +} + +export interface RequestedCredentials { + requestedAttributes?: Record + requestedPredicates?: Record + selfAttestedAttributes: Record +} diff --git a/packages/anoncreds/src/models/registry.ts b/packages/anoncreds/src/models/registry.ts new file mode 100644 index 0000000000..98268e1e84 --- /dev/null +++ b/packages/anoncreds/src/models/registry.ts @@ -0,0 +1,38 @@ +export interface AnonCredsSchema { + name: string + version: string + attrNames: string[] +} + +export interface AnonCredsCredentialDefinition { + schemaId: string + type: 'CL' + tag: string + // TODO: work out in more detail + value: { + primary: Record + revocation?: unknown + } +} + +export interface AnonCredsRevocationRegistryDefinition { + type: 'CL_ACCUM' + credDefId: string + tag: string + publicKeys: { + accumKey: { + z: string + } + } + maxCredNum: number + tailsLocation: string + tailsHash: string +} + +export interface AnonCredsRevocationList { + // TODO: this is a new property, but do we keep abbreviation or not? + revRegId: string + revocationList: number[] + currentAccumulator: string + timestamp: number +} diff --git a/packages/anoncreds/src/services/AnonCredsHolderService.ts b/packages/anoncreds/src/services/AnonCredsHolderService.ts new file mode 100644 index 0000000000..62c7a49aa4 --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsHolderService.ts @@ -0,0 +1,40 @@ +import type { CredentialInfo } from '../models' +import type { AnonCredsProof } from '../models/exchange' +import type { + CreateCredentialRequestOptions, + CreateCredentialRequestReturn, + CreateProofOptions, + GetCredentialOptions, + StoreCredentialOptions, + GetCredentialsForProofRequestOptions, + GetCredentialsForProofRequestReturn, +} from './AnonCredsHolderServiceOptions' +import type { AgentContext } from '@aries-framework/core' + +export interface AnonCredsHolderService { + createProof( + agentContext: AgentContext, + options: CreateProofOptions, + metadata?: Record + ): Promise + storeCredential( + agentContext: AgentContext, + options: StoreCredentialOptions, + metadata?: Record + ): Promise + + // TODO: indy has different return types for the credential + getCredential(agentContext: AgentContext, options: GetCredentialOptions): Promise + + createCredentialRequest( + agentContext: AgentContext, + options: CreateCredentialRequestOptions, + metadata?: Record + ): Promise + + deleteCredential(agentContext: AgentContext, credentialId: string): Promise + getCredentialsForProofRequest( + agentContext: AgentContext, + options: GetCredentialsForProofRequestOptions + ): Promise +} diff --git a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts new file mode 100644 index 0000000000..86cb8bbcf9 --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts @@ -0,0 +1,80 @@ +import type { CredentialInfo, RequestedCredentials } from '../models' +import type { + AnonCredsCredential, + AnonCredsCredentialOffer, + AnonCredsCredentialRequest, + AnonCredsProofRequest, + NonRevokedInterval, + ReferentWalletQuery, +} from '../models/exchange' +import type { + AnonCredsCredentialDefinition, + AnonCredsRevocationList, + AnonCredsRevocationRegistryDefinition, + AnonCredsSchema, +} from '../models/registry' + +export interface AttributeInfo { + name?: string + names?: string[] +} + +export interface CreateProofOptions { + proofRequest: AnonCredsProofRequest + requestedCredentials: RequestedCredentials + schemas: { + [schemaId: string]: AnonCredsSchema + } + credentialDefinitions: { + [credentialDefinitionId: string]: AnonCredsCredentialDefinition + } + revocationStates: { + [revocationRegistryDefinitionId: string]: { + definition: AnonCredsRevocationRegistryDefinition + revocationLists: { + [timestamp: string]: AnonCredsRevocationList + } + } + } +} + +export interface StoreCredentialOptions { + // TODO: what is in credential request metadata? + credentialRequestMetadata: Record + credential: AnonCredsCredential + credentialDefinition: AnonCredsCredentialDefinition + credentialDefinitionId: string + credentialId?: string + revocationRegistryDefinition?: AnonCredsRevocationRegistryDefinition + revocationRegistryDefinitionId: string +} + +export interface GetCredentialOptions { + credentialId: string +} + +export interface GetCredentialsForProofRequestOptions { + proofRequest: AnonCredsProofRequest + attributeReferent: string + start?: number + limit?: number + extraQuery?: ReferentWalletQuery +} + +export interface GetCredentialsForProofRequestReturn { + credentialInfo: CredentialInfo + interval?: NonRevokedInterval +} + +export interface CreateCredentialRequestOptions { + // TODO: Why is this needed? It is just used as context in Ursa, can be any string. Should we remove it? + // Should we not make it did related? (related to comment in AnonCredsCredentialRequest) + holderDid: string + credentialOffer: AnonCredsCredentialOffer + credentialDefinition: AnonCredsCredentialDefinition +} + +export interface CreateCredentialRequestReturn { + credentialRequest: AnonCredsCredentialRequest + credentialRequestMetadata: Record +} diff --git a/packages/anoncreds/src/services/AnonCredsIssuerService.ts b/packages/anoncreds/src/services/AnonCredsIssuerService.ts new file mode 100644 index 0000000000..f3fb8c128f --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsIssuerService.ts @@ -0,0 +1,29 @@ +import type { AnonCredsCredentialOffer } from '../models/exchange' +import type { AnonCredsCredentialDefinition, AnonCredsSchema } from '../models/registry' +import type { + CreateSchemaOptions, + CreateCredentialDefinitionOptions, + CreateCredentialOfferOptions, + CreateCredentialReturn, + CreateCredentialOptions, +} from './AnonCredsIssuerServiceOptions' +import type { AgentContext } from '@aries-framework/core' + +export interface AnonCredsIssuerService { + createSchema(agentContext: AgentContext, options: CreateSchemaOptions): Promise + + // This should store the private part of the credential definition as in the indy-sdk + // we don't have access to the private part of the credential definition + createCredentialDefinition( + agentContext: AgentContext, + options: CreateCredentialDefinitionOptions, + metadata?: Record + ): Promise + + createCredentialOffer( + agentContext: AgentContext, + options: CreateCredentialOfferOptions + ): Promise + + createCredential(agentContext: AgentContext, options: CreateCredentialOptions): Promise +} diff --git a/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts new file mode 100644 index 0000000000..27f5450d2f --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts @@ -0,0 +1,44 @@ +import type { + AnonCredsCredential, + AnonCredsCredentialOffer, + AnonCredsCredentialRequest, + CredValue, +} from '../models/exchange' +import type { AnonCredsSchema } from '../models/registry' + +export interface CreateSchemaOptions { + issuerId: string + name: string + version: string + attrNames: string[] +} + +export interface CreateCredentialDefinitionOptions { + issuerId: string + tag: string // TODO: this was initially defined as optional, is that the case? + supportRevocation?: boolean + + // If schema doesn't include the id, we need to add it as a separate input parameter + schema: { + value: AnonCredsSchema + id: string + } +} + +export interface CreateCredentialOfferOptions { + credentialDefinitionId: string +} + +export interface CreateCredentialOptions { + credentialOffer: AnonCredsCredentialOffer + credentialRequest: AnonCredsCredentialRequest + credentialValues: Record + revocationRegistryId?: string + // TODO: should this just be the tails file instead of a path? + tailsFilePath?: string +} + +export interface CreateCredentialReturn { + credential: AnonCredsCredential + credentialRevocationId?: string +} diff --git a/packages/anoncreds/src/services/AnonCredsRegistry.ts b/packages/anoncreds/src/services/AnonCredsRegistry.ts new file mode 100644 index 0000000000..ead8f8aa70 --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsRegistry.ts @@ -0,0 +1,41 @@ +import type { + AnonCredsCredentialDefinition, + AnonCredsRevocationList, + AnonCredsRevocationRegistryDefinition, + AnonCredsSchema, +} from '../models/registry' +import type { + RegisterCredentialDefinitionOptions, + RegisterCredentialDefinitionReturn, + RegisterRevocationListOptions, + RegisterRevocationListReturn, + RegisterRevocationRegistryDefinitionOptions, + RegisterRevocationRegistryDefinitionReturn, + RegisterSchemaOptions, + RegisterSchemaReturn, +} from './AnonCredsRegistryOptions' +import type { AgentContext } from '@aries-framework/core' + +// This service can be registered multiple times in a single AFJ instance. +// TODO: should all methods have interfaces as input for consistency? +export interface AnonCredsRegistry { + getSchema(agentContext: AgentContext, schemaId: string): Promise + registerSchema(options: RegisterSchemaOptions): Promise + + getCredentialDefinition(credentialDefinitionId: string): Promise + registerCredentialDefinition( + options: RegisterCredentialDefinitionOptions + ): Promise + + getRevocationRegistryDefinition( + revocationRegistryDefinitionId: string + ): Promise + registerRevocationRegistryDefinition( + options: RegisterRevocationRegistryDefinitionOptions + ): Promise + + // TODO: The name of this data model is still tbd. + getRevocationList(revocationRegistryId: string, timestamp: string): Promise + + registerRevocationList(options: RegisterRevocationListOptions): Promise +} diff --git a/packages/anoncreds/src/services/AnonCredsRegistryOptions.ts b/packages/anoncreds/src/services/AnonCredsRegistryOptions.ts new file mode 100644 index 0000000000..206d56fc4d --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsRegistryOptions.ts @@ -0,0 +1,47 @@ +import type { + AnonCredsCredentialDefinition, + AnonCredsRevocationList, + AnonCredsRevocationRegistryDefinition, + AnonCredsSchema, +} from '../models/registry' +import type { AgentContext } from '@aries-framework/core' + +export interface RegisterSchemaOptions { + agentContext: AgentContext + schema: AnonCredsSchema + + // Identifier of issuer that will create the credential definition. + issuerId: string +} +export interface RegisterSchemaReturn { + schemaId: string +} + +export interface RegisterCredentialDefinitionOptions { + credentialDefinition: AnonCredsCredentialDefinition + + // Identifier of issuer that will create the credential definition. + issuerId: string +} + +export interface RegisterCredentialDefinitionReturn { + credentialDefinitionId: string +} + +export interface RegisterRevocationRegistryDefinitionOptions { + revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition +} + +export interface RegisterRevocationRegistryDefinitionReturn { + revocationRegistryDefinitionId: string +} + +export interface RegisterRevocationListOptions { + // Timestamp is often calculated by the ledger, otherwise method should just take current time + // Return type does include the timestamp. + revocationList: Omit +} + +export interface RegisterRevocationListReturn { + timestamp: string +} diff --git a/packages/anoncreds/src/services/AnonCredsVerifierService.ts b/packages/anoncreds/src/services/AnonCredsVerifierService.ts new file mode 100644 index 0000000000..ec68021817 --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsVerifierService.ts @@ -0,0 +1,7 @@ +import type { VerifyProofOptions } from './AnonCredsVerifierServiceOptions' + +export interface AnonCredsVerifierService { + // TODO: do we want to extend the return type with more info besides a boolean. + // If the value is false it would be nice to have some extra contexts about why it failed + verifyProof(options: VerifyProofOptions): Promise +} diff --git a/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts new file mode 100644 index 0000000000..c67470fd55 --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts @@ -0,0 +1,31 @@ +import type { AnonCredsProof, AnonCredsProofRequest } from '../models/exchange' +import type { + AnonCredsCredentialDefinition, + AnonCredsRevocationList, + AnonCredsRevocationRegistryDefinition, + AnonCredsSchema, +} from '../models/registry' + +export interface VerifyProofOptions { + proofRequest: AnonCredsProofRequest + proof: AnonCredsProof + schemas: { + [schemaId: string]: AnonCredsSchema + } + credentialDefinitions: { + [credentialDefinitionId: string]: AnonCredsCredentialDefinition + } + revocationStates: { + [revocationRegistryDefinitionId: string]: { + definition: AnonCredsRevocationRegistryDefinition + // NOTE: the verifier only needs the accumulator, not the whole state of the revocation registry + // Requiring this to be the full state means we need to retrieve the full state from the ledger + // as a verifier. This is just following the data models from the AnonCreds spec, but for e.g. indy + // this means we need to retrieve _ALL_ deltas from the ledger to verify a proof. While currently we + // only need to fetch the registry. + revocationLists: { + [timestamp: string]: AnonCredsRevocationList + } + } + } +} diff --git a/packages/anoncreds/src/services/index.ts b/packages/anoncreds/src/services/index.ts new file mode 100644 index 0000000000..1a612359ed --- /dev/null +++ b/packages/anoncreds/src/services/index.ts @@ -0,0 +1,8 @@ +export * from './AnonCredsHolderService' +export * from './AnonCredsHolderServiceOptions' +export * from './AnonCredsIssuerService' +export * from './AnonCredsIssuerServiceOptions' +export * from './AnonCredsRegistry' +export * from './AnonCredsRegistryOptions' +export * from './AnonCredsVerifierService' +export * from './AnonCredsVerifierServiceOptions' diff --git a/packages/anoncreds/tsconfig.build.json b/packages/anoncreds/tsconfig.build.json new file mode 100644 index 0000000000..2b75d0adab --- /dev/null +++ b/packages/anoncreds/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["src/**/*"] +} diff --git a/packages/anoncreds/tsconfig.json b/packages/anoncreds/tsconfig.json new file mode 100644 index 0000000000..46efe6f721 --- /dev/null +++ b/packages/anoncreds/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + } +} From 2f6ae143313b49e71265dc87e5bfd8caa4942127 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 04:33:49 +0100 Subject: [PATCH 112/125] build(deps): bump luxon from 1.28.0 to 1.28.1 (#1196) Bumps [luxon](https://github.com/moment/luxon) from 1.28.0 to 1.28.1. - [Release notes](https://github.com/moment/luxon/releases) - [Changelog](https://github.com/moment/luxon/blob/master/CHANGELOG.md) - [Commits](moment/luxon@1.28.0...1.28.1) --- updated-dependencies: - dependency-name: luxon dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3bd2eab136..e542fe1654 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7402,9 +7402,9 @@ lru_map@^0.4.1: integrity sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg== luxon@^1.27.0: - version "1.28.0" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" - integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ== + version "1.28.1" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.1.tgz#528cdf3624a54506d710290a2341aa8e6e6c61b0" + integrity sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw== make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" From 59d1982f5cbfbea82199320fead61038e445d49c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 04:07:44 +0000 Subject: [PATCH 113/125] build(deps): bump json5 from 1.0.1 to 1.0.2 (#1191) --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index e542fe1654..d28283327d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7082,9 +7082,9 @@ json5@2.x, json5@^2.2.1: integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" @@ -7888,9 +7888,9 @@ minimist-options@4.1.0: kind-of "^6.0.3" minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== minipass-collect@^1.0.2: version "1.0.2" From fd006f262a91f901e7f8a9c6e6882ea178230005 Mon Sep 17 00:00:00 2001 From: Kim Ebert Date: Tue, 10 Jan 2023 03:55:48 -0700 Subject: [PATCH 114/125] feat: adding trust ping events and trust ping command (#1182) Signed-off-by: Kim Ebert --- .../src/modules/connections/ConnectionsApi.ts | 40 ++++++++++++++++ .../modules/connections/TrustPingEvents.ts | 24 ++++++++++ .../core/src/modules/connections/index.ts | 1 + .../connections/services/TrustPingService.ts | 30 +++++++++++- packages/core/tests/connections.test.ts | 21 ++++++++- packages/core/tests/helpers.ts | 46 +++++++++++++++++++ 6 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 packages/core/src/modules/connections/TrustPingEvents.ts diff --git a/packages/core/src/modules/connections/ConnectionsApi.ts b/packages/core/src/modules/connections/ConnectionsApi.ts index b437c46b4e..da895de772 100644 --- a/packages/core/src/modules/connections/ConnectionsApi.ts +++ b/packages/core/src/modules/connections/ConnectionsApi.ts @@ -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 { /** @@ -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 { return this.connectionService.returnWhenIsConnected(this.agentContext, connectionId, options?.timeoutMs) } diff --git a/packages/core/src/modules/connections/TrustPingEvents.ts b/packages/core/src/modules/connections/TrustPingEvents.ts new file mode 100644 index 0000000000..55200e6c5b --- /dev/null +++ b/packages/core/src/modules/connections/TrustPingEvents.ts @@ -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 + } +} diff --git a/packages/core/src/modules/connections/index.ts b/packages/core/src/modules/connections/index.ts index 52fe834617..e9dd5862d9 100644 --- a/packages/core/src/modules/connections/index.ts +++ b/packages/core/src/modules/connections/index.ts @@ -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' diff --git a/packages/core/src/modules/connections/services/TrustPingService.ts b/packages/core/src/modules/connections/services/TrustPingService.ts index 17032e089e..5d4b10eb20 100644 --- a/packages/core/src/modules/connections/services/TrustPingService.ts +++ b/packages/core/src/modules/connections/services/TrustPingService.ts @@ -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, connection: ConnectionRecord) { + this.eventEmitter.emit(agentContext, { + type: TrustPingEventTypes.TrustPingReceivedEvent, + payload: { + connectionRecord: connection, + message: message, + }, + }) + if (message.responseRequested) { const response = new TrustPingResponseMessage({ threadId: message.id, @@ -18,8 +35,17 @@ export class TrustPingService { } } - // eslint-disable-next-line @typescript-eslint/no-unused-vars public processPingResponse(inboundMessage: InboundMessageContext) { - // TODO: handle ping response message + const { agentContext, message } = inboundMessage + + const connection = inboundMessage.assertReadyConnection() + + this.eventEmitter.emit(agentContext, { + type: TrustPingEventTypes.TrustPingResponseReceivedEvent, + payload: { + connectionRecord: connection, + message: message, + }, + }) } } diff --git a/packages/core/tests/connections.test.ts b/packages/core/tests/connections.test.ts index 598a6d8fe0..24c03ad907 100644 --- a/packages/core/tests/connections.test.ts +++ b/packages/core/tests/connections.test.ts @@ -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 @@ -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], diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 70864471ff..7354fbe5a4 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -8,6 +8,7 @@ import type { ConnectionRecordProps, CredentialDefinitionTemplate, CredentialStateChangedEvent, + TrustPingResponseReceivedEvent, InitConfig, InjectionToken, ProofStateChangedEvent, @@ -45,6 +46,7 @@ import { ConnectionRecord, CredentialEventTypes, CredentialState, + TrustPingEventTypes, DependencyManager, DidExchangeRole, DidExchangeState, @@ -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( + TrustPingEventTypes.TrustPingResponseReceivedEvent + ) + + return waitForTrustPingResponseReceivedEventSubject(observable, options) +} + +export function waitForTrustPingResponseReceivedEventSubject( + subject: ReplaySubject | Observable, + { + 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 | Observable, { From da7abdebeb6d5d9de5281a095d1f67e1c3e08e5b Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 11 Jan 2023 00:54:57 +0800 Subject: [PATCH 115/125] chore: rename plugin to module (#1201) Signed-off-by: Timo Glastra --- packages/action-menu/README.md | 8 ++++---- packages/question-answer/README.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/action-menu/README.md b/packages/action-menu/README.md index ffd98caf55..c47c6a4ac7 100644 --- a/packages/action-menu/README.md +++ b/packages/action-menu/README.md @@ -6,7 +6,7 @@ height="250px" />

-

Aries Framework JavaScript Action Menu Plugin

+

Aries Framework JavaScript Action Menu Module


-Action Menu plugin for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). Implements [Aries RFC 0509](https://github.com/hyperledger/aries-rfcs/blob/1795d5c2d36f664f88f5e8045042ace8e573808c/features/0509-action-menu/README.md). +Action Menu module for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). Implements [Aries RFC 0509](https://github.com/hyperledger/aries-rfcs/blob/1795d5c2d36f664f88f5e8045042ace8e573808c/features/0509-action-menu/README.md). ### Installation @@ -38,7 +38,7 @@ Make sure you have set up the correct version of Aries Framework JavaScript acco npm info "@aries-framework/action-menu" peerDependencies ``` -Then add the action-menu plugin to your project. +Then add the action-menu module to your project. ```sh yarn add @aries-framework/action-menu @@ -46,7 +46,7 @@ yarn add @aries-framework/action-menu ### Quick start -In order for this plugin to work, we have to inject it into the agent to access agent functionality. See the example for more information. +In order for this module to work, we have to inject it into the agent to access agent functionality. See the example for more information. ### Example of usage diff --git a/packages/question-answer/README.md b/packages/question-answer/README.md index 00ebca6637..33d1b17750 100644 --- a/packages/question-answer/README.md +++ b/packages/question-answer/README.md @@ -6,7 +6,7 @@ height="250px" />

-

Aries Framework JavaScript Question Answer Plugin

+

Aries Framework JavaScript Question Answer Module


-Question Answer plugin for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). Implements [Aries RFC 0113](https://github.com/hyperledger/aries-rfcs/blob/1795d5c2d36f664f88f5e8045042ace8e573808c/features/0113-question-answer/README.md). +Question Answer module for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). Implements [Aries RFC 0113](https://github.com/hyperledger/aries-rfcs/blob/1795d5c2d36f664f88f5e8045042ace8e573808c/features/0113-question-answer/README.md). ### Installation @@ -38,7 +38,7 @@ Make sure you have set up the correct version of Aries Framework JavaScript acco npm info "@aries-framework/question-answer" peerDependencies ``` -Then add the question-answer plugin to your project. +Then add the question-answer module to your project. ```sh yarn add @aries-framework/question-answer @@ -46,7 +46,7 @@ yarn add @aries-framework/question-answer ### Quick start -In order for this plugin to work, we have to inject it into the agent to access agent functionality. See the example for more information. +In order for this module to work, we have to inject it into the agent to access agent functionality. See the example for more information. ### Example of usage From 9933b35a6aa4524caef8a885e71b742cd0d7186b Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 11 Jan 2023 18:33:45 +0800 Subject: [PATCH 116/125] feat(indy-sdk): add indy-sdk package (#1200) Co-authored-by: Karim Stekelenburg Signed-off-by: Timo Glastra --- packages/action-menu/package.json | 7 +- packages/anoncreds/package.json | 6 +- packages/anoncreds/src/models/exchange.ts | 2 +- packages/anoncreds/src/models/internal.ts | 6 +- packages/anoncreds/src/models/registry.ts | 5 +- .../src/services/AnonCredsHolderService.ts | 11 +- .../services/AnonCredsHolderServiceOptions.ts | 14 +- .../services/AnonCredsIssuerServiceOptions.ts | 9 +- .../src/services/AnonCredsRegistry.ts | 41 - .../src/services/AnonCredsRegistryOptions.ts | 47 -- .../AnonCredsVerifierServiceOptions.ts | 2 +- packages/anoncreds/src/services/index.ts | 3 +- .../services/registry/AnonCredsRegistry.ts | 48 ++ .../registry/CredentialDefinitionOptions.ts | 45 ++ .../registry/RevocationListOptions.ts | 18 + .../RevocationRegistryDefinitionOptions.ts | 18 + .../src/services/registry/SchemaOptions.ts | 46 ++ .../anoncreds/src/services/registry/base.ts | 19 + .../anoncreds/src/services/registry/index.ts | 6 + packages/core/package.json | 2 +- packages/core/src/index.ts | 5 +- .../IndyCredentialFormatService.test.ts | 6 +- ...v2.ldproof.credentials-auto-accept.test.ts | 25 +- .../indy/services/IndyRevocationService.ts | 4 +- .../indy/services/IndyVerifierService.ts | 16 +- .../ledger/__tests__/LedgerApi.test.ts | 6 +- .../core/src/modules/ledger/error/index.ts | 3 + .../modules/routing/__tests__/pickup.test.ts | 34 +- .../modules/vc/__tests__/documentLoader.ts | 30 +- packages/core/src/utils/validators.ts | 2 +- packages/core/tests/helpers.ts | 52 +- packages/indy-sdk/README.md | 31 + packages/indy-sdk/jest.config.ts | 14 + packages/indy-sdk/package.json | 40 + packages/indy-sdk/src/IndySdkModule.ts | 17 + packages/indy-sdk/src/IndySdkModuleConfig.ts | 45 ++ packages/indy-sdk/src/anoncreds/index.ts | 4 + .../services/IndySdkAnonCredsRegistry.ts | 514 ++++++++++++ .../services/IndySdkHolderService.ts | 327 ++++++++ .../services/IndySdkIssuerService.ts | 129 +++ .../services/IndySdkIssuerServiceMetadata.ts | 3 + .../services/IndySdkRevocationService.ts | 177 ++++ .../services/IndySdkUtilitiesService.ts | 65 ++ .../services/IndySdkVerifierService.ts | 86 ++ .../utils/__tests__/identifiers.test.ts | 48 ++ .../utils/__tests__/transform.test.ts | 114 +++ .../src/anoncreds/utils/identifiers.ts | 41 + .../indy-sdk/src/anoncreds/utils/transform.ts | 153 ++++ .../src/dids/IndySdkSovDidRegistrar.ts | 265 ++++++ .../src/dids/IndySdkSovDidResolver.ts | 95 +++ packages/indy-sdk/src/dids/didSovUtil.ts | 132 +++ packages/indy-sdk/src/dids/index.ts | 7 + packages/indy-sdk/src/error/IndySdkError.ts | 11 + packages/indy-sdk/src/error/index.ts | 2 + packages/indy-sdk/src/error/indyError.ts | 100 +++ packages/indy-sdk/src/index.ts | 26 + packages/indy-sdk/src/ledger/IndySdkPool.ts | 208 +++++ .../indy-sdk/src/ledger/IndySdkPoolService.ts | 338 ++++++++ .../__tests__/IndySdkPoolService.test.ts | 444 ++++++++++ .../src/ledger/__tests__/util.test.ts | 45 ++ .../src/ledger/error/IndySdkPoolError.ts | 7 + .../error/IndySdkPoolNotConfiguredError.ts | 7 + .../ledger/error/IndySdkPoolNotFoundError.ts | 7 + packages/indy-sdk/src/ledger/error/index.ts | 3 + packages/indy-sdk/src/ledger/index.ts | 2 + packages/indy-sdk/src/ledger/util.ts | 9 + .../src/storage/IndySdkStorageService.ts | 324 ++++++++ .../__tests__/IndySdkStorageService.test.ts | 297 +++++++ packages/indy-sdk/src/storage/index.ts | 1 + packages/indy-sdk/src/types.ts | 6 + .../indy-sdk/src/utils/__tests__/did.test.ts | 73 ++ .../indy-sdk/src/utils/assertIndySdkWallet.ts | 13 + packages/indy-sdk/src/utils/did.ts | 89 ++ packages/indy-sdk/src/utils/promises.ts | 44 + packages/indy-sdk/src/wallet/IndySdkWallet.ts | 686 ++++++++++++++++ .../wallet/__tests__/IndySdkWallet.test.ts | 125 +++ packages/indy-sdk/src/wallet/index.ts | 1 + packages/indy-sdk/tsconfig.build.json | 7 + packages/indy-sdk/tsconfig.json | 6 + packages/question-answer/package.json | 6 +- packages/react-native/package.json | 2 +- tests/transport/SubjectOutboundTransport.ts | 2 +- yarn.lock | 761 +++++++++--------- 83 files changed, 5940 insertions(+), 557 deletions(-) delete mode 100644 packages/anoncreds/src/services/AnonCredsRegistry.ts delete mode 100644 packages/anoncreds/src/services/AnonCredsRegistryOptions.ts create mode 100644 packages/anoncreds/src/services/registry/AnonCredsRegistry.ts create mode 100644 packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts create mode 100644 packages/anoncreds/src/services/registry/RevocationListOptions.ts create mode 100644 packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts create mode 100644 packages/anoncreds/src/services/registry/SchemaOptions.ts create mode 100644 packages/anoncreds/src/services/registry/base.ts create mode 100644 packages/anoncreds/src/services/registry/index.ts create mode 100644 packages/core/src/modules/ledger/error/index.ts create mode 100644 packages/indy-sdk/README.md create mode 100644 packages/indy-sdk/jest.config.ts create mode 100644 packages/indy-sdk/package.json create mode 100644 packages/indy-sdk/src/IndySdkModule.ts create mode 100644 packages/indy-sdk/src/IndySdkModuleConfig.ts create mode 100644 packages/indy-sdk/src/anoncreds/index.ts create mode 100644 packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts create mode 100644 packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts create mode 100644 packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts create mode 100644 packages/indy-sdk/src/anoncreds/services/IndySdkIssuerServiceMetadata.ts create mode 100644 packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts create mode 100644 packages/indy-sdk/src/anoncreds/services/IndySdkUtilitiesService.ts create mode 100644 packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts create mode 100644 packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts create mode 100644 packages/indy-sdk/src/anoncreds/utils/__tests__/transform.test.ts create mode 100644 packages/indy-sdk/src/anoncreds/utils/identifiers.ts create mode 100644 packages/indy-sdk/src/anoncreds/utils/transform.ts create mode 100644 packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts create mode 100644 packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts create mode 100644 packages/indy-sdk/src/dids/didSovUtil.ts create mode 100644 packages/indy-sdk/src/dids/index.ts create mode 100644 packages/indy-sdk/src/error/IndySdkError.ts create mode 100644 packages/indy-sdk/src/error/index.ts create mode 100644 packages/indy-sdk/src/error/indyError.ts create mode 100644 packages/indy-sdk/src/index.ts create mode 100644 packages/indy-sdk/src/ledger/IndySdkPool.ts create mode 100644 packages/indy-sdk/src/ledger/IndySdkPoolService.ts create mode 100644 packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts create mode 100644 packages/indy-sdk/src/ledger/__tests__/util.test.ts create mode 100644 packages/indy-sdk/src/ledger/error/IndySdkPoolError.ts create mode 100644 packages/indy-sdk/src/ledger/error/IndySdkPoolNotConfiguredError.ts create mode 100644 packages/indy-sdk/src/ledger/error/IndySdkPoolNotFoundError.ts create mode 100644 packages/indy-sdk/src/ledger/error/index.ts create mode 100644 packages/indy-sdk/src/ledger/index.ts create mode 100644 packages/indy-sdk/src/ledger/util.ts create mode 100644 packages/indy-sdk/src/storage/IndySdkStorageService.ts create mode 100644 packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts create mode 100644 packages/indy-sdk/src/storage/index.ts create mode 100644 packages/indy-sdk/src/types.ts create mode 100644 packages/indy-sdk/src/utils/__tests__/did.test.ts create mode 100644 packages/indy-sdk/src/utils/assertIndySdkWallet.ts create mode 100644 packages/indy-sdk/src/utils/did.ts create mode 100644 packages/indy-sdk/src/utils/promises.ts create mode 100644 packages/indy-sdk/src/wallet/IndySdkWallet.ts create mode 100644 packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts create mode 100644 packages/indy-sdk/src/wallet/index.ts create mode 100644 packages/indy-sdk/tsconfig.build.json create mode 100644 packages/indy-sdk/tsconfig.json diff --git a/packages/action-menu/package.json b/packages/action-menu/package.json index cedf2db5ef..7537700f4c 100644 --- a/packages/action-menu/package.json +++ b/packages/action-menu/package.json @@ -26,13 +26,10 @@ "dependencies": { "class-transformer": "0.5.1", "class-validator": "0.13.1", - "rxjs": "^7.2.0" - }, - "peerDependencies": { - "@aries-framework/core": "0.2.5" + "rxjs": "^7.2.0", + "@aries-framework/core": "0.3.2" }, "devDependencies": { - "@aries-framework/node": "0.3.2", "reflect-metadata": "^0.1.13", "rimraf": "~3.0.2", "typescript": "~4.3.0" diff --git a/packages/anoncreds/package.json b/packages/anoncreds/package.json index 29946c1b20..25033d94a2 100644 --- a/packages/anoncreds/package.json +++ b/packages/anoncreds/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/anoncreds", "main": "build/index", "types": "build/index", - "version": "0.2.5", + "version": "0.3.2", "files": [ "build" ], @@ -25,10 +25,10 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "*" + "@aries-framework/core": "0.3.2" }, - "peerDependencies": {}, "devDependencies": { + "rimraf": "~3.0.2", "typescript": "~4.3.0" } } diff --git a/packages/anoncreds/src/models/exchange.ts b/packages/anoncreds/src/models/exchange.ts index 420c26a158..bd30979a86 100644 --- a/packages/anoncreds/src/models/exchange.ts +++ b/packages/anoncreds/src/models/exchange.ts @@ -90,7 +90,7 @@ export interface AnonCredsProofRequest { string, { name?: string - names?: string + names?: string[] restrictions?: WalletQuery[] non_revoked?: NonRevokedInterval } diff --git a/packages/anoncreds/src/models/internal.ts b/packages/anoncreds/src/models/internal.ts index f0b64d614c..c838dcf865 100644 --- a/packages/anoncreds/src/models/internal.ts +++ b/packages/anoncreds/src/models/internal.ts @@ -5,7 +5,7 @@ export interface CredentialInfo { } schemaId: string credentialDefinitionId: string - revocationRegistryId?: number | undefined + revocationRegistryId?: string | undefined credentialRevocationId?: string | undefined } @@ -13,14 +13,14 @@ export interface RequestedAttribute { credentialId: string timestamp?: number revealed: boolean - credentialInfo?: CredentialInfo + credentialInfo: CredentialInfo revoked?: boolean } export interface RequestedPredicate { credentialId: string timestamp?: number - credentialInfo?: CredentialInfo + credentialInfo: CredentialInfo revoked?: boolean } diff --git a/packages/anoncreds/src/models/registry.ts b/packages/anoncreds/src/models/registry.ts index 98268e1e84..1e5e6d7879 100644 --- a/packages/anoncreds/src/models/registry.ts +++ b/packages/anoncreds/src/models/registry.ts @@ -1,10 +1,12 @@ export interface AnonCredsSchema { + issuerId: string name: string version: string attrNames: string[] } export interface AnonCredsCredentialDefinition { + issuerId: string schemaId: string type: 'CL' tag: string @@ -16,6 +18,7 @@ export interface AnonCredsCredentialDefinition { } export interface AnonCredsRevocationRegistryDefinition { + issuerId: string type: 'CL_ACCUM' credDefId: string tag: string @@ -30,7 +33,7 @@ export interface AnonCredsRevocationRegistryDefinition { } export interface AnonCredsRevocationList { - // TODO: this is a new property, but do we keep abbreviation or not? + issuerId: string revRegId: string revocationList: number[] currentAccumulator: string diff --git a/packages/anoncreds/src/services/AnonCredsHolderService.ts b/packages/anoncreds/src/services/AnonCredsHolderService.ts index 62c7a49aa4..3e287bde00 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderService.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderService.ts @@ -12,11 +12,7 @@ import type { import type { AgentContext } from '@aries-framework/core' export interface AnonCredsHolderService { - createProof( - agentContext: AgentContext, - options: CreateProofOptions, - metadata?: Record - ): Promise + createProof(agentContext: AgentContext, options: CreateProofOptions): Promise storeCredential( agentContext: AgentContext, options: StoreCredentialOptions, @@ -28,13 +24,12 @@ export interface AnonCredsHolderService { createCredentialRequest( agentContext: AgentContext, - options: CreateCredentialRequestOptions, - metadata?: Record + options: CreateCredentialRequestOptions ): Promise deleteCredential(agentContext: AgentContext, credentialId: string): Promise getCredentialsForProofRequest( agentContext: AgentContext, options: GetCredentialsForProofRequestOptions - ): Promise + ): Promise } diff --git a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts index 86cb8bbcf9..3de66df703 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts @@ -28,8 +28,10 @@ export interface CreateProofOptions { credentialDefinitions: { [credentialDefinitionId: string]: AnonCredsCredentialDefinition } - revocationStates: { + revocationRegistries: { [revocationRegistryDefinitionId: string]: { + // tails file MUST already be downloaded on a higher level and stored + tailsFilePath: string definition: AnonCredsRevocationRegistryDefinition revocationLists: { [timestamp: string]: AnonCredsRevocationList @@ -45,8 +47,10 @@ export interface StoreCredentialOptions { credentialDefinition: AnonCredsCredentialDefinition credentialDefinitionId: string credentialId?: string - revocationRegistryDefinition?: AnonCredsRevocationRegistryDefinition - revocationRegistryDefinitionId: string + revocationRegistry?: { + id: string + definition: AnonCredsRevocationRegistryDefinition + } } export interface GetCredentialOptions { @@ -61,10 +65,10 @@ export interface GetCredentialsForProofRequestOptions { extraQuery?: ReferentWalletQuery } -export interface GetCredentialsForProofRequestReturn { +export type GetCredentialsForProofRequestReturn = Array<{ credentialInfo: CredentialInfo interval?: NonRevokedInterval -} +}> export interface CreateCredentialRequestOptions { // TODO: Why is this needed? It is just used as context in Ursa, can be any string. Should we remove it? diff --git a/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts index 27f5450d2f..e3bb8dcdfb 100644 --- a/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts @@ -15,14 +15,11 @@ export interface CreateSchemaOptions { export interface CreateCredentialDefinitionOptions { issuerId: string - tag: string // TODO: this was initially defined as optional, is that the case? + tag: string supportRevocation?: boolean - // If schema doesn't include the id, we need to add it as a separate input parameter - schema: { - value: AnonCredsSchema - id: string - } + schemaId: string + schema: AnonCredsSchema } export interface CreateCredentialOfferOptions { diff --git a/packages/anoncreds/src/services/AnonCredsRegistry.ts b/packages/anoncreds/src/services/AnonCredsRegistry.ts deleted file mode 100644 index ead8f8aa70..0000000000 --- a/packages/anoncreds/src/services/AnonCredsRegistry.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { - AnonCredsCredentialDefinition, - AnonCredsRevocationList, - AnonCredsRevocationRegistryDefinition, - AnonCredsSchema, -} from '../models/registry' -import type { - RegisterCredentialDefinitionOptions, - RegisterCredentialDefinitionReturn, - RegisterRevocationListOptions, - RegisterRevocationListReturn, - RegisterRevocationRegistryDefinitionOptions, - RegisterRevocationRegistryDefinitionReturn, - RegisterSchemaOptions, - RegisterSchemaReturn, -} from './AnonCredsRegistryOptions' -import type { AgentContext } from '@aries-framework/core' - -// This service can be registered multiple times in a single AFJ instance. -// TODO: should all methods have interfaces as input for consistency? -export interface AnonCredsRegistry { - getSchema(agentContext: AgentContext, schemaId: string): Promise - registerSchema(options: RegisterSchemaOptions): Promise - - getCredentialDefinition(credentialDefinitionId: string): Promise - registerCredentialDefinition( - options: RegisterCredentialDefinitionOptions - ): Promise - - getRevocationRegistryDefinition( - revocationRegistryDefinitionId: string - ): Promise - registerRevocationRegistryDefinition( - options: RegisterRevocationRegistryDefinitionOptions - ): Promise - - // TODO: The name of this data model is still tbd. - getRevocationList(revocationRegistryId: string, timestamp: string): Promise - - registerRevocationList(options: RegisterRevocationListOptions): Promise -} diff --git a/packages/anoncreds/src/services/AnonCredsRegistryOptions.ts b/packages/anoncreds/src/services/AnonCredsRegistryOptions.ts deleted file mode 100644 index 206d56fc4d..0000000000 --- a/packages/anoncreds/src/services/AnonCredsRegistryOptions.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { - AnonCredsCredentialDefinition, - AnonCredsRevocationList, - AnonCredsRevocationRegistryDefinition, - AnonCredsSchema, -} from '../models/registry' -import type { AgentContext } from '@aries-framework/core' - -export interface RegisterSchemaOptions { - agentContext: AgentContext - schema: AnonCredsSchema - - // Identifier of issuer that will create the credential definition. - issuerId: string -} -export interface RegisterSchemaReturn { - schemaId: string -} - -export interface RegisterCredentialDefinitionOptions { - credentialDefinition: AnonCredsCredentialDefinition - - // Identifier of issuer that will create the credential definition. - issuerId: string -} - -export interface RegisterCredentialDefinitionReturn { - credentialDefinitionId: string -} - -export interface RegisterRevocationRegistryDefinitionOptions { - revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition -} - -export interface RegisterRevocationRegistryDefinitionReturn { - revocationRegistryDefinitionId: string -} - -export interface RegisterRevocationListOptions { - // Timestamp is often calculated by the ledger, otherwise method should just take current time - // Return type does include the timestamp. - revocationList: Omit -} - -export interface RegisterRevocationListReturn { - timestamp: string -} diff --git a/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts index c67470fd55..f3ecb3b70c 100644 --- a/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts @@ -24,7 +24,7 @@ export interface VerifyProofOptions { // this means we need to retrieve _ALL_ deltas from the ledger to verify a proof. While currently we // only need to fetch the registry. revocationLists: { - [timestamp: string]: AnonCredsRevocationList + [timestamp: number]: AnonCredsRevocationList } } } diff --git a/packages/anoncreds/src/services/index.ts b/packages/anoncreds/src/services/index.ts index 1a612359ed..fe7b176754 100644 --- a/packages/anoncreds/src/services/index.ts +++ b/packages/anoncreds/src/services/index.ts @@ -2,7 +2,6 @@ export * from './AnonCredsHolderService' export * from './AnonCredsHolderServiceOptions' export * from './AnonCredsIssuerService' export * from './AnonCredsIssuerServiceOptions' -export * from './AnonCredsRegistry' -export * from './AnonCredsRegistryOptions' +export * from './registry' export * from './AnonCredsVerifierService' export * from './AnonCredsVerifierServiceOptions' diff --git a/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts b/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts new file mode 100644 index 0000000000..966a1afb95 --- /dev/null +++ b/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts @@ -0,0 +1,48 @@ +import type { + GetCredentialDefinitionReturn, + RegisterCredentialDefinitionOptions, + RegisterCredentialDefinitionReturn, +} from './CredentialDefinitionOptions' +import type { GetRevocationListReturn } from './RevocationListOptions' +import type { GetRevocationRegistryDefinitionReturn } from './RevocationRegistryDefinitionOptions' +import type { GetSchemaReturn, RegisterSchemaOptions, RegisterSchemaReturn } from './SchemaOptions' +import type { AgentContext } from '@aries-framework/core' + +// This service can be registered multiple times in a single AFJ instance. +export interface AnonCredsRegistry { + getSchema(agentContext: AgentContext, schemaId: string): Promise + registerSchema(agentContext: AgentContext, options: RegisterSchemaOptions): Promise + + getCredentialDefinition( + agentContext: AgentContext, + credentialDefinitionId: string + ): Promise + registerCredentialDefinition( + agentContext: AgentContext, + options: RegisterCredentialDefinitionOptions + ): Promise + + getRevocationRegistryDefinition( + agentContext: AgentContext, + revocationRegistryDefinitionId: string + ): Promise + + // TODO: issuance of revocable credentials + // registerRevocationRegistryDefinition( + // agentContext: AgentContext, + // options: RegisterRevocationRegistryDefinitionOptions + // ): Promise + + // TODO: The name of this data model is still tbd. + getRevocationList( + agentContext: AgentContext, + revocationRegistryId: string, + timestamp: number + ): Promise + + // TODO: issuance of revocable credentials + // registerRevocationList( + // agentContext: AgentContext, + // options: RegisterRevocationListOptions + // ): Promise +} diff --git a/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts b/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts new file mode 100644 index 0000000000..e2f7e14298 --- /dev/null +++ b/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts @@ -0,0 +1,45 @@ +import type { AnonCredsCredentialDefinition } from '../../models/registry' +import type { + AnonCredsResolutionMetadata, + Extensible, + AnonCredsOperationStateFailed, + AnonCredsOperationStateFinished, + AnonCredsOperationState, +} from './base' + +export interface GetCredentialDefinitionReturn { + credentialDefinition: AnonCredsCredentialDefinition | null + credentialDefinitionId: string + resolutionMetadata: AnonCredsResolutionMetadata + credentialDefinitionMetadata: Extensible +} + +export interface RegisterCredentialDefinitionOptions { + credentialDefinition: AnonCredsCredentialDefinition + options: Extensible +} + +export interface RegisterCredentialDefinitionReturnStateFailed extends AnonCredsOperationStateFailed { + credentialDefinition: AnonCredsCredentialDefinition + credentialDefinitionId?: string +} + +export interface RegisterCredentialDefinitionReturnStateFinished extends AnonCredsOperationStateFinished { + credentialDefinition: AnonCredsCredentialDefinition + credentialDefinitionId?: string +} + +export interface RegisterCredentialDefinitionReturnState extends AnonCredsOperationState { + credentialDefinition: AnonCredsCredentialDefinition + credentialDefinitionId?: string +} + +export interface RegisterCredentialDefinitionReturn { + jobId?: string + credentialDefinitionState: + | RegisterCredentialDefinitionReturnState + | RegisterCredentialDefinitionReturnStateFinished + | RegisterCredentialDefinitionReturnStateFailed + credentialDefinitionMetadata: Extensible + registrationMetadata: AnonCredsResolutionMetadata +} diff --git a/packages/anoncreds/src/services/registry/RevocationListOptions.ts b/packages/anoncreds/src/services/registry/RevocationListOptions.ts new file mode 100644 index 0000000000..aea0c5d5b9 --- /dev/null +++ b/packages/anoncreds/src/services/registry/RevocationListOptions.ts @@ -0,0 +1,18 @@ +import type { AnonCredsRevocationList } from '../../models/registry' +import type { AnonCredsResolutionMetadata, Extensible } from './base' + +export interface GetRevocationListReturn { + revocationList: AnonCredsRevocationList | null + resolutionMetadata: AnonCredsResolutionMetadata + revocationListMetadata: Extensible +} + +// TODO: Support for issuance of revocable credentials +// export interface RegisterRevocationListOptions { +// // Timestamp is often calculated by the ledger, otherwise method should just take current time +// // Return type does include the timestamp. +// revocationList: Omit +// } +// export interface RegisterRevocationListReturn { +// timestamp: string +// } diff --git a/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts b/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts new file mode 100644 index 0000000000..5e63f79995 --- /dev/null +++ b/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts @@ -0,0 +1,18 @@ +import type { AnonCredsRevocationRegistryDefinition } from '../../models/registry' +import type { AnonCredsResolutionMetadata, Extensible } from './base' + +export interface GetRevocationRegistryDefinitionReturn { + revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition | null + revocationRegistryDefinitionId: string + resolutionMetadata: AnonCredsResolutionMetadata + revocationRegistryDefinitionMetadata: Extensible +} + +// TODO: Support for issuance of revocable credentials +// export interface RegisterRevocationRegistryDefinitionOptions { +// revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition +// } + +// export interface RegisterRevocationRegistryDefinitionReturn { +// revocationRegistryDefinitionId: string +// } diff --git a/packages/anoncreds/src/services/registry/SchemaOptions.ts b/packages/anoncreds/src/services/registry/SchemaOptions.ts new file mode 100644 index 0000000000..f4d706223a --- /dev/null +++ b/packages/anoncreds/src/services/registry/SchemaOptions.ts @@ -0,0 +1,46 @@ +import type { AnonCredsSchema } from '../../models/registry' +import type { + AnonCredsResolutionMetadata, + Extensible, + AnonCredsOperationStateFailed, + AnonCredsOperationStateFinished, + AnonCredsOperationState, +} from './base' + +// Get Schema +export interface GetSchemaReturn { + schema: AnonCredsSchema | null + schemaId: string + // Can contain e.g. the ledger transaction request/response + resolutionMetadata: AnonCredsResolutionMetadata + // Can contain additional fields + schemaMetadata: Extensible +} + +// +export interface RegisterSchemaOptions { + schema: AnonCredsSchema + options: Extensible +} + +export interface RegisterSchemaReturnStateFailed extends AnonCredsOperationStateFailed { + schema: AnonCredsSchema + schemaId?: string +} + +export interface RegisterSchemaReturnStateFinished extends AnonCredsOperationStateFinished { + schema: AnonCredsSchema + schemaId: string +} + +export interface RegisterSchemaReturnState extends AnonCredsOperationState { + schema: AnonCredsSchema + schemaId?: string +} + +export interface RegisterSchemaReturn { + jobId?: string + schemaState: RegisterSchemaReturnState | RegisterSchemaReturnStateFinished | RegisterSchemaReturnStateFailed + schemaMetadata: Extensible + registrationMetadata: Extensible +} diff --git a/packages/anoncreds/src/services/registry/base.ts b/packages/anoncreds/src/services/registry/base.ts new file mode 100644 index 0000000000..af9b52ee43 --- /dev/null +++ b/packages/anoncreds/src/services/registry/base.ts @@ -0,0 +1,19 @@ +export type Extensible = Record + +export interface AnonCredsOperationState { + state: 'action' | 'wait' +} + +export interface AnonCredsOperationStateFinished { + state: 'finished' +} + +export interface AnonCredsOperationStateFailed { + state: 'failed' + reason: string +} + +export interface AnonCredsResolutionMetadata extends Extensible { + error?: 'invalid' | 'notFound' | 'unsupportedAnonCredsMethod' | string + message?: string +} diff --git a/packages/anoncreds/src/services/registry/index.ts b/packages/anoncreds/src/services/registry/index.ts new file mode 100644 index 0000000000..5d36ce3dd9 --- /dev/null +++ b/packages/anoncreds/src/services/registry/index.ts @@ -0,0 +1,6 @@ +export * from './AnonCredsRegistry' +export * from './CredentialDefinitionOptions' +export * from './SchemaOptions' +export * from './RevocationRegistryDefinitionOptions' +export * from './RevocationListOptions' +export { AnonCredsResolutionMetadata } from './base' diff --git a/packages/core/package.json b/packages/core/package.json index 3325039d25..cd0e119a36 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -30,7 +30,7 @@ "@stablelib/ed25519": "^1.0.2", "@stablelib/random": "^1.0.1", "@stablelib/sha256": "^1.0.1", - "@types/indy-sdk": "^1.16.21", + "@types/indy-sdk": "1.16.24", "@types/node-fetch": "^2.5.10", "@types/ws": "^7.4.6", "abort-controller": "^3.0.0", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f551cb8d59..5b2eaf1762 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -24,6 +24,8 @@ export type { JsonArray, JsonObject, JsonValue, + WalletConfigRekey, + WalletExportImportConfig, } from './types' export { DidCommMimeType, KeyDerivationMethod } from './types' export type { FileSystem } from './storage/FileSystem' @@ -31,7 +33,7 @@ export * from './storage/BaseRecord' export { InMemoryMessageRepository } from './storage/InMemoryMessageRepository' export { Repository } from './storage/Repository' export * from './storage/RepositoryEvents' -export { StorageService, Query } from './storage/StorageService' +export { StorageService, Query, BaseRecordConstructor } from './storage/StorageService' export * from './storage/migration' export { getDirFromFilePath } from './utils/path' export { InjectionSymbols } from './constants' @@ -63,6 +65,7 @@ export { parseMessageType, IsValidMessageType } from './utils/messageType' export type { Constructor } from './utils/mixins' export * from './agent/Events' export * from './crypto/' +export { PersistedLruCache, CacheRepository } from './cache' import { parseInvitationUrl } from './utils/parseInvitation' import { uuid } from './utils/uuid' diff --git a/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts index ebc32c4785..90b6db58c0 100644 --- a/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts +++ b/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts @@ -67,7 +67,11 @@ const revDef: RevocRegDef = { maxCredNum: 33, tailsHash: 'd', tailsLocation: 'x', - publicKeys: ['x'], + publicKeys: { + accumKey: { + z: 'x', + }, + }, }, ver: 't', } diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts index 3870b22bb3..a9bf76f0f5 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts @@ -310,7 +310,17 @@ describe('credentials', () => { const aliceExchangeCredentialRecord = await aliceAgent.credentials.negotiateOffer({ credentialRecordId: aliceCredentialRecord.id, credentialFormats: { - jsonld: signCredentialOptions, + // Send a different object + jsonld: { + ...signCredentialOptions, + credential: { + ...signCredentialOptions.credential, + credentialSubject: { + ...signCredentialOptions.credential.credentialSubject, + name: 'Different Property', + }, + }, + }, }, comment: 'v2 propose credential test', }) @@ -328,6 +338,7 @@ describe('credentials', () => { aliceCredentialRecord = await aliceAgent.credentials.getById(aliceCredentialRecord.id) aliceCredentialRecord.assertState(CredentialState.ProposalSent) }) + test('Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ @@ -348,7 +359,17 @@ describe('credentials', () => { await faberAgent.credentials.negotiateProposal({ credentialRecordId: faberCredentialRecord.id, credentialFormats: { - jsonld: signCredentialOptions, + // Send a different object + jsonld: { + ...signCredentialOptions, + credential: { + ...signCredentialOptions.credential, + credentialSubject: { + ...signCredentialOptions.credential.credentialSubject, + name: 'Different Property', + }, + }, + }, }, }) diff --git a/packages/core/src/modules/indy/services/IndyRevocationService.ts b/packages/core/src/modules/indy/services/IndyRevocationService.ts index 52c666d3ca..4dd89c2e4d 100644 --- a/packages/core/src/modules/indy/services/IndyRevocationService.ts +++ b/packages/core/src/modules/indy/services/IndyRevocationService.ts @@ -1,7 +1,7 @@ import type { AgentContext } from '../../../agent' import type { IndyRevocationInterval } from '../../credentials' import type { RequestedCredentials } from '../../proofs/formats/indy/models/RequestedCredentials' -import type { default as Indy } from 'indy-sdk' +import type { default as Indy, RevState, RevStates } from 'indy-sdk' import { AgentDependencies } from '../../../agent/AgentDependencies' import { InjectionSymbols } from '../../../constants' @@ -48,7 +48,7 @@ export class IndyRevocationService { proofRequest, requestedCredentials, }) - const revocationStates: Indy.RevStates = {} + const revocationStates: RevStates = {} const referentCredentials = [] //Retrieve information for referents and push to single array diff --git a/packages/core/src/modules/indy/services/IndyVerifierService.ts b/packages/core/src/modules/indy/services/IndyVerifierService.ts index b6cc387c31..c6ad15bb77 100644 --- a/packages/core/src/modules/indy/services/IndyVerifierService.ts +++ b/packages/core/src/modules/indy/services/IndyVerifierService.ts @@ -26,7 +26,7 @@ export class IndyVerifierService { { proofRequest, proof, schemas, credentialDefinitions }: VerifyProofOptions ): Promise { try { - const { revocationRegistryDefinitions, revocationRegistryStates } = await this.getRevocationRegistries( + const { revocationRegistryDefinitions, revocationRegistries } = await this.getRevocationRegistries( agentContext, proof ) @@ -37,7 +37,7 @@ export class IndyVerifierService { schemas, credentialDefinitions, revocationRegistryDefinitions, - revocationRegistryStates + revocationRegistries ) } catch (error) { throw isIndyError(error) ? new IndySdkError(error) : error @@ -46,7 +46,7 @@ export class IndyVerifierService { private async getRevocationRegistries(agentContext: AgentContext, proof: Indy.IndyProof) { const revocationRegistryDefinitions: Indy.RevocRegDefs = {} - const revocationRegistryStates: Indy.RevStates = Object.create(null) + const revocationRegistries: Indy.RevRegs = Object.create(null) for (const identifier of proof.identifiers) { const revocationRegistryId = identifier.rev_reg_id const timestamp = identifier.timestamp @@ -61,19 +61,19 @@ export class IndyVerifierService { } //Fetch Revocation Registry by Timestamp if not already fetched - if (revocationRegistryId && timestamp && !revocationRegistryStates[revocationRegistryId]?.[timestamp]) { - if (!revocationRegistryStates[revocationRegistryId]) { - revocationRegistryStates[revocationRegistryId] = Object.create(null) + if (revocationRegistryId && timestamp && !revocationRegistries[revocationRegistryId]?.[timestamp]) { + if (!revocationRegistries[revocationRegistryId]) { + revocationRegistries[revocationRegistryId] = Object.create(null) } const { revocationRegistry } = await this.ledgerService.getRevocationRegistry( agentContext, revocationRegistryId, timestamp ) - revocationRegistryStates[revocationRegistryId][timestamp] = revocationRegistry + revocationRegistries[revocationRegistryId][timestamp] = revocationRegistry } } - return { revocationRegistryDefinitions, revocationRegistryStates } + return { revocationRegistryDefinitions, revocationRegistries } } } diff --git a/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts b/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts index 5ca80f5fcd..cf7b35bbc5 100644 --- a/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts +++ b/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts @@ -77,7 +77,11 @@ const revocRegDef: Indy.RevocRegDef = { maxCredNum: 3, tailsHash: 'abcde', tailsLocation: 'xyz', - publicKeys: ['abcde', 'fghijk'], + publicKeys: { + accumKey: { + z: 'z', + }, + }, }, ver: 'abcde', } diff --git a/packages/core/src/modules/ledger/error/index.ts b/packages/core/src/modules/ledger/error/index.ts new file mode 100644 index 0000000000..79c42fc2b6 --- /dev/null +++ b/packages/core/src/modules/ledger/error/index.ts @@ -0,0 +1,3 @@ +export * from './LedgerError' +export * from './LedgerNotConfiguredError' +export * from './LedgerNotFoundError' diff --git a/packages/core/src/modules/routing/__tests__/pickup.test.ts b/packages/core/src/modules/routing/__tests__/pickup.test.ts index 69d43e8c46..8b33fcacf2 100644 --- a/packages/core/src/modules/routing/__tests__/pickup.test.ts +++ b/packages/core/src/modules/routing/__tests__/pickup.test.ts @@ -5,7 +5,7 @@ import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' -import { getAgentOptions, waitForBasicMessage } from '../../../../tests/helpers' +import { getAgentOptions, waitForBasicMessage, waitForTrustPingReceivedEvent } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' @@ -16,7 +16,7 @@ const recipientOptions = getAgentOptions('Mediation: Recipient Pickup', { }) const mediatorOptions = getAgentOptions('Mediation: Mediator Pickup', { autoAcceptConnections: true, - endpoints: ['rxjs:mediator'], + endpoints: ['wss://mediator'], indyLedgers: [], }) @@ -25,17 +25,17 @@ describe('E2E Pick Up protocol', () => { let mediatorAgent: Agent afterEach(async () => { - await recipientAgent?.shutdown() - await recipientAgent?.wallet.delete() - await mediatorAgent?.shutdown() - await mediatorAgent?.wallet.delete() + await recipientAgent.shutdown() + await recipientAgent.wallet.delete() + await mediatorAgent.shutdown() + await mediatorAgent.wallet.delete() }) test('E2E Pick Up V1 protocol', async () => { const mediatorMessages = new Subject() const subjectMap = { - 'rxjs:mediator': mediatorMessages, + 'wss://mediator': mediatorMessages, } // Initialize mediatorReceived message @@ -74,7 +74,7 @@ describe('E2E Pick Up protocol', () => { const message = 'hello pickup V1' await mediatorAgent.basicMessages.sendMessage(mediatorRecipientConnection.id, message) - await recipientAgent.mediationRecipient.pickupMessages(recipientMediatorConnection) + await recipientAgent.mediationRecipient.pickupMessages(recipientMediatorConnection, MediatorPickupStrategy.PickUpV1) const basicMessage = await waitForBasicMessage(recipientAgent, { content: message, @@ -86,8 +86,12 @@ describe('E2E Pick Up protocol', () => { test('E2E Pick Up V2 protocol', async () => { const mediatorMessages = new Subject() + // FIXME: we harcoded that pickup of messages MUST be using ws(s) scheme when doing implicit pickup + // For liver delivery we need a duplex transport. however that means we can't test it with the subject transport. Using wss here to 'hack' this. We should + // extend the API to allow custom schemes (or maybe add a `supportsDuplex` transport / `supportMultiReturnMessages`) + // For pickup v2 pickup message (which we're testing here) we could just as well use `http` as it is just request/response. const subjectMap = { - 'rxjs:mediator': mediatorMessages, + 'wss://mediator': mediatorMessages, } // Initialize mediatorReceived message @@ -124,14 +128,20 @@ describe('E2E Pick Up protocol', () => { mediatorRecipientConnection = await mediatorAgent.connections.returnWhenIsConnected(mediatorRecipientConnection!.id) const message = 'hello pickup V2' - await mediatorAgent.basicMessages.sendMessage(mediatorRecipientConnection.id, message) - await recipientAgent.mediationRecipient.pickupMessages(recipientMediatorConnection, MediatorPickupStrategy.PickUpV2) + await mediatorAgent.basicMessages.sendMessage(mediatorRecipientConnection.id, message) - const basicMessage = await waitForBasicMessage(recipientAgent, { + const basicMessagePromise = waitForBasicMessage(recipientAgent, { content: message, }) + const trustPingPromise = waitForTrustPingReceivedEvent(mediatorAgent, {}) + await recipientAgent.mediationRecipient.pickupMessages(recipientMediatorConnection, MediatorPickupStrategy.PickUpV2) + const basicMessage = await basicMessagePromise expect(basicMessage.content).toBe(message) + + // Wait for trust ping to be received and stop message pickup + await trustPingPromise + await recipientAgent.mediationRecipient.stopMessagePickup() }) }) diff --git a/packages/core/src/modules/vc/__tests__/documentLoader.ts b/packages/core/src/modules/vc/__tests__/documentLoader.ts index 29c47d7d91..544ae02972 100644 --- a/packages/core/src/modules/vc/__tests__/documentLoader.ts +++ b/packages/core/src/modules/vc/__tests__/documentLoader.ts @@ -32,35 +32,45 @@ export const DOCUMENTS = { [DID_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV['id']]: DID_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV, [DID_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa[ 'id' - ]]: DID_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa, + ]]: + DID_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa, [DID_EXAMPLE_48939859['id']]: DID_EXAMPLE_48939859, [DID_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh[ 'id' - ]]: DID_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh, + ]]: + DID_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh, [DID_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn[ 'id' - ]]: DID_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn, + ]]: + DID_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn, [DID_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ[ 'id' - ]]: DID_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ, + ]]: + DID_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ, [DID_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox[ 'id' - ]]: DID_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox, + ]]: + DID_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox, [DID_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F[ 'id' - ]]: DID_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F, + ]]: + DID_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F, [DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4[ 'id' - ]]: DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4, + ]]: + DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4, [DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4[ 'id' - ]]: DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4, + ]]: + DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4, [DID_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD[ 'id' - ]]: DID_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD, + ]]: + DID_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD, [DID_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN[ 'id' - ]]: DID_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN, + ]]: + DID_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN, [DID_SOV_QqEfJxe752NCmWqR5TssZ5['id']]: DID_SOV_QqEfJxe752NCmWqR5TssZ5, SECURITY_CONTEXT_V1_URL: SECURITY_V1, SECURITY_CONTEXT_V2_URL: SECURITY_V2, diff --git a/packages/core/src/utils/validators.ts b/packages/core/src/utils/validators.ts index 8e7240b5f2..57b1a1ec17 100644 --- a/packages/core/src/utils/validators.ts +++ b/packages/core/src/utils/validators.ts @@ -69,7 +69,7 @@ export const UriValidator = /\w+:(\/?\/?)[^\s]+/ export function IsUri(validationOptions?: ValidationOptions): PropertyDecorator { return ValidateBy( { - name: 'isInstanceOrArrayOfInstances', + name: 'isUri', validator: { validate: (value): boolean => { return UriValidator.test(value) diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 7354fbe5a4..9e57c21943 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -8,7 +8,6 @@ import type { ConnectionRecordProps, CredentialDefinitionTemplate, CredentialStateChangedEvent, - TrustPingResponseReceivedEvent, InitConfig, InjectionToken, ProofStateChangedEvent, @@ -16,6 +15,7 @@ import type { Wallet, } from '../src' import type { AgentModulesInput, EmptyModuleMap } from '../src/agent/AgentModules' +import type { TrustPingReceivedEvent, TrustPingResponseReceivedEvent } from '../src/modules/connections/TrustPingEvents' import type { IndyOfferCredentialFormat } from '../src/modules/credentials/formats/indy/IndyCredentialFormat' import type { ProofAttributeInfo, ProofPredicateInfo } from '../src/modules/proofs/formats/indy/models' import type { AutoAcceptProof } from '../src/modules/proofs/models/ProofAutoAcceptType' @@ -73,6 +73,7 @@ import { PresentationPreviewPredicate, } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' import { customDocumentLoader } from '../src/modules/vc/__tests__/documentLoader' +import { KeyDerivationMethod } from '../src/types' import { LinkedAttachment } from '../src/utils/LinkedAttachment' import { uuid } from '../src/utils/uuid' @@ -96,7 +97,8 @@ export function getAgentOptions { throw new Error( - `ProofStateChangedEvent event not emitted within specified timeout: { + `ProofStateChangedEvent event not emitted within specified timeout: ${timeoutMs} previousState: ${previousState}, threadId: ${threadId}, parentThreadId: ${parentThreadId}, @@ -242,13 +244,49 @@ export function waitForProofExchangeRecordSubject( ) } +export async function waitForTrustPingReceivedEvent( + agent: Agent, + options: { + threadId?: string + timeoutMs?: number + } +) { + const observable = agent.events.observable(TrustPingEventTypes.TrustPingReceivedEvent) + + return waitForTrustPingReceivedEventSubject(observable, options) +} + +export function waitForTrustPingReceivedEventSubject( + subject: ReplaySubject | Observable, + { + 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( + `TrustPingReceivedEvent event not emitted within specified timeout: ${timeoutMs} + threadId: ${threadId}, +}` + ) + }), + map((e) => e.payload.message) + ) + ) +} + export async function waitForTrustPingResponseReceivedEvent( agent: Agent, options: { threadId?: string - parentThreadId?: string - state?: ProofState - previousState?: ProofState | null timeoutMs?: number } ) { @@ -276,7 +314,7 @@ export function waitForTrustPingResponseReceivedEventSubject( timeout(timeoutMs), catchError(() => { throw new Error( - `TrustPingResponseReceivedEvent event not emitted within specified timeout: { + `TrustPingResponseReceivedEvent event not emitted within specified timeout: ${timeoutMs} threadId: ${threadId}, }` ) diff --git a/packages/indy-sdk/README.md b/packages/indy-sdk/README.md new file mode 100644 index 0000000000..368d25db71 --- /dev/null +++ b/packages/indy-sdk/README.md @@ -0,0 +1,31 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript IndySDK Module

+

+ License + typescript + @aries-framework/indy-sdk version + +

+
+ +IndySDK module for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). diff --git a/packages/indy-sdk/jest.config.ts b/packages/indy-sdk/jest.config.ts new file mode 100644 index 0000000000..c7c5196637 --- /dev/null +++ b/packages/indy-sdk/jest.config.ts @@ -0,0 +1,14 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + name: packageJson.name, + displayName: packageJson.name, + // setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/packages/indy-sdk/package.json b/packages/indy-sdk/package.json new file mode 100644 index 0000000000..4c19732005 --- /dev/null +++ b/packages/indy-sdk/package.json @@ -0,0 +1,40 @@ +{ + "name": "@aries-framework/indy-sdk", + "main": "build/index", + "types": "build/index", + "version": "0.2.5", + "private": true, + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/indy-sdk", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/indy-sdk" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf -rf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@aries-framework/anoncreds": "0.3.2", + "@aries-framework/core": "0.3.2", + "@types/indy-sdk": "1.16.24", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", + "rxjs": "^7.2.0", + "tsyringe": "^4.7.0" + }, + "devDependencies": { + "rimraf": "~3.0.2", + "typescript": "~4.3.0" + } +} diff --git a/packages/indy-sdk/src/IndySdkModule.ts b/packages/indy-sdk/src/IndySdkModule.ts new file mode 100644 index 0000000000..ea3baa5a9a --- /dev/null +++ b/packages/indy-sdk/src/IndySdkModule.ts @@ -0,0 +1,17 @@ +import type { IndySdkModuleConfigOptions } from './IndySdkModuleConfig' +import type { DependencyManager, Module } from '@aries-framework/core' + +import { IndySdkModuleConfig } from './IndySdkModuleConfig' +import { IndySdkSymbol } from './types' + +export class IndySdkModule implements Module { + public readonly config: IndySdkModuleConfig + + public constructor(config: IndySdkModuleConfigOptions) { + this.config = new IndySdkModuleConfig(config) + } + + public register(dependencyManager: DependencyManager) { + dependencyManager.registerInstance(IndySdkSymbol, this.config.indySdk) + } +} diff --git a/packages/indy-sdk/src/IndySdkModuleConfig.ts b/packages/indy-sdk/src/IndySdkModuleConfig.ts new file mode 100644 index 0000000000..a01bf813b3 --- /dev/null +++ b/packages/indy-sdk/src/IndySdkModuleConfig.ts @@ -0,0 +1,45 @@ +import type * as IndySdk from 'indy-sdk' + +/** + * IndySdkModuleConfigOptions defines the interface for the options of the IndySdkModuleConfig class. + */ +export interface IndySdkModuleConfigOptions { + /** + * Implementation of the IndySdk interface according to the @types/indy-sdk package. + * + * + * ## Node.JS + * + * ```ts + * import * as indySdk from 'indy-sdk' + * + * const indySdkModule = new IndySdkModule({ + * indySdk + * }) + * ``` + * + * ## React Native + * + * ```ts + * import * as indySdk from 'indy-sdk-react-native' + * + * const indySdkModule = new IndySdkModule({ + * indySdk + * }) + * ``` + */ + indySdk: typeof IndySdk +} + +export class IndySdkModuleConfig { + private options: IndySdkModuleConfigOptions + + public constructor(options: IndySdkModuleConfigOptions) { + this.options = options + } + + /** See {@link IndySdkModuleConfigOptions.resolvers} */ + public get indySdk() { + return this.options.indySdk + } +} diff --git a/packages/indy-sdk/src/anoncreds/index.ts b/packages/indy-sdk/src/anoncreds/index.ts new file mode 100644 index 0000000000..adba521ce0 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/index.ts @@ -0,0 +1,4 @@ +export { IndySdkAnonCredsRegistry } from './services/IndySdkAnonCredsRegistry' +export { IndySdkHolderService } from './services/IndySdkHolderService' +export { IndySdkIssuerService } from './services/IndySdkIssuerService' +export { IndySdkVerifierService } from './services/IndySdkVerifierService' diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts new file mode 100644 index 0000000000..3b5c6a08ce --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts @@ -0,0 +1,514 @@ +import type { + AnonCredsRegistry, + GetCredentialDefinitionReturn, + GetRevocationListReturn, + GetRevocationRegistryDefinitionReturn, + GetSchemaReturn, + RegisterCredentialDefinitionOptions, + RegisterCredentialDefinitionReturn, + RegisterSchemaOptions, + RegisterSchemaReturn, +} from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' +import type { Schema as IndySdkSchema } from 'indy-sdk' + +import { inject } from '@aries-framework/core' + +import { IndySdkError, isIndyError } from '../../error' +import { IndySdkPoolService } from '../../ledger' +import { IndySdk, IndySdkSymbol } from '../../types' +import { + didFromCredentialDefinitionId, + didFromRevocationRegistryDefinitionId, + didFromSchemaId, + getLegacyCredentialDefinitionId, + getLegacySchemaId, +} from '../utils/identifiers' +import { + anonCredsRevocationListFromIndySdk, + anonCredsRevocationRegistryDefinitionFromIndySdk, +} from '../utils/transform' + +/** + * TODO: validation of the identifiers. The Indy SDK classes only support the legacy (unqualified) identifiers. + */ +export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { + private indySdk: IndySdk + private indySdkPoolService: IndySdkPoolService + + public constructor(@inject(IndySdkSymbol) indySdk: IndySdk, indySdkPoolService: IndySdkPoolService) { + this.indySdk = indySdk + this.indySdkPoolService = indySdkPoolService + } + + public async getSchema(agentContext: AgentContext, schemaId: string): Promise { + try { + const did = didFromSchemaId(schemaId) + const { pool } = await this.indySdkPoolService.getPoolForDid(agentContext, did) + agentContext.config.logger.debug(`Getting schema '${schemaId}' from ledger '${pool.didIndyNamespace}'`) + + const request = await this.indySdk.buildGetSchemaRequest(null, schemaId) + + agentContext.config.logger.trace( + `Submitting get schema request for schema '${schemaId}' to ledger '${pool.didIndyNamespace}'` + ) + const response = await this.indySdkPoolService.submitReadRequest(pool, request) + + agentContext.config.logger.trace(`Got un-parsed schema '${schemaId}' from ledger '${pool.didIndyNamespace}'`, { + response, + }) + + const [, schema] = await this.indySdk.parseGetSchemaResponse(response) + agentContext.config.logger.debug(`Got schema '${schemaId}' from ledger '${pool.didIndyNamespace}'`, { + schema, + }) + + const issuerId = didFromSchemaId(schema.id) + + return { + schema: { + attrNames: schema.attrNames, + name: schema.name, + version: schema.version, + issuerId: issuerId, + }, + schemaId: schema.id, + resolutionMetadata: { + didIndyNamespace: pool.didIndyNamespace, + // NOTE: the seqNo is required by the indy-sdk even though not present in AnonCreds v1. + // For this reason we return it in the metadata. + indyLedgerSeqNo: schema.seqNo, + }, + schemaMetadata: {}, + } + } catch (error) { + agentContext.config.logger.error(`Error retrieving schema '${schemaId}'`, { + error, + schemaId, + }) + + return { + schema: null, + schemaId, + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve credential definition: ${error.message}`, + }, + schemaMetadata: {}, + } + } + } + + public async registerSchema( + agentContext: AgentContext, + options: IndySdkRegisterSchemaOptions + ): Promise { + // Make sure didIndyNamespace is passed + if (!options.options.didIndyNamespace) { + return { + schemaMetadata: {}, + registrationMetadata: {}, + schemaState: { + reason: 'no didIndyNamespace defined in the options. didIndyNamespace is required when using the Indy SDK', + schema: options.schema, + state: 'failed', + }, + } + } + + try { + const pool = this.indySdkPoolService.getPoolForNamespace(options.options.didIndyNamespace) + agentContext.config.logger.debug( + `Register schema on ledger '${pool.didIndyNamespace}' with did '${options.schema.issuerId}'`, + options.schema + ) + + const schema = { + attrNames: options.schema.attrNames, + name: options.schema.name, + version: options.schema.version, + id: getLegacySchemaId(options.schema.issuerId, options.schema.name, options.schema.version), + ver: '1.0', + // Casted as because the type expect a seqNo, but that's not actually required for the input of + // buildSchemaRequest (seqNo is not yet known) + } as IndySdkSchema + + const request = await this.indySdk.buildSchemaRequest(options.schema.issuerId, schema) + + const response = await this.indySdkPoolService.submitWriteRequest( + agentContext, + pool, + request, + options.schema.issuerId + ) + agentContext.config.logger.debug(`Registered schema '${schema.id}' on ledger '${pool.didIndyNamespace}'`, { + response, + schema, + }) + + return { + schemaState: { + state: 'finished', + schema: { + attrNames: schema.attrNames, + issuerId: options.schema.issuerId, + name: schema.name, + version: schema.version, + }, + schemaId: schema.id, + }, + registrationMetadata: { + // NOTE: the seqNo is required by the indy-sdk even though not present in AnonCreds v1. + // For this reason we return it in the metadata. + indyLedgerSeqNo: schema.seqNo, + didIndyNamespace: pool.didIndyNamespace, + }, + schemaMetadata: {}, + } + } catch (error) { + agentContext.config.logger.error(`Error registering schema for did '${options.schema.issuerId}'`, { + error, + did: options.schema.issuerId, + schema: options.schema, + }) + + return { + schemaMetadata: {}, + registrationMetadata: {}, + schemaState: { + state: 'failed', + schema: options.schema, + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async getCredentialDefinition( + agentContext: AgentContext, + credentialDefinitionId: string + ): Promise { + try { + const did = didFromCredentialDefinitionId(credentialDefinitionId) + const { pool } = await this.indySdkPoolService.getPoolForDid(agentContext, did) + + agentContext.config.logger.debug( + `Using ledger '${pool.didIndyNamespace}' to retrieve credential definition '${credentialDefinitionId}'` + ) + const request = await this.indySdk.buildGetCredDefRequest(null, credentialDefinitionId) + + agentContext.config.logger.trace( + `Submitting get credential definition request for credential definition '${credentialDefinitionId}' to ledger '${pool.didIndyNamespace}'` + ) + + const response = await this.indySdkPoolService.submitReadRequest(pool, request) + agentContext.config.logger.trace( + `Got un-parsed credential definition '${credentialDefinitionId}' from ledger '${pool.didIndyNamespace}'`, + { + response, + } + ) + + const [, credentialDefinition] = await this.indySdk.parseGetCredDefResponse(response) + agentContext.config.logger.debug( + `Got credential definition '${credentialDefinitionId}' from ledger '${pool.didIndyNamespace}'`, + { + credentialDefinition, + } + ) + + return { + credentialDefinitionId: credentialDefinition.id, + credentialDefinition: { + issuerId: didFromCredentialDefinitionId(credentialDefinition.id), + schemaId: credentialDefinition.schemaId, + tag: credentialDefinition.tag, + type: 'CL', + value: credentialDefinition.value, + }, + credentialDefinitionMetadata: {}, + resolutionMetadata: { + didIndyNamespace: pool.didIndyNamespace, + }, + } + } catch (error) { + agentContext.config.logger.error(`Error retrieving credential definition '${credentialDefinitionId}'`, { + error, + credentialDefinitionId, + }) + + return { + credentialDefinitionId, + credentialDefinition: null, + credentialDefinitionMetadata: {}, + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve credential definition: ${error.message}`, + }, + } + } + } + + public async registerCredentialDefinition( + agentContext: AgentContext, + options: IndySdkRegisterCredentialDefinitionOptions + ): Promise { + // Make sure didIndyNamespace is passed + if (!options.options.didIndyNamespace) { + return { + credentialDefinitionMetadata: {}, + registrationMetadata: {}, + credentialDefinitionState: { + reason: 'no didIndyNamespace defined in the options. didIndyNamespace is required when using the Indy SDK', + credentialDefinition: options.credentialDefinition, + state: 'failed', + }, + } + } + + try { + const pool = this.indySdkPoolService.getPoolForNamespace(options.options.didIndyNamespace) + agentContext.config.logger.debug( + `Registering credential definition on ledger '${pool.didIndyNamespace}' with did '${options.credentialDefinition.issuerId}'`, + options.credentialDefinition + ) + + // TODO: this will bypass caching if done on a higher level. + const { schema, resolutionMetadata } = await this.getSchema(agentContext, options.credentialDefinition.schemaId) + + if (!schema || !resolutionMetadata.indyLedgerSeqNo || typeof resolutionMetadata.indyLedgerSeqNo !== 'number') { + return { + registrationMetadata: { + didIndyNamespace: pool.didIndyNamespace, + }, + credentialDefinitionMetadata: {}, + credentialDefinitionState: { + credentialDefinition: options.credentialDefinition, + state: 'failed', + reason: `error resolving schema with id ${options.credentialDefinition.schemaId}: ${resolutionMetadata.error} ${resolutionMetadata.message}`, + }, + } + } + + const credentialDefinitionId = getLegacyCredentialDefinitionId( + options.credentialDefinition.issuerId, + resolutionMetadata.indyLedgerSeqNo, + options.credentialDefinition.tag + ) + + const request = await this.indySdk.buildCredDefRequest(options.credentialDefinition.issuerId, { + id: credentialDefinitionId, + schemaId: options.credentialDefinition.schemaId, + tag: options.credentialDefinition.tag, + type: options.credentialDefinition.type, + value: options.credentialDefinition.value, + ver: '1.0', + }) + + const response = await this.indySdkPoolService.submitWriteRequest( + agentContext, + pool, + request, + options.credentialDefinition.issuerId + ) + + agentContext.config.logger.debug( + `Registered credential definition '${credentialDefinitionId}' on ledger '${pool.didIndyNamespace}'`, + { + response, + credentialDefinition: options.credentialDefinition, + } + ) + + return { + credentialDefinitionMetadata: {}, + credentialDefinitionState: { + credentialDefinition: options.credentialDefinition, + credentialDefinitionId, + state: 'finished', + }, + registrationMetadata: { + didIndyNamespace: pool.didIndyNamespace, + }, + } + } catch (error) { + agentContext.config.logger.error( + `Error registering credential definition for schema '${options.credentialDefinition.schemaId}'`, + { + error, + did: options.credentialDefinition.issuerId, + credentialDefinition: options.credentialDefinition, + } + ) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async getRevocationRegistryDefinition( + agentContext: AgentContext, + revocationRegistryDefinitionId: string + ): Promise { + try { + const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) + const { pool } = await this.indySdkPoolService.getPoolForDid(agentContext, did) + + agentContext.config.logger.debug( + `Using ledger '${pool.didIndyNamespace}' to retrieve revocation registry definition '${revocationRegistryDefinitionId}'` + ) + const request = await this.indySdk.buildGetRevocRegDefRequest(null, revocationRegistryDefinitionId) + + agentContext.config.logger.trace( + `Submitting get revocation registry definition request for revocation registry definition '${revocationRegistryDefinitionId}' to ledger` + ) + const response = await this.indySdkPoolService.submitReadRequest(pool, request) + agentContext.config.logger.trace( + `Got un-parsed revocation registry definition '${revocationRegistryDefinitionId}' from ledger '${pool.didIndyNamespace}'`, + { + response, + } + ) + + const [, revocationRegistryDefinition] = await this.indySdk.parseGetRevocRegDefResponse(response) + + agentContext.config.logger.debug( + `Got revocation registry definition '${revocationRegistryDefinitionId}' from ledger`, + { + revocationRegistryDefinition, + } + ) + + return { + resolutionMetadata: { + didIndyNamespace: pool.didIndyNamespace, + }, + revocationRegistryDefinition: anonCredsRevocationRegistryDefinitionFromIndySdk(revocationRegistryDefinition), + revocationRegistryDefinitionId, + revocationRegistryDefinitionMetadata: { + issuanceType: revocationRegistryDefinition.value.issuanceType, + }, + } + } catch (error) { + agentContext.config.logger.error( + `Error retrieving revocation registry definition '${revocationRegistryDefinitionId}' from ledger`, + { + error, + revocationRegistryDefinitionId: revocationRegistryDefinitionId, + } + ) + + return { + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve revocation registry definition: ${error.message}`, + }, + revocationRegistryDefinition: null, + revocationRegistryDefinitionId, + revocationRegistryDefinitionMetadata: {}, + } + } + } + + public async getRevocationList( + agentContext: AgentContext, + revocationRegistryId: string, + timestamp: number + ): Promise { + try { + const did = didFromRevocationRegistryDefinitionId(revocationRegistryId) + const { pool } = await this.indySdkPoolService.getPoolForDid(agentContext, did) + + agentContext.config.logger.debug( + `Using ledger '${pool.id}' to retrieve revocation registry deltas with revocation registry definition id '${revocationRegistryId}' until ${timestamp}` + ) + + // TODO: implement caching for returned deltas + const request = await this.indySdk.buildGetRevocRegDeltaRequest(null, revocationRegistryId, 0, timestamp) + + agentContext.config.logger.trace( + `Submitting get revocation registry delta request for revocation registry '${revocationRegistryId}' to ledger` + ) + + const response = await this.indySdkPoolService.submitReadRequest(pool, request) + agentContext.config.logger.trace( + `Got revocation registry delta unparsed-response '${revocationRegistryId}' from ledger`, + { + response, + } + ) + + const [, revocationRegistryDelta, deltaTimestamp] = await this.indySdk.parseGetRevocRegDeltaResponse(response) + + agentContext.config.logger.debug( + `Got revocation registry deltas '${revocationRegistryId}' until timestamp ${timestamp} from ledger`, + { + revocationRegistryDelta, + deltaTimestamp, + } + ) + + const { resolutionMetadata, revocationRegistryDefinition, revocationRegistryDefinitionMetadata } = + await this.getRevocationRegistryDefinition(agentContext, revocationRegistryId) + + if ( + !revocationRegistryDefinition || + !revocationRegistryDefinitionMetadata.issuanceType || + typeof revocationRegistryDefinitionMetadata.issuanceType !== 'string' + ) { + return { + resolutionMetadata: { + didIndyNamespace: pool.didIndyNamespace, + error: `error resolving revocation registry definition with id ${revocationRegistryId}: ${resolutionMetadata.error} ${resolutionMetadata.message}`, + }, + revocationListMetadata: {}, + revocationList: null, + } + } + + const isIssuanceByDefault = revocationRegistryDefinitionMetadata.issuanceType === 'ISSUANCE_BY_DEFAULT' + + return { + resolutionMetadata: { + didIndyNamespace: pool.didIndyNamespace, + }, + revocationList: anonCredsRevocationListFromIndySdk( + revocationRegistryId, + revocationRegistryDefinition, + revocationRegistryDelta, + deltaTimestamp, + isIssuanceByDefault + ), + revocationListMetadata: {}, + } + } catch (error) { + agentContext.config.logger.error( + `Error retrieving revocation registry delta '${revocationRegistryId}' from ledger, potentially revocation interval ends before revocation registry creation?"`, + { + error, + revocationRegistryId: revocationRegistryId, + } + ) + + return { + resolutionMetadata: { + error: 'notFound', + message: `Error retrieving revocation registry delta '${revocationRegistryId}' from ledger, potentially revocation interval ends before revocation registry creation: ${error.message}`, + }, + revocationList: null, + revocationListMetadata: {}, + } + } + } +} + +export interface IndySdkRegisterSchemaOptions extends RegisterSchemaOptions { + options: { + didIndyNamespace: string + } +} + +export interface IndySdkRegisterCredentialDefinitionOptions extends RegisterCredentialDefinitionOptions { + options: { + didIndyNamespace: string + } +} diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts new file mode 100644 index 0000000000..88179381a9 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts @@ -0,0 +1,327 @@ +import type { + AnonCredsHolderService, + AnonCredsProof, + CreateCredentialRequestOptions, + CreateCredentialRequestReturn, + CreateProofOptions, + CredentialInfo, + GetCredentialOptions, + StoreCredentialOptions, + GetCredentialsForProofRequestOptions, + GetCredentialsForProofRequestReturn, + RequestedCredentials, +} from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' +import type { + Cred, + CredentialDefs, + IndyRequestedCredentials, + RevStates, + Schemas, + IndyCredential as IndySdkCredential, +} from 'indy-sdk' + +import { inject } from '@aries-framework/core' + +import { IndySdkError, isIndyError } from '../../error' +import { IndySdk, IndySdkSymbol } from '../../types' +import { assertIndySdkWallet } from '../../utils/assertIndySdkWallet' +import { getIndySeqNoFromUnqualifiedCredentialDefinitionId } from '../utils/identifiers' +import { + indySdkCredentialDefinitionFromAnonCreds, + indySdkRevocationRegistryDefinitionFromAnonCreds, + indySdkSchemaFromAnonCreds, +} from '../utils/transform' + +import { IndySdkRevocationService } from './IndySdkRevocationService' + +export class IndySdkHolderService implements AnonCredsHolderService { + private indySdk: IndySdk + private indyRevocationService: IndySdkRevocationService + + public constructor(indyRevocationService: IndySdkRevocationService, @inject(IndySdkSymbol) indySdk: IndySdk) { + this.indySdk = indySdk + this.indyRevocationService = indyRevocationService + } + + public async createProof(agentContext: AgentContext, options: CreateProofOptions): Promise { + const { credentialDefinitions, proofRequest, requestedCredentials, schemas } = options + + assertIndySdkWallet(agentContext.wallet) + + try { + agentContext.config.logger.debug('Creating Indy Proof') + const indyRevocationStates: RevStates = await this.indyRevocationService.createRevocationState( + agentContext, + proofRequest, + requestedCredentials, + options.revocationRegistries + ) + + // The AnonCredsSchema doesn't contain the seqNo anymore. However, the indy credential definition id + // does contain the seqNo, so we can extract it from the credential definition id. + const seqNoMap: { [schemaId: string]: number } = {} + + // Convert AnonCreds credential definitions to Indy credential definitions + const indyCredentialDefinitions: CredentialDefs = {} + for (const credentialDefinitionId in credentialDefinitions) { + const credentialDefinition = credentialDefinitions[credentialDefinitionId] + indyCredentialDefinitions[credentialDefinitionId] = indySdkCredentialDefinitionFromAnonCreds( + credentialDefinitionId, + credentialDefinition + ) + + // Get the seqNo for the schemas so we can use it when transforming the schemas + const schemaSeqNo = getIndySeqNoFromUnqualifiedCredentialDefinitionId(credentialDefinitionId) + seqNoMap[credentialDefinition.schemaId] = schemaSeqNo + } + + // Convert AnonCreds schemas to Indy schemas + const indySchemas: Schemas = {} + for (const schemaId in schemas) { + const schema = schemas[schemaId] + indySchemas[schemaId] = indySdkSchemaFromAnonCreds(schemaId, schema, seqNoMap[schemaId]) + } + + const indyProof = await this.indySdk.proverCreateProof( + agentContext.wallet.handle, + proofRequest, + this.parseRequestedCredentials(requestedCredentials), + agentContext.wallet.masterSecretId, + indySchemas, + indyCredentialDefinitions, + indyRevocationStates + ) + + agentContext.config.logger.trace('Created Indy Proof', { + indyProof, + }) + + return indyProof + } catch (error) { + agentContext.config.logger.error(`Error creating Indy Proof`, { + error, + proofRequest, + requestedCredentials, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async storeCredential(agentContext: AgentContext, options: StoreCredentialOptions): Promise { + assertIndySdkWallet(agentContext.wallet) + + const indyRevocationRegistryDefinition = options.revocationRegistry + ? indySdkRevocationRegistryDefinitionFromAnonCreds( + options.revocationRegistry.id, + options.revocationRegistry.definition + ) + : null + + try { + return await this.indySdk.proverStoreCredential( + agentContext.wallet.handle, + options.credentialId ?? null, + options.credentialRequestMetadata, + options.credential, + indySdkCredentialDefinitionFromAnonCreds(options.credentialDefinitionId, options.credentialDefinition), + indyRevocationRegistryDefinition + ) + } catch (error) { + agentContext.config.logger.error(`Error storing Indy Credential '${options.credentialId}'`, { + error, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async getCredential(agentContext: AgentContext, options: GetCredentialOptions): Promise { + assertIndySdkWallet(agentContext.wallet) + + try { + const result = await this.indySdk.proverGetCredential(agentContext.wallet.handle, options.credentialId) + + return { + credentialDefinitionId: result.cred_def_id, + attributes: result.attrs, + referent: result.referent, + schemaId: result.schema_id, + credentialRevocationId: result.cred_rev_id, + revocationRegistryId: result.rev_reg_id, + } + } catch (error) { + agentContext.config.logger.error(`Error getting Indy Credential '${options.credentialId}'`, { + error, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async createCredentialRequest( + agentContext: AgentContext, + options: CreateCredentialRequestOptions + ): Promise { + assertIndySdkWallet(agentContext.wallet) + + try { + const result = await this.indySdk.proverCreateCredentialReq( + agentContext.wallet.handle, + options.holderDid, + options.credentialOffer, + // NOTE: Is it safe to use the cred_def_id from the offer? I think so. You can't create a request + // for a cred def that is not in the offer + indySdkCredentialDefinitionFromAnonCreds(options.credentialOffer.cred_def_id, options.credentialDefinition), + // FIXME: we need to remove the masterSecret from the wallet, as it is AnonCreds specific + // Issue: https://github.com/hyperledger/aries-framework-javascript/issues/1198 + agentContext.wallet.masterSecretId + ) + + return { + credentialRequest: result[0], + credentialRequestMetadata: result[1], + } + } catch (error) { + agentContext.config.logger.error(`Error creating Indy Credential Request`, { + error, + credentialOffer: options.credentialOffer, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async deleteCredential(agentContext: AgentContext, credentialId: string): Promise { + assertIndySdkWallet(agentContext.wallet) + + try { + return await this.indySdk.proverDeleteCredential(agentContext.wallet.handle, credentialId) + } catch (error) { + agentContext.config.logger.error(`Error deleting Indy Credential from Wallet`, { + error, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async getCredentialsForProofRequest( + agentContext: AgentContext, + options: GetCredentialsForProofRequestOptions + ): Promise { + assertIndySdkWallet(agentContext.wallet) + + try { + // Open indy credential search + const searchHandle = await this.indySdk.proverSearchCredentialsForProofReq( + agentContext.wallet.handle, + options.proofRequest, + options.extraQuery ?? null + ) + + const start = options.start ?? 0 + + try { + // Make sure database cursors start at 'start' (bit ugly, but no way around in indy) + if (start > 0) { + await this.fetchCredentialsForReferent(agentContext, searchHandle, options.attributeReferent, start) + } + + // Fetch the credentials + const credentials = await this.fetchCredentialsForReferent( + agentContext, + searchHandle, + options.attributeReferent, + options.limit + ) + + // TODO: sort the credentials (irrevocable first) + return credentials.map((credential) => ({ + credentialInfo: { + credentialDefinitionId: credential.cred_info.cred_def_id, + referent: credential.cred_info.referent, + attributes: credential.cred_info.attrs, + schemaId: credential.cred_info.schema_id, + revocationRegistryId: credential.cred_info.rev_reg_id, + credentialRevocationId: credential.cred_info.cred_rev_id, + }, + interval: credential.interval, + })) + } finally { + // Always close search + await this.indySdk.proverCloseCredentialsSearchForProofReq(searchHandle) + } + } catch (error) { + if (isIndyError(error)) { + throw new IndySdkError(error) + } + + throw error + } + } + + private async fetchCredentialsForReferent( + agentContext: AgentContext, + searchHandle: number, + referent: string, + limit?: number + ) { + try { + let credentials: IndySdkCredential[] = [] + + // Allow max of 256 per fetch operation + const chunk = limit ? Math.min(256, limit) : 256 + + // Loop while limit not reached (or no limit specified) + while (!limit || credentials.length < limit) { + // Retrieve credentials + const credentialsJson = await this.indySdk.proverFetchCredentialsForProofReq(searchHandle, referent, chunk) + credentials = [...credentials, ...credentialsJson] + + // If the number of credentials returned is less than chunk + // It means we reached the end of the iterator (no more credentials) + if (credentialsJson.length < chunk) { + return credentials + } + } + + return credentials + } catch (error) { + agentContext.config.logger.error(`Error Fetching Indy Credentials For Referent`, { + error, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + /** + * Converts a public api form of {@link RequestedCredentials} interface into a format {@link Indy.IndyRequestedCredentials} that Indy SDK expects. + **/ + private parseRequestedCredentials(requestedCredentials: RequestedCredentials): IndyRequestedCredentials { + const indyRequestedCredentials: IndyRequestedCredentials = { + requested_attributes: {}, + requested_predicates: {}, + self_attested_attributes: {}, + } + + for (const groupName in requestedCredentials.requestedAttributes) { + indyRequestedCredentials.requested_attributes[groupName] = { + cred_id: requestedCredentials.requestedAttributes[groupName].credentialId, + revealed: requestedCredentials.requestedAttributes[groupName].revealed, + timestamp: requestedCredentials.requestedAttributes[groupName].timestamp, + } + } + + for (const groupName in requestedCredentials.requestedPredicates) { + indyRequestedCredentials.requested_predicates[groupName] = { + cred_id: requestedCredentials.requestedPredicates[groupName].credentialId, + timestamp: requestedCredentials.requestedPredicates[groupName].timestamp, + } + } + + return indyRequestedCredentials + } +} diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts new file mode 100644 index 0000000000..f877be4f75 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts @@ -0,0 +1,129 @@ +import type { CreateCredentialDefinitionMetadata } from './IndySdkIssuerServiceMetadata' +import type { IndySdkUtilitiesService } from './IndySdkUtilitiesService' +import type { + AnonCredsIssuerService, + CreateCredentialDefinitionOptions, + CreateCredentialOfferOptions, + CreateCredentialOptions, + CreateCredentialReturn, + CreateSchemaOptions, + AnonCredsCredentialOffer, + AnonCredsSchema, + AnonCredsCredentialDefinition, +} from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' + +import { AriesFrameworkError, inject } from '@aries-framework/core' + +import { IndySdkError, isIndyError } from '../../error' +import { IndySdk, IndySdkSymbol } from '../../types' +import { assertIndySdkWallet } from '../../utils/assertIndySdkWallet' +import { indySdkSchemaFromAnonCreds } from '../utils/transform' + +export class IndySdkIssuerService implements AnonCredsIssuerService { + private indySdk: IndySdk + private IndySdkUtilitiesService: IndySdkUtilitiesService + + public constructor(IndySdkUtilitiesService: IndySdkUtilitiesService, @inject(IndySdkSymbol) indySdk: IndySdk) { + this.indySdk = indySdk + this.IndySdkUtilitiesService = IndySdkUtilitiesService + } + + public async createSchema(agentContext: AgentContext, options: CreateSchemaOptions): Promise { + const { issuerId, name, version, attrNames } = options + assertIndySdkWallet(agentContext.wallet) + + try { + const [, schema] = await this.indySdk.issuerCreateSchema(issuerId, name, version, attrNames) + + return { + issuerId, + attrNames: schema.attrNames, + name: schema.name, + version: schema.version, + } + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async createCredentialDefinition( + agentContext: AgentContext, + options: CreateCredentialDefinitionOptions, + metadata?: CreateCredentialDefinitionMetadata + ): Promise { + const { tag, supportRevocation, schema, issuerId, schemaId } = options + + if (!metadata) + throw new AriesFrameworkError('The metadata parameter is required when using Indy, but received undefined.') + + try { + assertIndySdkWallet(agentContext.wallet) + const [, credentialDefinition] = await this.indySdk.issuerCreateAndStoreCredentialDef( + agentContext.wallet.handle, + issuerId, + indySdkSchemaFromAnonCreds(schemaId, schema, metadata.indyLedgerSchemaSeqNo), + tag, + 'CL', + { + support_revocation: supportRevocation, + } + ) + + return { + issuerId, + tag: credentialDefinition.tag, + schemaId: credentialDefinition.schemaId, + type: 'CL', + value: credentialDefinition.value, + } + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async createCredentialOffer( + agentContext: AgentContext, + options: CreateCredentialOfferOptions + ): Promise { + assertIndySdkWallet(agentContext.wallet) + try { + return await this.indySdk.issuerCreateCredentialOffer(agentContext.wallet.handle, options.credentialDefinitionId) + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async createCredential( + agentContext: AgentContext, + options: CreateCredentialOptions + ): Promise { + const { tailsFilePath, credentialOffer, credentialRequest, credentialValues, revocationRegistryId } = options + + assertIndySdkWallet(agentContext.wallet) + try { + // Indy SDK requires tailsReaderHandle. Use null if no tailsFilePath is present + const tailsReaderHandle = tailsFilePath ? await this.IndySdkUtilitiesService.createTailsReader(tailsFilePath) : 0 + + if (revocationRegistryId || tailsFilePath) { + throw new AriesFrameworkError('Revocation not supported yet') + } + + const [credential, credentialRevocationId] = await this.indySdk.issuerCreateCredential( + agentContext.wallet.handle, + credentialOffer, + credentialRequest, + credentialValues, + revocationRegistryId ?? null, + tailsReaderHandle + ) + + return { + credential, + credentialRevocationId, + } + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } +} diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerServiceMetadata.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerServiceMetadata.ts new file mode 100644 index 0000000000..bb02f17967 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerServiceMetadata.ts @@ -0,0 +1,3 @@ +export type CreateCredentialDefinitionMetadata = { + indyLedgerSchemaSeqNo: number +} diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts new file mode 100644 index 0000000000..0ed637a6ee --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts @@ -0,0 +1,177 @@ +import type { + AnonCredsRevocationRegistryDefinition, + AnonCredsRevocationList, + AnonCredsProofRequest, + RequestedCredentials, + CredentialInfo, + NonRevokedInterval, +} from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' +import type { RevStates } from 'indy-sdk' + +import { AriesFrameworkError, inject, injectable } from '@aries-framework/core' + +import { IndySdkError, isIndyError } from '../../error' +import { IndySdk, IndySdkSymbol } from '../../types' +import { + indySdkRevocationDeltaFromAnonCreds, + indySdkRevocationRegistryDefinitionFromAnonCreds, +} from '../utils/transform' + +import { IndySdkUtilitiesService } from './IndySdkUtilitiesService' + +enum RequestReferentType { + Attribute = 'attribute', + Predicate = 'predicate', + SelfAttestedAttribute = 'self-attested-attribute', +} + +/** + * Internal class that handles revocation related logic for the Indy SDK + * + * @internal + */ +@injectable() +export class IndySdkRevocationService { + private indySdk: IndySdk + private indySdkUtilitiesService: IndySdkUtilitiesService + + public constructor(indyUtilitiesService: IndySdkUtilitiesService, @inject(IndySdkSymbol) indySdk: IndySdk) { + this.indySdk = indySdk + this.indySdkUtilitiesService = indyUtilitiesService + } + + /** + * Creates the revocation state for the requested credentials in a format that the Indy SDK expects. + */ + public async createRevocationState( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + requestedCredentials: RequestedCredentials, + revocationRegistries: { + [revocationRegistryDefinitionId: string]: { + // Tails is already downloaded + tailsFilePath: string + definition: AnonCredsRevocationRegistryDefinition + revocationLists: { + [timestamp: string]: AnonCredsRevocationList + } + } + } + ): Promise { + try { + agentContext.config.logger.debug(`Creating Revocation State(s) for proof request`, { + proofRequest, + requestedCredentials, + }) + const indyRevocationStates: RevStates = {} + const referentCredentials: Array<{ + type: RequestReferentType + referent: string + credentialInfo: CredentialInfo + referentRevocationInterval: NonRevokedInterval | undefined + }> = [] + + //Retrieve information for referents and push to single array + for (const [referent, requestedCredential] of Object.entries(requestedCredentials.requestedAttributes ?? {})) { + referentCredentials.push({ + referent, + credentialInfo: requestedCredential.credentialInfo, + type: RequestReferentType.Attribute, + referentRevocationInterval: proofRequest.requested_attributes[referent].non_revoked, + }) + } + for (const [referent, requestedCredential] of Object.entries(requestedCredentials.requestedPredicates ?? {})) { + referentCredentials.push({ + referent, + credentialInfo: requestedCredential.credentialInfo, + type: RequestReferentType.Predicate, + referentRevocationInterval: proofRequest.requested_predicates[referent].non_revoked, + }) + } + + for (const { referent, credentialInfo, type, referentRevocationInterval } of referentCredentials) { + // Prefer referent-specific revocation interval over global revocation interval + const requestRevocationInterval = referentRevocationInterval ?? proofRequest.non_revoked + const credentialRevocationId = credentialInfo.credentialRevocationId + const revocationRegistryId = credentialInfo.revocationRegistryId + + // If revocation interval is present and the credential is revocable then create revocation state + if (requestRevocationInterval && credentialRevocationId && revocationRegistryId) { + agentContext.config.logger.trace( + `Presentation is requesting proof of non revocation for ${type} referent '${referent}', creating revocation state for credential`, + { + requestRevocationInterval, + credentialRevocationId, + revocationRegistryId, + } + ) + + this.assertRevocationInterval(requestRevocationInterval) + + const { definition, revocationLists, tailsFilePath } = revocationRegistries[revocationRegistryId] + // NOTE: we assume that the revocationLists have been added based on timestamps of the `to` query. On a higher level it means we'll find the + // most accurate revocation list for a given timestamp. It doesn't have to be that the revocationList is from the `to` timestamp however. + const revocationList = revocationLists[requestRevocationInterval.to] + + const tails = await this.indySdkUtilitiesService.createTailsReader(tailsFilePath) + + const revocationState = await this.indySdk.createRevocationState( + tails, + indySdkRevocationRegistryDefinitionFromAnonCreds(revocationRegistryId, definition), + indySdkRevocationDeltaFromAnonCreds(revocationList), + revocationList.timestamp, + credentialRevocationId + ) + const timestamp = revocationState.timestamp + + if (!indyRevocationStates[revocationRegistryId]) { + indyRevocationStates[revocationRegistryId] = {} + } + indyRevocationStates[revocationRegistryId][timestamp] = revocationState + } + } + + agentContext.config.logger.debug(`Created Revocation States for Proof Request`, { + indyRevocationStates, + }) + + return indyRevocationStates + } catch (error) { + agentContext.config.logger.error(`Error creating Indy Revocation State for Proof Request`, { + error, + proofRequest, + requestedCredentials, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + // TODO: Add Test + // TODO: we should do this verification on a higher level I think? + // Check revocation interval in accordance with https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0441-present-proof-best-practices/README.md#semantics-of-non-revocation-interval-endpoints + private assertRevocationInterval( + revocationInterval: NonRevokedInterval + ): asserts revocationInterval is BestPracticeNonRevokedInterval { + if (!revocationInterval.to) { + throw new AriesFrameworkError(`Presentation requests proof of non-revocation with no 'to' value specified`) + } + + if ( + (revocationInterval.from || revocationInterval.from === 0) && + revocationInterval.to !== revocationInterval.from + ) { + throw new AriesFrameworkError( + `Presentation requests proof of non-revocation with an interval from: '${revocationInterval.from}' that does not match the interval to: '${revocationInterval.to}', as specified in Aries RFC 0441` + ) + } + } +} + +// This sets the `to` value to be required. We do this check in the `assertRevocationInterval` method, +// and it makes it easier to work with the object in TS +interface BestPracticeNonRevokedInterval { + from?: number + to: number +} diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkUtilitiesService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkUtilitiesService.ts new file mode 100644 index 0000000000..1ac0dec33e --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkUtilitiesService.ts @@ -0,0 +1,65 @@ +import type { BlobReaderHandle } from 'indy-sdk' + +import { + AriesFrameworkError, + FileSystem, + getDirFromFilePath, + IndySdkError, + InjectionSymbols, + Logger, +} from '@aries-framework/core' +import { inject, injectable } from 'tsyringe' + +import { isIndyError } from '../../error' +import { IndySdk, IndySdkSymbol } from '../../types' + +@injectable() +export class IndySdkUtilitiesService { + private indySdk: IndySdk + private logger: Logger + private fileSystem: FileSystem + + public constructor( + @inject(InjectionSymbols.Logger) logger: Logger, + @inject(InjectionSymbols.FileSystem) fileSystem: FileSystem, + @inject(IndySdkSymbol) indySdk: IndySdk + ) { + this.indySdk = indySdk + this.logger = logger + this.fileSystem = fileSystem + } + + /** + * Get a handler for the blob storage tails file reader. + * + * @param tailsFilePath The path of the tails file + * @returns The blob storage reader handle + */ + public async createTailsReader(tailsFilePath: string): Promise { + try { + this.logger.debug(`Opening tails reader at path ${tailsFilePath}`) + const tailsFileExists = await this.fileSystem.exists(tailsFilePath) + + // Extract directory from path (should also work with windows paths) + const dirname = getDirFromFilePath(tailsFilePath) + + if (!tailsFileExists) { + throw new AriesFrameworkError(`Tails file does not exist at path ${tailsFilePath}`) + } + + const tailsReaderConfig = { + base_dir: dirname, + } + + const tailsReader = await this.indySdk.openBlobStorageReader('default', tailsReaderConfig) + this.logger.debug(`Opened tails reader at path ${tailsFilePath}`) + return tailsReader + } catch (error) { + if (isIndyError(error)) { + throw new IndySdkError(error) + } + + throw error + } + } +} diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts new file mode 100644 index 0000000000..d302e66c97 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts @@ -0,0 +1,86 @@ +import type { AnonCredsVerifierService, VerifyProofOptions } from '@aries-framework/anoncreds' +import type { CredentialDefs, Schemas, RevocRegDefs, RevRegs } from 'indy-sdk' + +import { inject } from '@aries-framework/core' + +import { IndySdkError, isIndyError } from '../../error' +import { IndySdk, IndySdkSymbol } from '../../types' +import { getIndySeqNoFromUnqualifiedCredentialDefinitionId } from '../utils/identifiers' +import { + indySdkCredentialDefinitionFromAnonCreds, + indySdkRevocationRegistryDefinitionFromAnonCreds, + indySdkRevocationRegistryFromAnonCreds, + indySdkSchemaFromAnonCreds, +} from '../utils/transform' + +export class IndySdkVerifierService implements AnonCredsVerifierService { + private indySdk: IndySdk + + public constructor(@inject(IndySdkSymbol) indySdk: IndySdk) { + this.indySdk = indySdk + } + + public async verifyProof(options: VerifyProofOptions): Promise { + try { + // The AnonCredsSchema doesn't contain the seqNo anymore. However, the indy credential definition id + // does contain the seqNo, so we can extract it from the credential definition id. + const seqNoMap: { [schemaId: string]: number } = {} + + // Convert AnonCreds credential definitions to Indy credential definitions + const indyCredentialDefinitions: CredentialDefs = {} + for (const credentialDefinitionId in options.credentialDefinitions) { + const credentialDefinition = options.credentialDefinitions[credentialDefinitionId] + + indyCredentialDefinitions[credentialDefinitionId] = indySdkCredentialDefinitionFromAnonCreds( + credentialDefinitionId, + credentialDefinition + ) + + // Get the seqNo for the schemas so we can use it when transforming the schemas + const schemaSeqNo = getIndySeqNoFromUnqualifiedCredentialDefinitionId(credentialDefinitionId) + seqNoMap[credentialDefinition.schemaId] = schemaSeqNo + } + + // Convert AnonCreds schemas to Indy schemas + const indySchemas: Schemas = {} + for (const schemaId in options.schemas) { + const schema = options.schemas[schemaId] + indySchemas[schemaId] = indySdkSchemaFromAnonCreds(schemaId, schema, seqNoMap[schemaId]) + } + + // Convert AnonCreds revocation definitions to Indy revocation definitions + const indyRevocationDefinitions: RevocRegDefs = {} + const indyRevocationRegistries: RevRegs = {} + + for (const revocationRegistryDefinitionId in options.revocationStates) { + const { definition, revocationLists } = options.revocationStates[revocationRegistryDefinitionId] + indyRevocationDefinitions[revocationRegistryDefinitionId] = indySdkRevocationRegistryDefinitionFromAnonCreds( + revocationRegistryDefinitionId, + definition + ) + + // Initialize empty object for this revocation registry + indyRevocationRegistries[revocationRegistryDefinitionId] = {} + + // Also transform the revocation lists for the specified timestamps into the revocation registry + // format Indy expects + for (const timestamp in revocationLists) { + const revocationList = revocationLists[timestamp] + indyRevocationRegistries[revocationRegistryDefinitionId][timestamp] = + indySdkRevocationRegistryFromAnonCreds(revocationList) + } + } + + return await this.indySdk.verifierVerifyProof( + options.proofRequest, + options.proof, + indySchemas, + indyCredentialDefinitions, + indyRevocationDefinitions, + indyRevocationRegistries + ) + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } +} diff --git a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts new file mode 100644 index 0000000000..f85ec160b5 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts @@ -0,0 +1,48 @@ +import { + didFromSchemaId, + didFromCredentialDefinitionId, + didFromRevocationRegistryDefinitionId, + getIndySeqNoFromUnqualifiedCredentialDefinitionId, + getLegacyCredentialDefinitionId, + getLegacySchemaId, +} from '../identifiers' + +describe('identifiers', () => { + it('getLegacySchemaId should return a valid schema id given a did, name, and version', () => { + const did = '12345' + const name = 'backbench' + const version = '420' + + expect(getLegacySchemaId(did, name, version)).toEqual('12345:2:backbench:420') + }) + + it('getLegacyCredentialDefinitionId should return a valid credential definition id given a did, seqNo, and tag', () => { + const did = '12345' + const seqNo = 420 + const tag = 'someTag' + + expect(getLegacyCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') + }) + + it('getIndySeqNoFromUnqualifiedCredentialDefinitionId should return the seqNo from the credential definition id', () => { + expect(getIndySeqNoFromUnqualifiedCredentialDefinitionId('12345:3:CL:420:someTag')).toEqual(420) + }) + + it('didFromSchemaId should return the did from the schema id', () => { + const schemaId = '12345:2:backbench:420' + + expect(didFromSchemaId(schemaId)).toEqual('12345') + }) + + it('didFromCredentialDefinitionId should return the did from the credential definition id', () => { + const credentialDefinitionId = '12345:3:CL:420:someTag' + + expect(didFromCredentialDefinitionId(credentialDefinitionId)).toEqual('12345') + }) + + it('didFromRevocationRegistryDefinitionId should return the did from the revocation registry id', () => { + const revocationRegistryId = '12345:3:CL:420:someTag' + + expect(didFromRevocationRegistryDefinitionId(revocationRegistryId)).toEqual('12345') + }) +}) diff --git a/packages/indy-sdk/src/anoncreds/utils/__tests__/transform.test.ts b/packages/indy-sdk/src/anoncreds/utils/__tests__/transform.test.ts new file mode 100644 index 0000000000..20b16fa0ff --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/transform.test.ts @@ -0,0 +1,114 @@ +import type { AnonCredsCredentialDefinition, AnonCredsSchema } from '../../../../../anoncreds/src' +import type { CredDef, Schema } from 'indy-sdk' + +import { + anonCredsCredentialDefinitionFromIndySdk, + anonCredsSchemaFromIndySdk, + indySdkCredentialDefinitionFromAnonCreds, + indySdkSchemaFromAnonCreds, +} from '../transform' + +describe('transform', () => { + it('anonCredsSchemaFromIndySdk should return a valid anoncreds schema', () => { + const schema: Schema = { + attrNames: ['hello'], + id: '12345:2:Example Schema:1.0.0', + name: 'Example Schema', + seqNo: 150, + ver: '1.0', + version: '1.0.0', + } + + expect(anonCredsSchemaFromIndySdk(schema)).toEqual({ + attrNames: ['hello'], + issuerId: '12345', + name: 'Example Schema', + version: '1.0.0', + }) + }) + + it('indySdkSchemaFromAnonCreds should return a valid indy sdk schema', () => { + const schemaId = '12345:2:Example Schema:1.0.0' + const schema: AnonCredsSchema = { + attrNames: ['hello'], + issuerId: '12345', + name: 'Example Schema', + version: '1.0.0', + } + + expect(indySdkSchemaFromAnonCreds(schemaId, schema, 150)).toEqual({ + attrNames: ['hello'], + id: '12345:2:Example Schema:1.0.0', + name: 'Example Schema', + seqNo: 150, + ver: '1.0', + version: '1.0.0', + }) + }) + + it('anonCredsCredentialDefinitionFromIndySdk should return a valid anoncreds credential definition', () => { + const credDef: CredDef = { + id: '12345:3:CL:420:someTag', + schemaId: '8910:2:Example Schema:1.0.0', + tag: 'someTag', + type: 'CL', + value: { + primary: { + something: 'string', + }, + }, + ver: '1.0', + } + + expect(anonCredsCredentialDefinitionFromIndySdk(credDef)).toEqual({ + issuerId: '12345', + schemaId: '8910:2:Example Schema:1.0.0', + tag: 'someTag', + type: 'CL', + value: { + primary: { + something: 'string', + }, + }, + }) + }) + + it('indySdkCredentialDefinitionFromAnonCreds should return a valid indy sdk credential definition', () => { + const credentialDefinitionId = '12345:3:CL:420:someTag' + const credentialDefinition: AnonCredsCredentialDefinition = { + issuerId: '12345', + schemaId: '8910:2:Example Schema:1.0.0', + tag: 'someTag', + type: 'CL', + value: { + primary: { + something: 'string', + }, + }, + } + + expect(indySdkCredentialDefinitionFromAnonCreds(credentialDefinitionId, credentialDefinition)).toEqual({ + id: '12345:3:CL:420:someTag', + schemaId: '8910:2:Example Schema:1.0.0', + tag: 'someTag', + type: 'CL', + value: { + primary: { + something: 'string', + }, + }, + ver: '1.0', + }) + }) + + // TODO: add tests for these models once finalized in the anoncreds spec + test.todo( + 'anonCredsRevocationRegistryDefinitionFromIndySdk should return a valid anoncreds revocation registry definition' + ) + test.todo( + 'indySdkRevocationRegistryDefinitionFromAnonCreds should return a valid indy sdk revocation registry definition' + ) + test.todo('anonCredsRevocationListFromIndySdk should return a valid anoncreds revocation list') + test.todo('indySdkRevocationRegistryFromAnonCreds should return a valid indy sdk revocation registry') + test.todo('indySdkRevocationDeltaFromAnonCreds should return a valid indy sdk revocation delta') +}) diff --git a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts new file mode 100644 index 0000000000..bc59b5f8d4 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts @@ -0,0 +1,41 @@ +export function getIndySeqNoFromUnqualifiedCredentialDefinitionId(unqualifiedCredentialDefinitionId: string): number { + // 5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npbd + const [, , , seqNo] = unqualifiedCredentialDefinitionId.split(':') + + return Number(seqNo) +} + +export function getLegacySchemaId(unqualifiedDid: string, name: string, version: string) { + return `${unqualifiedDid}:2:${name}:${version}` +} + +export function getLegacyCredentialDefinitionId(unqualifiedDid: string, seqNo: number, tag: string) { + return `${unqualifiedDid}:3:CL:${seqNo}:${tag}` +} + +/** + * Extract did from schema id + */ +export function didFromSchemaId(schemaId: string) { + const [did] = schemaId.split(':') + + return did +} + +/** + * Extract did from credential definition id + */ +export function didFromCredentialDefinitionId(credentialDefinitionId: string) { + const [did] = credentialDefinitionId.split(':') + + return did +} + +/** + * Extract did from revocation registry definition id + */ +export function didFromRevocationRegistryDefinitionId(revocationRegistryId: string) { + const [did] = revocationRegistryId.split(':') + + return did +} diff --git a/packages/indy-sdk/src/anoncreds/utils/transform.ts b/packages/indy-sdk/src/anoncreds/utils/transform.ts new file mode 100644 index 0000000000..a5ad8afd60 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/utils/transform.ts @@ -0,0 +1,153 @@ +import type { + AnonCredsCredentialDefinition, + AnonCredsRevocationList, + AnonCredsRevocationRegistryDefinition, + AnonCredsSchema, +} from '@aries-framework/anoncreds' +import type { CredDef, RevocReg, RevocRegDef, RevocRegDelta, Schema } from 'indy-sdk' + +import { didFromCredentialDefinitionId, didFromRevocationRegistryDefinitionId, didFromSchemaId } from './identifiers' + +export function anonCredsSchemaFromIndySdk(schema: Schema): AnonCredsSchema { + const issuerId = didFromSchemaId(schema.id) + return { + issuerId, + name: schema.name, + version: schema.version, + attrNames: schema.attrNames, + } +} + +export function indySdkSchemaFromAnonCreds(schemaId: string, schema: AnonCredsSchema, indyLedgerSeqNo: number): Schema { + return { + id: schemaId, + attrNames: schema.attrNames, + name: schema.name, + version: schema.version, + ver: '1.0', + seqNo: indyLedgerSeqNo, + } +} + +export function anonCredsCredentialDefinitionFromIndySdk(credentialDefinition: CredDef): AnonCredsCredentialDefinition { + const issuerId = didFromCredentialDefinitionId(credentialDefinition.id) + + return { + issuerId, + schemaId: credentialDefinition.schemaId, + tag: credentialDefinition.tag, + type: 'CL', + value: credentialDefinition.value, + } +} + +export function indySdkCredentialDefinitionFromAnonCreds( + credentialDefinitionId: string, + credentialDefinition: AnonCredsCredentialDefinition +): CredDef { + return { + id: credentialDefinitionId, + schemaId: credentialDefinition.schemaId, + tag: credentialDefinition.tag, + type: credentialDefinition.type, + value: credentialDefinition.value, + ver: '1.0', + } +} + +export function anonCredsRevocationRegistryDefinitionFromIndySdk( + revocationRegistryDefinition: RevocRegDef +): AnonCredsRevocationRegistryDefinition { + const issuerId = didFromRevocationRegistryDefinitionId(revocationRegistryDefinition.id) + + return { + issuerId, + credDefId: revocationRegistryDefinition.credDefId, + maxCredNum: revocationRegistryDefinition.value.maxCredNum, + publicKeys: revocationRegistryDefinition.value.publicKeys, + tag: revocationRegistryDefinition.tag, + tailsHash: revocationRegistryDefinition.value.tailsHash, + tailsLocation: revocationRegistryDefinition.value.tailsLocation, + type: 'CL_ACCUM', + } +} + +export function indySdkRevocationRegistryDefinitionFromAnonCreds( + revocationRegistryDefinitionId: string, + revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition +): RevocRegDef { + return { + id: revocationRegistryDefinitionId, + credDefId: revocationRegistryDefinition.credDefId, + revocDefType: revocationRegistryDefinition.type, + tag: revocationRegistryDefinition.tag, + value: { + issuanceType: 'ISSUANCE_BY_DEFAULT', // NOTE: we always use ISSUANCE_BY_DEFAULT when passing to the indy-sdk. It doesn't matter, as we have the revocation List with the full state + maxCredNum: revocationRegistryDefinition.maxCredNum, + publicKeys: revocationRegistryDefinition.publicKeys, + tailsHash: revocationRegistryDefinition.tailsHash, + tailsLocation: revocationRegistryDefinition.tailsLocation, + }, + ver: '1.0', + } +} + +export function anonCredsRevocationListFromIndySdk( + revocationRegistryDefinitionId: string, + revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition, + delta: RevocRegDelta, + timestamp: number, + isIssuanceByDefault: boolean +): AnonCredsRevocationList { + // 0 means unrevoked, 1 means revoked + const defaultState = isIssuanceByDefault ? 0 : 1 + + // Fill with default value + const revocationList = new Array(revocationRegistryDefinition.maxCredNum).fill(defaultState) + + // Set all `issuer` indexes to 0 (not revoked) + for (const issued of delta.value.issued ?? []) { + revocationList[issued] = 0 + } + + // Set all `revoked` indexes to 1 (revoked) + for (const revoked of delta.value.revoked ?? []) { + revocationList[revoked] = 1 + } + + return { + issuerId: revocationRegistryDefinition.issuerId, + currentAccumulator: delta.value.accum, + revRegId: revocationRegistryDefinitionId, + revocationList, + timestamp, + } +} + +export function indySdkRevocationRegistryFromAnonCreds(revocationList: AnonCredsRevocationList): RevocReg { + return { + ver: '1.0', + value: { + accum: revocationList.currentAccumulator, + }, + } +} + +export function indySdkRevocationDeltaFromAnonCreds(revocationList: AnonCredsRevocationList): RevocRegDelta { + // Get all indices from the revocationList that are revoked (so have value '1') + const revokedIndices = revocationList.revocationList.reduce( + (revoked, current, index) => (current === 1 ? [...revoked, index] : revoked), + [] + ) + + return { + value: { + accum: revocationList.currentAccumulator, + issued: [], + revoked: revokedIndices, + // NOTE: I don't think this is used? + prevAccum: '', + }, + ver: '1.0', + } +} diff --git a/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts b/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts new file mode 100644 index 0000000000..9f94c7326c --- /dev/null +++ b/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts @@ -0,0 +1,265 @@ +import type { IndySdkPool } from '../ledger' +import type { IndyEndpointAttrib } from './didSovUtil' +import type { + AgentContext, + DidRegistrar, + DidCreateOptions, + DidCreateResult, + DidDeactivateResult, + DidUpdateResult, + Key, +} from '@aries-framework/core' +import type { NymRole } from 'indy-sdk' + +import { inject, injectable, DidDocumentRole, DidRecord, DidRepository } from '@aries-framework/core' + +import { IndySdkError } from '../error' +import { isIndyError } from '../error/indyError' +import { IndySdkPoolService } from '../ledger' +import { IndySdk, IndySdkSymbol } from '../types' +import { assertIndySdkWallet } from '../utils/assertIndySdkWallet' + +import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './didSovUtil' + +@injectable() +export class IndySdkSovDidRegistrar implements DidRegistrar { + public readonly supportedMethods = ['sov'] + private didRepository: DidRepository + private indySdk: IndySdk + private indySdkPoolService: IndySdkPoolService + + public constructor( + didRepository: DidRepository, + indySdkPoolService: IndySdkPoolService, + @inject(IndySdkSymbol) indySdk: IndySdk + ) { + this.didRepository = didRepository + this.indySdk = indySdk + this.indySdkPoolService = indySdkPoolService + } + + public async create(agentContext: AgentContext, options: IndySdkSovDidCreateOptions): Promise { + const { alias, role, submitterDid, indyNamespace } = options.options + const seed = options.secret?.seed + + if (seed && (typeof seed !== 'string' || seed.length !== 32)) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Invalid seed provided', + }, + } + } + + if (!submitterDid.startsWith('did:sov:')) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Submitter did must be a valid did:sov did', + }, + } + } + + try { + // NOTE: we need to use the createAndStoreMyDid method from indy to create the did + // If we just create a key and handle the creating of the did ourselves, indy will throw a + // WalletItemNotFound when it needs to sign ledger transactions using this did. This means we need + // to rely directly on the indy SDK, as we don't want to expose a createDid method just for. + assertIndySdkWallet(agentContext.wallet) + const [unqualifiedIndyDid, verkey] = await this.indySdk.createAndStoreMyDid(agentContext.wallet.handle, { + seed, + }) + + const qualifiedSovDid = `did:sov:${unqualifiedIndyDid}` + const unqualifiedSubmitterDid = submitterDid.replace('did:sov:', '') + + // TODO: it should be possible to pass the pool used for writing to the indy ledger service. + // The easiest way to do this would be to make the submitterDid a fully qualified did, including the indy namespace. + const pool = this.indySdkPoolService.getPoolForNamespace(indyNamespace) + await this.registerPublicDid(agentContext, unqualifiedSubmitterDid, unqualifiedIndyDid, verkey, alias, pool, role) + + // Create did document + const didDocumentBuilder = sovDidDocumentFromDid(qualifiedSovDid, verkey) + + // Add services if endpoints object was passed. + if (options.options.endpoints) { + await this.setEndpointsForDid(agentContext, unqualifiedIndyDid, options.options.endpoints, pool) + addServicesFromEndpointsAttrib( + didDocumentBuilder, + qualifiedSovDid, + options.options.endpoints, + `${qualifiedSovDid}#key-agreement-1` + ) + } + + // Build did document. + const didDocument = didDocumentBuilder.build() + + const didIndyNamespace = pool.config.indyNamespace + const qualifiedIndyDid = `did:indy:${didIndyNamespace}:${unqualifiedIndyDid}` + + // Save the did so we know we created it and can issue with it + const didRecord = new DidRecord({ + id: qualifiedSovDid, + did: qualifiedSovDid, + role: DidDocumentRole.Created, + tags: { + recipientKeyFingerprints: didDocument.recipientKeys.map((key: Key) => key.fingerprint), + qualifiedIndyDid, + }, + }) + await this.didRepository.save(agentContext, didRecord) + + return { + didDocumentMetadata: { + qualifiedIndyDid, + }, + didRegistrationMetadata: { + didIndyNamespace, + }, + didState: { + state: 'finished', + did: qualifiedSovDid, + didDocument, + secret: { + // FIXME: the uni-registrar creates the seed in the registrar method + // if it doesn't exist so the seed can always be returned. Currently + // we can only return it if the seed was passed in by the user. Once + // we have a secure method for generating seeds we should use the same + // approach + seed: options.secret?.seed, + }, + }, + } + } catch (error) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async update(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:sov not implemented yet`, + }, + } + } + + public async deactivate(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:sov not implemented yet`, + }, + } + } + + public async registerPublicDid( + agentContext: AgentContext, + submitterDid: string, + targetDid: string, + verkey: string, + alias: string, + pool: IndySdkPool, + role?: NymRole + ) { + try { + agentContext.config.logger.debug(`Register public did '${targetDid}' on ledger '${pool.didIndyNamespace}'`) + + const request = await this.indySdk.buildNymRequest(submitterDid, targetDid, verkey, alias, role || null) + + const response = await this.indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterDid) + + agentContext.config.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.didIndyNamespace}'`, { + response, + }) + + return targetDid + } catch (error) { + agentContext.config.logger.error( + `Error registering public did '${targetDid}' on ledger '${pool.didIndyNamespace}'`, + { + error, + submitterDid, + targetDid, + verkey, + alias, + role, + pool: pool.didIndyNamespace, + } + ) + + throw error + } + } + + public async setEndpointsForDid( + agentContext: AgentContext, + did: string, + endpoints: IndyEndpointAttrib, + pool: IndySdkPool + ): Promise { + try { + agentContext.config.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.didIndyNamespace}'`, endpoints) + + const request = await this.indySdk.buildAttribRequest(did, did, null, { endpoint: endpoints }, null) + + const response = await this.indySdkPoolService.submitWriteRequest(agentContext, pool, request, did) + agentContext.config.logger.debug( + `Successfully set endpoints for did '${did}' on ledger '${pool.didIndyNamespace}'`, + { + response, + endpoints, + } + ) + } catch (error) { + agentContext.config.logger.error( + `Error setting endpoints for did '${did}' on ledger '${pool.didIndyNamespace}'`, + { + error, + did, + endpoints, + } + ) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } +} + +export interface IndySdkSovDidCreateOptions extends DidCreateOptions { + method: 'sov' + did?: undefined + // As did:sov is so limited, we require everything needed to construct the did document to be passed + // through the options object. Once we support did:indy we can allow the didDocument property. + didDocument?: never + options: { + alias: string + role?: NymRole + endpoints?: IndyEndpointAttrib + indyNamespace?: string + submitterDid: string + } + secret?: { + seed?: string + } +} + +// Update and Deactivate not supported for did:sov +export type IndySdkSovDidUpdateOptions = never +export type IndySdkSovDidDeactivateOptions = never diff --git a/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts new file mode 100644 index 0000000000..c4d584568c --- /dev/null +++ b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts @@ -0,0 +1,95 @@ +import type { IndyEndpointAttrib } from './didSovUtil' +import type { DidResolutionResult, ParsedDid, DidResolver, AgentContext } from '@aries-framework/core' + +import { inject, injectable } from '@aries-framework/core' + +import { isIndyError, IndySdkError } from '../error' +import { IndySdkPoolService } from '../ledger/IndySdkPoolService' +import { IndySdkSymbol, IndySdk } from '../types' + +import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './didSovUtil' + +@injectable() +export class IndySdkSovDidResolver implements DidResolver { + private indySdk: IndySdk + private indySdkPoolService: IndySdkPoolService + + public constructor(indyPoolService: IndySdkPoolService, @inject(IndySdkSymbol) indySdk: IndySdk) { + this.indySdk = indySdk + this.indySdkPoolService = indyPoolService + } + + public readonly supportedMethods = ['sov'] + + public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { + const didDocumentMetadata = {} + + try { + const nym = await this.getPublicDid(agentContext, parsed.id) + const endpoints = await this.getEndpointsForDid(agentContext, parsed.id) + + const keyAgreementId = `${parsed.did}#key-agreement-1` + const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) + addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) + + return { + didDocument: builder.build(), + didDocumentMetadata, + didResolutionMetadata: { contentType: 'application/did+ld+json' }, + } + } catch (error) { + return { + didDocument: null, + didDocumentMetadata, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did '${did}': ${error}`, + }, + } + } + } + + private async getPublicDid(agentContext: AgentContext, did: string) { + // Getting the pool for a did also retrieves the DID. We can just use that + const { did: didResponse } = await this.indySdkPoolService.getPoolForDid(agentContext, did) + + return didResponse + } + + private async getEndpointsForDid(agentContext: AgentContext, did: string) { + const { pool } = await this.indySdkPoolService.getPoolForDid(agentContext, did) + + try { + agentContext.config.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.didIndyNamespace}'`) + + const request = await this.indySdk.buildGetAttribRequest(null, did, 'endpoint', null, null) + + agentContext.config.logger.debug( + `Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.didIndyNamespace}'` + ) + const response = await this.indySdkPoolService.submitReadRequest(pool, request) + + if (!response.result.data) return {} + + const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib + agentContext.config.logger.debug( + `Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.didIndyNamespace}'`, + { + response, + endpoints, + } + ) + + return endpoints ?? {} + } catch (error) { + agentContext.config.logger.error( + `Error retrieving endpoints for did '${did}' from ledger '${pool.didIndyNamespace}'`, + { + error, + } + ) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } +} diff --git a/packages/indy-sdk/src/dids/didSovUtil.ts b/packages/indy-sdk/src/dids/didSovUtil.ts new file mode 100644 index 0000000000..b5af6ee3f0 --- /dev/null +++ b/packages/indy-sdk/src/dids/didSovUtil.ts @@ -0,0 +1,132 @@ +import { + TypedArrayEncoder, + DidDocumentService, + DidDocumentBuilder, + DidCommV1Service, + DidCommV2Service, + convertPublicKeyToX25519, +} from '@aries-framework/core' + +import { getFullVerkey } from '../utils/did' + +export interface IndyEndpointAttrib { + endpoint?: string + types?: Array<'endpoint' | 'did-communication' | 'DIDComm'> + routingKeys?: string[] + [key: string]: unknown +} + +export function sovDidDocumentFromDid(fullDid: string, verkey: string) { + const verificationMethodId = `${fullDid}#key-1` + const keyAgreementId = `${fullDid}#key-agreement-1` + + const publicKeyBase58 = getFullVerkey(fullDid, verkey) + const publicKeyX25519 = TypedArrayEncoder.toBase58( + convertPublicKeyToX25519(TypedArrayEncoder.fromBase58(publicKeyBase58)) + ) + + const builder = new DidDocumentBuilder(fullDid) + .addContext('https://w3id.org/security/suites/ed25519-2018/v1') + .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addVerificationMethod({ + controller: fullDid, + id: verificationMethodId, + publicKeyBase58: publicKeyBase58, + type: 'Ed25519VerificationKey2018', + }) + .addVerificationMethod({ + controller: fullDid, + id: keyAgreementId, + publicKeyBase58: publicKeyX25519, + type: 'X25519KeyAgreementKey2019', + }) + .addAuthentication(verificationMethodId) + .addAssertionMethod(verificationMethodId) + .addKeyAgreement(keyAgreementId) + + return builder +} + +// Process Indy Attrib Endpoint Types according to: https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html > Read (Resolve) > DID Service Endpoint +function processEndpointTypes(types?: string[]) { + const expectedTypes = ['endpoint', 'did-communication', 'DIDComm'] + const defaultTypes = ['endpoint', 'did-communication'] + + // Return default types if types "is NOT present [or] empty" + if (!types || types.length <= 0) { + return defaultTypes + } + + // Return default types if types "contain any other values" + for (const type of types) { + if (!expectedTypes.includes(type)) { + return defaultTypes + } + } + + // Return provided types + return types +} + +export function addServicesFromEndpointsAttrib( + builder: DidDocumentBuilder, + did: string, + endpoints: IndyEndpointAttrib, + keyAgreementId: string +) { + const { endpoint, routingKeys, types, ...otherEndpoints } = endpoints + + if (endpoint) { + const processedTypes = processEndpointTypes(types) + + // If 'endpoint' included in types, add id to the services array + if (processedTypes.includes('endpoint')) { + builder.addService( + new DidDocumentService({ + id: `${did}#endpoint`, + serviceEndpoint: endpoint, + type: 'endpoint', + }) + ) + } + + // If 'did-communication' included in types, add DIDComm v1 entry + if (processedTypes.includes('did-communication')) { + builder.addService( + new DidCommV1Service({ + id: `${did}#did-communication`, + serviceEndpoint: endpoint, + priority: 0, + routingKeys: routingKeys ?? [], + recipientKeys: [keyAgreementId], + accept: ['didcomm/aip2;env=rfc19'], + }) + ) + + // If 'DIDComm' included in types, add DIDComm v2 entry + if (processedTypes.includes('DIDComm')) { + builder + .addService( + new DidCommV2Service({ + id: `${did}#didcomm-1`, + serviceEndpoint: endpoint, + routingKeys: routingKeys ?? [], + accept: ['didcomm/v2'], + }) + ) + .addContext('https://didcomm.org/messaging/contexts/v2') + } + } + } + + // Add other endpoint types + for (const [type, endpoint] of Object.entries(otherEndpoints)) { + builder.addService( + new DidDocumentService({ + id: `${did}#${type}`, + serviceEndpoint: endpoint as string, + type, + }) + ) + } +} diff --git a/packages/indy-sdk/src/dids/index.ts b/packages/indy-sdk/src/dids/index.ts new file mode 100644 index 0000000000..68eabe204d --- /dev/null +++ b/packages/indy-sdk/src/dids/index.ts @@ -0,0 +1,7 @@ +export { + IndySdkSovDidRegistrar, + IndySdkSovDidCreateOptions, + IndySdkSovDidDeactivateOptions, + IndySdkSovDidUpdateOptions, +} from './IndySdkSovDidRegistrar' +export { IndySdkSovDidResolver } from './IndySdkSovDidResolver' diff --git a/packages/indy-sdk/src/error/IndySdkError.ts b/packages/indy-sdk/src/error/IndySdkError.ts new file mode 100644 index 0000000000..4b67802a9a --- /dev/null +++ b/packages/indy-sdk/src/error/IndySdkError.ts @@ -0,0 +1,11 @@ +import type { IndyError } from './indyError' + +import { AriesFrameworkError } from '@aries-framework/core' + +export class IndySdkError extends AriesFrameworkError { + public constructor(indyError: IndyError, message?: string) { + const base = `${indyError.name}(${indyError.indyName}): ${indyError.message}` + + super(message ? `${message}: ${base}` : base, { cause: indyError }) + } +} diff --git a/packages/indy-sdk/src/error/index.ts b/packages/indy-sdk/src/error/index.ts new file mode 100644 index 0000000000..5829a46d0a --- /dev/null +++ b/packages/indy-sdk/src/error/index.ts @@ -0,0 +1,2 @@ +export * from './IndySdkError' +export * from './indyError' diff --git a/packages/indy-sdk/src/error/indyError.ts b/packages/indy-sdk/src/error/indyError.ts new file mode 100644 index 0000000000..5d67cfdbf1 --- /dev/null +++ b/packages/indy-sdk/src/error/indyError.ts @@ -0,0 +1,100 @@ +import { AriesFrameworkError } from '@aries-framework/core' + +export const indyErrors = { + 100: 'CommonInvalidParam1', + 101: 'CommonInvalidParam2', + 102: 'CommonInvalidParam3', + 103: 'CommonInvalidParam4', + 104: 'CommonInvalidParam5', + 105: 'CommonInvalidParam6', + 106: 'CommonInvalidParam7', + 107: 'CommonInvalidParam8', + 108: 'CommonInvalidParam9', + 109: 'CommonInvalidParam10', + 110: 'CommonInvalidParam11', + 111: 'CommonInvalidParam12', + 112: 'CommonInvalidState', + 113: 'CommonInvalidStructure', + 114: 'CommonIOError', + 115: 'CommonInvalidParam13', + 116: 'CommonInvalidParam14', + 200: 'WalletInvalidHandle', + 201: 'WalletUnknownTypeError', + 202: 'WalletTypeAlreadyRegisteredError', + 203: 'WalletAlreadyExistsError', + 204: 'WalletNotFoundError', + 205: 'WalletIncompatiblePoolError', + 206: 'WalletAlreadyOpenedError', + 207: 'WalletAccessFailed', + 208: 'WalletInputError', + 209: 'WalletDecodingError', + 210: 'WalletStorageError', + 211: 'WalletEncryptionError', + 212: 'WalletItemNotFound', + 213: 'WalletItemAlreadyExists', + 214: 'WalletQueryError', + 300: 'PoolLedgerNotCreatedError', + 301: 'PoolLedgerInvalidPoolHandle', + 302: 'PoolLedgerTerminated', + 303: 'LedgerNoConsensusError', + 304: 'LedgerInvalidTransaction', + 305: 'LedgerSecurityError', + 306: 'PoolLedgerConfigAlreadyExistsError', + 307: 'PoolLedgerTimeout', + 308: 'PoolIncompatibleProtocolVersion', + 309: 'LedgerNotFound', + 400: 'AnoncredsRevocationRegistryFullError', + 401: 'AnoncredsInvalidUserRevocId', + 404: 'AnoncredsMasterSecretDuplicateNameError', + 405: 'AnoncredsProofRejected', + 406: 'AnoncredsCredentialRevoked', + 407: 'AnoncredsCredDefAlreadyExistsError', + 500: 'UnknownCryptoTypeError', + 600: 'DidAlreadyExistsError', + 700: 'PaymentUnknownMethodError', + 701: 'PaymentIncompatibleMethodsError', + 702: 'PaymentInsufficientFundsError', + 703: 'PaymentSourceDoesNotExistError', + 704: 'PaymentOperationNotSupportedError', + 705: 'PaymentExtraFundsError', + 706: 'TransactionNotAllowedError', +} as const + +type IndyErrorValues = typeof indyErrors[keyof typeof indyErrors] + +export interface IndyError { + name: 'IndyError' + message: string + indyName?: string +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function isIndyError(error: any, errorName?: IndyErrorValues): error is IndyError { + if (typeof error !== 'object' || error === null) return false + + const indyError = error.name === 'IndyError' + + // if no specific indy error name is passed + // or the error is no indy error + // we can already return + if (!indyError || !errorName) return indyError + + // NodeJS Wrapper is missing some type names. When a type is missing it will + // only have the error code as string in the message field + // Until that is fixed we take that into account to make AFJ work with rn-indy-sdk + // See: https://github.com/AbsaOSS/rn-indy-sdk/pull/24 + // See: https://github.com/hyperledger/indy-sdk/pull/2283 + if (!error.indyName) { + const errorCode = Number(error.message) + if (!isNaN(errorCode) && Object.prototype.hasOwnProperty.call(indyErrors, errorCode)) { + // We already check if the property is set. We can safely ignore this typescript error + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return errorName === indyErrors[errorCode] + } + + throw new AriesFrameworkError(`Could not determine errorName of indyError ${error.message}`) + } + + return error.indyName === errorName +} diff --git a/packages/indy-sdk/src/index.ts b/packages/indy-sdk/src/index.ts new file mode 100644 index 0000000000..ea099b7cf4 --- /dev/null +++ b/packages/indy-sdk/src/index.ts @@ -0,0 +1,26 @@ +// Dids +export { + IndySdkSovDidRegistrar, + IndySdkSovDidCreateOptions, + IndySdkSovDidDeactivateOptions, + IndySdkSovDidUpdateOptions, + IndySdkSovDidResolver, +} from './dids' + +// Wallet +export { IndySdkWallet } from './wallet' + +// Storage +export { IndySdkStorageService } from './storage' + +// AnonCreds +export { + IndySdkAnonCredsRegistry, + IndySdkHolderService, + IndySdkIssuerService, + IndySdkVerifierService, +} from './anoncreds' + +// Module +export { IndySdkModule } from './IndySdkModule' +export { IndySdkModuleConfig } from './IndySdkModuleConfig' diff --git a/packages/indy-sdk/src/ledger/IndySdkPool.ts b/packages/indy-sdk/src/ledger/IndySdkPool.ts new file mode 100644 index 0000000000..a24a1c7ba5 --- /dev/null +++ b/packages/indy-sdk/src/ledger/IndySdkPool.ts @@ -0,0 +1,208 @@ +import type { IndySdk } from '../types' +import type { FileSystem, Logger } from '@aries-framework/core' +import type { LedgerReadReplyResponse, LedgerRequest, LedgerWriteReplyResponse } from 'indy-sdk' +import type { Subject } from 'rxjs' + +import { AriesFrameworkError } from '@aries-framework/core' + +import { isIndyError, IndySdkError } from '../error' + +import { IndySdkPoolError } from './error' +import { isLedgerRejectResponse, isLedgerReqnackResponse } from './util' + +export interface TransactionAuthorAgreement { + version: `${number}.${number}` | `${number}` + acceptanceMechanism: string +} + +export interface IndySdkPoolConfig { + genesisPath?: string + genesisTransactions?: string + id: string + isProduction: boolean + indyNamespace: string + transactionAuthorAgreement?: TransactionAuthorAgreement +} + +export class IndySdkPool { + private indySdk: IndySdk + private logger: Logger + private fileSystem: FileSystem + private poolConfig: IndySdkPoolConfig + private _poolHandle?: number + private poolConnected?: Promise + public authorAgreement?: AuthorAgreement | null + + public constructor( + poolConfig: IndySdkPoolConfig, + indySdk: IndySdk, + logger: Logger, + stop$: Subject, + fileSystem: FileSystem + ) { + this.indySdk = indySdk + this.fileSystem = fileSystem + this.poolConfig = poolConfig + this.logger = logger + + // Listen to stop$ (shutdown) and close pool + stop$.subscribe(async () => { + if (this._poolHandle) { + await this.close() + } + }) + } + + public get didIndyNamespace(): string { + return this.didIndyNamespace + } + + public get id() { + return this.poolConfig.id + } + + public get config() { + return this.poolConfig + } + + public async close() { + const poolHandle = this._poolHandle + + if (!poolHandle) { + return + } + + this._poolHandle = undefined + this.poolConnected = undefined + + await this.indySdk.closePoolLedger(poolHandle) + } + + public async delete() { + // Close the pool if currently open + if (this._poolHandle) { + await this.close() + } + + await this.indySdk.deletePoolLedgerConfig(this.poolConfig.id) + } + + public async connect() { + if (!this.poolConnected) { + // Save the promise of connectToLedger to determine if we are done connecting + this.poolConnected = this.connectToLedger() + this.poolConnected.catch((error) => { + // Set poolConnected to undefined so we can retry connection upon failure + this.poolConnected = undefined + this.logger.error('Connection to pool: ' + this.poolConfig.genesisPath + ' failed.', { error }) + }) + return this.poolConnected + } else { + throw new AriesFrameworkError('Cannot attempt connection to ledger, already connecting.') + } + } + + private async connectToLedger() { + const poolName = this.poolConfig.id + const genesisPath = await this.getGenesisPath() + + if (!genesisPath) { + throw new AriesFrameworkError('Cannot connect to ledger without genesis file') + } + + this.logger.debug(`Connecting to ledger pool '${poolName}'`, { genesisPath }) + await this.indySdk.setProtocolVersion(2) + + try { + this._poolHandle = await this.indySdk.openPoolLedger(poolName) + return this._poolHandle + } catch (error) { + if (!isIndyError(error, 'PoolLedgerNotCreatedError')) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + this.logger.debug(`Pool '${poolName}' does not exist yet, creating.`, { + indyError: 'PoolLedgerNotCreatedError', + }) + try { + await this.indySdk.createPoolLedgerConfig(poolName, { genesis_txn: genesisPath }) + this._poolHandle = await this.indySdk.openPoolLedger(poolName) + return this._poolHandle + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + private async submitRequest(request: LedgerRequest) { + return this.indySdk.submitRequest(await this.getPoolHandle(), request) + } + + public async submitReadRequest(request: LedgerRequest) { + const response = await this.submitRequest(request) + + if (isLedgerRejectResponse(response) || isLedgerReqnackResponse(response)) { + throw new IndySdkPoolError(`Ledger '${this.id}' rejected read transaction request: ${response.reason}`) + } + + return response as LedgerReadReplyResponse + } + + public async submitWriteRequest(request: LedgerRequest) { + const response = await this.submitRequest(request) + + if (isLedgerRejectResponse(response) || isLedgerReqnackResponse(response)) { + throw new IndySdkPoolError(`Ledger '${this.id}' rejected write transaction request: ${response.reason}`) + } + + return response as LedgerWriteReplyResponse + } + + private async getPoolHandle() { + if (this.poolConnected) { + // If we have tried to already connect to pool wait for it + try { + await this.poolConnected + } catch (error) { + this.logger.error('Connection to pool: ' + this.poolConfig.genesisPath + ' failed.', { error }) + } + } + + if (!this._poolHandle) { + return this.connect() + } + + return this._poolHandle + } + + private async getGenesisPath() { + // If the path is already provided return it + if (this.poolConfig.genesisPath) return this.poolConfig.genesisPath + + // Determine the genesisPath + const genesisPath = this.fileSystem.basePath + `/afj/genesis-${this.poolConfig.id}.txn` + // Store genesis data if provided + if (this.poolConfig.genesisTransactions) { + await this.fileSystem.write(genesisPath, this.poolConfig.genesisTransactions) + this.poolConfig.genesisPath = genesisPath + return genesisPath + } + + // No genesisPath + return null + } +} + +export interface AuthorAgreement { + digest: string + version: string + text: string + ratification_ts: number + acceptanceMechanisms: AcceptanceMechanisms +} + +export interface AcceptanceMechanisms { + aml: Record + amlContext: string + version: string +} diff --git a/packages/indy-sdk/src/ledger/IndySdkPoolService.ts b/packages/indy-sdk/src/ledger/IndySdkPoolService.ts new file mode 100644 index 0000000000..773b7db2cc --- /dev/null +++ b/packages/indy-sdk/src/ledger/IndySdkPoolService.ts @@ -0,0 +1,338 @@ +import type { AcceptanceMechanisms, AuthorAgreement, IndySdkPoolConfig } from './IndySdkPool' +import type { AgentContext } from '@aries-framework/core' +import type { GetNymResponse, LedgerReadReplyResponse, LedgerRequest, LedgerWriteReplyResponse } from 'indy-sdk' + +import { + InjectionSymbols, + Logger, + injectable, + inject, + FileSystem, + CacheRepository, + PersistedLruCache, +} from '@aries-framework/core' +import { Subject } from 'rxjs' + +import { IndySdkError, isIndyError } from '../error' +import { IndySdk } from '../types' +import { assertIndySdkWallet } from '../utils/assertIndySdkWallet' +import { isSelfCertifiedDid } from '../utils/did' +import { allSettled, onlyFulfilled, onlyRejected } from '../utils/promises' + +import { IndySdkPool } from './IndySdkPool' +import { IndySdkPoolError, IndySdkPoolNotConfiguredError, IndySdkPoolNotFoundError } from './error' + +export const INDY_SDK_DID_POOL_CACHE_ID = 'INDY_SDK_DID_POOL_CACHE' +export const INDY_SDK_DID_POOL_CACHE_LIMIT = 500 +export interface CachedDidResponse { + nymResponse: GetNymResponse + poolId: string +} + +@injectable() +export class IndySdkPoolService { + public pools: IndySdkPool[] = [] + private logger: Logger + private indySdk: IndySdk + private stop$: Subject + private fileSystem: FileSystem + private didCache: PersistedLruCache + + public constructor( + cacheRepository: CacheRepository, + indySdk: IndySdk, + @inject(InjectionSymbols.Logger) logger: Logger, + @inject(InjectionSymbols.Stop$) stop$: Subject, + @inject(InjectionSymbols.FileSystem) fileSystem: FileSystem + ) { + this.logger = logger + this.indySdk = indySdk + this.fileSystem = fileSystem + this.stop$ = stop$ + + this.didCache = new PersistedLruCache(INDY_SDK_DID_POOL_CACHE_ID, INDY_SDK_DID_POOL_CACHE_LIMIT, cacheRepository) + } + + public setPools(poolConfigs: IndySdkPoolConfig[]) { + this.pools = poolConfigs.map( + (poolConfig) => new IndySdkPool(poolConfig, this.indySdk, this.logger, this.stop$, this.fileSystem) + ) + } + + /** + * Create connections to all ledger pools + */ + public async connectToPools() { + const handleArray: number[] = [] + // Sequentially connect to pools so we don't use up too many resources connecting in parallel + for (const pool of this.pools) { + this.logger.debug(`Connecting to pool: ${pool.id}`) + const poolHandle = await pool.connect() + this.logger.debug(`Finished connection to pool: ${pool.id}`) + handleArray.push(poolHandle) + } + return handleArray + } + + /** + * Get the most appropriate pool for the given did. The algorithm is based on the approach as described in this document: + * https://docs.google.com/document/d/109C_eMsuZnTnYe2OAd02jAts1vC4axwEKIq7_4dnNVA/edit + */ + public async getPoolForDid( + agentContext: AgentContext, + did: string + ): Promise<{ pool: IndySdkPool; did: GetNymResponse }> { + const pools = this.pools + + if (pools.length === 0) { + throw new IndySdkPoolNotConfiguredError( + "No indy ledgers configured. Provide at least one pool configuration in the 'indyLedgers' agent configuration" + ) + } + + const cachedNymResponse = await this.didCache.get(agentContext, did) + const pool = this.pools.find((pool) => pool.id === cachedNymResponse?.poolId) + + // If we have the nym response with associated pool in the cache, we'll use that + if (cachedNymResponse && pool) { + this.logger.trace(`Found ledger id '${pool.id}' for did '${did}' in cache`) + return { did: cachedNymResponse.nymResponse, pool } + } + + const { successful, rejected } = await this.getSettledDidResponsesFromPools(did, pools) + + if (successful.length === 0) { + const allNotFound = rejected.every((e) => e.reason instanceof IndySdkPoolNotFoundError) + const rejectedOtherThanNotFound = rejected.filter((e) => !(e.reason instanceof IndySdkPoolNotFoundError)) + + // All ledgers returned response that the did was not found + if (allNotFound) { + throw new IndySdkPoolNotFoundError(`Did '${did}' not found on any of the ledgers (total ${this.pools.length}).`) + } + + // one or more of the ledgers returned an unknown error + throw new IndySdkPoolError( + `Unknown error retrieving did '${did}' from '${rejectedOtherThanNotFound.length}' of '${pools.length}' ledgers`, + { cause: rejectedOtherThanNotFound[0].reason } + ) + } + + // If there are self certified DIDs we always prefer it over non self certified DIDs + // We take the first self certifying DID as we take the order in the + // indyLedgers config as the order of preference of ledgers + let value = successful.find((response) => + isSelfCertifiedDid(response.value.did.did, response.value.did.verkey) + )?.value + + if (!value) { + // Split between production and nonProduction ledgers. If there is at least one + // successful response from a production ledger, only keep production ledgers + // otherwise we only keep the non production ledgers. + const production = successful.filter((s) => s.value.pool.config.isProduction) + const nonProduction = successful.filter((s) => !s.value.pool.config.isProduction) + const productionOrNonProduction = production.length >= 1 ? production : nonProduction + + // We take the first value as we take the order in the indyLedgers config as + // the order of preference of ledgers + value = productionOrNonProduction[0].value + } + + await this.didCache.set(agentContext, did, { + nymResponse: value.did, + poolId: value.pool.id, + }) + return { pool: value.pool, did: value.did } + } + + private async getSettledDidResponsesFromPools(did: string, pools: IndySdkPool[]) { + this.logger.trace(`Retrieving did '${did}' from ${pools.length} ledgers`) + const didResponses = await allSettled(pools.map((pool) => this.getDidFromPool(did, pool))) + + const successful = onlyFulfilled(didResponses) + this.logger.trace(`Retrieved ${successful.length} responses from ledgers for did '${did}'`) + + const rejected = onlyRejected(didResponses) + + return { + rejected, + successful, + } + } + + /** + * Get the most appropriate pool for the given indyNamespace + */ + public getPoolForNamespace(indyNamespace?: string) { + if (this.pools.length === 0) { + throw new IndySdkPoolNotConfiguredError( + "No indy ledgers configured. Provide at least one pool configuration in the 'indyLedgers' agent configuration" + ) + } + + if (!indyNamespace) { + this.logger.warn('Not passing the indyNamespace is deprecated and will be removed in the future version.') + return this.pools[0] + } + + const pool = this.pools.find((pool) => pool.didIndyNamespace === indyNamespace) + + if (!pool) { + throw new IndySdkPoolNotFoundError(`No ledgers found for IndyNamespace '${indyNamespace}'.`) + } + + return pool + } + + public async submitWriteRequest( + agentContext: AgentContext, + pool: IndySdkPool, + request: LedgerRequest, + signDid: string + ): Promise { + try { + const requestWithTaa = await this.appendTaa(pool, request) + const signedRequestWithTaa = await this.signRequest(agentContext, signDid, requestWithTaa) + + const response = await pool.submitWriteRequest(signedRequestWithTaa) + + return response + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async submitReadRequest(pool: IndySdkPool, request: LedgerRequest): Promise { + try { + const response = await pool.submitReadRequest(request) + + return response + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + private async signRequest(agentContext: AgentContext, did: string, request: LedgerRequest): Promise { + assertIndySdkWallet(agentContext.wallet) + + try { + return this.indySdk.signRequest(agentContext.wallet.handle, did, request) + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + private async appendTaa(pool: IndySdkPool, request: LedgerRequest) { + try { + const authorAgreement = await this.getTransactionAuthorAgreement(pool) + const taa = pool.config.transactionAuthorAgreement + + // If ledger does not have TAA, we can just send request + if (authorAgreement == null) { + return request + } + // Ledger has taa but user has not specified which one to use + if (!taa) { + throw new IndySdkPoolError( + `Please, specify a transaction author agreement with version and acceptance mechanism. ${JSON.stringify( + authorAgreement + )}` + ) + } + + // Throw an error if the pool doesn't have the specified version and acceptance mechanism + if ( + authorAgreement.version !== taa.version || + !(taa.acceptanceMechanism in authorAgreement.acceptanceMechanisms.aml) + ) { + // Throw an error with a helpful message + const errMessage = `Unable to satisfy matching TAA with mechanism ${JSON.stringify( + taa.acceptanceMechanism + )} and version ${JSON.stringify(taa.version)} in pool.\n Found ${JSON.stringify( + Object.keys(authorAgreement.acceptanceMechanisms.aml) + )} and version ${authorAgreement.version} in pool.` + throw new IndySdkPoolError(errMessage) + } + + const requestWithTaa = await this.indySdk.appendTxnAuthorAgreementAcceptanceToRequest( + request, + authorAgreement.text, + taa.version, + authorAgreement.digest, + taa.acceptanceMechanism, + // Current time since epoch + // We can't use ratification_ts, as it must be greater than 1499906902 + Math.floor(new Date().getTime() / 1000) + ) + + return requestWithTaa + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + private async getTransactionAuthorAgreement(pool: IndySdkPool): Promise { + try { + // TODO Replace this condition with memoization + if (pool.authorAgreement !== undefined) { + return pool.authorAgreement + } + + const taaRequest = await this.indySdk.buildGetTxnAuthorAgreementRequest(null) + const taaResponse = await this.submitReadRequest(pool, taaRequest) + const acceptanceMechanismRequest = await this.indySdk.buildGetAcceptanceMechanismsRequest(null) + const acceptanceMechanismResponse = await this.submitReadRequest(pool, acceptanceMechanismRequest) + + // TAA can be null + if (taaResponse.result.data == null) { + pool.authorAgreement = null + return null + } + + // If TAA is not null, we can be sure AcceptanceMechanisms is also not null + const authorAgreement = taaResponse.result.data as AuthorAgreement + const acceptanceMechanisms = acceptanceMechanismResponse.result.data as AcceptanceMechanisms + pool.authorAgreement = { + ...authorAgreement, + acceptanceMechanisms, + } + return pool.authorAgreement + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + private async getDidFromPool(did: string, pool: IndySdkPool): Promise { + try { + this.logger.trace(`Get public did '${did}' from ledger '${pool.id}'`) + const request = await this.indySdk.buildGetNymRequest(null, did) + + this.logger.trace(`Submitting get did request for did '${did}' to ledger '${pool.id}'`) + const response = await pool.submitReadRequest(request) + + const result = await this.indySdk.parseGetNymResponse(response) + this.logger.trace(`Retrieved did '${did}' from ledger '${pool.id}'`, result) + + return { + did: result, + pool, + response, + } + } catch (error) { + this.logger.trace(`Error retrieving did '${did}' from ledger '${pool.id}'`, { + error, + did, + }) + if (isIndyError(error, 'LedgerNotFound')) { + throw new IndySdkPoolNotFoundError(`Did '${did}' not found on ledger ${pool.id}`) + } else { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + } +} + +export interface PublicDidRequest { + did: GetNymResponse + pool: IndySdkPool + response: LedgerReadReplyResponse +} diff --git a/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts b/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts new file mode 100644 index 0000000000..74debe2656 --- /dev/null +++ b/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts @@ -0,0 +1,444 @@ +import type { IndySdkPoolConfig } from '../IndySdkPool' +import type { CachedDidResponse } from '../IndySdkPoolService' +import type { AgentContext } from '@aries-framework/core' + +import { SigningProviderRegistry, AriesFrameworkError } from '@aries-framework/core' +import { Subject } from 'rxjs' + +import { CacheRecord } from '../../../../core/src/cache' +import { CacheRepository } from '../../../../core/src/cache/CacheRepository' +import { getDidResponsesForDid } from '../../../../core/src/modules/ledger/__tests__/didResponses' +import { agentDependencies, getAgentConfig, getAgentContext, mockFunction } from '../../../../core/tests/helpers' +import { NodeFileSystem } from '../../../../node/src/NodeFileSystem' +import { IndySdkWallet } from '../../wallet/IndySdkWallet' +import { INDY_SDK_DID_POOL_CACHE_ID, IndySdkPoolService } from '../IndySdkPoolService' +import { IndySdkPoolError, IndySdkPoolNotConfiguredError, IndySdkPoolNotFoundError } from '../error' + +jest.mock('../../../../core/src/cache/CacheRepository') +const CacheRepositoryMock = CacheRepository as jest.Mock + +const pools: IndySdkPoolConfig[] = [ + { + id: 'sovrinMain', + indyNamespace: 'sovrin', + isProduction: true, + genesisTransactions: 'xxx', + transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + }, + { + id: 'sovrinBuilder', + indyNamespace: 'sovrin:builder', + isProduction: false, + genesisTransactions: 'xxx', + transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + }, + { + id: 'sovringStaging', + indyNamespace: 'sovrin:staging', + isProduction: false, + genesisTransactions: 'xxx', + transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + }, + { + id: 'indicioMain', + indyNamespace: 'indicio', + isProduction: true, + genesisTransactions: 'xxx', + transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + }, + { + id: 'bcovrinTest', + indyNamespace: 'bcovrin:test', + isProduction: false, + genesisTransactions: 'xxx', + transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + }, +] + +describe('IndySdkPoolService', () => { + const config = getAgentConfig('IndySdkPoolServiceTest', { + indyLedgers: pools, + }) + let agentContext: AgentContext + let wallet: IndySdkWallet + let poolService: IndySdkPoolService + let cacheRepository: CacheRepository + + beforeAll(async () => { + wallet = new IndySdkWallet(config.agentDependencies.indy, config.logger, new SigningProviderRegistry([])) + agentContext = getAgentContext() + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await wallet.createAndOpen(config.walletConfig!) + }) + + afterAll(async () => { + await wallet.delete() + }) + + beforeEach(async () => { + cacheRepository = new CacheRepositoryMock() + mockFunction(cacheRepository.findById).mockResolvedValue(null) + + poolService = new IndySdkPoolService( + cacheRepository, + agentDependencies.indy, + config.logger, + new Subject(), + new NodeFileSystem() + ) + + poolService.setPools(pools) + }) + + describe('getPoolForDid', () => { + it('should throw a IndySdkPoolNotConfiguredError error if no pools are configured on the pool service', async () => { + poolService.setPools([]) + + expect(poolService.getPoolForDid(agentContext, 'some-did')).rejects.toThrow(IndySdkPoolNotConfiguredError) + }) + + it('should throw a IndySdkPoolError if all ledger requests throw an error other than NotFoundError', async () => { + const did = 'Y5bj4SjCiTM9PgeheKAiXx' + + poolService.pools.forEach((pool) => { + const spy = jest.spyOn(pool, 'submitReadRequest') + spy.mockImplementationOnce(() => Promise.reject(new AriesFrameworkError('Something went wrong'))) + }) + + expect(poolService.getPoolForDid(agentContext, did)).rejects.toThrowError(IndySdkPoolError) + }) + + it('should throw a IndySdkPoolNotFoundError if all pools did not find the did on the ledger', async () => { + const did = 'Y5bj4SjCiTM9PgeheKAiXx' + // Not found on any of the ledgers + const responses = getDidResponsesForDid(did, pools, {}) + + poolService.pools.forEach((pool, index) => { + const spy = jest.spyOn(pool, 'submitReadRequest') + spy.mockImplementationOnce(responses[index]) + }) + + expect(poolService.getPoolForDid(agentContext, did)).rejects.toThrowError(IndySdkPoolNotFoundError) + }) + + it('should return the pool if the did was only found on one ledger', async () => { + const did = 'TL1EaPFCZ8Si5aUrqScBDt' + // Only found on one ledger + const responses = getDidResponsesForDid(did, pools, { + sovrinMain: '~43X4NhAFqREffK7eWdKgFH', + }) + + poolService.pools.forEach((pool, index) => { + const spy = jest.spyOn(pool, 'submitReadRequest') + spy.mockImplementationOnce(responses[index]) + }) + + const { pool } = await poolService.getPoolForDid(agentContext, did) + + expect(pool.config.id).toBe('sovrinMain') + }) + + it('should return the first pool with a self certifying DID if at least one did is self certifying ', async () => { + const did = 'did:sov:q7ATwTYbQDgiigVijUAej' + // Found on one production and one non production ledger + const responses = getDidResponsesForDid(did, pools, { + indicioMain: '~43X4NhAFqREffK7eWdKgFH', + bcovrinTest: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', + sovrinBuilder: '~43X4NhAFqREffK7eWdKgFH', + }) + + poolService.pools.forEach((pool, index) => { + const spy = jest.spyOn(pool, 'submitReadRequest') + spy.mockImplementationOnce(responses[index]) + }) + + const { pool } = await poolService.getPoolForDid(agentContext, did) + + expect(pool.config.id).toBe('sovrinBuilder') + }) + + it('should return the production pool if the did was found on one production and one non production ledger and both DIDs are not self certifying', async () => { + const did = 'V6ty6ttM3EjuCtosH6sGtW' + // Found on one production and one non production ledger + const responses = getDidResponsesForDid(did, pools, { + indicioMain: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', + sovrinBuilder: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', + }) + + poolService.pools.forEach((pool, index) => { + const spy = jest.spyOn(pool, 'submitReadRequest') + spy.mockImplementationOnce(responses[index]) + }) + + const { pool } = await poolService.getPoolForDid(agentContext, did) + + expect(pool.config.id).toBe('indicioMain') + }) + + it('should return the pool with the self certified did if the did was found on two production ledgers where one did is self certified', async () => { + const did = 'VsKV7grR1BUE29mG2Fm2kX' + // Found on two production ledgers. Sovrin is self certified + const responses = getDidResponsesForDid(did, pools, { + sovrinMain: '~43X4NhAFqREffK7eWdKgFH', + indicioMain: 'kqa2HyagzfMAq42H5f9u3UMwnSBPQx2QfrSyXbUPxMn', + }) + + poolService.pools.forEach((pool, index) => { + const spy = jest.spyOn(pool, 'submitReadRequest') + spy.mockImplementationOnce(responses[index]) + }) + + const { pool } = await poolService.getPoolForDid(agentContext, did) + + expect(pool.config.id).toBe('sovrinMain') + }) + + it('should return the first pool with a self certified did if the did was found on three non production ledgers where two DIDs are self certified', async () => { + const did = 'HEi9QViXNThGQaDsQ3ptcw' + // Found on two non production ledgers. Sovrin is self certified + const responses = getDidResponsesForDid(did, pools, { + sovrinBuilder: '~M9kv2Ez61cur7X39DXWh8W', + sovrinStaging: '~M9kv2Ez61cur7X39DXWh8W', + bcovrinTest: '3SeuRm3uYuQDYmHeuMLu1xNHozNTtzS3kbZRFMMCWrX4', + }) + + poolService.pools.forEach((pool, index) => { + const spy = jest.spyOn(pool, 'submitReadRequest') + spy.mockImplementationOnce(responses[index]) + }) + + const { pool } = await poolService.getPoolForDid(agentContext, did) + + expect(pool.config.id).toBe('sovrinBuilder') + }) + + it('should return the pool from the cache if the did was found in the cache', async () => { + const did = 'HEi9QViXNThGQaDsQ3ptcw' + + const expectedPool = pools[3] + + const didResponse: CachedDidResponse = { + nymResponse: { + did, + role: 'ENDORSER', + verkey: '~M9kv2Ez61cur7X39DXWh8W', + }, + poolId: expectedPool.id, + } + + const cachedEntries = [ + { + key: did, + value: didResponse, + }, + ] + + mockFunction(cacheRepository.findById).mockResolvedValue( + new CacheRecord({ + id: INDY_SDK_DID_POOL_CACHE_ID, + entries: cachedEntries, + }) + ) + + const { pool } = await poolService.getPoolForDid(agentContext, did) + + expect(pool.config.id).toBe(pool.id) + }) + + it('should set the poolId in the cache if the did was not found in the cache, but resolved later on', async () => { + const did = 'HEi9QViXNThGQaDsQ3ptcw' + // Found on one ledger + const responses = getDidResponsesForDid(did, pools, { + sovrinBuilder: '~M9kv2Ez61cur7X39DXWh8W', + }) + + mockFunction(cacheRepository.findById).mockResolvedValue( + new CacheRecord({ + id: INDY_SDK_DID_POOL_CACHE_ID, + entries: [], + }) + ) + + const spy = mockFunction(cacheRepository.update).mockResolvedValue() + + poolService.pools.forEach((pool, index) => { + const spy = jest.spyOn(pool, 'submitReadRequest') + spy.mockImplementationOnce(responses[index]) + }) + + const { pool } = await poolService.getPoolForDid(agentContext, did) + + expect(pool.config.id).toBe('sovrinBuilder') + expect(pool.config.indyNamespace).toBe('sovrin:builder') + + const cacheRecord = spy.mock.calls[0][1] + expect(cacheRecord.entries.length).toBe(1) + expect(cacheRecord.entries[0].key).toBe(did) + expect(cacheRecord.entries[0].value).toEqual({ + nymResponse: { + did, + verkey: '~M9kv2Ez61cur7X39DXWh8W', + role: '0', + }, + poolId: 'sovrinBuilder', + }) + }) + }) + + describe('getPoolForNamespace', () => { + it('should throw a IndySdkPoolNotConfiguredError error if no pools are configured on the pool service', async () => { + poolService.setPools([]) + + expect(() => poolService.getPoolForNamespace()).toThrow(IndySdkPoolNotConfiguredError) + }) + + it('should return the first pool if indyNamespace is not provided', async () => { + const expectedPool = pools[0] + + expect(poolService.getPoolForNamespace().id).toEqual(expectedPool.id) + }) + + it('should throw a IndySdkPoolNotFoundError error if any of the pools did not have the provided indyNamespace', async () => { + const indyNameSpace = 'test' + const responses = pools.map((pool) => pool.indyNamespace) + + poolService.pools.forEach((pool, index) => { + const spy = jest.spyOn(pool, 'didIndyNamespace', 'get') + spy.mockReturnValueOnce(responses[index]) + }) + + expect(() => poolService.getPoolForNamespace(indyNameSpace)).toThrow(IndySdkPoolNotFoundError) + }) + + it('should return the first pool that indyNamespace matches', async () => { + const expectedPool = pools[3] + const indyNameSpace = 'indicio' + const responses = pools.map((pool) => pool.indyNamespace) + + poolService.pools.forEach((pool, index) => { + const spy = jest.spyOn(pool, 'didIndyNamespace', 'get') + spy.mockReturnValueOnce(responses[index]) + }) + + const pool = poolService.getPoolForNamespace(indyNameSpace) + + expect(pool.id).toEqual(expectedPool.id) + }) + }) + + describe('submitWriteRequest', () => { + it('should throw an error if the config version does not match', async () => { + const pool = poolService.getPoolForNamespace() + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(poolService, 'getTransactionAuthorAgreement').mockResolvedValue({ + digest: 'abcde', + version: '2.0', + text: 'jhsdhbv', + ratification_ts: 12345678, + acceptanceMechanisms: { + aml: { accept: 'accept' }, + amlContext: 'accept', + version: '3', + }, + } as never) + await expect( + poolService.submitWriteRequest( + agentContext, + pool, + { + reqId: 1668174449192969000, + identifier: 'BBPoJqRKatdcfLEAFL7exC', + operation: { + type: '1', + dest: 'N8NQHLtCKfPmWMgCSdfa7h', + verkey: 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', + alias: 'Heinz57', + }, + protocolVersion: 2, + }, + 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf' + ) + ).rejects.toThrowError( + 'Unable to satisfy matching TAA with mechanism "accept" and version "1" in pool.\n Found ["accept"] and version 2.0 in pool.' + ) + }) + + it('should throw an error if the config acceptance mechanism does not match', async () => { + const pool = poolService.getPoolForNamespace() + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(poolService, 'getTransactionAuthorAgreement').mockResolvedValue({ + digest: 'abcde', + version: '1.0', + text: 'jhsdhbv', + ratification_ts: 12345678, + acceptanceMechanisms: { + aml: { decline: 'accept' }, + amlContext: 'accept', + version: '1', + }, + } as never) + await expect( + poolService.submitWriteRequest( + agentContext, + pool, + { + reqId: 1668174449192969000, + identifier: 'BBPoJqRKatdcfLEAFL7exC', + operation: { + type: '1', + dest: 'N8NQHLtCKfPmWMgCSdfa7h', + verkey: 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', + alias: 'Heinz57', + }, + protocolVersion: 2, + }, + 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf' + ) + ).rejects.toThrowError( + 'Unable to satisfy matching TAA with mechanism "accept" and version "1" in pool.\n Found ["decline"] and version 1.0 in pool.' + ) + }) + + it('should throw an error if no config is present', async () => { + const pool = poolService.getPoolForNamespace() + pool.authorAgreement = undefined + pool.config.transactionAuthorAgreement = undefined + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(poolService, 'getTransactionAuthorAgreement').mockResolvedValue({ + digest: 'abcde', + version: '1.0', + text: 'jhsdhbv', + ratification_ts: 12345678, + acceptanceMechanisms: { + aml: { accept: 'accept' }, + amlContext: 'accept', + version: '3', + }, + } as never) + await expect( + poolService.submitWriteRequest( + agentContext, + pool, + { + reqId: 1668174449192969000, + identifier: 'BBPoJqRKatdcfLEAFL7exC', + operation: { + type: '1', + dest: 'N8NQHLtCKfPmWMgCSdfa7h', + verkey: 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', + alias: 'Heinz57', + }, + protocolVersion: 2, + }, + 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf' + ) + ).rejects.toThrowError(/Please, specify a transaction author agreement with version and acceptance mechanism/) + }) + }) +}) diff --git a/packages/indy-sdk/src/ledger/__tests__/util.test.ts b/packages/indy-sdk/src/ledger/__tests__/util.test.ts new file mode 100644 index 0000000000..38976758ae --- /dev/null +++ b/packages/indy-sdk/src/ledger/__tests__/util.test.ts @@ -0,0 +1,45 @@ +import type { LedgerRejectResponse, LedgerReqnackResponse } from 'indy-sdk' + +import * as LedgerUtil from '../util' + +describe('LedgerUtils', () => { + // IsLedgerRejectResponse + it('Should return true if the response op is: REJECT', () => { + const ledgerResponse: LedgerRejectResponse = { + op: 'REJECT', + reqId: 1, + reason: 'Why not', + identifier: '123456', + } + expect(LedgerUtil.isLedgerRejectResponse(ledgerResponse)).toEqual(true) + }) + it('Should return false if the response op is not: REJECT', () => { + const ledgerResponse: LedgerReqnackResponse = { + op: 'REQNACK', + reqId: 1, + reason: 'Why not', + identifier: '123456', + } + expect(LedgerUtil.isLedgerRejectResponse(ledgerResponse)).toEqual(false) + }) + + // isLedgerReqnackResponse + it('Should return true if the response op is: REQNACK', () => { + const ledgerResponse: LedgerReqnackResponse = { + op: 'REQNACK', + reqId: 1, + reason: 'Why not', + identifier: '123456', + } + expect(LedgerUtil.isLedgerReqnackResponse(ledgerResponse)).toEqual(true) + }) + it('Should return false if the response op is NOT: REQNACK', () => { + const ledgerResponse: LedgerRejectResponse = { + op: 'REJECT', + reqId: 1, + reason: 'Why not', + identifier: '123456', + } + expect(LedgerUtil.isLedgerReqnackResponse(ledgerResponse)).toEqual(false) + }) +}) diff --git a/packages/indy-sdk/src/ledger/error/IndySdkPoolError.ts b/packages/indy-sdk/src/ledger/error/IndySdkPoolError.ts new file mode 100644 index 0000000000..fa6679d789 --- /dev/null +++ b/packages/indy-sdk/src/ledger/error/IndySdkPoolError.ts @@ -0,0 +1,7 @@ +import { AriesFrameworkError } from '@aries-framework/core' + +export class IndySdkPoolError extends AriesFrameworkError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/indy-sdk/src/ledger/error/IndySdkPoolNotConfiguredError.ts b/packages/indy-sdk/src/ledger/error/IndySdkPoolNotConfiguredError.ts new file mode 100644 index 0000000000..91cd3c7199 --- /dev/null +++ b/packages/indy-sdk/src/ledger/error/IndySdkPoolNotConfiguredError.ts @@ -0,0 +1,7 @@ +import { IndySdkPoolError } from './IndySdkPoolError' + +export class IndySdkPoolNotConfiguredError extends IndySdkPoolError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/indy-sdk/src/ledger/error/IndySdkPoolNotFoundError.ts b/packages/indy-sdk/src/ledger/error/IndySdkPoolNotFoundError.ts new file mode 100644 index 0000000000..4977428cba --- /dev/null +++ b/packages/indy-sdk/src/ledger/error/IndySdkPoolNotFoundError.ts @@ -0,0 +1,7 @@ +import { IndySdkPoolError } from './IndySdkPoolError' + +export class IndySdkPoolNotFoundError extends IndySdkPoolError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/indy-sdk/src/ledger/error/index.ts b/packages/indy-sdk/src/ledger/error/index.ts new file mode 100644 index 0000000000..e2554abbdf --- /dev/null +++ b/packages/indy-sdk/src/ledger/error/index.ts @@ -0,0 +1,3 @@ +export * from './IndySdkPoolError' +export * from './IndySdkPoolNotConfiguredError' +export * from './IndySdkPoolNotFoundError' diff --git a/packages/indy-sdk/src/ledger/index.ts b/packages/indy-sdk/src/ledger/index.ts new file mode 100644 index 0000000000..fe016abcec --- /dev/null +++ b/packages/indy-sdk/src/ledger/index.ts @@ -0,0 +1,2 @@ +export * from './IndySdkPool' +export * from './IndySdkPoolService' diff --git a/packages/indy-sdk/src/ledger/util.ts b/packages/indy-sdk/src/ledger/util.ts new file mode 100644 index 0000000000..d7b5fc2076 --- /dev/null +++ b/packages/indy-sdk/src/ledger/util.ts @@ -0,0 +1,9 @@ +import type { LedgerResponse, LedgerRejectResponse, LedgerReqnackResponse } from 'indy-sdk' + +export function isLedgerRejectResponse(response: LedgerResponse): response is LedgerRejectResponse { + return response.op === 'REJECT' +} + +export function isLedgerReqnackResponse(response: LedgerResponse): response is LedgerReqnackResponse { + return response.op === 'REQNACK' +} diff --git a/packages/indy-sdk/src/storage/IndySdkStorageService.ts b/packages/indy-sdk/src/storage/IndySdkStorageService.ts new file mode 100644 index 0000000000..48f3022154 --- /dev/null +++ b/packages/indy-sdk/src/storage/IndySdkStorageService.ts @@ -0,0 +1,324 @@ +import type { IndySdkWallet } from '../wallet/IndySdkWallet' +import type { + BaseRecordConstructor, + AgentContext, + BaseRecord, + TagsBase, + Query, + StorageService, +} from '@aries-framework/core' +import type { WalletQuery, WalletRecord, WalletSearchOptions } from 'indy-sdk' + +import { RecordDuplicateError, RecordNotFoundError, injectable, inject, JsonTransformer } from '@aries-framework/core' + +import { isIndyError, IndySdkError } from '../error' +import { IndySdk, IndySdkSymbol } from '../types' +import { assertIndySdkWallet } from '../utils/assertIndySdkWallet' + +@injectable() +export class IndySdkStorageService implements StorageService { + private indySdk: IndySdk + + private static DEFAULT_QUERY_OPTIONS = { + retrieveType: true, + retrieveTags: true, + } + + public constructor(@inject(IndySdkSymbol) indySdk: IndySdk) { + this.indySdk = indySdk + } + + private transformToRecordTagValues(tags: { [key: number]: string | undefined }): TagsBase { + const transformedTags: TagsBase = {} + + for (const [key, value] of Object.entries(tags)) { + // If the value is a boolean string ('1' or '0') + // use the boolean val + if (value === '1' && key?.includes(':')) { + const [tagName, tagValue] = key.split(':') + + const transformedValue = transformedTags[tagName] + + if (Array.isArray(transformedValue)) { + transformedTags[tagName] = [...transformedValue, tagValue] + } else { + transformedTags[tagName] = [tagValue] + } + } + // Transform '1' and '0' to boolean + else if (value === '1' || value === '0') { + transformedTags[key] = value === '1' + } + // If 1 or 0 is prefixed with 'n__' we need to remove it. This is to prevent + // casting the value to a boolean + else if (value === 'n__1' || value === 'n__0') { + transformedTags[key] = value === 'n__1' ? '1' : '0' + } + // Otherwise just use the value + else { + transformedTags[key] = value + } + } + + return transformedTags + } + + private transformFromRecordTagValues(tags: TagsBase): { [key: string]: string | undefined } { + const transformedTags: { [key: string]: string | undefined } = {} + + for (const [key, value] of Object.entries(tags)) { + // If the value is of type null we use the value undefined + // Indy doesn't support null as a value + if (value === null) { + transformedTags[key] = undefined + } + // If the value is a boolean use the indy + // '1' or '0' syntax + else if (typeof value === 'boolean') { + transformedTags[key] = value ? '1' : '0' + } + // If the value is 1 or 0, we need to add something to the value, otherwise + // the next time we deserialize the tag values it will be converted to boolean + else if (value === '1' || value === '0') { + transformedTags[key] = `n__${value}` + } + // If the value is an array we create a tag for each array + // item ("tagName:arrayItem" = "1") + else if (Array.isArray(value)) { + value.forEach((item) => { + const tagName = `${key}:${item}` + transformedTags[tagName] = '1' + }) + } + // Otherwise just use the value + else { + transformedTags[key] = value + } + } + + return transformedTags + } + + /** + * Transforms the search query into a wallet query compatible with indy WQL. + * + * The format used by AFJ is almost the same as the indy query, with the exception of + * the encoding of values, however this is handled by the {@link IndyStorageService.transformToRecordTagValues} + * method. + */ + private indyQueryFromSearchQuery(query: Query): Record { + // eslint-disable-next-line prefer-const + let { $and, $or, $not, ...tags } = query + + $and = ($and as Query[] | undefined)?.map((q) => this.indyQueryFromSearchQuery(q)) + $or = ($or as Query[] | undefined)?.map((q) => this.indyQueryFromSearchQuery(q)) + $not = $not ? this.indyQueryFromSearchQuery($not as Query) : undefined + + const indyQuery = { + ...this.transformFromRecordTagValues(tags as unknown as TagsBase), + $and, + $or, + $not, + } + + return indyQuery + } + + private recordToInstance(record: WalletRecord, recordClass: BaseRecordConstructor): T { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const instance = JsonTransformer.deserialize(record.value!, recordClass) + instance.id = record.id + + const tags = record.tags ? this.transformToRecordTagValues(record.tags) : {} + instance.replaceTags(tags) + + return instance + } + + /** @inheritDoc */ + public async save(agentContext: AgentContext, record: T) { + assertIndySdkWallet(agentContext.wallet) + + const value = JsonTransformer.serialize(record) + const tags = this.transformFromRecordTagValues(record.getTags()) as Record + + try { + await this.indySdk.addWalletRecord(agentContext.wallet.handle, record.type, record.id, value, tags) + } catch (error) { + // Record already exists + if (isIndyError(error, 'WalletItemAlreadyExists')) { + throw new RecordDuplicateError(`Record with id ${record.id} already exists`, { recordType: record.type }) + } + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + /** @inheritDoc */ + public async update(agentContext: AgentContext, record: T): Promise { + assertIndySdkWallet(agentContext.wallet) + + const value = JsonTransformer.serialize(record) + const tags = this.transformFromRecordTagValues(record.getTags()) as Record + + try { + await this.indySdk.updateWalletRecordValue(agentContext.wallet.handle, record.type, record.id, value) + await this.indySdk.updateWalletRecordTags(agentContext.wallet.handle, record.type, record.id, tags) + } catch (error) { + // Record does not exist + if (isIndyError(error, 'WalletItemNotFound')) { + throw new RecordNotFoundError(`record with id ${record.id} not found.`, { + recordType: record.type, + cause: error, + }) + } + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + /** @inheritDoc */ + public async delete(agentContext: AgentContext, record: T) { + assertIndySdkWallet(agentContext.wallet) + + try { + await this.indySdk.deleteWalletRecord(agentContext.wallet.handle, record.type, record.id) + } catch (error) { + // Record does not exist + if (isIndyError(error, 'WalletItemNotFound')) { + throw new RecordNotFoundError(`record with id ${record.id} not found.`, { + recordType: record.type, + cause: error, + }) + } + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + /** @inheritDoc */ + public async deleteById( + agentContext: AgentContext, + recordClass: BaseRecordConstructor, + id: string + ): Promise { + assertIndySdkWallet(agentContext.wallet) + + try { + await this.indySdk.deleteWalletRecord(agentContext.wallet.handle, recordClass.type, id) + } catch (error) { + if (isIndyError(error, 'WalletItemNotFound')) { + throw new RecordNotFoundError(`record with id ${id} not found.`, { + recordType: recordClass.type, + cause: error, + }) + } + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + /** @inheritDoc */ + public async getById(agentContext: AgentContext, recordClass: BaseRecordConstructor, id: string): Promise { + assertIndySdkWallet(agentContext.wallet) + + try { + const record = await this.indySdk.getWalletRecord( + agentContext.wallet.handle, + recordClass.type, + id, + IndySdkStorageService.DEFAULT_QUERY_OPTIONS + ) + return this.recordToInstance(record, recordClass) + } catch (error) { + if (isIndyError(error, 'WalletItemNotFound')) { + throw new RecordNotFoundError(`record with id ${id} not found.`, { + recordType: recordClass.type, + cause: error, + }) + } + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + /** @inheritDoc */ + public async getAll(agentContext: AgentContext, recordClass: BaseRecordConstructor): Promise { + assertIndySdkWallet(agentContext.wallet) + + const recordIterator = this.search( + agentContext.wallet, + recordClass.type, + {}, + IndySdkStorageService.DEFAULT_QUERY_OPTIONS + ) + const records = [] + for await (const record of recordIterator) { + records.push(this.recordToInstance(record, recordClass)) + } + return records + } + + /** @inheritDoc */ + public async findByQuery( + agentContext: AgentContext, + recordClass: BaseRecordConstructor, + query: Query + ): Promise { + assertIndySdkWallet(agentContext.wallet) + + const indyQuery = this.indyQueryFromSearchQuery(query) + + const recordIterator = this.search( + agentContext.wallet, + recordClass.type, + indyQuery, + IndySdkStorageService.DEFAULT_QUERY_OPTIONS + ) + const records = [] + for await (const record of recordIterator) { + records.push(this.recordToInstance(record, recordClass)) + } + return records + } + + private async *search( + wallet: IndySdkWallet, + type: string, + query: WalletQuery, + { limit = Infinity, ...options }: WalletSearchOptions & { limit?: number } + ) { + try { + const searchHandle = await this.indySdk.openWalletSearch(wallet.handle, type, query, options) + + let records: WalletRecord[] = [] + + // Allow max of 256 per fetch operation + const chunk = limit ? Math.min(256, limit) : 256 + + // Loop while limit not reached (or no limit specified) + while (!limit || records.length < limit) { + // Retrieve records + const recordsJson = await this.indySdk.fetchWalletSearchNextRecords(wallet.handle, searchHandle, chunk) + + if (recordsJson.records) { + records = [...records, ...recordsJson.records] + + for (const record of recordsJson.records) { + yield record + } + } + + // If the number of records returned is less than chunk + // It means we reached the end of the iterator (no more records) + if (!records.length || !recordsJson.records || recordsJson.records.length < chunk) { + await this.indySdk.closeWalletSearch(searchHandle) + + return + } + } + } catch (error) { + throw new IndySdkError(error, `Searching '${type}' records for query '${JSON.stringify(query)}' failed`) + } + } +} diff --git a/packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts b/packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts new file mode 100644 index 0000000000..7a8855c9d5 --- /dev/null +++ b/packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts @@ -0,0 +1,297 @@ +import type { IndySdk } from '../../types' +import type { AgentContext, TagsBase } from '@aries-framework/core' + +import { SigningProviderRegistry, RecordDuplicateError, RecordNotFoundError } from '@aries-framework/core' + +import { TestRecord } from '../../../../core/src/storage/__tests__/TestRecord' +import { agentDependencies, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { IndySdkWallet } from '../../wallet/IndySdkWallet' +import { IndySdkStorageService } from '../IndySdkStorageService' + +describe('IndySdkStorageService', () => { + let wallet: IndySdkWallet + let indy: IndySdk + let storageService: IndySdkStorageService + let agentContext: AgentContext + + beforeEach(async () => { + indy = agentDependencies.indy + const agentConfig = getAgentConfig('IndySdkStorageServiceTest') + wallet = new IndySdkWallet(indy, agentConfig.logger, new SigningProviderRegistry([])) + agentContext = getAgentContext({ + wallet, + agentConfig, + }) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await wallet.createAndOpen(agentConfig.walletConfig!) + storageService = new IndySdkStorageService(indy) + }) + + afterEach(async () => { + await wallet.delete() + }) + + const insertRecord = async ({ id, tags }: { id?: string; tags?: TagsBase }) => { + const props = { + id, + foo: 'bar', + tags: tags ?? { myTag: 'foobar' }, + } + const record = new TestRecord(props) + await storageService.save(agentContext, record) + return record + } + + describe('tag transformation', () => { + it('should correctly transform tag values to string before storing', async () => { + const record = await insertRecord({ + id: 'test-id', + tags: { + someBoolean: true, + someOtherBoolean: false, + someStringValue: 'string', + anArrayValue: ['foo', 'bar'], + // booleans are stored as '1' and '0' so we store the string values '1' and '0' as 'n__1' and 'n__0' + someStringNumberValue: '1', + anotherStringNumberValue: '0', + }, + }) + + const retrieveRecord = await indy.getWalletRecord(wallet.handle, record.type, record.id, { + retrieveType: true, + retrieveTags: true, + }) + + expect(retrieveRecord.tags).toEqual({ + someBoolean: '1', + someOtherBoolean: '0', + someStringValue: 'string', + 'anArrayValue:foo': '1', + 'anArrayValue:bar': '1', + someStringNumberValue: 'n__1', + anotherStringNumberValue: 'n__0', + }) + }) + + it('should correctly transform tag values from string after retrieving', async () => { + await indy.addWalletRecord(wallet.handle, TestRecord.type, 'some-id', '{}', { + someBoolean: '1', + someOtherBoolean: '0', + someStringValue: 'string', + 'anArrayValue:foo': '1', + 'anArrayValue:bar': '1', + // booleans are stored as '1' and '0' so we store the string values '1' and '0' as 'n__1' and 'n__0' + someStringNumberValue: 'n__1', + anotherStringNumberValue: 'n__0', + }) + + const record = await storageService.getById(agentContext, TestRecord, 'some-id') + + expect(record.getTags()).toEqual({ + someBoolean: true, + someOtherBoolean: false, + someStringValue: 'string', + anArrayValue: expect.arrayContaining(['bar', 'foo']), + someStringNumberValue: '1', + anotherStringNumberValue: '0', + }) + }) + }) + + describe('save()', () => { + it('should throw RecordDuplicateError if a record with the id already exists', async () => { + const record = await insertRecord({ id: 'test-id' }) + + return expect(() => storageService.save(agentContext, record)).rejects.toThrowError(RecordDuplicateError) + }) + + it('should save the record', async () => { + const record = await insertRecord({ id: 'test-id' }) + const found = await storageService.getById(agentContext, TestRecord, 'test-id') + + expect(record).toEqual(found) + }) + }) + + describe('getById()', () => { + it('should throw RecordNotFoundError if the record does not exist', async () => { + return expect(() => storageService.getById(agentContext, TestRecord, 'does-not-exist')).rejects.toThrowError( + RecordNotFoundError + ) + }) + + it('should return the record by id', async () => { + const record = await insertRecord({ id: 'test-id' }) + const found = await storageService.getById(agentContext, TestRecord, 'test-id') + + expect(found).toEqual(record) + }) + }) + + describe('update()', () => { + it('should throw RecordNotFoundError if the record does not exist', async () => { + const record = new TestRecord({ + id: 'test-id', + foo: 'test', + tags: { some: 'tag' }, + }) + + return expect(() => storageService.update(agentContext, record)).rejects.toThrowError(RecordNotFoundError) + }) + + it('should update the record', async () => { + const record = await insertRecord({ id: 'test-id' }) + + record.replaceTags({ ...record.getTags(), foo: 'bar' }) + record.foo = 'foobaz' + await storageService.update(agentContext, record) + + const retrievedRecord = await storageService.getById(agentContext, TestRecord, record.id) + expect(retrievedRecord).toEqual(record) + }) + }) + + describe('delete()', () => { + it('should throw RecordNotFoundError if the record does not exist', async () => { + const record = new TestRecord({ + id: 'test-id', + foo: 'test', + tags: { some: 'tag' }, + }) + + return expect(() => storageService.delete(agentContext, record)).rejects.toThrowError(RecordNotFoundError) + }) + + it('should delete the record', async () => { + const record = await insertRecord({ id: 'test-id' }) + await storageService.delete(agentContext, record) + + return expect(() => storageService.getById(agentContext, TestRecord, record.id)).rejects.toThrowError( + RecordNotFoundError + ) + }) + }) + + describe('getAll()', () => { + it('should retrieve all records', async () => { + const createdRecords = await Promise.all( + Array(5) + .fill(undefined) + .map((_, index) => insertRecord({ id: `record-${index}` })) + ) + + const records = await storageService.getAll(agentContext, TestRecord) + + expect(records).toEqual(expect.arrayContaining(createdRecords)) + }) + }) + + describe('findByQuery()', () => { + it('should retrieve all records that match the query', async () => { + const expectedRecord = await insertRecord({ tags: { myTag: 'foobar' } }) + await insertRecord({ tags: { myTag: 'notfoobar' } }) + + const records = await storageService.findByQuery(agentContext, TestRecord, { myTag: 'foobar' }) + + expect(records.length).toBe(1) + expect(records[0]).toEqual(expectedRecord) + }) + + it('finds records using $and statements', async () => { + const expectedRecord = await insertRecord({ tags: { myTag: 'foo', anotherTag: 'bar' } }) + await insertRecord({ tags: { myTag: 'notfoobar' } }) + + const records = await storageService.findByQuery(agentContext, TestRecord, { + $and: [{ myTag: 'foo' }, { anotherTag: 'bar' }], + }) + + expect(records.length).toBe(1) + expect(records[0]).toEqual(expectedRecord) + }) + + it('finds records using $or statements', async () => { + const expectedRecord = await insertRecord({ tags: { myTag: 'foo' } }) + const expectedRecord2 = await insertRecord({ tags: { anotherTag: 'bar' } }) + await insertRecord({ tags: { myTag: 'notfoobar' } }) + + const records = await storageService.findByQuery(agentContext, TestRecord, { + $or: [{ myTag: 'foo' }, { anotherTag: 'bar' }], + }) + + expect(records.length).toBe(2) + expect(records).toEqual(expect.arrayContaining([expectedRecord, expectedRecord2])) + }) + + it('finds records using $not statements', async () => { + const expectedRecord = await insertRecord({ tags: { myTag: 'foo' } }) + const expectedRecord2 = await insertRecord({ tags: { anotherTag: 'bar' } }) + await insertRecord({ tags: { myTag: 'notfoobar' } }) + + const records = await storageService.findByQuery(agentContext, TestRecord, { + $not: { myTag: 'notfoobar' }, + }) + + expect(records.length).toBe(2) + expect(records).toEqual(expect.arrayContaining([expectedRecord, expectedRecord2])) + }) + + it('correctly transforms an advanced query into a valid WQL query', async () => { + const indySpy = jest.fn() + const storageServiceWithoutIndy = new IndySdkStorageService({ + openWalletSearch: indySpy, + fetchWalletSearchNextRecords: jest.fn(() => ({ records: undefined })), + closeWalletSearch: jest.fn(), + } as unknown as IndySdk) + + await storageServiceWithoutIndy.findByQuery(agentContext, TestRecord, { + $and: [ + { + $or: [{ myTag: true }, { myTag: false }], + }, + { + $and: [{ theNumber: '0' }, { theNumber: '1' }], + }, + ], + $or: [ + { + aValue: ['foo', 'bar'], + }, + ], + $not: { myTag: 'notfoobar' }, + }) + + const expectedQuery = { + $and: [ + { + $and: undefined, + $not: undefined, + $or: [ + { myTag: '1', $and: undefined, $or: undefined, $not: undefined }, + { myTag: '0', $and: undefined, $or: undefined, $not: undefined }, + ], + }, + { + $or: undefined, + $not: undefined, + $and: [ + { theNumber: 'n__0', $and: undefined, $or: undefined, $not: undefined }, + { theNumber: 'n__1', $and: undefined, $or: undefined, $not: undefined }, + ], + }, + ], + $or: [ + { + 'aValue:foo': '1', + 'aValue:bar': '1', + $and: undefined, + $or: undefined, + $not: undefined, + }, + ], + $not: { myTag: 'notfoobar', $and: undefined, $or: undefined, $not: undefined }, + } + + expect(indySpy).toBeCalledWith(expect.anything(), expect.anything(), expectedQuery, expect.anything()) + }) + }) +}) diff --git a/packages/indy-sdk/src/storage/index.ts b/packages/indy-sdk/src/storage/index.ts new file mode 100644 index 0000000000..ff59756cfa --- /dev/null +++ b/packages/indy-sdk/src/storage/index.ts @@ -0,0 +1 @@ +export * from './IndySdkStorageService' diff --git a/packages/indy-sdk/src/types.ts b/packages/indy-sdk/src/types.ts new file mode 100644 index 0000000000..f6ac41c161 --- /dev/null +++ b/packages/indy-sdk/src/types.ts @@ -0,0 +1,6 @@ +import type { default as _IndySdk } from 'indy-sdk' + +type IndySdk = typeof _IndySdk + +export const IndySdkSymbol = Symbol('IndySdk') +export type { IndySdk } diff --git a/packages/indy-sdk/src/utils/__tests__/did.test.ts b/packages/indy-sdk/src/utils/__tests__/did.test.ts new file mode 100644 index 0000000000..45344136d9 --- /dev/null +++ b/packages/indy-sdk/src/utils/__tests__/did.test.ts @@ -0,0 +1,73 @@ +import { isAbbreviatedVerkey, isFullVerkey, isSelfCertifiedDid } from '../did' + +const validAbbreviatedVerkeys = [ + '~PKAYz8Ev4yoQgr2LaMAWFx', + '~Soy1augaQrQYtNZRRHsikB', + '~BUF7uxYTxZ6qYdZ4G9e1Gi', + '~DbZ4gkBqhFRVsT5P7BJqyZ', + '~4zmNTdG78iYyMAQdEQLrf8', +] + +const invalidAbbreviatedVerkeys = [ + '6YnVN5Qdb6mqimTRQcQmSXrHXKdTEdRn5YHZReezUTvt', + '8jG2Bim1HNSybCTdKBRppP4PCQSSijx1pBnreqsdo8JG', + 'ABUF7uxYTxZ6qYdZ4G9e1Gi', + '~Db3IgkBqhFRVsT5P7BJqyZ', + '~4zmNTlG78iYyMAQdEQLrf8', +] + +const validFullVerkeys = [ + '6YnVN5Qdb6mqimTRQcQmSXrHXKdTEdRn5YHZReezUTvt', + '8jG2Bim1HNSybCTdKBRppP4PCQSSijx1pBnreqsdo8JG', + '9wMLhw9SSxtTUyosrndMbvWY4TtDbVvRnMtzG2NysniP', + '6m2XT39vivJ7tLSxNPM8siMnhYCZcdMxbkTcJDSzAQTu', + 'CAgL85iEecPNQMmxQ1hgbqczwq7SAerQ8RbWTRtC7SoK', + 'MqXmB7cTsTXqyxDPBbrgu5EPqw61kouK1qjMvnoPa96', +] + +const invalidFullVerkeys = [ + '~PKAYz8Ev4yoQgr2LaMAWFx', + '~Soy1augaQrQYtNZRRHsikB', + '6YnVN5Qdb6mqimTRQcQmSXrHXKdTEdRn5YHZReezUTvta', + '6m2XT39vIvJ7tLSxNPM8siMnhYCZcdMxbkTcJDSzAQTu', + 'CAgL85iEecPNQMlxQ1hgbqczwq7SAerQ8RbWTRtC7SoK', +] + +describe('Utils | Did', () => { + describe('isSelfCertifiedDid()', () => { + test('returns true if the verkey is abbreviated', () => { + expect(isSelfCertifiedDid('PW8ZHpNupeWXbmpPWog6Ki', '~QQ5jiH1dgXPAnvHdJvazn9')).toBe(true) + }) + + test('returns true if the verkey is not abbreviated and the did is generated from the verkey', () => { + expect(isSelfCertifiedDid('Y8q4Aq6gRAcmB6jjKk3Z7t', 'HyEoPRNvC7q4jj5joUo8AWYtxbNccbEnTAeuMYkpmNS2')).toBe(true) + }) + + test('returns false if the verkey is not abbreviated and the did is not generated from the verkey', () => { + expect(isSelfCertifiedDid('Y8q4Aq6gRAcmB6jjKk3Z7t', 'AcU7DnRqoXGYATD6VqsRq4eHuz55gdM3uzFBEhFd6rGh')).toBe(false) + }) + }) + + describe('isAbbreviatedVerkey()', () => { + test.each(validAbbreviatedVerkeys)('returns true when valid abbreviated verkey "%s" is passed in', (verkey) => { + expect(isAbbreviatedVerkey(verkey)).toBe(true) + }) + + test.each(invalidAbbreviatedVerkeys)( + 'returns false when invalid abbreviated verkey "%s" is passed in', + (verkey) => { + expect(isAbbreviatedVerkey(verkey)).toBe(false) + } + ) + }) + + describe('isFullVerkey()', () => { + test.each(validFullVerkeys)('returns true when valid full verkey "%s" is passed in', (verkey) => { + expect(isFullVerkey(verkey)).toBe(true) + }) + + test.each(invalidFullVerkeys)('returns false when invalid full verkey "%s" is passed in', (verkey) => { + expect(isFullVerkey(verkey)).toBe(false) + }) + }) +}) diff --git a/packages/indy-sdk/src/utils/assertIndySdkWallet.ts b/packages/indy-sdk/src/utils/assertIndySdkWallet.ts new file mode 100644 index 0000000000..0b1914555f --- /dev/null +++ b/packages/indy-sdk/src/utils/assertIndySdkWallet.ts @@ -0,0 +1,13 @@ +import type { Wallet } from '@aries-framework/core' + +import { AriesFrameworkError } from '@aries-framework/core' + +import { IndySdkWallet } from '../wallet/IndySdkWallet' + +export function assertIndySdkWallet(wallet: Wallet): asserts wallet is IndySdkWallet { + if (!(wallet instanceof IndySdkWallet)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const walletClassName = (wallet as any).constructor?.name ?? 'unknown' + throw new AriesFrameworkError(`Expected wallet to be instance of IndySdkWallet, found ${walletClassName}`) + } +} diff --git a/packages/indy-sdk/src/utils/did.ts b/packages/indy-sdk/src/utils/did.ts new file mode 100644 index 0000000000..90b465a0f6 --- /dev/null +++ b/packages/indy-sdk/src/utils/did.ts @@ -0,0 +1,89 @@ +/** + * Based on DidUtils implementation in Aries Framework .NET + * @see: https://github.com/hyperledger/aries-framework-dotnet/blob/f90eaf9db8548f6fc831abea917e906201755763/src/Hyperledger.Aries/Utils/DidUtils.cs + * + * Some context about full verkeys versus abbreviated verkeys: + * A standard verkey is 32 bytes, and by default in Indy the DID is chosen as the first 16 bytes of that key, before base58 encoding. + * An abbreviated verkey replaces the first 16 bytes of the verkey with ~ when it matches the DID. + * + * When a full verkey is used to register on the ledger, this is stored as a full verkey on the ledger and also returned from the ledger as a full verkey. + * The same applies to an abbreviated verkey. If an abbreviated verkey is used to register on the ledger, this is stored as an abbreviated verkey on the ledger and also returned from the ledger as an abbreviated verkey. + * + * For this reason we need some methods to check whether verkeys are full or abbreviated, so we can align this with `indy.abbreviateVerkey` + * + * Aries Framework .NET also abbreviates verkey before sending to ledger: + * https://github.com/hyperledger/aries-framework-dotnet/blob/f90eaf9db8548f6fc831abea917e906201755763/src/Hyperledger.Aries/Ledger/DefaultLedgerService.cs#L139-L147 + */ + +import { Buffer, TypedArrayEncoder } from '@aries-framework/core' + +export const FULL_VERKEY_REGEX = /^[1-9A-HJ-NP-Za-km-z]{43,44}$/ +export const ABBREVIATED_VERKEY_REGEX = /^~[1-9A-HJ-NP-Za-km-z]{21,22}$/ + +/** + * Check whether the did is a self certifying did. If the verkey is abbreviated this method + * will always return true. Make sure that the verkey you pass in this method belongs to the + * did passed in + * + * @return Boolean indicating whether the did is self certifying + */ +export function isSelfCertifiedDid(did: string, verkey: string): boolean { + // If the verkey is Abbreviated, it means the full verkey + // is the did + the verkey + if (isAbbreviatedVerkey(verkey)) { + return true + } + + const didFromVerkey = indyDidFromPublicKeyBase58(verkey) + + if (didFromVerkey === did) { + return true + } + + return false +} + +export function indyDidFromPublicKeyBase58(publicKeyBase58: string): string { + const buffer = TypedArrayEncoder.fromBase58(publicKeyBase58) + + const did = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) + + return did +} + +export function getFullVerkey(did: string, verkey: string) { + if (isFullVerkey(verkey)) return verkey + + // Did could have did:xxx prefix, only take the last item after : + const id = did.split(':').pop() ?? did + // Verkey is prefixed with ~ if abbreviated + const verkeyWithoutTilde = verkey.slice(1) + + // Create base58 encoded public key (32 bytes) + return TypedArrayEncoder.toBase58( + Buffer.concat([ + // Take did identifier (16 bytes) + TypedArrayEncoder.fromBase58(id), + // Concat the abbreviated verkey (16 bytes) + TypedArrayEncoder.fromBase58(verkeyWithoutTilde), + ]) + ) +} + +/** + * Check a base58 encoded string against a regex expression to determine if it is a full valid verkey + * @param verkey Base58 encoded string representation of a verkey + * @return Boolean indicating if the string is a valid verkey + */ +export function isFullVerkey(verkey: string): boolean { + return FULL_VERKEY_REGEX.test(verkey) +} + +/** + * Check a base58 encoded string against a regex expression to determine if it is a valid abbreviated verkey + * @param verkey Base58 encoded string representation of an abbreviated verkey + * @returns Boolean indicating if the string is a valid abbreviated verkey + */ +export function isAbbreviatedVerkey(verkey: string): boolean { + return ABBREVIATED_VERKEY_REGEX.test(verkey) +} diff --git a/packages/indy-sdk/src/utils/promises.ts b/packages/indy-sdk/src/utils/promises.ts new file mode 100644 index 0000000000..0e843d73b5 --- /dev/null +++ b/packages/indy-sdk/src/utils/promises.ts @@ -0,0 +1,44 @@ +// This file polyfills the allSettled method introduced in ESNext + +export type AllSettledFulfilled = { + status: 'fulfilled' + value: T +} + +export type AllSettledRejected = { + status: 'rejected' + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reason: any +} + +export function allSettled(promises: Promise[]) { + return Promise.all( + promises.map((p) => + p + .then( + (value) => + ({ + status: 'fulfilled', + value, + } as AllSettledFulfilled) + ) + .catch( + (reason) => + ({ + status: 'rejected', + reason, + } as AllSettledRejected) + ) + ) + ) +} + +export function onlyFulfilled(entries: Array | AllSettledRejected>) { + // We filter for only the rejected values, so we can safely cast the type + return entries.filter((e) => e.status === 'fulfilled') as AllSettledFulfilled[] +} + +export function onlyRejected(entries: Array | AllSettledRejected>) { + // We filter for only the rejected values, so we can safely cast the type + return entries.filter((e) => e.status === 'rejected') as AllSettledRejected[] +} diff --git a/packages/indy-sdk/src/wallet/IndySdkWallet.ts b/packages/indy-sdk/src/wallet/IndySdkWallet.ts new file mode 100644 index 0000000000..9230ed5f28 --- /dev/null +++ b/packages/indy-sdk/src/wallet/IndySdkWallet.ts @@ -0,0 +1,686 @@ +import type { + EncryptedMessage, + KeyDerivationMethod, + WalletConfig, + Buffer, + WalletCreateKeyOptions, + DidConfig, + DidInfo, + WalletSignOptions, + UnpackedMessageContext, + WalletVerifyOptions, + Wallet, + KeyPair, + WalletExportImportConfig, + WalletConfigRekey, +} from '@aries-framework/core' +import type { WalletStorageConfig, WalletConfig as IndySdkWalletConfig, OpenWalletCredentials } from 'indy-sdk' + +const isError = (error: unknown): error is Error => error instanceof Error + +import { + AriesFrameworkError, + RecordDuplicateError, + RecordNotFoundError, + Logger, + JsonEncoder, + WalletDuplicateError, + WalletError, + WalletNotFoundError, + WalletInvalidKeyError, + InjectionSymbols, + KeyType, + Key, + SigningProviderRegistry, + TypedArrayEncoder, +} from '@aries-framework/core' +import { inject, injectable } from 'tsyringe' + +import { isIndyError, IndySdkError } from '../error' +import { IndySdk, IndySdkSymbol } from '../types' + +@injectable() +export class IndySdkWallet implements Wallet { + private walletConfig?: WalletConfig + private walletHandle?: number + + private logger: Logger + private signingKeyProviderRegistry: SigningProviderRegistry + private publicDidInfo: DidInfo | undefined + private indySdk: IndySdk + + public constructor( + @inject(IndySdkSymbol) indySdk: IndySdk, + @inject(InjectionSymbols.Logger) logger: Logger, + signingKeyProviderRegistry: SigningProviderRegistry + ) { + this.logger = logger + this.signingKeyProviderRegistry = signingKeyProviderRegistry + this.indySdk = indySdk + } + + public get isProvisioned() { + return this.walletConfig !== undefined + } + + public get isInitialized() { + return this.walletHandle !== undefined + } + + public get publicDid() { + return this.publicDidInfo + } + + public get handle() { + if (!this.walletHandle) { + throw new AriesFrameworkError( + 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' + ) + } + + return this.walletHandle + } + + public get masterSecretId() { + if (!this.isInitialized || !(this.walletConfig?.id || this.walletConfig?.masterSecretId)) { + throw new AriesFrameworkError( + 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' + ) + } + + return this.walletConfig?.masterSecretId ?? this.walletConfig.id + } + + /** + * Dispose method is called when an agent context is disposed. + */ + public async dispose() { + if (this.isInitialized) { + await this.close() + } + } + + private walletStorageConfig(walletConfig: WalletConfig): IndySdkWalletConfig { + const walletStorageConfig: IndySdkWalletConfig = { + id: walletConfig.id, + storage_type: walletConfig.storage?.type, + } + + if (walletConfig.storage?.config) { + walletStorageConfig.storage_config = walletConfig.storage?.config as WalletStorageConfig + } + + return walletStorageConfig + } + + private walletCredentials( + walletConfig: WalletConfig, + rekey?: string, + rekeyDerivation?: KeyDerivationMethod + ): OpenWalletCredentials { + const walletCredentials: OpenWalletCredentials = { + key: walletConfig.key, + key_derivation_method: walletConfig.keyDerivationMethod, + } + if (rekey) { + walletCredentials.rekey = rekey + } + if (rekeyDerivation) { + walletCredentials.rekey_derivation_method = rekeyDerivation + } + if (walletConfig.storage?.credentials) { + walletCredentials.storage_credentials = walletConfig.storage?.credentials as Record + } + + return walletCredentials + } + + /** + * @throws {WalletDuplicateError} if the wallet already exists + * @throws {WalletError} if another error occurs + */ + public async create(walletConfig: WalletConfig): Promise { + await this.createAndOpen(walletConfig) + await this.close() + } + + /** + * @throws {WalletDuplicateError} if the wallet already exists + * @throws {WalletError} if another error occurs + */ + public async createAndOpen(walletConfig: WalletConfig): Promise { + this.logger.debug(`Creating wallet '${walletConfig.id}' using SQLite storage`) + + try { + await this.indySdk.createWallet(this.walletStorageConfig(walletConfig), this.walletCredentials(walletConfig)) + this.walletConfig = walletConfig + + // We usually want to create master secret only once, therefore, we can to do so when creating a wallet. + await this.open(walletConfig) + + // We need to open wallet before creating master secret because we need wallet handle here. + await this.createMasterSecret(this.handle, this.masterSecretId) + } catch (error) { + // If an error ocurred while creating the master secret, we should close the wallet + if (this.isInitialized) await this.close() + + if (isIndyError(error, 'WalletAlreadyExistsError')) { + const errorMessage = `Wallet '${walletConfig.id}' already exists` + this.logger.debug(errorMessage) + + throw new WalletDuplicateError(errorMessage, { + walletType: 'IndySdkWallet', + cause: error, + }) + } else { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + const errorMessage = `Error creating wallet '${walletConfig.id}'` + this.logger.error(errorMessage, { + error, + errorMessage: error.message, + }) + + throw new WalletError(errorMessage, { cause: error }) + } + } + + this.logger.debug(`Successfully created wallet '${walletConfig.id}'`) + } + + /** + * @throws {WalletNotFoundError} if the wallet does not exist + * @throws {WalletError} if another error occurs + */ + public async open(walletConfig: WalletConfig): Promise { + await this._open(walletConfig) + } + + /** + * @throws {WalletNotFoundError} if the wallet does not exist + * @throws {WalletError} if another error occurs + */ + public async rotateKey(walletConfig: WalletConfigRekey): Promise { + if (!walletConfig.rekey) { + throw new WalletError('Wallet rekey undefined!. Please specify the new wallet key') + } + await this._open( + { + id: walletConfig.id, + key: walletConfig.key, + keyDerivationMethod: walletConfig.keyDerivationMethod, + }, + walletConfig.rekey, + walletConfig.rekeyDerivationMethod + ) + } + + /** + * @throws {WalletNotFoundError} if the wallet does not exist + * @throws {WalletError} if another error occurs + */ + private async _open( + walletConfig: WalletConfig, + rekey?: string, + rekeyDerivation?: KeyDerivationMethod + ): Promise { + if (this.walletHandle) { + throw new WalletError( + 'Wallet instance already opened. Close the currently opened wallet before re-opening the wallet' + ) + } + + try { + this.walletHandle = await this.indySdk.openWallet( + this.walletStorageConfig(walletConfig), + this.walletCredentials(walletConfig, rekey, rekeyDerivation) + ) + if (rekey) { + this.walletConfig = { ...walletConfig, key: rekey, keyDerivationMethod: rekeyDerivation } + } else { + this.walletConfig = walletConfig + } + } catch (error) { + if (isIndyError(error, 'WalletNotFoundError')) { + const errorMessage = `Wallet '${walletConfig.id}' not found` + this.logger.debug(errorMessage) + + throw new WalletNotFoundError(errorMessage, { + walletType: 'IndySdkWallet', + cause: error, + }) + } else if (isIndyError(error, 'WalletAccessFailed')) { + const errorMessage = `Incorrect key for wallet '${walletConfig.id}'` + this.logger.debug(errorMessage) + throw new WalletInvalidKeyError(errorMessage, { + walletType: 'IndySdkWallet', + cause: error, + }) + } else { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + const errorMessage = `Error opening wallet '${walletConfig.id}': ${error.message}` + this.logger.error(errorMessage, { + error, + errorMessage: error.message, + }) + + throw new WalletError(errorMessage, { cause: error }) + } + } + + this.logger.debug(`Wallet '${walletConfig.id}' opened with handle '${this.handle}'`) + } + + /** + * @throws {WalletNotFoundError} if the wallet does not exist + * @throws {WalletError} if another error occurs + */ + public async delete(): Promise { + if (!this.walletConfig) { + throw new WalletError( + 'Can not delete wallet that does not have wallet config set. Make sure to call create wallet before deleting the wallet' + ) + } + + this.logger.info(`Deleting wallet '${this.walletConfig.id}'`) + + if (this.walletHandle) { + await this.close() + } + + try { + await this.indySdk.deleteWallet( + this.walletStorageConfig(this.walletConfig), + this.walletCredentials(this.walletConfig) + ) + } catch (error) { + if (isIndyError(error, 'WalletNotFoundError')) { + const errorMessage = `Error deleting wallet: wallet '${this.walletConfig.id}' not found` + this.logger.debug(errorMessage) + + throw new WalletNotFoundError(errorMessage, { + walletType: 'IndySdkWallet', + cause: error, + }) + } else { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + const errorMessage = `Error deleting wallet '${this.walletConfig.id}': ${error.message}` + this.logger.error(errorMessage, { + error, + errorMessage: error.message, + }) + + throw new WalletError(errorMessage, { cause: error }) + } + } + } + + public async export(exportConfig: WalletExportImportConfig) { + try { + this.logger.debug(`Exporting wallet ${this.walletConfig?.id} to path ${exportConfig.path}`) + await this.indySdk.exportWallet(this.handle, exportConfig) + } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + const errorMessage = `Error exporting wallet: ${error.message}` + this.logger.error(errorMessage, { + error, + }) + + throw new WalletError(errorMessage, { cause: error }) + } + } + + public async import(walletConfig: WalletConfig, importConfig: WalletExportImportConfig) { + try { + this.logger.debug(`Importing wallet ${walletConfig.id} from path ${importConfig.path}`) + await this.indySdk.importWallet( + { id: walletConfig.id }, + { key: walletConfig.key, key_derivation_method: walletConfig.keyDerivationMethod }, + importConfig + ) + } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + const errorMessage = `Error importing wallet': ${error.message}` + this.logger.error(errorMessage, { + error, + }) + + throw new WalletError(errorMessage, { cause: error }) + } + } + + /** + * @throws {WalletError} if the wallet is already closed or another error occurs + */ + public async close(): Promise { + this.logger.debug(`Closing wallet ${this.walletConfig?.id}`) + if (!this.walletHandle) { + throw new WalletError('Wallet is in invalid state, you are trying to close wallet that has no `walletHandle`.') + } + + try { + await this.indySdk.closeWallet(this.walletHandle) + this.walletHandle = undefined + this.publicDidInfo = undefined + } catch (error) { + if (isIndyError(error, 'WalletInvalidHandle')) { + const errorMessage = `Error closing wallet: wallet already closed` + this.logger.debug(errorMessage) + + throw new WalletError(errorMessage, { + cause: error, + }) + } else { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + const errorMessage = `Error closing wallet': ${error.message}` + this.logger.error(errorMessage, { + error, + errorMessage: error.message, + }) + + throw new WalletError(errorMessage, { cause: error }) + } + } + } + + /** + * Create master secret with specified id in currently opened wallet. + * + * If a master secret by this id already exists in the current wallet, the method + * will return without doing anything. + * + * @throws {WalletError} if an error occurs + */ + private async createMasterSecret(walletHandle: number, masterSecretId: string): Promise { + this.logger.debug(`Creating master secret with id '${masterSecretId}' in wallet with handle '${walletHandle}'`) + + try { + await this.indySdk.proverCreateMasterSecret(walletHandle, masterSecretId) + + return masterSecretId + } catch (error) { + if (isIndyError(error, 'AnoncredsMasterSecretDuplicateNameError')) { + // master secret id is the same as the master secret id passed in the create function + // so if it already exists we can just assign it. + this.logger.debug( + `Master secret with id '${masterSecretId}' already exists in wallet with handle '${walletHandle}'`, + { + indyError: 'AnoncredsMasterSecretDuplicateNameError', + } + ) + + return masterSecretId + } else { + if (!isIndyError(error)) { + throw new AriesFrameworkError('Attempted to throw Indy error, but it was not an Indy error') + } + + this.logger.error(`Error creating master secret with id ${masterSecretId}`, { + indyError: error.indyName, + error, + }) + + throw new WalletError( + `Error creating master secret with id ${masterSecretId} in wallet with handle '${walletHandle}'`, + { cause: error } + ) + } + } + } + + public async initPublicDid(didConfig: DidConfig) { + const { did, verkey } = await this.createDid(didConfig) + this.publicDidInfo = { + did, + verkey, + } + } + + public async createDid(didConfig?: DidConfig): Promise { + try { + const [did, verkey] = await this.indySdk.createAndStoreMyDid(this.handle, didConfig || {}) + + return { did, verkey } + } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + throw new WalletError('Error creating Did', { cause: error }) + } + } + + /** + * Create a key with an optional seed and keyType. + * The keypair is also automatically stored in the wallet afterwards + * + * Bls12381g1g2 and X25519 are not supported. + * + * @param seed string The seed for creating a key + * @param keyType KeyType the type of key that should be created + * + * @returns a Key instance with a publicKeyBase58 + * + * @throws {WalletError} When an unsupported keytype is requested + * @throws {WalletError} When the key could not be created + */ + public async createKey({ seed, keyType }: WalletCreateKeyOptions): Promise { + try { + // Ed25519 is supported natively in Indy wallet + if (keyType === KeyType.Ed25519) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + const verkey = await this.indySdk.createKey(this.handle, { seed, crypto_type: 'ed25519' }) + return Key.fromPublicKeyBase58(verkey, keyType) + } + + // Check if there is a signing key provider for the specified key type. + if (this.signingKeyProviderRegistry.hasProviderForKeyType(keyType)) { + const signingKeyProvider = this.signingKeyProviderRegistry.getProviderForKeyType(keyType) + + const keyPair = await signingKeyProvider.createKeyPair({ seed }) + await this.storeKeyPair(keyPair) + return Key.fromPublicKeyBase58(keyPair.publicKeyBase58, keyType) + } + } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + throw new WalletError(`Error creating key with key type '${keyType}': ${error.message}`, { cause: error }) + } + + throw new WalletError(`Unsupported key type: '${keyType}' for wallet IndySdkWallet`) + } + + /** + * sign a Buffer with an instance of a Key class + * + * Bls12381g1g2, Bls12381g1 and X25519 are not supported. + * + * @param data Buffer The data that needs to be signed + * @param key Key The key that is used to sign the data + * + * @returns A signature for the data + */ + public async sign({ data, key }: WalletSignOptions): Promise { + try { + // Ed25519 is supported natively in Indy wallet + if (key.keyType === KeyType.Ed25519) { + // Checks to see if it is an not an Array of messages, but just a single one + if (!TypedArrayEncoder.isTypedArray(data)) { + throw new WalletError(`${KeyType.Ed25519} does not support multiple singing of multiple messages`) + } + return await this.indySdk.cryptoSign(this.handle, key.publicKeyBase58, data as Buffer) + } + + // Check if there is a signing key provider for the specified key type. + if (this.signingKeyProviderRegistry.hasProviderForKeyType(key.keyType)) { + const signingKeyProvider = this.signingKeyProviderRegistry.getProviderForKeyType(key.keyType) + + const keyPair = await this.retrieveKeyPair(key.publicKeyBase58) + const signed = await signingKeyProvider.sign({ + data, + privateKeyBase58: keyPair.privateKeyBase58, + publicKeyBase58: key.publicKeyBase58, + }) + + return signed + } + } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + throw new WalletError(`Error signing data with verkey ${key.publicKeyBase58}`, { cause: error }) + } + throw new WalletError(`Unsupported keyType: ${key.keyType}`) + } + + /** + * Verify the signature with the data and the used key + * + * Bls12381g1g2, Bls12381g1 and X25519 are not supported. + * + * @param data Buffer The data that has to be confirmed to be signed + * @param key Key The key that was used in the signing process + * @param signature Buffer The signature that was created by the signing process + * + * @returns A boolean whether the signature was created with the supplied data and key + * + * @throws {WalletError} When it could not do the verification + * @throws {WalletError} When an unsupported keytype is used + */ + public async verify({ data, key, signature }: WalletVerifyOptions): Promise { + try { + // Ed25519 is supported natively in Indy wallet + if (key.keyType === KeyType.Ed25519) { + // Checks to see if it is an not an Array of messages, but just a single one + if (!TypedArrayEncoder.isTypedArray(data)) { + throw new WalletError(`${KeyType.Ed25519} does not support multiple singing of multiple messages`) + } + return await this.indySdk.cryptoVerify(key.publicKeyBase58, data as Buffer, signature) + } + + // Check if there is a signing key provider for the specified key type. + if (this.signingKeyProviderRegistry.hasProviderForKeyType(key.keyType)) { + const signingKeyProvider = this.signingKeyProviderRegistry.getProviderForKeyType(key.keyType) + + const signed = await signingKeyProvider.verify({ + data, + signature, + publicKeyBase58: key.publicKeyBase58, + }) + + return signed + } + } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + throw new WalletError(`Error verifying signature of data signed with verkey ${key.publicKeyBase58}`, { + cause: error, + }) + } + throw new WalletError(`Unsupported keyType: ${key.keyType}`) + } + + public async pack( + payload: Record, + recipientKeys: string[], + senderVerkey?: string + ): Promise { + try { + const messageRaw = JsonEncoder.toBuffer(payload) + const packedMessage = await this.indySdk.packMessage(this.handle, messageRaw, recipientKeys, senderVerkey ?? null) + return JsonEncoder.fromBuffer(packedMessage) + } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + throw new WalletError('Error packing message', { cause: error }) + } + } + + public async unpack(messagePackage: EncryptedMessage): Promise { + try { + const unpackedMessageBuffer = await this.indySdk.unpackMessage(this.handle, JsonEncoder.toBuffer(messagePackage)) + const unpackedMessage = JsonEncoder.fromBuffer(unpackedMessageBuffer) + return { + senderKey: unpackedMessage.sender_verkey, + recipientKey: unpackedMessage.recipient_verkey, + plaintextMessage: JsonEncoder.fromString(unpackedMessage.message), + } + } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + throw new WalletError('Error unpacking message', { cause: error }) + } + } + + public async generateNonce(): Promise { + try { + return await this.indySdk.generateNonce() + } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + throw new WalletError('Error generating nonce', { cause: error }) + } + } + + private async retrieveKeyPair(publicKeyBase58: string): Promise { + try { + const { value } = await this.indySdk.getWalletRecord(this.handle, 'KeyPairRecord', `key-${publicKeyBase58}`, {}) + if (value) { + return JsonEncoder.fromString(value) as KeyPair + } else { + throw new WalletError(`No content found for record with public key: ${publicKeyBase58}`) + } + } catch (error) { + if (isIndyError(error, 'WalletItemNotFound')) { + throw new RecordNotFoundError(`KeyPairRecord not found for public key: ${publicKeyBase58}.`, { + recordType: 'KeyPairRecord', + cause: error, + }) + } + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + private async storeKeyPair(keyPair: KeyPair): Promise { + try { + await this.indySdk.addWalletRecord( + this.handle, + 'KeyPairRecord', + `key-${keyPair.publicKeyBase58}`, + JSON.stringify(keyPair), + { + keyType: keyPair.keyType, + } + ) + } catch (error) { + if (isIndyError(error, 'WalletItemAlreadyExists')) { + throw new RecordDuplicateError(`Record already exists`, { recordType: 'KeyPairRecord' }) + } + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async generateWalletKey() { + try { + return await this.indySdk.generateWalletKey() + } catch (error) { + throw new WalletError('Error generating wallet key', { cause: error }) + } + } +} diff --git a/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts b/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts new file mode 100644 index 0000000000..4b7f822f0e --- /dev/null +++ b/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts @@ -0,0 +1,125 @@ +import type { WalletConfig } from '@aries-framework/core' + +import { + KeyType, + WalletError, + SigningProviderRegistry, + TypedArrayEncoder, + KeyDerivationMethod, +} from '@aries-framework/core' + +import testLogger from '../../../../core/tests/logger' +import { agentDependencies } from '../../../../node/src' +import { IndySdkWallet } from '../IndySdkWallet' + +// use raw key derivation method to speed up wallet creating / opening / closing between tests +const walletConfig: WalletConfig = { + id: 'Wallet: IndySdkWalletTest', + // generated using indy.generateWalletKey + key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', + keyDerivationMethod: KeyDerivationMethod.Raw, +} + +const walletConfigWithMasterSecretId: WalletConfig = { + id: 'Wallet: WalletTestWithMasterSecretId', + // generated using indy.generateWalletKey + key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', + keyDerivationMethod: KeyDerivationMethod.Raw, + masterSecretId: 'customMasterSecretId', +} + +describe('IndySdkWallet', () => { + let indySdkWallet: IndySdkWallet + + const seed = 'sample-seed' + const message = TypedArrayEncoder.fromString('sample-message') + + beforeEach(async () => { + indySdkWallet = new IndySdkWallet(agentDependencies.indy, testLogger, new SigningProviderRegistry([])) + await indySdkWallet.createAndOpen(walletConfig) + }) + + afterEach(async () => { + await indySdkWallet.delete() + }) + + test('Get the public DID', async () => { + await indySdkWallet.initPublicDid({ seed: '000000000000000000000000Trustee9' }) + expect(indySdkWallet.publicDid).toMatchObject({ + did: expect.any(String), + verkey: expect.any(String), + }) + }) + + test('Get the Master Secret', () => { + expect(indySdkWallet.masterSecretId).toEqual('Wallet: IndySdkWalletTest') + }) + + test('Get the wallet handle', () => { + expect(indySdkWallet.handle).toEqual(expect.any(Number)) + }) + + test('Initializes a public did', async () => { + await indySdkWallet.initPublicDid({ seed: '00000000000000000000000Forward01' }) + + expect(indySdkWallet.publicDid).toEqual({ + did: 'DtWRdd6C5dN5vpcN6XRAvu', + verkey: '82RBSn3heLgXzZd74UsMC8Q8YRfEEhQoAM7LUqE6bevJ', + }) + }) + + test('Generate Nonce', async () => { + await expect(indySdkWallet.generateNonce()).resolves.toEqual(expect.any(String)) + }) + + test('Create ed25519 keypair', async () => { + await expect( + indySdkWallet.createKey({ seed: '2103de41b4ae37e8e28586d84a342b67', keyType: KeyType.Ed25519 }) + ).resolves.toMatchObject({ + keyType: KeyType.Ed25519, + }) + }) + + test('Fail to create x25519 keypair', async () => { + await expect(indySdkWallet.createKey({ seed, keyType: KeyType.X25519 })).rejects.toThrowError(WalletError) + }) + + test('Create a signature with a ed25519 keypair', async () => { + const ed25519Key = await indySdkWallet.createKey({ keyType: KeyType.Ed25519 }) + const signature = await indySdkWallet.sign({ + data: message, + key: ed25519Key, + }) + expect(signature.length).toStrictEqual(64) + }) + + test('Verify a signed message with a ed25519 publicKey', async () => { + const ed25519Key = await indySdkWallet.createKey({ keyType: KeyType.Ed25519 }) + const signature = await indySdkWallet.sign({ + data: message, + key: ed25519Key, + }) + await expect(indySdkWallet.verify({ key: ed25519Key, data: message, signature })).resolves.toStrictEqual(true) + }) + + test('masterSecretId is equal to wallet ID by default', async () => { + expect(indySdkWallet.masterSecretId).toEqual(walletConfig.id) + }) +}) + +describe('IndySdkWallet with custom Master Secret Id', () => { + let indySdkWallet: IndySdkWallet + + beforeEach(async () => { + indySdkWallet = new IndySdkWallet(agentDependencies.indy, testLogger, new SigningProviderRegistry([])) + await indySdkWallet.createAndOpen(walletConfigWithMasterSecretId) + }) + + afterEach(async () => { + await indySdkWallet.delete() + }) + + test('masterSecretId is set by config', async () => { + expect(indySdkWallet.masterSecretId).toEqual(walletConfigWithMasterSecretId.masterSecretId) + }) +}) diff --git a/packages/indy-sdk/src/wallet/index.ts b/packages/indy-sdk/src/wallet/index.ts new file mode 100644 index 0000000000..b327ed63bf --- /dev/null +++ b/packages/indy-sdk/src/wallet/index.ts @@ -0,0 +1 @@ +export { IndySdkWallet } from './IndySdkWallet' diff --git a/packages/indy-sdk/tsconfig.build.json b/packages/indy-sdk/tsconfig.build.json new file mode 100644 index 0000000000..2b75d0adab --- /dev/null +++ b/packages/indy-sdk/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["src/**/*"] +} diff --git a/packages/indy-sdk/tsconfig.json b/packages/indy-sdk/tsconfig.json new file mode 100644 index 0000000000..46efe6f721 --- /dev/null +++ b/packages/indy-sdk/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + } +} diff --git a/packages/question-answer/package.json b/packages/question-answer/package.json index 8be53f94cc..01a769bea8 100644 --- a/packages/question-answer/package.json +++ b/packages/question-answer/package.json @@ -26,10 +26,8 @@ "dependencies": { "class-transformer": "0.5.1", "class-validator": "0.13.1", - "rxjs": "^7.2.0" - }, - "peerDependencies": { - "@aries-framework/core": "0.2.5" + "rxjs": "^7.2.0", + "@aries-framework/core": "0.3.2" }, "devDependencies": { "@aries-framework/node": "0.3.2", diff --git a/packages/react-native/package.json b/packages/react-native/package.json index dfef41e5d2..dab91dd4ec 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -29,7 +29,7 @@ "events": "^3.3.0" }, "devDependencies": { - "@types/indy-sdk-react-native": "npm:@types/indy-sdk@^1.16.21", + "@types/indy-sdk-react-native": "npm:@types/indy-sdk@1.16.24", "@types/react-native": "^0.64.10", "indy-sdk-react-native": "^0.3.0", "react": "17.0.1", diff --git a/tests/transport/SubjectOutboundTransport.ts b/tests/transport/SubjectOutboundTransport.ts index 7a7adfaa8e..16868df737 100644 --- a/tests/transport/SubjectOutboundTransport.ts +++ b/tests/transport/SubjectOutboundTransport.ts @@ -11,7 +11,7 @@ export class SubjectOutboundTransport implements OutboundTransport { private agent!: Agent private stop$!: Subject - public supportedSchemes = ['rxjs'] + public supportedSchemes = ['rxjs', 'wss'] public constructor(subjectMap: { [key: string]: Subject | undefined }) { this.subjectMap = subjectMap diff --git a/yarn.lock b/yarn.lock index d28283327d..eb7826abc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,38 +29,38 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.1.tgz#72d647b4ff6a4f82878d184613353af1dd0290f9" - integrity sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30" + integrity sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ== "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.1.tgz#c8fa615c5e88e272564ace3d42fbc8b17bfeb22b" - integrity sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw== + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.2.tgz#8dc9b1620a673f92d3624bd926dc49a52cf25b92" + integrity sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.0" - "@babel/helper-compilation-targets" "^7.19.1" - "@babel/helper-module-transforms" "^7.19.0" - "@babel/helpers" "^7.19.0" - "@babel/parser" "^7.19.1" + "@babel/generator" "^7.20.2" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-module-transforms" "^7.20.2" + "@babel/helpers" "^7.20.1" + "@babel/parser" "^7.20.2" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.1" - "@babel/types" "^7.19.0" + "@babel/traverse" "^7.20.1" + "@babel/types" "^7.20.2" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.19.0", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.0.tgz#785596c06425e59334df2ccee63ab166b738419a" - integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== +"@babel/generator@^7.20.1", "@babel/generator@^7.20.2", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": + version "7.20.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.4.tgz#4d9f8f0c30be75fd90a0562099a26e5839602ab8" + integrity sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA== dependencies: - "@babel/types" "^7.19.0" + "@babel/types" "^7.20.2" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" @@ -79,27 +79,27 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz#7f630911d83b408b76fe584831c98e5395d7a17c" - integrity sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a" + integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ== dependencies: - "@babel/compat-data" "^7.19.1" + "@babel/compat-data" "^7.20.0" "@babel/helper-validator-option" "^7.18.6" browserslist "^4.21.3" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b" - integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.2.tgz#3c08a5b5417c7f07b5cf3dfb6dc79cbec682e8c2" + integrity sha512-k22GoYRAHPYr9I+Gvy2ZQlAe5mGy8BqWst2wRt8cwIufWTxrsVshhIBvYNqC80N0GSFWTsqRVexOtfzlgOEDvA== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" "@babel/helper-member-expression-to-functions" "^7.18.9" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-replace-supers" "^7.19.1" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-create-regexp-features-plugin@^7.18.6": @@ -163,19 +163,19 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30" - integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== +"@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz#ac53da669501edd37e658602a21ba14c08748712" + integrity sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" + "@babel/traverse" "^7.20.1" + "@babel/types" "^7.20.2" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -184,12 +184,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" - integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" + integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== @@ -200,19 +200,19 @@ "@babel/traverse" "^7.19.1" "@babel/types" "^7.19.0" -"@babel/helper-simple-access@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" - integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== +"@babel/helper-simple-access@^7.19.4", "@babel/helper-simple-access@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" + integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.20.2" "@babel/helper-skip-transparent-expression-wrappers@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818" - integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw== + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" + integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== dependencies: - "@babel/types" "^7.18.9" + "@babel/types" "^7.20.0" "@babel/helper-split-export-declaration@^7.18.6": version "7.18.6" @@ -221,12 +221,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-string-parser@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" - integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== -"@babel/helper-validator-identifier@^7.18.6": +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== @@ -236,14 +236,14 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== -"@babel/helpers@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18" - integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== +"@babel/helpers@^7.20.1": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.1.tgz#2ab7a0fcb0a03b5bf76629196ed63c2d7311f4c9" + integrity sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg== dependencies: "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" + "@babel/traverse" "^7.20.1" + "@babel/types" "^7.20.0" "@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": version "7.18.6" @@ -254,10 +254,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.1.tgz#6f6d6c2e621aad19a92544cc217ed13f1aac5b4c" - integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.20.1", "@babel/parser@^7.20.2": + version "7.20.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.3.tgz#5358cf62e380cf69efcb87a7bb922ff88bfac6e2" + integrity sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg== "@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.1.0": version "7.18.6" @@ -284,15 +284,15 @@ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" "@babel/plugin-proposal-object-rest-spread@^7.0.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz#f9434f6beb2c8cae9dfcf97d2a5941bbbf9ad4e7" - integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q== + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz#a556f59d555f06961df1e572bb5eca864c84022d" + integrity sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ== dependencies: - "@babel/compat-data" "^7.18.8" - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/compat-data" "^7.20.1" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-transform-parameters" "^7.20.1" "@babel/plugin-proposal-optional-catch-binding@^7.0.0": version "7.18.6" @@ -423,12 +423,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.18.6", "@babel/plugin-syntax-typescript@^7.7.2": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz#1c09cd25795c7c2b8a4ba9ae49394576d4133285" - integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== +"@babel/plugin-syntax-typescript@^7.20.0", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz#4e9a0cfc769c85689b77a2e642d24e9f697fc8c7" + integrity sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-arrow-functions@^7.0.0": version "7.18.6" @@ -445,24 +445,24 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-block-scoping@^7.0.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz#f9b7e018ac3f373c81452d6ada8bd5a18928926d" - integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw== + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz#f59b1767e6385c663fd0bce655db6ca9c8b236ed" + integrity sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-classes@^7.0.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz#0e61ec257fba409c41372175e7c1e606dc79bb20" - integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz#c0033cf1916ccf78202d04be4281d161f6709bb2" + integrity sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-compilation-targets" "^7.20.0" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-replace-supers" "^7.19.1" "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" @@ -474,11 +474,11 @@ "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-destructuring@^7.0.0": - version "7.18.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz#9e03bc4a94475d62b7f4114938e6c5c33372cbf5" - integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow== + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz#c23741cfa44ddd35f5e53896e88c75331b8b2792" + integrity sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-exponentiation-operator@^7.0.0": version "7.18.6" @@ -527,14 +527,13 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.1.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz#afd243afba166cca69892e24a8fd8c9f2ca87883" - integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q== + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz#25b32feef24df8038fc1ec56038917eacb0b730c" + integrity sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ== dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-simple-access" "^7.18.6" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-simple-access" "^7.19.4" "@babel/plugin-transform-object-assign@^7.0.0": version "7.18.6" @@ -551,12 +550,12 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-replace-supers" "^7.18.6" -"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.18.8": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz#ee9f1a0ce6d78af58d0956a9378ea3427cccb48a" - integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg== +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.20.1": + version "7.20.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz#7b3468d70c3c5b62e46be0a47b6045d8590fb748" + integrity sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-property-literals@^7.0.0": version "7.18.6" @@ -580,11 +579,11 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-react-jsx-source@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.18.6.tgz#06e9ae8a14d2bc19ce6e3c447d842032a50598fc" - integrity sha512-utZmlASneDfdaMh0m/WausbjUjEdGrQJz0vFK93d7wD3xf5wBtX219+q6IlCNZeguIcxS2f/CvLZrlLSvSHQXw== + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz#88578ae8331e5887e8ce28e4c9dc83fb29da0b86" + integrity sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-react-jsx@^7.0.0": version "7.19.0" @@ -606,9 +605,9 @@ regenerator-transform "^0.15.0" "@babel/plugin-transform-runtime@^7.0.0": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.1.tgz#a3df2d7312eea624c7889a2dcd37fd1dfd25b2c6" - integrity sha512-2nJjTUFIzBMP/f/miLxEK9vxwW/KUXsdvN4sR//TmuDhe6yU2h57WmIOE12Gng3MDP/xpjUV/ToZRdcf8Yj4fA== + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz#9d2a9dbf4e12644d6f46e5e75bfbf02b5d6e9194" + integrity sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw== dependencies: "@babel/helper-module-imports" "^7.18.6" "@babel/helper-plugin-utils" "^7.19.0" @@ -647,13 +646,13 @@ "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-typescript@^7.18.6", "@babel/plugin-transform-typescript@^7.5.0": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.1.tgz#adcf180a041dcbd29257ad31b0c65d4de531ce8d" - integrity sha512-+ILcOU+6mWLlvCwnL920m2Ow3wWx3Wo8n2t5aROQmV55GZt+hOiLvBaa3DNzRjSEHa1aauRs4/YLmkCfFkhhRQ== + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.2.tgz#91515527b376fc122ba83b13d70b01af8fe98f3f" + integrity sha512-jvS+ngBfrnTUBfOQq8NfGnSbF9BrqlR6hjJ2yVxMkmO5nL/cdifNbI30EfjRlN4g5wYWNnMPyj5Sa6R1pbLeag== dependencies: - "@babel/helper-create-class-features-plugin" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/plugin-syntax-typescript" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.20.2" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-typescript" "^7.20.0" "@babel/plugin-transform-unicode-regex@^7.0.0": version "7.18.6" @@ -693,11 +692,11 @@ source-map-support "^0.5.16" "@babel/runtime@^7.8.4": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" - integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9" + integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg== dependencies: - regenerator-runtime "^0.13.4" + regenerator-runtime "^0.13.10" "@babel/template@^7.0.0", "@babel/template@^7.18.10", "@babel/template@^7.3.3": version "7.18.10" @@ -708,29 +707,29 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.7.2": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.1.tgz#0fafe100a8c2a603b4718b1d9bf2568d1d193347" - integrity sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.7.2": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.1.tgz#9b15ccbf882f6d107eeeecf263fbcdd208777ec8" + integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.0" + "@babel/generator" "^7.20.1" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.1" - "@babel/types" "^7.19.0" + "@babel/parser" "^7.20.1" + "@babel/types" "^7.20.0" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600" - integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.2.tgz#67ac09266606190f496322dbaff360fdaa5e7842" + integrity sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog== dependencies: - "@babel/helper-string-parser" "^7.18.10" - "@babel/helper-validator-identifier" "^7.18.6" + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -1075,7 +1074,7 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@^3.0.3": +"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== @@ -1085,7 +1084,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== @@ -1099,12 +1098,12 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping@^0.3.9": - version "0.3.15" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" - integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" "@lerna/add@4.0.0": version "4.0.0" @@ -2032,10 +2031,10 @@ dependencies: "@octokit/openapi-types" "^12.11.0" -"@peculiar/asn1-schema@^2.1.6": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.0.tgz#5368416eb336138770c692ffc2bab119ee3ae917" - integrity sha512-DtNLAG4vmDrdSJFPe7rypkcj597chNQL7u+2dBtYo5mh7VW2+im6ke+O0NVr8W1f4re4C3F71LhoMb0Yxqa48Q== +"@peculiar/asn1-schema@^2.1.6", "@peculiar/asn1-schema@^2.3.0": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.3.tgz#21418e1f3819e0b353ceff0c2dad8ccb61acd777" + integrity sha512-6GptMYDMyWBHTUKndHaDsRZUO/XMSgIns2krxcm2L7SEExRHwawFvSwNBhqNPR9HJwv3MruAiF1bhN0we6j6GQ== dependencies: asn1js "^3.0.5" pvtsutils "^1.3.2" @@ -2049,14 +2048,14 @@ tslib "^2.0.0" "@peculiar/webcrypto@^1.0.22": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.0.tgz#f941bd95285a0f8a3d2af39ccda5197b80cd32bf" - integrity sha512-U58N44b2m3OuTgpmKgf0LPDOmP3bhwNz01vAnj1mBwxBASRhptWYK+M3zG+HBkDqGQM+bFsoIihTW8MdmPXEqg== + version "1.4.1" + resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.1.tgz#821493bd5ad0f05939bd5f53b28536f68158360a" + integrity sha512-eK4C6WTNYxoI7JOabMoZICiyqRRtJB220bh0Mbj5RwRycleZf9BPyZoxsTvpP0FpmVS2aS13NKOuh5/tN3sIRw== dependencies: - "@peculiar/asn1-schema" "^2.1.6" + "@peculiar/asn1-schema" "^2.3.0" "@peculiar/json-schema" "^1.1.12" pvtsutils "^1.3.2" - tslib "^2.4.0" + tslib "^2.4.1" webcrypto-core "^1.7.4" "@react-native-community/cli-debugger-ui@^5.0.1": @@ -2216,9 +2215,9 @@ integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== "@sinonjs/commons@^1.7.0": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + version "1.8.5" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.5.tgz#e280c94c95f206dcfd5aca00a43f2156b758c764" + integrity sha512-rTpCA0wG1wUxglBSFdMMY0oTrKYvgf4fNgv/sXbfCVAdf+FnPBdKJR/7XbpTCwbCrvCbdPYnlWaUUYz4V2fPDA== dependencies: type-detect "4.0.8" @@ -2325,9 +2324,9 @@ integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": - version "7.1.19" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" - integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== + version "7.1.20" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.20.tgz#e168cdd612c92a2d335029ed62ac94c95b362359" + integrity sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" @@ -2442,17 +2441,17 @@ dependencies: "@types/node" "*" -"@types/indy-sdk-react-native@npm:@types/indy-sdk@^1.16.21", "@types/indy-sdk@^1.16.21": - version "1.16.21" - resolved "https://registry.yarnpkg.com/@types/indy-sdk/-/indy-sdk-1.16.21.tgz#bb6178e2a515115b1bf225fb78506a3017d08aa8" - integrity sha512-SIu1iOa77lkxkGlW09OinFwebe7U5oDYwI70NnPoe9nbDr63i0FozITWEyIdC1BloKvZRXne6nM4i9zy6E3n6g== +"@types/indy-sdk-react-native@npm:@types/indy-sdk@1.16.24", "@types/indy-sdk@1.16.24": + version "1.16.24" + resolved "https://registry.yarnpkg.com/@types/indy-sdk/-/indy-sdk-1.16.24.tgz#1b8e33e8fd2a095a29cb06b76146ed14d1477cdd" + integrity sha512-5YliU8lqahihz46MPpiu1ZWNkG2c/lm9SI+Fp3DUV2HrGbuAPxI8dYg2CP6avuD5kfCYr6Y5+TaqeOH/aID0FQ== dependencies: buffer "^6.0.0" "@types/inquirer@^8.1.3": - version "8.2.3" - resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.3.tgz#985515d04879a0d0c1f5f49ec375767410ba9dab" - integrity sha512-ZlBqD+8WIVNy3KIVkl+Qne6bGLW2erwN0GJXY9Ri/9EMbyupee3xw3H0Mmv5kJoLyNpfd/oHlwKxO0DUDH7yWA== + version "8.2.5" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.5.tgz#c508423bcc11126db278170ab07347783ac2300c" + integrity sha512-QXlzybid60YtAwfgG3cpykptRYUx2KomzNutMlWsQC64J/WG/gQSl+P4w7A21sGN0VIxRVava4rgnT7FQmFCdg== dependencies: "@types/through" "*" @@ -2562,16 +2561,16 @@ integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== "@types/react-native@^0.64.10": - version "0.64.27" - resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.64.27.tgz#f16e0b713c733c2476e7b16c92bf767f0675c560" - integrity sha512-vOEGMQGKNp6B1UfofKvCit2AxwByI6cbSa71E2uuxuvFr7FATVlykDbUS/Yht1HJhnbP5qlNOYw4ocUvDGjwbA== + version "0.64.29" + resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.64.29.tgz#521544f12f01192e38cdaa376817eceb0c4104db" + integrity sha512-nCa4rcAlilTWL7wEUwTnxo6HjxQvFjVeDPK9taglDvId06pw/eOUu2NozfpwY91o8K7UdZn8VUoDRaGt2i8LBA== dependencies: "@types/react" "^17" "@types/react@^17": - version "17.0.50" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.50.tgz#39abb4f7098f546cfcd6b51207c90c4295ee81fc" - integrity sha512-ZCBHzpDb5skMnc1zFXAXnL3l1FAdi+xZvwxK+PkglMmBrwjpp9nKaWuEvrGnSifCJmBFGxZOOFuwC6KH/s0NuA== + version "17.0.52" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.52.tgz#10d8b907b5c563ac014a541f289ae8eaa9bf2e9b" + integrity sha512-vwk8QqVODi0VaZZpDXQCmEmiOuyjEFPY7Ttaw5vjM112LOq37yz1CDJGrRJwA1fYEq4Iitd5rnjd1yWAc/bT+A== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -2621,15 +2620,15 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== -"@types/validator@^13.1.3": - version "13.7.7" - resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.7.tgz#e87cf34dd08522d21acf30130fd8941f433b81b5" - integrity sha512-jiEw2kTUJ8Jsh4A1K4b5Pkjj9Xz6FktLLOQ36ZVLRkmxFbpTvAV2VRoKMojz8UlZxNg/2dZqzpigH4JYn1bkQg== +"@types/validator@^13.1.3", "@types/validator@^13.7.10": + version "13.7.10" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.10.tgz#f9763dc0933f8324920afa9c0790308eedf55ca7" + integrity sha512-t1yxFAR2n0+VO6hd/FJ9F2uezAZVWHLmpmlJzm1eX03+H7+HsuTAp7L8QJs+2pQCfWkP1+EXsGK9Z9v7o/qPVQ== "@types/varint@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@types/varint/-/varint-6.0.0.tgz#4ad73c23cbc9b7e44379a7729ace7ed9c8bc9854" - integrity sha512-2jBazyxGl4644tvu3VAez8UA/AtrcEetT9HOeAbqZ/vAcRVL/ZDFQjSS7rkWusU5cyONQVUz+nwwrNZdMva4ow== + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/varint/-/varint-6.0.1.tgz#018d424627c7951d370d73816e97e143dc99523b" + integrity sha512-fQdOiZpDMBvaEdl12P1x7xlTPRAtd7qUUtVaWgkCy8DC//wCv19nqFFtrnR3y/ac6VFY0UUvYuQqfKzZTSE26w== dependencies: "@types/node" "*" @@ -2811,9 +2810,9 @@ acorn@^7.1.1, acorn@^7.4.0: integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.2.4, acorn@^8.4.1: - version "8.8.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" - integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + version "8.8.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" + integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== add-stream@^1.0.0: version "1.0.0" @@ -2855,9 +2854,9 @@ ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: uri-js "^4.2.2" ajv@^8.0.1: - version "8.11.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" - integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== + version "8.11.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.2.tgz#aecb20b50607acf2569b6382167b65a96008bb78" + integrity sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -2933,9 +2932,9 @@ anymatch@^2.0.0: normalize-path "^2.1.1" anymatch@^3.0.3: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" @@ -3037,20 +3036,20 @@ array-ify@^1.0.0: integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== array-includes@^3.1.4: - version "3.1.5" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" - integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== + version "3.1.6" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" + integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" - get-intrinsic "^1.1.1" + es-abstract "^1.20.4" + get-intrinsic "^1.1.3" is-string "^1.0.7" array-map@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" - integrity sha512-123XMszMB01QKVptpDQ7x1m1pP5NmJIG1kbl0JSPPRezvwQChxAN0Gvzo7rvR1IZ2tOL2tmiy7kY/KKgnpVVpg== + version "0.0.1" + resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.1.tgz#d1bf3cc8813a7daaa335e5c8eb21d9d06230c1a7" + integrity sha512-sxHIeJTGEsRC8/hYkZzdJNNPZ41EXHVys7pqMw1iwE/Kx8/hto0UbDuGQsSJ0ujPovj9qUZl6EOY/EiZ2g3d9Q== array-reduce@~0.0.0: version "0.0.0" @@ -3068,23 +3067,23 @@ array-unique@^0.3.2: integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== array.prototype.flat@^1.2.5: - version "1.3.0" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b" - integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw== + version "1.3.1" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" + integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" es-shim-unscopables "^1.0.0" -array.prototype.reduce@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz#8167e80089f78bff70a99e20bd4201d4663b0a6f" - integrity sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw== +array.prototype.reduce@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz#6b20b0daa9d9734dd6bc7ea66b5bbce395471eac" + integrity sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" es-array-method-boxes-properly "^1.0.0" is-string "^1.0.7" @@ -3228,13 +3227,6 @@ babel-jest@^27.5.1: graceful-fs "^4.2.9" slash "^3.0.0" -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== - dependencies: - object.assign "^4.1.0" - babel-plugin-istanbul@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" @@ -3387,9 +3379,9 @@ bcrypt-pbkdf@^1.0.0: tweetnacl "^0.14.3" before-after-hook@^2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" - integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== + version "2.2.3" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" + integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== big-integer@1.6.x: version "1.6.51" @@ -3413,10 +3405,10 @@ bn.js@^5.2.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -body-parser@1.20.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" - integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== dependencies: bytes "3.1.2" content-type "~1.0.4" @@ -3426,7 +3418,7 @@ body-parser@1.20.0: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.10.3" + qs "6.11.0" raw-body "2.5.1" type-is "~1.6.18" unpipe "1.0.0" @@ -3654,9 +3646,9 @@ camelcase@^6.0.0, camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001400: - version "1.0.30001412" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001412.tgz#30f67d55a865da43e0aeec003f073ea8764d5d7c" - integrity sha512-+TeEIee1gS5bYOiuf+PS/kp2mrXic37Hl66VY6EAfxasIk5fELTktK2oOezYed12H8w7jt3s512PpulQidPjwA== + version "1.0.30001434" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz#ec1ec1cfb0a93a34a0600d37903853030520a4e5" + integrity sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA== canonicalize@^1.0.1: version "1.0.8" @@ -3726,16 +3718,16 @@ ci-info@^2.0.0: integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== ci-info@^3.2.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.4.0.tgz#b28484fd436cbc267900364f096c9dc185efb251" - integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== + version "3.7.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.7.0.tgz#6d01b3696c59915b6ce057e4aa4adfc2fa25f5ef" + integrity sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog== cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== -class-transformer@0.5.1: +class-transformer@0.5.1, class-transformer@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== @@ -3759,6 +3751,15 @@ class-validator@0.13.1: libphonenumber-js "^1.9.7" validator "^13.5.2" +class-validator@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.0.tgz#40ed0ecf3c83b2a8a6a320f4edb607be0f0df159" + integrity sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A== + dependencies: + "@types/validator" "^13.7.10" + libphonenumber-js "^1.10.14" + validator "^13.7.0" + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -4146,11 +4147,9 @@ conventional-recommended-bump@^6.1.0: q "^1.5.1" convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== cookie-signature@1.0.6: version "1.0.6" @@ -4168,9 +4167,9 @@ copy-descriptor@^0.1.0: integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== core-js-compat@^3.25.1: - version "3.25.3" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.3.tgz#d6a442a03f4eade4555d4e640e6a06151dd95d38" - integrity sha512-xVtYpJQ5grszDHEUU9O7XbjjcZ0ccX3LgQsyqSvTnjX97ZqEgn9F5srmrwwwMtbKzDllyFPL+O+2OFMl1lU4TQ== + version "3.26.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.26.1.tgz#0e710b09ebf689d719545ac36e49041850f943df" + integrity sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A== dependencies: browserslist "^4.21.4" @@ -4203,9 +4202,9 @@ cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: parse-json "^4.0.0" cosmiconfig@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" - integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== dependencies: "@types/parse-json" "^4.0.0" import-fresh "^3.2.1" @@ -4304,9 +4303,9 @@ dateformat@^3.0.0: integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== dayjs@^1.8.15: - version "1.11.5" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93" - integrity sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA== + version "1.11.6" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.6.tgz#2e79a226314ec3ec904e3ee1dd5a4f5e5b1c7afb" + integrity sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ== debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" @@ -4335,9 +4334,9 @@ debuglog@^1.0.1: integrity sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw== decamelize-keys@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" - integrity sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg== + version "1.1.1" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" + integrity sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg== dependencies: decamelize "^1.1.0" map-obj "^1.0.0" @@ -4348,9 +4347,9 @@ decamelize@^1.1.0, decamelize@^1.2.0: integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== decimal.js@^10.2.1: - version "10.4.1" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.1.tgz#be75eeac4a2281aace80c1a8753587c27ef053e7" - integrity sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw== + version "10.4.2" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.2.tgz#0341651d1d997d86065a2ce3a441fbd0d8e8b98e" + integrity sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA== decode-uri-component@^0.2.0: version "0.2.2" @@ -4383,9 +4382,9 @@ deepmerge@^4.2.2: integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== defaults@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - integrity sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA== + version "1.0.4" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== dependencies: clone "^1.0.2" @@ -4493,9 +4492,9 @@ did-resolver@^3.1.3: integrity sha512-Eeo2F524VM5N3W4GwglZrnul2y6TLTwMQP3In62JdG34NZoqihYyOZLk+5wUW8sSgvIYIcJM8Dlt3xsdKZZ3tg== did-resolver@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-4.0.0.tgz#fc8f657b4cd7f44c2921051fb046599fbe7d4b31" - integrity sha512-/roxrDr9EnAmLs+s9T+8+gcpilMo+IkeytcsGO7dcxvTmVJ+0Rt60HtV8o0UXHhGBo0Q+paMH/0ffXz1rqGFYg== + version "4.0.1" + resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-4.0.1.tgz#11bb3f19ed1c8f53f4af4702912fa9f7852fc305" + integrity sha512-eHs2VLKhcANmh08S87PKvOauIAmSOd7nb7AlhNxcvOyDAIGQY1UfbiqI1VOW5IDKvOO6aEWY+5edOt1qrCp1Eg== diff-sequences@^26.6.2: version "26.6.2" @@ -4578,9 +4577,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.4.251: - version "1.4.262" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.262.tgz#25715dfbae4c2e0640517cba184715241ecd8e63" - integrity sha512-Ckn5haqmGh/xS8IbcgK3dnwAVnhDyo/WQnklWn6yaMucYTq7NNxwlGE8ElzEOnonzRLzUCo2Ot3vUb2GYUF2Hw== + version "1.4.284" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" + integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== emittery@^0.8.1: version "0.8.1" @@ -4655,10 +4654,10 @@ errorhandler@^1.5.0: accepts "~1.3.7" escape-html "~1.0.3" -es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.1: - version "1.20.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.3.tgz#90b143ff7aedc8b3d189bcfac7f1e3e3f81e9da1" - integrity sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw== +es-abstract@^1.19.0, es-abstract@^1.20.4: + version "1.20.4" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.4.tgz#1d103f9f8d78d4cf0713edcd6d0ed1a46eed5861" + integrity sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" @@ -4670,7 +4669,7 @@ es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19 has-property-descriptors "^1.0.0" has-symbols "^1.0.3" internal-slot "^1.0.3" - is-callable "^1.2.6" + is-callable "^1.2.7" is-negative-zero "^2.0.2" is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" @@ -5014,20 +5013,20 @@ expo-modules-autolinking@^0.0.3: fs-extra "^9.1.0" expo-random@*: - version "12.3.0" - resolved "https://registry.yarnpkg.com/expo-random/-/expo-random-12.3.0.tgz#4a45bcb14e285a4a9161e4a5dc82ff6c3fc2ac0c" - integrity sha512-q+AsTfGNT+Q+fb2sRrYtRkI3g5tV4H0kuYXM186aueILGO/vLn/YYFa7xFZj1IZ8LJZg2h96JDPDpsqHfRG2mQ== + version "13.0.0" + resolved "https://registry.yarnpkg.com/expo-random/-/expo-random-13.0.0.tgz#fc9c1496ac9f7555563d86de0db25966739c028f" + integrity sha512-aGb0vtUmFFuW0TF1rdOgsz89zEVD/RXUPUnnZy5+i3jJeQ2PerJ4uo72/EuWqHpCBNto8/qT+aCzFinmQDeTAA== dependencies: base64-js "^1.3.0" express@^4.17.1: - version "4.18.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" - integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.0" + body-parser "1.20.1" content-disposition "0.5.4" content-type "~1.0.4" cookie "0.5.0" @@ -5046,7 +5045,7 @@ express@^4.17.1: parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.7" - qs "6.10.3" + qs "6.11.0" range-parser "~1.2.1" safe-buffer "5.2.1" send "0.18.0" @@ -5314,9 +5313,9 @@ flatted@^3.1.0: integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== flow-parser@0.*: - version "0.187.1" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.187.1.tgz#52b2c7ebd7544b75bda0676380138bc5b3de3177" - integrity sha512-ZvlTeakTTMmYGukt4EIQtLEp4ie45W+jK325uukGgiqFg2Rl7TdpOJQbOLUN2xMeGS+WvXaK0uIJ3coPGDXFGQ== + version "0.193.0" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.193.0.tgz#8d705fc2d6b378a24bae189014f6f0320d040c4f" + integrity sha512-x7ZoArE1UO3Nk2rkq/KK/Tkp714QDMVzEsxIyK2+p7Alx+88LY7KgqmeQZuiAG8TCHucmYuHefbk3KsVFVjouA== flow-parser@^0.121.0: version "0.121.0" @@ -5652,9 +5651,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.6.0, globals@^13.9.0: - version "13.17.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" - integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== + version "13.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.18.0.tgz#fb224daeeb2bb7d254cd2c640f003528b8d0c1dc" + integrity sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A== dependencies: type-fest "^0.20.2" @@ -6103,7 +6102,7 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.2.6: +is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== @@ -6116,9 +6115,9 @@ is-ci@^2.0.0: ci-info "^2.0.0" is-core-module@^2.5.0, is-core-module@^2.8.1, is-core-module@^2.9.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" - integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== dependencies: has "^1.0.3" @@ -6404,9 +6403,9 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" - integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== dependencies: "@babel/core" "^7.12.3" "@babel/parser" "^7.14.7" @@ -6701,9 +6700,9 @@ jest-mock@^27.5.1: "@types/node" "*" jest-pnp-resolver@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== jest-regex-util@^26.0.0: version "26.0.0" @@ -6933,9 +6932,9 @@ jetifier@^1.6.2: integrity sha512-3Zi16h6L5tXDRQJTb221cnRoVG9/9OvreLdLU2/ZjRv/GILL+2Cemt0IKvkowwkDpvouAU1DQPOJ7qaiHeIdrw== joi@^17.2.1: - version "17.6.1" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.1.tgz#e77422f277091711599634ac39a409e599d7bdaa" - integrity sha512-Hl7/iBklIX345OCM1TiFSCZRVaAOLDGlWCp0Df2vWYgBgjkezaR7Kvm3joBciBHQjZj5sxXs859r6eqsRSlG8w== + version "17.7.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.0.tgz#591a33b1fe1aca2bc27f290bcad9b9c1c570a6b3" + integrity sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg== dependencies: "@hapi/hoek" "^9.0.0" "@hapi/topo" "^5.0.0" @@ -7112,9 +7111,9 @@ jsonfile@^6.0.1: graceful-fs "^4.1.6" jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha512-trvBk1ki43VZptdBI5rIlG4YOzyeH/WefQt5rj1grasPn4iiZWKet8nkgc4GlsAylaztn0qZfUYOiTsASJFdNA== + version "0.0.1" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" + integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== jsonparse@^1.2.0, jsonparse@^1.3.1: version "1.3.1" @@ -7246,10 +7245,15 @@ libnpmpublish@^4.0.0: semver "^7.1.3" ssri "^8.0.1" +libphonenumber-js@^1.10.14: + version "1.10.17" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.17.tgz#7efcfa3068fc076bc59a43a08a723ccd95308474" + integrity sha512-UQrNzsusSn5qaojdpWqporWRdpx6AGeb+egj64NrpYuyKHvnSH9jMp/1Dy3b/WnMyJA5zgV1yw//jC6J0dCXkw== + libphonenumber-js@^1.9.7: - version "1.10.13" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.13.tgz#0b5833c7fdbf671140530d83531c6753f7e0ea3c" - integrity sha512-b74iyWmwb4GprAUPjPkJ11GTC7KX4Pd3onpJfKxYyY8y9Rbb4ERY47LvCMEDM09WD3thiLDMXtkfDK/AX+zT7Q== + version "1.10.14" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.14.tgz#e29da7f539751f724ac54017a098e3c7ca23de94" + integrity sha512-McGS7GV/WjJ2KjfOGhJU1oJn29RYeo7Q+RpANRbUNMQ9gj5XArpbjurSuyYPTejFwbaUojstQ4XyWCrAzGOUXw== lines-and-columns@^1.1.6: version "1.2.4" @@ -7948,9 +7952,9 @@ minipass@^2.6.0, minipass@^2.9.0: yallist "^3.0.0" minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: - version "3.3.4" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae" - integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== dependencies: yallist "^4.0.0" @@ -8040,9 +8044,9 @@ mute-stream@0.0.8, mute-stream@~0.0.4: integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== nan@^2.11.1: - version "2.16.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916" - integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA== + version "2.17.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" + integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== nanomatch@^1.2.9: version "1.2.13" @@ -8496,7 +8500,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.0, object.assign@^4.1.4: +object.assign@^4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== @@ -8507,14 +8511,14 @@ object.assign@^4.1.0, object.assign@^4.1.4: object-keys "^1.1.1" object.getownpropertydescriptors@^2.0.3: - version "2.1.4" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz#7965e6437a57278b587383831a9b829455a4bc37" - integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== + version "2.1.5" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz#db5a9002489b64eef903df81d6623c07e5b4b4d3" + integrity sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw== dependencies: - array.prototype.reduce "^1.0.4" + array.prototype.reduce "^1.0.5" call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.20.1" + es-abstract "^1.20.4" object.pick@^1.3.0: version "1.3.0" @@ -8524,13 +8528,13 @@ object.pick@^1.3.0: isobject "^3.0.1" object.values@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" - integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== + version "1.1.6" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" + integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.1.4" + es-abstract "^1.20.4" on-finished@2.4.1: version "2.4.1" @@ -8963,9 +8967,9 @@ prettier-linter-helpers@^1.0.0: fast-diff "^1.1.2" prettier@^2.3.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" - integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== + version "2.8.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.0.tgz#c7df58393c9ba77d6fba3921ae01faf994fb9dc9" + integrity sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA== pretty-format@^26.0.0, pretty-format@^26.5.2, pretty-format@^26.6.2: version "26.6.2" @@ -9010,9 +9014,9 @@ promise-retry@^2.0.1: retry "^0.12.0" promise@^8.0.3: - version "8.2.0" - resolved "https://registry.yarnpkg.com/promise/-/promise-8.2.0.tgz#a1f6280ab67457fbfc8aad2b198c9497e9e5c806" - integrity sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg== + version "8.3.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" + integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg== dependencies: asap "~2.0.6" @@ -9098,14 +9102,7 @@ q@^1.5.1: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== -qs@6.10.3: - version "6.10.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" - integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== - dependencies: - side-channel "^1.0.4" - -qs@^6.9.4: +qs@6.11.0, qs@^6.9.4: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== @@ -9178,9 +9175,9 @@ rc@^1.2.8: strip-json-comments "~2.0.1" react-devtools-core@^4.6.0: - version "4.26.0" - resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.26.0.tgz#d3d0f59d62ccf1ac03017a7e92f0fe71455019cc" - integrity sha512-OO0Q+vXtHYCXvRQ6elLiOUph3MjsCpuYktGTLnBpizYm46f8tAPuJKihGkwsceitHSJNpzNIjJaYHgX96CyTUQ== + version "4.26.1" + resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.26.1.tgz#2893fea58089be64c5356d5bd0eebda8d1bbf317" + integrity sha512-r1csa5n9nABVpSdAadwTG7K+SfgRJPc/Hdx89BkV5IlA1mEGgGi3ir630ST5D/xYlJQaY3VE75YGADgpNW7HIw== dependencies: shell-quote "^1.6.1" ws "^7" @@ -9468,15 +9465,15 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.4: - version "0.13.9" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" - integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +regenerator-runtime@^0.13.10, regenerator-runtime@^0.13.2: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== regenerator-transform@^0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" - integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== + version "0.15.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" + integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== dependencies: "@babel/runtime" "^7.8.4" @@ -9503,16 +9500,16 @@ regexpp@^3.1.0: integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== regexpu-core@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.1.tgz#a69c26f324c1e962e9ffd0b88b055caba8089139" - integrity sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ== + version "5.2.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.2.tgz#3e4e5d12103b64748711c3aad69934d7718e75fc" + integrity sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw== dependencies: regenerate "^1.4.2" regenerate-unicode-properties "^10.1.0" regjsgen "^0.7.1" regjsparser "^0.9.1" unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" regjsgen@^0.7.1: version "0.7.1" @@ -9793,9 +9790,9 @@ scheduler@^0.20.1: integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== semver@7.x, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: - version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== dependencies: lru-cache "^6.0.0" @@ -9907,9 +9904,9 @@ shell-quote@1.6.1: jsonify "~0.0.0" shell-quote@^1.6.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" - integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== + version "1.7.4" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.4.tgz#33fe15dee71ab2a81fcbd3a52106c5cfb9fb75d8" + integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw== shelljs@^0.8.4: version "0.8.5" @@ -10030,9 +10027,9 @@ socks-proxy-agent@^6.0.0: socks "^2.6.2" socks@^2.3.3, socks@^2.6.2: - version "2.7.0" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.0.tgz#f9225acdb841e874dca25f870e9130990f3913d0" - integrity sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA== + version "2.7.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== dependencies: ip "^2.0.0" smart-buffer "^4.2.0" @@ -10170,9 +10167,9 @@ ssri@^8.0.0, ssri@^8.0.1: minipass "^3.1.1" stack-utils@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" - integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: escape-string-regexp "^2.0.0" @@ -10248,22 +10245,22 @@ string-width@^1.0.1: strip-ansi "^6.0.1" string.prototype.trimend@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" - integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" + integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" + es-abstract "^1.20.4" string.prototype.trimstart@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" - integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" + integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" + es-abstract "^1.20.4" string_decoder@^1.1.1: version "1.3.0" @@ -10401,9 +10398,9 @@ table-layout@^1.0.2: wordwrapjs "^4.0.0" table@^6.0.9: - version "6.8.0" - resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca" - integrity sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA== + version "6.8.1" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" + integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA== dependencies: ajv "^8.0.1" lodash.truncate "^4.4.2" @@ -10425,9 +10422,9 @@ tar@^4.4.12, tar@^4.4.13: yallist "^3.1.1" tar@^6.0.2, tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: - version "6.1.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" - integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + version "6.1.12" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.12.tgz#3b742fb05669b55671fb769ab67a7791ea1a62e6" + integrity sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -10671,10 +10668,10 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== tslog@^3.2.0: version "3.3.4" @@ -10807,9 +10804,9 @@ uglify-es@^3.1.9: source-map "~0.6.1" uglify-js@^3.1.4: - version "3.17.2" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.2.tgz#f55f668b9a64b213977ae688703b6bbb7ca861c6" - integrity sha512-bbxglRjsGQMchfvXZNusUcYgiB9Hx2K4AHYXQy2DITZ9Rd+JzhX7+hoocE5Winr7z2oHvPsekkBwXtigvxevXg== + version "3.17.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" + integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== uid-number@0.0.6: version "0.0.6" @@ -10849,10 +10846,10 @@ unicode-match-property-ecmascript@^2.0.0: unicode-canonical-property-names-ecmascript "^2.0.0" unicode-property-aliases-ecmascript "^2.0.0" -unicode-match-property-value-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" - integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== unicode-property-aliases-ecmascript@^2.0.0: version "2.1.0" @@ -10922,9 +10919,9 @@ upath@^2.0.1: integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== update-browserslist-db@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz#2924d3927367a38d5c555413a7ce138fc95fcb18" - integrity sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg== + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== dependencies: escalade "^3.1.1" picocolors "^1.0.0" @@ -11032,7 +11029,7 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" -validator@^13.5.2: +validator@^13.5.2, validator@^13.7.0: version "13.7.0" resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== @@ -11090,9 +11087,9 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: defaults "^1.0.3" web-did-resolver@^2.0.8: - version "2.0.20" - resolved "https://registry.yarnpkg.com/web-did-resolver/-/web-did-resolver-2.0.20.tgz#22e053b0f8bc1f4ab03da05989ce934852b7623f" - integrity sha512-qGcrm01B+ytCZUYhxH0mGOk0Ldf67kXUXLsNth6F3sx3fhUKNSIE8D+MnMFRugQm7j87mDHqUTDLmW9c90g3nw== + version "2.0.21" + resolved "https://registry.yarnpkg.com/web-did-resolver/-/web-did-resolver-2.0.21.tgz#065797dee3e37cd9f19261d04a90144fe576e5df" + integrity sha512-vKYz0s9spYfYrKhrF88F44lkofS1yj6TCF40+i077a7boru2BNROl5VZEIVL9jJRUDsNzvmVSKkq3kS8kZnB2Q== dependencies: cross-fetch "^3.1.5" did-resolver "^4.0.0" From c34a3df580114a87a7d276213594f0d2339ba243 Mon Sep 17 00:00:00 2001 From: Karim Stekelenburg Date: Thu, 12 Jan 2023 05:26:34 +0100 Subject: [PATCH 117/125] docs: update readme packages (#1206) * docs: update readme packages Signed-off-by: Karim Stekelenburg Co-authored-by: Ariel Gentile --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 074c7a0127..314db5a4d6 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Some features are not yet supported, but are on our roadmap. Check [the roadmap] - ✅ Smart Auto Acceptance of Connections, Credentials and Proofs - 🚧 Receiving and Verifying revocable Indy Credentials - 🚧 W3C Linked Data VCs, BBS+ Signatures -- 🚧 Multi Tenancy +- ✅ Multi Tenancy - ❌ Browser ### Packages @@ -101,6 +101,29 @@ Some features are not yet supported, but are on our roadmap. Check [the roadmap] + + @aries-framework/action-menu + + + @aries-framework/action-menu version + + + + @aries-framework/question-answer + + + @aries-framework/question-answer version + + + + + @aries-framework/tenants + + + @aries-framework/tenants version + + + ## Getting Started From 8a04d1b8e84f2198e1663aa9232ae59f53459ea5 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 12 Jan 2023 18:01:19 +0800 Subject: [PATCH 118/125] chore: remove husky pre-push hook (#1209) Signed-off-by: Timo Glastra Signed-off-by: Timo Glastra --- .husky/pre-push | 1 - package.json | 2 -- yarn.lock | 5 ----- 3 files changed, 8 deletions(-) delete mode 100755 .husky/pre-push diff --git a/.husky/pre-push b/.husky/pre-push deleted file mode 100755 index b969aaaf8d..0000000000 --- a/.husky/pre-push +++ /dev/null @@ -1 +0,0 @@ -yarn validate \ No newline at end of file diff --git a/package.json b/package.json index 139ef64ae7..636ee9f284 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "test": "jest", "lint": "eslint --ignore-path .gitignore .", "validate": "yarn lint && yarn check-types && yarn check-format", - "prepare": "husky install", "run-mediator": "ts-node ./samples/mediator.ts", "next-version-bump": "ts-node ./scripts/get-next-bump.ts" }, @@ -48,7 +47,6 @@ "eslint-plugin-import": "^2.23.4", "eslint-plugin-prettier": "^3.4.0", "express": "^4.17.1", - "husky": "^7.0.1", "indy-sdk": "^1.16.0-dev-1636", "jest": "^27.0.4", "lerna": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index eb7826abc9..dfd89ae3dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5871,11 +5871,6 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -husky@^7.0.1: - version "7.0.4" - resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" - integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== - iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" From 86647e7f55c9a362f6ab500538c4de2112e42206 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 12 Jan 2023 19:05:19 +0800 Subject: [PATCH 119/125] feat(anoncreds): add anoncreds registry service (#1204) Signed-off-by: Timo Glastra --- packages/anoncreds/src/AnonCredsModule.ts | 23 +++++ .../anoncreds/src/AnonCredsModuleConfig.ts | 28 ++++++ .../src/__tests__/AnonCredsModule.test.ts | 28 ++++++ .../__tests__/AnonCredsModuleConfig.test.ts | 15 ++++ .../anoncreds/src/error/AnonCredsError.ts | 7 ++ packages/anoncreds/src/error/index.ts | 1 + packages/anoncreds/src/index.ts | 3 + .../services/registry/AnonCredsRegistry.ts | 6 +- .../registry/AnonCredsRegistryService.ts | 28 ++++++ .../AnonCredsRegistryService.test.ts | 38 ++++++++ packages/indy-sdk/src/IndySdkModuleConfig.ts | 2 +- .../services/IndySdkAnonCredsRegistry.ts | 86 +++++++++++-------- .../services/IndySdkHolderService.ts | 1 - .../utils/__tests__/identifiers.test.ts | 17 ++++ .../src/anoncreds/utils/identifiers.ts | 11 +++ 15 files changed, 254 insertions(+), 40 deletions(-) create mode 100644 packages/anoncreds/src/AnonCredsModule.ts create mode 100644 packages/anoncreds/src/AnonCredsModuleConfig.ts create mode 100644 packages/anoncreds/src/__tests__/AnonCredsModule.test.ts create mode 100644 packages/anoncreds/src/__tests__/AnonCredsModuleConfig.test.ts create mode 100644 packages/anoncreds/src/error/AnonCredsError.ts create mode 100644 packages/anoncreds/src/error/index.ts create mode 100644 packages/anoncreds/src/services/registry/AnonCredsRegistryService.ts create mode 100644 packages/anoncreds/src/services/registry/__tests__/AnonCredsRegistryService.test.ts diff --git a/packages/anoncreds/src/AnonCredsModule.ts b/packages/anoncreds/src/AnonCredsModule.ts new file mode 100644 index 0000000000..0da6e242f7 --- /dev/null +++ b/packages/anoncreds/src/AnonCredsModule.ts @@ -0,0 +1,23 @@ +import type { AnonCredsModuleConfigOptions } from './AnonCredsModuleConfig' +import type { DependencyManager, Module } from '@aries-framework/core' + +import { AnonCredsModuleConfig } from './AnonCredsModuleConfig' +import { AnonCredsRegistryService } from './services/registry/AnonCredsRegistryService' + +/** + * @public + */ +export class AnonCredsModule implements Module { + public readonly config: AnonCredsModuleConfig + + public constructor(config: AnonCredsModuleConfigOptions) { + this.config = new AnonCredsModuleConfig(config) + } + + public register(dependencyManager: DependencyManager) { + // Config + dependencyManager.registerInstance(AnonCredsModuleConfig, this.config) + + dependencyManager.registerSingleton(AnonCredsRegistryService) + } +} diff --git a/packages/anoncreds/src/AnonCredsModuleConfig.ts b/packages/anoncreds/src/AnonCredsModuleConfig.ts new file mode 100644 index 0000000000..9f7b971aab --- /dev/null +++ b/packages/anoncreds/src/AnonCredsModuleConfig.ts @@ -0,0 +1,28 @@ +import type { AnonCredsRegistry } from './services' + +/** + * @public + * AnonCredsModuleConfigOptions defines the interface for the options of the AnonCredsModuleConfig class. + */ +export interface AnonCredsModuleConfigOptions { + /** + * A list of AnonCreds registries to make available to the AnonCreds module. + */ + registries: [AnonCredsRegistry, ...AnonCredsRegistry[]] +} + +/** + * @public + */ +export class AnonCredsModuleConfig { + private options: AnonCredsModuleConfigOptions + + public constructor(options: AnonCredsModuleConfigOptions) { + this.options = options + } + + /** See {@link AnonCredsModuleConfigOptions.registries} */ + public get registries() { + return this.options.registries + } +} diff --git a/packages/anoncreds/src/__tests__/AnonCredsModule.test.ts b/packages/anoncreds/src/__tests__/AnonCredsModule.test.ts new file mode 100644 index 0000000000..90aa51ce66 --- /dev/null +++ b/packages/anoncreds/src/__tests__/AnonCredsModule.test.ts @@ -0,0 +1,28 @@ +import type { AnonCredsRegistry } from '../services' +import type { DependencyManager } from '@aries-framework/core' + +import { AnonCredsModule } from '../AnonCredsModule' +import { AnonCredsModuleConfig } from '../AnonCredsModuleConfig' +import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistryService' + +const dependencyManager = { + registerInstance: jest.fn(), + registerSingleton: jest.fn(), +} as unknown as DependencyManager + +const registry = {} as AnonCredsRegistry + +describe('AnonCredsModule', () => { + test('registers dependencies on the dependency manager', () => { + const anonCredsModule = new AnonCredsModule({ + registries: [registry], + }) + anonCredsModule.register(dependencyManager) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsRegistryService) + + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(AnonCredsModuleConfig, anonCredsModule.config) + }) +}) diff --git a/packages/anoncreds/src/__tests__/AnonCredsModuleConfig.test.ts b/packages/anoncreds/src/__tests__/AnonCredsModuleConfig.test.ts new file mode 100644 index 0000000000..beaca8bf53 --- /dev/null +++ b/packages/anoncreds/src/__tests__/AnonCredsModuleConfig.test.ts @@ -0,0 +1,15 @@ +import type { AnonCredsRegistry } from '../services' + +import { AnonCredsModuleConfig } from '../AnonCredsModuleConfig' + +describe('AnonCredsModuleConfig', () => { + test('sets values', () => { + const registry = {} as AnonCredsRegistry + + const config = new AnonCredsModuleConfig({ + registries: [registry], + }) + + expect(config.registries).toEqual([registry]) + }) +}) diff --git a/packages/anoncreds/src/error/AnonCredsError.ts b/packages/anoncreds/src/error/AnonCredsError.ts new file mode 100644 index 0000000000..eb6d250a4a --- /dev/null +++ b/packages/anoncreds/src/error/AnonCredsError.ts @@ -0,0 +1,7 @@ +import { AriesFrameworkError } from '@aries-framework/core' + +export class AnonCredsError extends AriesFrameworkError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/anoncreds/src/error/index.ts b/packages/anoncreds/src/error/index.ts new file mode 100644 index 0000000000..d9786950bf --- /dev/null +++ b/packages/anoncreds/src/error/index.ts @@ -0,0 +1 @@ +export * from './AnonCredsError' diff --git a/packages/anoncreds/src/index.ts b/packages/anoncreds/src/index.ts index 83fdeb7877..759e343c2c 100644 --- a/packages/anoncreds/src/index.ts +++ b/packages/anoncreds/src/index.ts @@ -1,2 +1,5 @@ export * from './models' export * from './services' +export * from './error' +export { AnonCredsModule } from './AnonCredsModule' +export { AnonCredsModuleConfig, AnonCredsModuleConfigOptions } from './AnonCredsModuleConfig' diff --git a/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts b/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts index 966a1afb95..e3061043dd 100644 --- a/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts +++ b/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts @@ -8,8 +8,12 @@ import type { GetRevocationRegistryDefinitionReturn } from './RevocationRegistry import type { GetSchemaReturn, RegisterSchemaOptions, RegisterSchemaReturn } from './SchemaOptions' import type { AgentContext } from '@aries-framework/core' -// This service can be registered multiple times in a single AFJ instance. +/** + * @public + */ export interface AnonCredsRegistry { + supportedIdentifier: RegExp + getSchema(agentContext: AgentContext, schemaId: string): Promise registerSchema(agentContext: AgentContext, options: RegisterSchemaOptions): Promise diff --git a/packages/anoncreds/src/services/registry/AnonCredsRegistryService.ts b/packages/anoncreds/src/services/registry/AnonCredsRegistryService.ts new file mode 100644 index 0000000000..8ee8eb4b50 --- /dev/null +++ b/packages/anoncreds/src/services/registry/AnonCredsRegistryService.ts @@ -0,0 +1,28 @@ +import type { AnonCredsRegistry } from '.' +import type { AgentContext } from '@aries-framework/core' + +import { injectable } from '@aries-framework/core' + +import { AnonCredsModuleConfig } from '../../AnonCredsModuleConfig' +import { AnonCredsError } from '../../error' + +/** + * @internal + * The AnonCreds registry service manages multiple {@link AnonCredsRegistry} instances + * and returns the correct registry based on a given identifier + */ +@injectable() +export class AnonCredsRegistryService { + public async getRegistryForIdentifier(agentContext: AgentContext, identifier: string): Promise { + const registries = agentContext.dependencyManager.resolve(AnonCredsModuleConfig).registries + + // TODO: should we check if multiple are registered? + const registry = registries.find((registry) => registry.supportedIdentifier.test(identifier)) + + if (!registry) { + throw new AnonCredsError(`No AnonCredsRegistry registered for identifier '${registry}'`) + } + + return registry + } +} diff --git a/packages/anoncreds/src/services/registry/__tests__/AnonCredsRegistryService.test.ts b/packages/anoncreds/src/services/registry/__tests__/AnonCredsRegistryService.test.ts new file mode 100644 index 0000000000..096626f805 --- /dev/null +++ b/packages/anoncreds/src/services/registry/__tests__/AnonCredsRegistryService.test.ts @@ -0,0 +1,38 @@ +import type { AnonCredsRegistry } from '../AnonCredsRegistry' + +import { getAgentContext } from '../../../../../core/tests/helpers' +import { AnonCredsModuleConfig } from '../../../AnonCredsModuleConfig' +import { AnonCredsError } from '../../../error' +import { AnonCredsRegistryService } from '../AnonCredsRegistryService' + +const registryOne = { + supportedIdentifier: /a/, +} as AnonCredsRegistry + +const registryTwo = { + supportedIdentifier: /b/, +} as AnonCredsRegistry + +const agentContext = getAgentContext({ + registerInstances: [ + [ + AnonCredsModuleConfig, + new AnonCredsModuleConfig({ + registries: [registryOne, registryTwo], + }), + ], + ], +}) + +const anonCredsRegistryService = new AnonCredsRegistryService() + +describe('AnonCredsRegistryService', () => { + test('returns the registry for an identifier based on the supportedMethods regex', async () => { + await expect(anonCredsRegistryService.getRegistryForIdentifier(agentContext, 'a')).resolves.toEqual(registryOne) + await expect(anonCredsRegistryService.getRegistryForIdentifier(agentContext, 'b')).resolves.toEqual(registryTwo) + }) + + test('throws AnonCredsError if no registry is found for the given identifier', async () => { + await expect(anonCredsRegistryService.getRegistryForIdentifier(agentContext, 'c')).rejects.toThrow(AnonCredsError) + }) +}) diff --git a/packages/indy-sdk/src/IndySdkModuleConfig.ts b/packages/indy-sdk/src/IndySdkModuleConfig.ts index a01bf813b3..e5b16142ee 100644 --- a/packages/indy-sdk/src/IndySdkModuleConfig.ts +++ b/packages/indy-sdk/src/IndySdkModuleConfig.ts @@ -38,7 +38,7 @@ export class IndySdkModuleConfig { this.options = options } - /** See {@link IndySdkModuleConfigOptions.resolvers} */ + /** See {@link IndySdkModuleConfigOptions.indySdk} */ public get indySdk() { return this.options.indySdk } diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts index 3b5c6a08ce..95b08fa88b 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts @@ -1,3 +1,4 @@ +import type { IndySdk } from '../../types' import type { AnonCredsRegistry, GetCredentialDefinitionReturn, @@ -12,17 +13,16 @@ import type { import type { AgentContext } from '@aries-framework/core' import type { Schema as IndySdkSchema } from 'indy-sdk' -import { inject } from '@aries-framework/core' - import { IndySdkError, isIndyError } from '../../error' import { IndySdkPoolService } from '../../ledger' -import { IndySdk, IndySdkSymbol } from '../../types' +import { IndySdkSymbol } from '../../types' import { didFromCredentialDefinitionId, didFromRevocationRegistryDefinitionId, didFromSchemaId, getLegacyCredentialDefinitionId, getLegacySchemaId, + indySdkAnonCredsRegistryIdentifierRegex, } from '../utils/identifiers' import { anonCredsRevocationListFromIndySdk, @@ -33,32 +33,34 @@ import { * TODO: validation of the identifiers. The Indy SDK classes only support the legacy (unqualified) identifiers. */ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { - private indySdk: IndySdk - private indySdkPoolService: IndySdkPoolService - - public constructor(@inject(IndySdkSymbol) indySdk: IndySdk, indySdkPoolService: IndySdkPoolService) { - this.indySdk = indySdk - this.indySdkPoolService = indySdkPoolService - } + /** + * This class only supports resolving and registering objects with legacy indy identifiers. + * It needs to include support for the schema, credential definition, revocation registry as well + * as the issuer id (which is needed when registering objects). + */ + public readonly supportedIdentifier = indySdkAnonCredsRegistryIdentifierRegex public async getSchema(agentContext: AgentContext, schemaId: string): Promise { try { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const did = didFromSchemaId(schemaId) - const { pool } = await this.indySdkPoolService.getPoolForDid(agentContext, did) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug(`Getting schema '${schemaId}' from ledger '${pool.didIndyNamespace}'`) - const request = await this.indySdk.buildGetSchemaRequest(null, schemaId) + const request = await indySdk.buildGetSchemaRequest(null, schemaId) agentContext.config.logger.trace( `Submitting get schema request for schema '${schemaId}' to ledger '${pool.didIndyNamespace}'` ) - const response = await this.indySdkPoolService.submitReadRequest(pool, request) + const response = await indySdkPoolService.submitReadRequest(pool, request) agentContext.config.logger.trace(`Got un-parsed schema '${schemaId}' from ledger '${pool.didIndyNamespace}'`, { response, }) - const [, schema] = await this.indySdk.parseGetSchemaResponse(response) + const [, schema] = await indySdk.parseGetSchemaResponse(response) agentContext.config.logger.debug(`Got schema '${schemaId}' from ledger '${pool.didIndyNamespace}'`, { schema, }) @@ -117,7 +119,10 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } try { - const pool = this.indySdkPoolService.getPoolForNamespace(options.options.didIndyNamespace) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + + const pool = indySdkPoolService.getPoolForNamespace(options.options.didIndyNamespace) agentContext.config.logger.debug( `Register schema on ledger '${pool.didIndyNamespace}' with did '${options.schema.issuerId}'`, options.schema @@ -133,14 +138,9 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { // buildSchemaRequest (seqNo is not yet known) } as IndySdkSchema - const request = await this.indySdk.buildSchemaRequest(options.schema.issuerId, schema) + const request = await indySdk.buildSchemaRequest(options.schema.issuerId, schema) - const response = await this.indySdkPoolService.submitWriteRequest( - agentContext, - pool, - request, - options.schema.issuerId - ) + const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, options.schema.issuerId) agentContext.config.logger.debug(`Registered schema '${schema.id}' on ledger '${pool.didIndyNamespace}'`, { response, schema, @@ -189,19 +189,22 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { credentialDefinitionId: string ): Promise { try { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const did = didFromCredentialDefinitionId(credentialDefinitionId) - const { pool } = await this.indySdkPoolService.getPoolForDid(agentContext, did) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.didIndyNamespace}' to retrieve credential definition '${credentialDefinitionId}'` ) - const request = await this.indySdk.buildGetCredDefRequest(null, credentialDefinitionId) + const request = await indySdk.buildGetCredDefRequest(null, credentialDefinitionId) agentContext.config.logger.trace( `Submitting get credential definition request for credential definition '${credentialDefinitionId}' to ledger '${pool.didIndyNamespace}'` ) - const response = await this.indySdkPoolService.submitReadRequest(pool, request) + const response = await indySdkPoolService.submitReadRequest(pool, request) agentContext.config.logger.trace( `Got un-parsed credential definition '${credentialDefinitionId}' from ledger '${pool.didIndyNamespace}'`, { @@ -209,7 +212,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } ) - const [, credentialDefinition] = await this.indySdk.parseGetCredDefResponse(response) + const [, credentialDefinition] = await indySdk.parseGetCredDefResponse(response) agentContext.config.logger.debug( `Got credential definition '${credentialDefinitionId}' from ledger '${pool.didIndyNamespace}'`, { @@ -267,7 +270,10 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } try { - const pool = this.indySdkPoolService.getPoolForNamespace(options.options.didIndyNamespace) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + + const pool = indySdkPoolService.getPoolForNamespace(options.options.didIndyNamespace) agentContext.config.logger.debug( `Registering credential definition on ledger '${pool.didIndyNamespace}' with did '${options.credentialDefinition.issuerId}'`, options.credentialDefinition @@ -296,7 +302,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { options.credentialDefinition.tag ) - const request = await this.indySdk.buildCredDefRequest(options.credentialDefinition.issuerId, { + const request = await indySdk.buildCredDefRequest(options.credentialDefinition.issuerId, { id: credentialDefinitionId, schemaId: options.credentialDefinition.schemaId, tag: options.credentialDefinition.tag, @@ -305,7 +311,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { ver: '1.0', }) - const response = await this.indySdkPoolService.submitWriteRequest( + const response = await indySdkPoolService.submitWriteRequest( agentContext, pool, request, @@ -350,18 +356,21 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { revocationRegistryDefinitionId: string ): Promise { try { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) - const { pool } = await this.indySdkPoolService.getPoolForDid(agentContext, did) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.didIndyNamespace}' to retrieve revocation registry definition '${revocationRegistryDefinitionId}'` ) - const request = await this.indySdk.buildGetRevocRegDefRequest(null, revocationRegistryDefinitionId) + const request = await indySdk.buildGetRevocRegDefRequest(null, revocationRegistryDefinitionId) agentContext.config.logger.trace( `Submitting get revocation registry definition request for revocation registry definition '${revocationRegistryDefinitionId}' to ledger` ) - const response = await this.indySdkPoolService.submitReadRequest(pool, request) + const response = await indySdkPoolService.submitReadRequest(pool, request) agentContext.config.logger.trace( `Got un-parsed revocation registry definition '${revocationRegistryDefinitionId}' from ledger '${pool.didIndyNamespace}'`, { @@ -369,7 +378,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } ) - const [, revocationRegistryDefinition] = await this.indySdk.parseGetRevocRegDefResponse(response) + const [, revocationRegistryDefinition] = await indySdk.parseGetRevocRegDefResponse(response) agentContext.config.logger.debug( `Got revocation registry definition '${revocationRegistryDefinitionId}' from ledger`, @@ -415,21 +424,24 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { timestamp: number ): Promise { try { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const did = didFromRevocationRegistryDefinitionId(revocationRegistryId) - const { pool } = await this.indySdkPoolService.getPoolForDid(agentContext, did) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.id}' to retrieve revocation registry deltas with revocation registry definition id '${revocationRegistryId}' until ${timestamp}` ) // TODO: implement caching for returned deltas - const request = await this.indySdk.buildGetRevocRegDeltaRequest(null, revocationRegistryId, 0, timestamp) + const request = await indySdk.buildGetRevocRegDeltaRequest(null, revocationRegistryId, 0, timestamp) agentContext.config.logger.trace( `Submitting get revocation registry delta request for revocation registry '${revocationRegistryId}' to ledger` ) - const response = await this.indySdkPoolService.submitReadRequest(pool, request) + const response = await indySdkPoolService.submitReadRequest(pool, request) agentContext.config.logger.trace( `Got revocation registry delta unparsed-response '${revocationRegistryId}' from ledger`, { @@ -437,7 +449,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } ) - const [, revocationRegistryDelta, deltaTimestamp] = await this.indySdk.parseGetRevocRegDeltaResponse(response) + const [, revocationRegistryDelta, deltaTimestamp] = await indySdk.parseGetRevocRegDeltaResponse(response) agentContext.config.logger.debug( `Got revocation registry deltas '${revocationRegistryId}' until timestamp ${timestamp} from ledger`, diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts index 88179381a9..49b619332d 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts @@ -13,7 +13,6 @@ import type { } from '@aries-framework/anoncreds' import type { AgentContext } from '@aries-framework/core' import type { - Cred, CredentialDefs, IndyRequestedCredentials, RevStates, diff --git a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts index f85ec160b5..76454b615a 100644 --- a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts @@ -5,9 +5,26 @@ import { getIndySeqNoFromUnqualifiedCredentialDefinitionId, getLegacyCredentialDefinitionId, getLegacySchemaId, + indySdkAnonCredsRegistryIdentifierRegex, } from '../identifiers' describe('identifiers', () => { + it('matches against a legacy indy did, schema id, credential definition id and revocation registry id', () => { + const did = '7Tqg6BwSSWapxgUDm9KKgg' + const schemaId = 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0' + const credentialDefinitionId = 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID' + const revocationRegistryId = + 'N7baRMcyvPwWc8v85CtZ6e:4:N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID:CL_ACCUM:1-1024' + + const anotherId = 'some:id' + + expect(indySdkAnonCredsRegistryIdentifierRegex.test(did)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) + }) + it('getLegacySchemaId should return a valid schema id given a did, name, and version', () => { const did = '12345' const name = 'backbench' diff --git a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts index bc59b5f8d4..62d2650602 100644 --- a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts @@ -1,3 +1,14 @@ +export const legacyIndyIssuerIdRegex = /^[a-zA-Z0-9]{21,22}$/ +export const legacyIndySchemaIdRegex = /^[a-zA-Z0-9]{21,22}:2:.+:[0-9.]+$/ +export const legacyIndyCredentialDefinitionIdRegex = + /^[a-zA-Z0-9]{21,22}:3:CL:(([1-9][0-9]*)|([a-zA-Z0-9]{21,22}:2:.+:[0-9.]+)):(.+)?$/ +export const legacyIndyRevocationRegistryIdRegex = + /^[a-zA-Z0-9]{21,22}:4:[a-zA-Z0-9]{21,22}:3:CL:(([1-9][0-9]*)|([a-zA-Z0-9]{21,22}:2:.+:[0-9.]+))(:.+)?:CL_ACCUM:(.+$)/ + +export const indySdkAnonCredsRegistryIdentifierRegex = new RegExp( + `${legacyIndyIssuerIdRegex.source}|${legacyIndySchemaIdRegex.source}|${legacyIndyCredentialDefinitionIdRegex.source}|${legacyIndyRevocationRegistryIdRegex.source}` +) + export function getIndySeqNoFromUnqualifiedCredentialDefinitionId(unqualifiedCredentialDefinitionId: string): number { // 5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npbd const [, , , seqNo] = unqualifiedCredentialDefinitionId.split(':') From b6f89f943dc4417626f868ac9f43a3d890ab62c6 Mon Sep 17 00:00:00 2001 From: Karim Stekelenburg Date: Thu, 12 Jan 2023 14:59:39 +0100 Subject: [PATCH 120/125] feat: add minimal oidc-client package (#1197) Work funded by the Government of Ontario. * feat: add openid4vc-client package Signed-off-by: Karim Stekelenburg Co-authored-by: Timo Glastra --- packages/openid4vc-client/README.md | 37 ++++++++++++++++++ packages/openid4vc-client/jest.config.ts | 14 +++++++ packages/openid4vc-client/package.json | 39 +++++++++++++++++++ .../src/OpenId4VcClientApi.ts | 17 ++++++++ .../src/OpenId4VcClientApiOptions.ts | 0 .../src/OpenId4VcClientModule.ts | 22 +++++++++++ .../src/OpenId4VcClientService.ts | 10 +++++ .../__tests__/OpenId4VcClientModule.test.ts | 24 ++++++++++++ packages/openid4vc-client/src/index.ts | 3 ++ packages/openid4vc-client/tests/setup.ts | 3 ++ packages/openid4vc-client/tsconfig.build.json | 7 ++++ packages/openid4vc-client/tsconfig.json | 6 +++ yarn.lock | 34 ++++++++++++++++ 13 files changed, 216 insertions(+) create mode 100644 packages/openid4vc-client/README.md create mode 100644 packages/openid4vc-client/jest.config.ts create mode 100644 packages/openid4vc-client/package.json create mode 100644 packages/openid4vc-client/src/OpenId4VcClientApi.ts create mode 100644 packages/openid4vc-client/src/OpenId4VcClientApiOptions.ts create mode 100644 packages/openid4vc-client/src/OpenId4VcClientModule.ts create mode 100644 packages/openid4vc-client/src/OpenId4VcClientService.ts create mode 100644 packages/openid4vc-client/src/__tests__/OpenId4VcClientModule.test.ts create mode 100644 packages/openid4vc-client/src/index.ts create mode 100644 packages/openid4vc-client/tests/setup.ts create mode 100644 packages/openid4vc-client/tsconfig.build.json create mode 100644 packages/openid4vc-client/tsconfig.json diff --git a/packages/openid4vc-client/README.md b/packages/openid4vc-client/README.md new file mode 100644 index 0000000000..e89f6cab7a --- /dev/null +++ b/packages/openid4vc-client/README.md @@ -0,0 +1,37 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript Open ID Connect For Verifiable Credentials Client Module

+

+ License + typescript + @aries-framework/openid4vc-client version + +

+
+ +Open ID Connect For Verifiable Credentials Client Module for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript). + +### Installation + +### Quick start + +### Example of usage diff --git a/packages/openid4vc-client/jest.config.ts b/packages/openid4vc-client/jest.config.ts new file mode 100644 index 0000000000..55c67d70a6 --- /dev/null +++ b/packages/openid4vc-client/jest.config.ts @@ -0,0 +1,14 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + name: packageJson.name, + displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/packages/openid4vc-client/package.json b/packages/openid4vc-client/package.json new file mode 100644 index 0000000000..ee4b727356 --- /dev/null +++ b/packages/openid4vc-client/package.json @@ -0,0 +1,39 @@ +{ + "name": "@aries-framework/openid4vc-client", + "main": "build/index", + "types": "build/index", + "version": "0.3.2", + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/openid4vc-client", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/openid4vc-client" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf -rf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@aries-framework/core": "0.3.2", + "@sphereon/openid4vci-client": "^0.3.6", + "class-transformer": "0.5.1", + "class-validator": "0.13.1" + }, + "peerDependencies": {}, + "devDependencies": { + "@aries-framework/node": "0.3.2", + "reflect-metadata": "^0.1.13", + "rimraf": "~3.0.2", + "typescript": "~4.3.0" + } +} diff --git a/packages/openid4vc-client/src/OpenId4VcClientApi.ts b/packages/openid4vc-client/src/OpenId4VcClientApi.ts new file mode 100644 index 0000000000..ccf2cb84f3 --- /dev/null +++ b/packages/openid4vc-client/src/OpenId4VcClientApi.ts @@ -0,0 +1,17 @@ +import { AgentContext, injectable } from '@aries-framework/core' + +import { OpenId4VcClientService } from './OpenId4VcClientService' + +/** + * @public + */ +@injectable() +export class OpenId4VcClientApi { + private agentContext: AgentContext + private openId4VcClientService: OpenId4VcClientService + + public constructor(agentContext: AgentContext, openId4VcClientService: OpenId4VcClientService) { + this.agentContext = agentContext + this.openId4VcClientService = openId4VcClientService + } +} diff --git a/packages/openid4vc-client/src/OpenId4VcClientApiOptions.ts b/packages/openid4vc-client/src/OpenId4VcClientApiOptions.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/openid4vc-client/src/OpenId4VcClientModule.ts b/packages/openid4vc-client/src/OpenId4VcClientModule.ts new file mode 100644 index 0000000000..fe941949cf --- /dev/null +++ b/packages/openid4vc-client/src/OpenId4VcClientModule.ts @@ -0,0 +1,22 @@ +import type { DependencyManager, Module } from '@aries-framework/core' + +import { OpenId4VcClientApi } from './OpenId4VcClientApi' +import { OpenId4VcClientService } from './OpenId4VcClientService' + +/** + * @public + */ +export class OpenId4VcClientModule implements Module { + public readonly api = OpenId4VcClientApi + + /** + * Registers the dependencies of the question answer module on the dependency manager. + */ + public register(dependencyManager: DependencyManager) { + // Api + dependencyManager.registerContextScoped(OpenId4VcClientApi) + + // Services + dependencyManager.registerSingleton(OpenId4VcClientService) + } +} diff --git a/packages/openid4vc-client/src/OpenId4VcClientService.ts b/packages/openid4vc-client/src/OpenId4VcClientService.ts new file mode 100644 index 0000000000..9c54e9b81c --- /dev/null +++ b/packages/openid4vc-client/src/OpenId4VcClientService.ts @@ -0,0 +1,10 @@ +import { injectable, W3cCredentialService } from '@aries-framework/core' + +@injectable() +export class OpenId4VcClientService { + private w3cCredentialService: W3cCredentialService + + public constructor(w3cCredentialService: W3cCredentialService) { + this.w3cCredentialService = w3cCredentialService + } +} diff --git a/packages/openid4vc-client/src/__tests__/OpenId4VcClientModule.test.ts b/packages/openid4vc-client/src/__tests__/OpenId4VcClientModule.test.ts new file mode 100644 index 0000000000..b02fa08ffc --- /dev/null +++ b/packages/openid4vc-client/src/__tests__/OpenId4VcClientModule.test.ts @@ -0,0 +1,24 @@ +import type { DependencyManager } from '@aries-framework/core' + +import { OpenId4VcClientApi } from '../OpenId4VcClientApi' +import { OpenId4VcClientModule } from '../OpenId4VcClientModule' +import { OpenId4VcClientService } from '../OpenId4VcClientService' + +const dependencyManager = { + registerInstance: jest.fn(), + registerSingleton: jest.fn(), + registerContextScoped: jest.fn(), +} as unknown as DependencyManager + +describe('OpenId4VcClientModule', () => { + test('registers dependencies on the dependency manager', () => { + const openId4VcClientModule = new OpenId4VcClientModule() + openId4VcClientModule.register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(OpenId4VcClientApi) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(OpenId4VcClientService) + }) +}) diff --git a/packages/openid4vc-client/src/index.ts b/packages/openid4vc-client/src/index.ts new file mode 100644 index 0000000000..3200b8fa6d --- /dev/null +++ b/packages/openid4vc-client/src/index.ts @@ -0,0 +1,3 @@ +export * from './OpenId4VcClientModule' +export * from './OpenId4VcClientApi' +export * from './OpenId4VcClientService' diff --git a/packages/openid4vc-client/tests/setup.ts b/packages/openid4vc-client/tests/setup.ts new file mode 100644 index 0000000000..4955aeb601 --- /dev/null +++ b/packages/openid4vc-client/tests/setup.ts @@ -0,0 +1,3 @@ +import 'reflect-metadata' + +jest.setTimeout(20000) diff --git a/packages/openid4vc-client/tsconfig.build.json b/packages/openid4vc-client/tsconfig.build.json new file mode 100644 index 0000000000..2b75d0adab --- /dev/null +++ b/packages/openid4vc-client/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["src/**/*"] +} diff --git a/packages/openid4vc-client/tsconfig.json b/packages/openid4vc-client/tsconfig.json new file mode 100644 index 0000000000..46efe6f721 --- /dev/null +++ b/packages/openid4vc-client/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + } +} diff --git a/yarn.lock b/yarn.lock index dfd89ae3dc..5fb0819bd7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2233,6 +2233,23 @@ resolved "https://registry.yarnpkg.com/@sovpro/delimited-stream/-/delimited-stream-1.1.0.tgz#4334bba7ee241036e580fdd99c019377630d26b4" integrity sha512-kQpk267uxB19X3X2T1mvNMjyvIEonpNSHrMlK5ZaBU6aZxw7wPbpgKJOjHN3+/GPVpXgAV9soVT2oyHpLkLtyw== +"@sphereon/openid4vci-client@^0.3.6": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@sphereon/openid4vci-client/-/openid4vci-client-0.3.6.tgz#70bd76bb888b458274007170e2bb7e784849cbbe" + integrity sha512-C336F3VPMu1LSQAv1rn1KkXOVC3JxbrUzMzOBFlJfSKfnSxfqTqGSK0NTJLnAwGNOEAQ1M4Q/2KIXbDIaTU4Ww== + dependencies: + "@sphereon/ssi-types" "^0.8.1-next.123" + cross-fetch "^3.1.5" + debug "^4.3.4" + uint8arrays "^3.1.1" + +"@sphereon/ssi-types@^0.8.1-next.123": + version "0.8.1-unstable.145" + resolved "https://registry.yarnpkg.com/@sphereon/ssi-types/-/ssi-types-0.8.1-unstable.145.tgz#418cf00ebb077ccb9644e652bc4e7fb0eb23b645" + integrity sha512-ixT8z5bwDWKJaMQTsUeRs7vMg5fz68BRJhxn10Tkeg68nJUEUHck44QJOhog0MmjNJKw2k6U/IqIS0oOdxTSHQ== + dependencies: + jwt-decode "^3.1.2" + "@stablelib/binary@^1.0.0", "@stablelib/binary@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@stablelib/binary/-/binary-1.0.1.tgz#c5900b94368baf00f811da5bdb1610963dfddf7f" @@ -7125,6 +7142,11 @@ jsprim@^1.2.2: json-schema "0.4.0" verror "1.10.0" +jwt-decode@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" + integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -8022,6 +8044,11 @@ msrcrypto@^1.5.6: resolved "https://registry.yarnpkg.com/msrcrypto/-/msrcrypto-1.5.8.tgz#be419be4945bf134d8af52e9d43be7fa261f4a1c" integrity sha512-ujZ0TRuozHKKm6eGbKHfXef7f+esIhEckmThVnz7RNyiOJd7a6MXj2JGBoL9cnPDW+JMG16MoTUh5X+XXjI66Q== +multiformats@^9.4.2: + version "9.9.0" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== + multimatch@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" @@ -10808,6 +10835,13 @@ uid-number@0.0.6: resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" integrity sha512-c461FXIljswCuscZn67xq9PpszkPT6RjheWFQTgCyabJrTUozElanb0YEqv2UGgk247YpcJkFBuSGNvBlpXM9w== +uint8arrays@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.1.1.tgz#2d8762acce159ccd9936057572dade9459f65ae0" + integrity sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg== + dependencies: + multiformats "^9.4.2" + ultron@1.0.x: version "1.0.2" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" From c697716bf1837b9fef307f60ff97f01d3d926728 Mon Sep 17 00:00:00 2001 From: Karim Stekelenburg Date: Thu, 12 Jan 2023 16:13:02 +0100 Subject: [PATCH 121/125] fix(openid4vc-client): set package to private (#1210) Signed-off-by: Karim Stekelenburg --- packages/openid4vc-client/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/openid4vc-client/package.json b/packages/openid4vc-client/package.json index ee4b727356..cac010d054 100644 --- a/packages/openid4vc-client/package.json +++ b/packages/openid4vc-client/package.json @@ -6,6 +6,7 @@ "files": [ "build" ], + "private": true, "license": "Apache-2.0", "publishConfig": { "access": "public" From 409d36c7e3623845f4718802b884bb40867806e1 Mon Sep 17 00:00:00 2001 From: Grammatopoulos Athanasios Vasileios Date: Tue, 17 Jan 2023 15:33:01 +0200 Subject: [PATCH 122/125] docs: corrected some mistakes on demo documentation (#1215) Signed-off-by: Grammatopoulos Athanasios Vasileios --- demo/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/demo/README.md b/demo/README.md index 7f4d71df0d..93f14ee99f 100644 --- a/demo/README.md +++ b/demo/README.md @@ -17,7 +17,7 @@ Alice, a former student of Faber College, connects with the College, is issued a In order to use Aries Framework JavaScript some platform specific dependencies and setup is required. See our guides below to quickly set up you project with Aries Framework JavaScript for NodeJS, React Native and Electron. -- [NodeJS](https:/aries.js.org/guides/getting-started/prerequisites/nodejs) +- [NodeJS](https://aries.js.org/guides/getting-started/installation/nodejs) ### Run the demo @@ -57,8 +57,8 @@ yarn faber To set up a connection: -- Select 'setup connection' in both Agents -- Alice will print a invitation link which you then copy and paste to Faber +- Select 'receive connection invitation' in Alice and 'create connection invitation' in Faber +- Faber will print a invitation link which you then copy and paste to Alice - You have now set up a connection! To offer a credential: @@ -66,7 +66,7 @@ To offer a credential: - Select 'offer credential' in Faber - Faber will start with registering a schema and the credential definition accordingly - You have now send a credential offer to Alice! -- Go to Alice to accept the incoming credential offer +- Go to Alice to accept the incoming credential offer by selecting 'yes'. To request a proof: From 087980f1adf3ee0bc434ca9782243a62c6124444 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 17 Jan 2023 23:48:40 +0800 Subject: [PATCH 123/125] fix: fix typing issues with typescript 4.9 (#1214) Fixes https://github.com/hyperledger/aries-framework-javascript/issues/1205 Signed-off-by: Timo Glastra --- package.json | 14 +- packages/action-menu/package.json | 6 +- .../src/services/ActionMenuService.ts | 4 +- .../action-menu/tests/action-menu.e2e.test.ts | 4 +- packages/anoncreds/package.json | 6 +- .../src/services/AnonCredsHolderService.ts | 4 +- .../src/services/AnonCredsIssuerService.ts | 4 +- .../registry/CredentialDefinitionOptions.ts | 2 +- .../registry/RevocationListOptions.ts | 2 +- .../RevocationRegistryDefinitionOptions.ts | 2 +- .../src/services/registry/SchemaOptions.ts | 2 +- packages/bbs-signatures/package.json | 6 +- packages/core/package.json | 6 +- packages/core/src/agent/Agent.ts | 6 +- packages/core/src/agent/AgentConfig.ts | 2 +- packages/core/src/agent/AgentModules.ts | 2 +- packages/core/src/agent/BaseAgent.ts | 6 +- packages/core/src/agent/EnvelopeService.ts | 2 +- packages/core/src/agent/Events.ts | 2 +- .../core/src/agent/MessageHandlerRegistry.ts | 2 +- packages/core/src/agent/MessageReceiver.ts | 6 +- packages/core/src/agent/MessageSender.ts | 10 +- packages/core/src/agent/TransportService.ts | 4 +- .../core/src/agent/context/AgentContext.ts | 2 +- packages/core/src/cache/PersistedLruCache.ts | 2 +- packages/core/src/crypto/JwsService.ts | 2 +- packages/core/src/crypto/WalletKeyPair.ts | 2 +- .../SigningProviderRegistry.ts | 2 +- .../service/ServiceDecoratorExtension.ts | 2 +- .../basic-messages/BasicMessageEvents.ts | 2 +- .../basic-messages/BasicMessagesApi.ts | 2 +- .../modules/connections/ConnectionEvents.ts | 2 +- .../src/modules/connections/ConnectionsApi.ts | 4 +- .../modules/connections/ConnectionsModule.ts | 2 +- .../connections/DidExchangeProtocol.ts | 4 +- .../connections/DidExchangeStateMachine.ts | 2 +- .../modules/connections/TrustPingEvents.ts | 2 +- .../errors/ConnectionProblemReportError.ts | 2 +- .../errors/DidExchangeProblemReportError.ts | 2 +- .../modules/connections/models/did/DidDoc.ts | 2 +- .../did/authentication/Authentication.ts | 2 +- .../repository/ConnectionRecord.ts | 2 +- .../modules/credentials/CredentialEvents.ts | 2 +- .../credentials/CredentialProtocolOptions.ts | 37 +- .../src/modules/credentials/CredentialsApi.ts | 30 +- .../credentials/CredentialsApiOptions.ts | 16 +- .../modules/credentials/CredentialsModule.ts | 4 +- .../errors/CredentialProblemReportError.ts | 2 +- .../formats/CredentialFormatService.ts | 4 +- .../formats/CredentialFormatServiceOptions.ts | 4 +- .../formats/indy/IndyCredentialFormat.ts | 2 +- .../indy/IndyCredentialFormatService.ts | 2 +- .../jsonld/JsonLdCredentialFormatService.ts | 12 +- .../protocol/BaseCredentialProtocol.ts | 65 +- ...ldproof.connectionless-credentials.test.ts | 4 +- .../repository/CredentialExchangeRecord.ts | 2 +- packages/core/src/modules/dids/DidsModule.ts | 2 +- .../dids/__tests__/dids-registrar.e2e.test.ts | 3 +- .../dids/domain/key-type/bls12381g1.ts | 2 +- .../dids/domain/key-type/bls12381g2.ts | 2 +- .../modules/dids/domain/key-type/ed25519.ts | 2 +- .../modules/dids/domain/key-type/x25519.ts | 2 +- .../src/modules/dids/repository/DidRecord.ts | 2 +- .../discover-features/DiscoverFeaturesApi.ts | 4 +- .../DiscoverFeaturesApiOptions.ts | 2 +- .../DiscoverFeaturesModule.ts | 2 +- .../services/DiscoverFeaturesService.ts | 10 +- .../generic-records/GenericRecordsApi.ts | 2 +- .../indy/services/IndyRevocationService.ts | 2 +- .../core/src/modules/ledger/LedgerModule.ts | 2 +- packages/core/src/modules/oob/OutOfBandApi.ts | 2 +- .../core/src/modules/oob/OutOfBandService.ts | 4 +- .../src/modules/oob/domain/OutOfBandEvents.ts | 2 +- .../core/src/modules/proofs/ProofEvents.ts | 2 +- .../proofs/ProofResponseCoordinator.ts | 2 +- .../core/src/modules/proofs/ProofService.ts | 50 +- packages/core/src/modules/proofs/ProofsApi.ts | 12 +- .../core/src/modules/proofs/ProofsModule.ts | 2 +- .../proofs/__tests__/V1ProofService.test.ts | 2 +- .../proofs/formats/ProofFormatService.ts | 42 +- .../formats/ProofFormatServiceOptions.ts | 4 +- .../proofs/formats/indy/IndyProofFormat.ts | 6 +- .../formats/indy/IndyProofFormatService.ts | 4 +- .../indy/IndyProofFormatsServiceOptions.ts | 6 +- .../models/ProofFormatServiceOptions.ts | 2 +- .../proofs/models/ProofServiceOptions.ts | 4 +- .../modules/proofs/models/SharedOptions.ts | 2 +- .../proofs/protocol/v2/V2ProofService.ts | 2 +- .../core/src/modules/routing/MediatorApi.ts | 2 +- .../src/modules/routing/MediatorModule.ts | 2 +- .../core/src/modules/routing/RecipientApi.ts | 4 +- .../src/modules/routing/RecipientModule.ts | 2 +- .../core/src/modules/routing/RoutingEvents.ts | 4 +- .../pickup/v1/MessagePickupService.ts | 2 +- .../pickup/v2/V2MessagePickupService.ts | 2 +- .../services/MediationRecipientService.ts | 2 +- .../src/modules/vc/W3cCredentialService.ts | 6 +- packages/core/src/modules/vc/W3cVcModule.ts | 2 +- .../vc/__tests__/W3cCredentialService.test.ts | 2 +- packages/core/src/modules/vc/jsonldUtil.ts | 2 +- .../modules/vc/libraries/documentLoader.ts | 2 +- .../vc/models/W3cCredentialServiceOptions.ts | 6 +- .../vc/models/credential/W3cCredential.ts | 2 +- .../credential/W3cVerifiableCredential.ts | 2 +- .../credential/W3cVerifyCredentialResult.ts | 2 +- .../presentation/W3cVerifiablePresentation.ts | 2 +- packages/core/src/plugins/Module.ts | 2 +- .../src/storage/InMemoryMessageRepository.ts | 2 +- .../core/src/storage/IndyStorageService.ts | 7 +- packages/core/src/storage/Repository.ts | 4 +- packages/core/src/storage/RepositoryEvents.ts | 2 +- packages/core/src/storage/StorageService.ts | 2 +- .../storage/didcomm/DidCommMessageRecord.ts | 2 +- .../didcomm/DidCommMessageRepository.ts | 2 +- .../storage/migration/StorageUpdateService.ts | 2 +- .../src/storage/migration/UpdateAssistant.ts | 2 +- .../core/src/storage/migration/updates.ts | 4 +- .../migration/updates/0.1-0.2/mediation.ts | 2 +- .../src/transport/HttpOutboundTransport.ts | 2 +- .../core/src/transport/WsOutboundTransport.ts | 4 +- packages/core/src/types.ts | 1 - packages/core/src/utils/attachment.ts | 2 +- packages/core/src/utils/indyError.ts | 2 +- packages/core/src/utils/messageType.ts | 2 +- packages/core/src/wallet/IndyWallet.ts | 18 +- packages/core/src/wallet/WalletApi.ts | 2 +- packages/core/tests/oob.test.ts | 21 +- packages/indy-sdk/package.json | 6 +- .../src/dids/IndySdkSovDidRegistrar.ts | 2 +- packages/indy-sdk/src/error/indyError.ts | 2 +- packages/node/package.json | 10 +- packages/openid4vc-client/package.json | 6 +- packages/question-answer/package.json | 6 +- .../tests/question-answer.e2e.test.ts | 4 +- packages/react-native/package.json | 6 +- packages/tenants/package.json | 6 +- tests/e2e-subject.test.ts | 3 +- tsconfig.build.json | 4 +- yarn.lock | 1012 ++++++++++------- 139 files changed, 982 insertions(+), 741 deletions(-) diff --git a/package.json b/package.json index 636ee9f284..24f487b9a2 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "test": "jest", "lint": "eslint --ignore-path .gitignore .", "validate": "yarn lint && yarn check-types && yarn check-format", + "prepare": "husky install", "run-mediator": "ts-node ./samples/mediator.ts", "next-version-bump": "ts-node ./scripts/get-next-bump.ts" }, @@ -31,12 +32,12 @@ "@types/eslint": "^7.2.13", "@types/express": "^4.17.13", "@types/jest": "^26.0.23", - "@types/node": "^15.14.4", + "@types/node": "^16.11.7", "@types/uuid": "^8.3.1", "@types/varint": "^6.0.0", "@types/ws": "^7.4.6", - "@typescript-eslint/eslint-plugin": "^4.26.1", - "@typescript-eslint/parser": "^4.26.1", + "@typescript-eslint/eslint-plugin": "^5.48.1", + "@typescript-eslint/parser": "^5.48.1", "conventional-changelog-conventionalcommits": "^5.0.0", "conventional-recommended-bump": "^6.1.0", "cors": "^2.8.5", @@ -47,6 +48,7 @@ "eslint-plugin-import": "^2.23.4", "eslint-plugin-prettier": "^3.4.0", "express": "^4.17.1", + "husky": "^7.0.1", "indy-sdk": "^1.16.0-dev-1636", "jest": "^27.0.4", "lerna": "^4.0.0", @@ -54,13 +56,13 @@ "rxjs": "^7.2.0", "ts-jest": "^27.0.3", "ts-node": "^10.0.0", - "tsconfig-paths": "^3.9.0", + "tsconfig-paths": "^4.1.2", "tsyringe": "^4.7.0", - "typescript": "~4.3.0", + "typescript": "~4.9.4", "ws": "^7.4.6" }, "resolutions": { - "@types/node": "^15.14.4" + "@types/node": "^16.11.7" }, "engines": { "node": ">= 14" diff --git a/packages/action-menu/package.json b/packages/action-menu/package.json index 7537700f4c..e4b54a744a 100644 --- a/packages/action-menu/package.json +++ b/packages/action-menu/package.json @@ -18,7 +18,7 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" @@ -31,7 +31,7 @@ }, "devDependencies": { "reflect-metadata": "^0.1.13", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "rimraf": "^4.0.7", + "typescript": "~4.9.4" } } diff --git a/packages/action-menu/src/services/ActionMenuService.ts b/packages/action-menu/src/services/ActionMenuService.ts index 89c27f54a4..8282bc60bf 100644 --- a/packages/action-menu/src/services/ActionMenuService.ts +++ b/packages/action-menu/src/services/ActionMenuService.ts @@ -1,5 +1,3 @@ -import type { ActionMenuStateChangedEvent } from '../ActionMenuEvents' -import type { ActionMenuProblemReportMessage } from '../messages' import type { ClearMenuOptions, CreateMenuOptions, @@ -7,6 +5,8 @@ import type { CreateRequestOptions, FindMenuOptions, } from './ActionMenuServiceOptions' +import type { ActionMenuStateChangedEvent } from '../ActionMenuEvents' +import type { ActionMenuProblemReportMessage } from '../messages' import type { AgentContext, InboundMessageContext, Logger, Query } from '@aries-framework/core' import { AgentConfig, EventEmitter, AriesFrameworkError, injectable, JsonTransformer } from '@aries-framework/core' diff --git a/packages/action-menu/tests/action-menu.e2e.test.ts b/packages/action-menu/tests/action-menu.e2e.test.ts index b15524fd93..553d7e0c20 100644 --- a/packages/action-menu/tests/action-menu.e2e.test.ts +++ b/packages/action-menu/tests/action-menu.e2e.test.ts @@ -9,8 +9,6 @@ import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutbou import { getAgentOptions, makeConnection } from '../../core/tests/helpers' import testLogger from '../../core/tests/logger' -import { waitForActionMenuRecord } from './helpers' - import { ActionMenu, ActionMenuModule, @@ -19,6 +17,8 @@ import { ActionMenuState, } from '@aries-framework/action-menu' +import { waitForActionMenuRecord } from './helpers' + const faberAgentOptions = getAgentOptions( 'Faber Action Menu', { diff --git a/packages/anoncreds/package.json b/packages/anoncreds/package.json index 25033d94a2..473c115b01 100644 --- a/packages/anoncreds/package.json +++ b/packages/anoncreds/package.json @@ -19,7 +19,7 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" @@ -28,7 +28,7 @@ "@aries-framework/core": "0.3.2" }, "devDependencies": { - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "rimraf": "^4.0.7", + "typescript": "~4.9.4" } } diff --git a/packages/anoncreds/src/services/AnonCredsHolderService.ts b/packages/anoncreds/src/services/AnonCredsHolderService.ts index 3e287bde00..4991dbca1f 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderService.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderService.ts @@ -1,5 +1,3 @@ -import type { CredentialInfo } from '../models' -import type { AnonCredsProof } from '../models/exchange' import type { CreateCredentialRequestOptions, CreateCredentialRequestReturn, @@ -9,6 +7,8 @@ import type { GetCredentialsForProofRequestOptions, GetCredentialsForProofRequestReturn, } from './AnonCredsHolderServiceOptions' +import type { CredentialInfo } from '../models' +import type { AnonCredsProof } from '../models/exchange' import type { AgentContext } from '@aries-framework/core' export interface AnonCredsHolderService { diff --git a/packages/anoncreds/src/services/AnonCredsIssuerService.ts b/packages/anoncreds/src/services/AnonCredsIssuerService.ts index f3fb8c128f..0f34d300ef 100644 --- a/packages/anoncreds/src/services/AnonCredsIssuerService.ts +++ b/packages/anoncreds/src/services/AnonCredsIssuerService.ts @@ -1,5 +1,3 @@ -import type { AnonCredsCredentialOffer } from '../models/exchange' -import type { AnonCredsCredentialDefinition, AnonCredsSchema } from '../models/registry' import type { CreateSchemaOptions, CreateCredentialDefinitionOptions, @@ -7,6 +5,8 @@ import type { CreateCredentialReturn, CreateCredentialOptions, } from './AnonCredsIssuerServiceOptions' +import type { AnonCredsCredentialOffer } from '../models/exchange' +import type { AnonCredsCredentialDefinition, AnonCredsSchema } from '../models/registry' import type { AgentContext } from '@aries-framework/core' export interface AnonCredsIssuerService { diff --git a/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts b/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts index e2f7e14298..142e784405 100644 --- a/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts +++ b/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts @@ -1,4 +1,3 @@ -import type { AnonCredsCredentialDefinition } from '../../models/registry' import type { AnonCredsResolutionMetadata, Extensible, @@ -6,6 +5,7 @@ import type { AnonCredsOperationStateFinished, AnonCredsOperationState, } from './base' +import type { AnonCredsCredentialDefinition } from '../../models/registry' export interface GetCredentialDefinitionReturn { credentialDefinition: AnonCredsCredentialDefinition | null diff --git a/packages/anoncreds/src/services/registry/RevocationListOptions.ts b/packages/anoncreds/src/services/registry/RevocationListOptions.ts index aea0c5d5b9..b6f0edea42 100644 --- a/packages/anoncreds/src/services/registry/RevocationListOptions.ts +++ b/packages/anoncreds/src/services/registry/RevocationListOptions.ts @@ -1,5 +1,5 @@ -import type { AnonCredsRevocationList } from '../../models/registry' import type { AnonCredsResolutionMetadata, Extensible } from './base' +import type { AnonCredsRevocationList } from '../../models/registry' export interface GetRevocationListReturn { revocationList: AnonCredsRevocationList | null diff --git a/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts b/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts index 5e63f79995..6d45377114 100644 --- a/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts +++ b/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts @@ -1,5 +1,5 @@ -import type { AnonCredsRevocationRegistryDefinition } from '../../models/registry' import type { AnonCredsResolutionMetadata, Extensible } from './base' +import type { AnonCredsRevocationRegistryDefinition } from '../../models/registry' export interface GetRevocationRegistryDefinitionReturn { revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition | null diff --git a/packages/anoncreds/src/services/registry/SchemaOptions.ts b/packages/anoncreds/src/services/registry/SchemaOptions.ts index f4d706223a..c436859060 100644 --- a/packages/anoncreds/src/services/registry/SchemaOptions.ts +++ b/packages/anoncreds/src/services/registry/SchemaOptions.ts @@ -1,4 +1,3 @@ -import type { AnonCredsSchema } from '../../models/registry' import type { AnonCredsResolutionMetadata, Extensible, @@ -6,6 +5,7 @@ import type { AnonCredsOperationStateFinished, AnonCredsOperationState, } from './base' +import type { AnonCredsSchema } from '../../models/registry' // Get Schema export interface GetSchemaReturn { diff --git a/packages/bbs-signatures/package.json b/packages/bbs-signatures/package.json index 353048460f..96f1e6cdde 100644 --- a/packages/bbs-signatures/package.json +++ b/packages/bbs-signatures/package.json @@ -19,7 +19,7 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" @@ -36,8 +36,8 @@ "devDependencies": { "@aries-framework/node": "*", "reflect-metadata": "^0.1.13", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "rimraf": "^4.0.7", + "typescript": "~4.9.4" }, "peerDependenciesMeta": { "@animo-id/react-native-bbs-signatures": { diff --git a/packages/core/package.json b/packages/core/package.json index cd0e119a36..971091140e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -18,7 +18,7 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build" }, @@ -60,8 +60,8 @@ "@types/uuid": "^8.3.0", "@types/varint": "^6.0.0", "node-fetch": "^2.0", - "rimraf": "~3.0.2", + "rimraf": "^4.0.7", "tslog": "^3.2.0", - "typescript": "~4.3.0" + "typescript": "~4.9.4" } } diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index c4c5609387..a3a6b11ab1 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -1,9 +1,9 @@ -import type { InboundTransport } from '../transport/InboundTransport' -import type { OutboundTransport } from '../transport/OutboundTransport' -import type { InitConfig } from '../types' import type { AgentDependencies } from './AgentDependencies' import type { AgentModulesInput } from './AgentModules' import type { AgentMessageReceivedEvent } from './Events' +import type { InboundTransport } from '../transport/InboundTransport' +import type { OutboundTransport } from '../transport/OutboundTransport' +import type { InitConfig } from '../types' import type { Subscription } from 'rxjs' import { Subject } from 'rxjs' diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index cceef0e271..28ad67488a 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -1,6 +1,6 @@ +import type { AgentDependencies } from './AgentDependencies' import type { Logger } from '../logger' import type { InitConfig } from '../types' -import type { AgentDependencies } from './AgentDependencies' import { DID_COMM_TRANSPORT_QUEUE } from '../constants' import { AriesFrameworkError } from '../error' diff --git a/packages/core/src/agent/AgentModules.ts b/packages/core/src/agent/AgentModules.ts index ce59b48daa..4bb5cc4067 100644 --- a/packages/core/src/agent/AgentModules.ts +++ b/packages/core/src/agent/AgentModules.ts @@ -1,7 +1,7 @@ +import type { AgentConfig } from './AgentConfig' import type { Module, DependencyManager, ApiModule } from '../plugins' import type { IsAny } from '../types' import type { Constructor } from '../utils/mixins' -import type { AgentConfig } from './AgentConfig' import { BasicMessagesModule } from '../modules/basic-messages' import { ConnectionsModule } from '../modules/connections' diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index b0abda3209..606b586a67 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -1,9 +1,9 @@ -import type { Logger } from '../logger' -import type { CredentialsModule } from '../modules/credentials' -import type { DependencyManager } from '../plugins' import type { AgentConfig } from './AgentConfig' import type { AgentApi, EmptyModuleMap, ModulesMap, WithoutDefaultModules, CustomOrDefaultApi } from './AgentModules' import type { TransportSession } from './TransportService' +import type { Logger } from '../logger' +import type { CredentialsModule } from '../modules/credentials' +import type { DependencyManager } from '../plugins' import { AriesFrameworkError } from '../error' import { BasicMessagesApi } from '../modules/basic-messages' diff --git a/packages/core/src/agent/EnvelopeService.ts b/packages/core/src/agent/EnvelopeService.ts index cd50b22fc0..df72e3d983 100644 --- a/packages/core/src/agent/EnvelopeService.ts +++ b/packages/core/src/agent/EnvelopeService.ts @@ -1,6 +1,6 @@ -import type { EncryptedMessage, PlaintextMessage } from '../types' import type { AgentMessage } from './AgentMessage' import type { AgentContext } from './context' +import type { EncryptedMessage, PlaintextMessage } from '../types' import { InjectionSymbols } from '../constants' import { Key, KeyType } from '../crypto' diff --git a/packages/core/src/agent/Events.ts b/packages/core/src/agent/Events.ts index 4e7bb4b076..0eecc21fe4 100644 --- a/packages/core/src/agent/Events.ts +++ b/packages/core/src/agent/Events.ts @@ -1,6 +1,6 @@ -import type { ConnectionRecord } from '../modules/connections' import type { AgentMessage } from './AgentMessage' import type { OutboundMessageContext, OutboundMessageSendStatus } from './models' +import type { ConnectionRecord } from '../modules/connections' import type { Observable } from 'rxjs' import { filter } from 'rxjs' diff --git a/packages/core/src/agent/MessageHandlerRegistry.ts b/packages/core/src/agent/MessageHandlerRegistry.ts index 3942c43c55..574a9331f3 100644 --- a/packages/core/src/agent/MessageHandlerRegistry.ts +++ b/packages/core/src/agent/MessageHandlerRegistry.ts @@ -39,7 +39,7 @@ export class MessageHandlerRegistry { */ public get supportedMessageTypes() { return this.messageHandlers - .reduce((all, cur) => [...all, ...cur.supportedMessages], []) + .reduce<(typeof AgentMessage)[]>((all, cur) => [...all, ...cur.supportedMessages], []) .map((m) => m.type) } diff --git a/packages/core/src/agent/MessageReceiver.ts b/packages/core/src/agent/MessageReceiver.ts index a87b42b7e2..befabec616 100644 --- a/packages/core/src/agent/MessageReceiver.ts +++ b/packages/core/src/agent/MessageReceiver.ts @@ -1,10 +1,10 @@ -import type { ConnectionRecord } from '../modules/connections' -import type { InboundTransport } from '../transport' -import type { EncryptedMessage, PlaintextMessage } from '../types' import type { AgentMessage } from './AgentMessage' import type { DecryptedMessageContext } from './EnvelopeService' import type { TransportSession } from './TransportService' import type { AgentContext } from './context' +import type { ConnectionRecord } from '../modules/connections' +import type { InboundTransport } from '../transport' +import type { EncryptedMessage, PlaintextMessage } from '../types' import { InjectionSymbols } from '../constants' import { AriesFrameworkError } from '../error' diff --git a/packages/core/src/agent/MessageSender.ts b/packages/core/src/agent/MessageSender.ts index ac9ac731a2..04aad34d38 100644 --- a/packages/core/src/agent/MessageSender.ts +++ b/packages/core/src/agent/MessageSender.ts @@ -1,14 +1,14 @@ +import type { AgentMessage } from './AgentMessage' +import type { EnvelopeKeys } from './EnvelopeService' +import type { AgentMessageSentEvent } from './Events' +import type { TransportSession } from './TransportService' +import type { AgentContext } from './context' import type { ConnectionRecord } from '../modules/connections' import type { ResolvedDidCommService } from '../modules/didcomm' import type { DidDocument } from '../modules/dids' import type { OutOfBandRecord } from '../modules/oob/repository' import type { OutboundTransport } from '../transport/OutboundTransport' import type { OutboundPackage, EncryptedMessage } from '../types' -import type { AgentMessage } from './AgentMessage' -import type { EnvelopeKeys } from './EnvelopeService' -import type { AgentMessageSentEvent } from './Events' -import type { TransportSession } from './TransportService' -import type { AgentContext } from './context' import { DID_COMM_TRANSPORT_QUEUE, InjectionSymbols } from '../constants' import { ReturnRouteTypes } from '../decorators/transport/TransportDecorator' diff --git a/packages/core/src/agent/TransportService.ts b/packages/core/src/agent/TransportService.ts index 55a9708ae0..0eda25500c 100644 --- a/packages/core/src/agent/TransportService.ts +++ b/packages/core/src/agent/TransportService.ts @@ -1,8 +1,8 @@ +import type { AgentMessage } from './AgentMessage' +import type { EnvelopeKeys } from './EnvelopeService' import type { ConnectionRecord } from '../modules/connections/repository' import type { DidDocument } from '../modules/dids' import type { EncryptedMessage } from '../types' -import type { AgentMessage } from './AgentMessage' -import type { EnvelopeKeys } from './EnvelopeService' import { DID_COMM_TRANSPORT_QUEUE } from '../constants' import { injectable } from '../plugins' diff --git a/packages/core/src/agent/context/AgentContext.ts b/packages/core/src/agent/context/AgentContext.ts index 714a5933a5..73a857d518 100644 --- a/packages/core/src/agent/context/AgentContext.ts +++ b/packages/core/src/agent/context/AgentContext.ts @@ -1,6 +1,6 @@ +import type { AgentContextProvider } from './AgentContextProvider' import type { DependencyManager } from '../../plugins' import type { Wallet } from '../../wallet' -import type { AgentContextProvider } from './AgentContextProvider' import { InjectionSymbols } from '../../constants' import { AgentConfig } from '../AgentConfig' diff --git a/packages/core/src/cache/PersistedLruCache.ts b/packages/core/src/cache/PersistedLruCache.ts index ab00e0d14e..bb94c41bee 100644 --- a/packages/core/src/cache/PersistedLruCache.ts +++ b/packages/core/src/cache/PersistedLruCache.ts @@ -1,5 +1,5 @@ -import type { AgentContext } from '../agent' import type { CacheRepository } from './CacheRepository' +import type { AgentContext } from '../agent' import { LRUMap } from 'lru_map' diff --git a/packages/core/src/crypto/JwsService.ts b/packages/core/src/crypto/JwsService.ts index 29a7f390e0..ffad03c128 100644 --- a/packages/core/src/crypto/JwsService.ts +++ b/packages/core/src/crypto/JwsService.ts @@ -1,6 +1,6 @@ +import type { Jws, JwsGeneralFormat } from './JwsTypes' import type { AgentContext } from '../agent' import type { Buffer } from '../utils' -import type { Jws, JwsGeneralFormat } from './JwsTypes' import { AriesFrameworkError } from '../error' import { injectable } from '../plugins' diff --git a/packages/core/src/crypto/WalletKeyPair.ts b/packages/core/src/crypto/WalletKeyPair.ts index f5008112db..7df8cbb6ad 100644 --- a/packages/core/src/crypto/WalletKeyPair.ts +++ b/packages/core/src/crypto/WalletKeyPair.ts @@ -1,6 +1,6 @@ +import type { Key } from './Key' import type { LdKeyPairOptions } from '../modules/vc/models/LdKeyPair' import type { Wallet } from '../wallet' -import type { Key } from './Key' import { VerificationMethod } from '../modules/dids' import { getKeyDidMappingByVerificationMethod } from '../modules/dids/domain/key-type/keyDidMapping' diff --git a/packages/core/src/crypto/signing-provider/SigningProviderRegistry.ts b/packages/core/src/crypto/signing-provider/SigningProviderRegistry.ts index 8a33483d2d..db71348ef6 100644 --- a/packages/core/src/crypto/signing-provider/SigningProviderRegistry.ts +++ b/packages/core/src/crypto/signing-provider/SigningProviderRegistry.ts @@ -1,5 +1,5 @@ -import type { KeyType } from '..' import type { SigningProvider } from './SigningProvider' +import type { KeyType } from '../KeyType' import { AriesFrameworkError } from '../../error' import { injectable, injectAll } from '../../plugins' diff --git a/packages/core/src/decorators/service/ServiceDecoratorExtension.ts b/packages/core/src/decorators/service/ServiceDecoratorExtension.ts index ecf2f9a044..776e8f702f 100644 --- a/packages/core/src/decorators/service/ServiceDecoratorExtension.ts +++ b/packages/core/src/decorators/service/ServiceDecoratorExtension.ts @@ -1,5 +1,5 @@ -import type { BaseMessageConstructor } from '../../agent/BaseMessage' import type { ServiceDecoratorOptions } from './ServiceDecorator' +import type { BaseMessageConstructor } from '../../agent/BaseMessage' import { Expose, Type } from 'class-transformer' import { IsOptional, ValidateNested } from 'class-validator' diff --git a/packages/core/src/modules/basic-messages/BasicMessageEvents.ts b/packages/core/src/modules/basic-messages/BasicMessageEvents.ts index 7c25f5b9c4..f05873f5de 100644 --- a/packages/core/src/modules/basic-messages/BasicMessageEvents.ts +++ b/packages/core/src/modules/basic-messages/BasicMessageEvents.ts @@ -1,6 +1,6 @@ -import type { BaseEvent } from '../../agent/Events' import type { BasicMessage } from './messages' import type { BasicMessageRecord } from './repository' +import type { BaseEvent } from '../../agent/Events' export enum BasicMessageEventTypes { BasicMessageStateChanged = 'BasicMessageStateChanged', diff --git a/packages/core/src/modules/basic-messages/BasicMessagesApi.ts b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts index 816340429d..938f6b8407 100644 --- a/packages/core/src/modules/basic-messages/BasicMessagesApi.ts +++ b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts @@ -1,5 +1,5 @@ -import type { Query } from '../../storage/StorageService' import type { BasicMessageRecord } from './repository/BasicMessageRecord' +import type { Query } from '../../storage/StorageService' import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' diff --git a/packages/core/src/modules/connections/ConnectionEvents.ts b/packages/core/src/modules/connections/ConnectionEvents.ts index 327a897dda..c9f1064bab 100644 --- a/packages/core/src/modules/connections/ConnectionEvents.ts +++ b/packages/core/src/modules/connections/ConnectionEvents.ts @@ -1,6 +1,6 @@ -import type { BaseEvent } from '../../agent/Events' import type { DidExchangeState } from './models' import type { ConnectionRecord } from './repository/ConnectionRecord' +import type { BaseEvent } from '../../agent/Events' export enum ConnectionEventTypes { ConnectionStateChanged = 'ConnectionStateChanged', diff --git a/packages/core/src/modules/connections/ConnectionsApi.ts b/packages/core/src/modules/connections/ConnectionsApi.ts index da895de772..9777bc903a 100644 --- a/packages/core/src/modules/connections/ConnectionsApi.ts +++ b/packages/core/src/modules/connections/ConnectionsApi.ts @@ -1,8 +1,8 @@ -import type { Query } from '../../storage/StorageService' -import type { OutOfBandRecord } from '../oob/repository' import type { ConnectionType } from './models' import type { ConnectionRecord } from './repository/ConnectionRecord' import type { Routing } from './services' +import type { Query } from '../../storage/StorageService' +import type { OutOfBandRecord } from '../oob/repository' import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' diff --git a/packages/core/src/modules/connections/ConnectionsModule.ts b/packages/core/src/modules/connections/ConnectionsModule.ts index b589f202ce..537f7695a7 100644 --- a/packages/core/src/modules/connections/ConnectionsModule.ts +++ b/packages/core/src/modules/connections/ConnectionsModule.ts @@ -1,6 +1,6 @@ +import type { ConnectionsModuleConfigOptions } from './ConnectionsModuleConfig' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' -import type { ConnectionsModuleConfigOptions } from './ConnectionsModuleConfig' import { Protocol } from '../../agent/models' diff --git a/packages/core/src/modules/connections/DidExchangeProtocol.ts b/packages/core/src/modules/connections/DidExchangeProtocol.ts index 23965bbebc..c577a260f7 100644 --- a/packages/core/src/modules/connections/DidExchangeProtocol.ts +++ b/packages/core/src/modules/connections/DidExchangeProtocol.ts @@ -1,11 +1,11 @@ +import type { ConnectionRecord } from './repository' +import type { Routing } from './services/ConnectionService' import type { AgentContext } from '../../agent' import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' import type { ParsedMessageType } from '../../utils/messageType' import type { ResolvedDidCommService } from '../didcomm' import type { PeerDidCreateOptions } from '../dids' import type { OutOfBandRecord } from '../oob/repository' -import type { ConnectionRecord } from './repository' -import type { Routing } from './services/ConnectionService' import { InjectionSymbols } from '../../constants' import { Key, KeyType } from '../../crypto' diff --git a/packages/core/src/modules/connections/DidExchangeStateMachine.ts b/packages/core/src/modules/connections/DidExchangeStateMachine.ts index 3f32f4e48d..be73793ab3 100644 --- a/packages/core/src/modules/connections/DidExchangeStateMachine.ts +++ b/packages/core/src/modules/connections/DidExchangeStateMachine.ts @@ -1,5 +1,5 @@ -import type { ParsedMessageType } from '../../utils/messageType' import type { ConnectionRecord } from './repository' +import type { ParsedMessageType } from '../../utils/messageType' import { AriesFrameworkError } from '../../error' import { canHandleMessageType } from '../../utils/messageType' diff --git a/packages/core/src/modules/connections/TrustPingEvents.ts b/packages/core/src/modules/connections/TrustPingEvents.ts index 55200e6c5b..2b0fcf66c9 100644 --- a/packages/core/src/modules/connections/TrustPingEvents.ts +++ b/packages/core/src/modules/connections/TrustPingEvents.ts @@ -1,6 +1,6 @@ -import type { BaseEvent } from '../../agent/Events' import type { TrustPingMessage, TrustPingResponseMessage } from './messages' import type { ConnectionRecord } from './repository/ConnectionRecord' +import type { BaseEvent } from '../../agent/Events' export enum TrustPingEventTypes { TrustPingReceivedEvent = 'TrustPingReceivedEvent', diff --git a/packages/core/src/modules/connections/errors/ConnectionProblemReportError.ts b/packages/core/src/modules/connections/errors/ConnectionProblemReportError.ts index 764be043f9..f96f98261d 100644 --- a/packages/core/src/modules/connections/errors/ConnectionProblemReportError.ts +++ b/packages/core/src/modules/connections/errors/ConnectionProblemReportError.ts @@ -1,5 +1,5 @@ -import type { ProblemReportErrorOptions } from '../../problem-reports' import type { ConnectionProblemReportReason } from './ConnectionProblemReportReason' +import type { ProblemReportErrorOptions } from '../../problem-reports' import { ProblemReportError } from '../../problem-reports' import { ConnectionProblemReportMessage } from '../messages' diff --git a/packages/core/src/modules/connections/errors/DidExchangeProblemReportError.ts b/packages/core/src/modules/connections/errors/DidExchangeProblemReportError.ts index 17bf72ad9b..6e8d9a9925 100644 --- a/packages/core/src/modules/connections/errors/DidExchangeProblemReportError.ts +++ b/packages/core/src/modules/connections/errors/DidExchangeProblemReportError.ts @@ -1,5 +1,5 @@ -import type { ProblemReportErrorOptions } from '../../problem-reports' import type { DidExchangeProblemReportReason } from './DidExchangeProblemReportReason' +import type { ProblemReportErrorOptions } from '../../problem-reports' import { ProblemReportError } from '../../problem-reports' import { DidExchangeProblemReportMessage } from '../messages' diff --git a/packages/core/src/modules/connections/models/did/DidDoc.ts b/packages/core/src/modules/connections/models/did/DidDoc.ts index 896d314221..4d86fa7e19 100644 --- a/packages/core/src/modules/connections/models/did/DidDoc.ts +++ b/packages/core/src/modules/connections/models/did/DidDoc.ts @@ -1,6 +1,6 @@ -import type { DidDocumentService } from '../../../dids/domain/service' import type { Authentication } from './authentication' import type { PublicKey } from './publicKey' +import type { DidDocumentService } from '../../../dids/domain/service' import { Expose } from 'class-transformer' import { Equals, IsArray, IsString, ValidateNested } from 'class-validator' diff --git a/packages/core/src/modules/connections/models/did/authentication/Authentication.ts b/packages/core/src/modules/connections/models/did/authentication/Authentication.ts index 76c2cef823..41ff0bc5d5 100644 --- a/packages/core/src/modules/connections/models/did/authentication/Authentication.ts +++ b/packages/core/src/modules/connections/models/did/authentication/Authentication.ts @@ -1,5 +1,5 @@ import type { PublicKey } from '../publicKey/PublicKey' export abstract class Authentication { - abstract publicKey: PublicKey + public abstract publicKey: PublicKey } diff --git a/packages/core/src/modules/connections/repository/ConnectionRecord.ts b/packages/core/src/modules/connections/repository/ConnectionRecord.ts index 382b0f0eac..f05106e5c9 100644 --- a/packages/core/src/modules/connections/repository/ConnectionRecord.ts +++ b/packages/core/src/modules/connections/repository/ConnectionRecord.ts @@ -1,7 +1,7 @@ +import type { ConnectionMetadata } from './ConnectionMetadataTypes' import type { TagsBase } from '../../../storage/BaseRecord' import type { HandshakeProtocol } from '../models' import type { ConnectionType } from '../models/ConnectionType' -import type { ConnectionMetadata } from './ConnectionMetadataTypes' import { AriesFrameworkError } from '../../../error' import { BaseRecord } from '../../../storage/BaseRecord' diff --git a/packages/core/src/modules/credentials/CredentialEvents.ts b/packages/core/src/modules/credentials/CredentialEvents.ts index d9324d2bfe..2a60193d51 100644 --- a/packages/core/src/modules/credentials/CredentialEvents.ts +++ b/packages/core/src/modules/credentials/CredentialEvents.ts @@ -1,6 +1,6 @@ -import type { BaseEvent } from '../../agent/Events' import type { CredentialState } from './models/CredentialState' import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' +import type { BaseEvent } from '../../agent/Events' export enum CredentialEventTypes { CredentialStateChanged = 'CredentialStateChanged', diff --git a/packages/core/src/modules/credentials/CredentialProtocolOptions.ts b/packages/core/src/modules/credentials/CredentialProtocolOptions.ts index a26a0e6861..5b934bdb0c 100644 --- a/packages/core/src/modules/credentials/CredentialProtocolOptions.ts +++ b/packages/core/src/modules/credentials/CredentialProtocolOptions.ts @@ -1,6 +1,3 @@ -import type { AgentMessage } from '../../agent/AgentMessage' -import type { FlatArray } from '../../types' -import type { ConnectionRecord } from '../connections/repository/ConnectionRecord' import type { CredentialFormat, CredentialFormatPayload, @@ -11,6 +8,8 @@ import type { CredentialPreviewAttributeOptions } from './models' import type { AutoAcceptCredential } from './models/CredentialAutoAcceptType' import type { CredentialProtocol } from './protocol/CredentialProtocol' import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' +import type { AgentMessage } from '../../agent/AgentMessage' +import type { ConnectionRecord } from '../connections/repository/ConnectionRecord' /** * Get the format data payload for a specific message from a list of CredentialFormat interfaces and a message @@ -42,14 +41,15 @@ export type FormatDataMessagePayload< } /** - * Infer an array of {@link CredentialFormatService} types based on a {@link CredentialProtocol}. + * Infer the {@link CredentialFormat} types based on an array of {@link CredentialProtocol} types. * - * It does this by extracting the `CredentialFormatServices` generic from the `CredentialProtocol`. + * It does this by extracting the `CredentialFormatServices` generic from the `CredentialProtocol`, and + * then extracting the `CredentialFormat` generic from each of the `CredentialFormatService` types. * * @example * ``` * // TheCredentialFormatServices is now equal to [IndyCredentialFormatService] - * type TheCredentialFormatServices = ExtractCredentialFormatServices + * type TheCredentialFormatServices = CredentialFormatsFromProtocols<[V1CredentialProtocol]> * ``` * * Because the `V1CredentialProtocol` is defined as follows: @@ -58,27 +58,14 @@ export type FormatDataMessagePayload< * } * ``` */ -export type ExtractCredentialFormatServices = Type extends CredentialProtocol - ? CredentialFormatServices +export type CredentialFormatsFromProtocols = Type[number] extends CredentialProtocol< + infer CredentialFormatServices +> + ? CredentialFormatServices extends CredentialFormatService[] + ? ExtractCredentialFormats + : never : never -/** - * Infer an array of {@link CredentialFormat} types based on an array of {@link CredentialProtocol} types. - * - * This is based on {@link ExtractCredentialFormatServices}, but allows to handle arrays. - */ -export type CFsFromCPs = _CFsFromCPs extends CredentialFormat[] - ? _CFsFromCPs - : [] - -/** - * Utility type for {@link ExtractCredentialFormatServicesFromCredentialProtocols} to reduce duplication. - * Should not be used directly. - */ -type _CFsFromCPs = FlatArray<{ - [CP in keyof CPs]: ExtractCredentialFormats> -}>[] - /** * Get format data return value. Each key holds a mapping of credential format key to format data. * diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index 10520e4c2e..e69fe6c6a5 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -1,6 +1,4 @@ -import type { AgentMessage } from '../../agent/AgentMessage' -import type { Query } from '../../storage/StorageService' -import type { CFsFromCPs, DeleteCredentialOptions } from './CredentialProtocolOptions' +import type { CredentialFormatsFromProtocols, DeleteCredentialOptions } from './CredentialProtocolOptions' import type { AcceptCredentialOptions, AcceptCredentialOfferOptions, @@ -21,6 +19,8 @@ import type { } from './CredentialsApiOptions' import type { CredentialProtocol } from './protocol/CredentialProtocol' import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' +import type { AgentMessage } from '../../agent/AgentMessage' +import type { Query } from '../../storage/StorageService' import { AgentContext } from '../../agent' import { MessageSender } from '../../agent/MessageSender' @@ -77,7 +77,7 @@ export interface CredentialsApi { findById(credentialRecordId: string): Promise deleteById(credentialRecordId: string, options?: DeleteCredentialOptions): Promise update(credentialRecord: CredentialExchangeRecord): Promise - getFormatData(credentialRecordId: string): Promise>> + getFormatData(credentialRecordId: string): Promise>> // DidComm Message Records findProposalMessage(credentialExchangeId: string): Promise> @@ -134,12 +134,12 @@ export class CredentialsApi implements Credent ) as CredentialProtocolMap } - private getProtocol>(protocolVersion: PVT) { + private getProtocol>(protocolVersion: PVT): CredentialProtocol { if (!this.credentialProtocolMap[protocolVersion]) { throw new AriesFrameworkError(`No credential protocol registered for protocol version ${protocolVersion}`) } - return this.credentialProtocolMap[protocolVersion] + return this.credentialProtocolMap[protocolVersion] as CredentialProtocol } /** @@ -593,7 +593,9 @@ export class CredentialsApi implements Credent return credentialRecord } - public async getFormatData(credentialRecordId: string): Promise>> { + public async getFormatData( + credentialRecordId: string + ): Promise>> { const credentialRecord = await this.getById(credentialRecordId) const service = this.getProtocol(credentialRecord.protocolVersion) @@ -664,25 +666,31 @@ export class CredentialsApi implements Credent public async findProposalMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findProposalMessage(this.agentContext, credentialExchangeId) + return service.findProposalMessage( + this.agentContext, + credentialExchangeId + ) as FindCredentialProposalMessageReturn } public async findOfferMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findOfferMessage(this.agentContext, credentialExchangeId) + return service.findOfferMessage(this.agentContext, credentialExchangeId) as FindCredentialOfferMessageReturn } public async findRequestMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findRequestMessage(this.agentContext, credentialExchangeId) + return service.findRequestMessage( + this.agentContext, + credentialExchangeId + ) as FindCredentialRequestMessageReturn } public async findCredentialMessage(credentialExchangeId: string): Promise> { const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findCredentialMessage(this.agentContext, credentialExchangeId) + return service.findCredentialMessage(this.agentContext, credentialExchangeId) as FindCredentialMessageReturn } private async getServiceForCredentialExchangeId(credentialExchangeId: string) { diff --git a/packages/core/src/modules/credentials/CredentialsApiOptions.ts b/packages/core/src/modules/credentials/CredentialsApiOptions.ts index 7eb6c7488f..24fb0a86d1 100644 --- a/packages/core/src/modules/credentials/CredentialsApiOptions.ts +++ b/packages/core/src/modules/credentials/CredentialsApiOptions.ts @@ -1,4 +1,4 @@ -import type { CFsFromCPs, GetFormatDataReturn } from './CredentialProtocolOptions' +import type { CredentialFormatsFromProtocols, GetFormatDataReturn } from './CredentialProtocolOptions' import type { CredentialFormatPayload } from './formats' import type { AutoAcceptCredential } from './models/CredentialAutoAcceptType' import type { CredentialProtocol } from './protocol/CredentialProtocol' @@ -53,7 +53,7 @@ interface BaseOptions { export interface ProposeCredentialOptions extends BaseOptions { connectionId: string protocolVersion: CredentialProtocolVersionType - credentialFormats: CredentialFormatPayload, 'createProposal'> + credentialFormats: CredentialFormatPayload, 'createProposal'> } /** @@ -64,7 +64,7 @@ export interface ProposeCredentialOptions extends BaseOptions { credentialRecordId: string - credentialFormats?: CredentialFormatPayload, 'acceptProposal'> + credentialFormats?: CredentialFormatPayload, 'acceptProposal'> } /** @@ -73,7 +73,7 @@ export interface AcceptCredentialProposalOptions extends BaseOptions { credentialRecordId: string - credentialFormats: CredentialFormatPayload, 'createOffer'> + credentialFormats: CredentialFormatPayload, 'createOffer'> } /** @@ -81,7 +81,7 @@ export interface NegotiateCredentialProposalOptions extends BaseOptions { protocolVersion: CredentialProtocolVersionType - credentialFormats: CredentialFormatPayload, 'createOffer'> + credentialFormats: CredentialFormatPayload, 'createOffer'> } /** @@ -101,7 +101,7 @@ export interface OfferCredentialOptions extends BaseOptions { credentialRecordId: string - credentialFormats?: CredentialFormatPayload, 'acceptOffer'> + credentialFormats?: CredentialFormatPayload, 'acceptOffer'> } /** @@ -110,7 +110,7 @@ export interface AcceptCredentialOfferOptions extends BaseOptions { credentialRecordId: string - credentialFormats: CredentialFormatPayload, 'createProposal'> + credentialFormats: CredentialFormatPayload, 'createProposal'> } /** @@ -121,7 +121,7 @@ export interface NegotiateCredentialOfferOptions extends BaseOptions { credentialRecordId: string - credentialFormats?: CredentialFormatPayload, 'acceptRequest'> + credentialFormats?: CredentialFormatPayload, 'acceptRequest'> autoAcceptCredential?: AutoAcceptCredential comment?: string } diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index a581f5e551..b141f63f8a 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -1,9 +1,9 @@ +import type { CredentialsModuleConfigOptions } from './CredentialsModuleConfig' +import type { CredentialProtocol } from './protocol/CredentialProtocol' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { ApiModule, DependencyManager } from '../../plugins' import type { Constructor } from '../../utils/mixins' import type { Optional } from '../../utils/type' -import type { CredentialsModuleConfigOptions } from './CredentialsModuleConfig' -import type { CredentialProtocol } from './protocol/CredentialProtocol' import { Protocol } from '../../agent/models' diff --git a/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts b/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts index ffbe633004..41a5ed808b 100644 --- a/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts +++ b/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts @@ -1,5 +1,5 @@ -import type { ProblemReportErrorOptions } from '../../problem-reports' import type { CredentialProblemReportReason } from './CredentialProblemReportReason' +import type { ProblemReportErrorOptions } from '../../problem-reports' import { V1CredentialProblemReportMessage } from '../protocol/v1/messages' diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts index 4e8e1e4102..20d98623d3 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts @@ -1,5 +1,3 @@ -import type { AgentContext } from '../../../agent' -import type { Attachment } from '../../../decorators/attachment/Attachment' import type { CredentialFormat } from './CredentialFormat' import type { FormatCreateProposalOptions, @@ -18,6 +16,8 @@ import type { FormatAutoRespondRequestOptions, FormatProcessCredentialOptions, } from './CredentialFormatServiceOptions' +import type { AgentContext } from '../../../agent' +import type { Attachment } from '../../../decorators/attachment/Attachment' export interface CredentialFormatService { formatKey: CF['formatKey'] diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts index eb2430498f..2f494da4ae 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts @@ -1,9 +1,9 @@ +import type { CredentialFormat, CredentialFormatPayload } from './CredentialFormat' +import type { CredentialFormatService } from './CredentialFormatService' import type { Attachment } from '../../../decorators/attachment/Attachment' import type { CredentialFormatSpec } from '../models/CredentialFormatSpec' import type { CredentialPreviewAttribute } from '../models/CredentialPreviewAttribute' import type { CredentialExchangeRecord } from '../repository/CredentialExchangeRecord' -import type { CredentialFormat, CredentialFormatPayload } from './CredentialFormat' -import type { CredentialFormatService } from './CredentialFormatService' /** * Infer the {@link CredentialFormat} based on a {@link CredentialFormatService}. diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts index 9f15c1152f..eeee56e5d9 100644 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts @@ -1,7 +1,7 @@ +import type { IndyCredProposeOptions } from './models/IndyCredPropose' import type { LinkedAttachment } from '../../../../utils/LinkedAttachment' import type { CredentialPreviewAttributeOptions } from '../../models' import type { CredentialFormat } from '../CredentialFormat' -import type { IndyCredProposeOptions } from './models/IndyCredPropose' import type { Cred, CredOffer, CredReq } from 'indy-sdk' /** diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts index cf14a6c4db..aca8aec43a 100644 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts @@ -1,3 +1,4 @@ +import type { IndyCredentialFormat } from './IndyCredentialFormat' import type { AgentContext } from '../../../../agent' import type { LinkedAttachment } from '../../../../utils/LinkedAttachment' import type { CredentialPreviewAttributeOptions } from '../../models/CredentialPreviewAttribute' @@ -19,7 +20,6 @@ import type { FormatProcessOptions, FormatProcessCredentialOptions, } from '../CredentialFormatServiceOptions' -import type { IndyCredentialFormat } from './IndyCredentialFormat' import type * as Indy from 'indy-sdk' import { KeyType } from '../../../../crypto' diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts index a5df1d86e6..36f88eb4db 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts @@ -1,3 +1,9 @@ +import type { + JsonLdCredentialFormat, + JsonCredential, + JsonLdFormatDataCredentialDetail, + JsonLdFormatDataVerifiableCredential, +} from './JsonLdCredentialFormat' import type { AgentContext } from '../../../../agent' import type { CredentialFormatService } from '../CredentialFormatService' import type { @@ -17,12 +23,6 @@ import type { FormatProcessOptions, FormatAutoRespondCredentialOptions, } from '../CredentialFormatServiceOptions' -import type { - JsonLdCredentialFormat, - JsonCredential, - JsonLdFormatDataCredentialDetail, - JsonLdFormatDataVerifiableCredential, -} from './JsonLdCredentialFormat' import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../../error' diff --git a/packages/core/src/modules/credentials/protocol/BaseCredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/BaseCredentialProtocol.ts index d6a7682c8b..d4a10f4ebc 100644 --- a/packages/core/src/modules/credentials/protocol/BaseCredentialProtocol.ts +++ b/packages/core/src/modules/credentials/protocol/BaseCredentialProtocol.ts @@ -1,3 +1,4 @@ +import type { CredentialProtocol } from './CredentialProtocol' import type { AgentContext } from '../../../agent' import type { AgentMessage } from '../../../agent/AgentMessage' import type { FeatureRegistry } from '../../../agent/FeatureRegistry' @@ -23,7 +24,6 @@ import type { } from '../CredentialProtocolOptions' import type { CredentialFormatService, ExtractCredentialFormats } from '../formats' import type { CredentialExchangeRecord } from '../repository' -import type { CredentialProtocol } from './CredentialProtocol' import { EventEmitter } from '../../../agent/EventEmitter' import { DidCommMessageRepository } from '../../../storage' @@ -39,74 +39,93 @@ import { CredentialRepository } from '../repository' export abstract class BaseCredentialProtocol implements CredentialProtocol { - abstract readonly version: string + public abstract readonly version: string protected abstract getFormatServiceForRecordType(credentialRecordType: string): CFs[number] // methods for proposal - abstract createProposal( + public abstract createProposal( agentContext: AgentContext, options: CreateProposalOptions ): Promise> - abstract processProposal(messageContext: InboundMessageContext): Promise - abstract acceptProposal( + public abstract processProposal( + messageContext: InboundMessageContext + ): Promise + public abstract acceptProposal( agentContext: AgentContext, options: AcceptProposalOptions ): Promise> - abstract negotiateProposal( + public abstract negotiateProposal( agentContext: AgentContext, options: NegotiateProposalOptions ): Promise> // methods for offer - abstract createOffer( + public abstract createOffer( agentContext: AgentContext, options: CreateOfferOptions ): Promise> - abstract processOffer(messageContext: InboundMessageContext): Promise - abstract acceptOffer( + public abstract processOffer(messageContext: InboundMessageContext): Promise + public abstract acceptOffer( agentContext: AgentContext, options: AcceptOfferOptions ): Promise> - abstract negotiateOffer( + public abstract negotiateOffer( agentContext: AgentContext, options: NegotiateOfferOptions ): Promise> // methods for request - abstract createRequest( + public abstract createRequest( agentContext: AgentContext, options: CreateRequestOptions ): Promise> - abstract processRequest(messageContext: InboundMessageContext): Promise - abstract acceptRequest( + public abstract processRequest(messageContext: InboundMessageContext): Promise + public abstract acceptRequest( agentContext: AgentContext, options: AcceptRequestOptions ): Promise> // methods for issue - abstract processCredential(messageContext: InboundMessageContext): Promise - abstract acceptCredential( + public abstract processCredential( + messageContext: InboundMessageContext + ): Promise + public abstract acceptCredential( agentContext: AgentContext, options: AcceptCredentialOptions ): Promise> // methods for ack - abstract processAck(messageContext: InboundMessageContext): Promise + public abstract processAck(messageContext: InboundMessageContext): Promise // methods for problem-report - abstract createProblemReport(agentContext: AgentContext, options: CreateProblemReportOptions): ProblemReportMessage + public abstract createProblemReport( + agentContext: AgentContext, + options: CreateProblemReportOptions + ): ProblemReportMessage - abstract findProposalMessage(agentContext: AgentContext, credentialExchangeId: string): Promise - abstract findOfferMessage(agentContext: AgentContext, credentialExchangeId: string): Promise - abstract findRequestMessage(agentContext: AgentContext, credentialExchangeId: string): Promise - abstract findCredentialMessage(agentContext: AgentContext, credentialExchangeId: string): Promise - abstract getFormatData( + public abstract findProposalMessage( + agentContext: AgentContext, + credentialExchangeId: string + ): Promise + public abstract findOfferMessage( + agentContext: AgentContext, + credentialExchangeId: string + ): Promise + public abstract findRequestMessage( + agentContext: AgentContext, + credentialExchangeId: string + ): Promise + public abstract findCredentialMessage( + agentContext: AgentContext, + credentialExchangeId: string + ): Promise + public abstract getFormatData( agentContext: AgentContext, credentialExchangeId: string ): Promise>> - abstract register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void + public abstract register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void /** * Decline a credential offer diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts index e8085a05f2..c7780cf414 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts @@ -58,8 +58,8 @@ let wallet let signCredentialOptions: JsonLdCredentialDetailFormat describe('credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent + let faberAgent: Agent<(typeof faberAgentOptions)['modules']> + let aliceAgent: Agent<(typeof aliceAgentOptions)['modules']> let faberReplay: ReplaySubject let aliceReplay: ReplaySubject const seed = 'testseed000000000000000000000001' diff --git a/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts b/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts index 304fc67487..290865b83a 100644 --- a/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts +++ b/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts @@ -1,8 +1,8 @@ +import type { CredentialMetadata } from './CredentialMetadataTypes' import type { TagsBase } from '../../../storage/BaseRecord' import type { AutoAcceptCredential } from '../models/CredentialAutoAcceptType' import type { CredentialState } from '../models/CredentialState' import type { RevocationNotification } from '../models/RevocationNotification' -import type { CredentialMetadata } from './CredentialMetadataTypes' import { Type } from 'class-transformer' diff --git a/packages/core/src/modules/dids/DidsModule.ts b/packages/core/src/modules/dids/DidsModule.ts index cf438e3ae8..a82dabeb8f 100644 --- a/packages/core/src/modules/dids/DidsModule.ts +++ b/packages/core/src/modules/dids/DidsModule.ts @@ -1,5 +1,5 @@ -import type { DependencyManager, Module } from '../../plugins' import type { DidsModuleConfigOptions } from './DidsModuleConfig' +import type { DependencyManager, Module } from '../../plugins' import { DidsApi } from './DidsApi' import { DidsModuleConfig } from './DidsModuleConfig' diff --git a/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts index 7dd78ca824..f6d6763a4f 100644 --- a/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts +++ b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts @@ -10,10 +10,11 @@ import { Agent } from '../../../agent/Agent' import { KeyType } from '../../../crypto' import { TypedArrayEncoder } from '../../../utils' import { indyDidFromPublicKeyBase58 } from '../../../utils/did' -import { PeerDidNumAlgo } from '../methods/peer/didPeer' import { InjectionSymbols, JsonTransformer } from '@aries-framework/core' +import { PeerDidNumAlgo } from '../methods/peer/didPeer' + const agentOptions = getAgentOptions('Faber Dids Registrar', { indyLedgers: [ { diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts index 6ac241f5d9..aee5774210 100644 --- a/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts @@ -1,5 +1,5 @@ -import type { VerificationMethod } from '../verificationMethod' import type { KeyDidMapping } from './keyDidMapping' +import type { VerificationMethod } from '../verificationMethod' import { KeyType } from '../../../../crypto' import { Key } from '../../../../crypto/Key' diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts index f7cc4b2a6f..e980f9d142 100644 --- a/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts @@ -1,5 +1,5 @@ -import type { VerificationMethod } from '../verificationMethod' import type { KeyDidMapping } from './keyDidMapping' +import type { VerificationMethod } from '../verificationMethod' import { KeyType } from '../../../../crypto' import { Key } from '../../../../crypto/Key' diff --git a/packages/core/src/modules/dids/domain/key-type/ed25519.ts b/packages/core/src/modules/dids/domain/key-type/ed25519.ts index 4098d230b5..5058accff3 100644 --- a/packages/core/src/modules/dids/domain/key-type/ed25519.ts +++ b/packages/core/src/modules/dids/domain/key-type/ed25519.ts @@ -1,5 +1,5 @@ -import type { VerificationMethod } from '../verificationMethod' import type { KeyDidMapping } from './keyDidMapping' +import type { VerificationMethod } from '../verificationMethod' import { convertPublicKeyToX25519 } from '@stablelib/ed25519' diff --git a/packages/core/src/modules/dids/domain/key-type/x25519.ts b/packages/core/src/modules/dids/domain/key-type/x25519.ts index 5ce7ff0683..399029928e 100644 --- a/packages/core/src/modules/dids/domain/key-type/x25519.ts +++ b/packages/core/src/modules/dids/domain/key-type/x25519.ts @@ -1,5 +1,5 @@ -import type { VerificationMethod } from '../verificationMethod' import type { KeyDidMapping } from './keyDidMapping' +import type { VerificationMethod } from '../verificationMethod' import { KeyType } from '../../../../crypto' import { Key } from '../../../../crypto/Key' diff --git a/packages/core/src/modules/dids/repository/DidRecord.ts b/packages/core/src/modules/dids/repository/DidRecord.ts index 752088323b..b36f3f03d0 100644 --- a/packages/core/src/modules/dids/repository/DidRecord.ts +++ b/packages/core/src/modules/dids/repository/DidRecord.ts @@ -1,5 +1,5 @@ -import type { TagsBase } from '../../../storage/BaseRecord' import type { DidRecordMetadata } from './didRecordMetadataTypes' +import type { TagsBase } from '../../../storage/BaseRecord' import { Type } from 'class-transformer' import { IsEnum, ValidateNested } from 'class-validator' diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts index 94e376f08d..3d074f9d18 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts @@ -1,4 +1,3 @@ -import type { Feature } from '../../agent/models' import type { DiscloseFeaturesOptions, QueryFeaturesOptions, @@ -6,6 +5,7 @@ import type { } from './DiscoverFeaturesApiOptions' import type { DiscoverFeaturesDisclosureReceivedEvent } from './DiscoverFeaturesEvents' import type { DiscoverFeaturesService } from './services' +import type { Feature } from '../../agent/models' import { firstValueFrom, of, ReplaySubject, Subject } from 'rxjs' import { catchError, filter, map, takeUntil, timeout } from 'rxjs/operators' @@ -80,7 +80,7 @@ export class DiscoverFeaturesApi< throw new AriesFrameworkError(`No discover features service registered for protocol version ${protocolVersion}`) } - return this.serviceMap[protocolVersion] + return this.serviceMap[protocolVersion] as DiscoverFeaturesService } /** diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts index 7cdcc18cb4..11bdf538b7 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts @@ -1,5 +1,5 @@ -import type { FeatureQueryOptions } from '../../agent/models' import type { DiscoverFeaturesService } from './services' +import type { FeatureQueryOptions } from '../../agent/models' /** * Get the supported protocol versions based on the provided discover features services. diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts index 79caa885f9..bd97e12ec4 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts @@ -1,6 +1,6 @@ +import type { DiscoverFeaturesModuleConfigOptions } from './DiscoverFeaturesModuleConfig' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' -import type { DiscoverFeaturesModuleConfigOptions } from './DiscoverFeaturesModuleConfig' import { Protocol } from '../../agent/models' diff --git a/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts b/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts index 0720c8747a..fb5cd56a1e 100644 --- a/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts +++ b/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts @@ -32,15 +32,15 @@ export abstract class DiscoverFeaturesService { this.discoverFeaturesModuleConfig = discoverFeaturesModuleConfig } - abstract readonly version: string + public abstract readonly version: string - abstract createQuery(options: CreateQueryOptions): Promise> - abstract processQuery( + public abstract createQuery(options: CreateQueryOptions): Promise> + public abstract processQuery( messageContext: InboundMessageContext ): Promise | void> - abstract createDisclosure( + public abstract createDisclosure( options: CreateDisclosureOptions ): Promise> - abstract processDisclosure(messageContext: InboundMessageContext): Promise + public abstract processDisclosure(messageContext: InboundMessageContext): Promise } diff --git a/packages/core/src/modules/generic-records/GenericRecordsApi.ts b/packages/core/src/modules/generic-records/GenericRecordsApi.ts index 56efe6667e..a995d7b4c5 100644 --- a/packages/core/src/modules/generic-records/GenericRecordsApi.ts +++ b/packages/core/src/modules/generic-records/GenericRecordsApi.ts @@ -1,5 +1,5 @@ -import type { Query } from '../../storage/StorageService' import type { GenericRecord, SaveGenericRecordOption } from './repository/GenericRecord' +import type { Query } from '../../storage/StorageService' import { AgentContext } from '../../agent' import { InjectionSymbols } from '../../constants' diff --git a/packages/core/src/modules/indy/services/IndyRevocationService.ts b/packages/core/src/modules/indy/services/IndyRevocationService.ts index 4dd89c2e4d..c1caf5b297 100644 --- a/packages/core/src/modules/indy/services/IndyRevocationService.ts +++ b/packages/core/src/modules/indy/services/IndyRevocationService.ts @@ -1,7 +1,7 @@ import type { AgentContext } from '../../../agent' import type { IndyRevocationInterval } from '../../credentials' import type { RequestedCredentials } from '../../proofs/formats/indy/models/RequestedCredentials' -import type { default as Indy, RevState, RevStates } from 'indy-sdk' +import type { default as Indy, RevStates } from 'indy-sdk' import { AgentDependencies } from '../../../agent/AgentDependencies' import { InjectionSymbols } from '../../../constants' diff --git a/packages/core/src/modules/ledger/LedgerModule.ts b/packages/core/src/modules/ledger/LedgerModule.ts index 8bb9a3de82..4090d146ab 100644 --- a/packages/core/src/modules/ledger/LedgerModule.ts +++ b/packages/core/src/modules/ledger/LedgerModule.ts @@ -1,5 +1,5 @@ -import type { DependencyManager, Module } from '../../plugins' import type { LedgerModuleConfigOptions } from './LedgerModuleConfig' +import type { DependencyManager, Module } from '../../plugins' import { AnonCredsCredentialDefinitionRepository } from '../indy/repository/AnonCredsCredentialDefinitionRepository' import { AnonCredsSchemaRepository } from '../indy/repository/AnonCredsSchemaRepository' diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts index 5936baac7f..aee58655da 100644 --- a/packages/core/src/modules/oob/OutOfBandApi.ts +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -1,10 +1,10 @@ +import type { HandshakeReusedEvent } from './domain/OutOfBandEvents' import type { AgentMessage } from '../../agent/AgentMessage' import type { AgentMessageReceivedEvent } from '../../agent/Events' import type { Attachment } from '../../decorators/attachment/Attachment' import type { Query } from '../../storage/StorageService' import type { PlaintextMessage } from '../../types' import type { ConnectionInvitationMessage, ConnectionRecord, Routing } from '../connections' -import type { HandshakeReusedEvent } from './domain/OutOfBandEvents' import { catchError, EmptyError, first, firstValueFrom, map, of, timeout } from 'rxjs' diff --git a/packages/core/src/modules/oob/OutOfBandService.ts b/packages/core/src/modules/oob/OutOfBandService.ts index f1a77c9bd5..377e8867c2 100644 --- a/packages/core/src/modules/oob/OutOfBandService.ts +++ b/packages/core/src/modules/oob/OutOfBandService.ts @@ -1,10 +1,10 @@ +import type { HandshakeReusedEvent, OutOfBandStateChangedEvent } from './domain/OutOfBandEvents' +import type { OutOfBandRecord } from './repository' import type { AgentContext } from '../../agent' import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' import type { Key } from '../../crypto' import type { Query } from '../../storage/StorageService' import type { ConnectionRecord } from '../connections' -import type { HandshakeReusedEvent, OutOfBandStateChangedEvent } from './domain/OutOfBandEvents' -import type { OutOfBandRecord } from './repository' import { EventEmitter } from '../../agent/EventEmitter' import { AriesFrameworkError } from '../../error' diff --git a/packages/core/src/modules/oob/domain/OutOfBandEvents.ts b/packages/core/src/modules/oob/domain/OutOfBandEvents.ts index a3936cc784..15561062b5 100644 --- a/packages/core/src/modules/oob/domain/OutOfBandEvents.ts +++ b/packages/core/src/modules/oob/domain/OutOfBandEvents.ts @@ -1,7 +1,7 @@ +import type { OutOfBandState } from './OutOfBandState' import type { BaseEvent } from '../../../agent/Events' import type { ConnectionRecord } from '../../connections' import type { OutOfBandRecord } from '../repository' -import type { OutOfBandState } from './OutOfBandState' export enum OutOfBandEventTypes { OutOfBandStateChanged = 'OutOfBandStateChanged', diff --git a/packages/core/src/modules/proofs/ProofEvents.ts b/packages/core/src/modules/proofs/ProofEvents.ts index 20394c56a9..86b0e7673c 100644 --- a/packages/core/src/modules/proofs/ProofEvents.ts +++ b/packages/core/src/modules/proofs/ProofEvents.ts @@ -1,6 +1,6 @@ -import type { BaseEvent } from '../../agent/Events' import type { ProofState } from './models/ProofState' import type { ProofExchangeRecord } from './repository' +import type { BaseEvent } from '../../agent/Events' export enum ProofEventTypes { ProofStateChanged = 'ProofStateChanged', diff --git a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts index 77c9a04fd5..db625f71e0 100644 --- a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts +++ b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts @@ -1,5 +1,5 @@ -import type { AgentContext } from '../../agent/context/AgentContext' import type { ProofExchangeRecord } from './repository' +import type { AgentContext } from '../../agent/context/AgentContext' import { injectable } from '../../plugins' diff --git a/packages/core/src/modules/proofs/ProofService.ts b/packages/core/src/modules/proofs/ProofService.ts index dca7cea41f..2fcbe509b1 100644 --- a/packages/core/src/modules/proofs/ProofService.ts +++ b/packages/core/src/modules/proofs/ProofService.ts @@ -1,14 +1,3 @@ -import type { AgentConfig } from '../../agent/AgentConfig' -import type { AgentMessage } from '../../agent/AgentMessage' -import type { Dispatcher } from '../../agent/Dispatcher' -import type { EventEmitter } from '../../agent/EventEmitter' -import type { AgentContext } from '../../agent/context/AgentContext' -import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' -import type { Logger } from '../../logger' -import type { DidCommMessageRepository, DidCommMessageRole } from '../../storage' -import type { Wallet } from '../../wallet/Wallet' -import type { ConnectionService } from '../connections/services' -import type { MediationRecipientService, RoutingService } from '../routing' import type { ProofStateChangedEvent } from './ProofEvents' import type { ProofResponseCoordinator } from './ProofResponseCoordinator' import type { ProofFormat } from './formats/ProofFormat' @@ -30,6 +19,17 @@ import type { } from './models/ProofServiceOptions' import type { ProofState } from './models/ProofState' import type { ProofExchangeRecord, ProofRepository } from './repository' +import type { AgentConfig } from '../../agent/AgentConfig' +import type { AgentMessage } from '../../agent/AgentMessage' +import type { Dispatcher } from '../../agent/Dispatcher' +import type { EventEmitter } from '../../agent/EventEmitter' +import type { AgentContext } from '../../agent/context/AgentContext' +import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' +import type { Logger } from '../../logger' +import type { DidCommMessageRepository, DidCommMessageRole } from '../../storage' +import type { Wallet } from '../../wallet/Wallet' +import type { ConnectionService } from '../connections/services' +import type { MediationRecipientService, RoutingService } from '../routing' import { JsonTransformer } from '../../utils/JsonTransformer' @@ -58,7 +58,7 @@ export abstract class ProofService { this.wallet = wallet this.logger = agentConfig.logger } - abstract readonly version: string + public abstract readonly version: string public emitStateChangedEvent( agentContext: AgentContext, @@ -104,7 +104,7 @@ export abstract class ProofService { * 5. Store proposal message * 6. Return proposal message + proof record */ - abstract createProposal( + public abstract createProposal( agentContext: AgentContext, options: CreateProposalOptions ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> @@ -119,7 +119,7 @@ export abstract class ProofService { * 5. Create or update proposal message * 6. Return proposal message + proof record */ - abstract createProposalAsResponse( + public abstract createProposalAsResponse( agentContext: AgentContext, options: CreateProposalAsResponseOptions ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> @@ -142,40 +142,42 @@ export abstract class ProofService { * 4. Loop through all format services to process proposal message * 5. Save & return record */ - abstract processProposal(messageContext: InboundMessageContext): Promise + public abstract processProposal(messageContext: InboundMessageContext): Promise - abstract createRequest( + public abstract createRequest( agentContext: AgentContext, options: CreateRequestOptions ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - abstract createRequestAsResponse( + public abstract createRequestAsResponse( agentContext: AgentContext, options: CreateRequestAsResponseOptions ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - abstract processRequest(messageContext: InboundMessageContext): Promise + public abstract processRequest(messageContext: InboundMessageContext): Promise - abstract createPresentation( + public abstract createPresentation( agentContext: AgentContext, options: CreatePresentationOptions ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - abstract processPresentation(messageContext: InboundMessageContext): Promise + public abstract processPresentation(messageContext: InboundMessageContext): Promise - abstract createAck( + public abstract createAck( agentContext: AgentContext, options: CreateAckOptions ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - abstract processAck(messageContext: InboundMessageContext): Promise + public abstract processAck(messageContext: InboundMessageContext): Promise - abstract createProblemReport( + public abstract createProblemReport( agentContext: AgentContext, options: CreateProblemReportOptions ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - abstract processProblemReport(messageContext: InboundMessageContext): Promise + public abstract processProblemReport( + messageContext: InboundMessageContext + ): Promise public abstract shouldAutoRespondToProposal( agentContext: AgentContext, diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index af54ebe234..38f5e642bd 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -1,5 +1,3 @@ -import type { AgentMessage } from '../../agent/AgentMessage' -import type { Query } from '../../storage/StorageService' import type { ProofService } from './ProofService' import type { AcceptProofPresentationOptions, @@ -33,6 +31,8 @@ import type { CreateProposalAsResponseOptions, } from './models/ProofServiceOptions' import type { ProofExchangeRecord } from './repository/ProofExchangeRecord' +import type { AgentMessage } from '../../agent/AgentMessage' +import type { Query } from '../../storage/StorageService' import { inject, injectable } from 'tsyringe' @@ -158,7 +158,7 @@ export class ProofsApi< throw new AriesFrameworkError(`No proof service registered for protocol version ${protocolVersion}`) } - return this.serviceMap[protocolVersion] + return this.serviceMap[protocolVersion] as ProofService } /** @@ -728,19 +728,19 @@ export class ProofsApi< public async findProposalMessage(proofRecordId: string): Promise> { const record = await this.getById(proofRecordId) const service = this.getService(record.protocolVersion) - return service.findProposalMessage(this.agentContext, proofRecordId) + return service.findProposalMessage(this.agentContext, proofRecordId) as FindProofProposalMessageReturn } public async findRequestMessage(proofRecordId: string): Promise> { const record = await this.getById(proofRecordId) const service = this.getService(record.protocolVersion) - return service.findRequestMessage(this.agentContext, proofRecordId) + return service.findRequestMessage(this.agentContext, proofRecordId) as FindProofRequestMessageReturn } public async findPresentationMessage(proofRecordId: string): Promise> { const record = await this.getById(proofRecordId) const service = this.getService(record.protocolVersion) - return service.findPresentationMessage(this.agentContext, proofRecordId) + return service.findPresentationMessage(this.agentContext, proofRecordId) as FindProofPresentationMessageReturn } private registerMessageHandlers(dispatcher: Dispatcher, mediationRecipientService: MediationRecipientService) { diff --git a/packages/core/src/modules/proofs/ProofsModule.ts b/packages/core/src/modules/proofs/ProofsModule.ts index 05136c95a0..339ecd0c41 100644 --- a/packages/core/src/modules/proofs/ProofsModule.ts +++ b/packages/core/src/modules/proofs/ProofsModule.ts @@ -1,6 +1,6 @@ +import type { ProofsModuleConfigOptions } from './ProofsModuleConfig' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' -import type { ProofsModuleConfigOptions } from './ProofsModuleConfig' import { Protocol } from '../../agent/models' diff --git a/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts b/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts index 180a1b8a34..3da57e27c1 100644 --- a/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts +++ b/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts @@ -1,8 +1,8 @@ +import type { CustomProofTags } from './../repository/ProofExchangeRecord' import type { AgentContext } from '../../../agent' import type { Wallet } from '../../../wallet/Wallet' import type { CredentialRepository } from '../../credentials/repository' import type { ProofStateChangedEvent } from '../ProofEvents' -import type { CustomProofTags } from './../repository/ProofExchangeRecord' import { Subject } from 'rxjs' diff --git a/packages/core/src/modules/proofs/formats/ProofFormatService.ts b/packages/core/src/modules/proofs/formats/ProofFormatService.ts index 1ce367cf33..931d4c886f 100644 --- a/packages/core/src/modules/proofs/formats/ProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/ProofFormatService.ts @@ -1,12 +1,3 @@ -import type { AgentContext } from '../../../agent' -import type { AgentConfig } from '../../../agent/AgentConfig' -import type { DidCommMessageRepository } from '../../../storage' -import type { - CreateRequestAsResponseOptions, - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, -} from '../models/ProofServiceOptions' -import type { ProofRequestFormats } from '../models/SharedOptions' import type { ProofFormat } from './ProofFormat' import type { IndyProofFormat } from './indy/IndyProofFormat' import type { GetRequestedCredentialsFormat } from './indy/IndyProofFormatsServiceOptions' @@ -20,6 +11,15 @@ import type { ProcessProposalOptions, ProcessRequestOptions, } from './models/ProofFormatServiceOptions' +import type { AgentContext } from '../../../agent' +import type { AgentConfig } from '../../../agent/AgentConfig' +import type { DidCommMessageRepository } from '../../../storage' +import type { + CreateRequestAsResponseOptions, + FormatRequestedCredentialReturn, + FormatRetrievedCredentialOptions, +} from '../models/ProofServiceOptions' +import type { ProofRequestFormats } from '../models/SharedOptions' /** * This abstract class is the base class for any proof format @@ -33,29 +33,31 @@ export abstract class ProofFormatService { protected didCommMessageRepository: DidCommMessageRepository protected agentConfig: AgentConfig - abstract readonly formatKey: PF['formatKey'] + public abstract readonly formatKey: PF['formatKey'] public constructor(didCommMessageRepository: DidCommMessageRepository, agentConfig: AgentConfig) { this.didCommMessageRepository = didCommMessageRepository this.agentConfig = agentConfig } - abstract createProposal(options: FormatCreateProofProposalOptions): Promise + public abstract createProposal(options: FormatCreateProofProposalOptions): Promise - abstract processProposal(options: ProcessProposalOptions): Promise + public abstract processProposal(options: ProcessProposalOptions): Promise - abstract createRequest(options: CreateRequestOptions): Promise + public abstract createRequest(options: CreateRequestOptions): Promise - abstract processRequest(options: ProcessRequestOptions): Promise + public abstract processRequest(options: ProcessRequestOptions): Promise - abstract createPresentation( + public abstract createPresentation( agentContext: AgentContext, options: FormatCreatePresentationOptions ): Promise - abstract processPresentation(agentContext: AgentContext, options: ProcessPresentationOptions): Promise + public abstract processPresentation(agentContext: AgentContext, options: ProcessPresentationOptions): Promise - abstract createProofRequestFromProposal(options: CreatePresentationFormatsOptions): Promise + public abstract createProofRequestFromProposal( + options: CreatePresentationFormatsOptions + ): Promise public abstract getRequestedCredentialsForProofRequest( agentContext: AgentContext, @@ -66,14 +68,14 @@ export abstract class ProofFormatService { options: FormatRetrievedCredentialOptions<[PF]> ): Promise> - abstract proposalAndRequestAreEqual( + public abstract proposalAndRequestAreEqual( proposalAttachments: ProofAttachmentFormat[], requestAttachments: ProofAttachmentFormat[] ): boolean - abstract supportsFormat(formatIdentifier: string): boolean + public abstract supportsFormat(formatIdentifier: string): boolean - abstract createRequestAsResponse( + public abstract createRequestAsResponse( options: CreateRequestAsResponseOptions<[IndyProofFormat]> ): Promise } diff --git a/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts b/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts index 0fcd3d405c..25731e43c8 100644 --- a/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts +++ b/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts @@ -1,7 +1,7 @@ -import type { Attachment } from '../../../decorators/attachment/Attachment' -import type { ProofFormatSpec } from '../models/ProofFormatSpec' import type { ProofFormat } from './ProofFormat' import type { ProofFormatService } from './ProofFormatService' +import type { Attachment } from '../../../decorators/attachment/Attachment' +import type { ProofFormatSpec } from '../models/ProofFormatSpec' /** * Get the service map for usage in the proofs module. Will return a type mapping of protocol version to service. diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts index 8d6769be1e..ae58b2db75 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts @@ -1,9 +1,9 @@ -import type { PresentationPreviewAttribute, PresentationPreviewPredicate } from '../../protocol/v1' -import type { ProofFormat } from '../ProofFormat' -import type { IndyRequestProofFormat } from '../indy/IndyProofFormatsServiceOptions' import type { RequestedAttribute } from './models/RequestedAttribute' import type { IndyRequestedCredentialsOptions } from './models/RequestedCredentials' import type { RequestedPredicate } from './models/RequestedPredicate' +import type { PresentationPreviewAttribute, PresentationPreviewPredicate } from '../../protocol/v1' +import type { ProofFormat } from '../ProofFormat' +import type { IndyRequestProofFormat } from '../indy/IndyProofFormatsServiceOptions' import type { IndyProof, IndyProofRequest } from 'indy-sdk' export interface IndyProposeProofFormat { diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts index 1aec763e65..ecdb78f358 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts @@ -1,3 +1,5 @@ +import type { IndyProofFormat, IndyProposeProofFormat } from './IndyProofFormat' +import type { GetRequestedCredentialsFormat } from './IndyProofFormatsServiceOptions' import type { AgentContext } from '../../../../agent' import type { Logger } from '../../../../logger' import type { @@ -20,8 +22,6 @@ import type { ProcessRequestOptions, VerifyProofOptions, } from '../models/ProofFormatServiceOptions' -import type { IndyProofFormat, IndyProposeProofFormat } from './IndyProofFormat' -import type { GetRequestedCredentialsFormat } from './IndyProofFormatsServiceOptions' import type { CredDef, IndyProof, Schema } from 'indy-sdk' import { Lifecycle, scoped } from 'tsyringe' diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts index b1ff554453..bb78139bb7 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts @@ -1,11 +1,11 @@ +import type { IndyRequestedCredentialsFormat } from './IndyProofFormat' +import type { ProofAttributeInfo } from '.././indy/models/ProofAttributeInfo' +import type { ProofPredicateInfo } from '.././indy/models/ProofPredicateInfo' import type { Attachment } from '../../../../decorators/attachment/Attachment' import type { IndyRevocationInterval } from '../../../credentials' import type { GetRequestedCredentialsConfig } from '../../models/GetRequestedCredentialsConfig' import type { PresentationPreview } from '../../protocol/v1/models/V1PresentationPreview' import type { ProofExchangeRecord } from '../../repository/ProofExchangeRecord' -import type { ProofAttributeInfo } from '.././indy/models/ProofAttributeInfo' -import type { ProofPredicateInfo } from '.././indy/models/ProofPredicateInfo' -import type { IndyRequestedCredentialsFormat } from './IndyProofFormat' export type IndyPresentationProofFormat = IndyRequestedCredentialsFormat diff --git a/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts b/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts index 2538aaf1b4..8859c48b64 100644 --- a/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts +++ b/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts @@ -1,9 +1,9 @@ +import type { ProofAttachmentFormat } from './ProofAttachmentFormat' import type { Attachment } from '../../../../decorators/attachment/Attachment' import type { ProposeProofFormats } from '../../models/SharedOptions' import type { ProofExchangeRecord } from '../../repository' import type { ProofFormat, ProofFormatPayload } from '../ProofFormat' import type { ProofRequestOptions } from '../indy/models/ProofRequest' -import type { ProofAttachmentFormat } from './ProofAttachmentFormat' export interface CreateRequestAttachmentOptions { id?: string diff --git a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts index 3c7e7e47af..1a978404eb 100644 --- a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts +++ b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts @@ -1,8 +1,8 @@ +import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' +import type { AutoAcceptProof } from './ProofAutoAcceptType' import type { ConnectionRecord } from '../../connections' import type { ProofFormat, ProofFormatPayload } from '../formats/ProofFormat' import type { ProofExchangeRecord } from '../repository' -import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' -import type { AutoAcceptProof } from './ProofAutoAcceptType' export type FormatDataMessagePayload< CFs extends ProofFormat[] = ProofFormat[], diff --git a/packages/core/src/modules/proofs/models/SharedOptions.ts b/packages/core/src/modules/proofs/models/SharedOptions.ts index e479dea456..18fe5ef7f3 100644 --- a/packages/core/src/modules/proofs/models/SharedOptions.ts +++ b/packages/core/src/modules/proofs/models/SharedOptions.ts @@ -1,9 +1,9 @@ +import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' import type { IndyProposeProofFormat } from '../formats/indy/IndyProofFormat' import type { IndyRequestProofFormat, IndyVerifyProofFormat } from '../formats/indy/IndyProofFormatsServiceOptions' import type { ProofRequest } from '../formats/indy/models/ProofRequest' import type { IndyRequestedCredentialsOptions } from '../formats/indy/models/RequestedCredentials' import type { RetrievedCredentials } from '../formats/indy/models/RetrievedCredentials' -import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' export interface ProposeProofFormats { // If you want to propose an indy proof without attributes or diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts index 1596e55e03..cfe6ae8e43 100644 --- a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts +++ b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts @@ -340,7 +340,7 @@ export class V2ProofService extends P for (const attachmentFormat of requestAttachments) { const service = this.getFormatServiceForFormat(attachmentFormat.format) - service?.processRequest({ + await service?.processRequest({ requestAttachment: attachmentFormat, }) } diff --git a/packages/core/src/modules/routing/MediatorApi.ts b/packages/core/src/modules/routing/MediatorApi.ts index bc366e0015..78a1cbd849 100644 --- a/packages/core/src/modules/routing/MediatorApi.ts +++ b/packages/core/src/modules/routing/MediatorApi.ts @@ -1,5 +1,5 @@ -import type { EncryptedMessage } from '../../types' import type { MediationRecord } from './repository' +import type { EncryptedMessage } from '../../types' import { AgentContext } from '../../agent' import { Dispatcher } from '../../agent/Dispatcher' diff --git a/packages/core/src/modules/routing/MediatorModule.ts b/packages/core/src/modules/routing/MediatorModule.ts index db81da0f1a..fa4ef31f13 100644 --- a/packages/core/src/modules/routing/MediatorModule.ts +++ b/packages/core/src/modules/routing/MediatorModule.ts @@ -1,6 +1,6 @@ +import type { MediatorModuleConfigOptions } from './MediatorModuleConfig' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' -import type { MediatorModuleConfigOptions } from './MediatorModuleConfig' import { Protocol } from '../../agent/models' diff --git a/packages/core/src/modules/routing/RecipientApi.ts b/packages/core/src/modules/routing/RecipientApi.ts index 49c09365eb..e74ee664ca 100644 --- a/packages/core/src/modules/routing/RecipientApi.ts +++ b/packages/core/src/modules/routing/RecipientApi.ts @@ -1,8 +1,8 @@ -import type { OutboundWebSocketClosedEvent, OutboundWebSocketOpenedEvent } from '../../transport' -import type { ConnectionRecord } from '../connections' import type { MediationStateChangedEvent } from './RoutingEvents' import type { MediationRecord } from './repository' import type { GetRoutingOptions } from './services/RoutingService' +import type { OutboundWebSocketClosedEvent, OutboundWebSocketOpenedEvent } from '../../transport' +import type { ConnectionRecord } from '../connections' import { firstValueFrom, interval, merge, ReplaySubject, Subject, timer } from 'rxjs' import { delayWhen, filter, first, takeUntil, tap, throttleTime, timeout } from 'rxjs/operators' diff --git a/packages/core/src/modules/routing/RecipientModule.ts b/packages/core/src/modules/routing/RecipientModule.ts index 7f160cdb4a..8ba9364a9d 100644 --- a/packages/core/src/modules/routing/RecipientModule.ts +++ b/packages/core/src/modules/routing/RecipientModule.ts @@ -1,6 +1,6 @@ +import type { RecipientModuleConfigOptions } from './RecipientModuleConfig' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' -import type { RecipientModuleConfigOptions } from './RecipientModuleConfig' import { Protocol } from '../../agent/models' diff --git a/packages/core/src/modules/routing/RoutingEvents.ts b/packages/core/src/modules/routing/RoutingEvents.ts index f7aa892ebf..86a151abff 100644 --- a/packages/core/src/modules/routing/RoutingEvents.ts +++ b/packages/core/src/modules/routing/RoutingEvents.ts @@ -1,8 +1,8 @@ -import type { BaseEvent } from '../../agent/Events' -import type { Routing } from '../connections' import type { KeylistUpdate } from './messages/KeylistUpdateMessage' import type { MediationState } from './models/MediationState' import type { MediationRecord } from './repository/MediationRecord' +import type { BaseEvent } from '../../agent/Events' +import type { Routing } from '../connections' export enum RoutingEventTypes { MediationStateChanged = 'MediationStateChanged', diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/MessagePickupService.ts b/packages/core/src/modules/routing/protocol/pickup/v1/MessagePickupService.ts index 7c6624af68..9211359eb0 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v1/MessagePickupService.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v1/MessagePickupService.ts @@ -1,6 +1,6 @@ +import type { BatchPickupMessage } from './messages' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' import type { EncryptedMessage } from '../../../../../types' -import type { BatchPickupMessage } from './messages' import { Dispatcher } from '../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../agent/EventEmitter' diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/V2MessagePickupService.ts b/packages/core/src/modules/routing/protocol/pickup/v2/V2MessagePickupService.ts index 77d23c2e69..147467f10d 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/V2MessagePickupService.ts +++ b/packages/core/src/modules/routing/protocol/pickup/v2/V2MessagePickupService.ts @@ -1,6 +1,6 @@ +import type { DeliveryRequestMessage, MessagesReceivedMessage, StatusRequestMessage } from './messages' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' import type { EncryptedMessage } from '../../../../../types' -import type { DeliveryRequestMessage, MessagesReceivedMessage, StatusRequestMessage } from './messages' import { Dispatcher } from '../../../../../agent/Dispatcher' import { OutboundMessageContext } from '../../../../../agent/models' diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index c2ff4acba6..aac33420d6 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -1,3 +1,4 @@ +import type { GetRoutingOptions, RemoveRoutingOptions } from './RoutingService' import type { AgentContext } from '../../../agent' import type { AgentMessage } from '../../../agent/AgentMessage' import type { AgentMessageReceivedEvent } from '../../../agent/Events' @@ -9,7 +10,6 @@ import type { Routing } from '../../connections/services/ConnectionService' import type { MediationStateChangedEvent, KeylistUpdatedEvent } from '../RoutingEvents' import type { MediationDenyMessage } from '../messages' import type { StatusMessage, MessageDeliveryMessage } from '../protocol' -import type { GetRoutingOptions, RemoveRoutingOptions } from './RoutingService' import { firstValueFrom, ReplaySubject } from 'rxjs' import { filter, first, timeout } from 'rxjs/operators' diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 46ac773401..e841a7162d 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -1,6 +1,3 @@ -import type { AgentContext } from '../../agent/context' -import type { Key } from '../../crypto/Key' -import type { Query } from '../../storage/StorageService' import type { W3cVerifyCredentialResult } from './models' import type { CreatePresentationOptions, @@ -12,6 +9,9 @@ import type { VerifyPresentationOptions, } from './models/W3cCredentialServiceOptions' import type { VerifyPresentationResult } from './models/presentation/VerifyPresentationResult' +import type { AgentContext } from '../../agent/context' +import type { Key } from '../../crypto/Key' +import type { Query } from '../../storage/StorageService' import { createWalletKeyPairClass } from '../../crypto/WalletKeyPair' import { AriesFrameworkError } from '../../error' diff --git a/packages/core/src/modules/vc/W3cVcModule.ts b/packages/core/src/modules/vc/W3cVcModule.ts index 96231aa168..a793da49f4 100644 --- a/packages/core/src/modules/vc/W3cVcModule.ts +++ b/packages/core/src/modules/vc/W3cVcModule.ts @@ -1,5 +1,5 @@ -import type { DependencyManager, Module } from '../../plugins' import type { W3cVcModuleConfigOptions } from './W3cVcModuleConfig' +import type { DependencyManager, Module } from '../../plugins' import { KeyType } from '../../crypto' import { diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index 2ab30fe7e5..c339fbfb4e 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -310,7 +310,7 @@ describe('W3cCredentialService', () => { describe('Credential Storage', () => { let w3cCredentialRecord: W3cCredentialRecord - let w3cCredentialRepositoryDeleteMock: jest.MockedFunction + let w3cCredentialRepositoryDeleteMock: jest.MockedFunction<(typeof w3cCredentialRepository)['delete']> beforeEach(async () => { const credential = JsonTransformer.fromJSON( diff --git a/packages/core/src/modules/vc/jsonldUtil.ts b/packages/core/src/modules/vc/jsonldUtil.ts index 761e22726f..b4500c3ba1 100644 --- a/packages/core/src/modules/vc/jsonldUtil.ts +++ b/packages/core/src/modules/vc/jsonldUtil.ts @@ -1,6 +1,6 @@ +import type { GetProofsOptions, GetProofsResult, GetTypeOptions } from './models' import type { JsonObject, JsonValue } from '../../types' import type { SingleOrArray } from '../../utils/type' -import type { GetProofsOptions, GetProofsResult, GetTypeOptions } from './models' import { SECURITY_CONTEXT_URL } from './constants' import jsonld from './libraries/jsonld' diff --git a/packages/core/src/modules/vc/libraries/documentLoader.ts b/packages/core/src/modules/vc/libraries/documentLoader.ts index d8679884d8..50fcde95d0 100644 --- a/packages/core/src/modules/vc/libraries/documentLoader.ts +++ b/packages/core/src/modules/vc/libraries/documentLoader.ts @@ -1,5 +1,5 @@ -import type { AgentContext } from '../../../agent/context/AgentContext' import type { DocumentLoader } from './jsonld' +import type { AgentContext } from '../../../agent/context/AgentContext' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' import { DidResolverService } from '../../dids' diff --git a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts index c15302a7a8..042550fd8e 100644 --- a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts +++ b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts @@ -1,10 +1,10 @@ -import type { JsonObject } from '../../../types' -import type { SingleOrArray } from '../../../utils/type' -import type { ProofPurpose } from '../proof-purposes/ProofPurpose' import type { W3cCredential } from './credential/W3cCredential' import type { W3cVerifiableCredential } from './credential/W3cVerifiableCredential' import type { W3cPresentation } from './presentation/W3cPresentation' import type { W3cVerifiablePresentation } from './presentation/W3cVerifiablePresentation' +import type { JsonObject } from '../../../types' +import type { SingleOrArray } from '../../../utils/type' +import type { ProofPurpose } from '../proof-purposes/ProofPurpose' export interface SignCredentialOptions { credential: W3cCredential diff --git a/packages/core/src/modules/vc/models/credential/W3cCredential.ts b/packages/core/src/modules/vc/models/credential/W3cCredential.ts index beed50d700..ca5a1398bc 100644 --- a/packages/core/src/modules/vc/models/credential/W3cCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cCredential.ts @@ -1,6 +1,6 @@ -import type { JsonObject } from '../../../../types' import type { CredentialSubjectOptions } from './CredentialSubject' import type { IssuerOptions } from './Issuer' +import type { JsonObject } from '../../../../types' import type { ValidationOptions } from 'class-validator' import { Expose, Type } from 'class-transformer' diff --git a/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts b/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts index 4fbce4e41e..67c09e1ac2 100644 --- a/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts @@ -1,5 +1,5 @@ -import type { LinkedDataProofOptions } from '../LinkedDataProof' import type { W3cCredentialOptions } from './W3cCredential' +import type { LinkedDataProofOptions } from '../LinkedDataProof' import { instanceToPlain, plainToInstance, Transform, TransformationType } from 'class-transformer' diff --git a/packages/core/src/modules/vc/models/credential/W3cVerifyCredentialResult.ts b/packages/core/src/modules/vc/models/credential/W3cVerifyCredentialResult.ts index 9f8880467a..aaecf7c931 100644 --- a/packages/core/src/modules/vc/models/credential/W3cVerifyCredentialResult.ts +++ b/packages/core/src/modules/vc/models/credential/W3cVerifyCredentialResult.ts @@ -1,5 +1,5 @@ -import type { JsonObject } from '../../../../types' import type { W3cVerifiableCredential } from './W3cVerifiableCredential' +import type { JsonObject } from '../../../../types' export interface VerifyCredentialResult { credential: W3cVerifiableCredential diff --git a/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts b/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts index fa1fd6001a..67a106ee59 100644 --- a/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts +++ b/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts @@ -1,5 +1,5 @@ -import type { LinkedDataProofOptions } from '../LinkedDataProof' import type { W3cPresentationOptions } from './W3cPresentation' +import type { LinkedDataProofOptions } from '../LinkedDataProof' import { SingleOrArray } from '../../../../utils/type' import { IsInstanceOrArrayOfInstances } from '../../../../utils/validators' diff --git a/packages/core/src/plugins/Module.ts b/packages/core/src/plugins/Module.ts index 8170b159de..93196d71cf 100644 --- a/packages/core/src/plugins/Module.ts +++ b/packages/core/src/plugins/Module.ts @@ -1,6 +1,6 @@ +import type { DependencyManager } from './DependencyManager' import type { FeatureRegistry } from '../agent/FeatureRegistry' import type { Constructor } from '../utils/mixins' -import type { DependencyManager } from './DependencyManager' export interface Module { api?: Constructor diff --git a/packages/core/src/storage/InMemoryMessageRepository.ts b/packages/core/src/storage/InMemoryMessageRepository.ts index a1f01b6515..cf98440a7d 100644 --- a/packages/core/src/storage/InMemoryMessageRepository.ts +++ b/packages/core/src/storage/InMemoryMessageRepository.ts @@ -1,5 +1,5 @@ -import type { EncryptedMessage } from '../types' import type { MessageRepository } from './MessageRepository' +import type { EncryptedMessage } from '../types' import { InjectionSymbols } from '../constants' import { Logger } from '../logger' diff --git a/packages/core/src/storage/IndyStorageService.ts b/packages/core/src/storage/IndyStorageService.ts index a66cb579fd..452ef555c1 100644 --- a/packages/core/src/storage/IndyStorageService.ts +++ b/packages/core/src/storage/IndyStorageService.ts @@ -1,7 +1,7 @@ -import type { AgentContext } from '../agent' -import type { IndyWallet } from '../wallet/IndyWallet' import type { BaseRecord, TagsBase } from './BaseRecord' import type { BaseRecordConstructor, Query, StorageService } from './StorageService' +import type { AgentContext } from '../agent' +import type { IndyWallet } from '../wallet/IndyWallet' import type { default as Indy, WalletQuery, WalletRecord, WalletSearchOptions } from 'indy-sdk' import { AgentDependencies } from '../agent/AgentDependencies' @@ -14,7 +14,8 @@ import { isBoolean } from '../utils/type' import { assertIndyWallet } from '../wallet/util/assertIndyWallet' @injectable() -export class IndyStorageService implements StorageService { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export class IndyStorageService> implements StorageService { private indy: typeof Indy private static DEFAULT_QUERY_OPTIONS = { diff --git a/packages/core/src/storage/Repository.ts b/packages/core/src/storage/Repository.ts index 674d2e3e7a..30cc980d7a 100644 --- a/packages/core/src/storage/Repository.ts +++ b/packages/core/src/storage/Repository.ts @@ -1,8 +1,8 @@ -import type { AgentContext } from '../agent' -import type { EventEmitter } from '../agent/EventEmitter' import type { BaseRecord } from './BaseRecord' import type { RecordSavedEvent, RecordUpdatedEvent, RecordDeletedEvent } from './RepositoryEvents' import type { BaseRecordConstructor, Query, StorageService } from './StorageService' +import type { AgentContext } from '../agent' +import type { EventEmitter } from '../agent/EventEmitter' import { RecordDuplicateError, RecordNotFoundError } from '../error' import { JsonTransformer } from '../utils/JsonTransformer' diff --git a/packages/core/src/storage/RepositoryEvents.ts b/packages/core/src/storage/RepositoryEvents.ts index cf9a0d3157..3e3b1e2952 100644 --- a/packages/core/src/storage/RepositoryEvents.ts +++ b/packages/core/src/storage/RepositoryEvents.ts @@ -1,5 +1,5 @@ -import type { BaseEvent } from '../agent/Events' import type { BaseRecord } from './BaseRecord' +import type { BaseEvent } from '../agent/Events' export enum RepositoryEventTypes { RecordSaved = 'RecordSaved', diff --git a/packages/core/src/storage/StorageService.ts b/packages/core/src/storage/StorageService.ts index 6ea701df56..180af06bd4 100644 --- a/packages/core/src/storage/StorageService.ts +++ b/packages/core/src/storage/StorageService.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import type { BaseRecord, TagsBase } from './BaseRecord' import type { AgentContext } from '../agent' import type { Constructor } from '../utils/mixins' -import type { BaseRecord, TagsBase } from './BaseRecord' // https://stackoverflow.com/questions/51954558/how-can-i-remove-a-wider-type-from-a-union-type-without-removing-its-subtypes-in/51955852#51955852 export type SimpleQuery = Partial> & TagsBase diff --git a/packages/core/src/storage/didcomm/DidCommMessageRecord.ts b/packages/core/src/storage/didcomm/DidCommMessageRecord.ts index e7c28a84a8..9f234bb15b 100644 --- a/packages/core/src/storage/didcomm/DidCommMessageRecord.ts +++ b/packages/core/src/storage/didcomm/DidCommMessageRecord.ts @@ -1,6 +1,6 @@ +import type { DidCommMessageRole } from './DidCommMessageRole' import type { ConstructableAgentMessage } from '../../agent/AgentMessage' import type { JsonObject } from '../../types' -import type { DidCommMessageRole } from './DidCommMessageRole' import { AriesFrameworkError } from '../../error' import { JsonTransformer } from '../../utils/JsonTransformer' diff --git a/packages/core/src/storage/didcomm/DidCommMessageRepository.ts b/packages/core/src/storage/didcomm/DidCommMessageRepository.ts index db2db2d04f..cffa511e3a 100644 --- a/packages/core/src/storage/didcomm/DidCommMessageRepository.ts +++ b/packages/core/src/storage/didcomm/DidCommMessageRepository.ts @@ -1,7 +1,7 @@ +import type { DidCommMessageRole } from './DidCommMessageRole' import type { AgentContext } from '../../agent' import type { AgentMessage, ConstructableAgentMessage } from '../../agent/AgentMessage' import type { JsonObject } from '../../types' -import type { DidCommMessageRole } from './DidCommMessageRole' import { EventEmitter } from '../../agent/EventEmitter' import { InjectionSymbols } from '../../constants' diff --git a/packages/core/src/storage/migration/StorageUpdateService.ts b/packages/core/src/storage/migration/StorageUpdateService.ts index eef16af7ff..b5b196406d 100644 --- a/packages/core/src/storage/migration/StorageUpdateService.ts +++ b/packages/core/src/storage/migration/StorageUpdateService.ts @@ -1,6 +1,6 @@ +import type { UpdateToVersion } from './updates' import type { AgentContext } from '../../agent' import type { VersionString } from '../../utils/version' -import type { UpdateToVersion } from './updates' import { InjectionSymbols } from '../../constants' import { Logger } from '../../logger' diff --git a/packages/core/src/storage/migration/UpdateAssistant.ts b/packages/core/src/storage/migration/UpdateAssistant.ts index 756da0e093..ad34a75a28 100644 --- a/packages/core/src/storage/migration/UpdateAssistant.ts +++ b/packages/core/src/storage/migration/UpdateAssistant.ts @@ -1,6 +1,6 @@ +import type { UpdateConfig, UpdateToVersion } from './updates' import type { BaseAgent } from '../../agent/BaseAgent' import type { FileSystem } from '../FileSystem' -import type { UpdateConfig, UpdateToVersion } from './updates' import { InjectionSymbols } from '../../constants' import { AriesFrameworkError } from '../../error' diff --git a/packages/core/src/storage/migration/updates.ts b/packages/core/src/storage/migration/updates.ts index 08c890fdd0..294975e0f4 100644 --- a/packages/core/src/storage/migration/updates.ts +++ b/packages/core/src/storage/migration/updates.ts @@ -1,6 +1,6 @@ +import type { V0_1ToV0_2UpdateConfig } from './updates/0.1-0.2' import type { BaseAgent } from '../../agent/BaseAgent' import type { VersionString } from '../../utils/version' -import type { V0_1ToV0_2UpdateConfig } from './updates/0.1-0.2' import { updateV0_1ToV0_2 } from './updates/0.1-0.2' import { updateV0_2ToV0_3 } from './updates/0.2-0.3' @@ -49,4 +49,4 @@ export const CURRENT_FRAMEWORK_STORAGE_VERSION = supportedUpdates[supportedUpdat // eslint-disable-next-line @typescript-eslint/no-unused-vars type LastItem = T extends readonly [...infer _, infer U] ? U : T[0] | undefined -export type UpdateToVersion = typeof supportedUpdates[number]['toVersion'] +export type UpdateToVersion = (typeof supportedUpdates)[number]['toVersion'] diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts b/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts index 7d41c3366d..c131646507 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts @@ -1,6 +1,6 @@ +import type { V0_1ToV0_2UpdateConfig } from './index' import type { BaseAgent } from '../../../../agent/BaseAgent' import type { MediationRecord } from '../../../../modules/routing' -import type { V0_1ToV0_2UpdateConfig } from './index' import { MediationRepository, MediationRole } from '../../../../modules/routing' diff --git a/packages/core/src/transport/HttpOutboundTransport.ts b/packages/core/src/transport/HttpOutboundTransport.ts index a9ff5c28d6..8ff21d71da 100644 --- a/packages/core/src/transport/HttpOutboundTransport.ts +++ b/packages/core/src/transport/HttpOutboundTransport.ts @@ -1,8 +1,8 @@ +import type { OutboundTransport } from './OutboundTransport' import type { Agent } from '../agent/Agent' import type { AgentMessageReceivedEvent } from '../agent/Events' import type { Logger } from '../logger' import type { OutboundPackage } from '../types' -import type { OutboundTransport } from './OutboundTransport' import type fetch from 'node-fetch' import { AbortController } from 'abort-controller' diff --git a/packages/core/src/transport/WsOutboundTransport.ts b/packages/core/src/transport/WsOutboundTransport.ts index 8e97107141..1c248036da 100644 --- a/packages/core/src/transport/WsOutboundTransport.ts +++ b/packages/core/src/transport/WsOutboundTransport.ts @@ -1,9 +1,9 @@ +import type { OutboundTransport } from './OutboundTransport' +import type { OutboundWebSocketClosedEvent, OutboundWebSocketOpenedEvent } from './TransportEventTypes' import type { Agent } from '../agent/Agent' import type { AgentMessageReceivedEvent } from '../agent/Events' import type { Logger } from '../logger' import type { OutboundPackage } from '../types' -import type { OutboundTransport } from './OutboundTransport' -import type { OutboundWebSocketClosedEvent, OutboundWebSocketOpenedEvent } from './TransportEventTypes' import type WebSocket from 'ws' import { AgentEventTypes } from '../agent/Events' diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 36450e0c3c..d2f5a21c8f 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -176,7 +176,6 @@ export interface JsonObject { [property: string]: JsonValue } -// Flatten an array of arrays /** * Flatten an array of arrays * @example diff --git a/packages/core/src/utils/attachment.ts b/packages/core/src/utils/attachment.ts index 5831e7a37c..a50441abb5 100644 --- a/packages/core/src/utils/attachment.ts +++ b/packages/core/src/utils/attachment.ts @@ -1,5 +1,5 @@ -import type { Attachment } from '../decorators/attachment/Attachment' import type { BaseName } from './MultiBaseEncoder' +import type { Attachment } from '../decorators/attachment/Attachment' import { AriesFrameworkError } from '../error/AriesFrameworkError' diff --git a/packages/core/src/utils/indyError.ts b/packages/core/src/utils/indyError.ts index c46ebef13e..0472ac0f04 100644 --- a/packages/core/src/utils/indyError.ts +++ b/packages/core/src/utils/indyError.ts @@ -60,7 +60,7 @@ export const indyErrors = { 706: 'TransactionNotAllowedError', } as const -type IndyErrorValues = typeof indyErrors[keyof typeof indyErrors] +type IndyErrorValues = (typeof indyErrors)[keyof typeof indyErrors] export interface IndyError { name: 'IndyError' diff --git a/packages/core/src/utils/messageType.ts b/packages/core/src/utils/messageType.ts index acac87978e..7d7232d330 100644 --- a/packages/core/src/utils/messageType.ts +++ b/packages/core/src/utils/messageType.ts @@ -1,5 +1,5 @@ -import type { PlaintextMessage } from '../types' import type { VersionString } from './version' +import type { PlaintextMessage } from '../types' import type { ValidationOptions, ValidationArguments } from 'class-validator' import { ValidateBy, buildMessage } from 'class-validator' diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts index 3e279ff2aa..0bef6447d6 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/core/src/wallet/IndyWallet.ts @@ -1,12 +1,3 @@ -import type { KeyPair } from '../crypto/signing-provider/SigningProvider' -import type { - EncryptedMessage, - KeyDerivationMethod, - WalletConfig, - WalletConfigRekey, - WalletExportImportConfig, -} from '../types' -import type { Buffer } from '../utils/buffer' import type { WalletCreateKeyOptions, DidConfig, @@ -16,6 +7,15 @@ import type { WalletVerifyOptions, Wallet, } from './Wallet' +import type { KeyPair } from '../crypto/signing-provider/SigningProvider' +import type { + EncryptedMessage, + KeyDerivationMethod, + WalletConfig, + WalletConfigRekey, + WalletExportImportConfig, +} from '../types' +import type { Buffer } from '../utils/buffer' import type { default as Indy, WalletStorageConfig } from 'indy-sdk' import { inject, injectable } from 'tsyringe' diff --git a/packages/core/src/wallet/WalletApi.ts b/packages/core/src/wallet/WalletApi.ts index 548df623cf..144e9722c3 100644 --- a/packages/core/src/wallet/WalletApi.ts +++ b/packages/core/src/wallet/WalletApi.ts @@ -1,5 +1,5 @@ -import type { WalletConfig, WalletConfigRekey, WalletExportImportConfig } from '../types' import type { Wallet } from './Wallet' +import type { WalletConfig, WalletConfigRekey, WalletExportImportConfig } from '../types' import { AgentContext } from '../agent' import { InjectionSymbols } from '../constants' diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index 96f7715bd6..4df883e972 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' -import type { CreateOfferOptions, V1CredentialProtocol } from '../src/modules/credentials' +import type { CreateOfferOptions, DefaultCredentialProtocols } from '../src/modules/credentials' import type { AgentMessage, AgentMessageReceivedEvent } from '@aries-framework/core' import { Subject } from 'rxjs' @@ -10,6 +10,15 @@ import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutbou import { Agent } from '../src/agent/Agent' import { Key } from '../src/crypto' import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' + +import { + AgentEventTypes, + AriesFrameworkError, + AutoAcceptCredential, + CredentialState, + V1CredentialPreview, +} from '@aries-framework/core' + import { OutOfBandDidCommService } from '../src/modules/oob/domain/OutOfBandDidCommService' import { OutOfBandEventTypes } from '../src/modules/oob/domain/OutOfBandEvents' import { OutOfBandRole } from '../src/modules/oob/domain/OutOfBandRole' @@ -21,14 +30,6 @@ import { JsonEncoder } from '../src/utils' import { TestMessage } from './TestMessage' import { getAgentOptions, prepareForIssuance, waitForCredentialRecord } from './helpers' -import { - AgentEventTypes, - AriesFrameworkError, - AutoAcceptCredential, - CredentialState, - V1CredentialPreview, -} from '@aries-framework/core' - const faberAgentOptions = getAgentOptions('Faber Agent OOB', { endpoints: ['rxjs:faber'], }) @@ -57,7 +58,7 @@ describe('out of band', () => { let faberAgent: Agent let aliceAgent: Agent - let credentialTemplate: CreateOfferOptions<[V1CredentialProtocol]> + let credentialTemplate: CreateOfferOptions beforeAll(async () => { const faberMessages = new Subject() diff --git a/packages/indy-sdk/package.json b/packages/indy-sdk/package.json index 4c19732005..02dec486ae 100644 --- a/packages/indy-sdk/package.json +++ b/packages/indy-sdk/package.json @@ -19,7 +19,7 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" @@ -34,7 +34,7 @@ "tsyringe": "^4.7.0" }, "devDependencies": { - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "rimraf": "^4.0.7", + "typescript": "~4.9.4" } } diff --git a/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts b/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts index 9f94c7326c..8553af9295 100644 --- a/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts +++ b/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts @@ -1,5 +1,5 @@ -import type { IndySdkPool } from '../ledger' import type { IndyEndpointAttrib } from './didSovUtil' +import type { IndySdkPool } from '../ledger' import type { AgentContext, DidRegistrar, diff --git a/packages/indy-sdk/src/error/indyError.ts b/packages/indy-sdk/src/error/indyError.ts index 5d67cfdbf1..c5d23f6093 100644 --- a/packages/indy-sdk/src/error/indyError.ts +++ b/packages/indy-sdk/src/error/indyError.ts @@ -60,7 +60,7 @@ export const indyErrors = { 706: 'TransactionNotAllowedError', } as const -type IndyErrorValues = typeof indyErrors[keyof typeof indyErrors] +type IndyErrorValues = (typeof indyErrors)[keyof typeof indyErrors] export interface IndyError { name: 'IndyError' diff --git a/packages/node/package.json b/packages/node/package.json index 540489b8b6..09f32cc942 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -22,13 +22,14 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" }, "dependencies": { "@aries-framework/core": "0.3.2", + "@types/express": "^4.17.15", "express": "^4.17.1", "ffi-napi": "^4.0.3", "indy-sdk": "^1.16.0-dev-1636", @@ -37,13 +38,12 @@ "ws": "^7.5.3" }, "devDependencies": { - "@types/express": "^4.17.13", "@types/ffi-napi": "^4.0.5", - "@types/node": "^15.14.4", + "@types/node": "^16.11.7", "@types/node-fetch": "^2.5.10", "@types/ref-napi": "^3.0.4", "@types/ws": "^7.4.6", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "rimraf": "^4.0.7", + "typescript": "~4.9.4" } } diff --git a/packages/openid4vc-client/package.json b/packages/openid4vc-client/package.json index cac010d054..7b251a86b3 100644 --- a/packages/openid4vc-client/package.json +++ b/packages/openid4vc-client/package.json @@ -19,7 +19,7 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" @@ -34,7 +34,7 @@ "devDependencies": { "@aries-framework/node": "0.3.2", "reflect-metadata": "^0.1.13", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "rimraf": "^4.0.7", + "typescript": "~4.9.4" } } diff --git a/packages/question-answer/package.json b/packages/question-answer/package.json index 01a769bea8..547a9a3f2c 100644 --- a/packages/question-answer/package.json +++ b/packages/question-answer/package.json @@ -18,7 +18,7 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" @@ -32,7 +32,7 @@ "devDependencies": { "@aries-framework/node": "0.3.2", "reflect-metadata": "^0.1.13", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "rimraf": "^4.0.7", + "typescript": "~4.9.4" } } diff --git a/packages/question-answer/tests/question-answer.e2e.test.ts b/packages/question-answer/tests/question-answer.e2e.test.ts index e8d6d5a0c3..c2c35d8c2b 100644 --- a/packages/question-answer/tests/question-answer.e2e.test.ts +++ b/packages/question-answer/tests/question-answer.e2e.test.ts @@ -9,10 +9,10 @@ import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutbou import { getAgentOptions, makeConnection } from '../../core/tests/helpers' import testLogger from '../../core/tests/logger' -import { waitForQuestionAnswerRecord } from './helpers' - import { QuestionAnswerModule, QuestionAnswerRole, QuestionAnswerState } from '@aries-framework/question-answer' +import { waitForQuestionAnswerRecord } from './helpers' + const bobAgentOptions = getAgentOptions( 'Bob Question Answer', { diff --git a/packages/react-native/package.json b/packages/react-native/package.json index dab91dd4ec..e2b1b02691 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -18,7 +18,7 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" @@ -36,8 +36,8 @@ "react-native": "0.64.2", "react-native-fs": "^2.18.0", "react-native-get-random-values": "^1.7.0", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "rimraf": "^4.0.7", + "typescript": "~4.9.4" }, "peerDependencies": { "indy-sdk-react-native": "^0.3.0", diff --git a/packages/tenants/package.json b/packages/tenants/package.json index 191baf900f..2c4ad8faa9 100644 --- a/packages/tenants/package.json +++ b/packages/tenants/package.json @@ -18,7 +18,7 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" @@ -30,7 +30,7 @@ "devDependencies": { "@aries-framework/node": "0.3.2", "reflect-metadata": "^0.1.13", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "rimraf": "^4.0.7", + "typescript": "~4.9.4" } } diff --git a/tests/e2e-subject.test.ts b/tests/e2e-subject.test.ts index 1b6cdb09cc..6f550435fc 100644 --- a/tests/e2e-subject.test.ts +++ b/tests/e2e-subject.test.ts @@ -6,10 +6,11 @@ import { getAgentOptions } from '../packages/core/tests/helpers' import { e2eTest } from './e2e-test' import { SubjectInboundTransport } from './transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from './transport/SubjectOutboundTransport' import { Agent, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' +import { SubjectOutboundTransport } from './transport/SubjectOutboundTransport' + const recipientAgentOptions = getAgentOptions('E2E Subject Recipient', { autoAcceptCredentials: AutoAcceptCredential.ContentApproved, mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, diff --git a/tsconfig.build.json b/tsconfig.build.json index 3ff691c0e8..45d3c20c52 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -12,7 +12,9 @@ "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "experimentalDecorators": true, - "emitDecoratorMetadata": true + "emitDecoratorMetadata": true, + // TODO: we should update code to assume errors are of type 'unknown' + "useUnknownInCatchVariables": false }, "exclude": ["node_modules", "build", "**/*.test.ts", "**/__tests__/*.ts", "**/__mocks__/*.ts", "**/build/**"] } diff --git a/yarn.lock b/yarn.lock index 5fb0819bd7..359ab25c51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,6 +10,50 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" +"@aries-framework/core@file:packages/core": + version "0.3.2" + dependencies: + "@digitalcredentials/jsonld" "^5.2.1" + "@digitalcredentials/jsonld-signatures" "^9.3.1" + "@digitalcredentials/vc" "^1.1.2" + "@multiformats/base-x" "^4.0.1" + "@stablelib/ed25519" "^1.0.2" + "@stablelib/random" "^1.0.1" + "@stablelib/sha256" "^1.0.1" + "@types/indy-sdk" "1.16.24" + "@types/node-fetch" "^2.5.10" + "@types/ws" "^7.4.6" + abort-controller "^3.0.0" + bn.js "^5.2.0" + borc "^3.0.0" + buffer "^6.0.3" + class-transformer "0.5.1" + class-validator "0.13.1" + did-resolver "^3.1.3" + lru_map "^0.4.1" + luxon "^1.27.0" + make-error "^1.3.6" + object-inspect "^1.10.3" + query-string "^7.0.1" + reflect-metadata "^0.1.13" + rxjs "^7.2.0" + tsyringe "^4.7.0" + uuid "^8.3.2" + varint "^6.0.0" + web-did-resolver "^2.0.8" + +"@aries-framework/node@file:packages/node": + version "0.3.2" + dependencies: + "@aries-framework/core" "0.3.2" + "@types/express" "^4.17.15" + express "^4.17.1" + ffi-napi "^4.0.3" + indy-sdk "^1.16.0-dev-1636" + node-fetch "^2.6.1" + ref-napi "^3.0.3" + ws "^7.5.3" + "@azure/core-asynciterator-polyfill@^1.0.0": version "1.0.2" resolved "https://registry.yarnpkg.com/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.2.tgz#0dd3849fb8d97f062a39db0e5cadc9ffaf861fec" @@ -29,38 +73,38 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30" - integrity sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5": + version "7.20.10" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.10.tgz#9d92fa81b87542fff50e848ed585b4212c1d34ec" + integrity sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg== "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.2.tgz#8dc9b1620a673f92d3624bd926dc49a52cf25b92" - integrity sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g== + version "7.20.12" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d" + integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.2" - "@babel/helper-compilation-targets" "^7.20.0" - "@babel/helper-module-transforms" "^7.20.2" - "@babel/helpers" "^7.20.1" - "@babel/parser" "^7.20.2" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.1" - "@babel/types" "^7.20.2" + "@babel/generator" "^7.20.7" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helpers" "^7.20.7" + "@babel/parser" "^7.20.7" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.12" + "@babel/types" "^7.20.7" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.1" + json5 "^2.2.2" semver "^6.3.0" -"@babel/generator@^7.20.1", "@babel/generator@^7.20.2", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": - version "7.20.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.4.tgz#4d9f8f0c30be75fd90a0562099a26e5839602ab8" - integrity sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA== +"@babel/generator@^7.20.7", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.7.tgz#f8ef57c8242665c5929fe2e8d82ba75460187b4a" + integrity sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw== dependencies: - "@babel/types" "^7.20.2" + "@babel/types" "^7.20.7" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" @@ -79,36 +123,38 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a" - integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb" + integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== dependencies: - "@babel/compat-data" "^7.20.0" + "@babel/compat-data" "^7.20.5" "@babel/helper-validator-option" "^7.18.6" browserslist "^4.21.3" + lru-cache "^5.1.1" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.2.tgz#3c08a5b5417c7f07b5cf3dfb6dc79cbec682e8c2" - integrity sha512-k22GoYRAHPYr9I+Gvy2ZQlAe5mGy8BqWst2wRt8cwIufWTxrsVshhIBvYNqC80N0GSFWTsqRVexOtfzlgOEDvA== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.7": + version "7.20.12" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.12.tgz#4349b928e79be05ed2d1643b20b99bb87c503819" + integrity sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" - "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.20.7" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.19.1" + "@babel/helper-replace-supers" "^7.20.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-create-regexp-features-plugin@^7.18.6": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" - integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz#5ea79b59962a09ec2acf20a963a01ab4d076ccca" + integrity sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - regexpu-core "^5.1.0" + regexpu-core "^5.2.1" "@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" @@ -149,12 +195,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-member-expression-to-functions@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" - integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== +"@babel/helper-member-expression-to-functions@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz#a6f26e919582275a93c3aa6594756d71b0bb7f05" + integrity sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw== dependencies: - "@babel/types" "^7.18.9" + "@babel/types" "^7.20.7" "@babel/helper-module-imports@^7.18.6": version "7.18.6" @@ -163,19 +209,19 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz#ac53da669501edd37e658602a21ba14c08748712" - integrity sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA== +"@babel/helper-module-transforms@^7.20.11": + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz#df4c7af713c557938c50ea3ad0117a7944b2f1b0" + integrity sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" "@babel/helper-simple-access" "^7.20.2" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.1" - "@babel/types" "^7.20.2" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.10" + "@babel/types" "^7.20.7" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -189,25 +235,26 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" - integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331" + integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A== dependencies: "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.20.7" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.19.1" - "@babel/types" "^7.19.0" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.7" + "@babel/types" "^7.20.7" -"@babel/helper-simple-access@^7.19.4", "@babel/helper-simple-access@^7.20.2": +"@babel/helper-simple-access@^7.20.2": version "7.20.2" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== dependencies: "@babel/types" "^7.20.2" -"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": +"@babel/helper-skip-transparent-expression-wrappers@^7.20.0": version "7.20.0" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== @@ -236,14 +283,14 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== -"@babel/helpers@^7.20.1": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.1.tgz#2ab7a0fcb0a03b5bf76629196ed63c2d7311f4c9" - integrity sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg== +"@babel/helpers@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.7.tgz#04502ff0feecc9f20ecfaad120a18f011a8e6dce" + integrity sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA== dependencies: - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.1" - "@babel/types" "^7.20.0" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.7" + "@babel/types" "^7.20.7" "@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": version "7.18.6" @@ -254,10 +301,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.20.1", "@babel/parser@^7.20.2": - version "7.20.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.3.tgz#5358cf62e380cf69efcb87a7bb922ff88bfac6e2" - integrity sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.7.tgz#66fe23b3c8569220817d5feb8b9dcdc95bb4f71b" + integrity sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg== "@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.1.0": version "7.18.6" @@ -284,15 +331,15 @@ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" "@babel/plugin-proposal-object-rest-spread@^7.0.0": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz#a556f59d555f06961df1e572bb5eca864c84022d" - integrity sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" + integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== dependencies: - "@babel/compat-data" "^7.20.1" - "@babel/helper-compilation-targets" "^7.20.0" + "@babel/compat-data" "^7.20.5" + "@babel/helper-compilation-targets" "^7.20.7" "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.20.1" + "@babel/plugin-transform-parameters" "^7.20.7" "@babel/plugin-proposal-optional-catch-binding@^7.0.0": version "7.18.6" @@ -303,12 +350,12 @@ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-proposal-optional-chaining@^7.0.0", "@babel/plugin-proposal-optional-chaining@^7.1.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993" - integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz#49f2b372519ab31728cc14115bb0998b15bfda55" + integrity sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-async-generators@^7.8.4": @@ -431,11 +478,11 @@ "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-arrow-functions@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" - integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz#bea332b0e8b2dab3dafe55a163d8227531ab0551" + integrity sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-block-scoped-functions@^7.0.0": version "7.18.6" @@ -445,38 +492,39 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-block-scoping@^7.0.0": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz#f59b1767e6385c663fd0bce655db6ca9c8b236ed" - integrity sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ== + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.11.tgz#9f5a3424bd112a3f32fe0cf9364fbb155cff262a" + integrity sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw== dependencies: "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-classes@^7.0.0": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz#c0033cf1916ccf78202d04be4281d161f6709bb2" - integrity sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz#f438216f094f6bb31dc266ebfab8ff05aecad073" + integrity sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-compilation-targets" "^7.20.7" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" "@babel/helper-optimise-call-expression" "^7.18.6" "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-replace-supers" "^7.19.1" + "@babel/helper-replace-supers" "^7.20.7" "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" "@babel/plugin-transform-computed-properties@^7.0.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" - integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz#704cc2fd155d1c996551db8276d55b9d46e4d0aa" + integrity sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/template" "^7.20.7" "@babel/plugin-transform-destructuring@^7.0.0": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz#c23741cfa44ddd35f5e53896e88c75331b8b2792" - integrity sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz#8bda578f71620c7de7c93af590154ba331415454" + integrity sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA== dependencies: "@babel/helper-plugin-utils" "^7.20.2" @@ -527,13 +575,13 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.1.0": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz#25b32feef24df8038fc1ec56038917eacb0b730c" - integrity sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ== + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz#8cb23010869bf7669fd4b3098598b6b2be6dc607" + integrity sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw== dependencies: - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-simple-access" "^7.19.4" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-simple-access" "^7.20.2" "@babel/plugin-transform-object-assign@^7.0.0": version "7.18.6" @@ -550,10 +598,10 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-replace-supers" "^7.18.6" -"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.20.1": - version "7.20.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz#7b3468d70c3c5b62e46be0a47b6045d8590fb748" - integrity sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA== +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz#0ee349e9d1bc96e78e3b37a7af423a4078a7083f" + integrity sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA== dependencies: "@babel/helper-plugin-utils" "^7.20.2" @@ -586,23 +634,23 @@ "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-react-jsx@^7.0.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz#b3cbb7c3a00b92ec8ae1027910e331ba5c500eb9" - integrity sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.20.7.tgz#025d85a1935fd7e19dfdcb1b1d4df34d4da484f7" + integrity sha512-Tfq7qqD+tRj3EoDhY00nn2uP2hsRxgYGi5mLQ5TimKav0a9Lrpd4deE+fcLXU8zFYRjlKPHZhpCvfEA6qnBxqQ== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-jsx" "^7.18.6" - "@babel/types" "^7.19.0" + "@babel/types" "^7.20.7" "@babel/plugin-transform-regenerator@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73" - integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz#57cda588c7ffb7f4f8483cc83bdcea02a907f04d" + integrity sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - regenerator-transform "^0.15.0" + "@babel/helper-plugin-utils" "^7.20.2" + regenerator-transform "^0.15.1" "@babel/plugin-transform-runtime@^7.0.0": version "7.19.6" @@ -624,12 +672,12 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-spread@^7.0.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" - integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz#c2d83e0b99d3bf83e07b11995ee24bf7ca09401e" + integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/plugin-transform-sticky-regex@^7.0.0": version "7.18.6" @@ -646,11 +694,11 @@ "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-typescript@^7.18.6", "@babel/plugin-transform-typescript@^7.5.0": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.2.tgz#91515527b376fc122ba83b13d70b01af8fe98f3f" - integrity sha512-jvS+ngBfrnTUBfOQq8NfGnSbF9BrqlR6hjJ2yVxMkmO5nL/cdifNbI30EfjRlN4g5wYWNnMPyj5Sa6R1pbLeag== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.7.tgz#673f49499cd810ae32a1ea5f3f8fab370987e055" + integrity sha512-m3wVKEvf6SoszD8pu4NZz3PvfKRCMgk6D6d0Qi9hNnlM5M6CFS92EgF4EiHVLKbU0r/r7ty1hg7NPZwE7WRbYw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.20.2" + "@babel/helper-create-class-features-plugin" "^7.20.7" "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-typescript" "^7.20.0" @@ -692,41 +740,41 @@ source-map-support "^0.5.16" "@babel/runtime@^7.8.4": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9" - integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd" + integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ== dependencies: - regenerator-runtime "^0.13.10" + regenerator-runtime "^0.13.11" -"@babel/template@^7.0.0", "@babel/template@^7.18.10", "@babel/template@^7.3.3": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" - integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== +"@babel/template@^7.0.0", "@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" + integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.10" - "@babel/types" "^7.18.10" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.7.2": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.1.tgz#9b15ccbf882f6d107eeeecf263fbcdd208777ec8" - integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.7", "@babel/traverse@^7.7.2": + version "7.20.12" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.12.tgz#7f0f787b3a67ca4475adef1f56cb94f6abd4a4b5" + integrity sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.1" + "@babel/generator" "^7.20.7" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.20.1" - "@babel/types" "^7.20.0" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.2.tgz#67ac09266606190f496322dbaff360fdaa5e7842" - integrity sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog== +"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f" + integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== dependencies: "@babel/helper-string-parser" "^7.19.4" "@babel/helper-validator-identifier" "^7.19.1" @@ -2205,9 +2253,9 @@ "@hapi/hoek" "^9.0.0" "@sideway/formula@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" - integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== "@sideway/pinpoint@^2.0.0": version "2.0.0" @@ -2215,9 +2263,9 @@ integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== "@sinonjs/commons@^1.7.0": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.5.tgz#e280c94c95f206dcfd5aca00a43f2156b758c764" - integrity sha512-rTpCA0wG1wUxglBSFdMMY0oTrKYvgf4fNgv/sXbfCVAdf+FnPBdKJR/7XbpTCwbCrvCbdPYnlWaUUYz4V2fPDA== + version "1.8.6" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9" + integrity sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ== dependencies: type-detect "4.0.8" @@ -2367,9 +2415,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.2.tgz#235bf339d17185bdec25e024ca19cce257cc7309" - integrity sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg== + version "7.18.3" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.3.tgz#dfc508a85781e5698d5b33443416b6268c4b3e8d" + integrity sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w== dependencies: "@babel/types" "^7.3.0" @@ -2396,9 +2444,11 @@ "@types/node" "*" "@types/cors@^2.8.10": - version "2.8.12" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" - integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== + version "2.8.13" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.13.tgz#b8ade22ba455a1b8cb3b5d3f35910fd204f84f94" + integrity sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA== + dependencies: + "@types/node" "*" "@types/eslint@^7.2.13": version "7.29.0" @@ -2418,29 +2468,29 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== -"@types/express-serve-static-core@^4.17.18": - version "4.17.31" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz#a1139efeab4e7323834bb0226e62ac019f474b2f" - integrity sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q== +"@types/express-serve-static-core@^4.17.31": + version "4.17.32" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" + integrity sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" -"@types/express@^4.17.13": - version "4.17.14" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" - integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg== +"@types/express@^4.17.13", "@types/express@^4.17.15": + version "4.17.15" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.15.tgz#9290e983ec8b054b65a5abccb610411953d417ff" + integrity sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ== dependencies: "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" + "@types/express-serve-static-core" "^4.17.31" "@types/qs" "*" "@types/serve-static" "*" "@types/ffi-napi@^4.0.5": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@types/ffi-napi/-/ffi-napi-4.0.6.tgz#cd1c65cc9e701de664e640ccb17a2e823a674d44" - integrity sha512-yrBtqeVD1aeVo271jXVEo3iAtbzSGVGRssJv9W9JlUfg5Z5FgHJx2MV88GRwVATu/XWg6zyenW/cb1MNAuOtaQ== + version "4.0.7" + resolved "https://registry.yarnpkg.com/@types/ffi-napi/-/ffi-napi-4.0.7.tgz#b3a9beeae160c74adca801ca1c9defb1ec0a1a32" + integrity sha512-2CvLfgxCUUSj7qVab6/uFLyVpgVd2gEV4H/TQEHHn6kZTV8iTesz9uo0bckhwzsh71atutOv8P3JmvRX2ZvpZg== dependencies: "@types/node" "*" "@types/ref-napi" "*" @@ -2452,9 +2502,9 @@ integrity sha512-0sMBeFoqdGgdXoR/hgKYSWMpFufSpToosNsI2VgmkPqZJgeEXsXNu2hGr0FN401dBro2tNO5y2D6uw3UxVaxbg== "@types/graceful-fs@^4.1.2": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" - integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" + integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== dependencies: "@types/node" "*" @@ -2499,7 +2549,7 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" -"@types/json-schema@*", "@types/json-schema@^7.0.7": +"@types/json-schema@*", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== @@ -2537,10 +2587,10 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@^15.14.4": - version "15.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa" - integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A== +"@types/node@*", "@types/node@^16.11.7": + version "16.18.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.11.tgz#cbb15c12ca7c16c85a72b6bdc4d4b01151bb3cae" + integrity sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -2558,9 +2608,9 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prettier@^2.1.5": - version "2.7.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e" - integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow== + version "2.7.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" + integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== "@types/prop-types@*": version "15.7.5" @@ -2594,16 +2644,16 @@ csstype "^3.0.2" "@types/ref-napi@*", "@types/ref-napi@^3.0.4": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@types/ref-napi/-/ref-napi-3.0.5.tgz#8db441d381737af5c353d7dd89c7593b5f2080c8" - integrity sha512-u+L/RdwTuJes3pDypOVR/MtcqzoULu8Z8yulP6Tw5z7eXV1ba1llizNVFtI/m2iPfDy/dPPt+3ar1QCgonTzsw== + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/ref-napi/-/ref-napi-3.0.6.tgz#56f95b10e7698dced16e05b2bd10b6a46cf90f20" + integrity sha512-yLbSiZkLQB9Bv6m46+c4Gdv5Xmw34ehdUagQCfc88FvqHLamaGpYInHbFQ3+sawFonAQ0GDysQIEdZmSOmMh3A== dependencies: "@types/node" "*" "@types/ref-struct-di@*": - version "1.1.7" - resolved "https://registry.yarnpkg.com/@types/ref-struct-di/-/ref-struct-di-1.1.7.tgz#85e0149858a81a14f12f15ff31a6dffa42bab2d3" - integrity sha512-nnHR26qrCnQqxwHTv+rqzu/hGgDZl45TUs4bO6ZjpuC8/M2JoXFxk63xrWmAmqsLe55oxOgAWssyr3YHAMY89g== + version "1.1.8" + resolved "https://registry.yarnpkg.com/@types/ref-struct-di/-/ref-struct-di-1.1.8.tgz#df8cbf7b9bbbc03f476dcbe1958f92bf443f17d9" + integrity sha512-t5jwtHlEH6c3rgBRtMQTAtysROr1gWt/ZfcytolK+45dag747fUdgmZy/iQs5q41jinMnr62nxwI0Q8GkdK9TA== dependencies: "@types/ref-napi" "*" @@ -2612,6 +2662,11 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== +"@types/semver@^7.3.12": + version "7.3.13" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" + integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== + "@types/serve-static@*": version "1.15.0" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" @@ -2662,88 +2717,101 @@ integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== "@types/yargs@^15.0.0": - version "15.0.14" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" - integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== + version "15.0.15" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.15.tgz#e609a2b1ef9e05d90489c2f5f45bbfb2be092158" + integrity sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg== dependencies: "@types/yargs-parser" "*" "@types/yargs@^16.0.0": - version "16.0.4" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" - integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== + version "16.0.5" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.5.tgz#12cc86393985735a283e387936398c2f9e5f88e3" + integrity sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ== dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^4.26.1": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" - integrity sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg== +"@typescript-eslint/eslint-plugin@^5.48.1": + version "5.48.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.1.tgz#deee67e399f2cb6b4608c935777110e509d8018c" + integrity sha512-9nY5K1Rp2ppmpb9s9S2aBiF3xo5uExCehMDmYmmFqqyxgenbHJ3qbarcLt4ITgaD6r/2ypdlcFRdcuVPnks+fQ== dependencies: - "@typescript-eslint/experimental-utils" "4.33.0" - "@typescript-eslint/scope-manager" "4.33.0" - debug "^4.3.1" - functional-red-black-tree "^1.0.1" - ignore "^5.1.8" - regexpp "^3.1.0" - semver "^7.3.5" + "@typescript-eslint/scope-manager" "5.48.1" + "@typescript-eslint/type-utils" "5.48.1" + "@typescript-eslint/utils" "5.48.1" + debug "^4.3.4" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + regexpp "^3.2.0" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz#6f2a786a4209fa2222989e9380b5331b2810f7fd" - integrity sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q== +"@typescript-eslint/parser@^5.48.1": + version "5.48.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.48.1.tgz#d0125792dab7e232035434ab8ef0658154db2f10" + integrity sha512-4yg+FJR/V1M9Xoq56SF9Iygqm+r5LMXvheo6DQ7/yUWynQ4YfCRnsKuRgqH4EQ5Ya76rVwlEpw4Xu+TgWQUcdA== dependencies: - "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.33.0" - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/typescript-estree" "4.33.0" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" + "@typescript-eslint/scope-manager" "5.48.1" + "@typescript-eslint/types" "5.48.1" + "@typescript-eslint/typescript-estree" "5.48.1" + debug "^4.3.4" -"@typescript-eslint/parser@^4.26.1": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899" - integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA== - dependencies: - "@typescript-eslint/scope-manager" "4.33.0" - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/typescript-estree" "4.33.0" - debug "^4.3.1" - -"@typescript-eslint/scope-manager@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3" - integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ== - dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" - -"@typescript-eslint/types@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" - integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== - -"@typescript-eslint/typescript-estree@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" - integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== - dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" - debug "^4.3.1" - globby "^11.0.3" - is-glob "^4.0.1" - semver "^7.3.5" +"@typescript-eslint/scope-manager@5.48.1": + version "5.48.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.48.1.tgz#39c71e4de639f5fe08b988005beaaf6d79f9d64d" + integrity sha512-S035ueRrbxRMKvSTv9vJKIWgr86BD8s3RqoRZmsSh/s8HhIs90g6UlK8ZabUSjUZQkhVxt7nmZ63VJ9dcZhtDQ== + dependencies: + "@typescript-eslint/types" "5.48.1" + "@typescript-eslint/visitor-keys" "5.48.1" + +"@typescript-eslint/type-utils@5.48.1": + version "5.48.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.48.1.tgz#5d94ac0c269a81a91ad77c03407cea2caf481412" + integrity sha512-Hyr8HU8Alcuva1ppmqSYtM/Gp0q4JOp1F+/JH5D1IZm/bUBrV0edoewQZiEc1r6I8L4JL21broddxK8HAcZiqQ== + dependencies: + "@typescript-eslint/typescript-estree" "5.48.1" + "@typescript-eslint/utils" "5.48.1" + debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" - integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== +"@typescript-eslint/types@5.48.1": + version "5.48.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.48.1.tgz#efd1913a9aaf67caf8a6e6779fd53e14e8587e14" + integrity sha512-xHyDLU6MSuEEdIlzrrAerCGS3T7AA/L8Hggd0RCYBi0w3JMvGYxlLlXHeg50JI9Tfg5MrtsfuNxbS/3zF1/ATg== + +"@typescript-eslint/typescript-estree@5.48.1": + version "5.48.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.1.tgz#9efa8ee2aa471c6ab62e649f6e64d8d121bc2056" + integrity sha512-Hut+Osk5FYr+sgFh8J/FHjqX6HFcDzTlWLrFqGoK5kVUN3VBHF/QzZmAsIXCQ8T/W9nQNBTqalxi1P3LSqWnRA== dependencies: - "@typescript-eslint/types" "4.33.0" - eslint-visitor-keys "^2.0.0" + "@typescript-eslint/types" "5.48.1" + "@typescript-eslint/visitor-keys" "5.48.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.48.1": + version "5.48.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.48.1.tgz#20f2f4e88e9e2a0961cbebcb47a1f0f7da7ba7f9" + integrity sha512-SmQuSrCGUOdmGMwivW14Z0Lj8dxG1mOFZ7soeJ0TQZEJcs3n5Ndgkg0A4bcMFzBELqLJ6GTHnEU+iIoaD6hFGA== + dependencies: + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.48.1" + "@typescript-eslint/types" "5.48.1" + "@typescript-eslint/typescript-estree" "5.48.1" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.48.1": + version "5.48.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.1.tgz#79fd4fb9996023ef86849bf6f904f33eb6c8fccb" + integrity sha512-Ns0XBwmfuX7ZknznfXozgnydyR8F6ev/KEGePP4i74uL3ArsKbEhJ7raeKr1JSa997DBDwol/4a0Y+At82c9dA== + dependencies: + "@typescript-eslint/types" "5.48.1" + eslint-visitor-keys "^3.3.0" "@unimodules/core@*": version "7.1.2" @@ -2871,9 +2939,9 @@ ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: uri-js "^4.2.2" ajv@^8.0.1: - version "8.11.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.2.tgz#aecb20b50607acf2569b6382167b65a96008bb78" - integrity sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg== + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -3052,7 +3120,7 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== -array-includes@^3.1.4: +array-includes@^3.1.6: version "3.1.6" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== @@ -3083,7 +3151,7 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== -array.prototype.flat@^1.2.5: +array.prototype.flat@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== @@ -3093,6 +3161,16 @@ array.prototype.flat@^1.2.5: es-abstract "^1.20.4" es-shim-unscopables "^1.0.0" +array.prototype.flatmap@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" + integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" + array.prototype.reduce@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz#6b20b0daa9d9734dd6bc7ea66b5bbce395471eac" @@ -3201,15 +3279,20 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== aws4@^1.8.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" - integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + version "1.12.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" + integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== b64-lite@^1.3.1, b64-lite@^1.4.0: version "1.4.0" @@ -3406,9 +3489,9 @@ big-integer@1.6.x: integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== bignumber.js@^9.0.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.0.tgz#8d340146107fe3a6cb8d40699643c302e8773b62" - integrity sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A== + version "9.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" + integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== bindings@^1.3.1: version "1.5.0" @@ -3663,9 +3746,9 @@ camelcase@^6.0.0, camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001400: - version "1.0.30001434" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz#ec1ec1cfb0a93a34a0600d37903853030520a4e5" - integrity sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA== + version "1.0.30001445" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001445.tgz#cf2d4eb93f2bcdf0310de9dd6d18be271bc0b447" + integrity sha512-8sdQIdMztYmzfTMO6KfLny878Ln9c2M0fc7EH60IjlP4Dc4PiCy7K2Vl3ITmWgOyPgVQKa5x+UP/KqFsxj4mBg== canonicalize@^1.0.1: version "1.0.8" @@ -3735,9 +3818,9 @@ ci-info@^2.0.0: integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== ci-info@^3.2.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.7.0.tgz#6d01b3696c59915b6ce057e4aa4adfc2fa25f5ef" - integrity sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog== + version "3.7.1" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.7.1.tgz#708a6cdae38915d597afdf3b145f2f8e1ff55f3f" + integrity sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w== cjs-module-lexer@^1.0.0: version "1.2.2" @@ -4184,9 +4267,9 @@ copy-descriptor@^0.1.0: integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== core-js-compat@^3.25.1: - version "3.26.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.26.1.tgz#0e710b09ebf689d719545ac36e49041850f943df" - integrity sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A== + version "3.27.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.27.1.tgz#b5695eb25c602d72b1d30cbfba3cb7e5e4cf0a67" + integrity sha512-Dg91JFeCDA17FKnneN7oCMz4BkQ4TcffkgHP4OWwp9yx3pi7ubqMDXXSacfNak1PQqjc95skyt+YBLHQJnkJwA== dependencies: browserslist "^4.21.4" @@ -4320,18 +4403,18 @@ dateformat@^3.0.0: integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== dayjs@^1.8.15: - version "1.11.6" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.6.tgz#2e79a226314ec3ec904e3ee1dd5a4f5e5b1c7afb" - integrity sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ== + version "1.11.7" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" + integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -4364,11 +4447,11 @@ decamelize@^1.1.0, decamelize@^1.2.0: integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== decimal.js@^10.2.1: - version "10.4.2" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.2.tgz#0341651d1d997d86065a2ce3a441fbd0d8e8b98e" - integrity sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA== + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== -decode-uri-component@^0.2.0: +decode-uri-component@^0.2.0, decode-uri-component@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== @@ -4672,40 +4755,58 @@ errorhandler@^1.5.0: escape-html "~1.0.3" es-abstract@^1.19.0, es-abstract@^1.20.4: - version "1.20.4" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.4.tgz#1d103f9f8d78d4cf0713edcd6d0ed1a46eed5861" - integrity sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA== + version "1.21.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.1.tgz#e6105a099967c08377830a0c9cb589d570dd86c6" + integrity sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg== dependencies: + available-typed-arrays "^1.0.5" call-bind "^1.0.2" + es-set-tostringtag "^2.0.1" es-to-primitive "^1.2.1" function-bind "^1.1.1" function.prototype.name "^1.1.5" get-intrinsic "^1.1.3" get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" has "^1.0.3" has-property-descriptors "^1.0.0" + has-proto "^1.0.1" has-symbols "^1.0.3" - internal-slot "^1.0.3" + internal-slot "^1.0.4" + is-array-buffer "^3.0.1" is-callable "^1.2.7" is-negative-zero "^2.0.2" is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" is-string "^1.0.7" + is-typed-array "^1.1.10" is-weakref "^1.0.2" object-inspect "^1.12.2" object-keys "^1.1.1" object.assign "^4.1.4" regexp.prototype.flags "^1.4.3" safe-regex-test "^1.0.0" - string.prototype.trimend "^1.0.5" - string.prototype.trimstart "^1.0.5" + string.prototype.trimend "^1.0.6" + string.prototype.trimstart "^1.0.6" + typed-array-length "^1.0.4" unbox-primitive "^1.0.2" + which-typed-array "^1.1.9" es-array-method-boxes-properly@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== +es-set-tostringtag@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" + integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== + dependencies: + get-intrinsic "^1.1.3" + has "^1.0.3" + has-tostringtag "^1.0.0" + es-shim-unscopables@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" @@ -4760,17 +4861,18 @@ escodegen@^2.0.0: source-map "~0.6.1" eslint-config-prettier@^8.3.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" - integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== + version "8.6.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz#dec1d29ab728f4fa63061774e1672ac4e363d207" + integrity sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA== -eslint-import-resolver-node@^0.3.6: - version "0.3.6" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" - integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== +eslint-import-resolver-node@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7" + integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA== dependencies: debug "^3.2.7" - resolve "^1.20.0" + is-core-module "^2.11.0" + resolve "^1.22.1" eslint-import-resolver-typescript@^2.4.0: version "2.7.1" @@ -4783,7 +4885,7 @@ eslint-import-resolver-typescript@^2.4.0: resolve "^1.22.0" tsconfig-paths "^3.14.1" -eslint-module-utils@^2.7.3: +eslint-module-utils@^2.7.4: version "2.7.4" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== @@ -4791,22 +4893,24 @@ eslint-module-utils@^2.7.3: debug "^3.2.7" eslint-plugin-import@^2.23.4: - version "2.26.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" - integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== + version "2.27.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.4.tgz#319c2f6f6580e1678d674a258ee5e981c10cc25b" + integrity sha512-Z1jVt1EGKia1X9CnBCkpAOhWy8FgQ7OmJ/IblEkT82yrFU/xJaxwujaTzLWqigewwynRQ9mmHfX9MtAfhxm0sA== dependencies: - array-includes "^3.1.4" - array.prototype.flat "^1.2.5" - debug "^2.6.9" + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + array.prototype.flatmap "^1.3.0" + debug "^3.2.7" doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.3" + eslint-import-resolver-node "^0.3.7" + eslint-module-utils "^2.7.4" has "^1.0.3" - is-core-module "^2.8.1" + is-core-module "^2.11.0" is-glob "^4.0.3" minimatch "^3.1.2" - object.values "^1.1.5" - resolve "^1.22.0" + object.values "^1.1.6" + resolve "^1.22.1" + semver "^6.3.0" tsconfig-paths "^3.14.1" eslint-plugin-prettier@^3.4.0: @@ -4848,6 +4952,11 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + eslint@^7.28.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" @@ -5168,9 +5277,9 @@ fast-text-encoding@^1.0.3: integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w== fastq@^1.6.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== dependencies: reusify "^1.0.4" @@ -5330,15 +5439,22 @@ flatted@^3.1.0: integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== flow-parser@0.*: - version "0.193.0" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.193.0.tgz#8d705fc2d6b378a24bae189014f6f0320d040c4f" - integrity sha512-x7ZoArE1UO3Nk2rkq/KK/Tkp714QDMVzEsxIyK2+p7Alx+88LY7KgqmeQZuiAG8TCHucmYuHefbk3KsVFVjouA== + version "0.197.0" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.197.0.tgz#9a581ef7c0b1c3377b195cec0bbad794b88be67b" + integrity sha512-yhwkJPxH1JBg0aJunk/jVRy5p3UhVZBGkzL1hq/GK+GaBh6bKr2YKkv6gDuiufaw+i3pKWQgOLtD++1cvrgXLA== flow-parser@^0.121.0: version "0.121.0" resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.121.0.tgz#9f9898eaec91a9f7c323e9e992d81ab5c58e618f" integrity sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg== +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -5514,7 +5630,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== @@ -5668,13 +5784,20 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.6.0, globals@^13.9.0: - version "13.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.18.0.tgz#fb224daeeb2bb7d254cd2c640f003528b8d0c1dc" - integrity sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A== + version "13.19.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.19.0.tgz#7a42de8e6ad4f7242fbcca27ea5b23aca367b5c8" + integrity sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ== dependencies: type-fest "^0.20.2" -globby@^11.0.2, globby@^11.0.3: +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +globby@^11.0.2, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -5686,6 +5809,13 @@ globby@^11.0.2, globby@^11.0.3: merge2 "^1.4.1" slash "^3.0.0" +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" @@ -5743,6 +5873,11 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.1.1" +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" @@ -5888,6 +6023,11 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" +husky@^7.0.1: + version "7.0.4" + resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" + integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== + iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -5919,10 +6059,10 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.8, ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +ignore@^5.2.0: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== image-size@^0.6.0: version "0.6.3" @@ -6039,12 +6179,12 @@ inquirer@^7.3.3: strip-ansi "^6.0.0" through "^2.3.6" -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== +internal-slot@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.4.tgz#8551e7baf74a7a6ba5f749cfb16aa60722f0d6f3" + integrity sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ== dependencies: - get-intrinsic "^1.1.0" + get-intrinsic "^1.1.3" has "^1.0.3" side-channel "^1.0.4" @@ -6089,6 +6229,15 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-array-buffer@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.1.tgz#deb1db4fcae48308d54ef2442706c0393997052a" + integrity sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-typed-array "^1.1.10" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -6114,7 +6263,7 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.2.7: +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== @@ -6126,7 +6275,7 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.5.0, is-core-module@^2.8.1, is-core-module@^2.9.0: +is-core-module@^2.11.0, is-core-module@^2.5.0, is-core-module@^2.9.0: version "2.11.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== @@ -6337,6 +6486,17 @@ is-text-path@^1.0.1: dependencies: text-extensions "^1.0.0" +is-typed-array@^1.1.10, is-typed-array@^1.1.9: + version "1.1.10" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" + integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -7087,10 +7247,10 @@ json-text-sequence@~0.3.0: dependencies: "@sovpro/delimited-stream" "^1.1.0" -json5@2.x, json5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== +json5@2.x, json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== json5@^1.0.1: version "1.0.2" @@ -7262,15 +7422,10 @@ libnpmpublish@^4.0.0: semver "^7.1.3" ssri "^8.0.1" -libphonenumber-js@^1.10.14: - version "1.10.17" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.17.tgz#7efcfa3068fc076bc59a43a08a723ccd95308474" - integrity sha512-UQrNzsusSn5qaojdpWqporWRdpx6AGeb+egj64NrpYuyKHvnSH9jMp/1Dy3b/WnMyJA5zgV1yw//jC6J0dCXkw== - -libphonenumber-js@^1.9.7: - version "1.10.14" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.14.tgz#e29da7f539751f724ac54017a098e3c7ca23de94" - integrity sha512-McGS7GV/WjJ2KjfOGhJU1oJn29RYeo7Q+RpANRbUNMQ9gj5XArpbjurSuyYPTejFwbaUojstQ4XyWCrAzGOUXw== +libphonenumber-js@^1.10.14, libphonenumber-js@^1.9.7: + version "1.10.18" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.18.tgz#657c419071c8a02c638c0e80d9ee1232f152f280" + integrity sha512-NS4ZEgNhwbcPz1gfSXCGFnQm0xEiyTSPRthIuWytDzOiEG9xnZ2FbLyfJC4tI2BMAAXpoWbNxHYH75pa3Dq9og== lines-and-columns@^1.1.6: version "1.2.4" @@ -7410,6 +7565,13 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -7975,6 +8137,13 @@ minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: dependencies: yallist "^4.0.0" +minipass@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.0.0.tgz#7cebb0f9fa7d56f0c5b17853cbe28838a8dbbd3b" + integrity sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw== + dependencies: + yallist "^4.0.0" + minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" @@ -8087,6 +8256,11 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -8173,7 +8347,7 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" -node-fetch@2.6.7, node-fetch@^2.0, node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: +node-fetch@2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -8188,10 +8362,17 @@ node-fetch@3.0.0-beta.9: data-uri-to-buffer "^3.0.1" fetch-blob "^2.1.1" +node-fetch@^2.0, node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: + version "2.6.8" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e" + integrity sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg== + dependencies: + whatwg-url "^5.0.0" + node-gyp-build@^4.2.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" - integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== + version "4.6.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" + integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== node-gyp@^5.0.2: version "5.1.1" @@ -8264,9 +8445,9 @@ node-pre-gyp@0.17.0: tar "^4.4.13" node-releases@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" - integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== + version "2.0.8" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.8.tgz#0f349cdc8fcfa39a92ac0be9bc48b7706292b9ae" + integrity sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A== node-stream-zip@^1.9.1: version "1.15.0" @@ -8506,9 +8687,9 @@ object-copy@^0.1.0: kind-of "^3.0.3" object-inspect@^1.10.3, object-inspect@^1.12.2, object-inspect@^1.9.0: - version "1.12.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" - integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== object-keys@^1.1.1: version "1.1.1" @@ -8549,7 +8730,7 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -object.values@^1.1.5: +object.values@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== @@ -8989,9 +9170,9 @@ prettier-linter-helpers@^1.0.0: fast-diff "^1.1.2" prettier@^2.3.1: - version "2.8.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.0.tgz#c7df58393c9ba77d6fba3921ae01faf994fb9dc9" - integrity sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA== + version "2.8.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.3.tgz#ab697b1d3dd46fb4626fbe2f543afe0cc98d8632" + integrity sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw== pretty-format@^26.0.0, pretty-format@^26.5.2, pretty-format@^26.6.2: version "26.6.2" @@ -9103,9 +9284,9 @@ pump@^3.0.0: once "^1.3.1" punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + version "2.2.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.2.0.tgz#2092cc57cd2582c38e4e7e8bb869dc8d3148bc74" + integrity sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw== pvtsutils@^1.3.2: version "1.3.2" @@ -9147,11 +9328,11 @@ query-string@^6.13.8: strict-uri-encode "^2.0.0" query-string@^7.0.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.1.tgz#754620669db978625a90f635f12617c271a088e1" - integrity sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w== + version "7.1.3" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" + integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== dependencies: - decode-uri-component "^0.2.0" + decode-uri-component "^0.2.2" filter-obj "^1.1.0" split-on-first "^1.0.0" strict-uri-encode "^2.0.0" @@ -9197,9 +9378,9 @@ rc@^1.2.8: strip-json-comments "~2.0.1" react-devtools-core@^4.6.0: - version "4.26.1" - resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.26.1.tgz#2893fea58089be64c5356d5bd0eebda8d1bbf317" - integrity sha512-r1csa5n9nABVpSdAadwTG7K+SfgRJPc/Hdx89BkV5IlA1mEGgGi3ir630ST5D/xYlJQaY3VE75YGADgpNW7HIw== + version "4.27.1" + resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.27.1.tgz#167aa174383c65786cbb7e965a5b39c702f0a2d3" + integrity sha512-qXhcxxDWiFmFAOq48jts9YQYe1+wVoUXzJTlY4jbaATzyio6dd6CUGu3dXBhREeVgpZ+y4kg6vFJzIOZh6vY2w== dependencies: shell-quote "^1.6.1" ws "^7" @@ -9487,12 +9668,12 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.13.10, regenerator-runtime@^0.13.2: +regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.2: version "0.13.11" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== -regenerator-transform@^0.15.0: +regenerator-transform@^0.15.1: version "0.15.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== @@ -9516,12 +9697,12 @@ regexp.prototype.flags@^1.4.3: define-properties "^1.1.3" functions-have-names "^1.2.2" -regexpp@^3.1.0: +regexpp@^3.1.0, regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -regexpu-core@^5.1.0: +regexpu-core@^5.2.1: version "5.2.2" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.2.tgz#3e4e5d12103b64748711c3aad69934d7718e75fc" integrity sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw== @@ -9634,11 +9815,11 @@ resolve-url@^0.2.1: integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== resolve.exports@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" - integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + version "1.1.1" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.1.tgz#05cfd5b3edf641571fd46fa608b610dda9ead999" + integrity sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ== -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.0: +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -9690,13 +9871,18 @@ rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: dependencies: glob "^7.1.3" -rimraf@^3.0.0, rimraf@^3.0.2, rimraf@~3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" +rimraf@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.0.7.tgz#f438c7d6a2d5e5cca1d81e3904a48ac7b053a542" + integrity sha512-CUEDDrZvc0swDgVdXGiv3FcYYQMpJxjvSGt85Amj6yU+MCVWurrLCeLiJDdJPHCzNJnwuebBEdcO//eP11Xa7w== + rimraf@~2.2.6: version "2.2.8" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" @@ -9734,9 +9920,9 @@ rxjs@^6.6.0: tslib "^1.9.0" rxjs@^7.2.0: - version "7.5.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39" - integrity sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA== + version "7.8.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" + integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg== dependencies: tslib "^2.1.0" @@ -9811,7 +9997,7 @@ scheduler@^0.20.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.x, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: +semver@7.x, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7: version "7.3.8" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== @@ -10266,7 +10452,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string.prototype.trimend@^1.0.5: +string.prototype.trimend@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== @@ -10275,7 +10461,7 @@ string.prototype.trimend@^1.0.5: define-properties "^1.1.4" es-abstract "^1.20.4" -string.prototype.trimstart@^1.0.5: +string.prototype.trimstart@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== @@ -10444,13 +10630,13 @@ tar@^4.4.12, tar@^4.4.13: yallist "^3.1.1" tar@^6.0.2, tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: - version "6.1.12" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.12.tgz#3b742fb05669b55671fb769ab67a7791ea1a62e6" - integrity sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw== + version "6.1.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" + integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" - minipass "^3.0.0" + minipass "^4.0.0" minizlib "^2.1.1" mkdirp "^1.0.3" yallist "^4.0.0" @@ -10519,9 +10705,9 @@ throat@^5.0.0: integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== throat@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" - integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== + version "6.0.2" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.2.tgz#51a3fbb5e11ae72e2cf74861ed5c8020f89f29fe" + integrity sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ== through2@^2.0.0, through2@^2.0.1: version "2.0.5" @@ -10675,7 +10861,7 @@ ts-typed-json@^0.3.2: resolved "https://registry.yarnpkg.com/ts-typed-json/-/ts-typed-json-0.3.2.tgz#f4f20f45950bae0a383857f7b0a94187eca1b56a" integrity sha512-Tdu3BWzaer7R5RvBIJcg9r8HrTZgpJmsX+1meXMJzYypbkj8NK2oJN0yvm4Dp/Iv6tzFa/L5jKRmEVTga6K3nA== -tsconfig-paths@^3.14.1, tsconfig-paths@^3.9.0: +tsconfig-paths@^3.14.1: version "3.14.1" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== @@ -10685,6 +10871,15 @@ tsconfig-paths@^3.14.1, tsconfig-paths@^3.9.0: minimist "^1.2.6" strip-bom "^3.0.0" +tsconfig-paths@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.1.2.tgz#4819f861eef82e6da52fb4af1e8c930a39ed979a" + integrity sha512-uhxiMgnXQp1IR622dUXI+9Ehnws7i/y6xvpZB9IbUVOPy0muvdvgXeZOn88UcGPiT98Vp3rJPTa8bFoalZ3Qhw== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -10790,6 +10985,15 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -10802,10 +11006,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@~4.3.0: - version "4.3.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" - integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== +typescript@~4.9.4: + version "4.9.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" + integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== typical@^4.0.0: version "4.0.0" @@ -11204,6 +11408,18 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== +which-typed-array@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" + integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.10" + which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -11383,7 +11599,7 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^3.0.0, yallist@^3.1.1: +yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== From 4a572fedd7508aeba3e91f4d1d98b9a177941648 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 18 Jan 2023 18:21:40 +0000 Subject: [PATCH 124/125] chore(release): v0.3.3 (#1217) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 15 +++++++++ lerna.json | 2 +- packages/action-menu/CHANGELOG.md | 10 ++++++ packages/action-menu/package.json | 6 ++-- packages/anoncreds/package.json | 4 +-- packages/core/CHANGELOG.md | 11 +++++++ packages/core/package.json | 2 +- packages/indy-sdk/package.json | 6 ++-- packages/node/CHANGELOG.md | 6 ++++ packages/node/package.json | 4 +-- packages/openid4vc-client/package.json | 6 ++-- packages/question-answer/CHANGELOG.md | 10 ++++++ packages/question-answer/package.json | 8 ++--- packages/react-native/CHANGELOG.md | 10 ++++++ packages/react-native/package.json | 4 +-- packages/tenants/CHANGELOG.md | 6 ++++ packages/tenants/package.json | 6 ++-- yarn.lock | 44 -------------------------- 18 files changed, 92 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50748153df..0d2a624f28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) +- **openid4vc-client:** set package to private ([#1210](https://github.com/hyperledger/aries-framework-javascript/issues/1210)) ([c697716](https://github.com/hyperledger/aries-framework-javascript/commit/c697716bf1837b9fef307f60ff97f01d3d926728)) + +### Features + +- add anoncreds package ([#1118](https://github.com/hyperledger/aries-framework-javascript/issues/1118)) ([adba83d](https://github.com/hyperledger/aries-framework-javascript/commit/adba83d8df176288083969f2c3f975bbfc1acd9c)) +- add minimal oidc-client package ([#1197](https://github.com/hyperledger/aries-framework-javascript/issues/1197)) ([b6f89f9](https://github.com/hyperledger/aries-framework-javascript/commit/b6f89f943dc4417626f868ac9f43a3d890ab62c6)) +- adding trust ping events and trust ping command ([#1182](https://github.com/hyperledger/aries-framework-javascript/issues/1182)) ([fd006f2](https://github.com/hyperledger/aries-framework-javascript/commit/fd006f262a91f901e7f8a9c6e6882ea178230005)) +- **anoncreds:** add anoncreds registry service ([#1204](https://github.com/hyperledger/aries-framework-javascript/issues/1204)) ([86647e7](https://github.com/hyperledger/aries-framework-javascript/commit/86647e7f55c9a362f6ab500538c4de2112e42206)) +- **indy-sdk:** add indy-sdk package ([#1200](https://github.com/hyperledger/aries-framework-javascript/issues/1200)) ([9933b35](https://github.com/hyperledger/aries-framework-javascript/commit/9933b35a6aa4524caef8a885e71b742cd0d7186b)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) ### Bug Fixes diff --git a/lerna.json b/lerna.json index 56d3a038a2..b8106b0a8a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.3.2", + "version": "0.3.3", "useWorkspaces": true, "npmClient": "yarn", "command": { diff --git a/packages/action-menu/CHANGELOG.md b/packages/action-menu/CHANGELOG.md index c3167eb95d..a18e55cd11 100644 --- a/packages/action-menu/CHANGELOG.md +++ b/packages/action-menu/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) + +### Features + +- **indy-sdk:** add indy-sdk package ([#1200](https://github.com/hyperledger/aries-framework-javascript/issues/1200)) ([9933b35](https://github.com/hyperledger/aries-framework-javascript/commit/9933b35a6aa4524caef8a885e71b742cd0d7186b)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) **Note:** Version bump only for package @aries-framework/action-menu diff --git a/packages/action-menu/package.json b/packages/action-menu/package.json index e4b54a744a..9ee1173bdc 100644 --- a/packages/action-menu/package.json +++ b/packages/action-menu/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/action-menu", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ "build" ], @@ -24,10 +24,10 @@ "test": "jest" }, "dependencies": { + "@aries-framework/core": "0.3.3", "class-transformer": "0.5.1", "class-validator": "0.13.1", - "rxjs": "^7.2.0", - "@aries-framework/core": "0.3.2" + "rxjs": "^7.2.0" }, "devDependencies": { "reflect-metadata": "^0.1.13", diff --git a/packages/anoncreds/package.json b/packages/anoncreds/package.json index 473c115b01..7947fe5642 100644 --- a/packages/anoncreds/package.json +++ b/packages/anoncreds/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/anoncreds", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ "build" ], @@ -25,7 +25,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.2" + "@aries-framework/core": "0.3.3" }, "devDependencies": { "rimraf": "^4.0.7", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 341ff76014..8f07830b5f 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) + +### Features + +- adding trust ping events and trust ping command ([#1182](https://github.com/hyperledger/aries-framework-javascript/issues/1182)) ([fd006f2](https://github.com/hyperledger/aries-framework-javascript/commit/fd006f262a91f901e7f8a9c6e6882ea178230005)) +- **indy-sdk:** add indy-sdk package ([#1200](https://github.com/hyperledger/aries-framework-javascript/issues/1200)) ([9933b35](https://github.com/hyperledger/aries-framework-javascript/commit/9933b35a6aa4524caef8a885e71b742cd0d7186b)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) ### Bug Fixes diff --git a/packages/core/package.json b/packages/core/package.json index 971091140e..8a54af7192 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/core", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ "build" ], diff --git a/packages/indy-sdk/package.json b/packages/indy-sdk/package.json index 02dec486ae..e29cfe6020 100644 --- a/packages/indy-sdk/package.json +++ b/packages/indy-sdk/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/indy-sdk", "main": "build/index", "types": "build/index", - "version": "0.2.5", + "version": "0.3.3", "private": true, "files": [ "build" @@ -25,8 +25,8 @@ "test": "jest" }, "dependencies": { - "@aries-framework/anoncreds": "0.3.2", - "@aries-framework/core": "0.3.2", + "@aries-framework/anoncreds": "0.3.3", + "@aries-framework/core": "0.3.3", "@types/indy-sdk": "1.16.24", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index a3f6e278ce..32ecc62fb0 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) **Note:** Version bump only for package @aries-framework/node diff --git a/packages/node/package.json b/packages/node/package.json index 09f32cc942..665472421c 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/node", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ "build", "bin" @@ -28,7 +28,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.2", + "@aries-framework/core": "0.3.3", "@types/express": "^4.17.15", "express": "^4.17.1", "ffi-napi": "^4.0.3", diff --git a/packages/openid4vc-client/package.json b/packages/openid4vc-client/package.json index 7b251a86b3..3f7a671015 100644 --- a/packages/openid4vc-client/package.json +++ b/packages/openid4vc-client/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/openid4vc-client", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ "build" ], @@ -25,14 +25,14 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.2", + "@aries-framework/core": "0.3.3", "@sphereon/openid4vci-client": "^0.3.6", "class-transformer": "0.5.1", "class-validator": "0.13.1" }, "peerDependencies": {}, "devDependencies": { - "@aries-framework/node": "0.3.2", + "@aries-framework/node": "0.3.3", "reflect-metadata": "^0.1.13", "rimraf": "^4.0.7", "typescript": "~4.9.4" diff --git a/packages/question-answer/CHANGELOG.md b/packages/question-answer/CHANGELOG.md index 216388c0ea..dc3f65cf84 100644 --- a/packages/question-answer/CHANGELOG.md +++ b/packages/question-answer/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) + +### Features + +- **indy-sdk:** add indy-sdk package ([#1200](https://github.com/hyperledger/aries-framework-javascript/issues/1200)) ([9933b35](https://github.com/hyperledger/aries-framework-javascript/commit/9933b35a6aa4524caef8a885e71b742cd0d7186b)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) **Note:** Version bump only for package @aries-framework/question-answer diff --git a/packages/question-answer/package.json b/packages/question-answer/package.json index 547a9a3f2c..1640e283cd 100644 --- a/packages/question-answer/package.json +++ b/packages/question-answer/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/question-answer", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ "build" ], @@ -24,13 +24,13 @@ "test": "jest" }, "dependencies": { + "@aries-framework/core": "0.3.3", "class-transformer": "0.5.1", "class-validator": "0.13.1", - "rxjs": "^7.2.0", - "@aries-framework/core": "0.3.2" + "rxjs": "^7.2.0" }, "devDependencies": { - "@aries-framework/node": "0.3.2", + "@aries-framework/node": "0.3.3", "reflect-metadata": "^0.1.13", "rimraf": "^4.0.7", "typescript": "~4.9.4" diff --git a/packages/react-native/CHANGELOG.md b/packages/react-native/CHANGELOG.md index 092f793c6d..c0ff907f14 100644 --- a/packages/react-native/CHANGELOG.md +++ b/packages/react-native/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) + +### Features + +- **indy-sdk:** add indy-sdk package ([#1200](https://github.com/hyperledger/aries-framework-javascript/issues/1200)) ([9933b35](https://github.com/hyperledger/aries-framework-javascript/commit/9933b35a6aa4524caef8a885e71b742cd0d7186b)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) **Note:** Version bump only for package @aries-framework/react-native diff --git a/packages/react-native/package.json b/packages/react-native/package.json index e2b1b02691..5eee471421 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/react-native", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ "build" ], @@ -24,7 +24,7 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.2", + "@aries-framework/core": "0.3.3", "@azure/core-asynciterator-polyfill": "^1.0.0", "events": "^3.3.0" }, diff --git a/packages/tenants/CHANGELOG.md b/packages/tenants/CHANGELOG.md index a73651ba84..d3f33ffa92 100644 --- a/packages/tenants/CHANGELOG.md +++ b/packages/tenants/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) ### Bug Fixes diff --git a/packages/tenants/package.json b/packages/tenants/package.json index 2c4ad8faa9..13f1f22438 100644 --- a/packages/tenants/package.json +++ b/packages/tenants/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/tenants", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ "build" ], @@ -24,11 +24,11 @@ "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.2", + "@aries-framework/core": "0.3.3", "async-mutex": "^0.3.2" }, "devDependencies": { - "@aries-framework/node": "0.3.2", + "@aries-framework/node": "0.3.3", "reflect-metadata": "^0.1.13", "rimraf": "^4.0.7", "typescript": "~4.9.4" diff --git a/yarn.lock b/yarn.lock index 359ab25c51..c79dff1e1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,50 +10,6 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@aries-framework/core@file:packages/core": - version "0.3.2" - dependencies: - "@digitalcredentials/jsonld" "^5.2.1" - "@digitalcredentials/jsonld-signatures" "^9.3.1" - "@digitalcredentials/vc" "^1.1.2" - "@multiformats/base-x" "^4.0.1" - "@stablelib/ed25519" "^1.0.2" - "@stablelib/random" "^1.0.1" - "@stablelib/sha256" "^1.0.1" - "@types/indy-sdk" "1.16.24" - "@types/node-fetch" "^2.5.10" - "@types/ws" "^7.4.6" - abort-controller "^3.0.0" - bn.js "^5.2.0" - borc "^3.0.0" - buffer "^6.0.3" - class-transformer "0.5.1" - class-validator "0.13.1" - did-resolver "^3.1.3" - lru_map "^0.4.1" - luxon "^1.27.0" - make-error "^1.3.6" - object-inspect "^1.10.3" - query-string "^7.0.1" - reflect-metadata "^0.1.13" - rxjs "^7.2.0" - tsyringe "^4.7.0" - uuid "^8.3.2" - varint "^6.0.0" - web-did-resolver "^2.0.8" - -"@aries-framework/node@file:packages/node": - version "0.3.2" - dependencies: - "@aries-framework/core" "0.3.2" - "@types/express" "^4.17.15" - express "^4.17.1" - ffi-napi "^4.0.3" - indy-sdk "^1.16.0-dev-1636" - node-fetch "^2.6.1" - ref-napi "^3.0.3" - ws "^7.5.3" - "@azure/core-asynciterator-polyfill@^1.0.0": version "1.0.2" resolved "https://registry.yarnpkg.com/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.2.tgz#0dd3849fb8d97f062a39db0e5cadc9ffaf861fec" From b7d136cc88bc9ed14212f264840af11edb280459 Mon Sep 17 00:00:00 2001 From: blu3beri Date: Wed, 18 Jan 2023 21:03:31 +0100 Subject: [PATCH 125/125] fix(transport): added docs moved connection to connectionId Signed-off-by: blu3beri --- packages/core/src/agent/TransportService.ts | 27 ++++++++++++++++++--- packages/core/src/agent/__tests__/stubs.ts | 3 +-- packages/core/src/foobarbaz | 0 3 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 packages/core/src/foobarbaz diff --git a/packages/core/src/agent/TransportService.ts b/packages/core/src/agent/TransportService.ts index 0eda25500c..1851b804e2 100644 --- a/packages/core/src/agent/TransportService.ts +++ b/packages/core/src/agent/TransportService.ts @@ -1,6 +1,5 @@ import type { AgentMessage } from './AgentMessage' import type { EnvelopeKeys } from './EnvelopeService' -import type { ConnectionRecord } from '../modules/connections/repository' import type { DidDocument } from '../modules/dids' import type { EncryptedMessage } from '../types' @@ -16,7 +15,7 @@ export class TransportService { } public findSessionByConnectionId(connectionId: string) { - return Object.values(this.transportSessionTable).find((session) => session?.connection?.id === connectionId) + return Object.values(this.transportSessionTable).find((session) => session?.id === connectionId) } public hasInboundEndpoint(didDocument: DidDocument): boolean { @@ -36,12 +35,34 @@ interface TransportSessionTable { [sessionId: string]: TransportSession | undefined } +// In the framework Transport sessions are used for communication. A session is +// associated with a connection and it can be reused when we want to respond to +// a message. If the message, for example, does not contain any way to reply to +// this message, the session should be closed. When a new sequence of messages +// starts it can be used again. A session will be deleted when a WebSocket +// closes, for the WsTransportSession that is. export interface TransportSession { + // unique identifier for a transport session. This can a uuid, or anything else, as long + // as it uniquely identifies a transport. id: string + + // The type is something that explicitly defines the transport type. For WebSocket it would + // be "WebSocket" and for HTTP it would be "HTTP". type: string + + // The enveloping keys that can be used during the transport. This is used so the framework + // does not have to look up the associated keys for sending a message. keys?: EnvelopeKeys + + // A received message that will be used to check whether it has any return routing. inboundMessage?: AgentMessage - connection?: ConnectionRecord + + // A stored connection id used to find this session via the `TransportService` for a specific connection + connectionId?: string + + // Send an encrypted message send(encryptedMessage: EncryptedMessage): Promise + + // Close the session to prevent dangling sessions. close(): Promise } diff --git a/packages/core/src/agent/__tests__/stubs.ts b/packages/core/src/agent/__tests__/stubs.ts index 49fcaae660..afd7ec4aaa 100644 --- a/packages/core/src/agent/__tests__/stubs.ts +++ b/packages/core/src/agent/__tests__/stubs.ts @@ -1,4 +1,3 @@ -import type { ConnectionRecord } from '../../modules/connections' import type { AgentMessage } from '../AgentMessage' import type { EnvelopeKeys } from '../EnvelopeService' import type { TransportSession } from '../TransportService' @@ -8,7 +7,7 @@ export class DummyTransportSession implements TransportSession { public readonly type = 'http' public keys?: EnvelopeKeys public inboundMessage?: AgentMessage - public connection?: ConnectionRecord + public connectionId?: string public constructor(id: string) { this.id = id diff --git a/packages/core/src/foobarbaz b/packages/core/src/foobarbaz new file mode 100644 index 0000000000..e69de29bb2