@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
@@ -7,32 +7,28 @@
7
7
  *
8
8
  * ## Discovery channels (browser, highest to lowest priority)
9
9
  *
10
- * 1. **URL fragment payload** — A `dwn://register` redirect just landed
10
+ * 1. **URL fragment payload** — A `dwn://connect` redirect just landed
11
11
  * on the page with the endpoint in `#`. Highest priority because it's
12
12
  * fresh and explicit.
13
13
  * 2. **Persisted endpoint** (localStorage) — A previously discovered
14
14
  * endpoint restored and re-validated via `GET /info`.
15
- * 3. **Agent-level discovery** (transparent, runs on every `sendRequest`)
16
- * — `~/.enbox/dwn.json` discovery file (Node/Bun only; skipped in
17
- * browsers) and sequential port probing on `127.0.0.1:{3000,55500–55509}`.
18
- * This channel works even if the browser-specific functions here
19
- * return `false`.
20
15
  *
21
16
  * ## Discovery channels (CLI / native, all transparent)
22
17
  *
23
- * In Node/Bun environments, all discovery happens automatically inside
18
+ * In Node/Bun environments, the agent's `LocalDwnDiscovery` reads the
19
+ * `~/.enbox/dwn.json` discovery file automatically inside
24
20
  * `AgentDwnApi.getLocalDwnEndpoint()`. The browser-specific functions
25
21
  * in this module (`checkUrlForDwnDiscoveryPayload`, `requestLocalDwnDiscovery`)
26
- * are not needed the agent reads `~/.enbox/dwn.json` and probes ports
27
- * on its own.
22
+ * are not needed in those environments.
28
23
  *
29
- * @see https://github.com/enboxorg/enbox/issues/589
24
+ * @see https://github.com/enboxorg/enbox/issues/677
30
25
  * @module
31
26
  */
32
27
 
33
28
  import type { EnboxUserAgent } from '@enbox/agent';
34
29
 
35
- import { buildDwnRegisterUrl, readDwnDiscoveryPayloadFromUrl } from '@enbox/agent';
30
+ import { EnboxRpcClient } from '@enbox/dwn-clients';
31
+ import { buildDwnConnectUrl, localDwnServerName, normalizeBaseUrl, readDwnDiscoveryPayloadFromUrl } from '@enbox/agent';
36
32
 
37
33
  import type { AuthEventEmitter } from '../events.js';
38
34
  import { STORAGE_KEYS } from '../types.js';
@@ -42,7 +38,7 @@ import type { StorageAdapter } from '../types.js';
42
38
  * Check the current page URL for a `DwnDiscoveryPayload` in the fragment.
43
39
  *
44
40
  * This is called once at the start of a connection flow to detect whether
45
- * the user was just redirected back from a `dwn://register` handler. If a
41
+ * the user was just redirected back from a `dwn://connect` handler. If a
46
42
  * valid payload is found, the endpoint is persisted and the fragment is
47
43
  * cleared to prevent double-reads.
48
44
  *
@@ -69,6 +65,83 @@ export function checkUrlForDwnDiscoveryPayload(): string | undefined {
69
65
  return payload.endpoint;
70
66
  }
71
67
 
