@enbox/auth 0.6.0 → 0.6.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 (44) hide show
  1. package/dist/esm/auth-manager.js +147 -5
  2. package/dist/esm/auth-manager.js.map +1 -1
  3. package/dist/esm/connect/lifecycle.js +145 -2
  4. package/dist/esm/connect/lifecycle.js.map +1 -1
  5. package/dist/esm/connect/local.js +19 -5
  6. package/dist/esm/connect/local.js.map +1 -1
  7. package/dist/esm/connect/restore.js +22 -8
  8. package/dist/esm/connect/restore.js.map +1 -1
  9. package/dist/esm/connect/wallet.js +24 -137
  10. package/dist/esm/connect/wallet.js.map +1 -1
  11. package/dist/esm/index.js +9 -15
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/esm/permissions.js +41 -0
  14. package/dist/esm/permissions.js.map +1 -0
  15. package/dist/esm/types.js +2 -0
  16. package/dist/esm/types.js.map +1 -1
  17. package/dist/esm/wallet-connect-client.js.map +1 -1
  18. package/dist/types/auth-manager.d.ts +70 -6
  19. package/dist/types/auth-manager.d.ts.map +1 -1
  20. package/dist/types/connect/lifecycle.d.ts +49 -2
  21. package/dist/types/connect/lifecycle.d.ts.map +1 -1
  22. package/dist/types/connect/local.d.ts +6 -1
  23. package/dist/types/connect/local.d.ts.map +1 -1
  24. package/dist/types/connect/restore.d.ts.map +1 -1
  25. package/dist/types/connect/wallet.d.ts +1 -15
  26. package/dist/types/connect/wallet.d.ts.map +1 -1
  27. package/dist/types/index.d.ts +10 -16
  28. package/dist/types/index.d.ts.map +1 -1
  29. package/dist/types/permissions.d.ts +18 -0
  30. package/dist/types/permissions.d.ts.map +1 -0
  31. package/dist/types/types.d.ts +148 -1
  32. package/dist/types/types.d.ts.map +1 -1
  33. package/dist/types/wallet-connect-client.d.ts +1 -4
  34. package/dist/types/wallet-connect-client.d.ts.map +1 -1
  35. package/package.json +3 -3
  36. package/src/auth-manager.ts +167 -6
  37. package/src/connect/lifecycle.ts +170 -4
  38. package/src/connect/local.ts +20 -5
  39. package/src/connect/restore.ts +25 -9
  40. package/src/connect/wallet.ts +26 -146
  41. package/src/index.ts +16 -16
  42. package/src/permissions.ts +48 -0
  43. package/src/types.ts +164 -1
  44. package/src/wallet-connect-client.ts +1 -4
@@ -14,12 +14,16 @@
14
14
  * @internal
15
15
  */
16
16
 
17
- import type { BearerIdentity, EnboxUserAgent } from '@enbox/agent';
17
+ import type { PortableDid } from '@enbox/dids';
18
+ import type { BearerIdentity, DwnDataEncodedRecordsWriteMessage, DwnMessagesPermissionScope, DwnRecordsPermissionScope, EnboxUserAgent } from '@enbox/agent';
18
19
 
19
20
  import type { AuthEventEmitter } from '../events.js';
20
21
  import type { PasswordProvider } from '../password-provider.js';
21
22
  import type { IdentityInfo, RegistrationOptions, StorageAdapter, SyncOption } from '../types.js';
22
23
 
24
+ import { Convert } from '@enbox/common';
25
+ import { DwnInterface, DwnPermissionGrant } from '@enbox/agent';
26
+
23
27
  import { AuthSession } from '../identity-session.js';
24
28
  import { DEFAULT_DWN_ENDPOINTS, INSECURE_DEFAULT_PASSWORD, STORAGE_KEYS } from '../types.js';
25
29
 
@@ -239,6 +243,166 @@ export function resolveIdentityDids(
239
243
  return { connectedDid, delegateDid };
240
244
  }
241
245
 
