@enbox/agent 0.2.1 → 0.2.2
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/dist/browser.mjs +8 -8
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/dwn-api.js +116 -7
- package/dist/esm/dwn-api.js.map +1 -1
- package/dist/esm/dwn-key-delivery.js +6 -5
- package/dist/esm/dwn-key-delivery.js.map +1 -1
- package/dist/esm/dwn-protocol-cache.js +6 -7
- package/dist/esm/dwn-protocol-cache.js.map +1 -1
- package/dist/esm/identity-api.js +1 -2
- package/dist/esm/identity-api.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/local-dwn.js +73 -0
- package/dist/esm/local-dwn.js.map +1 -0
- package/dist/esm/sync-engine-level.js +1 -2
- package/dist/esm/sync-engine-level.js.map +1 -1
- package/dist/esm/test-harness.js +2 -1
- package/dist/esm/test-harness.js.map +1 -1
- package/dist/esm/web5-user-agent.js +6 -2
- package/dist/esm/web5-user-agent.js.map +1 -1
- package/dist/types/dwn-api.d.ts +32 -1
- package/dist/types/dwn-api.d.ts.map +1 -1
- package/dist/types/dwn-key-delivery.d.ts +4 -2
- package/dist/types/dwn-key-delivery.d.ts.map +1 -1
- package/dist/types/dwn-protocol-cache.d.ts +6 -5
- package/dist/types/dwn-protocol-cache.d.ts.map +1 -1
- package/dist/types/identity-api.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/local-dwn.d.ts +43 -0
- package/dist/types/local-dwn.d.ts.map +1 -0
- package/dist/types/sync-engine-level.d.ts.map +1 -1
- package/dist/types/test-harness.d.ts.map +1 -1
- package/dist/types/web5-user-agent.d.ts +5 -1
- package/dist/types/web5-user-agent.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/dwn-api.ts +140 -5
- package/src/dwn-key-delivery.ts +6 -3
- package/src/dwn-protocol-cache.ts +9 -8
- package/src/identity-api.ts +1 -2
- package/src/index.ts +1 -0
- package/src/local-dwn.ts +81 -0
- package/src/sync-engine-level.ts +1 -2
- package/src/test-harness.ts +2 -1
- package/src/web5-user-agent.ts +13 -3
package/src/dwn-api.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
import { CryptoUtils, X25519 } from '@enbox/crypto';
|
|
34
34
|
import { DidDht, DidJwk, DidResolverCacheLevel, UniversalResolver } from '@enbox/dids';
|
|
35
35
|
|
|
36
|
+
import type { LocalDwnStrategy } from './local-dwn.js';
|
|
36
37
|
import type { Web5PlatformAgent } from './types/agent.js';
|
|
37
38
|
import type {
|
|
38
39
|
DwnMessage,
|
|
@@ -48,6 +49,7 @@ import type {
|
|
|
48
49
|
} from './types/dwn.js';
|
|
49
50
|
|
|
50
51
|
import { KeyDeliveryProtocolDefinition } from './store-data-protocols.js';
|
|
52
|
+
import { LocalDwnDiscovery } from './local-dwn.js';
|
|
51
53
|
import { DwnInterface, dwnMessageConstructors } from './types/dwn.js';
|
|
52
54
|
import { getDwnServiceEndpointUrls, isRecordsWrite } from './utils.js';
|
|
53
55
|
|
|
@@ -101,6 +103,7 @@ type DwnMessageWithBlob<T extends DwnInterface> = {
|
|
|
101
103
|
type DwnApiParams = {
|
|
102
104
|
agent?: Web5PlatformAgent;
|
|
103
105
|
dwn: Dwn;
|
|
106
|
+
localDwnStrategy?: LocalDwnStrategy;
|
|
104
107
|
};
|
|
105
108
|
|
|
106
109
|
interface DwnApiCreateDwnParams extends Partial<DwnConfig> {
|
|
@@ -148,12 +151,34 @@ export class AgentDwnApi {
|
|
|
148
151
|
ttl: 30 * 60 * 1000
|
|
149
152
|
});
|
|
150
153
|
|
|
151
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Cache of locally-managed DIDs (agent DID + identities). Used to decide
|
|
156
|
+
* whether a target DID should be routed through the local DWN server.
|
|
157
|
+
*/
|
|
158
|
+
private _localManagedDidCache = new TtlCache<string, boolean>({
|
|
159
|
+
ttl: 30 * 60 * 1000
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
/** Controls local DWN discovery behavior ('prefer' | 'only' | 'off'). */
|
|
163
|
+
private _localDwnStrategy: LocalDwnStrategy;
|
|
164
|
+
|
|
165
|
+
/** Lazy-initialized local DWN discovery instance. */
|
|
166
|
+
private _localDwnDiscovery?: LocalDwnDiscovery;
|
|
167
|
+
|
|
168
|
+
constructor({ agent, dwn, localDwnStrategy = 'prefer' }: DwnApiParams) {
|
|
152
169
|
// If an agent is provided, set it as the execution context for this API.
|
|
153
170
|
this._agent = agent;
|
|
154
171
|
|
|
155
172
|
// Set the DWN instance for this API.
|
|
156
173
|
this._dwn = dwn;
|
|
174
|
+
|
|
175
|
+
// Set the local DWN discovery strategy.
|
|
176
|
+
this._localDwnStrategy = localDwnStrategy;
|
|
177
|
+
|
|
178
|
+
// If agent is already available, eagerly initialize the discovery instance.
|
|
179
|
+
if (agent) {
|
|
180
|
+
this._localDwnDiscovery = new LocalDwnDiscovery(agent.rpc);
|
|
181
|
+
}
|
|
157
182
|
}
|
|
158
183
|
|
|
159
184
|
/**
|
|
@@ -172,6 +197,114 @@ export class AgentDwnApi {
|
|
|
172
197
|
|
|
173
198
|
set agent(agent: Web5PlatformAgent) {
|
|
174
199
|
this._agent = agent;
|
|
200
|
+
// Re-initialize local DWN discovery with the new agent's RPC client.
|
|
201
|
+
this._localDwnDiscovery = new LocalDwnDiscovery(agent.rpc);
|
|
202
|
+
this._localManagedDidCache.clear();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
get localDwnStrategy(): LocalDwnStrategy {
|
|
206
|
+
return this._localDwnStrategy;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public setLocalDwnStrategy(strategy: LocalDwnStrategy): void {
|
|
210
|
+
this._localDwnStrategy = strategy;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Resolves the DWN service endpoint URLs for the given target DID, optionally
|
|
215
|
+
* prepending a local DWN server endpoint when local discovery is enabled and
|
|
216
|
+
* the target is a locally-managed DID.
|
|
217
|
+
*
|
|
218
|
+
* @param targetDid - The DID whose DWN endpoints should be resolved.
|
|
219
|
+
* @returns An array of endpoint URLs.
|
|
220
|
+
* @throws When strategy is `'only'` and no local server is available.
|
|
221
|
+
*/
|
|
222
|
+
public async getDwnEndpointUrlsForTarget(targetDid: string): Promise<string[]> {
|
|
223
|
+
const shouldUseLocalDwn = await this.shouldUseLocalDwnForTarget(targetDid);
|
|
224
|
+
|
|
225
|
+
if (!shouldUseLocalDwn) {
|
|
226
|
+
return getDwnServiceEndpointUrls(targetDid, this.agent.did);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const localDwnEndpoint = await this.getLocalDwnEndpoint();
|
|
230
|
+
if (this._localDwnStrategy === 'only') {
|
|
231
|
+
if (!localDwnEndpoint) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`AgentDwnApi: Local DWN strategy is 'only' but no local server is available ` +
|
|
234
|
+
`on localhost/127.0.0.1:{3000,55555-55559}`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return [localDwnEndpoint];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let dwnEndpointUrls: string[] = [];
|
|
242
|
+
try {
|
|
243
|
+
dwnEndpointUrls = await getDwnServiceEndpointUrls(targetDid, this.agent.did);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
if (!localDwnEndpoint) {
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!localDwnEndpoint) {
|
|
251
|
+
return dwnEndpointUrls;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const uniqueEndpoints = new Set<string>([
|
|
255
|
+
localDwnEndpoint,
|
|
256
|
+
...dwnEndpointUrls,
|
|
257
|
+
]);
|
|
258
|
+
|
|
259
|
+
return [...uniqueEndpoints];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Lazily retrieves the local DWN server endpoint via discovery probing. */
|
|
263
|
+
private async getLocalDwnEndpoint(): Promise<string | undefined> {
|
|
264
|
+
this._localDwnDiscovery ??= new LocalDwnDiscovery(this.agent.rpc);
|
|
265
|
+
return this._localDwnDiscovery.getEndpoint();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Determines whether the given target DID should be routed through the
|
|
270
|
+
* local DWN server. Returns `true` if the DID is the agent DID or one
|
|
271
|
+
* of the locally-managed identity DIDs.
|
|
272
|
+
*/
|
|
273
|
+
private async shouldUseLocalDwnForTarget(targetDid: string): Promise<boolean> {
|
|
274
|
+
if (this._localDwnStrategy === 'off') {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const cached = this._localManagedDidCache.get(targetDid);
|
|
279
|
+
if (cached !== undefined) {
|
|
280
|
+
return cached;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (targetDid === this.agent.agentDid.uri) {
|
|
284
|
+
this._localManagedDidCache.set(targetDid, true);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const identities = await this.agent.identity.list();
|
|
289
|
+
const localManagedDids = new Set<string>();
|
|
290
|
+
|
|
291
|
+
for (const identity of identities) {
|
|
292
|
+
localManagedDids.add(identity.did.uri);
|
|
293
|
+
if (identity.metadata.connectedDid) {
|
|
294
|
+
localManagedDids.add(identity.metadata.connectedDid);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
for (const localDid of localManagedDids) {
|
|
299
|
+
this._localManagedDidCache.set(localDid, true);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const isLocalManaged = localManagedDids.has(targetDid);
|
|
303
|
+
if (!isLocalManaged) {
|
|
304
|
+
this._localManagedDidCache.set(targetDid, false);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return isLocalManaged;
|
|
175
308
|
}
|
|
176
309
|
|
|
177
310
|
/**
|
|
@@ -252,8 +385,8 @@ export class AgentDwnApi {
|
|
|
252
385
|
public async sendRequest<T extends DwnInterface>(
|
|
253
386
|
request: SendDwnRequest<T>
|
|
254
387
|
): Promise<DwnResponse<T>> {
|
|
255
|
-
//
|
|
256
|
-
const dwnEndpointUrls = await
|
|
388
|
+
// Resolve DWN service endpoint URLs, with local DWN discovery if enabled.
|
|
389
|
+
const dwnEndpointUrls = await this.getDwnEndpointUrlsForTarget(request.target);
|
|
257
390
|
if (dwnEndpointUrls.length === 0) {
|
|
258
391
|
throw new Error(`AgentDwnApi: DID Service is missing or malformed: ${request.target}#dwn`);
|
|
259
392
|
}
|
|
@@ -997,7 +1130,7 @@ export class AgentDwnApi {
|
|
|
997
1130
|
protocolUri: string,
|
|
998
1131
|
): Promise<ProtocolDefinition> {
|
|
999
1132
|
return fetchRemoteProtocolDefinitionFn(
|
|
1000
|
-
targetDid, protocolUri, this.
|
|
1133
|
+
targetDid, protocolUri, this.getDwnEndpointUrlsForTarget.bind(this),
|
|
1001
1134
|
this.sendDwnRpcRequest.bind(this), this._protocolDefinitionCache,
|
|
1002
1135
|
);
|
|
1003
1136
|
}
|
|
@@ -1022,7 +1155,7 @@ export class AgentDwnApi {
|
|
|
1022
1155
|
): Promise<{ rootKeyId: string; derivedPublicKey: PublicKeyJwk } | undefined> {
|
|
1023
1156
|
return extractDerivedPublicKeyFn(
|
|
1024
1157
|
targetDid, protocolUri, rootContextId, requesterDid,
|
|
1025
|
-
this.
|
|
1158
|
+
this.getDwnEndpointUrlsForTarget.bind(this), this.getSigner.bind(this),
|
|
1026
1159
|
this.sendDwnRpcRequest.bind(this),
|
|
1027
1160
|
);
|
|
1028
1161
|
}
|
|
@@ -1139,6 +1272,7 @@ export class AgentDwnApi {
|
|
|
1139
1272
|
this.agent, tenantDid, contextKeyMessage,
|
|
1140
1273
|
this.getDwnMessage.bind(this),
|
|
1141
1274
|
this.sendDwnRpcRequest.bind(this),
|
|
1275
|
+
this.getDwnEndpointUrlsForTarget.bind(this),
|
|
1142
1276
|
);
|
|
1143
1277
|
}
|
|
1144
1278
|
|
|
@@ -1160,6 +1294,7 @@ export class AgentDwnApi {
|
|
|
1160
1294
|
this.processRequest.bind(this),
|
|
1161
1295
|
this.getSigner.bind(this),
|
|
1162
1296
|
this.sendDwnRpcRequest.bind(this),
|
|
1297
|
+
this.getDwnEndpointUrlsForTarget.bind(this),
|
|
1163
1298
|
);
|
|
1164
1299
|
}
|
|
1165
1300
|
}
|
package/src/dwn-key-delivery.ts
CHANGED
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
Records,
|
|
23
23
|
} from '@enbox/dwn-sdk-js';
|
|
24
24
|
|
|
25
|
-
import { getDwnServiceEndpointUrls } from './utils.js';
|
|
26
25
|
import { KeyDeliveryProtocolDefinition } from './store-data-protocols.js';
|
|
27
26
|
import { buildEncryptionInput, encryptAndComputeCid, getEncryptionKeyDeriver, getKeyDecrypter, ivLength } from './dwn-encryption.js';
|
|
28
27
|
import { DwnInterface, dwnMessageConstructors } from './types/dwn.js';
|
|
@@ -219,6 +218,7 @@ export async function writeContextKeyRecord(
|
|
|
219
218
|
* @param contextKeyMessage - The context key message to send
|
|
220
219
|
* @param getDwnMessage - Function to read a full message from local DWN
|
|
221
220
|
* @param sendDwnRpcRequest - Function to send a DWN RPC request
|
|
221
|
+
* @param getDwnEndpointUrlsForTarget - Function to resolve DWN endpoint URLs (with local discovery)
|
|
222
222
|
*/
|
|
223
223
|
export async function eagerSendContextKeyRecord(
|
|
224
224
|
agent: Web5PlatformAgent,
|
|
@@ -226,10 +226,11 @@ export async function eagerSendContextKeyRecord(
|
|
|
226
226
|
contextKeyMessage: DwnMessage[DwnInterface.RecordsWrite],
|
|
227
227
|
getDwnMessage: (params: { author: string; messageType: DwnInterface; messageCid: string }) => Promise<{ message: any; data?: Blob }>,
|
|
228
228
|
sendDwnRpcRequest: (params: { targetDid: string; dwnEndpointUrls: string[]; message: any; data?: Blob }) => Promise<any>,
|
|
229
|
+
getDwnEndpointUrlsForTarget: (targetDid: string) => Promise<string[]>,
|
|
229
230
|
): Promise<void> {
|
|
230
231
|
let dwnEndpointUrls: string[];
|
|
231
232
|
try {
|
|
232
|
-
dwnEndpointUrls = await
|
|
233
|
+
dwnEndpointUrls = await getDwnEndpointUrlsForTarget(tenantDid);
|
|
233
234
|
} catch {
|
|
234
235
|
// DID resolution or endpoint lookup failed — not fatal, sync will handle it.
|
|
235
236
|
return;
|
|
@@ -266,6 +267,7 @@ export async function eagerSendContextKeyRecord(
|
|
|
266
267
|
* @param processRequest - The agent's processRequest method (bound)
|
|
267
268
|
* @param getSigner - Function to get a signer for a DID
|
|
268
269
|
* @param sendDwnRpcRequest - Function to send a DWN RPC request
|
|
270
|
+
* @param getDwnEndpointUrlsForTarget - Function to resolve DWN endpoint URLs (with local discovery)
|
|
269
271
|
* @returns The decrypted `DerivedPrivateJwk`, or `undefined` if no matching record found
|
|
270
272
|
*/
|
|
271
273
|
export async function fetchContextKeyRecord(
|
|
@@ -274,6 +276,7 @@ export async function fetchContextKeyRecord(
|
|
|
274
276
|
processRequest: ProcessRequestFn,
|
|
275
277
|
getSigner: (author: string) => Promise<any>,
|
|
276
278
|
sendDwnRpcRequest: (params: { targetDid: string; dwnEndpointUrls: string[]; message: any; data?: Blob }) => Promise<any>,
|
|
279
|
+
getDwnEndpointUrlsForTarget: (targetDid: string) => Promise<string[]>,
|
|
277
280
|
): Promise<DerivedPrivateJwk | undefined> {
|
|
278
281
|
const { ownerDid, requesterDid, sourceProtocol, sourceContextId } = params;
|
|
279
282
|
const protocolUri = KeyDeliveryProtocolDefinition.protocol;
|
|
@@ -323,7 +326,7 @@ export async function fetchContextKeyRecord(
|
|
|
323
326
|
} else {
|
|
324
327
|
// Remote query: participant queries the context owner's DWN
|
|
325
328
|
const signer = await getSigner(requesterDid);
|
|
326
|
-
const dwnEndpointUrls = await
|
|
329
|
+
const dwnEndpointUrls = await getDwnEndpointUrlsForTarget(ownerDid);
|
|
327
330
|
|
|
328
331
|
const recordsQuery = await dwnMessageConstructors[DwnInterface.RecordsQuery].create({
|
|
329
332
|
signer,
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { DidUrlDereferencer } from '@enbox/dids';
|
|
11
10
|
import type { PublicKeyJwk } from '@enbox/crypto';
|
|
12
11
|
import type { TtlCache } from '@enbox/common';
|
|
13
12
|
import type {
|
|
@@ -26,13 +25,15 @@ import type {
|
|
|
26
25
|
|
|
27
26
|
import { KeyDerivationScheme } from '@enbox/dwn-sdk-js';
|
|
28
27
|
|
|
29
|
-
import { getDwnServiceEndpointUrls } from './utils.js';
|
|
30
28
|
import { DwnInterface as DwnInterfaceEnum, dwnMessageConstructors } from './types/dwn.js';
|
|
31
29
|
|
|
32
30
|
// ---------------------------------------------------------------------------
|
|
33
31
|
// Dependency signatures — keep the extracted code free of `this` references.
|
|
34
32
|
// ---------------------------------------------------------------------------
|
|
35
33
|
|
|
34
|
+
/** Callback to resolve DWN endpoint URLs for a target DID (with local discovery). */
|
|
35
|
+
type GetDwnEndpointUrlsFn = (targetDid: string) => Promise<string[]>;
|
|
36
|
+
|
|
36
37
|
/** Callback to obtain a DWN signer for a given DID. */
|
|
37
38
|
type GetSignerFn = (author: string) => Promise<DwnSigner>;
|
|
38
39
|
|
|
@@ -106,7 +107,7 @@ export async function getProtocolDefinition(
|
|
|
106
107
|
*
|
|
107
108
|
* @param targetDid - The remote DWN owner
|
|
108
109
|
* @param protocolUri - The protocol URI to look up
|
|
109
|
-
* @param
|
|
110
|
+
* @param getDwnEndpointUrls - Callback to resolve DWN endpoint URLs (with local discovery)
|
|
110
111
|
* @param sendDwnRpcRequest - Callback to send the RPC query
|
|
111
112
|
* @param cache - The shared protocol definition cache
|
|
112
113
|
* @returns The protocol definition
|
|
@@ -115,7 +116,7 @@ export async function getProtocolDefinition(
|
|
|
115
116
|
export async function fetchRemoteProtocolDefinition(
|
|
116
117
|
targetDid: string,
|
|
117
118
|
protocolUri: string,
|
|
118
|
-
|
|
119
|
+
getDwnEndpointUrls: GetDwnEndpointUrlsFn,
|
|
119
120
|
sendDwnRpcRequest: SendDwnRpcRequestFn,
|
|
120
121
|
cache: TtlCache<string, ProtocolDefinition>,
|
|
121
122
|
): Promise<ProtocolDefinition> {
|
|
@@ -131,7 +132,7 @@ export async function fetchRemoteProtocolDefinition(
|
|
|
131
132
|
|
|
132
133
|
const reply = await sendDwnRpcRequest({
|
|
133
134
|
targetDid,
|
|
134
|
-
dwnEndpointUrls : await
|
|
135
|
+
dwnEndpointUrls : await getDwnEndpointUrls(targetDid),
|
|
135
136
|
message : protocolsQuery.message,
|
|
136
137
|
}) as ProtocolsQueryReply;
|
|
137
138
|
|
|
@@ -158,7 +159,7 @@ export async function fetchRemoteProtocolDefinition(
|
|
|
158
159
|
* @param protocolUri - The protocol URI to search
|
|
159
160
|
* @param rootContextId - The root context ID
|
|
160
161
|
* @param requesterDid - The DID of the requester (used for signing the query)
|
|
161
|
-
* @param
|
|
162
|
+
* @param getDwnEndpointUrls - Callback to resolve DWN endpoint URLs (with local discovery)
|
|
162
163
|
* @param getSigner - Callback to obtain the signer for `requesterDid`
|
|
163
164
|
* @param sendDwnRpcRequest - Callback to send the RPC query
|
|
164
165
|
* @returns The rootKeyId and derivedPublicKey, or `undefined` if no
|
|
@@ -169,7 +170,7 @@ export async function extractDerivedPublicKey(
|
|
|
169
170
|
protocolUri: string,
|
|
170
171
|
rootContextId: string,
|
|
171
172
|
requesterDid: string,
|
|
172
|
-
|
|
173
|
+
getDwnEndpointUrls: GetDwnEndpointUrlsFn,
|
|
173
174
|
getSigner: GetSignerFn,
|
|
174
175
|
sendDwnRpcRequest: SendDwnRpcRequestFn,
|
|
175
176
|
): Promise<{ rootKeyId: string; derivedPublicKey: PublicKeyJwk } | undefined> {
|
|
@@ -184,7 +185,7 @@ export async function extractDerivedPublicKey(
|
|
|
184
185
|
},
|
|
185
186
|
});
|
|
186
187
|
|
|
187
|
-
const dwnEndpointUrls = await
|
|
188
|
+
const dwnEndpointUrls = await getDwnEndpointUrls(targetDid);
|
|
188
189
|
const queryReply = await sendDwnRpcRequest<DwnInterfaceEnum.RecordsQuery>({
|
|
189
190
|
targetDid,
|
|
190
191
|
dwnEndpointUrls,
|
package/src/identity-api.ts
CHANGED
|
@@ -9,7 +9,6 @@ import type { IdentityMetadata, PortableIdentity } from './types/identity.js';
|
|
|
9
9
|
import { isPortableDid } from '@enbox/dids';
|
|
10
10
|
|
|
11
11
|
import { BearerIdentity } from './bearer-identity.js';
|
|
12
|
-
import { getDwnServiceEndpointUrls } from './utils.js';
|
|
13
12
|
import { InMemoryIdentityStore } from './store-identity.js';
|
|
14
13
|
|
|
15
14
|
export interface IdentityApiParams<TKeyManager extends AgentKeyManager> {
|
|
@@ -226,7 +225,7 @@ export class AgentIdentityApi<TKeyManager extends AgentKeyManager = AgentKeyMana
|
|
|
226
225
|
* @throws An error if the DID is not found, or no DWN service exists.
|
|
227
226
|
*/
|
|
228
227
|
public getDwnEndpoints({ didUri }: { didUri: string; }): Promise<string[]> {
|
|
229
|
-
return
|
|
228
|
+
return this.agent.dwn.getDwnEndpointUrlsForTarget(didUri);
|
|
230
229
|
}
|
|
231
230
|
|
|
232
231
|
/**
|
package/src/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ export * from './dwn-type-guards.js';
|
|
|
20
20
|
export * from './protocol-utils.js';
|
|
21
21
|
export * from './hd-identity-vault.js';
|
|
22
22
|
export * from './identity-api.js';
|
|
23
|
+
export * from './local-dwn.js';
|
|
23
24
|
export * from './local-key-manager.js';
|
|
24
25
|
export * from './permissions-api.js';
|
|
25
26
|
export * from './store-data.js';
|
package/src/local-dwn.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local DWN discovery — probes well-known localhost ports for a running
|
|
3
|
+
* `@enbox/dwn-server` instance so the agent can route traffic to it.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Web5Rpc } from '@enbox/dwn-clients';
|
|
9
|
+
|
|
10
|
+
/** Well-known ports the local DWN desktop app may bind to. */
|
|
11
|
+
export const localDwnPortCandidates = [3000, 55555, 55556, 55557, 55558, 55559] as const;
|
|
12
|
+
|
|
13
|
+
/** Hosts probed when discovering a local DWN server. */
|
|
14
|
+
export const localDwnHostCandidates = ['127.0.0.1', 'localhost'] as const;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Controls how the agent discovers and routes to a local DWN server.
|
|
18
|
+
*
|
|
19
|
+
* - `'prefer'` — probe localhost first; fall back to DID-document endpoints.
|
|
20
|
+
* - `'only'` — require a local server; throw if none is found.
|
|
21
|
+
* - `'off'` — skip local discovery entirely.
|
|
22
|
+
*/
|
|
23
|
+
export type LocalDwnStrategy = 'prefer' | 'only' | 'off';
|
|
24
|
+
|
|
25
|
+
/** The `server` field returned by `GET /info` on `@enbox/dwn-server`. */
|
|
26
|
+
export const localDwnServerName = '@enbox/dwn-server';
|
|
27
|
+
|
|
28
|
+
/** Strips a trailing slash from a URL so endpoint comparisons are consistent. */
|
|
29
|
+
function normalizeBaseUrl(url: string): string {
|
|
30
|
+
return url.endsWith('/') ? url.slice(0, -1) : url;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Probes well-known localhost ports for a running `@enbox/dwn-server` instance.
|
|
35
|
+
*
|
|
36
|
+
* Results are cached for {@link _cacheTtlMs} milliseconds (default 10 s) to
|
|
37
|
+
* avoid repeated HTTP round-trips on hot paths such as sync.
|
|
38
|
+
*
|
|
39
|
+
* TODO: Replace sequential port probing with an `enbox://` protocol-handler
|
|
40
|
+
* handshake (https://github.com/enboxorg/enbox/issues/287).
|
|
41
|
+
*/
|
|
42
|
+
export class LocalDwnDiscovery {
|
|
43
|
+
private _cachedEndpoint?: string;
|
|
44
|
+
private _cacheExpiry = 0;
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
private _rpcClient: Web5Rpc,
|
|
48
|
+
private _cacheTtlMs = 10_000
|
|
49
|
+
) {}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns the base URL of a local DWN server, or `undefined` if none is
|
|
53
|
+
* reachable on the well-known port candidates.
|
|
54
|
+
*/
|
|
55
|
+
public async getEndpoint(): Promise<string | undefined> {
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
if (now < this._cacheExpiry) {
|
|
58
|
+
return this._cachedEndpoint;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const port of localDwnPortCandidates) {
|
|
62
|
+
for (const host of localDwnHostCandidates) {
|
|
63
|
+
const endpoint = `http://${host}:${port}`;
|
|
64
|
+
try {
|
|
65
|
+
const serverInfo = await this._rpcClient.getServerInfo(endpoint);
|
|
66
|
+
if (serverInfo.server === localDwnServerName) {
|
|
67
|
+
this._cachedEndpoint = normalizeBaseUrl(endpoint);
|
|
68
|
+
this._cacheExpiry = now + this._cacheTtlMs;
|
|
69
|
+
return this._cachedEndpoint;
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
// keep probing candidate endpoints
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this._cachedEndpoint = undefined;
|
|
78
|
+
this._cacheExpiry = now + this._cacheTtlMs;
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
package/src/sync-engine-level.ts
CHANGED
|
@@ -12,7 +12,6 @@ import type { Web5Agent, Web5PlatformAgent } from './types/agent.js';
|
|
|
12
12
|
|
|
13
13
|
import { AgentPermissionsApi } from './permissions-api.js';
|
|
14
14
|
import { DwnInterface } from './types/dwn.js';
|
|
15
|
-
import { getDwnServiceEndpointUrls } from './utils.js';
|
|
16
15
|
import { isRecordsWrite } from './utils.js';
|
|
17
16
|
import { topologicalSort } from './sync-topological-sort.js';
|
|
18
17
|
import { pullMessages, pushMessages } from './sync-messages.js';
|
|
@@ -1063,7 +1062,7 @@ export class SyncEngineLevel implements SyncEngine {
|
|
|
1063
1062
|
}
|
|
1064
1063
|
const { protocols, delegateDid } = parsed;
|
|
1065
1064
|
|
|
1066
|
-
const dwnEndpointUrls = await
|
|
1065
|
+
const dwnEndpointUrls = await this.agent.dwn.getDwnEndpointUrlsForTarget(did);
|
|
1067
1066
|
if (dwnEndpointUrls.length === 0) {
|
|
1068
1067
|
continue;
|
|
1069
1068
|
}
|
package/src/test-harness.ts
CHANGED
|
@@ -277,7 +277,8 @@ export class PlatformAgentTestHarness {
|
|
|
277
277
|
});
|
|
278
278
|
|
|
279
279
|
// Instantiate Agent's DWN API using the custom DWN instance.
|
|
280
|
-
|
|
280
|
+
// Disable local DWN discovery so tests don't accidentally probe localhost.
|
|
281
|
+
const dwnApi = new AgentDwnApi({ dwn, localDwnStrategy: 'off' });
|
|
281
282
|
|
|
282
283
|
// Instantiate Agent's Sync API using a custom LevelDB-backed store.
|
|
283
284
|
const syncStore = new Level(testDataPath('SYNC_STORE'));
|
package/src/web5-user-agent.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentKeyManager } from './types/key-manager.js';
|
|
2
2
|
import type { BearerDid } from '@enbox/dids';
|
|
3
|
+
import type { LocalDwnStrategy } from './local-dwn.js';
|
|
3
4
|
import type { Web5PlatformAgent } from './types/agent.js';
|
|
4
5
|
import type { Web5Rpc } from '@enbox/dwn-clients';
|
|
5
6
|
import type { DidInterface, DidRequest, DidResponse } from './did-api.js';
|
|
@@ -87,6 +88,10 @@ export type AgentParams<TKeyManager extends AgentKeyManager = LocalKeyManager> =
|
|
|
87
88
|
syncApi: AgentSyncApi;
|
|
88
89
|
};
|
|
89
90
|
|
|
91
|
+
export type CreateUserAgentParams = Partial<AgentParams> & {
|
|
92
|
+
localDwnStrategy?: LocalDwnStrategy;
|
|
93
|
+
};
|
|
94
|
+
|
|
90
95
|
export class Web5UserAgent<TKeyManager extends AgentKeyManager = LocalKeyManager> implements Web5PlatformAgent<TKeyManager> {
|
|
91
96
|
public crypto: AgentCryptoApi;
|
|
92
97
|
public did: AgentDidApi<TKeyManager>;
|
|
@@ -140,8 +145,9 @@ export class Web5UserAgent<TKeyManager extends AgentKeyManager = LocalKeyManager
|
|
|
140
145
|
*/
|
|
141
146
|
public static async create({
|
|
142
147
|
dataPath = 'DATA/AGENT',
|
|
148
|
+
localDwnStrategy,
|
|
143
149
|
agentDid, agentVault, cryptoApi, didApi, dwnApi, identityApi, keyManager, permissionsApi, rpcClient, syncApi
|
|
144
|
-
}:
|
|
150
|
+
}: CreateUserAgentParams = {}
|
|
145
151
|
): Promise<Web5UserAgent> {
|
|
146
152
|
|
|
147
153
|
agentVault ??= new HdIdentityVault({
|
|
@@ -158,8 +164,12 @@ export class Web5UserAgent<TKeyManager extends AgentKeyManager = LocalKeyManager
|
|
|
158
164
|
});
|
|
159
165
|
|
|
160
166
|
dwnApi ??= new AgentDwnApi({
|
|
161
|
-
dwn: await AgentDwnApi.createDwn({ dataPath, didResolver: didApi })
|
|
167
|
+
dwn : await AgentDwnApi.createDwn({ dataPath, didResolver: didApi }),
|
|
168
|
+
localDwnStrategy : localDwnStrategy ?? 'prefer',
|
|
162
169
|
});
|
|
170
|
+
if (localDwnStrategy) {
|
|
171
|
+
dwnApi.setLocalDwnStrategy(localDwnStrategy);
|
|
172
|
+
}
|
|
163
173
|
|
|
164
174
|
identityApi ??= new AgentIdentityApi({ store: new DwnIdentityStore() });
|
|
165
175
|
|
|
@@ -250,4 +260,4 @@ export class Web5UserAgent<TKeyManager extends AgentKeyManager = LocalKeyManager
|
|
|
250
260
|
// Set the Agent's DID.
|
|
251
261
|
this.agentDid = await this.vault.getDid();
|
|
252
262
|
}
|
|
253
|
-
}
|
|
263
|
+
}
|