Skip to content

Commit

Permalink
feat: support newer did-communication service type (openwallet-founda…
Browse files Browse the repository at this point in the history
…tion#233)

Signed-off-by: Timo Glastra <[email protected]>
  • Loading branch information
TimoGlastra authored and berendsliedrecht committed May 11, 2021
1 parent 100d0ab commit b0b89de
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 28 deletions.
4 changes: 2 additions & 2 deletions src/agent/AgentConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ export class AgentConfig {

public getEndpoint() {
// If a mediator is used, always return that as endpoint
const mediatorEndpoint = this.inboundConnection?.connection?.theirDidDoc?.service[0].serviceEndpoint
if (mediatorEndpoint) return mediatorEndpoint
const didCommServices = this.inboundConnection?.connection?.theirDidDoc?.didCommServices
if (didCommServices && didCommServices?.length > 0) return didCommServices[0].serviceEndpoint

// Otherwise we check if an endpoint is set
if (this.initConfig.endpoint) return `${this.initConfig.endpoint}/msg`
Expand Down
4 changes: 2 additions & 2 deletions src/agent/__tests__/AgentConfig.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type Indy from 'indy-sdk'
import { getMockConnection } from '../../modules/connections/__tests__/ConnectionService.test'
import { DidDoc, IndyAgentService } from '../../modules/connections'
import { DidCommService, DidDoc } from '../../modules/connections'
import { AgentConfig } from '../AgentConfig'

const indy = {} as typeof Indy
Expand All @@ -24,7 +24,7 @@ describe('AgentConfig', () => {
publicKey: [],
authentication: [],
service: [
new IndyAgentService({
new DidCommService({
id: `test;indy`,
serviceEndpoint: endpoint,
recipientKeys: [],
Expand Down
3 changes: 1 addition & 2 deletions src/agent/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { ConnectionRecord } from '../modules/connections'
import { AgentMessage } from './AgentMessage'
import { OutboundMessage } from '../types'
import { ConnectionInvitationMessage } from '../modules/connections'
import { IndyAgentService } from '../modules/connections'

export function createOutboundMessage<T extends AgentMessage = AgentMessage>(
connection: ConnectionRecord,
Expand All @@ -28,7 +27,7 @@ export function createOutboundMessage<T extends AgentMessage = AgentMessage>(
throw new Error(`DidDoc for connection with verkey ${connection.verkey} not found!`)
}

const [service] = theirDidDoc.getServicesByClassType(IndyAgentService)
const [service] = theirDidDoc.didCommServices

return {
connection,
Expand Down
12 changes: 6 additions & 6 deletions src/modules/connections/__tests__/ConnectionService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Wallet } from '../../../wallet/Wallet'
import { ConnectionService } from '../services/ConnectionService'
import { ConnectionRecord, ConnectionStorageProps } from '../repository/ConnectionRecord'
import { AgentConfig } from '../../../agent/AgentConfig'
import { Connection, ConnectionState, ConnectionRole, DidDoc, IndyAgentService } from '../models'
import { Connection, ConnectionState, ConnectionRole, DidDoc, DidCommService } from '../models'
import { InitConfig } from '../../../types'
import {
ConnectionInvitationMessage,
Expand Down Expand Up @@ -35,7 +35,7 @@ export function getMockConnection({
publicKey: [],
authentication: [],
service: [
new IndyAgentService({
new DidCommService({
id: `${did};indy`,
serviceEndpoint: 'https://endpoint.com',
recipientKeys: [verkey],
Expand All @@ -54,7 +54,7 @@ export function getMockConnection({
publicKey: [],
authentication: [],
service: [
new IndyAgentService({
new DidCommService({
id: `${did};indy`,
serviceEndpoint: 'https://endpoint.com',
recipientKeys: [verkey],
Expand Down Expand Up @@ -317,7 +317,7 @@ describe('ConnectionService', () => {
publicKey: [],
authentication: [],
service: [
new IndyAgentService({
new DidCommService({
id: `${theirDid};indy`,
serviceEndpoint: 'https://endpoint.com',
recipientKeys: [theirVerkey],
Expand Down Expand Up @@ -518,7 +518,7 @@ describe('ConnectionService', () => {
publicKey: [],
authentication: [],
service: [
new IndyAgentService({
new DidCommService({
id: `${did};indy`,
serviceEndpoint: 'https://endpoint.com',
recipientKeys: [theirVerkey],
Expand Down Expand Up @@ -587,7 +587,7 @@ describe('ConnectionService', () => {
publicKey: [],
authentication: [],
service: [
new IndyAgentService({
new DidCommService({
id: `${did};indy`,
serviceEndpoint: 'https://endpoint.com',
recipientKeys: [theirVerkey],
Expand Down
16 changes: 15 additions & 1 deletion src/modules/connections/models/did/DidDoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Equals, IsArray, IsString, ValidateNested } from 'class-validator'

import { AuthenticationTransformer, Authentication } from './authentication'
import { PublicKey, PublicKeyTransformer } from './publicKey'
import { Service, ServiceTransformer } from './service'
import { DidCommService, IndyAgentService, Service, ServiceTransformer } from './service'

type DidDocOptions = Pick<DidDoc, 'id' | 'publicKey' | 'service' | 'authentication'>

Expand Down Expand Up @@ -65,4 +65,18 @@ export class DidDoc {
public getServicesByClassType<S extends Service = Service>(classType: new (...args: never[]) => S): S[] {
return this.service.filter((service) => service instanceof classType) as S[]
}

/**
* Get all DIDComm services ordered by priority descending. This means the highest
* priority will be the first entry.
*/
public get didCommServices(): Array<IndyAgentService | DidCommService> {
const didCommServiceTypes = [IndyAgentService.type, DidCommService.type]
const services = this.service.filter((service) => didCommServiceTypes.includes(service.type)) as Array<
IndyAgentService | DidCommService
>

// Sort services based on indicated priority
return services.sort((a, b) => b.priority - a.priority)
}
}
30 changes: 28 additions & 2 deletions src/modules/connections/models/did/__tests__/DidDoc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { classToPlain, plainToClass } from 'class-transformer'
import { ReferencedAuthentication, EmbeddedAuthentication } from '../authentication'
import { DidDoc } from '../DidDoc'
import { Ed25119Sig2018, EddsaSaSigSecp256k1, RsaSig2018 } from '../publicKey'
import { Service, IndyAgentService } from '../service'
import { Service, IndyAgentService, DidCommService } from '../service'

import diddoc from './diddoc.json'

Expand Down Expand Up @@ -56,6 +56,13 @@ const didDoc = new DidDoc({
routingKeys: ['Q4zqM7aXqm7gDQkUVLng9h'],
priority: 5,
}),
new DidCommService({
id: '7',
serviceEndpoint: 'https://agent.com/did-comm',
recipientKeys: ['DADEajsDSaksLng9h'],
routingKeys: ['DADEajsDSaksLng9h'],
priority: 10,
}),
],
})

Expand All @@ -82,6 +89,7 @@ describe('Did | DidDoc', () => {
// Check Service
expect(didDoc.service[0]).toBeInstanceOf(Service)
expect(didDoc.service[1]).toBeInstanceOf(IndyAgentService)
expect(didDoc.service[2]).toBeInstanceOf(DidCommService)

// Check Authentication
expect(didDoc.authentication[0]).toBeInstanceOf(ReferencedAuthentication)
Expand Down Expand Up @@ -134,6 +142,14 @@ describe('Did | DidDoc', () => {
routingKeys: ['Q4zqM7aXqm7gDQkUVLng9h'],
priority: 5,
})
expect(json.service[2]).toMatchObject({
id: '7',
type: 'did-communication',
serviceEndpoint: 'https://agent.com/did-comm',
recipientKeys: ['DADEajsDSaksLng9h'],
routingKeys: ['DADEajsDSaksLng9h'],
priority: 10,
})

// Check Authentication
expect(json.authentication[0]).toMatchObject({
Expand Down Expand Up @@ -162,11 +178,21 @@ describe('Did | DidDoc', () => {
})
})

describe('getServicesByType', () => {
describe('getServicesByClassType', () => {
it('returns all services with specified class', async () => {
expect(didDoc.getServicesByClassType(IndyAgentService)).toEqual(
didDoc.service.filter((service) => service instanceof IndyAgentService)
)
})
})

describe('didCommServices', () => {
it('returns all IndyAgentService and DidCommService instances', async () => {
expect(didDoc.didCommServices).toEqual(expect.arrayContaining([didDoc.service[1], didDoc.service[2]]))
})

it('returns all IndyAgentService and DidCommService instances sorted by priority', async () => {
expect(didDoc.didCommServices).toEqual([didDoc.service[2], didDoc.service[1]])
})
})
})
60 changes: 58 additions & 2 deletions src/modules/connections/models/did/__tests__/Service.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { classToPlain, plainToClass } from 'class-transformer'
import { Service, ServiceTransformer, serviceTypes, IndyAgentService } from '../service'
import { Service, ServiceTransformer, serviceTypes, IndyAgentService, DidCommService } from '../service'

describe('Did | Service', () => {
it('should correctly transform Json to Service class', async () => {
Expand Down Expand Up @@ -31,7 +31,6 @@ describe('Did | Service', () => {
expect(transformed).toEqual(json)
})

// TODO: make more generic like in PublicKey.test.ts
describe('IndyAgentService', () => {
it('should correctly transform Json to IndyAgentService class', async () => {
const json = {
Expand Down Expand Up @@ -86,6 +85,63 @@ describe('Did | Service', () => {
})
})

describe('DidCommService', () => {
it('should correctly transform Json to DidCommService class', async () => {
const json = {
id: 'test-id',
type: 'did-communication',
recipientKeys: ['917a109d-eae3-42bc-9436-b02426d3ce2c', '348d5200-0f8f-42cc-aad9-61e0d082a674'],
routingKeys: ['0094df0b-7b6d-4ebb-82de-234a621fb615'],
accept: ['media-type'],
priority: 10,
serviceEndpoint: 'https://example.com',
}
const service = plainToClass(DidCommService, json)

expect(service).toMatchObject(json)
})

it('should correctly transform DidCommService class to Json', async () => {
const json = {
id: 'test-id',
type: 'did-communication',
recipientKeys: ['917a109d-eae3-42bc-9436-b02426d3ce2c', '348d5200-0f8f-42cc-aad9-61e0d082a674'],
routingKeys: ['0094df0b-7b6d-4ebb-82de-234a621fb615'],
accept: ['media-type'],
priority: 10,
serviceEndpoint: 'https://example.com',
}

const service = new DidCommService({
...json,
})

const transformed = classToPlain(service)

expect(transformed).toEqual(json)
})

it("should set 'priority' to default (0) when not present in constructor or during transformation", async () => {
const json = {
id: 'test-id',
type: 'did-communication',
recipientKeys: ['917a109d-eae3-42bc-9436-b02426d3ce2c', '348d5200-0f8f-42cc-aad9-61e0d082a674'],
routingKeys: ['0094df0b-7b6d-4ebb-82de-234a621fb615'],
accept: ['media-type'],
serviceEndpoint: 'https://example.com',
}

const transformService = plainToClass(DidCommService, json)
const constructorService = new DidCommService({ ...json })

expect(transformService.priority).toBe(0)
expect(constructorService.priority).toBe(0)

expect(classToPlain(transformService).priority).toBe(0)
expect(classToPlain(constructorService).priority).toBe(0)
})
})

describe('ServiceTransformer', () => {
class ServiceTransformerTest {
@ServiceTransformer()
Expand Down
8 changes: 8 additions & 0 deletions src/modules/connections/models/did/__tests__/diddoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
"recipientKeys": ["Q4zqM7aXqm7gDQkUVLng9h"],
"routingKeys": ["Q4zqM7aXqm7gDQkUVLng9h"],
"priority": 5
},
{
"id": "7",
"type": "did-communication",
"serviceEndpoint": "https://agent.com/did-comm",
"recipientKeys": ["DADEajsDSaksLng9h"],
"routingKeys": ["DADEajsDSaksLng9h"],
"priority": 10
}
],
"authentication": [
Expand Down
38 changes: 38 additions & 0 deletions src/modules/connections/models/did/service/DidCommService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ArrayNotEmpty, IsOptional, IsString } from 'class-validator'
import { Service } from './Service'

export class DidCommService extends Service {
public constructor(options: {
id: string
serviceEndpoint: string
recipientKeys: string[]
routingKeys?: string[]
accept?: string[]
priority?: number
}) {
super({ ...options, type: DidCommService.type })

if (options) {
this.recipientKeys = options.recipientKeys
this.routingKeys = options.routingKeys
this.accept = options.accept
if (options.priority) this.priority = options.priority
}
}

public static type = 'did-communication'

@ArrayNotEmpty()
@IsString({ each: true })
public recipientKeys!: string[]

@IsString({ each: true })
@IsOptional()
public routingKeys?: string[]

@IsString({ each: true })
@IsOptional()
public accept?: string[]

public priority = 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class IndyAgentService extends Service {
routingKeys?: string[]
priority?: number
}) {
super({ ...options, type: 'IndyAgent' })
super({ ...options, type: IndyAgentService.type })

if (options) {
this.recipientKeys = options.recipientKeys
Expand All @@ -18,7 +18,7 @@ export class IndyAgentService extends Service {
}
}

public type = 'IndyAgent'
public static type = 'IndyAgent'

@ArrayNotEmpty()
@IsString({ each: true })
Expand Down
6 changes: 4 additions & 2 deletions src/modules/connections/models/did/service/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Transform, ClassConstructor, plainToClass } from 'class-transformer'

import { IndyAgentService } from './IndyAgentService'
import { DidCommService } from './DidCommService'
import { Service } from './Service'

export const serviceTypes: { [key: string]: unknown | undefined } = {
IndyAgent: IndyAgentService,
[IndyAgentService.type]: IndyAgentService,
[DidCommService.type]: DidCommService,
}

/**
Expand Down Expand Up @@ -32,4 +34,4 @@ export function ServiceTransformer() {
)
}

export { IndyAgentService, Service }
export { IndyAgentService, DidCommService, Service }
4 changes: 2 additions & 2 deletions src/modules/connections/repository/ConnectionRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export class ConnectionRecord extends BaseRecord implements ConnectionStoragePro
}

public get myKey() {
const [service] = this.didDoc?.getServicesByClassType(IndyAgentService) ?? []
const [service] = this.didDoc?.didCommServices ?? []

if (!service) {
return null
Expand All @@ -128,7 +128,7 @@ export class ConnectionRecord extends BaseRecord implements ConnectionStoragePro
}

public get theirKey() {
const [service] = this.theirDidDoc?.getServicesByClassType(IndyAgentService) ?? []
const [service] = this.theirDidDoc?.didCommServices ?? []

if (!service) {
return null
Expand Down
Loading

0 comments on commit b0b89de

Please sign in to comment.