@enbox/auth 0.5.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 (90) hide show
  1. package/dist/esm/auth-manager.js +240 -171
  2. package/dist/esm/auth-manager.js.map +1 -1
  3. package/dist/esm/connect/import.js +131 -0
  4. package/dist/esm/connect/import.js.map +1 -0
  5. package/dist/esm/connect/lifecycle.js +378 -0
  6. package/dist/esm/connect/lifecycle.js.map +1 -0
  7. package/dist/esm/connect/local.js +105 -0
  8. package/dist/esm/connect/local.js.map +1 -0
  9. package/dist/esm/connect/restore.js +117 -0
  10. package/dist/esm/connect/restore.js.map +1 -0
  11. package/dist/esm/connect/wallet.js +80 -0
  12. package/dist/esm/connect/wallet.js.map +1 -0
  13. package/dist/esm/{flows/dwn-discovery.js → discovery.js} +2 -2
  14. package/dist/esm/discovery.js.map +1 -0
  15. package/dist/esm/index.js +13 -19
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/permissions.js +41 -0
  18. package/dist/esm/permissions.js.map +1 -0
  19. package/dist/esm/{flows/dwn-registration.js → registration.js} +2 -2
  20. package/dist/esm/registration.js.map +1 -0
  21. package/dist/esm/types.js +4 -0
  22. package/dist/esm/types.js.map +1 -1
  23. package/dist/esm/wallet-connect-client.js +188 -0
  24. package/dist/esm/wallet-connect-client.js.map +1 -0
  25. package/dist/types/auth-manager.d.ts +89 -11
  26. package/dist/types/auth-manager.d.ts.map +1 -1
  27. package/dist/types/connect/import.d.ts +25 -0
  28. package/dist/types/connect/import.d.ts.map +1 -0
  29. package/dist/types/connect/lifecycle.d.ts +199 -0
  30. package/dist/types/connect/lifecycle.d.ts.map +1 -0
  31. package/dist/types/connect/local.d.ts +23 -0
  32. package/dist/types/connect/local.d.ts.map +1 -0
  33. package/dist/types/connect/restore.d.ts +18 -0
  34. package/dist/types/connect/restore.d.ts.map +1 -0
  35. package/dist/types/connect/wallet.d.ts +21 -0
  36. package/dist/types/connect/wallet.d.ts.map +1 -0
  37. package/dist/types/{flows/dwn-discovery.d.ts → discovery.d.ts} +3 -3
  38. package/dist/types/discovery.d.ts.map +1 -0
  39. package/dist/types/index.d.ts +14 -19
  40. package/dist/types/index.d.ts.map +1 -1
  41. package/dist/types/permissions.d.ts +18 -0
  42. package/dist/types/permissions.d.ts.map +1 -0
  43. package/dist/types/{flows/dwn-registration.d.ts → registration.d.ts} +2 -2
  44. package/dist/types/registration.d.ts.map +1 -0
  45. package/dist/types/types.d.ts +154 -4
  46. package/dist/types/types.d.ts.map +1 -1
  47. package/dist/types/wallet-connect-client.d.ts +86 -0
  48. package/dist/types/wallet-connect-client.d.ts.map +1 -0
  49. package/package.json +9 -5
  50. package/src/auth-manager.ts +258 -191
  51. package/src/connect/import.ts +148 -0
  52. package/src/connect/lifecycle.ts +487 -0
  53. package/src/connect/local.ts +116 -0
  54. package/src/connect/restore.ts +133 -0
  55. package/src/connect/wallet.ts +89 -0
  56. package/src/{flows/dwn-discovery.ts → discovery.ts} +4 -3
  57. package/src/index.ts +20 -19
  58. package/src/permissions.ts +48 -0
  59. package/src/{flows/dwn-registration.ts → registration.ts} +2 -2
  60. package/src/types.ts +171 -4
  61. package/src/wallet-connect-client.ts +275 -0
  62. package/dist/esm/flows/dwn-discovery.js.map +0 -1
  63. package/dist/esm/flows/dwn-registration.js.map +0 -1
  64. package/dist/esm/flows/import-identity.js +0 -177
  65. package/dist/esm/flows/import-identity.js.map +0 -1
  66. package/dist/esm/flows/local-connect.js +0 -158
  67. package/dist/esm/flows/local-connect.js.map +0 -1
  68. package/dist/esm/flows/session-restore.js +0 -125
  69. package/dist/esm/flows/session-restore.js.map +0 -1
  70. package/dist/esm/flows/wallet-connect.js +0 -200
  71. package/dist/esm/flows/wallet-connect.js.map +0 -1
  72. package/dist/esm/vault/vault-manager.js +0 -95
  73. package/dist/esm/vault/vault-manager.js.map +0 -1
  74. package/dist/types/flows/dwn-discovery.d.ts.map +0 -1
  75. package/dist/types/flows/dwn-registration.d.ts.map +0 -1
  76. package/dist/types/flows/import-identity.d.ts +0 -35
  77. package/dist/types/flows/import-identity.d.ts.map +0 -1
  78. package/dist/types/flows/local-connect.d.ts +0 -31
  79. package/dist/types/flows/local-connect.d.ts.map +0 -1
  80. package/dist/types/flows/session-restore.d.ts +0 -29
  81. package/dist/types/flows/session-restore.d.ts.map +0 -1
  82. package/dist/types/flows/wallet-connect.d.ts +0 -44
  83. package/dist/types/flows/wallet-connect.d.ts.map +0 -1
  84. package/dist/types/vault/vault-manager.d.ts +0 -57
  85. package/dist/types/vault/vault-manager.d.ts.map +0 -1
  86. package/src/flows/import-identity.ts +0 -219
  87. package/src/flows/local-connect.ts +0 -192
  88. package/src/flows/session-restore.ts +0 -155
  89. package/src/flows/wallet-connect.ts +0 -226
  90. package/src/vault/vault-manager.ts +0 -89
