@enbox/api 0.0.1

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 (92) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +585 -0
  3. package/dist/browser.js +2226 -0
  4. package/dist/browser.js.map +7 -0
  5. package/dist/browser.mjs +2226 -0
  6. package/dist/browser.mjs.map +7 -0
  7. package/dist/cjs/did-api.js +126 -0
  8. package/dist/cjs/did-api.js.map +1 -0
  9. package/dist/cjs/dwn-api.js +804 -0
  10. package/dist/cjs/dwn-api.js.map +1 -0
  11. package/dist/cjs/grant-revocation.js +183 -0
  12. package/dist/cjs/grant-revocation.js.map +1 -0
  13. package/dist/cjs/index.js +63 -0
  14. package/dist/cjs/index.js.map +1 -0
  15. package/dist/cjs/package.json +1 -0
  16. package/dist/cjs/permission-grant.js +365 -0
  17. package/dist/cjs/permission-grant.js.map +1 -0
  18. package/dist/cjs/permission-request.js +272 -0
  19. package/dist/cjs/permission-request.js.map +1 -0
  20. package/dist/cjs/protocol.js +110 -0
  21. package/dist/cjs/protocol.js.map +1 -0
  22. package/dist/cjs/record.js +1127 -0
  23. package/dist/cjs/record.js.map +1 -0
  24. package/dist/cjs/subscription-util.js +86 -0
  25. package/dist/cjs/subscription-util.js.map +1 -0
  26. package/dist/cjs/utils.js +127 -0
  27. package/dist/cjs/utils.js.map +1 -0
  28. package/dist/cjs/vc-api.js +64 -0
  29. package/dist/cjs/vc-api.js.map +1 -0
  30. package/dist/cjs/web5.js +471 -0
  31. package/dist/cjs/web5.js.map +1 -0
  32. package/dist/esm/did-api.js +69 -0
  33. package/dist/esm/did-api.js.map +1 -0
  34. package/dist/esm/dwn-api.js +573 -0
  35. package/dist/esm/dwn-api.js.map +1 -0
  36. package/dist/esm/grant-revocation.js +109 -0
  37. package/dist/esm/grant-revocation.js.map +1 -0
  38. package/dist/esm/index.js +34 -0
  39. package/dist/esm/index.js.map +1 -0
  40. package/dist/esm/permission-grant.js +233 -0
  41. package/dist/esm/permission-grant.js.map +1 -0
  42. package/dist/esm/permission-request.js +166 -0
  43. package/dist/esm/permission-request.js.map +1 -0
  44. package/dist/esm/protocol.js +67 -0
  45. package/dist/esm/protocol.js.map +1 -0
  46. package/dist/esm/record.js +814 -0
  47. package/dist/esm/record.js.map +1 -0
  48. package/dist/esm/subscription-util.js +35 -0
  49. package/dist/esm/subscription-util.js.map +1 -0
  50. package/dist/esm/utils.js +120 -0
  51. package/dist/esm/utils.js.map +1 -0
  52. package/dist/esm/vc-api.js +30 -0
  53. package/dist/esm/vc-api.js.map +1 -0
  54. package/dist/esm/web5.js +281 -0
  55. package/dist/esm/web5.js.map +1 -0
  56. package/dist/types/did-api.d.ts +66 -0
  57. package/dist/types/did-api.d.ts.map +1 -0
  58. package/dist/types/dwn-api.d.ts +336 -0
  59. package/dist/types/dwn-api.d.ts.map +1 -0
  60. package/dist/types/grant-revocation.d.ts +66 -0
  61. package/dist/types/grant-revocation.d.ts.map +1 -0
  62. package/dist/types/index.d.ts +34 -0
  63. package/dist/types/index.d.ts.map +1 -0
  64. package/dist/types/permission-grant.d.ts +157 -0
  65. package/dist/types/permission-grant.d.ts.map +1 -0
  66. package/dist/types/permission-request.d.ts +108 -0
  67. package/dist/types/permission-request.d.ts.map +1 -0
  68. package/dist/types/protocol.d.ts +59 -0
  69. package/dist/types/protocol.d.ts.map +1 -0
  70. package/dist/types/record.d.ts +441 -0
  71. package/dist/types/record.d.ts.map +1 -0
  72. package/dist/types/subscription-util.d.ts +19 -0
  73. package/dist/types/subscription-util.d.ts.map +1 -0
  74. package/dist/types/utils.d.ts +85 -0
  75. package/dist/types/utils.d.ts.map +1 -0
  76. package/dist/types/vc-api.d.ts +24 -0
  77. package/dist/types/vc-api.d.ts.map +1 -0
  78. package/dist/types/web5.d.ts +219 -0
  79. package/dist/types/web5.d.ts.map +1 -0
  80. package/package.json +111 -0
  81. package/src/did-api.ts +90 -0
  82. package/src/dwn-api.ts +952 -0
  83. package/src/grant-revocation.ts +124 -0
  84. package/src/index.ts +35 -0
  85. package/src/permission-grant.ts +327 -0
  86. package/src/permission-request.ts +214 -0
  87. package/src/protocol.ts +87 -0
  88. package/src/record.ts +1125 -0
  89. package/src/subscription-util.ts +42 -0
  90. package/src/utils.ts +128 -0
  91. package/src/vc-api.ts +30 -0
  92. package/src/web5.ts +516 -0
