diff --git a/README.md b/README.md index 7ba104900b..2c2d8176f9 100644 --- a/README.md +++ b/README.md @@ -193,10 +193,7 @@ Although Aries Framework JavaScript tries to follow the standards as described i If you would like to contribute to the framework, please read the [Framework Developers README](/DEVREADME.md) and the [CONTRIBUTING](/CONTRIBUTING.md) guidelines. These documents will provide more information to get you started! -The Aries Framework JavaScript call takes place every week at Thursday, 6AM Pacific Time. See [World Time Buddy](https://www.worldtimebuddy.com/?qm=1&lid=5,2759794,8&h=5&date=2023-5-19&sln=9-10&hf=1) for the time in your timezone. The meeting is held on [Zoom](https://zoom.us/j/99751084865?pwd=TW1rU0FDVTBqUlhnWnY2NERkd1diZz09). -This meeting is for contributors to groom and plan the backlog, and discuss issues. -Meeting agendas and recordings can be found [here](https://wiki.hyperledger.org/display/ARIES/Framework+JS+Meetings). -Feel free to join! +There are regular community working groups to discuss ongoing efforts within the framework, showcase items you've built with Credo, or ask questions. See [Meeting Information](https://github.com/openwallet-foundation/credo-ts/wiki/Meeting-Information) for up to date information on the meeting schedule. Everyone is welcome to join! ## License diff --git a/packages/anoncreds-rs/tests/LocalDidResolver.ts b/packages/anoncreds-rs/tests/LocalDidResolver.ts index 1f50c1b3aa..c10434f205 100644 --- a/packages/anoncreds-rs/tests/LocalDidResolver.ts +++ b/packages/anoncreds-rs/tests/LocalDidResolver.ts @@ -4,6 +4,7 @@ import { DidsApi } from '@aries-framework/core' export class LocalDidResolver implements DidResolver { public readonly supportedMethods = ['sov', 'indy'] + public readonly allowsCaching = false public async resolve(agentContext: AgentContext, did: string): Promise { const didDocumentMetadata = {} diff --git a/packages/cheqd/src/dids/CheqdDidResolver.ts b/packages/cheqd/src/dids/CheqdDidResolver.ts index edd94c2aa1..0b87fe9061 100644 --- a/packages/cheqd/src/dids/CheqdDidResolver.ts +++ b/packages/cheqd/src/dids/CheqdDidResolver.ts @@ -19,6 +19,7 @@ import { filterResourcesByNameAndType, getClosestResourceVersion, renderResource export class CheqdDidResolver implements DidResolver { public readonly supportedMethods = ['cheqd'] + public readonly allowsCaching = true public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { const didDocumentMetadata = {} diff --git a/packages/core/src/modules/connections/__tests__/InMemoryDidRegistry.ts b/packages/core/src/modules/connections/__tests__/InMemoryDidRegistry.ts index dc9e9e8107..ba17b1bb28 100644 --- a/packages/core/src/modules/connections/__tests__/InMemoryDidRegistry.ts +++ b/packages/core/src/modules/connections/__tests__/InMemoryDidRegistry.ts @@ -15,6 +15,8 @@ import { DidRecord, DidDocumentRole, DidRepository } from '../../dids' export class InMemoryDidRegistry implements DidRegistrar, DidResolver { public readonly supportedMethods = ['inmemory'] + public readonly allowsCaching = false + private dids: Record = {} public async create(agentContext: AgentContext, options: DidCreateOptions): Promise { diff --git a/packages/core/src/modules/dids/domain/DidResolver.ts b/packages/core/src/modules/dids/domain/DidResolver.ts index 050ea2cd97..6154030023 100644 --- a/packages/core/src/modules/dids/domain/DidResolver.ts +++ b/packages/core/src/modules/dids/domain/DidResolver.ts @@ -3,6 +3,8 @@ import type { ParsedDid, DidResolutionResult, DidResolutionOptions } from '../ty export interface DidResolver { readonly supportedMethods: string[] + readonly allowsCaching: boolean + resolve( agentContext: AgentContext, did: string, diff --git a/packages/core/src/modules/dids/methods/jwk/JwkDidResolver.ts b/packages/core/src/modules/dids/methods/jwk/JwkDidResolver.ts index 8207814895..5ac705c860 100644 --- a/packages/core/src/modules/dids/methods/jwk/JwkDidResolver.ts +++ b/packages/core/src/modules/dids/methods/jwk/JwkDidResolver.ts @@ -7,6 +7,11 @@ import { DidJwk } from './DidJwk' export class JwkDidResolver implements DidResolver { public readonly supportedMethods = ['jwk'] + /** + * No remote resolving done, did document is dynamically constructed. To not pollute the cache we don't allow caching + */ + public readonly allowsCaching = false + public async resolve(agentContext: AgentContext, did: string): Promise { const didDocumentMetadata = {} diff --git a/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts b/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts index 41f4a0e221..7719c29d1c 100644 --- a/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts +++ b/packages/core/src/modules/dids/methods/key/KeyDidResolver.ts @@ -7,6 +7,11 @@ import { DidKey } from './DidKey' export class KeyDidResolver implements DidResolver { public readonly supportedMethods = ['key'] + /** + * No remote resolving done, did document is dynamically constructed. To not pollute the cache we don't allow caching + */ + public readonly allowsCaching = false + public async resolve(agentContext: AgentContext, did: string): Promise { const didDocumentMetadata = {} diff --git a/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts index 37b0968820..4e15bee8fd 100644 --- a/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts +++ b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts @@ -14,6 +14,11 @@ import { didToNumAlgo4DidDocument, isShortFormDidPeer4 } from './peerDidNumAlgo4 export class PeerDidResolver implements DidResolver { public readonly supportedMethods = ['peer'] + /** + * No remote resolving done, did document is fetched from storage. To not pollute the cache we don't allow caching + */ + public readonly allowsCaching = false + public async resolve(agentContext: AgentContext, did: string): Promise { const didRepository = agentContext.dependencyManager.resolve(DidRepository) diff --git a/packages/core/src/modules/dids/methods/web/WebDidResolver.ts b/packages/core/src/modules/dids/methods/web/WebDidResolver.ts index 77d9b1e295..f56dbebbfd 100644 --- a/packages/core/src/modules/dids/methods/web/WebDidResolver.ts +++ b/packages/core/src/modules/dids/methods/web/WebDidResolver.ts @@ -11,6 +11,8 @@ import { DidDocument } from '../../domain' export class WebDidResolver implements DidResolver { public readonly supportedMethods + public readonly allowsCaching = true + // FIXME: Would be nice if we don't have to provide a did resolver instance private _resolverInstance = new Resolver() private resolver = didWeb.getResolver() diff --git a/packages/core/src/modules/dids/services/DidResolverService.ts b/packages/core/src/modules/dids/services/DidResolverService.ts index 7f97d3f9d1..a24c8908ba 100644 --- a/packages/core/src/modules/dids/services/DidResolverService.ts +++ b/packages/core/src/modules/dids/services/DidResolverService.ts @@ -6,7 +6,10 @@ import { InjectionSymbols } from '../../../constants' import { AriesFrameworkError } from '../../../error' import { Logger } from '../../../logger' import { injectable, inject } from '../../../plugins' +import { JsonTransformer } from '../../../utils' +import { CacheModuleConfig } from '../../cache' import { DidsModuleConfig } from '../DidsModuleConfig' +import { DidDocument } from '../domain' import { parseDid } from '../domain/parse' @injectable() @@ -53,9 +56,69 @@ export class DidResolverService { } } - return resolver.resolve(agentContext, parsed.did, parsed, options) + // extract caching options and set defaults + const { useCache = true, cacheDurationInSeconds = 300, persistInCache = true } = options + const cacheKey = `did:resolver:${parsed.did}` + + if (resolver.allowsCaching && useCache) { + const cache = agentContext.dependencyManager.resolve(CacheModuleConfig).cache + // FIXME: in multi-tenancy it can be that the same cache is used for different agent contexts + // This may become a problem when resolving dids, as you can get back a cache hit for a different + // tenant. did:peer has disabled caching, and I think we should just recommend disabling caching + // for these private dids + // We could allow providing a custom cache prefix in the resolver options, so that the cache key + // can be recognized in the cache implementation + const cachedDidDocument = await cache.get }>( + agentContext, + cacheKey + ) + + if (cachedDidDocument) { + return { + ...cachedDidDocument, + didDocument: JsonTransformer.fromJSON(cachedDidDocument.didDocument, DidDocument), + didResolutionMetadata: { + ...cachedDidDocument.didResolutionMetadata, + servedFromCache: true, + }, + } + } + } + + let resolutionResult = await resolver.resolve(agentContext, parsed.did, parsed, options) + // Avoid overwriting existing document + resolutionResult = { + ...resolutionResult, + didResolutionMetadata: { + ...resolutionResult.didResolutionMetadata, + resolutionTime: Date.now(), + // Did resolver implementation might use did method specific caching strategy + // We only set to false if not defined by the resolver + servedFromCache: resolutionResult.didResolutionMetadata.servedFromCache ?? false, + }, + } + + if (resolutionResult.didDocument && resolver.allowsCaching && persistInCache) { + const cache = agentContext.dependencyManager.resolve(CacheModuleConfig).cache + await cache.set( + agentContext, + cacheKey, + { + ...resolutionResult, + didDocument: resolutionResult.didDocument.toJSON(), + }, + // Set cache duration + cacheDurationInSeconds + ) + } + + return resolutionResult } + /** + * Resolve a did document. This uses the default resolution options, and thus + * will use caching if available. + */ public async resolveDidDocument(agentContext: AgentContext, did: string) { const { didDocument, 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 00b17ad458..76573d0ed3 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 { CacheModuleConfig, InMemoryLruCache } from '../../../cache' import { DidsModuleConfig } from '../../DidsModuleConfig' import didKeyEd25519Fixture from '../../__tests__/__fixtures__/didKeyEd25519.json' import { DidDocument } from '../../domain' @@ -9,12 +10,16 @@ import { parseDid } from '../../domain/parse' import { DidResolverService } from '../DidResolverService' const didResolverMock = { + allowsCaching: true, supportedMethods: ['key'], resolve: jest.fn(), } as DidResolver +const cache = new InMemoryLruCache({ limit: 10 }) const agentConfig = getAgentConfig('DidResolverService') -const agentContext = getAgentContext() +const agentContext = getAgentContext({ + registerInstances: [[CacheModuleConfig, new CacheModuleConfig({ cache })]], +}) describe('DidResolverService', () => { const didResolverService = new DidResolverService( @@ -22,6 +27,10 @@ describe('DidResolverService', () => { new DidsModuleConfig({ resolvers: [didResolverMock] }) ) + afterEach(() => { + jest.clearAllMocks() + }) + it('should correctly find and call the correct resolver for a specified did', async () => { const returnValue = { didDocument: JsonTransformer.fromJSON(didKeyEd25519Fixture, DidDocument), @@ -33,7 +42,14 @@ describe('DidResolverService', () => { mockFunction(didResolverMock.resolve).mockResolvedValue(returnValue) const result = await didResolverService.resolve(agentContext, 'did:key:xxxx', { someKey: 'string' }) - expect(result).toEqual(returnValue) + expect(result).toEqual({ + ...returnValue, + didResolutionMetadata: { + ...returnValue.didResolutionMetadata, + resolutionTime: expect.any(Number), + servedFromCache: false, + }, + }) expect(didResolverMock.resolve).toHaveBeenCalledTimes(1) expect(didResolverMock.resolve).toHaveBeenCalledWith(agentContext, 'did:key:xxxx', parseDid('did:key:xxxx'), { @@ -41,6 +57,57 @@ describe('DidResolverService', () => { }) }) + it('should return cached did document when resolved multiple times within caching duration', async () => { + const returnValue = { + didDocument: JsonTransformer.fromJSON(didKeyEd25519Fixture, DidDocument), + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + } + mockFunction(didResolverMock.resolve).mockResolvedValue(returnValue) + + const result = await didResolverService.resolve(agentContext, 'did:key:cached', { someKey: 'string' }) + const cachedValue = await cache.get(agentContext, 'did:resolver:did:key:cached') + + expect(result).toEqual({ + ...returnValue, + didResolutionMetadata: { + ...returnValue.didResolutionMetadata, + resolutionTime: expect.any(Number), + servedFromCache: false, + }, + }) + + expect(cachedValue).toEqual({ + ...returnValue, + didDocument: returnValue.didDocument.toJSON(), + didResolutionMetadata: { + ...returnValue.didResolutionMetadata, + resolutionTime: expect.any(Number), + servedFromCache: false, + }, + }) + + expect(didResolverMock.resolve).toHaveBeenCalledTimes(1) + expect(didResolverMock.resolve).toHaveBeenCalledWith(agentContext, 'did:key:cached', parseDid('did:key:cached'), { + someKey: 'string', + }) + + const resultCached = await didResolverService.resolve(agentContext, 'did:key:cached', { someKey: 'string' }) + expect(resultCached).toEqual({ + ...returnValue, + didResolutionMetadata: { + ...returnValue.didResolutionMetadata, + resolutionTime: expect.any(Number), + servedFromCache: true, + }, + }) + + // Still called once because served from cache + expect(didResolverMock.resolve).toHaveBeenCalledTimes(1) + }) + it("should return an error with 'invalidDid' if the did string couldn't be parsed", async () => { const did = 'did:__Asd:asdfa' diff --git a/packages/core/src/modules/dids/types.ts b/packages/core/src/modules/dids/types.ts index 359616a818..ccbc53fa70 100644 --- a/packages/core/src/modules/dids/types.ts +++ b/packages/core/src/modules/dids/types.ts @@ -2,11 +2,34 @@ import type { DidDocument } from './domain' import type { DIDDocumentMetadata, DIDResolutionMetadata, DIDResolutionOptions, ParsedDID } from 'did-resolver' export type ParsedDid = ParsedDID -export type DidResolutionOptions = DIDResolutionOptions export type DidDocumentMetadata = DIDDocumentMetadata +export interface DidResolutionOptions extends DIDResolutionOptions { + /** + * Whether to resolve the did document from the cache. + * + * @default true + */ + useCache?: boolean + + /** + * Whether to persist the did document in the cache. + * + * @default true + */ + persistInCache?: boolean + + /** + * How many seconds to persist the resolved document + * + * @default 3600 + */ + cacheDurationInSeconds?: number +} + export interface DidResolutionMetadata extends DIDResolutionMetadata { message?: string + servedFromCache?: boolean } export interface DidResolutionResult { diff --git a/packages/core/src/modules/oob/OutOfBandService.ts b/packages/core/src/modules/oob/OutOfBandService.ts index 1884cb694a..93fbb26c9e 100644 --- a/packages/core/src/modules/oob/OutOfBandService.ts +++ b/packages/core/src/modules/oob/OutOfBandService.ts @@ -67,7 +67,6 @@ export class OutOfBandService { // initiating the flow const outOfBandInvitation = new OutOfBandInvitation({ id: did, - label: '', services: [did], handshakeProtocols, }) diff --git a/packages/core/src/modules/oob/helpers.ts b/packages/core/src/modules/oob/helpers.ts index ccd35e7a31..be2fe3f0b4 100644 --- a/packages/core/src/modules/oob/helpers.ts +++ b/packages/core/src/modules/oob/helpers.ts @@ -45,7 +45,8 @@ export function convertToOldInvitation(newInvitation: OutOfBandInvitation) { if (typeof service === 'string') { options = { id: newInvitation.id, - label: newInvitation.label, + // label is optional + label: newInvitation.label ?? '', did: service, imageUrl: newInvitation.imageUrl, appendedAttachments: newInvitation.appendedAttachments, @@ -53,7 +54,8 @@ export function convertToOldInvitation(newInvitation: OutOfBandInvitation) { } else { options = { id: newInvitation.id, - label: newInvitation.label, + // label is optional + label: newInvitation.label ?? '', recipientKeys: service.recipientKeys.map(didKeyToVerkey), routingKeys: service.routingKeys?.map(didKeyToVerkey), serviceEndpoint: service.serviceEndpoint, diff --git a/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts b/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts index 80118119a0..d900284329 100644 --- a/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts +++ b/packages/core/src/modules/oob/messages/OutOfBandInvitation.ts @@ -17,7 +17,7 @@ import { OutOfBandDidCommService } from '../domain/OutOfBandDidCommService' export interface OutOfBandInvitationOptions { id?: string - label: string + label?: string goalCode?: string goal?: string accept?: string[] @@ -124,7 +124,7 @@ export class OutOfBandInvitation extends AgentMessage { public readonly type = OutOfBandInvitation.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/out-of-band/1.1/invitation') - public readonly label!: string + public readonly label?: string @Expose({ name: 'goal_code' }) public readonly goalCode?: string diff --git a/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts b/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts index 3260e806d2..6e3a85438a 100644 --- a/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts @@ -45,12 +45,6 @@ export class PresentationExchangeProofFormatService implements ProofFormatServic public readonly formatKey = 'presentationExchange' as const private presentationExchangeService(agentContext: AgentContext) { - if (!agentContext.dependencyManager.isRegistered(DifPresentationExchangeService)) { - throw new AriesFrameworkError( - 'DifPresentationExchangeService is not registered on the Agent. Please provide the PresentationExchangeModule as a module on the agent' - ) - } - return agentContext.dependencyManager.resolve(DifPresentationExchangeService) } diff --git a/packages/core/src/utils/__tests__/shortenedUrl.test.ts b/packages/core/src/utils/__tests__/parseInvitation.test.ts similarity index 69% rename from packages/core/src/utils/__tests__/shortenedUrl.test.ts rename to packages/core/src/utils/__tests__/parseInvitation.test.ts index ffe4a2934a..b9966f52cd 100644 --- a/packages/core/src/utils/__tests__/shortenedUrl.test.ts +++ b/packages/core/src/utils/__tests__/parseInvitation.test.ts @@ -1,9 +1,11 @@ +import { agentDependencies } from '../../../tests' import { ConnectionInvitationMessage } from '../../modules/connections' import { InvitationType, OutOfBandInvitation } from '../../modules/oob' import { convertToNewInvitation } from '../../modules/oob/helpers' +import { JsonEncoder } from '../JsonEncoder' import { JsonTransformer } from '../JsonTransformer' import { MessageValidator } from '../MessageValidator' -import { oobInvitationFromShortUrl } from '../parseInvitation' +import { oobInvitationFromShortUrl, parseInvitationShortUrl } from '../parseInvitation' const mockOobInvite = { '@type': 'did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/out-of-band/1.0/invitation', @@ -21,6 +23,16 @@ const mockConnectionInvite = { recipientKeys: ['5Gvpf9M4j7vWpHyeTyvBKbjYe7qWc72kGo6qZaLHkLrd'], } +const mockLegacyConnectionless = { + '@id': '035b6404-f496-4cb6-a2b5-8bd09e8c92c1', + '@type': 'https://didcomm.org/some-protocol/1.0/some-message', + '~service': { + recipientKeys: ['5Gvpf9M4j7vWpHyeTyvBKbjYe7qWc72kGo6qZaLHkLrd'], + routingKeys: ['5Gvpf9M4j7vWpHyeTyvBKbjYe7qWc72kGo6qZaLHkLrd'], + serviceEndpoint: 'https://example.com/endpoint', + }, +} + const header = new Headers() const dummyHeader = new Headers() @@ -49,6 +61,13 @@ const mockedResponseOobUrl = { dummyHeader.forEach(mockedResponseOobUrl.headers.append) +const mockedLegacyConnectionlessInvitationJson = { + status: 200, + ok: true, + json: async () => mockLegacyConnectionless, + headers: header, +} as Response + const mockedResponseConnectionJson = { status: 200, ok: true, @@ -103,15 +122,78 @@ describe('shortened urls resolving to oob invitations', () => { }) }) +describe('legacy connectionless', () => { + test('parse url containing d_m ', async () => { + const parsed = await parseInvitationShortUrl( + `https://example.com?d_m=${JsonEncoder.toBase64URL(mockLegacyConnectionless)}`, + agentDependencies + ) + expect(parsed.toJSON()).toMatchObject({ + '@id': expect.any(String), + '@type': 'https://didcomm.org/out-of-band/1.1/invitation', + label: undefined, + 'requests~attach': [ + { + '@id': expect.any(String), + data: { + base64: + 'eyJAaWQiOiIwMzViNjQwNC1mNDk2LTRjYjYtYTJiNS04YmQwOWU4YzkyYzEiLCJAdHlwZSI6Imh0dHBzOi8vZGlkY29tbS5vcmcvc29tZS1wcm90b2NvbC8xLjAvc29tZS1tZXNzYWdlIn0=', + }, + 'mime-type': 'application/json', + }, + ], + services: [ + { + id: expect.any(String), + recipientKeys: ['did:key:z6MkijBsFPbW4fQyvnpM9Yt2AhHYTh7N1zH6xp1mPrJJfZe1'], + routingKeys: ['did:key:z6MkijBsFPbW4fQyvnpM9Yt2AhHYTh7N1zH6xp1mPrJJfZe1'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + }, + ], + }) + }) + + test('parse short url returning legacy connectionless invitation to out of band invitation', async () => { + const parsed = await oobInvitationFromShortUrl(mockedLegacyConnectionlessInvitationJson) + expect(parsed.toJSON()).toMatchObject({ + '@id': expect.any(String), + '@type': 'https://didcomm.org/out-of-band/1.1/invitation', + label: undefined, + 'requests~attach': [ + { + '@id': expect.any(String), + data: { + base64: + 'eyJAaWQiOiIwMzViNjQwNC1mNDk2LTRjYjYtYTJiNS04YmQwOWU4YzkyYzEiLCJAdHlwZSI6Imh0dHBzOi8vZGlkY29tbS5vcmcvc29tZS1wcm90b2NvbC8xLjAvc29tZS1tZXNzYWdlIn0=', + }, + 'mime-type': 'application/json', + }, + ], + services: [ + { + id: expect.any(String), + recipientKeys: ['did:key:z6MkijBsFPbW4fQyvnpM9Yt2AhHYTh7N1zH6xp1mPrJJfZe1'], + routingKeys: ['did:key:z6MkijBsFPbW4fQyvnpM9Yt2AhHYTh7N1zH6xp1mPrJJfZe1'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + }, + ], + }) + }) +}) + 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) expect(short).toEqual(connectionInvitationToNew) }) + test('Resolve a mocked Response in the form of a connection invitation encoded in an url c_i query parameter', async () => { const short = await oobInvitationFromShortUrl(mockedResponseConnectionUrl) expect(short).toEqual(connectionInvitationToNew) }) + test('Resolve a mocked Response in the form of a connection invitation encoded in an url oob query parameter', async () => { const mockedResponseConnectionInOobUrl = { status: 200, diff --git a/packages/core/src/utils/parseInvitation.ts b/packages/core/src/utils/parseInvitation.ts index 1fb9d99b03..4b5b5232a8 100644 --- a/packages/core/src/utils/parseInvitation.ts +++ b/packages/core/src/utils/parseInvitation.ts @@ -59,6 +59,9 @@ export const parseInvitationJson = (invitationJson: Record): Ou const outOfBandInvitation = convertToNewInvitation(invitation) outOfBandInvitation.invitationType = InvitationType.Connection return outOfBandInvitation + } else if (invitationJson['~service']) { + // This is probably a legacy connectionless invitation + return transformLegacyConnectionlessInvitationToOutOfBandInvitation(invitationJson) } else { throw new AriesFrameworkError(`Invitation with '@type' ${parsedMessageType.messageTypeUri} not supported.`) } @@ -105,6 +108,30 @@ export const oobInvitationFromShortUrl = async (response: Response): Promise) { + const agentMessage = JsonTransformer.fromJSON(messageJson, AgentMessage) + + // ~service is required for legacy connectionless invitations + if (!agentMessage.service) { + throw new AriesFrameworkError('Invalid legacy connectionless invitation url. Missing ~service decorator.') + } + + // This destructuring removes the ~service property from the message, and + // we can can use messageWithoutService to create the out of band invitation + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { '~service': service, ...messageWithoutService } = messageJson + + // transform into out of band invitation + const invitation = new OutOfBandInvitation({ + services: [OutOfBandDidCommService.fromResolvedDidCommService(agentMessage.service.resolvedDidCommService)], + }) + + invitation.invitationType = InvitationType.Connectionless + invitation.addRequest(JsonTransformer.fromJSON(messageWithoutService, AgentMessage)) + + return invitation +} + /** * Parses URL containing encoded invitation and returns invitation message. Compatible with * parsing short Urls @@ -126,30 +153,7 @@ export const parseInvitationShortUrl = async ( // Legacy connectionless invitation else if (parsedUrl['d_m']) { const messageJson = JsonEncoder.fromBase64(parsedUrl['d_m'] as string) - const agentMessage = JsonTransformer.fromJSON(messageJson, AgentMessage) - - // ~service is required for legacy connectionless invitations - if (!agentMessage.service) { - throw new AriesFrameworkError('Invalid legacy connectionless invitation url. Missing ~service decorator.') - } - - // This destructuring removes the ~service property from the message, and - // we can can use messageWithoutService to create the out of band invitation - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { '~service': service, ...messageWithoutService } = messageJson - - // transform into out of band invitation - const invitation = new OutOfBandInvitation({ - // The label is currently required by the OutOfBandInvitation class, but not according to the specification. - // FIXME: In 0.5.0 we will make this optional: https://github.com/hyperledger/aries-framework-javascript/issues/1524 - label: '', - services: [OutOfBandDidCommService.fromResolvedDidCommService(agentMessage.service.resolvedDidCommService)], - }) - - invitation.invitationType = InvitationType.Connectionless - invitation.addRequest(JsonTransformer.fromJSON(messageWithoutService, AgentMessage)) - - return invitation + return transformLegacyConnectionlessInvitationToOutOfBandInvitation(messageJson) } else { try { const outOfBandInvitation = await oobInvitationFromShortUrl(await fetchShortUrl(invitationUrl, dependencies)) diff --git a/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts b/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts index 1c486eb3aa..c84999a8c1 100644 --- a/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts +++ b/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts @@ -16,6 +16,8 @@ import { addServicesFromEndpointsAttrib } from './didSovUtil' export class IndySdkIndyDidResolver implements DidResolver { public readonly supportedMethods = ['indy'] + public readonly allowsCaching = true + public async resolve(agentContext: AgentContext, did: string): Promise { const didDocumentMetadata = {} diff --git a/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts index ff6afc9571..3a881db50e 100644 --- a/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts +++ b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts @@ -12,6 +12,8 @@ import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './didSovU export class IndySdkSovDidResolver implements DidResolver { public readonly supportedMethods = ['sov'] + public readonly allowsCaching = true + public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { const didDocumentMetadata = {} diff --git a/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts b/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts index 89271f5fdd..7b2dfd768c 100644 --- a/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts +++ b/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts @@ -9,6 +9,8 @@ import { buildDidDocument } from './didIndyUtil' export class IndyVdrIndyDidResolver implements DidResolver { public readonly supportedMethods = ['indy'] + public readonly allowsCaching = true + public async resolve(agentContext: AgentContext, did: string): Promise { const didDocumentMetadata = {} try { diff --git a/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts b/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts index 4484586078..2563bbbd18 100644 --- a/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts +++ b/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts @@ -12,6 +12,8 @@ import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './didSovU export class IndyVdrSovDidResolver implements DidResolver { public readonly supportedMethods = ['sov'] + public readonly allowsCaching = true + public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { const didDocumentMetadata = {} diff --git a/packages/tenants/tests/tenants.e2e.test.ts b/packages/tenants/tests/tenants.e2e.test.ts index f37e962449..ffd2518f5b 100644 --- a/packages/tenants/tests/tenants.e2e.test.ts +++ b/packages/tenants/tests/tenants.e2e.test.ts @@ -1,6 +1,6 @@ import type { InitConfig } from '@aries-framework/core' -import { ConnectionsModule, OutOfBandRecord, Agent } from '@aries-framework/core' +import { ConnectionsModule, OutOfBandRecord, Agent, CacheModule, InMemoryLruCache } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/node' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' @@ -39,6 +39,9 @@ const agent1 = new Agent({ connections: new ConnectionsModule({ autoAcceptConnections: true, }), + cache: new CacheModule({ + cache: new InMemoryLruCache({ limit: 500 }), + }), }, dependencies: agentDependencies, }) @@ -51,6 +54,9 @@ const agent2 = new Agent({ connections: new ConnectionsModule({ autoAcceptConnections: true, }), + cache: new CacheModule({ + cache: new InMemoryLruCache({ limit: 500 }), + }), }, dependencies: agentDependencies, })