246
+ // ─── processConnectedGrants ─────────────────────────────────────
247
+
248
+ /**
249
+ * Process connected grants by storing them in the local DWN as the owner.
250
+ *
251
+ * This is the agent-level equivalent of `Enbox.processConnectedGrants()`.
252
+ * It stores each grant, signed as owner, and returns the deduplicated
253
+ * list of protocol URIs represented by the grants.
254
+ *
255
+ * @internal
256
+ */
257
+ export async function processConnectedGrants(params: {
258
+ agent: EnboxUserAgent;
259
+ delegateDid: string;
260
+ grants: DwnDataEncodedRecordsWriteMessage[];
261
+ }): Promise<string[]> {
262
+ const { agent, delegateDid, grants } = params;
263
+ const connectedProtocols = new Set<string>();
264
+
265
+ for (const grantMessage of grants) {
266
+ const grant = DwnPermissionGrant.parse(grantMessage);
267
+
268
+ // Store the grant as the owner of the DWN so the delegateDid
269
+ // can use it when impersonating the connectedDid.
270
+ const { encodedData, ...rawMessage } = grantMessage;
271
+ const dataStream = new Blob([Convert.base64Url(encodedData).toUint8Array() as BlobPart]);
272
+
273
+ const { reply } = await agent.processDwnRequest({
274
+ store : true,
275
+ author : delegateDid,
276
+ target : delegateDid,
277
+ messageType : DwnInterface.RecordsWrite,
278
+ signAsOwner : true,
279
+ rawMessage,
280
+ dataStream,
281
+ });
282
+
283
+ if (reply.status.code !== 202) {
284
+ throw new Error(
285
+ `[@enbox/auth] Failed to process connected grant: ${reply.status.detail}`
286
+ );
287
+ }
288
+
289
+ const protocol = (grant.scope as DwnMessagesPermissionScope | DwnRecordsPermissionScope).protocol;
290
+ if (protocol) {
291
+ connectedProtocols.add(protocol);
292
+ }
293
+ }
294
+
295
+ return [...connectedProtocols];
296
+ }
297
+
298
+ // ─── importDelegateAndSetupSync ─────────────────────────────────
299
+
300
+ /**
301
+ * Import a delegated DID, process its grants, register sync, and pull.
302
+ *
303
+ * This is the shared post-connect lifecycle used by both the DWeb Connect
304
+ * and relay WalletConnect flows. On failure, the imported identity is
305
+ * cleaned up before re-throwing.
306
+ *
307
+ * @internal
308
+ */
309
+ export async function importDelegateAndSetupSync(params: {
310
+ userAgent: EnboxUserAgent;
311
+ delegatePortableDid: PortableDid;
312
+ connectedDid: string;
313
+ delegateGrants: DwnDataEncodedRecordsWriteMessage[];
314
+ flowName: string;
315
+ }): Promise<BearerIdentity> {
316
+ const { userAgent, delegatePortableDid, connectedDid, delegateGrants, flowName } = params;
317
+
318
+ let identity: BearerIdentity | undefined;
319
+ try {
320
+ identity = await userAgent.identity.import({
321
+ portableIdentity: {
322
+ portableDid : delegatePortableDid,
323
+ metadata : {
324
+ connectedDid,
325
+ name : 'Default',
326
+ uri : delegatePortableDid.uri,
327
+ tenant : userAgent.agentDid.uri,
328
+ },
329
+ },
330
+ });
331
+
332
+ const connectedProtocols = await processConnectedGrants({
333
+ agent : userAgent,
334
+ delegateDid : delegatePortableDid.uri,
335
+ grants : delegateGrants,
336
+ });
337
+
338
+ await userAgent.sync.registerIdentity({
339
+ did : connectedDid,
340
+ options : {
341
+ delegateDid : delegatePortableDid.uri,
342
+ protocols : connectedProtocols,
343
+ },
344
+ });
345
+
346
+ await userAgent.sync.sync('pull');
347
+
348
+ return identity;
349
+ } catch (error: unknown) {
350
+ if (identity) {
351
+ try {
352
+ await userAgent.did.delete({
353
+ didUri : identity.did.uri,
354
+ tenant : identity.metadata.tenant,
355
+ deleteKey : true,
356
+ });
357
+ } catch { /* best effort */ }
358
+
359
+ try {
360
+ await userAgent.identity.delete({ didUri: identity.did.uri });
361
+ } catch { /* best effort */ }
362
+ }
363
+
364
+ const message = error instanceof Error ? error.message : String(error);
365
+ throw new Error(`[@enbox/auth] ${flowName} failed: ${message}`);
366
+ }
367
+ }
368
+
369
+ // ─── finalizeDelegateSession ────────────────────────────────────
370
+
371
+ /**
372
+ * Build an `AuthSession` for a delegated connect flow (DWeb Connect or
373
+ * relay WalletConnect). Starts sync and persists delegate/connected DID
374
+ * markers.
375
+ *
376
+ * @internal
377
+ */
378
+ export async function finalizeDelegateSession(params: {
379
+ userAgent: EnboxUserAgent;
380
+ emitter: AuthEventEmitter;
381
+ storage: StorageAdapter;
382
+ identity: BearerIdentity;
383
+ connectedDid: string;
384
+ delegateDid: string;
385
+ sync: SyncOption | undefined;
386
+ }): Promise<AuthSession> {
387
+ const { userAgent, emitter, storage, identity, connectedDid, delegateDid, sync } = params;
388
+
389
+ startSyncIfEnabled(userAgent, sync);
390
+
391
+ return finalizeSession({
392
+ userAgent,
393
+ emitter,
394
+ storage,
395
+ connectedDid,
396
+ delegateDid,
397
+ identityName : identity.metadata.name,
398
+ identityConnectedDid : identity.metadata.connectedDid,
399
+ extraStorageKeys : {
400
+ [STORAGE_KEYS.DELEGATE_DID] : delegateDid,
401
+ [STORAGE_KEYS.CONNECTED_DID] : connectedDid,
402
+ },
403
+ });
404
+ }
405
+
242
406
  // ─── finalizeSession ────────────────────────────────────────────
