@enbox/agent 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/browser.mjs +11 -11
  2. package/dist/browser.mjs.map +4 -4
  3. package/dist/esm/anonymous-dwn-api.js +1 -1
  4. package/dist/esm/anonymous-dwn-api.js.map +1 -1
  5. package/dist/esm/connect.js +4 -10
  6. package/dist/esm/connect.js.map +1 -1
  7. package/dist/esm/dwn-api.js +144 -195
  8. package/dist/esm/dwn-api.js.map +1 -1
  9. package/dist/esm/dwn-protocol-cache.js +149 -0
  10. package/dist/esm/dwn-protocol-cache.js.map +1 -0
  11. package/dist/esm/dwn-record-upgrade.js +3 -3
  12. package/dist/esm/dwn-record-upgrade.js.map +1 -1
  13. package/dist/esm/hd-identity-vault.js +6 -5
  14. package/dist/esm/hd-identity-vault.js.map +1 -1
  15. package/dist/esm/identity-api.js +0 -2
  16. package/dist/esm/identity-api.js.map +1 -1
  17. package/dist/esm/oidc.js +2 -1
  18. package/dist/esm/oidc.js.map +1 -1
  19. package/dist/esm/permissions-api.js +24 -6
  20. package/dist/esm/permissions-api.js.map +1 -1
  21. package/dist/esm/prototyping/crypto/jose/jwe-flattened.js +1 -1
  22. package/dist/esm/prototyping/crypto/jose/jwe-flattened.js.map +1 -1
  23. package/dist/esm/prototyping/crypto/jose/jwe.js +11 -3
  24. package/dist/esm/prototyping/crypto/jose/jwe.js.map +1 -1
  25. package/dist/esm/store-data-protocols.js +2 -2
  26. package/dist/esm/store-data-protocols.js.map +1 -1
  27. package/dist/esm/sync-api.js +3 -0
  28. package/dist/esm/sync-api.js.map +1 -1
  29. package/dist/esm/sync-engine-level.js +447 -29
  30. package/dist/esm/sync-engine-level.js.map +1 -1
  31. package/dist/esm/test-harness.js +3 -5
  32. package/dist/esm/test-harness.js.map +1 -1
  33. package/dist/esm/types/dwn.js.map +1 -1
  34. package/dist/types/anonymous-dwn-api.d.ts +3 -3
  35. package/dist/types/anonymous-dwn-api.d.ts.map +1 -1
  36. package/dist/types/connect.d.ts.map +1 -1
  37. package/dist/types/dwn-api.d.ts +11 -18
  38. package/dist/types/dwn-api.d.ts.map +1 -1
  39. package/dist/types/dwn-protocol-cache.d.ts +76 -0
  40. package/dist/types/dwn-protocol-cache.d.ts.map +1 -0
  41. package/dist/types/hd-identity-vault.d.ts.map +1 -1
  42. package/dist/types/identity-api.d.ts.map +1 -1
  43. package/dist/types/oidc.d.ts.map +1 -1
  44. package/dist/types/permissions-api.d.ts.map +1 -1
  45. package/dist/types/prototyping/crypto/jose/jwe-flattened.d.ts.map +1 -1
  46. package/dist/types/prototyping/crypto/jose/jwe.d.ts +12 -2
  47. package/dist/types/prototyping/crypto/jose/jwe.d.ts.map +1 -1
  48. package/dist/types/sync-api.d.ts +3 -4
  49. package/dist/types/sync-api.d.ts.map +1 -1
  50. package/dist/types/sync-engine-level.d.ts +63 -5
  51. package/dist/types/sync-engine-level.d.ts.map +1 -1
  52. package/dist/types/test-harness.d.ts.map +1 -1
  53. package/dist/types/types/dwn.d.ts +18 -19
  54. package/dist/types/types/dwn.d.ts.map +1 -1
  55. package/dist/types/types/sync.d.ts +47 -5
  56. package/dist/types/types/sync.d.ts.map +1 -1
  57. package/package.json +6 -6
  58. package/src/anonymous-dwn-api.ts +4 -4
  59. package/src/connect.ts +4 -10
  60. package/src/dwn-api.ts +192 -250
  61. package/src/dwn-protocol-cache.ts +216 -0
  62. package/src/dwn-record-upgrade.ts +3 -3
  63. package/src/hd-identity-vault.ts +6 -5
  64. package/src/identity-api.ts +0 -2
  65. package/src/oidc.ts +2 -1
  66. package/src/permissions-api.ts +28 -6
  67. package/src/prototyping/crypto/jose/jwe-flattened.ts +4 -1
  68. package/src/prototyping/crypto/jose/jwe.ts +24 -2
  69. package/src/store-data-protocols.ts +2 -2
  70. package/src/sync-api.ts +7 -3
  71. package/src/sync-engine-level.ts +509 -32
  72. package/src/test-harness.ts +3 -5
  73. package/src/types/dwn.ts +19 -21
  74. package/src/types/sync.ts +56 -5