package/src/dwn-api.ts ADDED
@@ -0,0 +1,952 @@
1
+ /**
2
+ * NOTE: Added reference types here to avoid a `pnpm` bug during build.
3
+ * https://github.com/TBD54566975/web5-js/pull/507
4
+ */
5
+ /// <reference types="@enbox/dwn-sdk-js" />
6
+
7
+ import type {
8
+ CreateGrantParams,
9
+ CreateRequestParams,
10
+ FetchPermissionRequestParams,
11
+ FetchPermissionsParams
12
+ } from '@enbox/agent';
13
+
14
+ import {
15
+ Web5Agent,
16
+ DwnMessage,
17
+ DwnResponse,
18
+ DwnMessageParams,
19
+ DwnMessageSubscription,
20
+ DwnResponseStatus,
21
+ ProcessDwnRequest,
22
+ DwnPaginationCursor,
23
+ AgentPermissionsApi,
24
+ } from '@enbox/agent';
25
+
26
+ import { isEmptyObject } from '@enbox/common';
27
+ import { DwnInterface, getRecordAuthor } from '@enbox/agent';
28
+
29
+ import { Record } from './record.js';
30
+ import { dataToBlob } from './utils.js';
31
+ import { Protocol } from './protocol.js';
32
+ import { PermissionGrant } from './permission-grant.js';
33
+ import { PermissionRequest } from './permission-request.js';
34
+ import { SubscriptionUtil } from './subscription-util.js';
35
+
36
+ /**
37
+ * Represents the request payload for fetching permission requests from a Decentralized Web Node (DWN).
38
+ *
39
+ * Optionally, specify a remote DWN target in the `from` property to fetch requests from.
40
+ */
41
+ export type FetchRequestsRequest = Omit<FetchPermissionRequestParams, 'author' | 'target' | 'remote'> & {
42
+ /** Optional DID specifying the remote target DWN tenant to be queried. */
43
+ from?: string;
44
+ };
45
+
46
+ /**
47
+ * Represents the request payload for fetching permission grants from a Decentralized Web Node (DWN).
48
+ *
49
+ * Optionally, specify a remote DWN target in the `from` property to fetch requests from.
50
+ * Optionally, specify whether to check if the grant is revoked in the `checkRevoked` property.
51
+ */
52
+ export type FetchGrantsRequest = Omit<FetchPermissionsParams, 'author' | 'target' | 'remote'> & {
53
+ /** Optional DID specifying the remote target DWN tenant to be queried. */
54
+ from?: string;
55
+ /** Optionally check if the grant has been revoked. */
56
+ checkRevoked?: boolean;
57
+ };
58
+
59
+ /**
60
+ * Represents the request payload for configuring a protocol on a Decentralized Web Node (DWN).
61
+ *
62
+ * This request type is used to specify the configuration options for the protocol.
63
+ */
64
+ export type ProtocolsConfigureRequest = {
65
+ /** Configuration options for the protocol. */
66
+ message: Omit<DwnMessageParams[DwnInterface.ProtocolsConfigure], 'signer'>;
67
+ }
68
+
69
+ /**
70
+ * Encapsulates the response from a protocol configuration request to a Decentralized Web Node (DWN).
71
+ *
72
+ * This response type combines the general operation status with the details of the protocol that
73
+ * was configured, if the operation was successful.
74
+ *
75
+ * @beta
76
+ */
77
+ export type ProtocolsConfigureResponse = DwnResponseStatus & {
78
+ /** The configured protocol, if successful. */
79
+ protocol?: Protocol;
80
+ }
81
+
82
+ /**
83
+ * Defines the request structure for querying protocols from a Decentralized Web Node (DWN).
84
+ *
85
+ * This request type is used to specify the target DWN from which protocols should be queried and
86
+ * any additional query filters or options. If the `from` property is not provided, the query will
87
+ * target the local DWN. If the `from` property is provided, the query will target the specified
88
+ * remote DWN.
89
+ */
90
+ export type ProtocolsQueryRequest = {
91
+ /** Optional DID specifying the remote target DWN tenant to be queried. */
92
+ from?: string;
93
+
94
+ /** Query filters and options that influence the results returned. */
95
+ message: Omit<DwnMessageParams[DwnInterface.ProtocolsQuery], 'signer'>
96
+ }
97
+
98
+ /**
99
+ * Wraps the response from a protocols query, including the operation status and the list of
100
+ * protocols.
101
+ */
102
+ export type ProtocolsQueryResponse = DwnResponseStatus & {
103
+ /** Array of protocols matching the query. */
104
+ protocols: Protocol[];
105
+ }
106
+
107
+ /**
108
+ * Type alias for {@link RecordsWriteRequest}
109
+ */
110
+ export type RecordsCreateRequest = RecordsWriteRequest;
111
+
112
+ /**
113
+ * Type alias for {@link RecordsWriteResponse}
114
+ */
115
+ export type RecordsCreateResponse = RecordsWriteResponse;
116
+
117
+ /**
118
+ * Represents a request to create a new record based on an existing one.
119
+ *
120
+ * This request type allows specifying the new data for the record, along with any additional
121
+ * message parameters required for the write operation.
122
+ */
123
+ export type RecordsCreateFromRequest = {
124
+ /** The DID of the entity authoring the record. */
125
+ author: string;
126
+ /** The new data for the record. */
127
+ data: unknown;
128
+ /** ptional additional parameters for the record write operation */
129
+ message?: Omit<DwnMessageParams[DwnInterface.RecordsWrite], 'signer'>;
130
+ /** The existing record instance that is being used as a basis for the new record. */
131
+ record: Record;
132
+ }
133
+
134
+ /**
135
+ * Defines a request to delete a record from the Decentralized Web Node (DWN).
136
+ *
137
+ * This request type optionally specifies the target from which the record should be deleted and the
138
+ * message parameters for the delete operation. If the `from` property is not provided, the record
139
+ * will be deleted from the local DWN.
140
+ */
141
+ export type RecordsDeleteRequest = {
142
+ /** Optional DID specifying the remote target DWN tenant the record will be deleted from. */
143
+ from?: string;
144
+
145
+ /** Records must be scoped to a specific protocol */
146
+ protocol?: string;
147
+
148
+ /** The parameters for the delete operation. */
149
+ message: Omit<DwnMessageParams[DwnInterface.RecordsDelete], 'signer'>;
150
+ }
151
+
152
+ /**
153
+ * Encapsulates a request to query records from a Decentralized Web Node (DWN).
154
+ *
155
+ * This request type is used to specify the criteria for querying records, including query
156
+ * parameters, and optionally the target DWN to query from. If the `from` property is not provided,
157
+ * the query will target the local DWN.
158
+ */
159
+ export type RecordsQueryRequest = {
160
+ /** Optional DID specifying the remote target DWN tenant to query from and return results. */
161
+ from?: string;
162
+
163
+ /** Records must be scoped to a specific protocol */
164
+ protocol?: string;
165
+
166
+ /** The parameters for the query operation, detailing the criteria for selecting records. */
167
+ message: Omit<DwnMessageParams[DwnInterface.RecordsQuery], 'signer'>;
168
+ }
169
+
170
+ /**
171
+ * Represents the response from a records query operation, including status, records, and an
172
+ * optional pagination cursor.
173
+ */
174
+ export type RecordsQueryResponse = DwnResponseStatus & {
175
+ /** Array of records matching the query. */
176
+ records?: Record[]
177
+
178
+ /** If there are additional results, the messageCid of the last record will be returned as a pagination cursor. */
179
+ cursor?: DwnPaginationCursor;
180
+ }
181
+
182
+ /**
183
+ * Represents a request to read a specific record from a Decentralized Web Node (DWN).
184
+ *
185
+ * This request type is used to specify the target DWN from which the record should be read and any
186
+ * additional parameters for the read operation. It's useful for fetching the details of a single
187
+ * record by its identifier or other criteria.
188
+ */
189
+ export type RecordsReadRequest = {
190
+ /** Optional DID specifying the remote target DWN tenant the record will be read from. */
191
+ from?: string;
192
+
193
+ /** Records must be scoped to a specific protocol */
194
+ protocol?: string;
195
+
196
+ /** The parameters for the read operation, detailing the criteria for selecting the record. */
197
+ message: Omit<DwnMessageParams[DwnInterface.RecordsRead], 'signer'>;
198
+ }
199
+
200
+ /**
201
+ * Encapsulates the response from a record read operation, combining the general operation status
202
+ * with the specific record that was retrieved.
203
+ */
204
+ export type RecordsReadResponse = DwnResponseStatus & {
205
+ /** The record retrieved by the read operation. */
206
+ record: Record;
207
+ }
208
+
209
+ /** Subscription handler for Records */
210
+ export type RecordsSubscriptionHandler = (record: Record) => void;
211
+
212
+ /**
213
+ * Represents a request to subscribe to records from a Decentralized Web Node (DWN).
214
+ *
215
+ * This request type is used to specify the target DWN from which records matching the subscription
216
+ * criteria should be emitted. It's useful for being notified in real time when records are written, deleted or modified.
217
+ */
218
+ export type RecordsSubscribeRequest = {
219
+ /** Optional DID specifying the remote target DWN tenant to subscribe from. */
220
+ from?: string;
221
+
222
+ /** Records must be scoped to a specific protocol */
223
+ protocol?: string;
224
+
225
+ /** The parameters for the subscription operation, detailing the criteria for the subscription filter */
226
+ message: Omit<DwnMessageParams[DwnInterface.RecordsSubscribe], 'signer'>;
227
+
228
+ /** The handler to process the subscription events */
229
+ subscriptionHandler: RecordsSubscriptionHandler;
230
+ }
231
+
232
+ /** Encapsulates the response from a DWN RecordsSubscriptionRequest */
233
+ export type RecordsSubscribeResponse = DwnResponseStatus & {
234
+ /**
235
+ * Represents the subscription that was created. Includes an ID and the close method to stop the subscription.
236
+ *
237
+ * */
238
+ subscription?: DwnMessageSubscription;
239
+ }
240
+
241
+ /**
242
+ * Defines a request to write (create) a record to a Decentralized Web Node (DWN).
243
+ *
244
+ * This request type allows specifying the data for the new or updated record, along with any
245
+ * additional message parameters required for the write operation, and an optional flag to indicate
246
+ * whether the record should be immediately stored.
247
+ *
248
+ * @param data -
249
+ * @param message - , excluding the signer.
250
+ * @param store -
251
+ */
252
+ export type RecordsWriteRequest = {
253
+ /** The data payload for the record, which can be of any type. */
254
+ data: unknown;
255
+
256
+ /** Optional additional parameters for the record write operation. */
257
+ message?: Omit<Partial<DwnMessageParams[DwnInterface.RecordsWrite]>, 'signer'>;
258
+
259
+ /**
260
+ * Optional flag indicating whether the record should be immediately stored. If true, the record
261
+ * is persisted in the DWN as part of the write operation. If false, the record is created,
262
+ * signed, and returned but not persisted.
263
+ */
264
+ store?: boolean;
265
+ }
266
+
267
+ /**
268
+ * Encapsulates the response from a record write operation to a Decentralized Web Node (DWN).
269
+ *
270
+ * This request type combines the general operation status with the details of the record that was
271
+ * written, if the operation was successful.
272
+ *
273
+ * The response includes a status object that contains the HTTP-like status code and detail message
274
+ * indicating the success or failure of the write operation. If the operation was successful and a
275
+ * record was created or updated, the `record` property will contain an instance of the `Record`
276
+ * class representing the written record. This allows the caller to access the written record's
277
+ * details and perform additional operations using the provided {@link Record} instance methods.
278
+ */
279
+ export type RecordsWriteResponse = DwnResponseStatus & {
280
+ /**
281
+ * The `Record` instance representing the record that was successfully written to the
282
+ * DWN as a result of the write operation.
283
+ */
284
+ record?: Record
285
+ }
286
+
287
+ /**
288
+ * Interface to interact with DWN Records and Protocols
289
+ */
290
+ export class DwnApi {
291
+ /**
292
+ * Holds the instance of a {@link Web5Agent} that represents the current execution context for
293
+ * the `DwnApi`. This agent is used to process DWN requests.
294
+ */
295
+ private agent: Web5Agent;
296
+
297
+ /** The DID of the DWN tenant under which operations are being performed. */
298
+ private connectedDid: string;
299
+
300
+ /** (optional) The DID of the signer when signing with permissions */
301
+ private delegateDid?: string;
302
+
303
+ /** Holds the instance of {@link AgentPermissionsApi} that helps when dealing with permissions protocol records */
304
+ private permissionsApi: AgentPermissionsApi;
305
+
306
+ constructor(options: { agent: Web5Agent, connectedDid: string, delegateDid?: string }) {
307
+ this.agent = options.agent;
308
+ this.connectedDid = options.connectedDid;
309
+ this.delegateDid = options.delegateDid;
310
+ this.permissionsApi = new AgentPermissionsApi({ agent: this.agent });
311
+ }
312
+
313
+ /**
314
+ * API to interact with Grants
315
+ *
316
+ * NOTE: This is an EXPERIMENTAL API that will change behavior.
317
+ *
318
+ * Currently only supports issuing requests, grants, revokes and queries on behalf without permissions or impersonation.
319
+ * If the agent is connected to a delegateDid, the delegateDid will be used to sign/author the underlying records.
320
+ * If the agent is not connected to a delegateDid, the connectedDid will be used to sign/author the underlying records.
321
+ *
322
+ * @beta
323
+ */
324
+ get permissions() {
325
+ return {
326
+ /**
327
+ * Request permission for a specific scope.
328
+ */
329
+ request: async(request: Omit<CreateRequestParams, 'author'>): Promise<PermissionRequest> => {
330
+ const { message } = await this.permissionsApi.createRequest({
331
+ ...request,
332
+ author: this.delegateDid ?? this.connectedDid,
333
+ });
334
+
335
+ const requestParams = {
336
+ connectedDid : this.delegateDid ?? this.connectedDid,
337
+ agent : this.agent,
338
+ message,
339
+ };
340
+
341
+ return await PermissionRequest.parse(requestParams);
342
+ },
343
+ /**
344
+ * Grant permission for a specific scope to a grantee DID.
345
+ */
346
+ grant: async(request: Omit<CreateGrantParams, 'author'>): Promise<PermissionGrant> => {
347
+ const { message } = await this.permissionsApi.createGrant({
348
+ ...request,
349
+ author: this.delegateDid ?? this.connectedDid,
350
+ });
351
+
352
+ const grantParams = {
353
+ connectedDid : this.delegateDid ?? this.connectedDid,
354
+ agent : this.agent,
355
+ message,
356
+ };
357
+
358
+ return await PermissionGrant.parse(grantParams);
359
+ },
360
+ /**
361
+ * Query permission requests. You can filter by protocol and specify if you want to query a remote DWN.
362
+ */
363
+ queryRequests: async(request: FetchRequestsRequest= {}): Promise<PermissionRequest[]> => {
364
+ const { from, ...params } = request;
365
+ const fetchResponse = await this.permissionsApi.fetchRequests({
366
+ ...params,
367
+ author : this.delegateDid ?? this.connectedDid,
368
+ target : from ?? this.delegateDid ?? this.connectedDid,
369
+ remote : from !== undefined,
370
+ });
371
+
372
+ const requests: PermissionRequest[] = [];
373
+ for (const permission of fetchResponse) {
374
+ const requestParams = {
375
+ connectedDid : this.delegateDid ?? this.connectedDid,
376
+ agent : this.agent,
377
+ message : permission.message,
378
+ };
379
+ requests.push(await PermissionRequest.parse(requestParams));
380
+ }
381
+
382
+ return requests;
383
+ },
384
+ /**
385
+ * Query permission grants. You can filter by grantee, grantor, protocol and specify if you want to query a remote DWN.
386
+ */
387
+ queryGrants: async(request: FetchGrantsRequest = {}): Promise<PermissionGrant[]> => {
388
+ const { checkRevoked, from, ...params } = request;
389
+ const remote = from !== undefined;
390
+ const author = this.delegateDid ?? this.connectedDid;
391
+ const target = from ?? this.delegateDid ?? this.connectedDid;
392
+ const fetchResponse = await this.permissionsApi.fetchGrants({
393
+ ...params,
394
+ author,
395
+ target,
396
+ remote,
397
+ });
398
+
399
+ const grants: PermissionGrant[] = [];
400
+ for (const permission of fetchResponse) {
401
+ const grantParams = {
402
+ connectedDid : this.delegateDid ?? this.connectedDid,
403
+ agent : this.agent,
404
+ message : permission.message,
405
+ };
406
+
407
+ if (checkRevoked) {
408
+ const grantRecordId = permission.grant.id;
409
+ if(await this.permissionsApi.isGrantRevoked({ author, target, grantRecordId, remote })) {
410
+ continue;
411
+ }
412
+ }
413
+ grants.push(await PermissionGrant.parse(grantParams));
414
+ }
415
+
416
+ return grants;
417
+ }
418
+ };
419
+ }
420
+
421
+ /**
422
+ * API to interact with DWN protocols (e.g., `dwn.protocols.configure()`).
423
+ */
424
+ get protocols() {
425
+ return {
426
+ /**
427
+ * Configure method, used to setup a new protocol (or update) with the passed definitions
428
+ */
429
+ configure: async (request: ProtocolsConfigureRequest): Promise<ProtocolsConfigureResponse> => {
430
+
431
+ const agentRequest:ProcessDwnRequest<DwnInterface.ProtocolsConfigure> = {
432
+ author : this.connectedDid,
433
+ messageParams : request.message,
434
+ messageType : DwnInterface.ProtocolsConfigure,
435
+ target : this.connectedDid
436
+ };
437
+
438
+ if (this.delegateDid) {
439
+ const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
440
+ connectedDid : this.connectedDid,
441
+ delegateDid : this.delegateDid,
442
+ protocol : request.message.definition.protocol,
443
+ delegate : true,
444
+ cached : true,
445
+ messageType : agentRequest.messageType
446
+ });
447
+
448
+ agentRequest.messageParams = {
449
+ ...agentRequest.messageParams,
450
+ delegatedGrant
451
+ };
452
+ agentRequest.granteeDid = this.delegateDid;
453
+ }
454
+
455
+ const agentResponse = await this.agent.processDwnRequest(agentRequest);
456
+
457
+ const { message, messageCid, reply: { status }} = agentResponse;
458
+ const response: ProtocolsConfigureResponse = { status };
459
+
460
+ if (status.code < 300) {
461
+ const metadata = { author: this.connectedDid, messageCid };
462
+ response.protocol = new Protocol(this.agent, message, metadata);
463
+ }
464
+
465
+ return response;
466
+ },
467
+
468
+ /**
469
+ * Query the available protocols
470
+ */
471
+ query: async (request: ProtocolsQueryRequest): Promise<ProtocolsQueryResponse> => {
472
+ const agentRequest: ProcessDwnRequest<DwnInterface.ProtocolsQuery> = {
473
+ author : this.connectedDid,
474
+ messageParams : request.message,
475
+ messageType : DwnInterface.ProtocolsQuery,
476
+ target : request.from || this.connectedDid
477
+ };
478
+
479
+ if (this.delegateDid) {
480
+ // We attempt to get a grant within a try catch, if there is no grant we will still sign the query with the delegate DID's key
481
+ // If the protocol is public, the query should be successful. This allows the app to query for public protocols without having a grant.
482
+
483
+ try {
484
+ const { grant: { id: permissionGrantId } } = await this.permissionsApi.getPermissionForRequest({
485
+ connectedDid : this.connectedDid,
486
+ delegateDid : this.delegateDid,
487
+ protocol : request.message.filter.protocol,
488
+ cached : true,
489
+ messageType : agentRequest.messageType
490
+ });
491
+
492
+ agentRequest.messageParams = {
493
+ ...agentRequest.messageParams,
494
+ permissionGrantId
495
+ };
496
+ agentRequest.granteeDid = this.delegateDid;
497
+ } catch(_error:any) {
498
+ // if a grant is not found, we should author the request as the delegated DID to get public protocols
499
+ agentRequest.author = this.delegateDid;
500
+ }
501
+ }
502
+
503
+ let agentResponse: DwnResponse<DwnInterface.ProtocolsQuery>;
504
+
505
+ if (request.from) {
506
+ agentResponse = await this.agent.sendDwnRequest(agentRequest);
507
+ } else {
508
+ agentResponse = await this.agent.processDwnRequest(agentRequest);
509
+ }
510
+
511
+ const reply = agentResponse.reply;
512
+ const { entries = [], status } = reply;
513
+
514
+ const protocols = entries.map((entry) => {
515
+ const metadata = { author: this.connectedDid };
516
+ return new Protocol(this.agent, entry, metadata);
517
+ });
518
+
519
+ return { protocols, status };
520
+ }
521
+ };
522
+ }
523
+
524
+ /**
525
+ * API to interact with DWN records (e.g., `dwn.records.create()`).
526
+ */
527
+ get records() {
528
+
529
+ return {
530
+ /**
531
+ * Alias for the `write` method
532
+ */
533
+ create: async (request: RecordsCreateRequest): Promise<RecordsCreateResponse> => {
534
+ return this.records.write(request);
535
+ },
536
+
537
+ /**
538
+ * Write a record based on an existing one (useful for updating an existing record)
539
+ */
540
+ createFrom: async (request: RecordsCreateFromRequest): Promise<RecordsWriteResponse> => {
541
+ const { author: inheritedAuthor, ...inheritedProperties } = request.record.toJSON();
542
+
543
+ // If `data` is being updated then `dataCid` and `dataSize` must not be present.
544
+ if (request.data !== undefined) {
545
+ delete inheritedProperties.dataCid;
546
+ delete inheritedProperties.dataSize;
547
+ }
548
+
549
+ // If `published` is set to false, ensure that `datePublished` is undefined. Otherwise, DWN SDK's schema validation
550
+ // will throw an error if `published` is false but `datePublished` is set.
551
+ if (request.message?.published === false && inheritedProperties.datePublished !== undefined) {
552
+ delete inheritedProperties.datePublished;
553
+ delete inheritedProperties.published;
554
+ }
555
+
556
+ // If the request changes the `author` or message `descriptor` then the deterministic `recordId` will change.
557
+ // As a result, we will discard the `recordId` if either of these changes occur.
558
+ if (!isEmptyObject(request.message) || (request.author && request.author !== inheritedAuthor)) {
559
+ delete inheritedProperties.recordId;
560
+ }
561
+
562
+ return this.records.write({
563
+ data : request.data,
564
+ message : {
565
+ ...inheritedProperties,
566
+ ...request.message,
567
+ },
568
+ });
569
+ },
570
+
571
+ /**
572
+ * Delete a record
573
+ */
574
+ delete: async (request: RecordsDeleteRequest): Promise<DwnResponseStatus> => {
575
+ const agentRequest: ProcessDwnRequest<DwnInterface.RecordsDelete> = {
576
+ /**
577
+ * The `author` is the DID that will sign the message and must be the DID the Web5 app is
578
+ * connected with and is authorized to access the signing private key of.
579
+ */
580
+ author : this.connectedDid,
581
+ messageParams : request.message,
582
+ messageType : DwnInterface.RecordsDelete,
583
+ /**
584
+ * The `target` is the DID of the DWN tenant under which the delete will be executed.
585
+ * If `from` is provided, the delete operation will be executed on a remote DWN.
586
+ * Otherwise, the record will be deleted on the local DWN.
587
+ */
588
+ target : request.from || this.connectedDid
589
+ };
590
+
591
+ if (this.delegateDid) {
592
+ const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
593
+ connectedDid : this.connectedDid,
594
+ delegateDid : this.delegateDid,
595
+ protocol : request.protocol,
596
+ delegate : true,
597
+ cached : true,
598
+ messageType : agentRequest.messageType
599
+ });
600
+
601
+ agentRequest.messageParams = {
602
+ ...agentRequest.messageParams,
603
+ delegatedGrant
604
+ };
605
+ agentRequest.granteeDid = this.delegateDid;
606
+ }
607
+
608
+ let agentResponse: DwnResponse<DwnInterface.RecordsDelete>;
609
+
610
+ if (request.from) {
611
+ agentResponse = await this.agent.sendDwnRequest(agentRequest);
612
+ } else {
613
+ agentResponse = await this.agent.processDwnRequest(agentRequest);
614
+ }
615
+
616
+ const { reply: { status } } = agentResponse;
617
+
618
+ return { status };
619
+ },
620
+ /**
621
+ * Query a single or multiple records based on the given filter
622
+ */
623
+ query: async (request: RecordsQueryRequest): Promise<RecordsQueryResponse> => {
624
+ const agentRequest: ProcessDwnRequest<DwnInterface.RecordsQuery> = {
625
+ /**
626
+ * The `author` is the DID that will sign the message and must be the DID the Web5 app is
627
+ * connected with and is authorized to access the signing private key of.
628
+ */
629
+ author : this.connectedDid,
630
+ messageParams : request.message,
631
+ messageType : DwnInterface.RecordsQuery,
632
+ /**
633
+ * The `target` is the DID of the DWN tenant under which the query will be executed.
634
+ * If `from` is provided, the query operation will be executed on a remote DWN.
635
+ * Otherwise, the local DWN will be queried.
636
+ */
637
+ target : request.from || this.connectedDid
638
+ };
639
+
640
+ if (this.delegateDid) {
641
+ // if we don't find a delegated grant, we will attempt to query signing as the delegated DID
642
+ // This is to allow the API caller to query public records without needing to impersonate the delegate.
643
+ //
644
+ // NOTE: When a read-only DwnApi is implemented, callers should use that instead when they don't have an explicit permission.
645
+ // This should fail if a permission is not found.
646
+ // TODO: https://github.com/TBD54566975/web5-js/issues/898
647
+ try {
648
+ const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
649
+ connectedDid : this.connectedDid,
650
+ delegateDid : this.delegateDid,
651
+ protocol : request.protocol,
652
+ delegate : true,
653
+ cached : true,
654
+ messageType : agentRequest.messageType
655
+ });
656
+
657
+ agentRequest.messageParams = {
658
+ ...agentRequest.messageParams,
659
+ delegatedGrant
660
+ };
661
+ agentRequest.granteeDid = this.delegateDid;
662
+ } catch(_error:any) {
663
+ // if a grant is not found, we should author the request as the delegated DID to get public records
664
+ agentRequest.author = this.delegateDid;
665
+ }
666
+ }
667
+
668
+
669
+ let agentResponse: DwnResponse<DwnInterface.RecordsQuery>;
670
+
671
+ if (request.from) {
672
+ agentResponse = await this.agent.sendDwnRequest(agentRequest);
673
+ } else {
674
+ agentResponse = await this.agent.processDwnRequest(agentRequest);
675
+ }
676
+
677
+ const reply = agentResponse.reply;
678
+ const { entries = [], status, cursor } = reply;
679
+
680
+ const records = entries.map((entry) => {
681
+
682
+ const recordOptions = {
683
+ /**
684
+ * Extract the `author` DID from the record entry since records may be signed by the
685
+ * tenant owner or any other entity.
686
+ */
687
+ author : getRecordAuthor(entry),
688
+ /**
689
+ * Set the `connectedDid` to currently connected DID so that subsequent calls to
690
+ * {@link Record} instance methods, such as `record.update()` are executed on the
691
+ * local DWN even if the record was returned by a query of a remote DWN.
692
+ */
693
+ connectedDid : this.connectedDid,
694
+ /**
695
+ * If the record was returned by a query of a remote DWN, set the `remoteOrigin` to
696
+ * the DID of the DWN that returned the record. The `remoteOrigin` property will be used
697
+ * to determine which DWN to send subsequent read requests to in the event the data
698
+ * payload exceeds the threshold for being returned with queries.
699
+ */
700
+ remoteOrigin : request.from,
701
+ delegateDid : this.delegateDid,
702
+ protocolRole : agentRequest.messageParams.protocolRole,
703
+ ...entry as DwnMessage[DwnInterface.RecordsWrite]
704
+ };
705
+ const record = new Record(this.agent, recordOptions, this.permissionsApi);
706
+ return record;
707
+ });
708
+
709
+ return { records, status, cursor };
710
+ },
711
+
712
+ /**
713
+ * Read a single record based on the given filter
714
+ */
715
+ read: async (request: RecordsReadRequest): Promise<RecordsReadResponse> => {
716
+ const agentRequest: ProcessDwnRequest<DwnInterface.RecordsRead> = {
717
+ /**
718
+ * The `author` is the DID that will sign the message and must be the DID the Web5 app is
719
+ * connected with and is authorized to access the signing private key of.
720
+ */
721
+ author : this.connectedDid,
722
+ messageParams : request.message,
723
+ messageType : DwnInterface.RecordsRead,
724
+ /**
725
+ * The `target` is the DID of the DWN tenant under which the read will be executed.
726
+ * If `from` is provided, the read operation will be executed on a remote DWN.
727
+ * Otherwise, the read will occur on the local DWN.
728
+ */
729
+ target : request.from || this.connectedDid
730
+ };
731
+ if (this.delegateDid) {
732
+ // if we don't find a delegated grant, we will attempt to read signing as the delegated DID
733
+ // This is to allow the API caller to read public records without needing to impersonate the delegate.
734
+ //
735
+ // NOTE: When a read-only DwnApi is implemented, callers should use that instead when they don't have an explicit permission.
736
+ // This should fail if a permission is not found.
737
+ // TODO: https://github.com/TBD54566975/web5-js/issues/898
738
+
739
+ try {
740
+ const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
741
+ connectedDid : this.connectedDid,
742
+ delegateDid : this.delegateDid,
743
+ protocol : request.protocol,
744
+ delegate : true,
745
+ cached : true,
746
+ messageType : agentRequest.messageType
747
+ });
748
+
749
+ agentRequest.messageParams = {
750
+ ...agentRequest.messageParams,
751
+ delegatedGrant
752
+ };
753
+ agentRequest.granteeDid = this.delegateDid;
754
+ } catch(_error:any) {
755
+ // if a grant is not found, we should author the request as the delegated DID to get public records
756
+ agentRequest.author = this.delegateDid;
757
+ }
758
+ }
759
+
760
+ let agentResponse: DwnResponse<DwnInterface.RecordsRead>;
761
+
762
+ if (request.from) {
763
+ agentResponse = await this.agent.sendDwnRequest(agentRequest);
764
+ } else {
765
+ agentResponse = await this.agent.processDwnRequest(agentRequest);
766
+ }
767
+
768
+ const { reply: { entry, status } } = agentResponse;
769
+
770
+ let record: Record;
771
+ if (200 <= status.code && status.code <= 299) {
772
+ const recordOptions = {
773
+ /**
774
+ * Extract the `author` DID from the record since records may be signed by the
775
+ * tenant owner or any other entity.
776
+ */
777
+ author : getRecordAuthor(entry.recordsWrite),
778
+ /**
779
+ * Set the `connectedDid` to currently connected DID so that subsequent calls to
780
+ * {@link Record} instance methods, such as `record.update()` are executed on the
781
+ * local DWN even if the record was read from a remote DWN.
782
+ */
783
+ connectedDid : this.connectedDid,
784
+ /**
785
+ * If the record was returned by reading from a remote DWN, set the `remoteOrigin` to
786
+ * the DID of the DWN that returned the record. The `remoteOrigin` property will be used
787
+ * to determine which DWN to send subsequent read requests to in the event the data
788
+ * payload must be read again (e.g., if the data stream is consumed).
789
+ */
790
+ remoteOrigin : request.from,
791
+ delegateDid : this.delegateDid,
792
+ data : entry.data,
793
+ initialWrite : entry.initialWrite,
794
+ ...entry.recordsWrite,
795
+ };
796
+
797
+ record = new Record(this.agent, recordOptions, this.permissionsApi);
798
+ }
799
+
800
+ return { record, status };
801
+ },
802
+
803
+ /**
804
+ * Subscribes to records based on the given filter and emits events to the `subscriptionHandler`.
805
+ *
806
+ * @param request must include the `message` with the subscription filter and the `subscriptionHandler` to process the events.
807
+ * @returns the subscription status and the subscription object used to close the subscription.
808
+ */
809
+ subscribe: async (request: RecordsSubscribeRequest): Promise<RecordsSubscribeResponse> => {
810
+ const agentRequest: ProcessDwnRequest<DwnInterface.RecordsSubscribe> = {
811
+ /**
812
+ * The `author` is the DID that will sign the message and must be the DID the Web5 app is
813
+ * connected with and is authorized to access the signing private key of.
814
+ */
815
+ author : this.connectedDid,
816
+ messageParams : request.message,
817
+ messageType : DwnInterface.RecordsSubscribe,
818
+ /**
819
+ * The `target` is the DID of the DWN tenant under which the subscribe operation will be executed.
820
+ * If `from` is provided, the subscribe operation will be executed on a remote DWN.
821
+ * Otherwise, the local DWN will execute the subscribe operation.
822
+ */
823
+ target : request.from || this.connectedDid,
824
+
825
+ /**
826
+ * The handler to process the subscription events.
827
+ */
828
+ subscriptionHandler: SubscriptionUtil.recordSubscriptionHandler({
829
+ agent : this.agent,
830
+ connectedDid : this.connectedDid,
831
+ delegateDid : this.delegateDid,
832
+ permissionsApi : this.permissionsApi,
833
+ protocolRole : request.message.protocolRole,
834
+ request
835
+ })
836
+ };
837
+
838
+ if (this.delegateDid) {
839
+ // if we don't find a delegated grant, we will attempt to subscribe signing as the delegated DID
840
+ // This is to allow the API caller to subscribe to public records without needing to impersonate the delegate.
841
+ //
842
+ // NOTE: When a read-only DwnApi is implemented, callers should use that instead when they don't have an explicit permission.
843
+ // This should fail if a permission is not found.
844
+ // TODO: https://github.com/TBD54566975/web5-js/issues/898
845
+ try {
846
+ const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
847
+ connectedDid : this.connectedDid,
848
+ delegateDid : this.delegateDid,
849
+ protocol : request.protocol,
850
+ delegate : true,
851
+ cached : true,
852
+ messageType : agentRequest.messageType
853
+ });
854
+
855
+ agentRequest.messageParams = {
856
+ ...agentRequest.messageParams,
857
+ delegatedGrant
858
+ };
859
+ agentRequest.granteeDid = this.delegateDid;
860
+ } catch(_error:any) {
861
+ // if a grant is not found, we should author the request as the delegated DID to get public records
862
+ agentRequest.author = this.delegateDid;
863
+ }
864
+ };
865
+
866
+ let agentResponse: DwnResponse<DwnInterface.RecordsSubscribe>;
867
+
868
+ if (request.from) {
869
+ agentResponse = await this.agent.sendDwnRequest(agentRequest);
870
+ } else {
871
+ agentResponse = await this.agent.processDwnRequest(agentRequest);
872
+ }
873
+
874
+ const reply = agentResponse.reply;
875
+ const { status, subscription } = reply;
876
+
877
+ return { status, subscription };
878
+ },
879
+
880
+ /**
881
+ * Writes a record to the DWN
882
+ *
883
+ * As a convenience, the Record instance returned will cache a copy of the data. This is done
884
+ * to maintain consistency with other DWN methods, like RecordsQuery, that include relatively
885
+ * small data payloads when returning RecordsWrite message properties. Regardless of data
886
+ * size, methods such as `record.data.stream()` will return the data when called even if it
887
+ * requires fetching from the DWN datastore.
888
+ */
889
+ write: async (request: RecordsWriteRequest): Promise<RecordsWriteResponse> => {
890
+ const { dataBlob, dataFormat } = dataToBlob(request.data, request.message?.dataFormat);
891
+
892
+ const dwnRequestParams: ProcessDwnRequest<DwnInterface.RecordsWrite> = {
893
+ store : request.store,
894
+ messageType : DwnInterface.RecordsWrite,
895
+ messageParams : {
896
+ ...request.message,
897
+ dataFormat
898
+ },
899
+ author : this.connectedDid,
900
+ target : this.connectedDid,
901
+ dataStream : dataBlob
902
+ };
903
+
904
+ // if impersonation is enabled, fetch the delegated grant to use with the write operation
905
+ if (this.delegateDid) {
906
+ const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
907
+ connectedDid : this.connectedDid,
908
+ delegateDid : this.delegateDid,
909
+ protocol : request.message.protocol,
910
+ delegate : true,
911
+ cached : true,
912
+ messageType : dwnRequestParams.messageType
913
+ });
914
+
915
+ dwnRequestParams.messageParams = {
916
+ ...dwnRequestParams.messageParams,
917
+ delegatedGrant
918
+ };
919
+ dwnRequestParams.granteeDid = this.delegateDid;
920
+ };
921
+
922
+ const agentResponse = await this.agent.processDwnRequest(dwnRequestParams);
923
+
924
+ const { message: responseMessage, reply: { status } } = agentResponse;
925
+
926
+ let record: Record;
927
+ if (200 <= status.code && status.code <= 299) {
928
+ const recordOptions = {
929
+ /**
930
+ * Assume the author is the connected DID since the record was just written to the
931
+ * local DWN.
932
+ */
933
+ author : this.connectedDid,
934
+ /**
935
+ * Set the `connectedDid` to currently connected DID so that subsequent calls to
936
+ * {@link Record} instance methods, such as `record.update()` are executed on the
937
+ * local DWN.
938
+ */
939
+ connectedDid : this.connectedDid,
940
+ encodedData : dataBlob,
941
+ delegateDid : this.delegateDid,
942
+ ...responseMessage,
943
+ };
944
+
945
+ record = new Record(this.agent, recordOptions, this.permissionsApi);
946
+ }
947
+
948
+ return { record, status };
949
+ },
950
+ };
951
+ }
952
+ }