243
407
 
244
408
  /**
@@ -267,7 +431,7 @@ export async function finalizeSession(params: {
267
431
  connectedDid: string;
268
432
  delegateDid?: string;
269
433
  recoveryPhrase?: string;
270
- identityName: string;
434
+ identityName?: string;
271
435
  identityConnectedDid?: string;
272
436
  emitIdentityAdded?: boolean;
273
437
  extraStorageKeys?: Record<string, string>;
@@ -295,9 +459,11 @@ export async function finalizeSession(params: {
295
459
  }
296
460
  }
297
461
 
462
+ // When identityName is undefined, no user identity exists (agent-only session).
463
+ // Build an IdentityInfo with the agent DID as a fallback.
298
464
  const identityInfo: IdentityInfo = {
299
465
  didUri : connectedDid,
300
- name : identityName,
466
+ name : identityName ?? 'Agent',
301
467
  connectedDid : identityConnectedDid,
302
468
  };
303
469
 
@@ -309,7 +475,7 @@ export async function finalizeSession(params: {
309
475
  identity : identityInfo,
310
476
  });
311
477
 
312
- if (emitIdentityAdded) {
478
+ if (emitIdentityAdded && identityName !== undefined) {
313
479
  emitter.emit('identity-added', { identity: identityInfo });
314
480
  }
315
481
 
@@ -18,8 +18,13 @@ import { createDefaultIdentity, ensureVaultReady, finalizeSession, resolveIdenti
18
18
  /**
19
19
  * Execute the local connect flow.
20
20
  *
21
- * - On first launch: initializes the vault, creates a new DID, returns recovery phrase.
21
+ * - On first launch: initializes the vault. Identity creation is opt-in via
22
+ * `options.createIdentity: true`.
22
23
  * - On subsequent launches: unlocks the vault and reconnects to the existing identity.
24
+ *
25
+ * When no identities exist and `createIdentity` is not `true`, the session
26
+ * is returned with the **agent DID** as the connected DID. This allows apps to
27
+ * manage identity creation separately from vault setup.
23
28
  */