package/src/dwn-api.ts CHANGED
@@ -3,13 +3,13 @@ import type {
3
3
  DwnConfig,
4
4
  EncryptionInput,
5
5
  EncryptionKeyDeriver,
6
+ GenericMessage,
6
7
  KeyDecrypter,
7
8
  ProtocolDefinition,
8
- ProtocolsQueryReply,
9
- RecordsQueryReply,
10
9
  RecordsWrite,
11
10
  RecordsWriteMessage,
12
11
  } from '@enbox/dwn-sdk-js';
12
+ import type { DwnSubscriptionHandler, ResubscribeFactory } from '@enbox/dwn-clients';
13
13
  import type { KeyIdentifier, PrivateKeyJwk, PublicKeyJwk } from '@enbox/crypto';
14
14
 
15
15
  import { TtlCache } from '@enbox/common';
@@ -20,7 +20,7 @@ import {
20
20
  DataStream,
21
21
  Dwn,
22
22
  DwnMethodName,
23
- EventEmitterStream,
23
+ EventEmitterEventLog,
24
24
  Jws,
25
25
  KeyDerivationScheme,
26
26
  Message,
@@ -72,7 +72,6 @@ import {
72
72
  // Import extracted protocol utilities
73
73
  import {
74
74
  detectNewParticipants as detectNewParticipantsFn,
75
- hasRelationalReadAccess as hasRelationalReadAccessFn,
76
75
  isMultiPartyContext as isMultiPartyContextFn,
77
76
  } from './protocol-utils.js';
78
77
 
@@ -87,6 +86,13 @@ import {
87
86
  // Import extracted record upgrade function
88
87
  import { upgradeExternalRootRecord as upgradeExternalRootRecordFn } from './dwn-record-upgrade.js';
89
88
 
89
+ // Import extracted protocol definition fetching functions
90
+ import {
91
+ extractDerivedPublicKey as extractDerivedPublicKeyFn,
92
+ fetchRemoteProtocolDefinition as fetchRemoteProtocolDefinitionFn,
93
+ getProtocolDefinition as getProtocolDefinitionFn,
94
+ } from './dwn-protocol-cache.js';
95
+
90
96
  type DwnMessageWithBlob<T extends DwnInterface> = {
91
97
  message: DwnMessage[T];
92
98
  data?: Blob;
@@ -184,7 +190,7 @@ export class AgentDwnApi {
184
190
  }
185
191
 
186
192
  public static async createDwn({
187
- dataPath, dataStore, didResolver, stateIndex, eventStream, messageStore, tenantGate, resumableTaskStore
193
+ dataPath, dataStore, didResolver, stateIndex, eventLog, messageStore, tenantGate, resumableTaskStore
188
194
  }: DwnApiCreateDwnParams): Promise<Dwn> {
189
195
  dataStore ??= new DataStoreLevel({ blockstoreLocation: `${dataPath}/DWN_DATASTORE` });
190
196
 
@@ -202,9 +208,9 @@ export class AgentDwnApi {
202
208
 
203
209
  resumableTaskStore ??= new ResumableTaskStoreLevel({ location: `${dataPath}/DWN_RESUMABLETASKSTORE` });
204
210
 
205
- eventStream ??= new EventEmitterStream();
211
+ eventLog ??= new EventEmitterEventLog();
206
212
 
207
- return await Dwn.create({ dataStore, didResolver, stateIndex, eventStream, messageStore, tenantGate, resumableTaskStore });
213
+ return await Dwn.create({ dataStore, didResolver, stateIndex, eventLog, messageStore, tenantGate, resumableTaskStore });
208
214
  }
209
215
 
210
216
  public async processRequest<T extends DwnInterface>(
@@ -229,122 +235,8 @@ export class AgentDwnApi {
229
235
 
230
236
 
231
237
  // Post-write key delivery: detect new participants and write contextKey records.
232
- // This replaces the old roleRecordAutoPush mechanism with the unified key delivery protocol.
233
- if (
234
- isDwnRequest(request, DwnInterface.RecordsWrite) &&
235
- request.encryption &&
236
- reply.status.code === 202
237
- ) {
238
- const writeParams = request.messageParams as DwnMessageParams[DwnInterface.RecordsWrite];
239
- // Skip key-delivery protocol writes to avoid infinite recursion (contextKey records are themselves encrypted)
240
- if (writeParams.protocol !== KeyDeliveryProtocolDefinition.protocol) {
241
- try {
242
- const protocolDefinition = await this.getProtocolDefinition(
243
- request.target, writeParams.protocol,
244
- );
245
- if (protocolDefinition) {
246
- const recordsWriteMessage = message as unknown as RecordsWriteMessage;
247
-
248
- // Reactive root-record upgrade (PR E): if this is an externally-authored
249
- // root record with only ProtocolPath encryption, the owner upgrades it by
250
- // appending a ProtocolContext recipient entry so that context key
251
- // holders (including the external author) can also decrypt.
252
- const authorDid = Jws.getSignerDid(
253
- recordsWriteMessage.authorization.signature.signatures[0]
254
- );
255
- const isExternallyAuthored = authorDid !== request.target;
256
- const isRootRecord = !writeParams.parentContextId;
257
- const rootPathSegment = writeParams.protocolPath.split('/')[0];
258
- const isMultiParty = isMultiPartyContextFn(protocolDefinition, rootPathSegment);
259
-
260
- if (isExternallyAuthored && isRootRecord && isMultiParty) {
261
- try {
262
- await upgradeExternalRootRecordFn(
263
- this.agent, request.target, recordsWriteMessage,
264
- this._dwn, this.getSigner.bind(this), this._contextKeyCache,
265
- );
266
- } catch (upgradeError: any) {
267
- console.warn(
268
- `AgentDwnApi: Reactive root-record upgrade failed for ` +
269
- `'${recordsWriteMessage.recordId}': ${upgradeError.message}`
270
- );
271
- }
272
- }
273
-
274
- const newParticipants = detectNewParticipantsFn({
275
- protocolDefinition,
276
- protocolPath : writeParams.protocolPath,
277
- recipient : writeParams.recipient,
278
- tenantDid : request.target,
279
- authorDid : isExternallyAuthored ? authorDid : undefined,
280
- });
238
+ await this.postWriteKeyDelivery(request, message, reply);
281
239
 
282
- if (newParticipants.size > 0) {
283
- // Derive the context key to deliver to participants
284
- const rootContextId = recordsWriteMessage.contextId?.split('/')[0]
285
- || recordsWriteMessage.contextId
286
- || recordsWriteMessage.recordId;
287
-
288
- const { keyId, keyUri } = await getEncryptionKeyInfoFn(this.agent, request.target);
289
- const contextDerivationPath = [
290
- KeyDerivationScheme.ProtocolContext,
291
- rootContextId,
292
- ];
293
- const contextDerivedPrivateKeyBytes =
294
- await this.agent.keyManager.derivePrivateKeyBytes({
295
- keyUri,
296
- derivationPath: contextDerivationPath,
297
- });
298
- const contextDerivedPrivateJwk =
299
- await X25519.bytesToPrivateKey({ privateKeyBytes: contextDerivedPrivateKeyBytes });
300
- const contextKeyPayload: DerivedPrivateJwk = {
301
- rootKeyId : keyId,
302
- derivationScheme : KeyDerivationScheme.ProtocolContext,
303
- derivationPath : contextDerivationPath,
304
- derivedPrivateKey : contextDerivedPrivateJwk as PrivateKeyJwk,
305
- };
306
-
307
- // Extract the author's key delivery public key from the record
308
- // so we can encrypt the contextKey directly to the external author.
309
- const authorKeyDeliveryPubKey =
310
- recordsWriteMessage.authorization?.authorKeyDeliveryPublicKey;
311
-
312
- for (const participantDid of newParticipants) {
313
- try {
314
- // Use the author's key delivery public key when delivering
315
- // to the external author; for other participants (e.g.
316
- // recipient, role holders) fall back to owner-key encryption.
317
- const recipientKey = (participantDid === authorDid && authorKeyDeliveryPubKey)
318
- ? authorKeyDeliveryPubKey
319
- : undefined;
320
-
321
- await this.writeContextKeyRecord({
322
- tenantDid : request.target,
323
- recipientDid : participantDid,
324
- contextKeyData : contextKeyPayload,
325
- sourceProtocol : writeParams.protocol,
326
- sourceContextId : rootContextId,
327
- recipientKeyDeliveryPublicKey : recipientKey,
328
- });
329
- } catch (keyDeliveryError: any) {
330
- console.warn(
331
- `AgentDwnApi: Key delivery to '${participantDid}' for context ` +
332
- `'${rootContextId}' failed: ${keyDeliveryError.message}. ` +
333
- `The participant may not be able to decrypt records in this context.`
334
- );
335
- }
336
- }
337
- }
338
- }
339
- } catch (detectionError: any) {
340
- // Participant detection failure is non-fatal — the record is still stored.
341
- console.warn(
342
- `AgentDwnApi: Post-write participant detection failed: ` +
343
- `${detectionError.message}`
344
- );
345
- }
346
- }
347
- }
348
240
  // Auto-decrypt reply data if encryption is enabled (Component 7)
349
241
  await this.maybeDecryptReply(request, reply);
350
242
 
@@ -390,13 +282,30 @@ export class AgentDwnApi {
390
282
  subscriptionHandler = request.subscriptionHandler;
391
283
  }
392
284
 
285
+ // Build a resubscribe factory for subscribe requests. This closure
286
+ // captures the original request so it can reconstruct and re-sign a new
287
+ // subscribe message with a cursor on reconnection.
288
+ let resubscribeFactory: ResubscribeFactory | undefined;
289
+ if (subscriptionHandler !== undefined && !('messageCid' in request)) {
290
+ resubscribeFactory = async (cursor?: string): Promise<GenericMessage> => {
291
+ const resumeParams = cursor !== undefined
292
+ ? { ...request.messageParams, cursor } as DwnMessageParams[T]
293
+ : request.messageParams;
294
+
295
+ const resumeRequest: ProcessDwnRequest<T> = { ...request, messageParams: resumeParams };
296
+ const { message: resumeMessage } = await this.constructDwnMessage({ request: resumeRequest });
297
+ return resumeMessage;
298
+ };
299
+ }
300
+
393
301
  // Send the RPC request to the target DID's DWN service endpoint using the Agent's RPC client.
394
302
  const reply = await this.sendDwnRpcRequest({
395
303
  targetDid: request.target,
396
304
  dwnEndpointUrls,
397
305
  message,
398
306
  data,
399
- subscriptionHandler
307
+ subscriptionHandler,
308
+ resubscribeFactory,
400
309
  });
401
310
 
402
311
  // Auto-decrypt reply data if encryption is enabled (Component 7)
@@ -410,14 +319,151 @@ export class AgentDwnApi {
410
319
  return { reply, message, messageCid };
411
320
  }
412
321
 
322
+ /**
323
+ * Post-write key delivery: after a successful encrypted `RecordsWrite`,
324
+ * detect new participants and write `contextKey` records so they can
325
+ * decrypt records in the context.
326
+ *
327
+ * This is a non-fatal operation — if participant detection or key delivery
328
+ * fails, the record is still stored and a warning is logged.
329
+ */
330
+ private async postWriteKeyDelivery<T extends DwnInterface>(
331
+ request: ProcessDwnRequest<T>,
332
+ message: DwnMessage[T],
333
+ reply: DwnMessageReply[T],
334
+ ): Promise<void> {
335
+ if (
336
+ !isDwnRequest(request, DwnInterface.RecordsWrite) ||
337
+ !request.encryption ||
338
+ reply.status.code !== 202
339
+ ) {
340
+ return;
341
+ }
342
+
343
+ const writeParams = request.messageParams as DwnMessageParams[DwnInterface.RecordsWrite];
344
+ // Skip key-delivery protocol writes to avoid infinite recursion (contextKey records are themselves encrypted)
345
+ if (writeParams.protocol === KeyDeliveryProtocolDefinition.protocol) {
346
+ return;
347
+ }
348
+
349
+ try {
350
+ const protocolDefinition = await this.getProtocolDefinition(
351
+ request.target, writeParams.protocol,
352
+ );
353
+ if (!protocolDefinition) {
354
+ return;
355
+ }
356
+
357
+ const recordsWriteMessage = message as unknown as RecordsWriteMessage;
358
+
359
+ // Reactive root-record upgrade (PR E): if this is an externally-authored
360
+ // root record with only ProtocolPath encryption, the owner upgrades it by
361
+ // appending a ProtocolContext recipient entry so that context key
362
+ // holders (including the external author) can also decrypt.
363
+ const authorDid = Jws.getSignerDid(
364
+ recordsWriteMessage.authorization.signature.signatures[0]
365
+ );
366
+ const isExternallyAuthored = authorDid !== request.target;
367
+ const isRootRecord = !writeParams.parentContextId;
368
+ const rootPathSegment = writeParams.protocolPath.split('/')[0];
369
+ const isMultiParty = isMultiPartyContextFn(protocolDefinition, rootPathSegment);
370
+
371
+ if (isExternallyAuthored && isRootRecord && isMultiParty) {
372
+ try {
373
+ await upgradeExternalRootRecordFn(
374
+ this.agent, request.target, recordsWriteMessage,
375
+ this._dwn, this.getSigner.bind(this), this._contextKeyCache,
376
+ );
377
+ } catch (upgradeError: any) {
378
+ console.warn(
379
+ `AgentDwnApi: Reactive root-record upgrade failed for ` +
380
+ `'${recordsWriteMessage.recordId}': ${upgradeError.message}`
381
+ );
382
+ }
383
+ }
384
+
385
+ const newParticipants = detectNewParticipantsFn({
386
+ protocolDefinition,
387
+ protocolPath : writeParams.protocolPath,
388
+ recipient : writeParams.recipient,
389
+ tenantDid : request.target,
390
+ authorDid : isExternallyAuthored ? authorDid : undefined,
391
+ });
392
+
393
+ if (newParticipants.size > 0) {
394
+ // Derive the context key to deliver to participants
395
+ const rootContextId = recordsWriteMessage.contextId?.split('/')[0]
396
+ || recordsWriteMessage.contextId
397
+ || recordsWriteMessage.recordId;
398
+
399
+ const { keyId, keyUri } = await getEncryptionKeyInfoFn(this.agent, request.target);
400
+ const contextDerivationPath = [
401
+ KeyDerivationScheme.ProtocolContext,
402
+ rootContextId,
403
+ ];
404
+ const contextDerivedPrivateKeyBytes =
405
+ await this.agent.keyManager.derivePrivateKeyBytes({
406
+ keyUri,
407
+ derivationPath: contextDerivationPath,
408
+ });
409
+ const contextDerivedPrivateJwk =
410
+ await X25519.bytesToPrivateKey({ privateKeyBytes: contextDerivedPrivateKeyBytes });
411
+ const contextKeyPayload: DerivedPrivateJwk = {
412
+ rootKeyId : keyId,
413
+ derivationScheme : KeyDerivationScheme.ProtocolContext,
414
+ derivationPath : contextDerivationPath,
415
+ derivedPrivateKey : contextDerivedPrivateJwk as PrivateKeyJwk,
416
+ };
417
+
418
+ // Extract the author's key delivery public key from the record
419
+ // so we can encrypt the contextKey directly to the external author.
420
+ const authorKeyDeliveryPubKey =
421
+ recordsWriteMessage.authorization?.authorKeyDeliveryPublicKey;
422
+
423
+ for (const participantDid of newParticipants) {
424
+ try {
425
+ // Use the author's key delivery public key when delivering
426
+ // to the external author; for other participants (e.g.
427
+ // recipient, role holders) fall back to owner-key encryption.
428
+ const recipientKey = (participantDid === authorDid && authorKeyDeliveryPubKey)
429
+ ? authorKeyDeliveryPubKey
430
+ : undefined;
431
+
432
+ await this.writeContextKeyRecord({
433
+ tenantDid : request.target,
434
+ recipientDid : participantDid,
435
+ contextKeyData : contextKeyPayload,
436
+ sourceProtocol : writeParams.protocol,
437
+ sourceContextId : rootContextId,
438
+ recipientKeyDeliveryPublicKey : recipientKey,
439
+ });
440
+ } catch (keyDeliveryError: any) {
441
+ console.warn(
442
+ `AgentDwnApi: Key delivery to '${participantDid}' for context ` +
443
+ `'${rootContextId}' failed: ${keyDeliveryError.message}. ` +
444
+ `The participant may not be able to decrypt records in this context.`
445
+ );
446
+ }
447
+ }
448
+ }
449
+ } catch (detectionError: any) {
450
+ // Participant detection failure is non-fatal — the record is still stored.
451
+ console.warn(
452
+ `AgentDwnApi: Post-write participant detection failed: ` +
453
+ `${detectionError.message}`
454
+ );
455
+ }
456
+ }
457
+
413
458
  private async sendDwnRpcRequest<T extends DwnInterface>({
414
- targetDid, dwnEndpointUrls, message, data, subscriptionHandler
459
+ targetDid, dwnEndpointUrls, message, data, subscriptionHandler, resubscribeFactory
415
460
  }: {
416
461
  targetDid: string;
417
462
  dwnEndpointUrls: string[];
418
463
  message: DwnMessage[T];
419
464
  data?: Blob;
420
465
  subscriptionHandler?: MessageHandler[T];
466
+ resubscribeFactory?: ResubscribeFactory;
421
467
  }
422
468
  ): Promise<DwnMessageReply[T]> {
423
469
  const errorMessages: { url: string, message: string }[] = [];
@@ -453,7 +499,10 @@ export class AgentDwnApi {
453
499
  targetDid,
454
500
  message,
455
501
  data,
456
- subscriptionHandler
502
+ subscription: subscriptionHandler ? {
503
+ handler: subscriptionHandler as DwnSubscriptionHandler,
504
+ resubscribeFactory,
505
+ } : undefined,
457
506
  });
458
507
 
459
508
  return dwnReply;
@@ -760,6 +809,9 @@ export class AgentDwnApi {
760
809
 
761
810
  // if there is no raw message provided, we need to create the dwn message
762
811
  if (!rawMessage) {
812
+ if (request.messageParams === undefined) {
813
+ throw new Error('AgentDwnApi: messageParams must be provided when rawMessage is not given.');
814
+ }
763
815
 
764
816
  // If we need to sign as an author delegate or with permissions we need to get the grantee's signer
765
817
  // The messageParams should include either a permissionGrantId, or a delegatedGrant message
@@ -768,8 +820,7 @@ export class AgentDwnApi {
768
820
  await this.getSigner(request.author);
769
821
 
770
822
  dwnMessage = await dwnMessageConstructor.create({
771
- // TODO: Implement alternative to type assertion.
772
- ...request.messageParams!,
823
+ ...request.messageParams,
773
824
  signer
774
825
  });
775
826
 
@@ -903,35 +954,6 @@ export class AgentDwnApi {
903
954
  return getKeyDecrypterFn(this.agent, didUri);
904
955
  }
905
956
 
906
- /**
907
- * Checks if a protocol path represents a multi-party context.
908
- *
909
- * @param protocolDefinition - The full protocol definition
910
- * @param rootProtocolPath - The root protocol path to check
911
- */
912
- private isMultiPartyContext(
913
- protocolDefinition: ProtocolDefinition,
914
- rootProtocolPath: string,
915
- ): boolean {
916
- return isMultiPartyContextFn(protocolDefinition, rootProtocolPath);
917
- }
918
-
919
- /**
920
- * Checks if any `$actions` rule in the protocol grants read access
921
- * via `who: '<actorType>'` and `of: '<path>'`.
922
- *
923
- * @param actorType - The actor type to check ('author', 'recipient', or undefined for any)
924
- * @param ofPath - The protocol path to check
925
- * @param protocolDefinition - The protocol definition
926
- */
927
- private hasRelationalReadAccess(
928
- actorType: 'author' | 'recipient' | undefined,
929
- ofPath: string,
930
- protocolDefinition: ProtocolDefinition,
931
- ): boolean {
932
- return hasRelationalReadAccessFn(actorType, ofPath, protocolDefinition);
933
- }
934
-
935
957
  /**
936
958
  * Analyses a record write to determine which DIDs need context key delivery.
937
959
  *
@@ -949,7 +971,7 @@ export class AgentDwnApi {
949
971
  }
950
972
 
951
973
  /**
952
- * Fetches a protocol definition from the local DWN, with caching.
974
+ * Fetches a protocol definition from the local DWN, with caching.
953
975
  * Returns undefined if the protocol is not installed.
954
976
  *
955
977
  * @param tenantDid - The tenant DID to query
@@ -958,33 +980,12 @@ export class AgentDwnApi {
958
980
  */
959
981
  private async getProtocolDefinition(
960
982
  tenantDid: string,
961
- protocolUri: string
983
+ protocolUri: string,
962
984
  ): Promise<ProtocolDefinition | undefined> {
963
- const cacheKey = `${tenantDid}~${protocolUri}`;
964
-
965
- const cached = this._protocolDefinitionCache.get(cacheKey);
966
- if (cached) {
967
- return cached;
968
- }
969
-
970
- const signer = await this.getSigner(tenantDid);
971
- const protocolsQuery = await dwnMessageConstructors[
972
- DwnInterface.ProtocolsQuery
973
- ].create({
974
- filter: { protocol: protocolUri },
975
- signer,
976
- });
977
-
978
- const reply = await this._dwn.processMessage(
979
- tenantDid, protocolsQuery.message,
985
+ return getProtocolDefinitionFn(
986
+ tenantDid, protocolUri, this._dwn,
987
+ this.getSigner.bind(this), this._protocolDefinitionCache,
980
988
  );
981
- if (reply.status.code !== 200 || !reply.entries?.length) {
982
- return undefined;
983
- }
984
-
985
- const definition = reply.entries[0].descriptor.definition;
986
- this._protocolDefinitionCache.set(cacheKey, definition);
987
- return definition;
988
989
  }
989
990
 
990
991
  /**
@@ -995,34 +996,10 @@ export class AgentDwnApi {
995
996
  targetDid: string,
996
997
  protocolUri: string,
997
998
  ): Promise<ProtocolDefinition> {
998
- const cacheKey = `remote~${targetDid}~${protocolUri}`;
999
- const cached = this._protocolDefinitionCache.get(cacheKey);
1000
- if (cached) { return cached; }
1001
-
1002
- const protocolsQuery = await dwnMessageConstructors[
1003
- DwnInterface.ProtocolsQuery
1004
- ].create({
1005
- filter: { protocol: protocolUri },
1006
- });
1007
-
1008
- const reply = await this.sendDwnRpcRequest({
1009
- targetDid,
1010
- dwnEndpointUrls: await getDwnServiceEndpointUrls(
1011
- targetDid, this.agent.did,
1012
- ),
1013
- message: protocolsQuery.message,
1014
- }) as ProtocolsQueryReply;
1015
-
1016
- if (reply.status.code !== 200 || !reply.entries?.length) {
1017
- throw new Error(
1018
- `AgentDwnApi: Failed to fetch protocol '${protocolUri}' from ` +
1019
- `'${targetDid}'. The recipient may not have the protocol installed.`
1020
- );
1021
- }
1022
-
1023
- const definition = reply.entries[0].descriptor.definition;
1024
- this._protocolDefinitionCache.set(cacheKey, definition);
1025
- return definition;
999
+ return fetchRemoteProtocolDefinitionFn(
1000
+ targetDid, protocolUri, this.agent.did,
1001
+ this.sendDwnRpcRequest.bind(this), this._protocolDefinitionCache,
1002
+ );
1026
1003
  }
1027
1004
 
1028
1005
  /**
@@ -1043,46 +1020,11 @@ export class AgentDwnApi {
1043
1020
  rootContextId: string,
1044
1021
  requesterDid: string,
1045
1022
  ): Promise<{ rootKeyId: string; derivedPublicKey: PublicKeyJwk } | undefined> {
1046
- const signer = await this.getSigner(requesterDid);
1047
-
1048
- // Query the target's DWN for any record in this context
1049
- const recordsQuery = await dwnMessageConstructors[DwnInterface.RecordsQuery].create({
1050
- signer,
1051
- filter: {
1052
- protocol : protocolUri,
1053
- contextId : rootContextId,
1054
- },
1055
- });
1056
-
1057
- const dwnEndpointUrls = await getDwnServiceEndpointUrls(targetDid, this.agent.did);
1058
- const queryReply = await this.sendDwnRpcRequest<DwnInterface.RecordsQuery>({
1059
- targetDid,
1060
- dwnEndpointUrls,
1061
- message: recordsQuery.message,
1062
- }) as RecordsQueryReply;
1063
-
1064
- if (queryReply.status.code !== 200 || !queryReply.entries?.length) {
1065
- return undefined;
1066
- }
1067
-
1068
- // Search entries for one with a ProtocolContext recipient entry
1069
- // that includes derivedPublicKey
1070
- for (const entry of queryReply.entries) {
1071
- if (entry.encryption?.recipients) {
1072
- const contextEntry = entry.encryption.recipients.find(
1073
- (r: { header: { derivationScheme: string; derivedPublicKey?: PublicKeyJwk } }) =>
1074
- r.header.derivationScheme === KeyDerivationScheme.ProtocolContext && r.header.derivedPublicKey
1075
- );
1076
- if (contextEntry?.header.derivedPublicKey) {
1077
- return {
1078
- rootKeyId : contextEntry.header.kid,
1079
- derivedPublicKey : contextEntry.header.derivedPublicKey,
1080
- };
1081
- }
1082
- }
1083
- }
1084
-
1085
- return undefined;
1023
+ return extractDerivedPublicKeyFn(
1024
+ targetDid, protocolUri, rootContextId, requesterDid,
1025
+ this.agent.did, this.getSigner.bind(this),
1026
+ this.sendDwnRpcRequest.bind(this),
1027
+ );
1086
1028
  }
1087
1029
 
1088
1030
  /**