@enbox/auth 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.
Files changed (50) hide show
  1. package/dist/esm/auth-manager.js +245 -5
  2. package/dist/esm/auth-manager.js.map +1 -1
  3. package/dist/esm/flows/dwn-discovery.js +96 -81
  4. package/dist/esm/flows/dwn-discovery.js.map +1 -1
  5. package/dist/esm/flows/dwn-registration.js +49 -3
  6. package/dist/esm/flows/dwn-registration.js.map +1 -1
  7. package/dist/esm/flows/import-identity.js +2 -0
  8. package/dist/esm/flows/import-identity.js.map +1 -1
  9. package/dist/esm/flows/local-connect.js +25 -8
  10. package/dist/esm/flows/local-connect.js.map +1 -1
  11. package/dist/esm/flows/session-restore.js +20 -4
  12. package/dist/esm/flows/session-restore.js.map +1 -1
  13. package/dist/esm/flows/wallet-connect.js +5 -4
  14. package/dist/esm/flows/wallet-connect.js.map +1 -1
  15. package/dist/esm/index.js +5 -1
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/password-provider.js +319 -0
  18. package/dist/esm/password-provider.js.map +1 -0
  19. package/dist/esm/types.js +9 -1
  20. package/dist/esm/types.js.map +1 -1
  21. package/dist/types/auth-manager.d.ts +83 -2
  22. package/dist/types/auth-manager.d.ts.map +1 -1
  23. package/dist/types/flows/dwn-discovery.d.ts +40 -53
  24. package/dist/types/flows/dwn-discovery.d.ts.map +1 -1
  25. package/dist/types/flows/dwn-registration.d.ts +20 -1
  26. package/dist/types/flows/dwn-registration.d.ts.map +1 -1
  27. package/dist/types/flows/import-identity.d.ts.map +1 -1
  28. package/dist/types/flows/local-connect.d.ts +2 -0
  29. package/dist/types/flows/local-connect.d.ts.map +1 -1
  30. package/dist/types/flows/session-restore.d.ts +2 -0
  31. package/dist/types/flows/session-restore.d.ts.map +1 -1
  32. package/dist/types/flows/wallet-connect.d.ts +2 -2
  33. package/dist/types/flows/wallet-connect.d.ts.map +1 -1
  34. package/dist/types/index.d.ts +5 -2
  35. package/dist/types/index.d.ts.map +1 -1
  36. package/dist/types/password-provider.d.ts +194 -0
  37. package/dist/types/password-provider.d.ts.map +1 -0
  38. package/dist/types/types.d.ts +106 -1
  39. package/dist/types/types.d.ts.map +1 -1
  40. package/package.json +8 -9
  41. package/src/auth-manager.ts +284 -9
  42. package/src/flows/dwn-discovery.ts +99 -79
  43. package/src/flows/dwn-registration.ts +60 -5
  44. package/src/flows/import-identity.ts +2 -0
  45. package/src/flows/local-connect.ts +24 -3
  46. package/src/flows/session-restore.ts +22 -2
  47. package/src/flows/wallet-connect.ts +5 -4
  48. package/src/index.ts +10 -1
  49. package/src/password-provider.ts +383 -0
  50. package/src/types.ts +114 -1
@@ -3,6 +3,7 @@
3
3
  * Public types for the authentication and identity management SDK.
4
4
  */
5
5
  import type { ConnectPermissionRequest, EnboxUserAgent, HdIdentityVault, LocalDwnStrategy, PortableIdentity } from '@enbox/agent';
6
+ import type { PasswordProvider } from './password-provider.js';
6
7
  export type { ConnectPermissionRequest, HdIdentityVault, IdentityVaultBackup, LocalDwnStrategy, PortableIdentity } from '@enbox/agent';
7
8
  export type { EnboxUserAgent } from '@enbox/agent';
8
9
  /**
@@ -132,13 +133,44 @@ export interface RegistrationOptions {
132
133
  * Pre-existing registration tokens from a previous session, keyed by
133
134
  * DWN endpoint URL. If a valid (non-expired) token exists for an
134
135
  * endpoint, it is used directly without re-running the auth flow.
136
+ *
137
+ * When {@link persistTokens} is `true`, this field is ignored —
138
+ * tokens are loaded automatically from the `StorageAdapter`.
135
139
  */
