diff --git a/packages/agent/package.json b/packages/agent/package.json index 8d44de2be..b902dcd87 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -74,7 +74,7 @@ "@tbd54566975/dwn-sdk-js": "0.4.5", "@web5/common": "1.0.0", "@web5/crypto": "workspace:*", - "@web5/dids": "1.1.0", + "@web5/dids": "workspace:*", "abstract-level": "1.0.4", "ed25519-keygen": "0.4.11", "isomorphic-ws": "^5.0.0", diff --git a/packages/agent/src/agent-did-resolver-cache.ts b/packages/agent/src/agent-did-resolver-cache.ts new file mode 100644 index 000000000..a10d325d9 --- /dev/null +++ b/packages/agent/src/agent-did-resolver-cache.ts @@ -0,0 +1,55 @@ +import { DidResolutionResult, DidResolverCache, DidResolverCacheLevel, DidResolverCacheLevelParams } from "@web5/dids"; +import { Web5PlatformAgent } from "./types/agent.js"; + + +export class AgentDidResolverCache extends DidResolverCacheLevel implements DidResolverCache { + + private _agent?: Web5PlatformAgent; + + private _resolving: Record<string, boolean> = {}; + + constructor({ agent, db, location, ttl }: DidResolverCacheLevelParams & { agent?: Web5PlatformAgent }) { + super ({ db, location, ttl }); + this._agent = agent; + } + + get agent() { + if (!this._agent) { + throw new Error("Agent not initialized"); + } + return this._agent; + } + + set agent(agent: Web5PlatformAgent) { + this._agent = agent; + } + + async get(did: string): Promise<DidResolutionResult | void> { + try { + const str = await this.cache.get(did); + const cachedResult = JSON.parse(str); + if (!this._resolving[did] && Date.now() >= cachedResult.ttlMillis) { + this._resolving[did] = true; + const list = await this.agent.identity.list(); + if (this.agent.agentDid.uri === did || list.find(identity => identity.did.uri === did)) { + this.agent.did.resolve(did).then(result => { + if (!result.didResolutionMetadata.error) { + this.set(did, result); + } + }).finally(() => delete this._resolving[did]) + } + else { + delete this._resolving[did]; + this.cache.nextTick(() => this.cache.del(did)); + return; + } + } + return cachedResult.value; + } catch(error: any) { + if (error.notFound) { + return; + } + throw error; + } + } +} \ No newline at end of file diff --git a/packages/agent/src/did-api.ts b/packages/agent/src/did-api.ts index 8b533b635..4df8c896b 100644 --- a/packages/agent/src/did-api.ts +++ b/packages/agent/src/did-api.ts @@ -3,12 +3,16 @@ import type { DidMetadata, PortableDid, DidMethodApi, - DidResolverCache, DidDhtCreateOptions, DidJwkCreateOptions, DidResolutionResult, DidResolutionOptions, DidVerificationMethod, + DidUrlDereferencer, + DidResolver, + DidDereferencingOptions, + DidDereferencingResult, + DidResolverCache, } from '@web5/dids'; import { BearerDid, Did, UniversalResolver } from '@web5/dids'; @@ -18,7 +22,7 @@ import type { AgentKeyManager } from './types/key-manager.js'; import type { ResponseStatus, Web5PlatformAgent } from './types/agent.js'; import { InMemoryDidStore } from './store-did.js'; -import { DidResolverCacheMemory } from './prototyping/dids/resolver-cache-memory.js'; +import { AgentDidResolverCache } from './agent-did-resolver-cache.js'; export enum DidInterface { Create = 'Create', @@ -84,11 +88,9 @@ export interface DidApiParams { agent?: Web5PlatformAgent; /** - * An optional `DidResolverCache` instance used for caching resolved DID documents. - * - * Providing a cache implementation can significantly enhance resolution performance by avoiding - * redundant resolutions for previously resolved DIDs. If omitted, a no-operation cache is used, - * which effectively disables caching. + * An optional `AgentDidResolverCache` instance used for caching resolved DID documents. + * + * If omitted, the default LevelDB and parameters are used to create a new cache instance. */ resolverCache?: DidResolverCache; @@ -101,7 +103,7 @@ export function isDidRequest<T extends DidInterface>( return didRequest.messageType === messageType; } -export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager> extends UniversalResolver { +export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager> implements DidResolver, DidUrlDereferencer { /** * Holds the instance of a `Web5PlatformAgent` that represents the current execution context for * the `AgentDidApi`. This agent is used to interact with other Web5 agent components. It's vital @@ -110,8 +112,12 @@ export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager> */ private _agent?: Web5PlatformAgent; + private _cache: DidResolverCache; + private _didMethods: Map<string, DidMethodApi> = new Map(); + private _resolver: UniversalResolver; + private _store: AgentDataStore<PortableDid>; constructor({ agent, didMethods, resolverCache, store }: DidApiParams) { @@ -119,11 +125,11 @@ export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager> throw new TypeError(`AgentDidApi: Required parameter missing: 'didMethods'`); } - // Initialize the DID resolver with the given DID methods and resolver cache, or use a default - // in-memory cache if none is provided. - super({ - didResolvers : didMethods, - cache : resolverCache ?? new DidResolverCacheMemory() + this._cache = resolverCache ?? new AgentDidResolverCache({ location: 'DATA/AGENT/DID_CACHE' }); + + this._resolver = new UniversalResolver({ + cache: this._cache, + didResolvers: didMethods }); this._agent = agent; @@ -152,6 +158,11 @@ export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager> set agent(agent: Web5PlatformAgent) { this._agent = agent; + + // AgentDidResolverCache should set the agent if it is the type of cache being used + if ('agent' in this._cache) { + this._cache.agent = agent; + } } public async create({ @@ -342,6 +353,14 @@ export class AgentDidApi<TKeyManager extends AgentKeyManager = AgentKeyManager> throw new Error(`AgentDidApi: Unsupported request type: ${request.messageType}`); } + public async resolve(didUri: string, options?: DidResolutionOptions): Promise<DidResolutionResult> { + return this._resolver.resolve(didUri, options); + } + + public async dereference(didUrl: string, options?: DidDereferencingOptions): Promise<DidDereferencingResult> { + return this._resolver.dereference(didUrl); + } + private getMethod(methodName: string): DidMethodApi { const didMethodApi = this._didMethods.get(methodName); diff --git a/packages/agent/src/identity-api.ts b/packages/agent/src/identity-api.ts index c64ef46ef..9f689f287 100644 --- a/packages/agent/src/identity-api.ts +++ b/packages/agent/src/identity-api.ts @@ -183,14 +183,14 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana // Retrieve the list of Identities from the Agent's Identity store. const storedIdentities = await this._store.list({ agent: this.agent, tenant }); - const identities: BearerIdentity[] = []; - - for (const metadata of storedIdentities) { - const identity = await this.get({ didUri: metadata.uri, tenant: metadata.tenant }); - identities.push(identity!); - } - - return identities; + const identities = await Promise.all( + storedIdentities.map(async metadata => { + console.log('metadata', metadata.uri); + return this.get({ didUri: metadata.uri, tenant: metadata.tenant }); + }) + ); + + return identities.filter(identity => typeof identity !== 'undefined') as BearerIdentity[]; } public async manage({ portableIdentity }: { diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index 5ed8caa62..7f7457575 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -7,6 +7,7 @@ export type * from './types/permissions.js'; export type * from './types/sync.js'; export type * from './types/vc.js'; +export * from './agent-did-resolver-cache.js'; export * from './bearer-identity.js'; export * from './cached-permissions.js'; export * from './crypto-api.js'; diff --git a/packages/dids/src/resolver/resolver-cache-level.ts b/packages/dids/src/resolver/resolver-cache-level.ts index 69481e143..c4d2f2f80 100644 --- a/packages/dids/src/resolver/resolver-cache-level.ts +++ b/packages/dids/src/resolver/resolver-cache-level.ts @@ -73,10 +73,10 @@ type CachedDidResolutionResult = { */ export class DidResolverCacheLevel implements DidResolverCache { /** The underlying LevelDB store used for caching. */ - private cache; + protected cache; /** The time-to-live for cache entries in milliseconds. */ - private ttl: number; + protected ttl: number; constructor({ db, diff --git a/packages/dids/src/resolver/universal-resolver.ts b/packages/dids/src/resolver/universal-resolver.ts index 93ff11fb8..e938613ff 100644 --- a/packages/dids/src/resolver/universal-resolver.ts +++ b/packages/dids/src/resolver/universal-resolver.ts @@ -66,7 +66,7 @@ export class UniversalResolver implements DidResolver, DidUrlDereferencer { /** * A cache for storing resolved DID documents. */ - private cache: DidResolverCache; + protected cache: DidResolverCache; /** * A map to store method resolvers against method names. diff --git a/packages/user-agent/src/user-agent.ts b/packages/user-agent/src/user-agent.ts index 426a0668d..76c85fcca 100644 --- a/packages/user-agent/src/user-agent.ts +++ b/packages/user-agent/src/user-agent.ts @@ -12,6 +12,7 @@ import { ProcessDwnRequest, Web5PlatformAgent, AgentPermissionsApi, + AgentDidResolverCache, } from '@web5/agent'; import { LevelStore } from '@web5/common'; @@ -150,9 +151,11 @@ export class Web5UserAgent<TKeyManager extends AgentKeyManager = LocalKeyManager cryptoApi ??= new AgentCryptoApi(); + const didResolverCache = new AgentDidResolverCache({ location: `${dataPath}/DID_RESOLVERCACHE` }); + didApi ??= new AgentDidApi({ didMethods : [DidDht, DidJwk], - resolverCache : new DidResolverCacheLevel({ location: `${dataPath}/DID_RESOLVERCACHE` }), + resolverCache : didResolverCache, store : new DwnDidStore() }); @@ -171,7 +174,7 @@ export class Web5UserAgent<TKeyManager extends AgentKeyManager = LocalKeyManager syncApi ??= new AgentSyncApi({ syncEngine: new SyncEngineLevel({ dataPath }) }); // Instantiate the Agent using the provided or default components. - return new Web5UserAgent({ + const web5USerAgent = new Web5UserAgent({ agentDid, agentVault, cryptoApi, @@ -183,6 +186,11 @@ export class Web5UserAgent<TKeyManager extends AgentKeyManager = LocalKeyManager rpcClient, syncApi }); + + // Set the agent on the resolver cache + didResolverCache.agent = web5USerAgent; + + return web5USerAgent; } public async firstLaunch(): Promise<boolean> { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ebf0fabad..16e21be6f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,8 +65,8 @@ importers: specifier: workspace:* version: link:../crypto '@web5/dids': - specifier: 1.1.0 - version: 1.1.0 + specifier: workspace:* + version: link:../dids abstract-level: specifier: 1.0.4 version: 1.0.4