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)