136
140
  registrationTokens?: Record<string, RegistrationTokenData>;
137
141
  /**
138
142
  * Called when new or refreshed registration tokens are obtained.
139
143
  * The app should persist these for future sessions.
144
+ *
145
+ * When {@link persistTokens} is `true`, tokens are saved automatically
146
+ * to the `StorageAdapter`. This callback is still invoked (if provided)
147
+ * **after** the automatic save, so consumers can observe token changes
148
+ * without handling persistence themselves.
140
149
  */
141
150
  onRegistrationTokens?: (tokens: Record<string, RegistrationTokenData>) => void;
151
+ /**
152
+ * Automatically persist and restore registration tokens using the
153
+ * auth manager's `StorageAdapter`.
154
+ *
155
+ * When `true`, tokens are loaded from storage before registration and
156
+ * saved back after new or refreshed tokens are obtained. This removes
157
+ * the need for consumers to implement their own token I/O via
158
+ * {@link registrationTokens} and {@link onRegistrationTokens}.
159
+ *
160
+ * Defaults to `false` for backward compatibility.
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * const auth = await AuthManager.create({
165
+ * registration: {
166
+ * onSuccess: () => {},
167
+ * onFailure: (err) => console.error(err),
168
+ * persistTokens: true,
169
+ * },
170
+ * });
171
+ * ```
172
+ */
173
+ persistTokens?: boolean;
142
174
  }
143
175
  /** Options for {@link AuthManager.create}. */
