@enbox/agent 0.3.1 → 0.5.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.
- package/dist/browser.mjs +12 -30
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/dwn-api.js +149 -22
- package/dist/esm/dwn-api.js.map +1 -1
- package/dist/esm/dwn-discovery-file.js +1 -1
- package/dist/esm/dwn-discovery-payload.js +20 -21
- package/dist/esm/dwn-discovery-payload.js.map +1 -1
- package/dist/esm/dwn-key-delivery.js.map +1 -1
- package/dist/esm/{oidc.js → enbox-connect-protocol.js} +219 -251
- package/dist/esm/enbox-connect-protocol.js.map +1 -0
- package/dist/esm/enbox-user-agent.js +19 -12
- package/dist/esm/enbox-user-agent.js.map +1 -1
- package/dist/esm/hd-identity-vault.js +11 -0
- package/dist/esm/hd-identity-vault.js.map +1 -1
- package/dist/esm/index.js +4 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/local-dwn.js +21 -51
- package/dist/esm/local-dwn.js.map +1 -1
- package/dist/esm/permissions-api.js.map +1 -1
- package/dist/esm/store-data.js.map +1 -1
- package/dist/esm/sync-engine-level.js +1 -1
- package/dist/esm/sync-engine-level.js.map +1 -1
- package/dist/esm/sync-messages.js +1 -1
- package/dist/esm/sync-messages.js.map +1 -1
- package/dist/esm/test-harness.js +2 -3
- package/dist/esm/test-harness.js.map +1 -1
- package/dist/esm/types/dwn.js.map +1 -1
- package/dist/types/dwn-api.d.ts +46 -6
- package/dist/types/dwn-api.d.ts.map +1 -1
- package/dist/types/dwn-discovery-file.d.ts +1 -1
- package/dist/types/dwn-discovery-payload.d.ts +18 -19
- package/dist/types/dwn-discovery-payload.d.ts.map +1 -1
- package/dist/types/enbox-connect-protocol.d.ts +206 -0
- package/dist/types/enbox-connect-protocol.d.ts.map +1 -0
- package/dist/types/enbox-user-agent.d.ts +13 -8
- package/dist/types/enbox-user-agent.d.ts.map +1 -1
- package/dist/types/hd-identity-vault.d.ts +7 -0
- package/dist/types/hd-identity-vault.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/local-dwn.d.ts +16 -32
- package/dist/types/local-dwn.d.ts.map +1 -1
- package/dist/types/test-harness.d.ts.map +1 -1
- package/dist/types/types/agent.d.ts +2 -7
- package/dist/types/types/agent.d.ts.map +1 -1
- package/dist/types/types/dwn.d.ts +0 -10
- package/dist/types/types/dwn.d.ts.map +1 -1
- package/dist/types/types/sync.d.ts +6 -0
- package/dist/types/types/sync.d.ts.map +1 -1
- package/package.json +14 -16
- package/src/dwn-api.ts +175 -29
- package/src/dwn-discovery-file.ts +1 -1
- package/src/dwn-discovery-payload.ts +23 -24
- package/src/dwn-key-delivery.ts +1 -1
- package/src/enbox-connect-protocol.ts +753 -0
- package/src/enbox-user-agent.ts +31 -18
- package/src/hd-identity-vault.ts +21 -0
- package/src/index.ts +4 -4
- package/src/local-dwn.ts +22 -53
- package/src/permissions-api.ts +3 -3
- package/src/store-data.ts +1 -1
- package/src/sync-engine-level.ts +1 -1
- package/src/sync-messages.ts +1 -1
- package/src/test-harness.ts +2 -3
- package/src/types/agent.ts +3 -14
- package/src/types/dwn.ts +1 -13
- package/src/types/sync.ts +7 -0
- package/dist/esm/connect.js +0 -180
- package/dist/esm/connect.js.map +0 -1
- package/dist/esm/oidc.js.map +0 -1
- package/dist/esm/sync-api.js +0 -64
- package/dist/esm/sync-api.js.map +0 -1
- package/dist/types/connect.d.ts +0 -88
- package/dist/types/connect.d.ts.map +0 -1
- package/dist/types/oidc.d.ts +0 -250
- package/dist/types/oidc.d.ts.map +0 -1
- package/dist/types/sync-api.d.ts +0 -40
- package/dist/types/sync-api.d.ts.map +0 -1
- package/src/connect.ts +0 -285
- package/src/oidc.ts +0 -864
- package/src/sync-api.ts +0 -75
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enbox/agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/esm/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
7
7
|
"types": "./dist/types/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"clean": "
|
|
10
|
-
"build:esm": "
|
|
11
|
-
"build:browser": "
|
|
9
|
+
"clean": "rm -rf dist",
|
|
10
|
+
"build:esm": "rm -rf dist/esm dist/types && bun tsc -p tsconfig.json",
|
|
11
|
+
"build:browser": "rm -rf dist/browser.mjs && bun ../../build/browser-bundle.js --node-shims",
|
|
12
12
|
"build": "bun run clean && bun run build:esm && bun run build:browser",
|
|
13
13
|
"lint": "eslint . --max-warnings 0",
|
|
14
14
|
"lint:fix": "eslint . --fix",
|
|
@@ -71,32 +71,30 @@
|
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@scure/bip39": "1.2.2",
|
|
74
|
-
"@enbox/dwn-clients": "0.
|
|
75
|
-
"@enbox/dwn-sdk-js": "0.
|
|
76
|
-
"@enbox/common": "0.0
|
|
77
|
-
"@enbox/crypto": "0.0
|
|
78
|
-
"@enbox/dids": "0.0
|
|
74
|
+
"@enbox/dwn-clients": "0.2.0",
|
|
75
|
+
"@enbox/dwn-sdk-js": "0.2.0",
|
|
76
|
+
"@enbox/common": "0.1.0",
|
|
77
|
+
"@enbox/crypto": "0.1.0",
|
|
78
|
+
"@enbox/dids": "0.1.0",
|
|
79
79
|
"abstract-level": "1.0.4",
|
|
80
80
|
"ed25519-keygen": "0.4.11",
|
|
81
|
-
"level": "8.0.
|
|
81
|
+
"level": "8.0.1",
|
|
82
82
|
"ms": "2.1.3",
|
|
83
83
|
"ulidx": "2.1.0"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@types/dns-packet": "5.6.4",
|
|
87
|
-
"@types/ms": "0.7.
|
|
88
|
-
"@types/node": "
|
|
87
|
+
"@types/ms": "0.7.34",
|
|
88
|
+
"@types/node": "22.19.15",
|
|
89
89
|
"@types/sinon": "17.0.3",
|
|
90
90
|
"@typescript-eslint/eslint-plugin": "8.32.1",
|
|
91
91
|
"@typescript-eslint/parser": "8.32.1",
|
|
92
92
|
"@vitest/browser-playwright": "4.0.18",
|
|
93
93
|
"@vitest/coverage-istanbul": "4.0.18",
|
|
94
|
-
"
|
|
95
|
-
"bun-types": "latest",
|
|
94
|
+
"bun-types": "1.3.10",
|
|
96
95
|
"eslint": "9.7.0",
|
|
97
|
-
"rimraf": "4.4.0",
|
|
98
96
|
"sinon": "18.0.0",
|
|
99
|
-
"typescript": "5.
|
|
97
|
+
"typescript": "5.9.3",
|
|
100
98
|
"vitest": "4.0.18"
|
|
101
99
|
}
|
|
102
100
|
}
|
package/src/dwn-api.ts
CHANGED
|
@@ -86,8 +86,9 @@ import {
|
|
|
86
86
|
writeContextKeyRecord as writeContextKeyRecordFn,
|
|
87
87
|
} from './dwn-key-delivery.js';
|
|
88
88
|
|
|
89
|
-
//
|
|
90
|
-
|
|
89
|
+
// NOTE: upgradeExternalRootRecord is disabled — see TODO in postWriteKeyDelivery().
|
|
90
|
+
// The module is kept for reference but no longer imported.
|
|
91
|
+
// import { upgradeExternalRootRecord as upgradeExternalRootRecordFn } from './dwn-record-upgrade.js';
|
|
91
92
|
|
|
92
93
|
// Import extracted protocol definition fetching functions
|
|
93
94
|
import {
|
|
@@ -103,9 +104,11 @@ type DwnMessageWithBlob<T extends DwnInterface> = {
|
|
|
103
104
|
|
|
104
105
|
type DwnApiParams = {
|
|
105
106
|
agent?: EnboxPlatformAgent;
|
|
106
|
-
dwn: Dwn;
|
|
107
107
|
localDwnStrategy?: LocalDwnStrategy;
|
|
108
|
-
}
|
|
108
|
+
} & (
|
|
109
|
+
| { dwn: Dwn; localDwnEndpoint?: never }
|
|
110
|
+
| { dwn?: never; localDwnEndpoint: string }
|
|
111
|
+
);
|
|
109
112
|
|
|
110
113
|
interface DwnApiCreateDwnParams extends Partial<DwnConfig> {
|
|
111
114
|
dataPath?: string;
|
|
@@ -122,8 +125,17 @@ export class AgentDwnApi {
|
|
|
122
125
|
|
|
123
126
|
/**
|
|
124
127
|
* The DWN instance to use for this API.
|
|
128
|
+
* `undefined` in remote mode — all operations route through RPC to
|
|
129
|
+
* the local DWN server endpoint.
|
|
130
|
+
*/
|
|
131
|
+
private _dwn?: Dwn;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The local DWN server endpoint for remote mode.
|
|
135
|
+
* When set, `_dwn` is `undefined` and `processRequest()` routes
|
|
136
|
+
* through `sendDwnRpcRequest()`.
|
|
125
137
|
*/
|
|
126
|
-
private
|
|
138
|
+
private _localDwnEndpoint?: string;
|
|
127
139
|
|
|
128
140
|
/**
|
|
129
141
|
* Protocol definition cache — TTL 30 minutes. Protocols rarely change.
|
|
@@ -166,12 +178,17 @@ export class AgentDwnApi {
|
|
|
166
178
|
/** Lazy-initialized local DWN discovery instance. */
|
|
167
179
|
private _localDwnDiscovery?: LocalDwnDiscovery;
|
|
168
180
|
|
|
169
|
-
constructor(
|
|
181
|
+
constructor(params: DwnApiParams) {
|
|
182
|
+
const { agent, localDwnStrategy = 'prefer' } = params;
|
|
183
|
+
|
|
170
184
|
// If an agent is provided, set it as the execution context for this API.
|
|
171
185
|
this._agent = agent;
|
|
172
186
|
|
|
173
|
-
// Set the DWN instance
|
|
174
|
-
this._dwn = dwn;
|
|
187
|
+
// Set the DWN instance (undefined in remote mode).
|
|
188
|
+
this._dwn = 'dwn' in params ? params.dwn : undefined;
|
|
189
|
+
|
|
190
|
+
// Set the remote endpoint (undefined in local mode).
|
|
191
|
+
this._localDwnEndpoint = 'localDwnEndpoint' in params ? params.localDwnEndpoint : undefined;
|
|
175
192
|
|
|
176
193
|
// Set the local DWN discovery strategy.
|
|
177
194
|
this._localDwnStrategy = localDwnStrategy;
|
|
@@ -186,6 +203,14 @@ export class AgentDwnApi {
|
|
|
186
203
|
}
|
|
187
204
|
}
|
|
188
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Whether the API is operating in remote mode (no in-process DWN).
|
|
208
|
+
* In remote mode, all DWN operations are routed through RPC.
|
|
209
|
+
*/
|
|
210
|
+
get isRemoteMode(): boolean {
|
|
211
|
+
return this._dwn === undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
189
214
|
/**
|
|
190
215
|
* Retrieves the `EnboxPlatformAgent` execution context.
|
|
191
216
|
*
|
|
@@ -220,7 +245,7 @@ export class AgentDwnApi {
|
|
|
220
245
|
}
|
|
221
246
|
|
|
222
247
|
/**
|
|
223
|
-
* Inject a cached local DWN endpoint (e.g. from a `dwn://
|
|
248
|
+
* Inject a cached local DWN endpoint (e.g. from a `dwn://connect`
|
|
224
249
|
* browser redirect or from persisted storage). The endpoint is validated
|
|
225
250
|
* via `GET /info` before being accepted.
|
|
226
251
|
*
|
|
@@ -257,8 +282,8 @@ export class AgentDwnApi {
|
|
|
257
282
|
if (this._localDwnStrategy === 'only') {
|
|
258
283
|
if (!localDwnEndpoint) {
|
|
259
284
|
throw new Error(
|
|
260
|
-
`AgentDwnApi: Local DWN strategy is 'only' but no local
|
|
261
|
-
`
|
|
285
|
+
`AgentDwnApi: Local DWN strategy is 'only' but no local DWN endpoint was discovered. ` +
|
|
286
|
+
`Ensure the local DWN server is running and discoverable via the discovery file (~/.enbox/dwn.json) or dwn://connect.`
|
|
262
287
|
);
|
|
263
288
|
}
|
|
264
289
|
|
|
@@ -288,6 +313,11 @@ export class AgentDwnApi {
|
|
|
288
313
|
|
|
289
314
|
/** Lazily retrieves the local DWN server endpoint via discovery. */
|
|
290
315
|
private async getLocalDwnEndpoint(): Promise<string | undefined> {
|
|
316
|
+
// In remote mode, the endpoint is always known.
|
|
317
|
+
if (this._localDwnEndpoint) {
|
|
318
|
+
return this._localDwnEndpoint;
|
|
319
|
+
}
|
|
320
|
+
|
|
291
321
|
this._localDwnDiscovery ??= new LocalDwnDiscovery(
|
|
292
322
|
this.agent.rpc,
|
|
293
323
|
10_000,
|
|
@@ -364,6 +394,14 @@ export class AgentDwnApi {
|
|
|
364
394
|
* the DWN instance and not `agent.dwn.dwn`.
|
|
365
395
|
*/
|
|
366
396
|
get node(): Dwn {
|
|
397
|
+
if (!this._dwn) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
'AgentDwnApi: The in-process DWN instance is not available. ' +
|
|
400
|
+
'The agent is operating in remote mode (local DWN server at ' +
|
|
401
|
+
`'${this._localDwnEndpoint}'). Use processRequest() instead ` +
|
|
402
|
+
'of accessing the DWN node directly.'
|
|
403
|
+
);
|
|
404
|
+
}
|
|
367
405
|
return this._dwn;
|
|
368
406
|
}
|
|
369
407
|
|
|
@@ -407,10 +445,35 @@ export class AgentDwnApi {
|
|
|
407
445
|
// processing, passing along the target DID, the message, and any associated data stream.
|
|
408
446
|
// - If `store` is set to false, it immediately returns a simulated 'accepted' status without
|
|
409
447
|
// storing the message/data in the DWN node.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
448
|
+
let reply: DwnMessageReply[T];
|
|
449
|
+
|
|
450
|
+
if (request.store === false) {
|
|
451
|
+
reply = { status: { code: 202, detail: 'Accepted' } };
|
|
452
|
+
} else if (this._dwn) {
|
|
453
|
+
// Local mode: process directly with the in-process DWN.
|
|
454
|
+
reply = await this._dwn.processMessage(
|
|
455
|
+
request.target, message,
|
|
456
|
+
{ dataStream: dataStream as any, subscriptionHandler },
|
|
457
|
+
);
|
|
458
|
+
} else {
|
|
459
|
+
// Remote mode: route through RPC to the local DWN server.
|
|
460
|
+
// TODO(#713): This buffers the entire stream into memory before
|
|
461
|
+
// re-streaming it over HTTP. The RPC transport should accept a
|
|
462
|
+
// ReadableStream directly to avoid the extra copy.
|
|
463
|
+
let data: Blob | undefined;
|
|
464
|
+
if (dataStream) {
|
|
465
|
+
const bytes = await DataStream.toBytes(dataStream);
|
|
466
|
+
data = new Blob([bytes as BlobPart]);
|
|
467
|
+
}
|
|
413
468
|
|
|
469
|
+
reply = await this.sendDwnRpcRequest({
|
|
470
|
+
targetDid : request.target,
|
|
471
|
+
dwnEndpointUrls : [this._localDwnEndpoint!],
|
|
472
|
+
message,
|
|
473
|
+
data,
|
|
474
|
+
subscriptionHandler,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
414
477
|
|
|
415
478
|
// Post-write key delivery: detect new participants and write contextKey records.
|
|
416
479
|
await this.postWriteKeyDelivery(request, message, reply);
|
|
@@ -427,6 +490,46 @@ export class AgentDwnApi {
|
|
|
427
490
|
};
|
|
428
491
|
}
|
|
429
492
|
|
|
493
|
+
/**
|
|
494
|
+
* Process a pre-constructed DWN message against the local DWN (in-process
|
|
495
|
+
* or remote server). Used by the sync engine to store messages that were
|
|
496
|
+
* already fetched from a remote DWN.
|
|
497
|
+
*
|
|
498
|
+
* Unlike {@link processRequest}, this method does NOT construct a new
|
|
499
|
+
* message — it takes an already-signed `GenericMessage` and routes it
|
|
500
|
+
* to the appropriate backend.
|
|
501
|
+
*
|
|
502
|
+
* @param tenant - The DID of the DWN tenant (target).
|
|
503
|
+
* @param message - The pre-constructed DWN message.
|
|
504
|
+
* @param options - Optional data stream and subscription handler.
|
|
505
|
+
* @returns The reply from processing the message.
|
|
506
|
+
*/
|
|
507
|
+
public async processRawMessage(
|
|
508
|
+
tenant: string,
|
|
509
|
+
message: GenericMessage,
|
|
510
|
+
options?: { dataStream?: ReadableStream<Uint8Array> },
|
|
511
|
+
): Promise<{ status: { code: number; detail: string } }> {
|
|
512
|
+
if (this._dwn) {
|
|
513
|
+
return this._dwn.processMessage(tenant, message, { dataStream: options?.dataStream });
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// TODO(#713): This buffers the entire stream into memory before
|
|
517
|
+
// re-streaming it over HTTP. The RPC transport should accept a
|
|
518
|
+
// ReadableStream directly to avoid the extra copy.
|
|
519
|
+
let data: Blob | undefined;
|
|
520
|
+
if (options?.dataStream) {
|
|
521
|
+
const bytes = await DataStream.toBytes(options.dataStream);
|
|
522
|
+
data = new Blob([bytes as BlobPart]);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return this.sendDwnRpcRequest({
|
|
526
|
+
targetDid : tenant,
|
|
527
|
+
dwnEndpointUrls : [this._localDwnEndpoint!],
|
|
528
|
+
message : message as DwnMessage[DwnInterface],
|
|
529
|
+
data,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
430
533
|
public async sendRequest<T extends DwnInterface>(
|
|
431
534
|
request: SendDwnRequest<T>
|
|
432
535
|
): Promise<DwnResponse<T>> {
|
|
@@ -547,17 +650,31 @@ export class AgentDwnApi {
|
|
|
547
650
|
const isMultiParty = isMultiPartyContextFn(protocolDefinition, rootPathSegment);
|
|
548
651
|
|
|
549
652
|
if (isExternallyAuthored && isRootRecord && isMultiParty) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
653
|
+
// TODO: Reactive root-record upgrade is disabled and needs redesign.
|
|
654
|
+
//
|
|
655
|
+
// The previous implementation (`upgradeExternalRootRecord` in
|
|
656
|
+
// `dwn-record-upgrade.ts`) bypassed DWN SDK conflict resolution by
|
|
657
|
+
// directly manipulating messageStore/stateIndex/eventLog internals.
|
|
658
|
+
// This is incompatible with remote DWN operation (local DWN server
|
|
659
|
+
// accessed via RPC) where the agent has no direct access to the
|
|
660
|
+
// server's storage layer.
|
|
661
|
+
//
|
|
662
|
+
// The correct approach is either:
|
|
663
|
+
// (a) Perform the upgrade BEFORE the initial processMessage() call
|
|
664
|
+
// (pre-store augmentation), so the message is stored in its
|
|
665
|
+
// final form on the first pass — no replacement needed.
|
|
666
|
+
// (b) Add DWN SDK support for same-timestamp owner-augmented
|
|
667
|
+
// replacements in the RecordsWrite handler.
|
|
668
|
+
//
|
|
669
|
+
// Bumping messageTimestamp is NOT viable because the author's
|
|
670
|
+
// signature payload contains descriptorCid (which includes the
|
|
671
|
+
// timestamp). The owner does not have the external author's signing
|
|
672
|
+
// key and cannot re-sign.
|
|
673
|
+
//
|
|
674
|
+
// Until this is redesigned, externally-authored root records in
|
|
675
|
+
// multi-party encrypted contexts will only have ProtocolPath
|
|
676
|
+
// encryption. Context key holders will not be able to decrypt
|
|
677
|
+
// these records via ProtocolContext.
|
|
561
678
|
}
|
|
562
679
|
|
|
563
680
|
const newParticipants = detectNewParticipantsFn({
|
|
@@ -1160,6 +1277,17 @@ export class AgentDwnApi {
|
|
|
1160
1277
|
tenantDid: string,
|
|
1161
1278
|
protocolUri: string,
|
|
1162
1279
|
): Promise<ProtocolDefinition | undefined> {
|
|
1280
|
+
if (!this._dwn) {
|
|
1281
|
+
// Remote mode: query via RPC (same as fetchRemoteProtocolDefinition,
|
|
1282
|
+
// but for locally-managed DIDs). The remote protocol definition
|
|
1283
|
+
// cache uses a different key prefix, so we use a dedicated call.
|
|
1284
|
+
try {
|
|
1285
|
+
return await this.fetchRemoteProtocolDefinition(tenantDid, protocolUri);
|
|
1286
|
+
} catch {
|
|
1287
|
+
// Protocol not found — return undefined (consistent with local mode).
|
|
1288
|
+
return undefined;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1163
1291
|
return getProtocolDefinitionFn(
|
|
1164
1292
|
tenantDid, protocolUri, this._dwn,
|
|
1165
1293
|
this.getSigner.bind(this), this._protocolDefinitionCache,
|
|
@@ -1233,7 +1361,19 @@ export class AgentDwnApi {
|
|
|
1233
1361
|
signer
|
|
1234
1362
|
});
|
|
1235
1363
|
|
|
1236
|
-
|
|
1364
|
+
let result: any;
|
|
1365
|
+
|
|
1366
|
+
if (this._dwn) {
|
|
1367
|
+
// Local mode: process directly with the in-process DWN.
|
|
1368
|
+
result = await this._dwn.processMessage(author, messagesRead.message);
|
|
1369
|
+
} else {
|
|
1370
|
+
// Remote mode: route through RPC to the local DWN server.
|
|
1371
|
+
result = await this.sendDwnRpcRequest({
|
|
1372
|
+
targetDid : author,
|
|
1373
|
+
dwnEndpointUrls : [this._localDwnEndpoint!],
|
|
1374
|
+
message : messagesRead.message,
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1237
1377
|
|
|
1238
1378
|
if (result.status.code !== 200) {
|
|
1239
1379
|
throw new Error(`AgentDwnApi: Failed to read message, response status: ${result.status.code} - ${result.status.detail}`);
|
|
@@ -1245,9 +1385,15 @@ export class AgentDwnApi {
|
|
|
1245
1385
|
const dwnMessageWithBlob: DwnMessageWithBlob<T> = { message };
|
|
1246
1386
|
// If the message is a RecordsWrite, data will be present in the form of a stream
|
|
1247
1387
|
|
|
1248
|
-
if (isRecordsWrite(messageEntry)
|
|
1249
|
-
|
|
1250
|
-
|
|
1388
|
+
if (isRecordsWrite(messageEntry)) {
|
|
1389
|
+
// The processMessage result includes a `data` ReadableStream for
|
|
1390
|
+
// RecordsWrite entries, but the RecordsWrite type doesn't declare
|
|
1391
|
+
// it. Access via index signature to avoid the type mismatch.
|
|
1392
|
+
const entryData = (messageEntry as unknown as Record<string, unknown>)['data'] as ReadableStream<Uint8Array> | undefined;
|
|
1393
|
+
if (entryData) {
|
|
1394
|
+
const dataBytes = await DataStream.toBytes(entryData);
|
|
1395
|
+
dwnMessageWithBlob.data = new Blob([ dataBytes as BlobPart ], { type: messageEntry.message.descriptor.dataFormat });
|
|
1396
|
+
}
|
|
1251
1397
|
}
|
|
1252
1398
|
|
|
1253
1399
|
return dwnMessageWithBlob;
|
|
@@ -139,7 +139,7 @@ export const DISCOVERY_FILENAME = 'dwn.json';
|
|
|
139
139
|
* Reads, writes, and validates the `~/.enbox/dwn.json` discovery file.
|
|
140
140
|
*
|
|
141
141
|
* This is the **file-based discovery channel** for CLI and native apps.
|
|
142
|
-
* It is complementary to the `dwn://
|
|
142
|
+
* It is complementary to the `dwn://connect` browser redirect flow.
|
|
143
143
|
*
|
|
144
144
|
* @example Reading the discovery file
|
|
145
145
|
* ```ts
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared types and utilities for the `dwn://
|
|
2
|
+
* Shared types and utilities for the `dwn://connect` discovery protocol.
|
|
3
3
|
*
|
|
4
4
|
* The payload is the JSON data exchanged between the local DWN server
|
|
5
|
-
* (electrobun-dwn) and the requesting app during the `dwn://
|
|
5
|
+
* (electrobun-dwn) and the requesting app during the `dwn://connect`
|
|
6
6
|
* redirect flow. It is encoded as base64url and placed in the URL
|
|
7
7
|
* fragment (`#`) of the callback URL.
|
|
8
8
|
*
|
|
@@ -10,14 +10,13 @@
|
|
|
10
10
|
* consumed from any environment (Bun, browser, Electrobun) without
|
|
11
11
|
* triggering transitive dependency resolution issues.
|
|
12
12
|
*
|
|
13
|
-
* @see https://github.com/enboxorg/enbox/issues/586
|
|
14
13
|
* @module
|
|
15
14
|
*/
|
|
16
15
|
|
|
17
16
|
// ─── Types ────────────────────────────────────────────────────────
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
|
-
* The JSON payload delivered via the URL fragment in a `dwn://
|
|
19
|
+
* The JSON payload delivered via the URL fragment in a `dwn://connect`
|
|
21
20
|
* callback redirect.
|
|
22
21
|
*
|
|
23
22
|
* Intentionally minimal — everything beyond the endpoint (version,
|
|
@@ -29,9 +28,9 @@ export type DwnDiscoveryPayload = {
|
|
|
29
28
|
};
|
|
30
29
|
|
|
31
30
|
/**
|
|
32
|
-
* Parsed result from a `dwn://
|
|
31
|
+
* Parsed result from a `dwn://connect` URL.
|
|
33
32
|
*/
|
|
34
|
-
export type
|
|
33
|
+
export type DwnConnectUrlParams = {
|
|
35
34
|
/** The callback URL to redirect to with the discovery payload. */
|
|
36
35
|
callback: string;
|
|
37
36
|
};
|
|
@@ -41,17 +40,17 @@ export type DwnRegisterUrlParams = {
|
|
|
41
40
|
/** The URL scheme for DWN discovery protocol handlers. */
|
|
42
41
|
export const DWN_PROTOCOL_SCHEME = 'dwn';
|
|
43
42
|
|
|
44
|
-
/** The `dwn://
|
|
45
|
-
export const
|
|
43
|
+
/** The `dwn://connect` path that triggers the discovery handshake. */
|
|
44
|
+
export const DWN_CONNECT_PATH = 'connect';
|
|
46
45
|
|
|
47
46
|
// ─── Register URL construction ───────────────────────────────────
|
|
48
47
|
|
|
49
48
|
/**
|
|
50
|
-
* Build a `dwn://
|
|
49
|
+
* Build a `dwn://connect?callback=<url>` URL that, when opened by the OS,
|
|
51
50
|
* triggers electrobun-dwn (or another `dwn://` scheme handler) to redirect
|
|
52
51
|
* back to `callbackUrl` with the local DWN endpoint in the URL fragment.
|
|
53
52
|
*
|
|
54
|
-
* This is the **trigger** side of the `dwn://
|
|
53
|
+
* This is the **trigger** side of the `dwn://connect` browser flow.
|
|
55
54
|
* The web app opens this URL (e.g. via `window.open()` or `location.href`),
|
|
56
55
|
* the OS routes it to the registered handler, and the handler redirects
|
|
57
56
|
* back with the discovery payload.
|
|
@@ -59,17 +58,17 @@ export const DWN_REGISTER_PATH = 'register';
|
|
|
59
58
|
* @param callbackUrl - The URL to redirect back to after discovery.
|
|
60
59
|
* This should be the current page (or a dedicated callback page) that
|
|
61
60
|
* will read the payload from `window.location.hash`.
|
|
62
|
-
* @returns The `dwn://
|
|
61
|
+
* @returns The `dwn://connect?callback=<encoded-url>` URL string.
|
|
63
62
|
*
|
|
64
63
|
* @example
|
|
65
64
|
* ```ts
|
|
66
|
-
* const registerUrl =
|
|
67
|
-
* // => 'dwn://
|
|
65
|
+
* const registerUrl = buildDwnConnectUrl('https://myapp.com/callback');
|
|
66
|
+
* // => 'dwn://connect?callback=https%3A%2F%2Fmyapp.com%2Fcallback'
|
|
68
67
|
* window.open(registerUrl);
|
|
69
68
|
* ```
|
|
70
69
|
*/
|
|
71
|
-
export function
|
|
72
|
-
return `${DWN_PROTOCOL_SCHEME}://${
|
|
70
|
+
export function buildDwnConnectUrl(callbackUrl: string): string {
|
|
71
|
+
return `${DWN_PROTOCOL_SCHEME}://${DWN_CONNECT_PATH}?callback=${encodeURIComponent(callbackUrl)}`;
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
// ─── Payload encoding/decoding ───────────────────────────────────
|
|
@@ -110,15 +109,15 @@ export function decodeDwnDiscoveryPayload(encoded: string): DwnDiscoveryPayload
|
|
|
110
109
|
// ─── URL parsing ─────────────────────────────────────────────────
|
|
111
110
|
|
|
112
111
|
/**
|
|
113
|
-
* Parse a `dwn://
|
|
112
|
+
* Parse a `dwn://connect?callback=<url>` URL into its components.
|
|
114
113
|
*
|
|
115
|
-
* @param url - The full `dwn://
|
|
114
|
+
* @param url - The full `dwn://connect?callback=...` URL.
|
|
116
115
|
* @returns The parsed parameters, or `undefined` if the URL is not a
|
|
117
|
-
* valid `dwn://
|
|
116
|
+
* valid `dwn://connect` URL or is missing the `callback` parameter.
|
|
118
117
|
*/
|
|
119
|
-
export function
|
|
118
|
+
export function parseDwnConnectUrl(url: string): DwnConnectUrlParams | undefined {
|
|
120
119
|
try {
|
|
121
|
-
// dwn://
|
|
120
|
+
// dwn://connect?callback=... is not a standard hierarchical URL, so
|
|
122
121
|
// we parse it manually to avoid URL constructor quirks with custom schemes.
|
|
123
122
|
const schemePrefix = `${DWN_PROTOCOL_SCHEME}://`;
|
|
124
123
|
if (!url.startsWith(schemePrefix)) {
|
|
@@ -134,7 +133,7 @@ export function parseDwnRegisterUrl(url: string): DwnRegisterUrlParams | undefin
|
|
|
134
133
|
}
|
|
135
134
|
|
|
136
135
|
const path = withoutScheme.slice(0, questionIndex);
|
|
137
|
-
if (path !==
|
|
136
|
+
if (path !== DWN_CONNECT_PATH) {
|
|
138
137
|
return undefined;
|
|
139
138
|
}
|
|
140
139
|
|
|
@@ -156,7 +155,7 @@ export function parseDwnRegisterUrl(url: string): DwnRegisterUrlParams | undefin
|
|
|
156
155
|
* Build the full callback redirect URL with the discovery payload
|
|
157
156
|
* encoded in the URL fragment.
|
|
158
157
|
*
|
|
159
|
-
* @param callbackUrl - The callback URL from the `dwn://
|
|
158
|
+
* @param callbackUrl - The callback URL from the `dwn://connect` request.
|
|
160
159
|
* @param payload - The discovery payload to encode in the fragment.
|
|
161
160
|
* @returns The full redirect URL (e.g. `https://notes.sh/dwn#eyJ...`).
|
|
162
161
|
*/
|
|
@@ -202,7 +201,7 @@ export function readDwnDiscoveryPayloadFromUrl(url: string): DwnDiscoveryPayload
|
|
|
202
201
|
* Type guard for a valid {@link DwnDiscoveryPayload}.
|
|
203
202
|
*
|
|
204
203
|
* The endpoint MUST point to a loopback address (`127.0.0.1`, `[::1]`,
|
|
205
|
-
* or `localhost`) because the `dwn://
|
|
204
|
+
* or `localhost`) because the `dwn://connect` payload is only intended
|
|
206
205
|
* for local DWN discovery. Accepting arbitrary hostnames would allow a
|
|
207
206
|
* malicious payload to redirect agent traffic to a remote server.
|
|
208
207
|
*
|
|
@@ -231,7 +230,7 @@ function isValidPayload(value: unknown): value is DwnDiscoveryPayload {
|
|
|
231
230
|
* address. Accepts `127.0.0.1`, `::1` (with or without brackets), and
|
|
232
231
|
* `localhost` (bare or with any subdomain suffix, per RFC 6761 §6.3).
|
|
233
232
|
*
|
|
234
|
-
* This is a security boundary: the `dwn://
|
|
233
|
+
* This is a security boundary: the `dwn://connect` redirect flow MUST
|
|
235
234
|
* NOT allow payloads that point to non-local servers.
|
|
236
235
|
*/
|
|
237
236
|
function isLoopbackEndpoint(endpoint: string): boolean {
|
package/src/dwn-key-delivery.ts
CHANGED
|
@@ -174,7 +174,7 @@ export async function writeContextKeyRecord(
|
|
|
174
174
|
target : tenantDid,
|
|
175
175
|
messageType : DwnInterface.RecordsWrite,
|
|
176
176
|
messageParams : { ...contextKeyParams, dataCid, dataSize, encryptionInput },
|
|
177
|
-
dataStream : new Blob([encryptedBytes]),
|
|
177
|
+
dataStream : new Blob([encryptedBytes as BlobPart]),
|
|
178
178
|
}));
|
|
179
179
|
} else {
|
|
180
180
|
// --- Fallback: encrypt to the owner's key (local self-delivery) ---
|