24
29
  export async function localConnect(
25
30
  ctx: FlowContext,
@@ -33,6 +38,7 @@ export async function localConnect(
33
38
 
34
39
  const sync = options.sync ?? ctx.defaultSync;
35
40
  const dwnEndpoints = options.dwnEndpoints ?? ctx.defaultDwnEndpoints ?? DEFAULT_DWN_ENDPOINTS;
41
+ const shouldCreateIdentity = options.createIdentity === true;
36
42
 
37
43
  // Initialize vault on first launch and start the agent.
38
44
  const recoveryPhrase = await ensureVaultReady({
@@ -55,12 +61,21 @@ export async function localConnect(
55
61
  let identity = identities[0];
56
62
  let isNewIdentity = false;
57
63
 
58
- if (!identity) {
64
+ if (!identity && shouldCreateIdentity) {
59
65
  isNewIdentity = true;
60
66
  identity = await createDefaultIdentity(userAgent, dwnEndpoints, options.metadata?.name ?? 'Default');
61
67
  }
62
68
 
63
- const { connectedDid, delegateDid } = resolveIdentityDids(identity);
69
+ // When no identity exists (createIdentity: false on first launch), use the
70
+ // agent DID as the session's connected DID. The session is still valid but
71
+ // operates in the agent's context rather than a user identity's context.
72
+ const connectedDid = identity
73
+ ? resolveIdentityDids(identity).connectedDid
74
+ : userAgent.agentDid.uri;
75
+
76
+ const delegateDid = identity
77
+ ? resolveIdentityDids(identity).delegateDid
78
+ : undefined;
64
79
 
65
80
  // Register with DWN endpoints (if registration options are provided).
66
81
  if (ctx.registration) {
@@ -95,7 +110,7 @@ export async function localConnect(
95
110
  connectedDid,
96
111
  delegateDid,
97
112
  recoveryPhrase,
98
- identityName : identity.metadata.name,
99
- identityConnectedDid : identity.metadata.connectedDid,
113
+ identityName : identity?.metadata.name,
114
+ identityConnectedDid : identity?.metadata.connectedDid,
100
115
  });
101
116
  }
@@ -86,22 +86,38 @@ export async function restoreSession(
86
86
  }
87
87
  }
88
88
 
89
+ // Start sync.
90
+ startSyncIfEnabled(userAgent, ctx.defaultSync);
91
+
89
92
  if (!identity) {
90
- // No identity found — clean up stale session data.
91
- await storage.remove(STORAGE_KEYS.PREVIOUSLY_CONNECTED);
92
- await storage.remove(STORAGE_KEYS.ACTIVE_IDENTITY);
93
- await storage.remove(STORAGE_KEYS.DELEGATE_DID);
94
- await storage.remove(STORAGE_KEYS.CONNECTED_DID);
95
- return undefined;
93
+ // No identity found — this is valid for agent-only sessions created
94
+ // with `createIdentity: false`. Restore a session using the agent DID.
95
+ // If the active identity stored was the agent DID, this is an
96
+ // intentional agent-only session rather than stale data.
97
+ const isAgentOnlySession = activeIdentityDid === userAgent.agentDid.uri;
98
+
99
+ if (!isAgentOnlySession) {
100
+ // Truly stale session data — clean up and bail.
101
+ await storage.remove(STORAGE_KEYS.PREVIOUSLY_CONNECTED);
102
+ await storage.remove(STORAGE_KEYS.ACTIVE_IDENTITY);
103
+ await storage.remove(STORAGE_KEYS.DELEGATE_DID);
104
+ await storage.remove(STORAGE_KEYS.CONNECTED_DID);
105
+ return undefined;
106
+ }
107
+
108
+ return finalizeSession({
109
+ userAgent,
110
+ emitter,
111
+ storage,
112
+ connectedDid : userAgent.agentDid.uri,
113
+ emitIdentityAdded : false,
114
+ });
96
115
  }
97
116
 
98
117
  const { connectedDid, delegateDid } = resolveIdentityDids(
99
118
  identity, storedDelegateDid ?? undefined,
100
119
  );
101
120
 
102
- // Start sync.
103
- startSyncIfEnabled(userAgent, ctx.defaultSync);
104
-
105
121
  // Persist session info, build AuthSession, and emit lifecycle events.
106
122
  // Session restore does not emit `identity-added` (identity was already added in the original flow).
107
123
  return finalizeSession({
@@ -7,68 +7,17 @@
7
7
  * @module
8
8
  */
9
9
 
10
- import type { DwnDataEncodedRecordsWriteMessage, DwnMessagesPermissionScope, DwnRecordsPermissionScope, EnboxUserAgent } from '@enbox/agent';
11
-
12
10
  import type { AuthSession } from '../identity-session.js';
13
11
  import type { FlowContext } from './lifecycle.js';
14
12
  import type { WalletConnectOptions } from '../types.js';
15
13
 
16
- import { Convert } from '@enbox/common';
14
+ import { DEFAULT_DWN_ENDPOINTS } from '../types.js';
17
15
  import { registerWithDwnEndpoints } from '../registration.js';
18
16
  import { WalletConnect } from '../wallet-connect-client.js';
19
- import { DEFAULT_DWN_ENDPOINTS, STORAGE_KEYS } from '../types.js';
20
- import { DwnInterface, DwnPermissionGrant } from '@enbox/agent';
21
- import { ensureVaultReady, finalizeSession, resolvePassword, startSyncIfEnabled } from './lifecycle.js';
22
-
23
- /**
24
- * Process connected grants by storing them in the local DWN as the owner.
25
- *
26
- * This is the agent-level equivalent of `Enbox.processConnectedGrants()`.
27
- * It stores each grant, signed as owner, and returns the deduplicated
28
- * list of protocol URIs represented by the grants.
29
- *
30
- * @internal
31
- */
32
- export async function processConnectedGrants(params: {
33
- agent: EnboxUserAgent;
34
- delegateDid: string;
35
- grants: DwnDataEncodedRecordsWriteMessage[];
36
- }): Promise<string[]> {
37
- const { agent, delegateDid, grants } = params;
38
- const connectedProtocols = new Set<string>();
39
-
40
- for (const grantMessage of grants) {
41
- const grant = DwnPermissionGrant.parse(grantMessage);
17
+ import { ensureVaultReady, finalizeDelegateSession, importDelegateAndSetupSync, resolvePassword } from './lifecycle.js';
42
18
 
43
- // Store the grant as the owner of the DWN so the delegateDid
44
- // can use it when impersonating the connectedDid.
45
- const { encodedData, ...rawMessage } = grantMessage;
46
- const dataStream = new Blob([Convert.base64Url(encodedData).toUint8Array() as BlobPart]);
47
-
48
- const { reply } = await agent.processDwnRequest({
49
- store : true,
50
- author : delegateDid,
51
- target : delegateDid,
52
- messageType : DwnInterface.RecordsWrite,
53
- signAsOwner : true,
54
- rawMessage,
55
- dataStream,
56
- });
57
-
58
- if (reply.status.code !== 202) {
59
- throw new Error(
60
- `[@enbox/auth] Failed to process connected grant: ${reply.status.detail}`
61
- );
62
- }
63
-
64
- const protocol = (grant.scope as DwnMessagesPermissionScope | DwnRecordsPermissionScope).protocol;
65
- if (protocol) {
66
- connectedProtocols.add(protocol);
67
- }
68
- }
69
-
70
- return [...connectedProtocols];
71
- }
19
+ // Re-export for backward compatibility processConnectedGrants moved to lifecycle.ts.
20
+ export { processConnectedGrants } from './lifecycle.js';
72
21
 
73
22
  /**
74
23
  * Execute the wallet connect flow.
@@ -94,16 +43,9 @@ export async function walletConnect(
94
43
  // Ensure the agent is initialized and started before the relay flow.
95
44
  const isFirstLaunch = await userAgent.firstLaunch();
96
45
  const password = await resolvePassword(ctx, undefined, isFirstLaunch);
97
-
98
- await ensureVaultReady({
99
- userAgent,
100
- emitter,
101
- password,
102
- isFirstLaunch,
103
- });
46
+ await ensureVaultReady({ userAgent, emitter, password, isFirstLaunch });
104
47
 
105
48
  // Run the Enbox Connect relay flow.
106
- // permissionRequests are already agent-level ConnectPermissionRequest objects.
107
49
  const result = await WalletConnect.initClient({
108
50
  displayName : options.displayName,
109
51
  connectServerUrl : options.connectServerUrl,
@@ -117,93 +59,31 @@ export async function walletConnect(
117
59
  throw new Error('[@enbox/auth] Wallet connect flow was cancelled or returned no result.');
118
60
  }
119
61
 
62
+ // Import delegate DID, process grants, and set up sync.
120
63
  const { delegatePortableDid, connectedDid, delegateGrants } = result;
64
+ const identity = await importDelegateAndSetupSync({
65
+ userAgent, delegatePortableDid, connectedDid, delegateGrants,
66
+ flowName: 'Wallet connect',
67
+ });
121
68
 
122
- // Import the delegated DID as an Identity.
123
- let identity;
124
- try {
125
- identity = await userAgent.identity.import({
126
- portableIdentity: {
127
- portableDid : delegatePortableDid,
128
- metadata : {
129
- connectedDid,
130
- name : 'Default',
131
- uri : delegatePortableDid.uri,
132
- tenant : userAgent.agentDid.uri,
133
- },
134
- },
135
- });
136
-
137
- // Process the connected grants using agent primitives.
138
- const connectedProtocols = await processConnectedGrants({
139
- agent : userAgent,
140
- delegateDid : delegatePortableDid.uri,
141
- grants : delegateGrants,
142
- });
143
-
144
- // Register with DWN endpoints (if registration options are provided).
145
- if (ctx.registration) {
146
- const dwnEndpoints = ctx.defaultDwnEndpoints ?? DEFAULT_DWN_ENDPOINTS;
147
- await registerWithDwnEndpoints(
148
- {
149
- userAgent : userAgent,
150
- dwnEndpoints,
151
- agentDid : userAgent.agentDid.uri,
152
- connectedDid,
153
- storage : storage,
154
- },
155
- ctx.registration,
156
- );
157
- }
158
-
159
- // Register sync for the connected identity.
160
- await userAgent.sync.registerIdentity({
161
- did : connectedDid,
162
- options : {
163
- delegateDid : delegatePortableDid.uri,
164
- protocols : connectedProtocols,
69
+ // Register with DWN endpoints (if registration options are provided).
70
+ if (ctx.registration) {
71
+ const dwnEndpoints = ctx.defaultDwnEndpoints ?? DEFAULT_DWN_ENDPOINTS;
72
+ await registerWithDwnEndpoints(
73
+ {
74
+ userAgent,
75
+ dwnEndpoints,
76
+ agentDid: userAgent.agentDid.uri,
77
+ connectedDid,
78
+ storage,
165
79
  },
166
- });
167
-
168
- // Pull down existing messages from the connected DID's DWN.
169
- await userAgent.sync.sync('pull');
170
- } catch (error: unknown) {
171
- // Clean up on failure.
172
- if (identity) {
173
- try {
174
- await userAgent.did.delete({
175
- didUri : identity.did.uri,
176
- tenant : identity.metadata.tenant,
177
- deleteKey : true,
178
- });
179
- } catch { /* best effort */ }
180
-
181
- try {
182
- await userAgent.identity.delete({ didUri: identity.did.uri });
183
- } catch { /* best effort */ }
184
- }
185
-
186
- const message = error instanceof Error ? error.message : String(error);
187
- throw new Error(`[@enbox/auth] Wallet connect failed: ${message}`);
80
+ ctx.registration,
81
+ );
188
82
  }
189
83
 
190
- const delegateDid = delegatePortableDid.uri;
191
-
192
- // Start sync.
193
- startSyncIfEnabled(userAgent, sync);
194
-
195
- // Persist session info, build AuthSession, and emit lifecycle events.
196
- return finalizeSession({
197
- userAgent,
198
- emitter,
199
- storage,
200
- connectedDid,
201
- delegateDid,
202
- identityName : identity.metadata.name,
203
- identityConnectedDid : identity.metadata.connectedDid,
204
- extraStorageKeys : {
205
- [STORAGE_KEYS.DELEGATE_DID] : delegateDid,
206
- [STORAGE_KEYS.CONNECTED_DID] : connectedDid,
207
- },
84
+ // Finalize session.
85
+ return finalizeDelegateSession({
86
+ userAgent, emitter, storage, identity,
87
+ connectedDid, delegateDid: delegatePortableDid.uri, sync,
208
88
  });
209
89
  }
package/src/index.ts CHANGED
@@ -5,30 +5,23 @@
5
5
  * in both browser and CLI environments. Depends only on `@enbox/agent`
6
6
  * and can be used standalone or consumed by `@enbox/api`.
7
7
  *
8
- * @example Standalone auth
8
+ * @example Standalone auth (wallet app)
9
9
  * ```ts
10
10
  * import { AuthManager } from '@enbox/auth';
11
11
  *
12
12
  * const auth = await AuthManager.create({ sync: '15s' });
13
- * const session = await auth.restoreSession() ?? await auth.connect();
14
- *
15
- * // session.agent — the authenticated Enbox agent
16
- * // session.did — the connected DID URI
13
+ * const session = await auth.connectLocal({ password: userPin });
17
14
  * ```
18
15
  *
19
- * @example With @enbox/api
16
+ * @example Dapp with browser connect handler
20
17
  * ```ts
21
18
  * import { AuthManager } from '@enbox/auth';
22
- * import { Enbox } from '@enbox/api';
23
- *
24
- * const auth = await AuthManager.create({ sync: '15s' });
25
- * const session = await auth.connect();
19
+ * import { BrowserConnectHandler } from '@enbox/browser';
26
20
  *
27
- * const enbox = Enbox.connect({
28
- * agent: session.agent,
29
- * connectedDid: session.did,
30
- * delegateDid: session.delegateDid,
21
+ * const auth = await AuthManager.create({
22
+ * connectHandler: BrowserConnectHandler(),
31
23
  * });
24
+ * const session = await auth.connect({ protocols: [NotesProtocol] });
32
25
  * ```
33
26
  *
34
27
  * @packageDocumentation
@@ -47,10 +40,11 @@ export type { PasswordContext } from './password-provider.js';
47
40
  // without a direct @enbox/agent dependency.
48
41
  export { EnboxUserAgent, HdIdentityVault } from '@enbox/agent';
49
42
 
50
- // Wallet-connect helpers
43
+ // Connect helpers
51
44
  export { processConnectedGrants } from './connect/wallet.js';
45
+ export { normalizeProtocolRequests } from './permissions.js';
52
46
  export { WalletConnect } from './wallet-connect-client.js';
53
- export type { Permission, ProtocolPermissionOptions, WalletConnectClientOptions } from './wallet-connect-client.js';
47
+ export type { ProtocolPermissionOptions, WalletConnectClientOptions } from './wallet-connect-client.js';
54
48
 
55
49
  // Registration token storage helpers
56
50
  export { loadTokensFromStorage, saveTokensToStorage } from './registration.js';
@@ -77,8 +71,12 @@ export type {
77
71
  AuthManagerOptions,
78
72
  AuthSessionInfo,
79
73
  AuthState,
74
+ ConnectHandler,
75
+ ConnectOptions,
80
76
  ConnectPermissionRequest,
77
+ ConnectResult,
81
78
  DisconnectOptions,
79
+ HandlerConnectOptions,
82
80
  HeadlessConnectOptions,
83
81
  IdentityInfo,
84
82
  IdentityVaultBackup,
@@ -86,7 +84,9 @@ export type {
86
84
  ImportFromPortableOptions,
87
85
  LocalConnectOptions,
88
86
  LocalDwnStrategy,
87
+ Permission,
89
88
  PortableIdentity,
89
+ ProtocolRequest,
90
90
  ProviderAuthParams,
91
91
  ProviderAuthResult,
92
92
  RegistrationOptions,
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Permission request normalization utilities.
3
+ *
4
+ * Converts simplified `ProtocolRequest` entries (just a protocol definition
5
+ * or `{ definition, permissions }`) into agent-level `ConnectPermissionRequest`
6
+ * objects used by connect handlers.
7
+ *
8
+ * @module
9
+ * @internal
10
+ */
11
+
12
+ import type { ConnectPermissionRequest, DwnProtocolDefinition } from '@enbox/agent';
13
+
14
+ import type { ProtocolRequest } from './types.js';
15
+
16
+ import { DEFAULT_PERMISSIONS } from './types.js';
17
+ import { WalletConnect } from './wallet-connect-client.js';
18
+
19
+ /**
20
+ * Normalize simplified `ProtocolRequest[]` into agent-level
21
+ * `ConnectPermissionRequest[]`.
22
+ */
23
+ export function normalizeProtocolRequests(
24
+ protocols: ProtocolRequest[] | undefined,
25
+ ): ConnectPermissionRequest[] {
26
+ if (!protocols || protocols.length === 0) { return []; }
27
+
28
+ return protocols.map((entry) => {
29
+ let definition: DwnProtocolDefinition;
30
+ let permissions: string[];
31
+
32
+ if ('protocol' in entry && 'types' in entry && 'structure' in entry) {
33
+ // Bare protocol definition — use default permissions.
34
+ definition = entry as DwnProtocolDefinition;
35
+ permissions = [...DEFAULT_PERMISSIONS];
36
+ } else {
37
+ // Object with explicit permissions.
38
+ const explicit = entry as { definition: DwnProtocolDefinition; permissions: string[] };
39
+ definition = explicit.definition;
40
+ permissions = explicit.permissions;
41
+ }
42
+
43
+ return WalletConnect.createPermissionRequestForProtocol({
44
+ definition,
45
+ permissions: permissions as Parameters<typeof WalletConnect.createPermissionRequestForProtocol>[0]['permissions'],
46
+ });
47
+ });
48
+ }