144
176
  export interface AuthManagerOptions {
@@ -182,8 +214,32 @@ export interface AuthManagerOptions {
182
214
  /**
183
215
  * Default password for vault operations.
184
216
  * If not provided, an insecure default is used (with a console warning).
217
+ *
218
+ * For more flexible password acquisition (env vars, TTY prompts,
219
+ * chained fallbacks), use {@link passwordProvider} instead.
185
220
  */
186
221
  password?: string;
222
+ /**
223
+ * A composable password provider for obtaining the vault password.
224
+ *
225
+ * When set, this provider is consulted by `connect()`,
226
+ * `restoreSession()`, and `connectHeadless()` whenever a password
227
+ * is needed and none was given explicitly. It takes precedence over
228
+ * the static {@link password} option.
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * import { AuthManager, PasswordProvider } from '@enbox/auth';
233
+ *
234
+ * const auth = await AuthManager.create({
235
+ * passwordProvider: PasswordProvider.chain([
236
+ * PasswordProvider.fromEnv('ENBOX_PASSWORD'),
237
+ * PasswordProvider.fromTty(),
238
+ * ]),
239
+ * });
240
+ * ```
241
+ */
242
+ passwordProvider?: PasswordProvider;
187
243
  /**
188
244
  * Sync interval for DWN synchronization.
189
245
  * - `'off'` — disable sync
@@ -256,6 +312,39 @@ export interface ImportFromPortableOptions {
256
312
  export interface RestoreSessionOptions {
257
313
  /** Password to unlock the vault (needed if vault is locked). */
258
314
  password?: string;
315
+ /**
316
+ * Called when the vault is locked and a password is required to proceed.
317
+ *
318
+ * If provided, this callback is invoked instead of falling back to the
319
+ * default password or the insecure static phrase. This is the recommended
320
+ * way to implement interactive password prompts (e.g., a PIN entry dialog
321
+ * or CLI prompt).
322
+ *
323
+ * @returns The password entered by the user.
324
+ *
325
+ * @example Browser PIN dialog
326
+ * ```ts
327
+ * const session = await auth.restoreSession({
328
+ * onPasswordRequired: async () => {
329
+ * return await showPinDialog();
330
+ * },
331
+ * });
332
+ * ```
333
+ */
334
+ onPasswordRequired?: () => Promise<string>;
335
+ }
336
+ /** Options for {@link AuthManager.connectHeadless}. */
337
+ export interface HeadlessConnectOptions {
338
+ /** Vault password (overrides manager default). */
339
+ password?: string;
340
+ }
341
+ /** Options for {@link AuthManager.shutdown}. */
342
+ export interface ShutdownOptions {
343
+ /**
344
+ * Milliseconds to wait for pending sync operations before shutting down.
345
+ * Default: `2000`.
346
+ */
347
+ timeout?: number;
259
348
  }
260
349
  /** Options for {@link AuthManager.disconnect}. */
261
350
  export interface DisconnectOptions {
@@ -284,6 +373,14 @@ export interface StorageAdapter {
284
373
  remove(key: string): Promise<void>;
285
374
  /** Clear all stored data. */
286
375
  clear(): Promise<void>;
376
+ /**
377
+ * Close the underlying storage resources (e.g. LevelDB handles).
378
+ *
379
+ * Optional — not all adapters need cleanup. Called by
380
+ * {@link AuthManager.shutdown} to release resources so the process
381
+ * can exit cleanly.
382
+ */
383
+ close?(): Promise<void>;
287
384
  }
288
385
  /** The insecure default password used when none is provided. */
289
386
  export declare const INSECURE_DEFAULT_PASSWORD = "insecure-static-phrase";
@@ -301,12 +398,20 @@ export declare const STORAGE_KEYS: {
301
398
  /** The connected DID (for wallet-connected sessions). */
302
399
  readonly CONNECTED_DID: "enbox:auth:connectedDid";
303
400
  /**
304
- * The base URL of the local DWN server discovered via the `dwn://register`
401
+ * The base URL of the local DWN server discovered via the `dwn://connect`
305
402
  * browser redirect flow. Persisted so subsequent page loads can skip the
306
403
  * redirect and inject the endpoint directly.
307
404
  *
308
405
  * @see https://github.com/enboxorg/enbox/issues/589
309
406
  */
310
407
  readonly LOCAL_DWN_ENDPOINT: "enbox:auth:localDwnEndpoint";
408
+ /**
409
+ * JSON-serialised `Record<string, RegistrationTokenData>` for DWN endpoint
410
+ * registration tokens. Automatically loaded before registration and saved
411
+ * after new/refreshed tokens are obtained when `persistTokens` is enabled.
412
+ *
413
+ * @see https://github.com/enboxorg/enbox/issues/690
414
+ */
415
+ readonly REGISTRATION_TOKENS: "enbox:auth:registrationTokens";
311
416
  };
312
417
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@enbox/auth",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "Headless authentication and identity management SDK for Enbox",
5
5
  "type": "module",
6
6
  "main": "./dist/esm/index.js",
7
7
  "module": "./dist/esm/index.js",
8
8
  "types": "./dist/types/index.d.ts",
9
9
  "scripts": {
10
- "clean": "rimraf dist",
11
- "build:esm": "rimraf dist/esm dist/types && bun tsc -p tsconfig.json",
10
+ "clean": "rm -rf dist",
11
+ "build:esm": "rm -rf dist/esm dist/types && bun tsc -p tsconfig.json",
12
12
  "build": "bun run clean && bun run build:esm",
13
13
  "lint": "eslint . --max-warnings 0",
14
14
  "lint:fix": "eslint . --fix",
@@ -56,16 +56,15 @@
56
56
  "bun": ">=1.0.0"
57
57
  },
58
58
  "dependencies": {
59
- "@enbox/agent": "0.3.1",
59
+ "@enbox/agent": "0.4.0",
60
60
  "@enbox/common": "0.0.7",
61
61
  "@enbox/dids": "0.0.9",
62
62
  "@enbox/dwn-clients": "0.1.0",
63
- "level": "8.0.0"
63
+ "level": "8.0.1"
64
64
  },
65
65
  "devDependencies": {
66
- "@types/node": "20.14.8",
67
- "bun-types": "latest",
68
- "rimraf": "4.4.0",
69
- "typescript": "5.5.4"
66
+ "@types/node": "22.19.15",
67
+ "bun-types": "1.3.10",
68
+ "typescript": "5.9.3"
70
69
  }
71
70
  }
@@ -12,23 +12,28 @@ import type { BearerIdentity, PortableIdentity } from '@enbox/agent';
12
12
  import { AuthEventEmitter } from './events.js';
13
13
  import { AuthSession } from './identity-session.js';
14
14
  import { createDefaultStorage } from './storage/storage.js';
15
+ import { discoverLocalDwn } from './flows/dwn-discovery.js';
15
16
  import { localConnect } from './flows/local-connect.js';
16
17
  import { restoreSession } from './flows/session-restore.js';
17
18
  import { STORAGE_KEYS } from './types.js';
18
19
  import { VaultManager } from './vault/vault-manager.js';
19
20
  import { walletConnect } from './flows/wallet-connect.js';
21
+
22
+ import type { PasswordProvider } from './password-provider.js';
20
23
  import type {
21
24
  AuthEvent,
22
25
  AuthEventHandler,
23
26
  AuthManagerOptions,
24
27
  AuthState,
25
28
  DisconnectOptions,
29
+ HeadlessConnectOptions,
26
30
  IdentityInfo,
27
31
  ImportFromPhraseOptions,
28
32
  ImportFromPortableOptions,
29
33
  LocalConnectOptions,
30
34
  RegistrationOptions,
31
35
  RestoreSessionOptions,
36
+ ShutdownOptions,
32
37
  StorageAdapter,
33
38
  SyncOption,
34
39
  WalletConnectOptions,
@@ -76,31 +81,45 @@ export class AuthManager {
76
81
  private _session: AuthSession | undefined;
77
82
  private _state: AuthState = 'uninitialized';
78
83
  private _isConnecting = false;
84
+ private _isShutDown = false;
79
85
 
80
86
  // Default options from create()
81
87
  private _defaultPassword?: string;
88
+ private _passwordProvider?: PasswordProvider;
82
89
  private _defaultSync?: SyncOption;
83
90
  private _defaultDwnEndpoints?: string[];
84
91
  private _registration?: RegistrationOptions;
85
92
 
93
+ /**
94
+ * The local DWN server endpoint discovered during `create()`, if any.
95
+ * `undefined` means no local server was found. This is set before any
96
+ * event listeners are attached, so consumers should check this property
97
+ * after `create()` returns rather than relying solely on events.
98
+ */
99
+ private _localDwnEndpoint?: string;
100
+
86
101
  private constructor(params: {
87
102
  userAgent: EnboxUserAgent;
88
103
  emitter: AuthEventEmitter;
89
104
  storage: StorageAdapter;
90
105
  vault: VaultManager;
91
106
  defaultPassword?: string;
107
+ passwordProvider?: PasswordProvider;
92
108
  defaultSync?: SyncOption;
93
109
  defaultDwnEndpoints?: string[];
94
110
  registration?: RegistrationOptions;
111
+ localDwnEndpoint?: string;
95
112
  }) {
96
113
  this._userAgent = params.userAgent;
97
114
  this._emitter = params.emitter;
98
115
  this._storage = params.storage;
99
116
  this._vault = params.vault;
100
117
  this._defaultPassword = params.defaultPassword;
118
+ this._passwordProvider = params.passwordProvider;
101
119
  this._defaultSync = params.defaultSync;
102
120
  this._defaultDwnEndpoints = params.defaultDwnEndpoints;
103
121
  this._registration = params.registration;
122
+ this._localDwnEndpoint = params.localDwnEndpoint;
104
123
  }
105
124
 
106
125
  /**
@@ -117,11 +136,27 @@ export class AuthManager {
117
136
  const emitter = new AuthEventEmitter();
118
137
  const storage = options.storage ?? createDefaultStorage();
119
138
 
139
+ // Run local DWN discovery BEFORE creating the agent. Discovery has
140
+ // zero vault/DWN dependencies — it only checks the URL fragment,
141
+ // reads localStorage, and validates via GET /info.
142
+ //
143
+ // When a local DWN server is available, the agent is created in
144
+ // "remote mode": it skips creating an in-process DWN and routes all
145
+ // DWN operations through RPC to the local server.
146
+ let localDwnEndpoint: string | undefined;
147
+ if (!options.agent && options.localDwnStrategy !== 'off') {
148
+ localDwnEndpoint = await discoverLocalDwn(storage);
149
+ // NOTE: We intentionally do NOT emit 'local-dwn-available' here
150
+ // because event listeners aren't attached yet. Consumers should
151
+ // check `authManager.localDwnEndpoint` after create() returns.
152
+ }
153
+
120
154
  // Use a pre-built agent or create one with the given options.
121
155
  const userAgent = options.agent ?? await EnboxUserAgent.create({
122
156
  dataPath : options.dataPath,
123
157
  agentVault : options.agentVault,
124
158
  localDwnStrategy : options.localDwnStrategy,
159
+ localDwnEndpoint,
125
160
  });
126
161
 
127
162
  const vault = new VaultManager(userAgent.vault, emitter);
@@ -132,9 +167,11 @@ export class AuthManager {
132
167
  storage,
133
168
  vault,
134
169
  defaultPassword : options.password,
170
+ passwordProvider : options.passwordProvider,
135
171
  defaultSync : options.sync,
136
172
  defaultDwnEndpoints : options.dwnEndpoints,
137
173
  registration : options.registration,
174
+ localDwnEndpoint,
138
175
  });
139
176
 
140
177
  // Determine initial state.
@@ -170,6 +207,7 @@ export class AuthManager {
170
207
  emitter : this._emitter,
171
208
  storage : this._storage,
172
209
  defaultPassword : this._defaultPassword,
210
+ passwordProvider : this._passwordProvider,
173
211
  defaultSync : this._defaultSync,
174
212
  defaultDwnEndpoints : this._defaultDwnEndpoints,
175
213
  registration : this._registration,
@@ -186,7 +224,7 @@ export class AuthManager {
186
224
  }
187
225
 
188
226
  /**
189
- * Connect to an external wallet via the OIDC/QR relay protocol.
227
+ * Connect to an external wallet via the Enbox Connect relay protocol.
190
228
  *
191
229
  * This runs the full WalletConnect flow: generates a URI for QR display,
192
230
  * validates the PIN, imports the delegated DID, and processes grants.
@@ -202,8 +240,22 @@ export class AuthManager {
202
240
 
203
241
  try {
204
242
  // Ensure the agent is initialized and started before wallet connect.
205
- const password = this._defaultPassword ?? 'insecure-static-phrase';
206
- if (await this._userAgent.firstLaunch()) {
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) {
207
259
  await this._userAgent.initialize({ password });
208
260
  }
209
261
  await this._userAgent.start({ password });
@@ -303,11 +355,12 @@ export class AuthManager {
303
355
  try {
304
356
  const session = await restoreSession(
305
357
  {
306
- userAgent : this._userAgent,
307
- emitter : this._emitter,
308
- storage : this._storage,
309
- defaultPassword : this._defaultPassword,
310
- defaultSync : this._defaultSync,
358
+ userAgent : this._userAgent,
359
+ emitter : this._emitter,
360
+ storage : this._storage,
361
+ defaultPassword : this._defaultPassword,
362
+ passwordProvider : this._passwordProvider,
363
+ defaultSync : this._defaultSync,
311
364
  },
312
365
  options,
313
366
  );
@@ -322,6 +375,90 @@ export class AuthManager {
322
375
  }
323
376
  }
324
377
 
378
+ /**
379
+ * Lightweight vault unlock for one-shot utilities and subprocesses.
380
+ *
381
+ * Unlocks the vault and retrieves the active (or first available)
382
+ * identity **without** starting sync, DWN registration, or persisting
383
+ * session markers. This is the recommended replacement for calling
384
+ * `agent.start({ password })` directly.
385
+ *
386
+ * Typical use cases:
387
+ * - Git credential helpers that need to sign a token and exit
388
+ * - CLI utilities that perform a single operation
389
+ * - Any subprocess that shares a data directory with a long-running daemon
390
+ *
391
+ * @param options - Optional password override.
392
+ * @returns An active AuthSession (with sync disabled).
393
+ *
394
+ * @example
395
+ * ```ts
396
+ * const session = await auth.connectHeadless({ password });
397
+ * const did = session.did; // ready to use
398
+ * await auth.shutdown(); // clean exit
399
+ * ```
400
+ */
401
+ async connectHeadless(options?: HeadlessConnectOptions): Promise<AuthSession> {
402
+ let password = options?.password ?? this._defaultPassword;
403
+
404
+ // Try the password provider if no explicit password.
405
+ if (!password && this._passwordProvider) {
406
+ const isFirstLaunch = await this._userAgent.firstLaunch();
407
+ password = await this._passwordProvider.getPassword({
408
+ reason: isFirstLaunch ? 'create' : 'unlock',
409
+ });
410
+ }
411
+
412
+ if (!password) {
413
+ throw new Error(
414
+ '[@enbox/auth] connectHeadless() requires a password. ' +
415
+ 'Provide one via options.password, a passwordProvider, or the AuthManager default.'
416
+ );
417
+ }
418
+
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', {});
426
+
427
+ // Find the active identity.
428
+ const identities = await this._userAgent.identity.list();
429
+ if (identities.length === 0) {
430
+ throw new Error('[@enbox/auth] No identities found in vault.');
431
+ }
432
+
433
+ // Prefer the previously-active identity, fall back to first.
434
+ const savedDid = await this._storage.get(STORAGE_KEYS.ACTIVE_IDENTITY);
435
+ const identity = (savedDid
436
+ ? identities.find(id => id.did.uri === savedDid || id.metadata.connectedDid === savedDid)
437
+ : undefined
438
+ ) ?? identities[0];
439
+
440
+ const connectedDid = identity.metadata.connectedDid ?? identity.did.uri;
441
+ const delegateDid = identity.metadata.connectedDid ? identity.did.uri : undefined;
442
+
443
+ const identityInfo: IdentityInfo = {
444
+ didUri : connectedDid,
445
+ name : identity.metadata.name,
446
+ connectedDid : identity.metadata.connectedDid,
447
+ };
448
+
449
+ // No sync, no registration, no session persistence markers.
450
+ this._session = new AuthSession({
451
+ agent : this._userAgent,
452
+ did : connectedDid,
453
+ delegateDid,
454
+ identity : identityInfo,
455
+ });
456
+
457
+ this._setState('connected');
458
+
459
+ return this._session;
460
+ }
461
+
325
462
  // ─── Session management ────────────────────────────────────────
326
463
 
327
464
  /** The current active session, or `undefined` if not connected. */
@@ -329,6 +466,43 @@ export class AuthManager {
329
466
  return this._session;
330
467
  }
331
468
 
469
+ /**
470
+ * Lock the auth manager.
471
+ *
472
+ * Stops sync, clears the active session, and locks the underlying vault
473
+ * so the password must be provided again to resume. Session storage
474
+ * markers are preserved so {@link restoreSession} can reconnect after
475
+ * the vault is unlocked again.
476
+ *
477
+ * After locking, the state transitions to `'locked'`.
478
+ *
479
+ * @param options - Optional lock configuration.
480
+ * @param options.timeout - Milliseconds to wait for sync to stop. Default: `2000`.
481
+ */
482
+ async lock(options: { timeout?: number } = {}): Promise<void> {
483
+ const { timeout = 2000 } = options;
484
+ const did = this._session?.did;
485
+
486
+ // 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
+ }
490
+
491
+ // 2. Clear the session (but keep storage markers for restore).
492
+ this._session = undefined;
493
+
494
+ // 3. Lock the vault (also emits 'vault-locked').
495
+ await this._vault.lock();
496
+
497
+ // 4. Transition state.
498
+ this._setState('locked');
499
+
500
+ // 5. Emit session-end if there was an active session.
501
+ if (did) {
502
+ this._emitter.emit('session-end', { did });
503
+ }
504
+ }
505
+
332
506
  /**
333
507
  * Disconnect the current session.
334
508
  *
@@ -385,6 +559,86 @@ export class AuthManager {
385
559
  }
386
560
  }
387
561
 
562
+ /**
563
+ * Gracefully shut down the auth manager, releasing all resources.
564
+ *
565
+ * This goes beyond {@link disconnect} or {@link lock}: it stops sync,
566
+ * clears the active session, locks the vault, and **closes** the
567
+ * underlying storage handles (e.g. LevelDB) so the process can exit
568
+ * without dangling timers or open file descriptors.
569
+ *
570
+ * After calling `shutdown()`, the `AuthManager` instance should not be
571
+ * reused — create a new one via {@link AuthManager.create} if needed.
572
+ *
573
+ * Idempotent: calling `shutdown()` more than once is safe.
574
+ *
575
+ * @param options - Optional shutdown configuration.
576
+ * @param options.timeout - Milliseconds to wait for sync to stop. Default: `2000`.
577
+ *
578
+ * @example
579
+ * ```ts
580
+ * const session = await auth.connectHeadless({ password });
581
+ * // ... perform work ...
582
+ * await auth.shutdown(); // clean exit, no process.exit() needed
583
+ * ```
584
+ */
585
+ async shutdown(options: ShutdownOptions = {}): Promise<void> {
586
+ if (this._isShutDown) {
587
+ return;
588
+ }
589
+
590
+ const { timeout = 2000 } = options;
591
+ const did = this._session?.did;
592
+
593
+ // 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
+ }
601
+ }
602
+
603
+ // 2. Clear the active session.
604
+ this._session = undefined;
605
+
606
+ // 3. Lock the vault (emits 'vault-locked').
607
+ try {
608
+ await this._vault.lock();
609
+ } catch {
610
+ // Vault may already be locked or uninitialised — safe to ignore.
611
+ }
612
+
613
+ // 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
+ }
621
+ }
622
+
623
+ // 5. Close the storage adapter (e.g. LevelDB session store).
624
+ if (typeof this._storage.close === 'function') {
625
+ try {
626
+ await this._storage.close();
627
+ } catch {
628
+ // Best-effort.
629
+ }
630
+ }
631
+
632
+ // 6. Mark as shut down and transition state.
633
+ this._isShutDown = true;
634
+ this._setState('locked');
635
+
636
+ // 7. Emit session-end if there was an active session.
637
+ if (did) {
638
+ this._emitter.emit('session-end', { did });
639
+ }
640
+ }
641
+
388
642
  // ─── Multi-identity ────────────────────────────────────────────