68
+ // ─── Standalone (pre-agent) discovery ───────────────────────────
69
+
70
+ /**
71
+ * Validate a local DWN endpoint by calling `GET /info` and checking
72
+ * that the server identifies itself as `@enbox/dwn-server`.
73
+ *
74
+ * This function has **zero** agent or vault dependencies — it only uses
75
+ * the network. It is safe to call before the agent exists.
76
+ *
77
+ * @param endpoint - The candidate endpoint URL.
78
+ * @returns The normalised endpoint if valid, `undefined` otherwise.
79
+ */
80
+ async function validateEndpointStandalone(endpoint: string): Promise<string | undefined> {
81
+ const normalized = normalizeBaseUrl(endpoint);
82
+ try {
83
+ const rpc = new EnboxRpcClient();
84
+ const serverInfo = await rpc.getServerInfo(normalized);
85
+ if (serverInfo.server === localDwnServerName) {
86
+ return normalized;
87
+ }
88
+ } catch {
89
+ // Server not reachable or not ours.
90
+ }
91
+ return undefined;
92
+ }
93
+
94
+ /**
95
+ * Run local DWN discovery **before the agent exists**.
96
+ *
97
+ * This is the standalone counterpart of {@link applyLocalDwnDiscovery} and
98
+ * is designed to be called in `AuthManager.create()`, before
99
+ * `EnboxUserAgent.create()`, so the agent creation can decide whether to
100
+ * spin up an in-process DWN or operate in remote mode.
101
+ *
102
+ * Discovery channels (highest → lowest priority):
103
+ * 1. **URL fragment payload** — A `dwn://connect` redirect just landed.
104
+ * 2. **Persisted endpoint** (localStorage) — A previously discovered
105
+ * endpoint, re-validated via `GET /info`.
106
+ *
107
+ * When a valid endpoint is found it is persisted to storage. When a
108
+ * previously-persisted endpoint is stale, it is removed.
109
+ *
110
+ * @param storage - The auth storage adapter (for reading/writing the
111
+ * cached endpoint).
112
+ * @returns The validated endpoint URL, or `undefined` if no local DWN
113
+ * server is available.
114
+ */
115
+ export async function discoverLocalDwn(
116
+ storage: StorageAdapter,
117
+ ): Promise<string | undefined> {
118
+ // Channel 1: Fresh redirect payload in the URL fragment.
119
+ const freshEndpoint = checkUrlForDwnDiscoveryPayload();
120
+ if (freshEndpoint) {
121
+ const validated = await validateEndpointStandalone(freshEndpoint);
122
+ if (validated) {
123
+ await persistLocalDwnEndpoint(storage, validated);
124
+ return validated;
125
+ }
126
+ // Payload was in the URL but the server is unreachable — fall through.
127
+ }
128
+
129
+ // Channel 2: Persisted endpoint from a previous session.
130
+ const cached = await storage.get(STORAGE_KEYS.LOCAL_DWN_ENDPOINT);
131
+ if (cached) {
132
+ const validated = await validateEndpointStandalone(cached);
133
+ if (validated) {
134
+ return validated;
135
+ }
136
+ // Stale — server no longer running.
137
+ await clearLocalDwnEndpoint(storage);
138
+ }
139
+
140
+ return undefined;
141
+ }
142
+
143
+ // ─── Storage helpers ────────────────────────────────────────────
144
+
72
145
  /**
73
146
  * Persist a discovered local DWN endpoint in auth storage.
74
147
  *
@@ -130,25 +203,18 @@ export async function restoreLocalDwnEndpoint(
130
203
  * Run the full local DWN discovery sequence for a browser connection flow.
131
204
  *
132
205
  * This function handles the **receiving** side of local DWN discovery in
133
- * the browser. It does NOT trigger the `dwn://register` redirect — use
206
+ * the browser. It does NOT trigger the `dwn://connect` redirect — use
134
207
  * {@link requestLocalDwnDiscovery} for that.
135
208
  *
136
209
  * The discovery channels, from highest to lowest priority:
137
210
  *
138
- * 1. **URL fragment payload** — A `dwn://register` redirect just landed on
211
+ * 1. **URL fragment payload** — A `dwn://connect` redirect just landed on
139
212
  * this page with the DWN endpoint in `#`. This is the highest-priority
140
213
  * signal because it's fresh and explicit.
141
214
  *
142
215
  * 2. **Persisted endpoint** (localStorage) — A previously discovered
143
216
  * endpoint is restored and re-validated via `GET /info`.
144
217
  *
145
- * 3. **Agent-level discovery** (transparent) — Even if this function
146
- * returns `false`, the agent's `LocalDwnDiscovery` will independently
147
- * try the discovery file (`~/.enbox/dwn.json`) and port probing on
148
- * every `sendRequest()` call. Those channels are not available in
149
- * browsers (no filesystem access, CORS may block probes), but they
150
- * work transparently in Node/Bun CLI environments.
151
- *
152
218
  * When an `emitter` is provided, this function emits:
153
219
  * - `'local-dwn-available'` with the endpoint when discovery succeeds.
154
220
  * - `'local-dwn-unavailable'` when no local DWN could be reached.
@@ -191,37 +257,31 @@ export async function applyLocalDwnDiscovery(
191
257
  return restored;
192
258
  }
193
259
 
194
- // ─── dwn://register trigger ─────────────────────────────────────
260
+ // ─── dwn://connect trigger ──────────────────────────────────────
195
261
 
196
262
  /**
197
- * Initiate the `dwn://register` flow by opening the register URL.
263
+ * Initiate the `dwn://connect` flow by opening the connect URL.
198
264
  *
199
- * This asks the operating system to route `dwn://register?callback=<url>`
265
+ * This asks the operating system to route `dwn://connect?callback=<url>`
200
266
  * to the registered handler (electrobun-dwn), which will redirect the
201
267
  * user's browser back to `callbackUrl` with the local DWN endpoint
202
268
  * encoded in the URL fragment.
203
269
  *
204
- * **Important:** There is no reliable cross-browser API to detect whether
205
- * a `dwn://` handler is installed. If no handler is registered, this call
206
- * will silently fail or show an OS-level error dialog. Use
207
- * {@link probeLocalDwn} first to check if a local DWN is already
208
- * reachable via port probing — if it is, you can skip the register flow
209
- * entirely and call {@link applyLocalDwnDiscovery} instead.
270
+ * **Note:** There is no reliable cross-browser API to detect whether a
271
+ * `dwn://` handler is installed. If no handler is registered, this call
272
+ * will silently fail or show an OS-level error dialog.
210
273
  *
211
274
  * @param callbackUrl - The URL to redirect back to. Defaults to the
212
275
  * current page URL (without its fragment) if running in a browser.
213
- * @returns `true` if the register URL was opened, `false` if no
276
+ * @returns `true` if the connect URL was opened, `false` if no
214
277
  * callback URL could be determined (e.g. no `globalThis.location`).
215
278
  *
216
279
  * @example
217
280
  * ```ts
218
- * // Check if local DWN is already available via direct probe.
219
- * const alreadyAvailable = await probeLocalDwn();
220
- * if (!alreadyAvailable) {
221
- * // No local DWN found trigger the dwn://register flow.
222
- * requestLocalDwnDiscovery();
223
- * // The page will reload with the endpoint in the URL fragment.
224
- * }
281
+ * // Trigger the dwn://connect flow to discover a local DWN.
282
+ * requestLocalDwnDiscovery();
283
+ * // The page will reload with the endpoint in the URL fragment.
284
+ * // On the next connect/restore, applyLocalDwnDiscovery() reads it.
225
285
  * ```
226
286
  */
