@enbox/agent 0.7.0 → 0.7.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.
- package/dist/browser.mjs +11 -11
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/agent-session.js +16 -0
- package/dist/esm/agent-session.js.map +1 -0
- package/dist/esm/dwn-api.js +5 -4
- package/dist/esm/dwn-api.js.map +1 -1
- package/dist/esm/dwn-encryption.js +11 -4
- package/dist/esm/dwn-encryption.js.map +1 -1
- package/dist/esm/enbox-connect-protocol.js +376 -207
- package/dist/esm/enbox-connect-protocol.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/local-key-manager.js.map +1 -1
- package/dist/esm/protocol-utils.js +19 -6
- package/dist/esm/protocol-utils.js.map +1 -1
- package/dist/esm/store-data.js.map +1 -1
- package/dist/esm/store-key.js +1 -1
- package/dist/esm/store-key.js.map +1 -1
- package/dist/esm/sync-engine-level.js +18 -5
- package/dist/esm/sync-engine-level.js.map +1 -1
- package/dist/esm/types/dwn.js.map +1 -1
- package/dist/esm/utils.js +0 -12
- package/dist/esm/utils.js.map +1 -1
- package/dist/types/agent-session.d.ts +59 -0
- package/dist/types/agent-session.d.ts.map +1 -0
- package/dist/types/dwn-api.d.ts.map +1 -1
- package/dist/types/dwn-encryption.d.ts.map +1 -1
- package/dist/types/enbox-connect-protocol.d.ts +34 -3
- package/dist/types/enbox-connect-protocol.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-key-manager.d.ts.map +1 -1
- package/dist/types/protocol-utils.d.ts.map +1 -1
- package/dist/types/sync-engine-level.d.ts +6 -1
- package/dist/types/sync-engine-level.d.ts.map +1 -1
- package/dist/types/types/dwn.d.ts +10 -1
- package/dist/types/types/dwn.d.ts.map +1 -1
- package/dist/types/utils.d.ts +0 -2
- package/dist/types/utils.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/agent-session.ts +78 -0
- package/src/dwn-api.ts +5 -4
- package/src/dwn-encryption.ts +14 -6
- package/src/enbox-connect-protocol.ts +466 -256
- package/src/index.ts +1 -0
- package/src/local-key-manager.ts +7 -3
- package/src/protocol-utils.ts +19 -8
- package/src/store-data.ts +1 -1
- package/src/store-key.ts +1 -1
- package/src/sync-engine-level.ts +19 -6
- package/src/types/dwn.ts +21 -12
- package/src/utils.ts +3 -18
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export { evaluateClosure, evaluateClosureBatch } from './sync-closure-resolver.j
|
|
|
13
13
|
export type * from './types/vc.js';
|
|
14
14
|
|
|
15
15
|
export * from './agent-did-resolver-cache.js';
|
|
16
|
+
export * from './agent-session.js';
|
|
16
17
|
export * from './anonymous-dwn-api.js';
|
|
17
18
|
export * from './bearer-identity.js';
|
|
18
19
|
export * from './crypto-api.js';
|
package/src/local-key-manager.ts
CHANGED
|
@@ -770,9 +770,13 @@ export class LocalKeyManager implements AgentKeyManager {
|
|
|
770
770
|
// agent DID's X25519 key, so looking up that key via the DwnKeyStore would
|
|
771
771
|
// cause infinite recursion (decrypt → getPrivateKey → DwnKeyStore.get → decrypt…).
|
|
772
772
|
try {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
773
|
+
// The base `KeyManager` interface doesn't declare `exportKey` — only
|
|
774
|
+
// some implementations support raw-key export. Narrowly-typed probe
|
|
775
|
+
// (no `any`) so the call below uses the structurally correct type.
|
|
776
|
+
type ExportableKeyManager = { exportKey: (params: { keyUri: string }) => Promise<unknown> };
|
|
777
|
+
const agentKeyManager = this.agent?.agentDid?.keyManager as Partial<ExportableKeyManager> | undefined;
|
|
778
|
+
if (agentKeyManager && typeof agentKeyManager.exportKey === 'function') {
|
|
779
|
+
const agentKey = await agentKeyManager.exportKey({ keyUri });
|
|
776
780
|
if (agentKey && isPrivateJwk(agentKey)) {
|
|
777
781
|
return agentKey;
|
|
778
782
|
}
|
package/src/protocol-utils.ts
CHANGED
|
@@ -10,12 +10,17 @@ export function getRuleSetAtPath(
|
|
|
10
10
|
protocolDefinition: ProtocolDefinition,
|
|
11
11
|
protocolPath: string,
|
|
12
12
|
): ProtocolRuleSet | undefined {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const [first, ...rest] = protocolPath.split('/');
|
|
14
|
+
// Top-level lookup uses the structure's declared `{ [key: string]:
|
|
15
|
+
// ProtocolRuleSet }` index signature directly — no top-level cast.
|
|
16
|
+
let ruleSet: ProtocolRuleSet | undefined = protocolDefinition.structure[first];
|
|
17
|
+
for (const segment of rest) {
|
|
18
18
|
if (!ruleSet) { return undefined; }
|
|
19
|
+
// Nested rule sets index into a `ProtocolRuleSet`'s child map.
|
|
20
|
+
// `ProtocolRuleSet` has typed `$encryption`/`$actions`/etc. fields
|
|
21
|
+
// alongside the child index signature, so the index access here
|
|
22
|
+
// returns a union; narrow it back to `ProtocolRuleSet | undefined`.
|
|
23
|
+
ruleSet = ruleSet[segment] as ProtocolRuleSet | undefined;
|
|
19
24
|
}
|
|
20
25
|
return ruleSet;
|
|
21
26
|
}
|
|
@@ -91,8 +96,6 @@ export function hasRelationalReadAccess(
|
|
|
91
96
|
ofPath: string,
|
|
92
97
|
protocolDefinition: ProtocolDefinition,
|
|
93
98
|
): boolean {
|
|
94
|
-
const structure = protocolDefinition.structure as unknown as ProtocolRuleSet;
|
|
95
|
-
|
|
96
99
|
function walkRuleSet(rs: ProtocolRuleSet): boolean {
|
|
97
100
|
// Check $actions on this node
|
|
98
101
|
if (rs.$actions) {
|
|
@@ -120,7 +123,15 @@ export function hasRelationalReadAccess(
|
|
|
120
123
|
return false;
|
|
121
124
|
}
|
|
122
125
|
|
|
123
|
-
|
|
126
|
+
// Walk every top-level type. The structure is typed as
|
|
127
|
+
// `{ [key: string]: ProtocolRuleSet }`, so each child is directly a
|
|
128
|
+
// ProtocolRuleSet — no top-level cast needed.
|
|
129
|
+
for (const key in protocolDefinition.structure) {
|
|
130
|
+
if (walkRuleSet(protocolDefinition.structure[key])) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
/**
|
package/src/store-data.ts
CHANGED
|
@@ -337,7 +337,7 @@ export class DwnDataStore<TStoreObject extends Record<string, any> = Jwk> implem
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
// If the record was found, convert back to store object format.
|
|
340
|
-
const storeObject = await Stream.consumeToJson({ readableStream: readReply.entry.data })
|
|
340
|
+
const storeObject = await Stream.consumeToJson<TStoreObject>({ readableStream: readReply.entry.data });
|
|
341
341
|
|
|
342
342
|
// If caching is enabled, add the store object to the cache.
|
|
343
343
|
if (useCache) {
|
package/src/store-key.ts
CHANGED
|
@@ -81,7 +81,7 @@ export class DwnKeyStore extends DwnDataStore<Jwk> implements AgentDataStore<Jwk
|
|
|
81
81
|
throw new Error(`${this.name}: Failed to read encrypted key record: ${record.recordId}`);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
storedKey = await Stream.consumeToJson({ readableStream: readResult.entry.data })
|
|
84
|
+
storedKey = await Stream.consumeToJson<Jwk>({ readableStream: readResult.entry.data });
|
|
85
85
|
} else {
|
|
86
86
|
// Unencrypted record (legacy or non-encrypted store) — read inline.
|
|
87
87
|
if (!record.encodedData) {
|
package/src/sync-engine-level.ts
CHANGED
|
@@ -6,12 +6,13 @@ import type { GenericMessage, MessageEvent, MessagesSubscribeReply, MessagesSync
|
|
|
6
6
|
import ms from 'ms';
|
|
7
7
|
|
|
8
8
|
import { Level } from 'level';
|
|
9
|
+
import { sleep } from '@enbox/common';
|
|
9
10
|
import { Encoder, hashToHex, initDefaultHashes, Message } from '@enbox/dwn-sdk-js';
|
|
10
11
|
|
|
11
12
|
import type { ClosureEvaluationContext } from './sync-closure-types.js';
|
|
13
|
+
import type { EnboxPlatformAgent } from './types/agent.js';
|
|
12
14
|
import type { PermissionsApi } from './types/permissions.js';
|
|
13
15
|
import type { DeadLetterCategory, DeadLetterEntry, PushResult, ReplicationLinkState, StartSyncParams, SyncConnectivityState, SyncEngine, SyncEvent, SyncEventListener, SyncHealthSummary, SyncIdentityOptions, SyncMode, SyncScope } from './types/sync.js';
|
|
14
|
-
import type { EnboxAgent, EnboxPlatformAgent } from './types/agent.js';
|
|
15
16
|
|
|
16
17
|
import { evaluateClosure } from './sync-closure-resolver.js';
|
|
17
18
|
import { MAX_PENDING_TOKENS } from './types/sync.js';
|
|
@@ -313,7 +314,7 @@ export class SyncEngineLevel implements SyncEngine {
|
|
|
313
314
|
|
|
314
315
|
constructor({ agent, dataPath, db }: SyncEngineLevelParams) {
|
|
315
316
|
this._agent = agent;
|
|
316
|
-
this._permissionsApi = new AgentPermissionsApi({ agent
|
|
317
|
+
this._permissionsApi = new AgentPermissionsApi({ agent });
|
|
317
318
|
this._db = (db) ? db : new Level<string, string>(dataPath ?? 'DATA/AGENT/SYNC_STORE');
|
|
318
319
|
}
|
|
319
320
|
|
|
@@ -346,7 +347,7 @@ export class SyncEngineLevel implements SyncEngine {
|
|
|
346
347
|
|
|
347
348
|
set agent(agent: EnboxPlatformAgent) {
|
|
348
349
|
this._agent = agent;
|
|
349
|
-
this._permissionsApi = new AgentPermissionsApi({ agent
|
|
350
|
+
this._permissionsApi = new AgentPermissionsApi({ agent });
|
|
350
351
|
// Cached sync targets were resolved through the previous agent's
|
|
351
352
|
// DID resolver / endpoint lookup — invalidate so the next sync
|
|
352
353
|
// tick re-resolves through the new agent.
|
|
@@ -597,18 +598,30 @@ export class SyncEngineLevel implements SyncEngine {
|
|
|
597
598
|
/**
|
|
598
599
|
* stopSync awaits the completion of the current sync operation before stopping the sync interval
|
|
599
600
|
* and tearing down any live subscriptions.
|
|
601
|
+
*
|
|
602
|
+
* @param timeout - Maximum milliseconds to wait for an in-progress
|
|
603
|
+
* sync cycle to finish. Non-finite values (`NaN`, `Infinity`) are
|
|
604
|
+
* coerced to the default to avoid a tight poll loop or never-exit
|
|
605
|
+
* condition.
|
|
600
606
|
*/
|
|
601
607
|
public async stopSync(timeout: number = 2000): Promise<void> {
|
|
608
|
+
// Coerce non-finite timeouts (NaN, Infinity) to the default. NaN
|
|
609
|
+
// comparisons are always false, so `elapsedTimeout >= NaN` would
|
|
610
|
+
// never trip the timeout exit; `Math.min(NaN, 100)` is NaN and
|
|
611
|
+
// `setTimeout(_, NaN)` clamps to 0, spinning the poll loop. Both
|
|
612
|
+
// are footguns for callers passing a computed timeout that
|
|
613
|
+
// accidentally evaluates to NaN.
|
|
614
|
+
const safeTimeout = Number.isFinite(timeout) ? timeout : 2000;
|
|
602
615
|
this._engineGeneration++;
|
|
603
616
|
let elapsedTimeout = 0;
|
|
604
617
|
|
|
605
618
|
while (this._syncLock) {
|
|
606
|
-
if (elapsedTimeout >=
|
|
607
|
-
throw new Error(`SyncEngineLevel: Existing sync operation did not complete within ${
|
|
619
|
+
if (elapsedTimeout >= safeTimeout) {
|
|
620
|
+
throw new Error(`SyncEngineLevel: Existing sync operation did not complete within ${safeTimeout} milliseconds.`);
|
|
608
621
|
}
|
|
609
622
|
|
|
610
623
|
elapsedTimeout += 100;
|
|
611
|
-
await
|
|
624
|
+
await sleep(Math.min(safeTimeout, 100));
|
|
612
625
|
}
|
|
613
626
|
|
|
614
627
|
if (this._syncIntervalId) {
|
package/src/types/dwn.ts
CHANGED
|
@@ -202,24 +202,33 @@ export type DwnResponse<T extends DwnInterface> = {
|
|
|
202
202
|
reply: DwnMessageReply[T];
|
|
203
203
|
};
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Per-DWN-interface message factory. Only the static `create` and `parse`
|
|
207
|
+
* methods are part of the contract — the underlying classes have private
|
|
208
|
+
* or protected constructors (factory pattern), so the interface
|
|
209
|
+
* intentionally does not declare `new ()`. Omitting `new ()` lets the
|
|
210
|
+
* mapped-type table below assign class values directly without casts:
|
|
211
|
+
* a class with a private constructor still satisfies a structural type
|
|
212
|
+
* that doesn't require constructability, as long as the static side
|
|
213
|
+
* (`create` / `parse`) lines up.
|
|
214
|
+
*/
|
|
205
215
|
export interface DwnMessageConstructor<T extends DwnInterface> {
|
|
206
|
-
new (): DwnMessageInstance[T];
|
|
207
216
|
create(params: DwnMessageParams[T]): Promise<DwnMessageInstance[T]>;
|
|
208
217
|
parse(rawMessage: DwnMessage[T]): Promise<DwnMessageInstance[T]>;
|
|
209
218
|
}
|
|
210
219
|
|
|
211
220
|
export const dwnMessageConstructors: { [T in DwnInterface]: DwnMessageConstructor<T> } = {
|
|
212
|
-
[DwnInterface.MessagesRead] : MessagesRead
|
|
213
|
-
[DwnInterface.MessagesSubscribe] : MessagesSubscribe
|
|
214
|
-
[DwnInterface.MessagesSync] : MessagesSync
|
|
215
|
-
[DwnInterface.ProtocolsConfigure] : ProtocolsConfigure
|
|
216
|
-
[DwnInterface.ProtocolsQuery] : ProtocolsQuery
|
|
217
|
-
[DwnInterface.RecordsDelete] : RecordsDelete
|
|
218
|
-
[DwnInterface.RecordsQuery] : RecordsQuery
|
|
219
|
-
[DwnInterface.RecordsRead] : RecordsRead
|
|
220
|
-
[DwnInterface.RecordsSubscribe] : RecordsSubscribe
|
|
221
|
-
[DwnInterface.RecordsWrite] : RecordsWrite
|
|
222
|
-
}
|
|
221
|
+
[DwnInterface.MessagesRead] : MessagesRead,
|
|
222
|
+
[DwnInterface.MessagesSubscribe] : MessagesSubscribe,
|
|
223
|
+
[DwnInterface.MessagesSync] : MessagesSync,
|
|
224
|
+
[DwnInterface.ProtocolsConfigure] : ProtocolsConfigure,
|
|
225
|
+
[DwnInterface.ProtocolsQuery] : ProtocolsQuery,
|
|
226
|
+
[DwnInterface.RecordsDelete] : RecordsDelete,
|
|
227
|
+
[DwnInterface.RecordsQuery] : RecordsQuery,
|
|
228
|
+
[DwnInterface.RecordsRead] : RecordsRead,
|
|
229
|
+
[DwnInterface.RecordsSubscribe] : RecordsSubscribe,
|
|
230
|
+
[DwnInterface.RecordsWrite] : RecordsWrite,
|
|
231
|
+
};
|
|
223
232
|
|
|
224
233
|
export interface DwnMessageInstance {
|
|
225
234
|
[DwnInterface.MessagesRead] : MessagesRead;
|
package/src/utils.ts
CHANGED
|
@@ -155,21 +155,6 @@ export function pollWithTtl(
|
|
|
155
155
|
});
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
/** Concatenates a base URL and a path ensuring that there is exactly one slash between them */
|
|
159
|
-
export function concatenateUrl(baseUrl: string, path: string): string {
|
|
160
|
-
// Remove trailing slash from baseUrl if it exists
|
|
161
|
-
if (baseUrl.endsWith('/')) {
|
|
162
|
-
baseUrl = baseUrl.slice(0, -1);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Remove leading slash from path if it exists
|
|
166
|
-
if (path.startsWith('/')) {
|
|
167
|
-
path = path.slice(1);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return `${baseUrl}/${path}`;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
158
|
/**
|
|
174
159
|
* Map over an array with bounded concurrency, preserving input order in the
|
|
175
160
|
* output array. Uses a sliding-window pool of workers so the next item is
|
|
@@ -229,12 +214,12 @@ export async function mapConcurrentSettled<T, R>(
|
|
|
229
214
|
concurrency: number,
|
|
230
215
|
fn: (item: T, index: number) => Promise<R>,
|
|
231
216
|
): Promise<PromiseSettledResult<R>[]> {
|
|
232
|
-
return mapConcurrent(items, concurrency, async (item, index) => {
|
|
217
|
+
return mapConcurrent<T, PromiseSettledResult<R>>(items, concurrency, async (item, index) => {
|
|
233
218
|
try {
|
|
234
219
|
const value = await fn(item, index);
|
|
235
|
-
return { status: 'fulfilled', value }
|
|
220
|
+
return { status: 'fulfilled', value };
|
|
236
221
|
} catch (reason) {
|
|
237
|
-
return { status: 'rejected', reason }
|
|
222
|
+
return { status: 'rejected', reason };
|
|
238
223
|
}
|
|
239
224
|
});
|
|
240
225
|
}
|