389
643
 
390
644
  /**
@@ -432,9 +686,19 @@ export class AuthManager {
432
686
  connectedDid : identity.metadata.connectedDid,
433
687
  };
434
688
 
435
- // Restart sync.
689
+ // Register the identity for sync and restart sync.
436
690
  const sync = this._defaultSync;
437
691
  if (sync !== 'off') {
692
+ // Register the identity for sync (idempotent — ignores "already registered" errors).
693
+ try {
694
+ await this._userAgent.sync.registerIdentity({
695
+ did : connectedDid,
696
+ options : { delegateDid, protocols: [] },
697
+ });
698
+ } catch {
699
+ // Already registered — safe to ignore.
700
+ }
701
+
438
702
  const syncMode = sync === undefined ? 'live' : 'poll';
439
703
  const syncInterval = sync ?? (syncMode === 'live' ? '5m' : '2m');
440
704
  this._userAgent.sync.startSync({ mode: syncMode, interval: syncInterval })
@@ -549,6 +813,17 @@ export class AuthManager {
549
813
  return this._userAgent;
550
814
  }
551
815
 
816
+ /**
817
+ * The local DWN server endpoint discovered during `create()`, if any.
818
+ *
819
+ * When set, the agent is operating in remote mode (no in-process DWN).
820
+ * This property is available immediately after `create()` returns,
821
+ * before any event listeners are attached.
822
+ */
823
+ get localDwnEndpoint(): string | undefined {
824
+ return this._localDwnEndpoint;
825
+ }
826
+
552
827
  // ─── Private helpers ───────────────────────────────────────────
553
828
 
554
829
  private _setState(state: AuthState): void {