@@ -6,26 +6,19 @@
6
6
  * @module
7
7
  */
8
8
 
9
- import { EnboxUserAgent } from '@enbox/agent';
10
- import type { BearerIdentity, PortableIdentity } from '@enbox/agent';
11
-
12
- import { AuthEventEmitter } from './events.js';
13
- import { AuthSession } from './identity-session.js';
14
- import { createDefaultStorage } from './storage/storage.js';
15
- import { discoverLocalDwn } from './flows/dwn-discovery.js';
16
- import { localConnect } from './flows/local-connect.js';
17
- import { restoreSession } from './flows/session-restore.js';
18
- import { STORAGE_KEYS } from './types.js';
19
- import { VaultManager } from './vault/vault-manager.js';
20
- import { walletConnect } from './flows/wallet-connect.js';
9
+ import type { BearerIdentity, HdIdentityVault, PortableIdentity } from '@enbox/agent';
21
10
 
11
+ import type { FlowContext } from './connect/lifecycle.js';
22
12
  import type { PasswordProvider } from './password-provider.js';
23
13
  import type {
24
14
  AuthEvent,
25
15
  AuthEventHandler,
26
16
  AuthManagerOptions,
27
17
  AuthState,
18
+ ConnectHandler,
19
+ ConnectOptions,
28
20
  DisconnectOptions,
21
+ HandlerConnectOptions,
29
22
  HeadlessConnectOptions,
30
23
  IdentityInfo,
31
24
  ImportFromPhraseOptions,
@@ -38,7 +31,20 @@ import type {
38
31
  SyncOption,
39
32
  WalletConnectOptions,
40
33
  } from './types.js';
41
- import { importFromPhrase, importFromPortable } from './flows/import-identity.js';
34
+
35
+ import { EnboxUserAgent } from '@enbox/agent';
36
+
37
+ import { AuthEventEmitter } from './events.js';
38
+ import { AuthSession } from './identity-session.js';
39
+ import { createDefaultStorage } from './storage/storage.js';
40
+ import { discoverLocalDwn } from './discovery.js';
41
+ import { localConnect } from './connect/local.js';
42
+ import { normalizeProtocolRequests } from './permissions.js';
43
+ import { restoreSession } from './connect/restore.js';
44
+ import { STORAGE_KEYS } from './types.js';
45
+ import { walletConnect } from './connect/wallet.js';
46
+ import { ensureVaultReady, finalizeDelegateSession, importDelegateAndSetupSync, resolveIdentityDids, resolvePassword, startSyncIfEnabled } from './connect/lifecycle.js';
47
+ import { importFromPhrase, importFromPortable } from './connect/import.js';
42
48
 
43
49
  /**
44
50
  * The primary entry point for authentication and identity management.
@@ -77,7 +83,6 @@ export class AuthManager {
77
83
  private _userAgent: EnboxUserAgent;
78
84
  private _emitter: AuthEventEmitter;
79
85
  private _storage: StorageAdapter;
80
- private _vault: VaultManager;
81
86
  private _session: AuthSession | undefined;
82
87
  private _state: AuthState = 'uninitialized';
83
88
  private _isConnecting = false;
@@ -89,6 +94,7 @@ export class AuthManager {
89
94
  private _defaultSync?: SyncOption;
90
95
  private _defaultDwnEndpoints?: string[];
91
96
  private _registration?: RegistrationOptions;
97
+ private _connectHandler?: ConnectHandler;
92
98
 
93
99
  /**
94
100
  * The local DWN server endpoint discovered during `create()`, if any.
@@ -102,24 +108,24 @@ export class AuthManager {
102
108
  userAgent: EnboxUserAgent;
103
109
  emitter: AuthEventEmitter;
104
110
  storage: StorageAdapter;
105
- vault: VaultManager;
106
111
  defaultPassword?: string;
107
112
  passwordProvider?: PasswordProvider;
108
113
  defaultSync?: SyncOption;
109
114
  defaultDwnEndpoints?: string[];
110
115
  registration?: RegistrationOptions;
111
116
  localDwnEndpoint?: string;
117
+ connectHandler?: ConnectHandler;
112
118
  }) {
113
119
  this._userAgent = params.userAgent;
114
120
  this._emitter = params.emitter;
115
121
  this._storage = params.storage;
116
- this._vault = params.vault;
117
122
  this._defaultPassword = params.defaultPassword;
118
123
  this._passwordProvider = params.passwordProvider;
119
124
  this._defaultSync = params.defaultSync;
120
125
  this._defaultDwnEndpoints = params.defaultDwnEndpoints;
121
126
  this._registration = params.registration;
122
127
  this._localDwnEndpoint = params.localDwnEndpoint;
128
+ this._connectHandler = params.connectHandler;
123
129
  }
124
130
 
125
131
  /**
@@ -159,24 +165,22 @@ export class AuthManager {
159
165
  localDwnEndpoint,
160
166
  });
161
167
 
162
- const vault = new VaultManager(userAgent.vault, emitter);
163
-
164
168
  const manager = new AuthManager({
165
169
  userAgent,
166
170
  emitter,
167
171
  storage,
168
- vault,
169
172
  defaultPassword : options.password,
170
173
  passwordProvider : options.passwordProvider,
171
174
  defaultSync : options.sync,
172
175
  defaultDwnEndpoints : options.dwnEndpoints,
173
176
  registration : options.registration,
174
177
  localDwnEndpoint,
178
+ connectHandler : options.connectHandler,
175
179
  });
176
180
 
177
181
  // Determine initial state.
178
- if (await vault.isInitialized()) {
179
- manager._setState(vault.isLocked ? 'locked' : 'unlocked');
182
+ if (await userAgent.vault.isInitialized()) {
183
+ manager._setState(userAgent.vault.isLocked() ? 'locked' : 'unlocked');
180
184
  } else {
181
185
  manager._setState('uninitialized');
182
186
  }
@@ -187,40 +191,75 @@ export class AuthManager {
187
191
  // ─── Connection flows ──────────────────────────────────────────
188
192
 
189
193
  /**
190
- * Create or reconnect a local identity.
194
+ * Connect to a wallet or create a local session.
195
+ *
196
+ * This is the primary entry point for dapps. It routes to the
197
+ * appropriate flow based on the options:
198
+ *
199
+ * **Handler-based connect** (dapps): Delegates credential acquisition
200
+ * to a {@link ConnectHandler}. Triggered when `protocols` or
201
+ * `connectHandler` is provided.
202
+ *
203
+ * **Local connect** (wallets / CLI): Creates or unlocks a local vault.
204
+ * Triggered when `password`, `createIdentity`, or `recoveryPhrase`
205
+ * is provided.
206
+ *
207
+ * In both cases, `connect()` first attempts to restore a previous
208
+ * session. If a valid session exists, it is returned immediately
209
+ * without any user interaction.
210
+ *
211
+ * @example Dapp (browser)
212
+ * ```ts
213
+ * import { BrowserConnectHandler } from '@enbox/browser';
214
+ *
215
+ * const auth = await AuthManager.create({
216
+ * connectHandler: BrowserConnectHandler(),
217
+ * });
218
+ * const session = await auth.connect({
219
+ * protocols: [NotesProtocol],
220
+ * });
221
+ * ```
191
222
  *
192
- * On first use, this creates a new vault, agent DID, and user identity.
193
- * On subsequent uses, it unlocks the vault and reconnects.
223
+ * @example Wallet / CLI
224
+ * ```ts
225
+ * const session = await auth.connect({
226
+ * password: userPin,
227
+ * createIdentity: true,
228
+ * });
229
+ * ```
194
230
  *
195
- * @param options - Optional overrides for password, sync, DWN endpoints.
231
+ * @param options - Connection options. The shape determines the flow.
196
232
  * @returns An active AuthSession.
197
233
  * @throws If a connection attempt is already in progress.
234
+ * @throws If handler-based connect is attempted without a handler.
198
235
  */
199
- async connect(options?: LocalConnectOptions): Promise<AuthSession> {
200
- this._guardConcurrency();
201
- this._isConnecting = true;
236
+ async connect(options?: ConnectOptions): Promise<AuthSession> {
237
+ return this._withConnect(async () => {
238
+ // 1. Try to restore a previous session first.
239
+ const restored = await restoreSession(this._flowContext());
240
+ if (restored) { return restored; }
241
+
242
+ // 2. Route to the appropriate flow.
243
+ if (this._isLocalConnect(options)) {
244
+ return localConnect(this._flowContext(), options as LocalConnectOptions);
245
+ }
202
246
 
203
- try {
204
- const session = await localConnect(
205
- {
206
- userAgent : this._userAgent,
207
- emitter : this._emitter,
208
- storage : this._storage,
209
- defaultPassword : this._defaultPassword,
210
- passwordProvider : this._passwordProvider,
211
- defaultSync : this._defaultSync,
212
- defaultDwnEndpoints : this._defaultDwnEndpoints,
213
- registration : this._registration,
214
- },
215
- options,
216
- );
247
+ return this._handlerConnect(options as HandlerConnectOptions | undefined);
248
+ });
249
+ }
217
250
 
218
- this._session = session;
219
- this._setState('connected');
220
- return session;
221
- } finally {
222
- this._isConnecting = false;
223
- }
251
+ /**
252
+ * Create or reconnect a local identity (explicit local connect).
253
+ *
254
+ * Use this when you explicitly want the local vault flow, bypassing
255
+ * auto-detection. This is the preferred method for wallet apps.
256
+ *
257
+ * @param options - Local connect options.
258
+ * @returns An active AuthSession.
259
+ * @throws If a connection attempt is already in progress.
260
+ */
261
+ async connectLocal(options?: LocalConnectOptions): Promise<AuthSession> {
262
+ return this._withConnect(() => localConnect(this._flowContext(), options));
224
263
  }
225
264
 
226
265
  /**
@@ -235,50 +274,7 @@ export class AuthManager {
235
274
  * @throws If a connection attempt is already in progress.
236
275
  */
237
276
  async walletConnect(options: WalletConnectOptions): Promise<AuthSession> {
238
- this._guardConcurrency();
239
- this._isConnecting = true;
240
-
241
- try {
242
- // Ensure the agent is initialized and started before wallet connect.
243
- const isFirstLaunch = await this._userAgent.firstLaunch();
244
- let password = this._defaultPassword;
245
-
246
- if (!password && this._passwordProvider) {
247
- try {
248
- password = await this._passwordProvider.getPassword({
249
- reason: isFirstLaunch ? 'create' : 'unlock',
250
- });
251
- } catch {
252
- // Provider failed — fall through to insecure default.
253
- }
254
- }
255
-
256
- password ??= 'insecure-static-phrase';
257
-
258
- if (isFirstLaunch) {
259
- await this._userAgent.initialize({ password });
260
- }
261
- await this._userAgent.start({ password });
262
- this._emitter.emit('vault-unlocked', {});
263
-
264
- const session = await walletConnect(
265
- {
266
- userAgent : this._userAgent,
267
- emitter : this._emitter,
268
- storage : this._storage,
269
- defaultSync : this._defaultSync,
270
- defaultDwnEndpoints : this._defaultDwnEndpoints,
271
- registration : this._registration,
272
- },
273
- options,
274
- );
275
-
276
- this._session = session;
277
- this._setState('connected');
278
- return session;
279
- } finally {
280
- this._isConnecting = false;
281
- }
277
+ return this._withConnect(() => walletConnect(this._flowContext(), options));
282
278
  }
283
279
 
284
280
  /**
@@ -288,28 +284,7 @@ export class AuthManager {
288
284
  * recovering the identity on this device.
289
285
  */
290
286
  async importFromPhrase(options: ImportFromPhraseOptions): Promise<AuthSession> {
291
- this._guardConcurrency();
292
- this._isConnecting = true;
293
-
294
- try {
295
- const session = await importFromPhrase(
296
- {
297
- userAgent : this._userAgent,
298
- emitter : this._emitter,
299
- storage : this._storage,
300
- defaultSync : this._defaultSync,
301
- defaultDwnEndpoints : this._defaultDwnEndpoints,
302
- registration : this._registration,
303
- },
304
- options,
305
- );
306
-
307
- this._session = session;
308
- this._setState('connected');
309
- return session;
310
- } finally {
311
- this._isConnecting = false;
312
- }
287
+ return this._withConnect(() => importFromPhrase(this._flowContext(), options));
313
288
  }
314
289
 
315
290
  /**
@@ -318,28 +293,7 @@ export class AuthManager {
318
293
  * The portable identity contains the DID's private keys and metadata.
319
294
  */
320
295
  async importFromPortable(options: ImportFromPortableOptions): Promise<AuthSession> {
321
- this._guardConcurrency();
322
- this._isConnecting = true;
323
-
324
- try {
325
- const session = await importFromPortable(
326
- {
327
- userAgent : this._userAgent,
328
- emitter : this._emitter,
329
- storage : this._storage,
330
- defaultSync : this._defaultSync,
331
- defaultDwnEndpoints : this._defaultDwnEndpoints,
332
- registration : this._registration,
333
- },
334
- options,
335
- );
336
-
337
- this._session = session;
338
- this._setState('connected');
339
- return session;
340
- } finally {
341
- this._isConnecting = false;
342
- }
296
+ return this._withConnect(() => importFromPortable(this._flowContext(), options));
343
297
  }
344
298
 
345
299
  /**
@@ -353,17 +307,7 @@ export class AuthManager {
353
307
  this._isConnecting = true;
354
308
 
355
309
  try {
356
- const session = await restoreSession(
357
- {
358
- userAgent : this._userAgent,
359
- emitter : this._emitter,
360
- storage : this._storage,
361
- defaultPassword : this._defaultPassword,
362
- passwordProvider : this._passwordProvider,
363
- defaultSync : this._defaultSync,
364
- },
365
- options,
366
- );
310
+ const session = await restoreSession(this._flowContext(), options);
367
311
 
368
312
  if (session) {
369
313
  this._session = session;
@@ -400,10 +344,10 @@ export class AuthManager {
400
344
  */
401
345
  async connectHeadless(options?: HeadlessConnectOptions): Promise<AuthSession> {
402
346
  let password = options?.password ?? this._defaultPassword;
347
+ const isFirstLaunch = await this._userAgent.firstLaunch();
403
348
 
404
349
  // Try the password provider if no explicit password.
405
350
  if (!password && this._passwordProvider) {
406
- const isFirstLaunch = await this._userAgent.firstLaunch();
407
351
  password = await this._passwordProvider.getPassword({
408
352
  reason: isFirstLaunch ? 'create' : 'unlock',
409
353
  });
@@ -416,13 +360,13 @@ export class AuthManager {
416
360
  );
417
361
  }
418
362
 
419
- // Unlock the vault (initialise on first launch).
420
- if (await this._userAgent.firstLaunch()) {
421
- await this._userAgent.initialize({ password });
422
- } else {
423
- await this._userAgent.start({ password });
424
- }
425
- this._emitter.emit('vault-unlocked', {});
363
+ // Unlock the vault (initialise on first launch, always start).
364
+ await ensureVaultReady({
365
+ userAgent : this._userAgent,
366
+ emitter : this._emitter,
367
+ password,
368
+ isFirstLaunch,
369
+ });
426
370
 
427
371
  // Find the active identity.
428
372
  const identities = await this._userAgent.identity.list();
@@ -437,8 +381,7 @@ export class AuthManager {
437
381
  : undefined
438
382
  ) ?? identities[0];
439
383
 
440
- const connectedDid = identity.metadata.connectedDid ?? identity.did.uri;
441
- const delegateDid = identity.metadata.connectedDid ? identity.did.uri : undefined;
384
+ const { connectedDid, delegateDid } = resolveIdentityDids(identity);
442
385
 
443
386
  const identityInfo: IdentityInfo = {
444
387
  didUri : connectedDid,
@@ -484,15 +427,14 @@ export class AuthManager {
484
427
  const did = this._session?.did;
485
428
 
486
429
  // 1. Stop sync.
487
- if ('sync' in this._userAgent && typeof (this._userAgent as any).sync?.stopSync === 'function') {
488
- await (this._userAgent as any).sync.stopSync(timeout);
489
- }
430
+ await this._userAgent.sync.stopSync(timeout);
490
431
 
491
432
  // 2. Clear the session (but keep storage markers for restore).
492
433
  this._session = undefined;
493
434
 
494
- // 3. Lock the vault (also emits 'vault-locked').
495
- await this._vault.lock();
435
+ // 3. Lock the vault.
436
+ await this._userAgent.vault.lock();
437
+ this._emitter.emit('vault-locked', {});
496
438
 
497
439
  // 4. Transition state.
498
440
  this._setState('locked');
@@ -517,9 +459,7 @@ export class AuthManager {
517
459
 
518
460
  // Stop sync.
519
461
  if (this._session) {
520
- if ('sync' in this._userAgent && typeof (this._userAgent as any).sync?.stopSync === 'function') {
521
- await (this._userAgent as any).sync.stopSync(timeout);
522
- }
462
+ await this._userAgent.sync.stopSync(timeout);
523
463
  }
524
464
 
525
465
  this._session = undefined;
@@ -591,33 +531,28 @@ export class AuthManager {
591
531
  const did = this._session?.did;
592
532
 
593
533
  // 1. Stop sync.
594
- if ('sync' in this._userAgent &&
595
- typeof (this._userAgent as any).sync?.stopSync === 'function') {
596
- try {
597
- await (this._userAgent as any).sync.stopSync(timeout);
598
- } catch {
599
- // Best-effort — don't block shutdown on sync errors.
600
- }
534
+ try {
535
+ await this._userAgent.sync.stopSync(timeout);
536
+ } catch {
537
+ // Best-effort don't block shutdown on sync errors.
601
538
  }
602
539
 
603
540
  // 2. Clear the active session.
604
541
  this._session = undefined;
605
542
 
606
- // 3. Lock the vault (emits 'vault-locked').
543
+ // 3. Lock the vault.
607
544
  try {
608
- await this._vault.lock();
545
+ await this._userAgent.vault.lock();
546
+ this._emitter.emit('vault-locked', {});
609
547
  } catch {
610
548
  // Vault may already be locked or uninitialised — safe to ignore.
611
549
  }
612
550
 
613
551
  // 4. Close the sync engine (releases LevelDB handles, timers).
614
- if ('sync' in this._userAgent &&
615
- typeof (this._userAgent as any).sync?.close === 'function') {
616
- try {
617
- await (this._userAgent as any).sync.close();
618
- } catch {
619
- // Best-effort.
620
- }
552
+ try {
553
+ await this._userAgent.sync.close();
554
+ } catch {
555
+ // Best-effort.
621
556
  }
622
557
 
623
558
  // 5. Close the storage adapter (e.g. LevelDB session store).
@@ -673,8 +608,7 @@ export class AuthManager {
673
608
  throw new Error(`[@enbox/auth] Identity not found: ${didUri}`);
674
609
  }
675
610
 
676
- const connectedDid = identity.metadata.connectedDid ?? identity.did.uri;
677
- const delegateDid = identity.metadata.connectedDid ? identity.did.uri : undefined;
611
+ const { connectedDid, delegateDid } = resolveIdentityDids(identity);
678
612
 
679
613
  // Persist the switch.
680
614
  await this._storage.set(STORAGE_KEYS.PREVIOUSLY_CONNECTED, 'true');
@@ -699,10 +633,7 @@ export class AuthManager {
699
633
  // Already registered — safe to ignore.
700
634
  }
701
635
 
702
- const syncMode = sync === undefined ? 'live' : 'poll';
703
- const syncInterval = sync ?? (syncMode === 'live' ? '5m' : '2m');
704
- this._userAgent.sync.startSync({ mode: syncMode, interval: syncInterval })
705
- .catch((err: unknown) => console.error('[@enbox/auth] Sync failed:', err));
636
+ startSyncIfEnabled(this._userAgent, sync);
706
637
  }
707
638
 
708
639
  this._session = new AuthSession({
@@ -768,9 +699,9 @@ export class AuthManager {
768
699
 
769
700
  // ─── Vault ─────────────────────────────────────────────────────
770
701
 
771
- /** Access the vault manager for lock/unlock/backup operations. */
772
- get vault(): VaultManager {
773
- return this._vault;
702
+ /** Access the underlying identity vault for lock/unlock/backup operations. */
703
+ get vault(): HdIdentityVault {
704
+ return this._userAgent.vault;
774
705
  }
775
706
 
776
707
  // ─── Events ────────────────────────────────────────────────────
@@ -800,7 +731,7 @@ export class AuthManager {
800
731
 
801
732
  /** Whether the vault is currently locked. */
802
733
  get isLocked(): boolean {
803
- return this._vault.isLocked;
734
+ return this._userAgent.vault.isLocked();
804
735
  }
805
736
 
806
737
  /** Whether a connection attempt is in progress. */
@@ -826,6 +757,142 @@ export class AuthManager {
826
757
 
827
758
  // ─── Private helpers ───────────────────────────────────────────
828
759
 
760
+ /**
761
+ * Determine whether the given options indicate a local connect flow.
762
+ *
763
+ * Local connect is indicated by the presence of `password`,
764
+ * `createIdentity`, or `recoveryPhrase` — signals that the caller
765
+ * is managing its own vault/identity lifecycle. In non-browser
766
+ * environments, local connect is the fallback.
767
+ */
768
+ private _isLocalConnect(options?: ConnectOptions): boolean {
769
+ const o = (options ?? {}) as Record<string, unknown>;
770
+
771
+ // If any local-connect-specific keys are present, it's definitely local.
772
+ const hasLocalSignals = (
773
+ o.password !== undefined ||
774
+ o.createIdentity !== undefined ||
775
+ o.recoveryPhrase !== undefined ||
776
+ o.dwnEndpoints !== undefined ||
777
+ o.metadata !== undefined
778
+ );
779
+ if (hasLocalSignals) { return true; }
780
+
781
+ // If any handler-connect signals are present, use the handler flow.
782
+ const hasHandlerSignals = (
783
+ o.protocols !== undefined ||
784
+ o.connectHandler !== undefined
785
+ );
786
+ if (hasHandlerSignals) { return false; }
787
+
788
+ // No explicit signals → default to local connect.
789
+ // Callers that want handler-based connect must provide protocols
790
+ // or a connectHandler.
791
+ return true;
792
+ }
793
+
794
+ /**
795
+ * Run a handler-based (delegated) connect flow.
796
+ *
797
+ * 1. Initialize the vault (agent-only, no identity).
798
+ * 2. Normalize protocol permission requests.
799
+ * 3. Delegate to the connect handler for credential acquisition.
800
+ * 4. Import the delegate DID, process grants, set up sync.
801
+ * 5. Finalize and return the AuthSession.
802
+ */
803
+ private async _handlerConnect(
804
+ options?: HandlerConnectOptions,
805
+ ): Promise<AuthSession> {
806
+ const ctx = this._flowContext();
807
+ const { userAgent, emitter, storage } = ctx;
808
+ const sync = options?.sync ?? ctx.defaultSync;
809
+
810
+ if (sync === 'off') {
811
+ throw new Error(
812
+ '[@enbox/auth] Sync must be enabled for delegated connect. ' +
813
+ 'Remove sync: "off" or set an interval like "15s".'
814
+ );
815
+ }
816
+
817
+ // 1. Initialize vault (agent-only, no identity).
818
+ const isFirstLaunch = await userAgent.firstLaunch();
819
+ const password = await resolvePassword(ctx, undefined, isFirstLaunch);
820
+ await ensureVaultReady({ userAgent, emitter, password, isFirstLaunch });
821
+
822
+ // 2. Normalize protocol requests.
823
+ const permissionRequests = normalizeProtocolRequests(options?.protocols);
824
+
825
+ // 3. Resolve the handler.
826
+ const handler = options?.connectHandler ?? this._connectHandler;
827
+ if (!handler) {
828
+ throw new Error(
829
+ '[@enbox/auth] No connect handler provided. ' +
830
+ 'Install @enbox/browser and pass BrowserConnectHandler(), ' +
831
+ 'or provide a custom ConnectHandler.'
832
+ );
833
+ }
834
+
835
+ // 4. Delegate to the handler.
836
+ const result = await handler.requestAccess({ permissionRequests });
837
+ if (!result) {
838
+ throw new Error('[@enbox/auth] Connect was denied or cancelled by the user.');
839
+ }
840
+
841
+ // 5. Import delegate DID, process grants, set up sync.
842
+ const { delegatePortableDid, connectedDid, delegateGrants } = result;
843
+ const identity = await importDelegateAndSetupSync({
844
+ userAgent, delegatePortableDid, connectedDid, delegateGrants,
845
+ flowName: 'Connect',
846
+ });
847
+
848
+ // 6. Finalize session.
849
+ return finalizeDelegateSession({
850
+ userAgent, emitter, storage, identity,
851
+ connectedDid, delegateDid: delegatePortableDid.uri, sync,
852
+ });
853
+ }
854
+
855
+ /**
856
+ * Build a `FlowContext` from the manager's current state.
857
+ *
858
+ * Replaces the 5 manual inline context constructions that were
859
+ * previously duplicated across `connect()`, `walletConnect()`,
860
+ * `importFromPhrase()`, `importFromPortable()`, and `restoreSession()`.
861
+ */
862
+ private _flowContext(): FlowContext {
863
+ return {
864
+ userAgent : this._userAgent,
865
+ emitter : this._emitter,
866
+ storage : this._storage,
867
+ defaultPassword : this._defaultPassword,
868
+ passwordProvider : this._passwordProvider,
869
+ defaultSync : this._defaultSync,
870
+ defaultDwnEndpoints : this._defaultDwnEndpoints,
871
+ registration : this._registration,
872
+ };
873
+ }
874
+
875
+ /**
876
+ * Template for connection flows that follow the guard → try/finally → setState pattern.
877
+ *
878
+ * Consolidates the duplicated concurrency guard, `_isConnecting` flag management,
879
+ * session assignment, and state transition across `connect()`, `walletConnect()`,
880
+ * `importFromPhrase()`, and `importFromPortable()`.
881
+ */
882
+ private async _withConnect(fn: () => Promise<AuthSession>): Promise<AuthSession> {
883
+ this._guardConcurrency();
884
+ this._isConnecting = true;
885
+
886
+ try {
887
+ const session = await fn();
888
+ this._session = session;
889
+ this._setState('connected');
890
+ return session;
891
+ } finally {
892
+ this._isConnecting = false;
893
+ }
894
+ }
895
+
829
896
  private _setState(state: AuthState): void {
830
897
  if (state === this._state) {return;}
831
898
  const previous = this._state;