@enbox/agent 0.1.6 → 0.1.7

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.
package/src/dwn-api.ts CHANGED
@@ -237,7 +237,7 @@ export class AgentDwnApi {
237
237
  ) {
238
238
  const writeParams = request.messageParams as DwnMessageParams[DwnInterface.RecordsWrite];
239
239
  // Skip key-delivery protocol writes to avoid infinite recursion (contextKey records are themselves encrypted)
240
- if (writeParams?.protocol && writeParams.protocolPath && writeParams.protocol !== KeyDeliveryProtocolDefinition.protocol) {
240
+ if (writeParams.protocol !== KeyDeliveryProtocolDefinition.protocol) {
241
241
  try {
242
242
  const protocolDefinition = await this.getProtocolDefinition(
243
243
  request.target, writeParams.protocol,
@@ -76,7 +76,8 @@ export class AgentPermissionsApi implements PermissionsApi {
76
76
  grantee,
77
77
  grantor,
78
78
  protocol,
79
- remote = false
79
+ remote = false,
80
+ checkRevoked = true,
80
81
  }: FetchPermissionsParams): Promise<PermissionGrantEntry[]> {
81
82
 
82
83
  // filter by a protocol using tags if provided
@@ -102,9 +103,17 @@ export class AgentPermissionsApi implements PermissionsApi {
102
103
  throw new Error(`PermissionsApi: Failed to fetch grants: ${reply.status.detail}`);
103
104
  }
104
105
 
106
+ // Build a set of revoked grant IDs so that revoked grants can be filtered out.
107
+ // Uses a single batch query for all revocations rather than N individual reads.
108
+ const revokedGrantIds = checkRevoked
109
+ ? await this.fetchRevokedGrantIds({ author, target, grantor, remote, tags })
110
+ : new Set<string>();
111
+
105
112
  const grants:PermissionGrantEntry[] = [];
106
113
  for (const entry of reply.entries! as DwnDataEncodedRecordsWriteMessage[]) {
107
- // TODO: Check for revocation status based on a request parameter and filter out revoked grants
114
+ if (revokedGrantIds.has(entry.recordId)) {
115
+ continue;
116
+ }
108
117
  const grant = await DwnPermissionGrant.parse(entry);
109
118
  grants.push({ grant, message: entry });
110
119
  }
@@ -112,6 +121,49 @@ export class AgentPermissionsApi implements PermissionsApi {
112
121
  return grants;
113
122
  }
114
123
 
124
+ /**
125
+ * Fetch all revocation record IDs for grants, returned as a Set of parent grant record IDs.
126
+ * Issues a single RecordsQuery for all revocation records, optionally scoped to a grantor and protocol.
127
+ */
128
+ private async fetchRevokedGrantIds({ author, target, grantor, remote, tags }: {
129
+ author: string;
130
+ target: string;
131
+ grantor?: string;
132
+ remote: boolean;
133
+ tags?: { protocol: string };
134
+ }): Promise<Set<string>> {
135
+ const revocationParams: ProcessDwnRequest<DwnInterface.RecordsQuery> = {
136
+ author,
137
+ target,
138
+ messageType : DwnInterface.RecordsQuery,
139
+ messageParams : {
140
+ filter: {
141
+ author : grantor, // revocations are authored by the same grantor
142
+ protocol : PermissionsProtocol.uri,
143
+ protocolPath : PermissionsProtocol.revocationPath,
144
+ tags,
145
+ }
146
+ }
147
+ };
148
+
149
+ const { reply: revocationReply } = remote
150
+ ? await this.agent.sendDwnRequest(revocationParams)
151
+ : await this.agent.processDwnRequest(revocationParams);
152
+
153
+ if (revocationReply.status.code !== 200) {
154
+ throw new Error(`PermissionsApi: Failed to fetch revocations: ${revocationReply.status.detail}`);
155
+ }
156
+
157
+ const revokedGrantIds = new Set<string>();
158
+ for (const entry of revocationReply.entries! as DwnDataEncodedRecordsWriteMessage[]) {
159
+ if (entry.descriptor.parentId !== undefined) {
160
+ revokedGrantIds.add(entry.descriptor.parentId);
161
+ }
162
+ }
163
+
164
+ return revokedGrantIds;
165
+ }
166
+
115
167
  async fetchRequests({
116
168
  author,
117
169
  target,
package/src/store-data.ts CHANGED
@@ -89,9 +89,12 @@ export class DwnDataStore<TStoreObject extends Record<string, any> = Jwk> implem
89
89
 
90
90
  /**
91
91
  * Properties to use when writing and querying records with the DWN store.
92
+ * Subclasses MUST override this to include `protocol` and `protocolPath`.
92
93
  */
93
- protected _recordProperties = {
94
- dataFormat: 'application/json',
94
+ protected _recordProperties: { dataFormat: string; protocol: string; protocolPath: string; schema?: string } = {
95
+ dataFormat : 'application/json',
96
+ protocol : '', // overridden by subclass
97
+ protocolPath : '', // overridden by subclass
95
98
  };
96
99
 
97
100
  public async delete({ id, agent, tenant }: DataStoreDeleteParams): Promise<boolean> {
@@ -187,7 +187,7 @@ export class SyncEngineLevel implements SyncEngine {
187
187
  }
188
188
  }
189
189
  } catch (error: any) {
190
- // If the remote DWN is unreachable, skip this target and continue.
190
+ // Skip this DWN endpoint for remaining targets and log the real cause.
191
191
  errored.add(dwnUrl);
192
192
  console.error(`SyncEngineLevel: Error syncing ${did} with ${dwnUrl}`, error);
193
193
  }
@@ -149,7 +149,8 @@ export async function fetchRemoteMessages({ did, dwnUrl, delegateDid, protocol,
149
149
  targetDid : did,
150
150
  message : messagesRead.message,
151
151
  }) as MessagesReadReply;
152
- } catch {
152
+ } catch (error: any) {
153
+ console.error(`SyncEngineLevel: pull - failed to read ${messageCid} from ${dwnUrl}:`, error.message ?? error);
153
154
  return undefined;
154
155
  }
155
156
 
@@ -217,9 +218,10 @@ export async function pushMessages({ did, dwnUrl, delegateDid, protocol, message
217
218
  const cid = await getMessageCid(entry.message);
218
219
  console.error(`SyncEngineLevel: push failed for ${cid}: ${reply.status.code} ${reply.status.detail}`);
219
220
  }
220
- } catch {
221
- // Remote unreachable stop pushing to this endpoint.
222
- throw new Error(`SyncEngineLevel: Remote DWN at ${dwnUrl} is unreachable.`);
221
+ } catch (error: any) {
222
+ // Preserve the original error so callers can diagnose the root cause.
223
+ const detail = error.message ?? error;
224
+ throw new Error(`SyncEngineLevel: push to ${dwnUrl} failed: ${detail}`);
223
225
  }
224
226
  }
225
227
  }
@@ -7,6 +7,8 @@ export type FetchPermissionsParams = {
7
7
  grantor?: string;
8
8
  protocol?: string;
9
9
  remote?: boolean;
10
+ /** Whether to filter out revoked grants. Defaults to `true`. */
11
+ checkRevoked?: boolean;
10
12
  };
11
13
 
12
14
  export type FetchPermissionRequestParams = {