@parity/product-sdk-signer 0.2.4 → 0.4.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/index.d.ts +150 -17
- package/dist/index.js +93 -19
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +2 -0
- package/src/providers/host.ts +80 -11
- package/src/signer-manager.ts +326 -28
- package/src/types.ts +56 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as polkadot_api from 'polkadot-api';
|
|
2
2
|
import { PolkadotSigner } from 'polkadot-api';
|
|
3
3
|
import { SS58String } from '@parity/product-sdk-address';
|
|
4
|
+
import { AllocatableResource, AllocationOutcome } from '@parity/product-sdk-host';
|
|
4
5
|
|
|
5
6
|
/** Function that unsubscribes a listener when called. */
|
|
6
7
|
type Unsubscribe = () => void;
|
|
@@ -139,7 +140,60 @@ interface SignerManagerOptions {
|
|
|
139
140
|
* Set to `null` to disable persistence entirely.
|
|
140
141
|
*/
|
|
141
142
|
persistence?: AccountPersistence | null;
|
|
143
|
+
/**
|
|
144
|
+
* Callback fired exactly when the manager transitions to `connected`
|
|
145
|
+
* with a selected account — not on subsequent state mutations while
|
|
146
|
+
* still connected. Fires again after auto-reconnect, so a fresh host
|
|
147
|
+
* session re-runs the callback.
|
|
148
|
+
*
|
|
149
|
+
* Common use: request product resource allocations once per session.
|
|
150
|
+
* The `ctx` exposes a pre-bound `requestResourceAllocation` helper
|
|
151
|
+
* plus an `AbortSignal` that fires if the user disconnects or
|
|
152
|
+
* destroys the manager mid-flight.
|
|
153
|
+
*
|
|
154
|
+
* `requestResourceAllocation` throws on failure (matches the
|
|
155
|
+
* `@parity/product-sdk-host` export of the same name); errors thrown
|
|
156
|
+
* from `onConnect` are logged but do not affect the connected state —
|
|
157
|
+
* the next reconnect retries.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```ts
|
|
161
|
+
* new SignerManager({
|
|
162
|
+
* onConnect: async (_account, { requestResourceAllocation, signal }) => {
|
|
163
|
+
* try {
|
|
164
|
+
* const outcomes = await requestResourceAllocation([
|
|
165
|
+
* { tag: "AutoSigning", value: undefined },
|
|
166
|
+
* ]);
|
|
167
|
+
* if (signal.aborted) return;
|
|
168
|
+
* if (outcomes.some((o) => o.tag !== "Allocated")) {
|
|
169
|
+
* logWarning("partial permissions", outcomes);
|
|
170
|
+
* }
|
|
171
|
+
* } catch (cause) {
|
|
172
|
+
* logWarning("resource allocation failed", cause);
|
|
173
|
+
* }
|
|
174
|
+
* },
|
|
175
|
+
* });
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
onConnect?: OnConnect;
|
|
142
179
|
}
|
|
180
|
+
/** Context passed to the `onConnect` callback. */
|
|
181
|
+
interface ConnectContext {
|
|
182
|
+
/**
|
|
183
|
+
* Aborted when the manager disconnects or is destroyed while the
|
|
184
|
+
* callback is still running. Pass through to `fetch` / cancellation
|
|
185
|
+
* primitives so mid-flight work stops promptly.
|
|
186
|
+
*/
|
|
187
|
+
signal: AbortSignal;
|
|
188
|
+
/**
|
|
189
|
+
* Request a batch of host resource allocations. Bound shorthand for
|
|
190
|
+
* `requestResourceAllocation` from `@parity/product-sdk-host` —
|
|
191
|
+
* throws on failure, returns the unwrapped outcomes on success.
|
|
192
|
+
*/
|
|
193
|
+
requestResourceAllocation: (resources: AllocatableResource[]) => Promise<AllocationOutcome[]>;
|
|
194
|
+
}
|
|
195
|
+
/** Callback signature for {@link SignerManagerOptions.onConnect}. */
|
|
196
|
+
type OnConnect = (account: SignerAccount, ctx: ConnectContext) => void | Promise<void>;
|
|
143
197
|
|
|
144
198
|
/** Base class for all signer errors. Use `instanceof SignerError` to catch any signer-related error. */
|
|
145
199
|
declare class SignerError extends Error {
|
|
@@ -193,7 +247,7 @@ interface HostProviderOptions {
|
|
|
193
247
|
/** Initial retry delay in ms. Default: 500 */
|
|
194
248
|
retryDelay?: number;
|
|
195
249
|
/**
|
|
196
|
-
* Custom SDK loader. Defaults to `import("@novasamatech/
|
|
250
|
+
* Custom SDK loader. Defaults to `import("@novasamatech/host-api-wrapper")`.
|
|
197
251
|
* Override this for testing or custom SDK setups.
|
|
198
252
|
* @internal
|
|
199
253
|
*/
|
|
@@ -268,7 +322,7 @@ interface AccountsProvider {
|
|
|
268
322
|
getLegacyAccounts: () => NeverthrowResultAsync<RawAccount[], unknown>;
|
|
269
323
|
getLegacyAccountSigner: (account: ProductAccount) => polkadot_api.PolkadotSigner;
|
|
270
324
|
getProductAccount: (dotNsIdentifier: string, derivationIndex?: number) => NeverthrowResultAsync<RawAccount, unknown>;
|
|
271
|
-
getProductAccountSigner: (account: ProductAccount) => polkadot_api.PolkadotSigner;
|
|
325
|
+
getProductAccountSigner: (account: ProductAccount, signerType?: "signPayload" | "createTransaction") => polkadot_api.PolkadotSigner;
|
|
272
326
|
getProductAccountAlias: (dotNsIdentifier: string, derivationIndex?: number) => NeverthrowResultAsync<ContextualAlias, unknown>;
|
|
273
327
|
createRingVRFProof: (dotNsIdentifier: string, derivationIndex: number, location: unknown, message: Uint8Array) => NeverthrowResultAsync<Uint8Array, unknown>;
|
|
274
328
|
subscribeAccountConnectionStatus: (callback: (status: string) => void) => {
|
|
@@ -300,7 +354,7 @@ interface ProductSdkModule {
|
|
|
300
354
|
/**
|
|
301
355
|
* Provider for the Host API (Polkadot Desktop / Android).
|
|
302
356
|
*
|
|
303
|
-
* Dynamically imports `@novasamatech/
|
|
357
|
+
* Dynamically imports `@novasamatech/host-api-wrapper` at runtime so it remains
|
|
304
358
|
* an optional peer dependency. Apps running outside a host container will
|
|
305
359
|
* gracefully get a `HOST_UNAVAILABLE` error.
|
|
306
360
|
*
|
|
@@ -339,6 +393,12 @@ declare class HostProvider implements SignerProvider {
|
|
|
339
393
|
*
|
|
340
394
|
* Convenience method for when you already have the product account details.
|
|
341
395
|
* Requires a prior successful `connect()` call.
|
|
396
|
+
*
|
|
397
|
+
* Routing is pinned to `signerType: "createTransaction"` via
|
|
398
|
+
* {@link PRODUCT_SIGNER_TYPE} so unknown signed extensions (e.g. `AsPgas`
|
|
399
|
+
* on Paseo Next) are forwarded to the host as opaque bytes for
|
|
400
|
+
* metadata-driven decoding, rather than going through the PJS bridge
|
|
401
|
+
* that throws on unknown extensions.
|
|
342
402
|
*/
|
|
343
403
|
getProductAccountSigner(account: ProductAccount): polkadot_api.PolkadotSigner;
|
|
344
404
|
/**
|
|
@@ -367,22 +427,69 @@ declare class HostProvider implements SignerProvider {
|
|
|
367
427
|
* Core orchestrator for signer management.
|
|
368
428
|
*
|
|
369
429
|
* Manages account discovery and signer creation via the Host API.
|
|
370
|
-
* Framework-agnostic — use the subscribe() pattern to integrate with
|
|
430
|
+
* Framework-agnostic — use the `subscribe()` pattern to integrate with
|
|
371
431
|
* React, Vue, or any framework.
|
|
372
432
|
*
|
|
433
|
+
* ## Lifecycle
|
|
434
|
+
*
|
|
435
|
+
* ```
|
|
436
|
+
* disconnected → connecting → connected ──── selectAccount / signRaw / …
|
|
437
|
+
* ▲ │ │
|
|
438
|
+
* │ ▼ ▼
|
|
439
|
+
* └── disconnect() provider drops → auto-reconnect → connected
|
|
440
|
+
* (onConnect re-fires)
|
|
441
|
+
*
|
|
442
|
+
* ┌─ destroy() ──► (terminal — manager unusable)
|
|
443
|
+
* ▼
|
|
444
|
+
* ```
|
|
445
|
+
*
|
|
446
|
+
* ## Callbacks
|
|
447
|
+
*
|
|
448
|
+
* - **`subscribe(cb)`** fires synchronously on every state mutation, in
|
|
449
|
+
* registration order, inside the call stack that mutated state. It does
|
|
450
|
+
* **not** fire with the initial state — call `getState()` if you need a
|
|
451
|
+
* priming read. Multiple mutations during the same operation produce
|
|
452
|
+
* multiple notifications.
|
|
453
|
+
*
|
|
454
|
+
* - **`onConnect(account, ctx)`** (from `SignerManagerOptions`) fires
|
|
455
|
+
* exactly when the manager transitions from non-connected to
|
|
456
|
+
* `"connected"` with a selected account. It fires on a microtask
|
|
457
|
+
* *after* the `subscribe` notification, so subscribers always observe
|
|
458
|
+
* `state.status === "connected"` before `onConnect`'s side effects run.
|
|
459
|
+
* It re-fires after auto-reconnect (the SDK reconnects automatically
|
|
460
|
+
* when the provider drops), and re-fires after a fresh `connect()`.
|
|
461
|
+
*
|
|
462
|
+
* - **Internal `onAccountsChange` wiring** is worth a behavioral note:
|
|
463
|
+
* when the provider reports an updated account list, the manager
|
|
464
|
+
* preserves the current selection if its address is still present, or
|
|
465
|
+
* sets `selectedAccount` to `null` if it isn't — it does **not** fall
|
|
466
|
+
* back to `accounts[0]`. The fallback-to-first only applies on
|
|
467
|
+
* connect-success, where there is no prior selection to preserve.
|
|
468
|
+
*
|
|
469
|
+
* ## `disconnect()` vs `destroy()`
|
|
470
|
+
*
|
|
471
|
+
* - `disconnect()` resets state to initial. Subsequent `connect()` calls
|
|
472
|
+
* work normally. Reversible.
|
|
473
|
+
* - `destroy()` is **terminal**: the instance is marked unusable, all
|
|
474
|
+
* subscribers are cleared, and any further call returns `DestroyedError`.
|
|
475
|
+
* Use in framework teardown (React `useEffect` cleanup, HMR dispose).
|
|
476
|
+
*
|
|
477
|
+
* Both methods cancel any in-flight `connect`, reconnect attempt, and
|
|
478
|
+
* `onConnect` callback (the `ctx.signal` becomes aborted).
|
|
479
|
+
*
|
|
373
480
|
* @example
|
|
374
481
|
* ```ts
|
|
375
|
-
* const manager = new SignerManager(
|
|
482
|
+
* const manager = new SignerManager({
|
|
483
|
+
* onConnect: async (_account, { requestResourceAllocation }) => {
|
|
484
|
+
* await requestResourceAllocation([{ tag: "AutoSigning", value: undefined }]);
|
|
485
|
+
* },
|
|
486
|
+
* });
|
|
376
487
|
* manager.subscribe(state => console.log(state.status));
|
|
377
488
|
*
|
|
378
|
-
* //
|
|
379
|
-
* await manager.connect();
|
|
489
|
+
* await manager.connect(); // host provider (default)
|
|
490
|
+
* await manager.connect("dev"); // Alice / Bob / … for testing
|
|
380
491
|
*
|
|
381
|
-
*
|
|
382
|
-
* await manager.connect("dev");
|
|
383
|
-
*
|
|
384
|
-
* // Select account and get signer
|
|
385
|
-
* manager.selectAccount("5GrwvaEF...");
|
|
492
|
+
* manager.selectAccount("5GrwvaEF…");
|
|
386
493
|
* const signer = manager.getSigner();
|
|
387
494
|
* ```
|
|
388
495
|
*/
|
|
@@ -401,13 +508,26 @@ declare class SignerManager {
|
|
|
401
508
|
private readonly dappName;
|
|
402
509
|
private readonly persistenceOption;
|
|
403
510
|
private resolvedPersistence;
|
|
511
|
+
private readonly onConnectCallback;
|
|
512
|
+
private onConnectController;
|
|
404
513
|
constructor(options?: SignerManagerOptions);
|
|
405
514
|
private getPersistence;
|
|
406
515
|
/** Get a snapshot of the current state. */
|
|
407
516
|
getState(): SignerState;
|
|
408
517
|
/**
|
|
409
|
-
* Subscribe to state changes.
|
|
410
|
-
*
|
|
518
|
+
* Subscribe to state changes.
|
|
519
|
+
*
|
|
520
|
+
* The callback fires synchronously on every state mutation, in
|
|
521
|
+
* registration order, inside the call stack that mutated state. It
|
|
522
|
+
* does **not** fire with the current state at subscription time —
|
|
523
|
+
* call {@link getState} if you need a priming read.
|
|
524
|
+
*
|
|
525
|
+
* For "fired once when the user connects" semantics, prefer the
|
|
526
|
+
* {@link SignerManagerOptions.onConnect} option instead of gating on
|
|
527
|
+
* `state.status` inside this callback — `subscribe` will fire many
|
|
528
|
+
* times while connected (`selectAccount`, account swaps, etc.).
|
|
529
|
+
*
|
|
530
|
+
* @returns an unsubscribe function.
|
|
411
531
|
*/
|
|
412
532
|
subscribe(callback: (state: SignerState) => void): () => void;
|
|
413
533
|
/**
|
|
@@ -421,7 +541,13 @@ declare class SignerManager {
|
|
|
421
541
|
* - `"dev"`: Connect using dev accounts (for testing)
|
|
422
542
|
*/
|
|
423
543
|
connect(providerType?: ProviderType): Promise<Result<SignerAccount[], SignerError>>;
|
|
424
|
-
/**
|
|
544
|
+
/**
|
|
545
|
+
* Disconnect from the current provider and reset state to initial.
|
|
546
|
+
*
|
|
547
|
+
* Reversible — subsequent `connect()` calls work normally. Cancels
|
|
548
|
+
* any in-flight `connect`, reconnect attempt, or `onConnect` callback
|
|
549
|
+
* (`ctx.signal` becomes aborted).
|
|
550
|
+
*/
|
|
425
551
|
disconnect(): void;
|
|
426
552
|
/**
|
|
427
553
|
* Select an account by address.
|
|
@@ -477,7 +603,12 @@ declare class SignerManager {
|
|
|
477
603
|
createRingVRFProof(dotNsIdentifier: string, derivationIndex: number, location: RingLocation, message: Uint8Array): Promise<Result<Uint8Array, SignerError>>;
|
|
478
604
|
/**
|
|
479
605
|
* Destroy the manager and release all resources.
|
|
480
|
-
*
|
|
606
|
+
*
|
|
607
|
+
* **Terminal** — clears all subscribers, cancels in-flight work, and
|
|
608
|
+
* marks the instance unusable. Any subsequent method returns
|
|
609
|
+
* `DestroyedError`. Idempotent. Use in framework teardown (React
|
|
610
|
+
* `useEffect` cleanup, HMR dispose). For a reversible reset, use
|
|
611
|
+
* {@link disconnect} instead.
|
|
481
612
|
*/
|
|
482
613
|
destroy(): void;
|
|
483
614
|
private connectToProvider;
|
|
@@ -488,6 +619,8 @@ declare class SignerManager {
|
|
|
488
619
|
private getHostProvider;
|
|
489
620
|
private cancelConnect;
|
|
490
621
|
private cancelReconnect;
|
|
622
|
+
private cancelOnConnect;
|
|
623
|
+
private fireOnConnect;
|
|
491
624
|
private disconnectInternal;
|
|
492
625
|
private persistAccount;
|
|
493
626
|
private loadPersistedAccount;
|
|
@@ -561,4 +694,4 @@ declare class DevProvider implements SignerProvider {
|
|
|
561
694
|
onAccountsChange(_callback: (accounts: SignerAccount[]) => void): Unsubscribe;
|
|
562
695
|
}
|
|
563
696
|
|
|
564
|
-
export { AccountNotFoundError, type AccountPersistence, type ConnectionStatus, type ContextualAlias, DestroyedError, type DevAccountName, type DevKeyType, DevProvider, type DevProviderOptions, HostDisconnectedError, HostProvider, type HostProviderOptions, HostRejectedError, HostUnavailableError, NoAccountsError, type ProductAccount, type ProviderFactory, type ProviderType, type Result, type RetryOptions, type RingLocation, type SignerAccount, SignerError, SignerManager, type SignerManagerOptions, type SignerProvider, type SignerState, SigningFailedError, TimeoutError, type Unsubscribe, err, isHostError, ok, sleep, withRetry };
|
|
697
|
+
export { AccountNotFoundError, type AccountPersistence, type ConnectContext, type ConnectionStatus, type ContextualAlias, DestroyedError, type DevAccountName, type DevKeyType, DevProvider, type DevProviderOptions, HostDisconnectedError, HostProvider, type HostProviderOptions, HostRejectedError, HostUnavailableError, NoAccountsError, type OnConnect, type ProductAccount, type ProviderFactory, type ProviderType, type Result, type RetryOptions, type RingLocation, type SignerAccount, SignerError, SignerManager, type SignerManagerOptions, type SignerProvider, type SignerState, SigningFailedError, TimeoutError, type Unsubscribe, err, isHostError, ok, sleep, withRetry };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLogger } from '@parity/product-sdk-logger';
|
|
2
|
-
import { getHostLocalStorage } from '@parity/product-sdk-host';
|
|
2
|
+
import { requestResourceAllocation, getHostLocalStorage } from '@parity/product-sdk-host';
|
|
3
3
|
import { seedToAccount } from '@parity/product-sdk-keys';
|
|
4
4
|
import { ss58Encode, deriveH160 } from '@parity/product-sdk-address';
|
|
5
5
|
|
|
@@ -180,8 +180,9 @@ async function withRetry(fn, options) {
|
|
|
180
180
|
|
|
181
181
|
// src/providers/host.ts
|
|
182
182
|
var log2 = createLogger("signer:host");
|
|
183
|
+
var PRODUCT_SIGNER_TYPE = "createTransaction";
|
|
183
184
|
async function defaultLoadSdk() {
|
|
184
|
-
return await import('@novasamatech/
|
|
185
|
+
return await import('@novasamatech/host-api-wrapper');
|
|
185
186
|
}
|
|
186
187
|
async function defaultLoadHostApiEnum() {
|
|
187
188
|
return await import('@novasamatech/host-api');
|
|
@@ -283,7 +284,10 @@ var HostProvider = class {
|
|
|
283
284
|
if (!this.accountsProvider) {
|
|
284
285
|
throw new Error("Host provider is disconnected");
|
|
285
286
|
}
|
|
286
|
-
return this.accountsProvider.getProductAccountSigner(
|
|
287
|
+
return this.accountsProvider.getProductAccountSigner(
|
|
288
|
+
productAccount,
|
|
289
|
+
PRODUCT_SIGNER_TYPE
|
|
290
|
+
);
|
|
287
291
|
}
|
|
288
292
|
});
|
|
289
293
|
} catch (cause) {
|
|
@@ -300,12 +304,18 @@ var HostProvider = class {
|
|
|
300
304
|
*
|
|
301
305
|
* Convenience method for when you already have the product account details.
|
|
302
306
|
* Requires a prior successful `connect()` call.
|
|
307
|
+
*
|
|
308
|
+
* Routing is pinned to `signerType: "createTransaction"` via
|
|
309
|
+
* {@link PRODUCT_SIGNER_TYPE} so unknown signed extensions (e.g. `AsPgas`
|
|
310
|
+
* on Paseo Next) are forwarded to the host as opaque bytes for
|
|
311
|
+
* metadata-driven decoding, rather than going through the PJS bridge
|
|
312
|
+
* that throws on unknown extensions.
|
|
303
313
|
*/
|
|
304
314
|
getProductAccountSigner(account) {
|
|
305
315
|
if (!this.accountsProvider) {
|
|
306
316
|
throw new Error("Host provider is not connected");
|
|
307
317
|
}
|
|
308
|
-
return this.accountsProvider.getProductAccountSigner(account);
|
|
318
|
+
return this.accountsProvider.getProductAccountSigner(account, PRODUCT_SIGNER_TYPE);
|
|
309
319
|
}
|
|
310
320
|
/**
|
|
311
321
|
* Get a contextual alias for a product account via Ring VRF.
|
|
@@ -517,6 +527,13 @@ function initialState() {
|
|
|
517
527
|
error: null
|
|
518
528
|
};
|
|
519
529
|
}
|
|
530
|
+
function resolveSelectedAccount(accounts, preferredAddress) {
|
|
531
|
+
if (preferredAddress) {
|
|
532
|
+
const found = accounts.find((a) => a.address === preferredAddress);
|
|
533
|
+
if (found) return found;
|
|
534
|
+
}
|
|
535
|
+
return accounts[0] ?? null;
|
|
536
|
+
}
|
|
520
537
|
var SignerManager = class {
|
|
521
538
|
state;
|
|
522
539
|
provider = null;
|
|
@@ -532,6 +549,8 @@ var SignerManager = class {
|
|
|
532
549
|
dappName;
|
|
533
550
|
persistenceOption;
|
|
534
551
|
resolvedPersistence;
|
|
552
|
+
onConnectCallback;
|
|
553
|
+
onConnectController = null;
|
|
535
554
|
constructor(options) {
|
|
536
555
|
this.ss58Prefix = options?.ss58Prefix ?? DEFAULT_SS58_PREFIX;
|
|
537
556
|
this.hostTimeout = options?.hostTimeout ?? DEFAULT_HOST_TIMEOUT;
|
|
@@ -540,6 +559,7 @@ var SignerManager = class {
|
|
|
540
559
|
this.dappName = options?.dappName ?? DEFAULT_DAPP_NAME;
|
|
541
560
|
this.persistenceOption = options?.persistence;
|
|
542
561
|
this.resolvedPersistence = options?.persistence;
|
|
562
|
+
this.onConnectCallback = options?.onConnect;
|
|
543
563
|
this.state = initialState();
|
|
544
564
|
}
|
|
545
565
|
async getPersistence() {
|
|
@@ -555,8 +575,19 @@ var SignerManager = class {
|
|
|
555
575
|
return this.state;
|
|
556
576
|
}
|
|
557
577
|
/**
|
|
558
|
-
* Subscribe to state changes.
|
|
559
|
-
*
|
|
578
|
+
* Subscribe to state changes.
|
|
579
|
+
*
|
|
580
|
+
* The callback fires synchronously on every state mutation, in
|
|
581
|
+
* registration order, inside the call stack that mutated state. It
|
|
582
|
+
* does **not** fire with the current state at subscription time —
|
|
583
|
+
* call {@link getState} if you need a priming read.
|
|
584
|
+
*
|
|
585
|
+
* For "fired once when the user connects" semantics, prefer the
|
|
586
|
+
* {@link SignerManagerOptions.onConnect} option instead of gating on
|
|
587
|
+
* `state.status` inside this callback — `subscribe` will fire many
|
|
588
|
+
* times while connected (`selectAccount`, account swaps, etc.).
|
|
589
|
+
*
|
|
590
|
+
* @returns an unsubscribe function.
|
|
560
591
|
*/
|
|
561
592
|
subscribe(callback) {
|
|
562
593
|
this.subscribers.add(callback);
|
|
@@ -580,6 +611,7 @@ var SignerManager = class {
|
|
|
580
611
|
}
|
|
581
612
|
this.cancelConnect();
|
|
582
613
|
this.cancelReconnect();
|
|
614
|
+
this.cancelOnConnect();
|
|
583
615
|
this.connectController = new AbortController();
|
|
584
616
|
const signal = this.connectController.signal;
|
|
585
617
|
this.disconnectInternal();
|
|
@@ -587,10 +619,17 @@ var SignerManager = class {
|
|
|
587
619
|
const targetProvider = providerType ?? "host";
|
|
588
620
|
return this.connectToProvider(targetProvider, signal);
|
|
589
621
|
}
|
|
590
|
-
/**
|
|
622
|
+
/**
|
|
623
|
+
* Disconnect from the current provider and reset state to initial.
|
|
624
|
+
*
|
|
625
|
+
* Reversible — subsequent `connect()` calls work normally. Cancels
|
|
626
|
+
* any in-flight `connect`, reconnect attempt, or `onConnect` callback
|
|
627
|
+
* (`ctx.signal` becomes aborted).
|
|
628
|
+
*/
|
|
591
629
|
disconnect() {
|
|
592
630
|
this.cancelConnect();
|
|
593
631
|
this.cancelReconnect();
|
|
632
|
+
this.cancelOnConnect();
|
|
594
633
|
this.disconnectInternal();
|
|
595
634
|
this.setState(initialState());
|
|
596
635
|
log3.info("disconnected");
|
|
@@ -709,13 +748,19 @@ var SignerManager = class {
|
|
|
709
748
|
}
|
|
710
749
|
/**
|
|
711
750
|
* Destroy the manager and release all resources.
|
|
712
|
-
*
|
|
751
|
+
*
|
|
752
|
+
* **Terminal** — clears all subscribers, cancels in-flight work, and
|
|
753
|
+
* marks the instance unusable. Any subsequent method returns
|
|
754
|
+
* `DestroyedError`. Idempotent. Use in framework teardown (React
|
|
755
|
+
* `useEffect` cleanup, HMR dispose). For a reversible reset, use
|
|
756
|
+
* {@link disconnect} instead.
|
|
713
757
|
*/
|
|
714
758
|
destroy() {
|
|
715
759
|
if (this.isDestroyed) return;
|
|
716
760
|
this.isDestroyed = true;
|
|
717
761
|
this.cancelConnect();
|
|
718
762
|
this.cancelReconnect();
|
|
763
|
+
this.cancelOnConnect();
|
|
719
764
|
this.disconnectInternal();
|
|
720
765
|
this.subscribers.clear();
|
|
721
766
|
this.state = initialState();
|
|
@@ -745,8 +790,7 @@ var SignerManager = class {
|
|
|
745
790
|
this.cleanups.push(accountUnsub);
|
|
746
791
|
const accounts = result.value;
|
|
747
792
|
const persisted = await this.loadPersistedAccount();
|
|
748
|
-
const
|
|
749
|
-
const selectedAccount = restoredAccount ?? (accounts.length > 0 ? accounts[0] : null);
|
|
793
|
+
const selectedAccount = resolveSelectedAccount(accounts, persisted);
|
|
750
794
|
this.setState({
|
|
751
795
|
status: "connected",
|
|
752
796
|
accounts,
|
|
@@ -756,6 +800,7 @@ var SignerManager = class {
|
|
|
756
800
|
});
|
|
757
801
|
if (selectedAccount) {
|
|
758
802
|
this.persistAccount(selectedAccount.address);
|
|
803
|
+
this.fireOnConnect(selectedAccount);
|
|
759
804
|
}
|
|
760
805
|
log3.info("connected", { provider: type, accounts: accounts.length });
|
|
761
806
|
return result;
|
|
@@ -821,13 +866,20 @@ var SignerManager = class {
|
|
|
821
866
|
});
|
|
822
867
|
this.cleanups.push(accountUnsub);
|
|
823
868
|
const accounts = result.value;
|
|
869
|
+
const selectedAccount = resolveSelectedAccount(
|
|
870
|
+
accounts,
|
|
871
|
+
this.state.selectedAccount?.address
|
|
872
|
+
);
|
|
824
873
|
this.setState({
|
|
825
874
|
status: "connected",
|
|
826
875
|
accounts,
|
|
827
876
|
activeProvider: providerType,
|
|
828
|
-
selectedAccount
|
|
877
|
+
selectedAccount,
|
|
829
878
|
error: null
|
|
830
879
|
});
|
|
880
|
+
if (selectedAccount) {
|
|
881
|
+
this.fireOnConnect(selectedAccount);
|
|
882
|
+
}
|
|
831
883
|
log3.info("reconnected", { provider: providerType });
|
|
832
884
|
return result;
|
|
833
885
|
},
|
|
@@ -861,16 +913,38 @@ var SignerManager = class {
|
|
|
861
913
|
return null;
|
|
862
914
|
}
|
|
863
915
|
cancelConnect() {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
this.connectController = null;
|
|
867
|
-
}
|
|
916
|
+
this.connectController?.abort();
|
|
917
|
+
this.connectController = null;
|
|
868
918
|
}
|
|
869
919
|
cancelReconnect() {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
920
|
+
this.reconnectController?.abort();
|
|
921
|
+
this.reconnectController = null;
|
|
922
|
+
}
|
|
923
|
+
cancelOnConnect() {
|
|
924
|
+
this.onConnectController?.abort();
|
|
925
|
+
this.onConnectController = null;
|
|
926
|
+
}
|
|
927
|
+
fireOnConnect(account) {
|
|
928
|
+
const callback = this.onConnectCallback;
|
|
929
|
+
if (!callback) return;
|
|
930
|
+
this.cancelOnConnect();
|
|
931
|
+
const controller = new AbortController();
|
|
932
|
+
this.onConnectController = controller;
|
|
933
|
+
const ctx = {
|
|
934
|
+
signal: controller.signal,
|
|
935
|
+
requestResourceAllocation
|
|
936
|
+
};
|
|
937
|
+
queueMicrotask(async () => {
|
|
938
|
+
try {
|
|
939
|
+
await callback(account, ctx);
|
|
940
|
+
} catch (cause) {
|
|
941
|
+
log3.warn("onConnect callback threw", { cause });
|
|
942
|
+
} finally {
|
|
943
|
+
if (this.onConnectController === controller) {
|
|
944
|
+
this.onConnectController = null;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
});
|
|
874
948
|
}
|
|
875
949
|
disconnectInternal() {
|
|
876
950
|
for (const cleanup of this.cleanups) {
|