227
287
  export function requestLocalDwnDiscovery(callbackUrl?: string): boolean {
@@ -230,7 +290,7 @@ export function requestLocalDwnDiscovery(callbackUrl?: string): boolean {
230
290
  return false;
231
291
  }
232
292
 
233
- const registerUrl = buildDwnRegisterUrl(resolvedCallback);
293
+ const registerUrl = buildDwnConnectUrl(resolvedCallback);
234
294
 
235
295
  // Open the dwn:// URL. Use window.open() rather than location.href
236
296
  // assignment to avoid navigating away from the current page if the
@@ -249,46 +309,6 @@ export function requestLocalDwnDiscovery(callbackUrl?: string): boolean {
249
309
  return false;
250
310
  }
251
311
 
252
- /**
253
- * Probe whether a local DWN server is reachable via direct HTTP fetch.
254
- *
255
- * Attempts `GET http://127.0.0.1:{port}/info` on the well-known port
256
- * candidates and returns the endpoint URL of the first server that
257
- * responds with a valid `@enbox/dwn-server` identity.
258
- *
259
- * This is useful in browsers to check if a local DWN is available
260
- * *before* triggering the `dwn://register` redirect flow — if the
261
- * server is already reachable (CORS permitting), the redirect is
262
- * unnecessary.
263
- *
264
- * @returns The local DWN endpoint URL, or `undefined` if no server
265
- * was found. Returns `undefined` (rather than throwing) on CORS
266
- * errors or network failures.
267
- */
268
- export async function probeLocalDwn(): Promise<string | undefined> {
269
- // Import port candidates from @enbox/agent. Using a dynamic import
270
- // here keeps the function self-contained and avoids circular deps.
271
- const { localDwnPortCandidates, localDwnHostCandidates } = await import('@enbox/agent');
272
-
273
- for (const port of localDwnPortCandidates) {
274
- for (const host of localDwnHostCandidates) {
275
- const endpoint = `http://${host}:${port}`;
276
- try {
277
- const response = await fetch(`${endpoint}/info`, { signal: AbortSignal.timeout(2_000) });
278
- if (!response.ok) { continue; }
279
-
280
- const serverInfo = await response.json() as { server?: string };
281
- if (serverInfo?.server === '@enbox/dwn-server') {
282
- return endpoint;
283
- }
284
- } catch {
285
- // Network error, CORS block, or timeout — try next candidate.
286
- }
287
- }
288
- }
289
- return undefined;
290
- }
291
-
292
312
  // ─── Internal helpers ───────────────────────────────────────────
293
313
 
294
314
  /** Return the current page URL without the fragment, or `undefined`. */
@@ -15,9 +15,12 @@ import type { EnboxUserAgent } from '@enbox/agent';
15
15
 
16
16
  import { DwnRegistrar } from '@enbox/dwn-clients';
17
17
 
18
+ import { STORAGE_KEYS } from '../types.js';
19
+
18
20
  import type {
19
21
  RegistrationOptions,
20
22
  RegistrationTokenData,
23
+ StorageAdapter,
21
24
  } from '../types.js';
22
25
 
23
26
  /** @internal */
@@ -33,6 +36,12 @@ export interface RegistrationContext {
33
36
 
34
37
  /** The connected DID URI (the identity's DID). */
35
38
  connectedDid: string;
39
+
40
+ /**
41
+ * Storage adapter for automatic token persistence.
42
+ * Only used when `registration.persistTokens` is `true`.
43
+ */
44
+ storage?: StorageAdapter;
36
45
  }
37
46
 
38
47
  /**
@@ -51,11 +60,19 @@ export async function registerWithDwnEndpoints(
51
60
  ctx: RegistrationContext,
52
61
  registration: RegistrationOptions,
53
62
  ): Promise<void> {
54
- const { userAgent, dwnEndpoints, agentDid, connectedDid } = ctx;
63
+ const { userAgent, dwnEndpoints, agentDid, connectedDid, storage } = ctx;
64
+
65
+ // Load initial tokens: when persistTokens is enabled, load from storage
66
+ // (ignoring any explicit registrationTokens). Otherwise use the explicit map.
67
+ let seedTokens: Record<string, RegistrationTokenData> = {};
55
68
 
56
- const updatedTokens: Record<string, RegistrationTokenData> = {
57
- ...(registration.registrationTokens ?? {}),
58
- };
69
+ if (registration.persistTokens && storage) {
70
+ seedTokens = await loadTokensFromStorage(storage);
71
+ } else {
72
+ seedTokens = registration.registrationTokens ?? {};
73
+ }
74
+
75
+ const updatedTokens: Record<string, RegistrationTokenData> = { ...seedTokens };
59
76
 
60
77
  try {
61
78
  for (const dwnEndpoint of dwnEndpoints) {
@@ -145,7 +162,12 @@ export async function registerWithDwnEndpoints(
145
162
  }
146
163
  }
147
164
 
148
- // Notify app of updated tokens for persistence.
165
+ // Persist tokens to storage when auto-persistence is enabled.
166
+ if (registration.persistTokens && storage) {
167
+ await saveTokensToStorage(storage, updatedTokens);
168
+ }
169
+
170
+ // Notify app of updated tokens (always, even when auto-persisting).
149
171
  if (registration.onRegistrationTokens) {
150
172
  registration.onRegistrationTokens(updatedTokens);
151
173
  }
@@ -155,3 +177,36 @@ export async function registerWithDwnEndpoints(
155
177
  registration.onFailure(error);
156
178
  }
157
179
  }
180
+
181
+ // ─── Storage helpers ──────────────────────────────────────────────
182
+
183
+ /**
184
+ * Load registration tokens from a `StorageAdapter`.
185
+ *
186
+ * Returns an empty record if no tokens are stored or the stored value
187
+ * is corrupt (best-effort — never throws).
188
+ *
189
+ * @internal
190
+ */
191
+ export async function loadTokensFromStorage(
192
+ storage: StorageAdapter,
193
+ ): Promise<Record<string, RegistrationTokenData>> {
194
+ try {
195
+ const raw = await storage.get(STORAGE_KEYS.REGISTRATION_TOKENS);
196
+ if (!raw) { return {}; }
197
+ return JSON.parse(raw) as Record<string, RegistrationTokenData>;
198
+ } catch {
199
+ return {};
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Save registration tokens to a `StorageAdapter`.
205
+ * @internal
206
+ */
207
+ export async function saveTokensToStorage(
208
+ storage: StorageAdapter,
209
+ tokens: Record<string, RegistrationTokenData>,
210
+ ): Promise<void> {
211
+ await storage.set(STORAGE_KEYS.REGISTRATION_TOKENS, JSON.stringify(tokens));
212
+ }
@@ -105,6 +105,7 @@ export async function importFromPhrase(
105
105
  dwnEndpoints,
106
106
  agentDid : userAgent.agentDid.uri,
107
107
  connectedDid,
108
+ storage : storage,
108
109
  },
109
110
  ctx.registration,
110
111
  );
@@ -174,6 +175,7 @@ export async function importFromPortable(
174
175
  dwnEndpoints,
175
176
  agentDid : userAgent.agentDid.uri,
176
177
  connectedDid,
178
+ storage : storage,
177
179
  },
178
180
  ctx.registration,
179
181
  );
@@ -9,6 +9,7 @@
9
9
  import type { EnboxUserAgent } from '@enbox/agent';
10
10
 
11
11
  import type { AuthEventEmitter } from '../events.js';
12
+ import type { PasswordProvider } from '../password-provider.js';
12
13
  import type { LocalConnectOptions, RegistrationOptions, StorageAdapter, SyncOption } from '../types.js';
13
14
 
14
15
  import { applyLocalDwnDiscovery } from './dwn-discovery.js';
@@ -22,6 +23,7 @@ export interface LocalConnectContext {
22
23
  emitter: AuthEventEmitter;
23
24
  storage: StorageAdapter;
24
25
  defaultPassword?: string;
26
+ passwordProvider?: PasswordProvider;
25
27
  defaultSync?: SyncOption;
26
28
  defaultDwnEndpoints?: string[];
27
29
  registration?: RegistrationOptions;
@@ -39,7 +41,22 @@ export async function localConnect(
39
41
  ): Promise<AuthSession> {
40
42
  const { userAgent, emitter, storage } = ctx;
41
43
 
42
- const password = options.password ?? ctx.defaultPassword ?? INSECURE_DEFAULT_PASSWORD;
44
+ // Resolve password: explicit option provider manager default → insecure fallback.
45
+ const isFirstLaunch = await userAgent.firstLaunch();
46
+ let password = options.password ?? ctx.defaultPassword;
47
+
48
+ if (!password && ctx.passwordProvider) {
49
+ try {
50
+ password = await ctx.passwordProvider.getPassword({
51
+ reason: isFirstLaunch ? 'create' : 'unlock',
52
+ });
53
+ } catch {
54
+ // Provider failed — fall through to insecure default.
55
+ }
56
+ }
57
+
58
+ password ??= INSECURE_DEFAULT_PASSWORD;
59
+
43
60
  const sync = options.sync ?? ctx.defaultSync;
44
61
  const dwnEndpoints = options.dwnEndpoints ?? ctx.defaultDwnEndpoints ?? ['https://enbox-dwn.fly.dev'];
45
62
 
@@ -55,7 +72,7 @@ export async function localConnect(
55
72
  let recoveryPhrase: string | undefined;
56
73
 
57
74
  // Initialize vault on first launch.
58
- if (await userAgent.firstLaunch()) {
75
+ if (isFirstLaunch) {
59
76
  recoveryPhrase = await userAgent.initialize({
60
77
  password,
61
78
  recoveryPhrase: options.recoveryPhrase,
@@ -68,7 +85,10 @@ export async function localConnect(
68
85
  emitter.emit('vault-unlocked', {});
69
86
 
70
87
  // Apply local DWN discovery (browser redirect payload or persisted endpoint).
71
- await applyLocalDwnDiscovery(userAgent, storage, emitter);
88
+ // In remote mode, discovery already ran before agent creation — skip.
89
+ if (!userAgent.dwn.isRemoteMode) {
90
+ await applyLocalDwnDiscovery(userAgent, storage, emitter);
91
+ }
72
92
 
73
93
  // Find or create the user identity.
74
94
  const identities = await userAgent.identity.list();
@@ -117,6 +137,7 @@ export async function localConnect(
117
137
  dwnEndpoints,
118
138
  agentDid : userAgent.agentDid.uri,
119
139
  connectedDid,
140
+ storage : storage,
120
141
  },
121
142
  ctx.registration,
122
143
  );
@@ -9,6 +9,7 @@
9
9
  import type { EnboxUserAgent } from '@enbox/agent';
10
10
 
11
11
  import type { AuthEventEmitter } from '../events.js';
12
+ import type { PasswordProvider } from '../password-provider.js';
12
13
  import type { RestoreSessionOptions, StorageAdapter, SyncOption } from '../types.js';
13
14
 
14
15
  import { applyLocalDwnDiscovery } from './dwn-discovery.js';
@@ -21,6 +22,7 @@ export interface SessionRestoreContext {
21
22
  emitter: AuthEventEmitter;
22
23
  storage: StorageAdapter;
23
24
  defaultPassword?: string;
25
+ passwordProvider?: PasswordProvider;
24
26
  defaultSync?: SyncOption;
25
27
  }
26
28
 
@@ -42,7 +44,22 @@ export async function restoreSession(
42
44
  return undefined;
43
45
  }
44
46
 
45
- const password = options.password ?? ctx.defaultPassword ?? INSECURE_DEFAULT_PASSWORD;
47
+ // Resolve password: explicit option callback provider → manager default → insecure fallback.
48
+ let password = options.password ?? ctx.defaultPassword;
49
+
50
+ if (!password && options.onPasswordRequired) {
51
+ password = await options.onPasswordRequired();
52
+ }
53
+
54
+ if (!password && ctx.passwordProvider) {
55
+ try {
56
+ password = await ctx.passwordProvider.getPassword({ reason: 'unlock' });
57
+ } catch {
58
+ // Provider failed — fall through to insecure default.
59
+ }
60
+ }
61
+
62
+ password ??= INSECURE_DEFAULT_PASSWORD;
46
63
 
47
64
  // Warn if using insecure default.
48
65
  if (password === INSECURE_DEFAULT_PASSWORD) {
@@ -64,7 +81,10 @@ export async function restoreSession(
64
81
  emitter.emit('vault-unlocked', {});
65
82
 
66
83
  // Apply local DWN discovery (browser redirect payload or persisted endpoint).
67
- await applyLocalDwnDiscovery(userAgent, storage, emitter);
84
+ // In remote mode, discovery already ran before agent creation — skip.
85
+ if (!userAgent.dwn.isRemoteMode) {
86
+ await applyLocalDwnDiscovery(userAgent, storage, emitter);
87
+ }
68
88
 
69
89
  // Determine which identity to reconnect.
70
90
  const activeIdentityDid = await storage.get(STORAGE_KEYS.ACTIVE_IDENTITY);
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Wallet connect (OIDC/QR) flow.
2
+ * Wallet connect (Enbox Connect relay) flow.
3
3
  *
4
- * Connects to an external wallet via the WalletConnect relay protocol,
4
+ * Connects to an external wallet via the Enbox Connect relay protocol,
5
5
  * importing a delegated DID with permission grants.
6
6
  * This replaces the "Mode B/C" paths in Enbox.connect().
7
7
  * @module
@@ -99,12 +99,12 @@ export async function walletConnect(
99
99
  );
100
100
  }
101
101
 
102
- // Run the full OIDC wallet connect flow.
102
+ // Run the Enbox Connect relay flow.
103
103
  // permissionRequests are already agent-level ConnectPermissionRequest objects.
104
104
  const result = await WalletConnect.initClient({
105
105
  displayName : options.displayName,
106
106
  connectServerUrl : options.connectServerUrl,
107
- walletUri : options.walletUri ?? 'web5://connect',
107
+ walletUri : options.walletUri ?? 'enbox://connect',
108
108
  permissionRequests : options.permissionRequests,
109
109
  onWalletUriReady : options.onWalletUriReady,
110
110
  validatePin : options.validatePin,
@@ -147,6 +147,7 @@ export async function walletConnect(
147
147
  dwnEndpoints,
148
148
  agentDid : userAgent.agentDid.uri,
149
149
  connectedDid,
150
+ storage : storage,
150
151
  },
151
152
  ctx.registration,
152
153
  );
package/src/index.ts CHANGED
@@ -40,6 +40,10 @@ export { AuthSession } from './identity-session.js';
40
40
  export { VaultManager } from './vault/vault-manager.js';
41
41
  export { AuthEventEmitter } from './events.js';
42
42
 
43
+ // Password providers
44
+ export { PasswordProvider } from './password-provider.js';
45
+ export type { PasswordContext } from './password-provider.js';
46
+
43
47
  // Re-export agent classes so consumers can construct custom agents/vaults
44
48
  // without a direct @enbox/agent dependency.
45
49
  export { EnboxUserAgent, HdIdentityVault } from '@enbox/agent';
@@ -47,13 +51,16 @@ export { EnboxUserAgent, HdIdentityVault } from '@enbox/agent';
47
51
  // Wallet-connect helpers
48
52
  export { processConnectedGrants } from './flows/wallet-connect.js';
49
53
 
54
+ // Registration token storage helpers
55
+ export { loadTokensFromStorage, saveTokensToStorage } from './flows/dwn-registration.js';
56
+
50
57
  // Local DWN discovery (browser dwn:// protocol integration)
51
58
  export {
52
59
  applyLocalDwnDiscovery,
53
60
  checkUrlForDwnDiscoveryPayload,
54
61
  clearLocalDwnEndpoint,
62
+ discoverLocalDwn,
55
63
  persistLocalDwnEndpoint,
56
- probeLocalDwn,
57
64
  requestLocalDwnDiscovery,
58
65
  restoreLocalDwnEndpoint,
59
66
  } from './flows/dwn-discovery.js';
@@ -71,6 +78,7 @@ export type {
71
78
  AuthState,
72
79
  ConnectPermissionRequest,
73
80
  DisconnectOptions,
81
+ HeadlessConnectOptions,
74
82
  IdentityInfo,
75
83
  IdentityVaultBackup,
76
84
  ImportFromPhraseOptions,
@@ -83,6 +91,7 @@ export type {
83
91
  RegistrationOptions,
84
92
  RegistrationTokenData,
85
93
  RestoreSessionOptions,
94
+ ShutdownOptions,
86
95
  StorageAdapter,
87
96
  SyncOption,
88
97
  WalletConnectOptions,