@enbox/auth 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/auth-manager.js +244 -121
- package/dist/esm/auth-manager.js.map +1 -1
- package/dist/esm/connect/import.js +131 -0
- package/dist/esm/connect/import.js.map +1 -0
- package/dist/esm/connect/lifecycle.js +235 -0
- package/dist/esm/connect/lifecycle.js.map +1 -0
- package/dist/esm/connect/local.js +91 -0
- package/dist/esm/connect/local.js.map +1 -0
- package/dist/esm/{flows/session-restore.js → connect/restore.js} +39 -50
- package/dist/esm/connect/restore.js.map +1 -0
- package/dist/esm/{flows/wallet-connect.js → connect/wallet.js} +33 -39
- package/dist/esm/connect/wallet.js.map +1 -0
- package/dist/esm/{flows/dwn-discovery.js → discovery.js} +98 -83
- package/dist/esm/discovery.js.map +1 -0
- package/dist/esm/index.js +7 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/password-provider.js +319 -0
- package/dist/esm/password-provider.js.map +1 -0
- package/dist/esm/{flows/dwn-registration.js → registration.js} +50 -4
- package/dist/esm/registration.js.map +1 -0
- package/dist/esm/types.js +11 -1
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/wallet-connect-client.js +188 -0
- package/dist/esm/wallet-connect-client.js.map +1 -0
- package/dist/types/auth-manager.d.ts +86 -7
- package/dist/types/auth-manager.d.ts.map +1 -1
- package/dist/types/connect/import.d.ts +25 -0
- package/dist/types/connect/import.d.ts.map +1 -0
- package/dist/types/connect/lifecycle.d.ts +152 -0
- package/dist/types/connect/lifecycle.d.ts.map +1 -0
- package/dist/types/connect/local.d.ts +18 -0
- package/dist/types/connect/local.d.ts.map +1 -0
- package/dist/types/connect/restore.d.ts +18 -0
- package/dist/types/connect/restore.d.ts.map +1 -0
- package/dist/types/{flows/wallet-connect.d.ts → connect/wallet.d.ts} +7 -16
- package/dist/types/connect/wallet.d.ts.map +1 -0
- package/dist/types/{flows/dwn-discovery.d.ts → discovery.d.ts} +43 -56
- package/dist/types/discovery.d.ts.map +1 -0
- package/dist/types/index.d.ts +8 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/password-provider.d.ts +194 -0
- package/dist/types/password-provider.d.ts.map +1 -0
- package/dist/types/{flows/dwn-registration.d.ts → registration.d.ts} +21 -2
- package/dist/types/registration.d.ts.map +1 -0
- package/dist/types/types.d.ts +92 -4
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/wallet-connect-client.d.ts +89 -0
- package/dist/types/wallet-connect-client.d.ts.map +1 -0
- package/package.json +15 -12
- package/src/auth-manager.ts +279 -145
- package/src/connect/import.ts +148 -0
- package/src/connect/lifecycle.ts +321 -0
- package/src/connect/local.ts +101 -0
- package/src/connect/restore.ts +117 -0
- package/src/{flows/wallet-connect.ts → connect/wallet.ts} +42 -58
- package/src/{flows/dwn-discovery.ts → discovery.ts} +103 -82
- package/src/index.ts +14 -4
- package/src/password-provider.ts +383 -0
- package/src/{flows/dwn-registration.ts → registration.ts} +61 -6
- package/src/types.ts +100 -4
- package/src/wallet-connect-client.ts +278 -0
- package/dist/esm/flows/dwn-discovery.js.map +0 -1
- package/dist/esm/flows/dwn-registration.js.map +0 -1
- package/dist/esm/flows/import-identity.js +0 -175
- package/dist/esm/flows/import-identity.js.map +0 -1
- package/dist/esm/flows/local-connect.js +0 -141
- package/dist/esm/flows/local-connect.js.map +0 -1
- package/dist/esm/flows/session-restore.js.map +0 -1
- package/dist/esm/flows/wallet-connect.js.map +0 -1
- package/dist/esm/vault/vault-manager.js +0 -95
- package/dist/esm/vault/vault-manager.js.map +0 -1
- package/dist/types/flows/dwn-discovery.d.ts.map +0 -1
- package/dist/types/flows/dwn-registration.d.ts.map +0 -1
- package/dist/types/flows/import-identity.d.ts +0 -35
- package/dist/types/flows/import-identity.d.ts.map +0 -1
- package/dist/types/flows/local-connect.d.ts +0 -29
- package/dist/types/flows/local-connect.d.ts.map +0 -1
- package/dist/types/flows/session-restore.d.ts +0 -27
- package/dist/types/flows/session-restore.d.ts.map +0 -1
- package/dist/types/flows/wallet-connect.d.ts.map +0 -1
- package/dist/types/vault/vault-manager.d.ts +0 -57
- package/dist/types/vault/vault-manager.d.ts.map +0 -1
- package/src/flows/import-identity.ts +0 -217
- package/src/flows/local-connect.ts +0 -171
- package/src/flows/session-restore.ts +0 -142
- package/src/vault/vault-manager.ts +0 -89
package/src/auth-manager.ts
CHANGED
|
@@ -6,34 +6,41 @@
|
|
|
6
6
|
* @module
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import type { BearerIdentity, PortableIdentity } from '@enbox/agent';
|
|
9
|
+
import type { BearerIdentity, HdIdentityVault, PortableIdentity } from '@enbox/agent';
|
|
11
10
|
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { createDefaultStorage } from './storage/storage.js';
|
|
15
|
-
import { localConnect } from './flows/local-connect.js';
|
|
16
|
-
import { restoreSession } from './flows/session-restore.js';
|
|
17
|
-
import { STORAGE_KEYS } from './types.js';
|
|
18
|
-
import { VaultManager } from './vault/vault-manager.js';
|
|
19
|
-
import { walletConnect } from './flows/wallet-connect.js';
|
|
11
|
+
import type { FlowContext } from './connect/lifecycle.js';
|
|
12
|
+
import type { PasswordProvider } from './password-provider.js';
|
|
20
13
|
import type {
|
|
21
14
|
AuthEvent,
|
|
22
15
|
AuthEventHandler,
|
|
23
16
|
AuthManagerOptions,
|
|
24
17
|
AuthState,
|
|
25
18
|
DisconnectOptions,
|
|
19
|
+
HeadlessConnectOptions,
|
|
26
20
|
IdentityInfo,
|
|
27
21
|
ImportFromPhraseOptions,
|
|
28
22
|
ImportFromPortableOptions,
|
|
29
23
|
LocalConnectOptions,
|
|
30
24
|
RegistrationOptions,
|
|
31
25
|
RestoreSessionOptions,
|
|
26
|
+
ShutdownOptions,
|
|
32
27
|
StorageAdapter,
|
|
33
28
|
SyncOption,
|
|
34
29
|
WalletConnectOptions,
|
|
35
30
|
} from './types.js';
|
|
36
|
-
|
|
31
|
+
|
|
32
|
+
import { EnboxUserAgent } from '@enbox/agent';
|
|
33
|
+
|
|
34
|
+
import { AuthEventEmitter } from './events.js';
|
|
35
|
+
import { AuthSession } from './identity-session.js';
|
|
36
|
+
import { createDefaultStorage } from './storage/storage.js';
|
|
37
|
+
import { discoverLocalDwn } from './discovery.js';
|
|
38
|
+
import { localConnect } from './connect/local.js';
|
|
39
|
+
import { restoreSession } from './connect/restore.js';
|
|
40
|
+
import { STORAGE_KEYS } from './types.js';
|
|
41
|
+
import { walletConnect } from './connect/wallet.js';
|
|
42
|
+
import { ensureVaultReady, resolveIdentityDids, startSyncIfEnabled } from './connect/lifecycle.js';
|
|
43
|
+
import { importFromPhrase, importFromPortable } from './connect/import.js';
|
|
37
44
|
|
|
38
45
|
/**
|
|
39
46
|
* The primary entry point for authentication and identity management.
|
|
@@ -72,35 +79,46 @@ export class AuthManager {
|
|
|
72
79
|
private _userAgent: EnboxUserAgent;
|
|
73
80
|
private _emitter: AuthEventEmitter;
|
|
74
81
|
private _storage: StorageAdapter;
|
|
75
|
-
private _vault: VaultManager;
|
|
76
82
|
private _session: AuthSession | undefined;
|
|
77
83
|
private _state: AuthState = 'uninitialized';
|
|
78
84
|
private _isConnecting = false;
|
|
85
|
+
private _isShutDown = false;
|
|
79
86
|
|
|
80
87
|
// Default options from create()
|
|
81
88
|
private _defaultPassword?: string;
|
|
89
|
+
private _passwordProvider?: PasswordProvider;
|
|
82
90
|
private _defaultSync?: SyncOption;
|
|
83
91
|
private _defaultDwnEndpoints?: string[];
|
|
84
92
|
private _registration?: RegistrationOptions;
|
|
85
93
|
|
|
94
|
+
/**
|
|
95
|
+
* The local DWN server endpoint discovered during `create()`, if any.
|
|
96
|
+
* `undefined` means no local server was found. This is set before any
|
|
97
|
+
* event listeners are attached, so consumers should check this property
|
|
98
|
+
* after `create()` returns rather than relying solely on events.
|
|
99
|
+
*/
|
|
100
|
+
private _localDwnEndpoint?: string;
|
|
101
|
+
|
|
86
102
|
private constructor(params: {
|
|
87
103
|
userAgent: EnboxUserAgent;
|
|
88
104
|
emitter: AuthEventEmitter;
|
|
89
105
|
storage: StorageAdapter;
|
|
90
|
-
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
|
-
this._vault = params.vault;
|
|
100
116
|
this._defaultPassword = params.defaultPassword;
|
|
117
|
+
this._passwordProvider = params.passwordProvider;
|
|
101
118
|
this._defaultSync = params.defaultSync;
|
|
102
119
|
this._defaultDwnEndpoints = params.defaultDwnEndpoints;
|
|
103
120
|
this._registration = params.registration;
|
|
121
|
+
this._localDwnEndpoint = params.localDwnEndpoint;
|
|
104
122
|
}
|
|
105
123
|
|
|
106
124
|
/**
|
|
@@ -117,29 +135,44 @@ export class AuthManager {
|
|
|
117
135
|
const emitter = new AuthEventEmitter();
|
|
118
136
|
const storage = options.storage ?? createDefaultStorage();
|
|
119
137
|
|
|
138
|
+
// Run local DWN discovery BEFORE creating the agent. Discovery has
|
|
139
|
+
// zero vault/DWN dependencies — it only checks the URL fragment,
|
|
140
|
+
// reads localStorage, and validates via GET /info.
|
|
141
|
+
//
|
|
142
|
+
// When a local DWN server is available, the agent is created in
|
|
143
|
+
// "remote mode": it skips creating an in-process DWN and routes all
|
|
144
|
+
// DWN operations through RPC to the local server.
|
|
145
|
+
let localDwnEndpoint: string | undefined;
|
|
146
|
+
if (!options.agent && options.localDwnStrategy !== 'off') {
|
|
147
|
+
localDwnEndpoint = await discoverLocalDwn(storage);
|
|
148
|
+
// NOTE: We intentionally do NOT emit 'local-dwn-available' here
|
|
149
|
+
// because event listeners aren't attached yet. Consumers should
|
|
150
|
+
// check `authManager.localDwnEndpoint` after create() returns.
|
|
151
|
+
}
|
|
152
|
+
|
|
120
153
|
// Use a pre-built agent or create one with the given options.
|
|
121
154
|
const userAgent = options.agent ?? await EnboxUserAgent.create({
|
|
122
155
|
dataPath : options.dataPath,
|
|
123
156
|
agentVault : options.agentVault,
|
|
124
157
|
localDwnStrategy : options.localDwnStrategy,
|
|
158
|
+
localDwnEndpoint,
|
|
125
159
|
});
|
|
126
160
|
|
|
127
|
-
const vault = new VaultManager(userAgent.vault, emitter);
|
|
128
|
-
|
|
129
161
|
const manager = new AuthManager({
|
|
130
162
|
userAgent,
|
|
131
163
|
emitter,
|
|
132
164
|
storage,
|
|
133
|
-
vault,
|
|
134
165
|
defaultPassword : options.password,
|
|
166
|
+
passwordProvider : options.passwordProvider,
|
|
135
167
|
defaultSync : options.sync,
|
|
136
168
|
defaultDwnEndpoints : options.dwnEndpoints,
|
|
137
169
|
registration : options.registration,
|
|
170
|
+
localDwnEndpoint,
|
|
138
171
|
});
|
|
139
172
|
|
|
140
173
|
// Determine initial state.
|
|
141
|
-
if (await vault.isInitialized()) {
|
|
142
|
-
manager._setState(vault.isLocked ? 'locked' : 'unlocked');
|
|
174
|
+
if (await userAgent.vault.isInitialized()) {
|
|
175
|
+
manager._setState(userAgent.vault.isLocked() ? 'locked' : 'unlocked');
|
|
143
176
|
} else {
|
|
144
177
|
manager._setState('uninitialized');
|
|
145
178
|
}
|
|
@@ -160,33 +193,11 @@ export class AuthManager {
|
|
|
160
193
|
* @throws If a connection attempt is already in progress.
|
|
161
194
|
*/
|
|
162
195
|
async connect(options?: LocalConnectOptions): Promise<AuthSession> {
|
|
163
|
-
this.
|
|
164
|
-
this._isConnecting = true;
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
const session = await localConnect(
|
|
168
|
-
{
|
|
169
|
-
userAgent : this._userAgent,
|
|
170
|
-
emitter : this._emitter,
|
|
171
|
-
storage : this._storage,
|
|
172
|
-
defaultPassword : this._defaultPassword,
|
|
173
|
-
defaultSync : this._defaultSync,
|
|
174
|
-
defaultDwnEndpoints : this._defaultDwnEndpoints,
|
|
175
|
-
registration : this._registration,
|
|
176
|
-
},
|
|
177
|
-
options,
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
this._session = session;
|
|
181
|
-
this._setState('connected');
|
|
182
|
-
return session;
|
|
183
|
-
} finally {
|
|
184
|
-
this._isConnecting = false;
|
|
185
|
-
}
|
|
196
|
+
return this._withConnect(() => localConnect(this._flowContext(), options));
|
|
186
197
|
}
|
|
187
198
|
|
|
188
199
|
/**
|
|
189
|
-
* Connect to an external wallet via the
|
|
200
|
+
* Connect to an external wallet via the Enbox Connect relay protocol.
|
|
190
201
|
*
|
|
191
202
|
* This runs the full WalletConnect flow: generates a URI for QR display,
|
|
192
203
|
* validates the PIN, imports the delegated DID, and processes grants.
|
|
@@ -197,36 +208,7 @@ export class AuthManager {
|
|
|
197
208
|
* @throws If a connection attempt is already in progress.
|
|
198
209
|
*/
|
|
199
210
|
async walletConnect(options: WalletConnectOptions): Promise<AuthSession> {
|
|
200
|
-
this.
|
|
201
|
-
this._isConnecting = true;
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
// Ensure the agent is initialized and started before wallet connect.
|
|
205
|
-
const password = this._defaultPassword ?? 'insecure-static-phrase';
|
|
206
|
-
if (await this._userAgent.firstLaunch()) {
|
|
207
|
-
await this._userAgent.initialize({ password });
|
|
208
|
-
}
|
|
209
|
-
await this._userAgent.start({ password });
|
|
210
|
-
this._emitter.emit('vault-unlocked', {});
|
|
211
|
-
|
|
212
|
-
const session = await walletConnect(
|
|
213
|
-
{
|
|
214
|
-
userAgent : this._userAgent,
|
|
215
|
-
emitter : this._emitter,
|
|
216
|
-
storage : this._storage,
|
|
217
|
-
defaultSync : this._defaultSync,
|
|
218
|
-
defaultDwnEndpoints : this._defaultDwnEndpoints,
|
|
219
|
-
registration : this._registration,
|
|
220
|
-
},
|
|
221
|
-
options,
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
this._session = session;
|
|
225
|
-
this._setState('connected');
|
|
226
|
-
return session;
|
|
227
|
-
} finally {
|
|
228
|
-
this._isConnecting = false;
|
|
229
|
-
}
|
|
211
|
+
return this._withConnect(() => walletConnect(this._flowContext(), options));
|
|
230
212
|
}
|
|
231
213
|
|
|
232
214
|
/**
|
|
@@ -236,28 +218,7 @@ export class AuthManager {
|
|
|
236
218
|
* recovering the identity on this device.
|
|
237
219
|
*/
|
|
238
220
|
async importFromPhrase(options: ImportFromPhraseOptions): Promise<AuthSession> {
|
|
239
|
-
this.
|
|
240
|
-
this._isConnecting = true;
|
|
241
|
-
|
|
242
|
-
try {
|
|
243
|
-
const session = await importFromPhrase(
|
|
244
|
-
{
|
|
245
|
-
userAgent : this._userAgent,
|
|
246
|
-
emitter : this._emitter,
|
|
247
|
-
storage : this._storage,
|
|
248
|
-
defaultSync : this._defaultSync,
|
|
249
|
-
defaultDwnEndpoints : this._defaultDwnEndpoints,
|
|
250
|
-
registration : this._registration,
|
|
251
|
-
},
|
|
252
|
-
options,
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
this._session = session;
|
|
256
|
-
this._setState('connected');
|
|
257
|
-
return session;
|
|
258
|
-
} finally {
|
|
259
|
-
this._isConnecting = false;
|
|
260
|
-
}
|
|
221
|
+
return this._withConnect(() => importFromPhrase(this._flowContext(), options));
|
|
261
222
|
}
|
|
262
223
|
|
|
263
224
|
/**
|
|
@@ -266,28 +227,7 @@ export class AuthManager {
|
|
|
266
227
|
* The portable identity contains the DID's private keys and metadata.
|
|
267
228
|
*/
|
|
268
229
|
async importFromPortable(options: ImportFromPortableOptions): Promise<AuthSession> {
|
|
269
|
-
this.
|
|
270
|
-
this._isConnecting = true;
|
|
271
|
-
|
|
272
|
-
try {
|
|
273
|
-
const session = await importFromPortable(
|
|
274
|
-
{
|
|
275
|
-
userAgent : this._userAgent,
|
|
276
|
-
emitter : this._emitter,
|
|
277
|
-
storage : this._storage,
|
|
278
|
-
defaultSync : this._defaultSync,
|
|
279
|
-
defaultDwnEndpoints : this._defaultDwnEndpoints,
|
|
280
|
-
registration : this._registration,
|
|
281
|
-
},
|
|
282
|
-
options,
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
this._session = session;
|
|
286
|
-
this._setState('connected');
|
|
287
|
-
return session;
|
|
288
|
-
} finally {
|
|
289
|
-
this._isConnecting = false;
|
|
290
|
-
}
|
|
230
|
+
return this._withConnect(() => importFromPortable(this._flowContext(), options));
|
|
291
231
|
}
|
|
292
232
|
|
|
293
233
|
/**
|
|
@@ -301,16 +241,7 @@ export class AuthManager {
|
|
|
301
241
|
this._isConnecting = true;
|
|
302
242
|
|
|
303
243
|
try {
|
|
304
|
-
const session = await restoreSession(
|
|
305
|
-
{
|
|
306
|
-
userAgent : this._userAgent,
|
|
307
|
-
emitter : this._emitter,
|
|
308
|
-
storage : this._storage,
|
|
309
|
-
defaultPassword : this._defaultPassword,
|
|
310
|
-
defaultSync : this._defaultSync,
|
|
311
|
-
},
|
|
312
|
-
options,
|
|
313
|
-
);
|
|
244
|
+
const session = await restoreSession(this._flowContext(), options);
|
|
314
245
|
|
|
315
246
|
if (session) {
|
|
316
247
|
this._session = session;
|
|
@@ -322,6 +253,89 @@ export class AuthManager {
|
|
|
322
253
|
}
|
|
323
254
|
}
|
|
324
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Lightweight vault unlock for one-shot utilities and subprocesses.
|
|
258
|
+
*
|
|
259
|
+
* Unlocks the vault and retrieves the active (or first available)
|
|
260
|
+
* identity **without** starting sync, DWN registration, or persisting
|
|
261
|
+
* session markers. This is the recommended replacement for calling
|
|
262
|
+
* `agent.start({ password })` directly.
|
|
263
|
+
*
|
|
264
|
+
* Typical use cases:
|
|
265
|
+
* - Git credential helpers that need to sign a token and exit
|
|
266
|
+
* - CLI utilities that perform a single operation
|
|
267
|
+
* - Any subprocess that shares a data directory with a long-running daemon
|
|
268
|
+
*
|
|
269
|
+
* @param options - Optional password override.
|
|
270
|
+
* @returns An active AuthSession (with sync disabled).
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```ts
|
|
274
|
+
* const session = await auth.connectHeadless({ password });
|
|
275
|
+
* const did = session.did; // ready to use
|
|
276
|
+
* await auth.shutdown(); // clean exit
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
async connectHeadless(options?: HeadlessConnectOptions): Promise<AuthSession> {
|
|
280
|
+
let password = options?.password ?? this._defaultPassword;
|
|
281
|
+
const isFirstLaunch = await this._userAgent.firstLaunch();
|
|
282
|
+
|
|
283
|
+
// Try the password provider if no explicit password.
|
|
284
|
+
if (!password && this._passwordProvider) {
|
|
285
|
+
password = await this._passwordProvider.getPassword({
|
|
286
|
+
reason: isFirstLaunch ? 'create' : 'unlock',
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!password) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
'[@enbox/auth] connectHeadless() requires a password. ' +
|
|
293
|
+
'Provide one via options.password, a passwordProvider, or the AuthManager default.'
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Unlock the vault (initialise on first launch, always start).
|
|
298
|
+
await ensureVaultReady({
|
|
299
|
+
userAgent : this._userAgent,
|
|
300
|
+
emitter : this._emitter,
|
|
301
|
+
password,
|
|
302
|
+
isFirstLaunch,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Find the active identity.
|
|
306
|
+
const identities = await this._userAgent.identity.list();
|
|
307
|
+
if (identities.length === 0) {
|
|
308
|
+
throw new Error('[@enbox/auth] No identities found in vault.');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Prefer the previously-active identity, fall back to first.
|
|
312
|
+
const savedDid = await this._storage.get(STORAGE_KEYS.ACTIVE_IDENTITY);
|
|
313
|
+
const identity = (savedDid
|
|
314
|
+
? identities.find(id => id.did.uri === savedDid || id.metadata.connectedDid === savedDid)
|
|
315
|
+
: undefined
|
|
316
|
+
) ?? identities[0];
|
|
317
|
+
|
|
318
|
+
const { connectedDid, delegateDid } = resolveIdentityDids(identity);
|
|
319
|
+
|
|
320
|
+
const identityInfo: IdentityInfo = {
|
|
321
|
+
didUri : connectedDid,
|
|
322
|
+
name : identity.metadata.name,
|
|
323
|
+
connectedDid : identity.metadata.connectedDid,
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// No sync, no registration, no session persistence markers.
|
|
327
|
+
this._session = new AuthSession({
|
|
328
|
+
agent : this._userAgent,
|
|
329
|
+
did : connectedDid,
|
|
330
|
+
delegateDid,
|
|
331
|
+
identity : identityInfo,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
this._setState('connected');
|
|
335
|
+
|
|
336
|
+
return this._session;
|
|
337
|
+
}
|
|
338
|
+
|
|
325
339
|
// ─── Session management ────────────────────────────────────────
|
|
326
340
|
|
|
327
341
|
/** The current active session, or `undefined` if not connected. */
|
|
@@ -347,15 +361,14 @@ export class AuthManager {
|
|
|
347
361
|
const did = this._session?.did;
|
|
348
362
|
|
|
349
363
|
// 1. Stop sync.
|
|
350
|
-
|
|
351
|
-
await (this._userAgent as any).sync.stopSync(timeout);
|
|
352
|
-
}
|
|
364
|
+
await this._userAgent.sync.stopSync(timeout);
|
|
353
365
|
|
|
354
366
|
// 2. Clear the session (but keep storage markers for restore).
|
|
355
367
|
this._session = undefined;
|
|
356
368
|
|
|
357
|
-
// 3. Lock the vault
|
|
358
|
-
await this.
|
|
369
|
+
// 3. Lock the vault.
|
|
370
|
+
await this._userAgent.vault.lock();
|
|
371
|
+
this._emitter.emit('vault-locked', {});
|
|
359
372
|
|
|
360
373
|
// 4. Transition state.
|
|
361
374
|
this._setState('locked');
|
|
@@ -380,9 +393,7 @@ export class AuthManager {
|
|
|
380
393
|
|
|
381
394
|
// Stop sync.
|
|
382
395
|
if (this._session) {
|
|
383
|
-
|
|
384
|
-
await (this._userAgent as any).sync.stopSync(timeout);
|
|
385
|
-
}
|
|
396
|
+
await this._userAgent.sync.stopSync(timeout);
|
|
386
397
|
}
|
|
387
398
|
|
|
388
399
|
this._session = undefined;
|
|
@@ -422,6 +433,81 @@ export class AuthManager {
|
|
|
422
433
|
}
|
|
423
434
|
}
|
|
424
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Gracefully shut down the auth manager, releasing all resources.
|
|
438
|
+
*
|
|
439
|
+
* This goes beyond {@link disconnect} or {@link lock}: it stops sync,
|
|
440
|
+
* clears the active session, locks the vault, and **closes** the
|
|
441
|
+
* underlying storage handles (e.g. LevelDB) so the process can exit
|
|
442
|
+
* without dangling timers or open file descriptors.
|
|
443
|
+
*
|
|
444
|
+
* After calling `shutdown()`, the `AuthManager` instance should not be
|
|
445
|
+
* reused — create a new one via {@link AuthManager.create} if needed.
|
|
446
|
+
*
|
|
447
|
+
* Idempotent: calling `shutdown()` more than once is safe.
|
|
448
|
+
*
|
|
449
|
+
* @param options - Optional shutdown configuration.
|
|
450
|
+
* @param options.timeout - Milliseconds to wait for sync to stop. Default: `2000`.
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* ```ts
|
|
454
|
+
* const session = await auth.connectHeadless({ password });
|
|
455
|
+
* // ... perform work ...
|
|
456
|
+
* await auth.shutdown(); // clean exit, no process.exit() needed
|
|
457
|
+
* ```
|
|
458
|
+
*/
|
|
459
|
+
async shutdown(options: ShutdownOptions = {}): Promise<void> {
|
|
460
|
+
if (this._isShutDown) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const { timeout = 2000 } = options;
|
|
465
|
+
const did = this._session?.did;
|
|
466
|
+
|
|
467
|
+
// 1. Stop sync.
|
|
468
|
+
try {
|
|
469
|
+
await this._userAgent.sync.stopSync(timeout);
|
|
470
|
+
} catch {
|
|
471
|
+
// Best-effort — don't block shutdown on sync errors.
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// 2. Clear the active session.
|
|
475
|
+
this._session = undefined;
|
|
476
|
+
|
|
477
|
+
// 3. Lock the vault.
|
|
478
|
+
try {
|
|
479
|
+
await this._userAgent.vault.lock();
|
|
480
|
+
this._emitter.emit('vault-locked', {});
|
|
481
|
+
} catch {
|
|
482
|
+
// Vault may already be locked or uninitialised — safe to ignore.
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// 4. Close the sync engine (releases LevelDB handles, timers).
|
|
486
|
+
try {
|
|
487
|
+
await this._userAgent.sync.close();
|
|
488
|
+
} catch {
|
|
489
|
+
// Best-effort.
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// 5. Close the storage adapter (e.g. LevelDB session store).
|
|
493
|
+
if (typeof this._storage.close === 'function') {
|
|
494
|
+
try {
|
|
495
|
+
await this._storage.close();
|
|
496
|
+
} catch {
|
|
497
|
+
// Best-effort.
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// 6. Mark as shut down and transition state.
|
|
502
|
+
this._isShutDown = true;
|
|
503
|
+
this._setState('locked');
|
|
504
|
+
|
|
505
|
+
// 7. Emit session-end if there was an active session.
|
|
506
|
+
if (did) {
|
|
507
|
+
this._emitter.emit('session-end', { did });
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
425
511
|
// ─── Multi-identity ────────────────────────────────────────────
|
|
426
512
|
|
|
427
513
|
/**
|
|
@@ -456,8 +542,7 @@ export class AuthManager {
|
|
|
456
542
|
throw new Error(`[@enbox/auth] Identity not found: ${didUri}`);
|
|
457
543
|
}
|
|
458
544
|
|
|
459
|
-
const connectedDid
|
|
460
|
-
const delegateDid = identity.metadata.connectedDid ? identity.did.uri : undefined;
|
|
545
|
+
const { connectedDid, delegateDid } = resolveIdentityDids(identity);
|
|
461
546
|
|
|
462
547
|
// Persist the switch.
|
|
463
548
|
await this._storage.set(STORAGE_KEYS.PREVIOUSLY_CONNECTED, 'true');
|
|
@@ -482,10 +567,7 @@ export class AuthManager {
|
|
|
482
567
|
// Already registered — safe to ignore.
|
|
483
568
|
}
|
|
484
569
|
|
|
485
|
-
|
|
486
|
-
const syncInterval = sync ?? (syncMode === 'live' ? '5m' : '2m');
|
|
487
|
-
this._userAgent.sync.startSync({ mode: syncMode, interval: syncInterval })
|
|
488
|
-
.catch((err: unknown) => console.error('[@enbox/auth] Sync failed:', err));
|
|
570
|
+
startSyncIfEnabled(this._userAgent, sync);
|
|
489
571
|
}
|
|
490
572
|
|
|
491
573
|
this._session = new AuthSession({
|
|
@@ -551,9 +633,9 @@ export class AuthManager {
|
|
|
551
633
|
|
|
552
634
|
// ─── Vault ─────────────────────────────────────────────────────
|
|
553
635
|
|
|
554
|
-
/** Access the vault
|
|
555
|
-
get vault():
|
|
556
|
-
return this.
|
|
636
|
+
/** Access the underlying identity vault for lock/unlock/backup operations. */
|
|
637
|
+
get vault(): HdIdentityVault {
|
|
638
|
+
return this._userAgent.vault;
|
|
557
639
|
}
|
|
558
640
|
|
|
559
641
|
// ─── Events ────────────────────────────────────────────────────
|
|
@@ -583,7 +665,7 @@ export class AuthManager {
|
|
|
583
665
|
|
|
584
666
|
/** Whether the vault is currently locked. */
|
|
585
667
|
get isLocked(): boolean {
|
|
586
|
-
return this.
|
|
668
|
+
return this._userAgent.vault.isLocked();
|
|
587
669
|
}
|
|
588
670
|
|
|
589
671
|
/** Whether a connection attempt is in progress. */
|
|
@@ -596,8 +678,60 @@ export class AuthManager {
|
|
|
596
678
|
return this._userAgent;
|
|
597
679
|
}
|
|
598
680
|
|
|
681
|
+
/**
|
|
682
|
+
* The local DWN server endpoint discovered during `create()`, if any.
|
|
683
|
+
*
|
|
684
|
+
* When set, the agent is operating in remote mode (no in-process DWN).
|
|
685
|
+
* This property is available immediately after `create()` returns,
|
|
686
|
+
* before any event listeners are attached.
|
|
687
|
+
*/
|
|
688
|
+
get localDwnEndpoint(): string | undefined {
|
|
689
|
+
return this._localDwnEndpoint;
|
|
690
|
+
}
|
|
691
|
+
|
|
599
692
|
// ─── Private helpers ───────────────────────────────────────────
|
|
600
693
|
|
|
694
|
+
/**
|
|
695
|
+
* Build a `FlowContext` from the manager's current state.
|
|
696
|
+
*
|
|
697
|
+
* Replaces the 5 manual inline context constructions that were
|
|
698
|
+
* previously duplicated across `connect()`, `walletConnect()`,
|
|
699
|
+
* `importFromPhrase()`, `importFromPortable()`, and `restoreSession()`.
|
|
700
|
+
*/
|
|
701
|
+
private _flowContext(): FlowContext {
|
|
702
|
+
return {
|
|
703
|
+
userAgent : this._userAgent,
|
|
704
|
+
emitter : this._emitter,
|
|
705
|
+
storage : this._storage,
|
|
706
|
+
defaultPassword : this._defaultPassword,
|
|
707
|
+
passwordProvider : this._passwordProvider,
|
|
708
|
+
defaultSync : this._defaultSync,
|
|
709
|
+
defaultDwnEndpoints : this._defaultDwnEndpoints,
|
|
710
|
+
registration : this._registration,
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Template for connection flows that follow the guard → try/finally → setState pattern.
|
|
716
|
+
*
|
|
717
|
+
* Consolidates the duplicated concurrency guard, `_isConnecting` flag management,
|
|
718
|
+
* session assignment, and state transition across `connect()`, `walletConnect()`,
|
|
719
|
+
* `importFromPhrase()`, and `importFromPortable()`.
|
|
720
|
+
*/
|
|
721
|
+
private async _withConnect(fn: () => Promise<AuthSession>): Promise<AuthSession> {
|
|
722
|
+
this._guardConcurrency();
|
|
723
|
+
this._isConnecting = true;
|
|
724
|
+
|
|
725
|
+
try {
|
|
726
|
+
const session = await fn();
|
|
727
|
+
this._session = session;
|
|
728
|
+
this._setState('connected');
|
|
729
|
+
return session;
|
|
730
|
+
} finally {
|
|
731
|
+
this._isConnecting = false;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
601
735
|
private _setState(state: AuthState): void {
|
|
602
736
|
if (state === this._state) {return;}
|
|
603
737
|
const previous = this._state;
|