@parity/product-sdk-signer 0.2.3 → 0.3.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 +141 -14
- package/dist/index.js +80 -16
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +2 -0
- 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 {
|
|
@@ -367,22 +421,69 @@ declare class HostProvider implements SignerProvider {
|
|
|
367
421
|
* Core orchestrator for signer management.
|
|
368
422
|
*
|
|
369
423
|
* Manages account discovery and signer creation via the Host API.
|
|
370
|
-
* Framework-agnostic — use the subscribe() pattern to integrate with
|
|
424
|
+
* Framework-agnostic — use the `subscribe()` pattern to integrate with
|
|
371
425
|
* React, Vue, or any framework.
|
|
372
426
|
*
|
|
427
|
+
* ## Lifecycle
|
|
428
|
+
*
|
|
429
|
+
* ```
|
|
430
|
+
* disconnected → connecting → connected ──── selectAccount / signRaw / …
|
|
431
|
+
* ▲ │ │
|
|
432
|
+
* │ ▼ ▼
|
|
433
|
+
* └── disconnect() provider drops → auto-reconnect → connected
|
|
434
|
+
* (onConnect re-fires)
|
|
435
|
+
*
|
|
436
|
+
* ┌─ destroy() ──► (terminal — manager unusable)
|
|
437
|
+
* ▼
|
|
438
|
+
* ```
|
|
439
|
+
*
|
|
440
|
+
* ## Callbacks
|
|
441
|
+
*
|
|
442
|
+
* - **`subscribe(cb)`** fires synchronously on every state mutation, in
|
|
443
|
+
* registration order, inside the call stack that mutated state. It does
|
|
444
|
+
* **not** fire with the initial state — call `getState()` if you need a
|
|
445
|
+
* priming read. Multiple mutations during the same operation produce
|
|
446
|
+
* multiple notifications.
|
|
447
|
+
*
|
|
448
|
+
* - **`onConnect(account, ctx)`** (from `SignerManagerOptions`) fires
|
|
449
|
+
* exactly when the manager transitions from non-connected to
|
|
450
|
+
* `"connected"` with a selected account. It fires on a microtask
|
|
451
|
+
* *after* the `subscribe` notification, so subscribers always observe
|
|
452
|
+
* `state.status === "connected"` before `onConnect`'s side effects run.
|
|
453
|
+
* It re-fires after auto-reconnect (the SDK reconnects automatically
|
|
454
|
+
* when the provider drops), and re-fires after a fresh `connect()`.
|
|
455
|
+
*
|
|
456
|
+
* - **Internal `onAccountsChange` wiring** is worth a behavioral note:
|
|
457
|
+
* when the provider reports an updated account list, the manager
|
|
458
|
+
* preserves the current selection if its address is still present, or
|
|
459
|
+
* sets `selectedAccount` to `null` if it isn't — it does **not** fall
|
|
460
|
+
* back to `accounts[0]`. The fallback-to-first only applies on
|
|
461
|
+
* connect-success, where there is no prior selection to preserve.
|
|
462
|
+
*
|
|
463
|
+
* ## `disconnect()` vs `destroy()`
|
|
464
|
+
*
|
|
465
|
+
* - `disconnect()` resets state to initial. Subsequent `connect()` calls
|
|
466
|
+
* work normally. Reversible.
|
|
467
|
+
* - `destroy()` is **terminal**: the instance is marked unusable, all
|
|
468
|
+
* subscribers are cleared, and any further call returns `DestroyedError`.
|
|
469
|
+
* Use in framework teardown (React `useEffect` cleanup, HMR dispose).
|
|
470
|
+
*
|
|
471
|
+
* Both methods cancel any in-flight `connect`, reconnect attempt, and
|
|
472
|
+
* `onConnect` callback (the `ctx.signal` becomes aborted).
|
|
473
|
+
*
|
|
373
474
|
* @example
|
|
374
475
|
* ```ts
|
|
375
|
-
* const manager = new SignerManager(
|
|
476
|
+
* const manager = new SignerManager({
|
|
477
|
+
* onConnect: async (_account, { requestResourceAllocation }) => {
|
|
478
|
+
* await requestResourceAllocation([{ tag: "AutoSigning", value: undefined }]);
|
|
479
|
+
* },
|
|
480
|
+
* });
|
|
376
481
|
* manager.subscribe(state => console.log(state.status));
|
|
377
482
|
*
|
|
378
|
-
* //
|
|
379
|
-
* await manager.connect();
|
|
483
|
+
* await manager.connect(); // host provider (default)
|
|
484
|
+
* await manager.connect("dev"); // Alice / Bob / … for testing
|
|
380
485
|
*
|
|
381
|
-
*
|
|
382
|
-
* await manager.connect("dev");
|
|
383
|
-
*
|
|
384
|
-
* // Select account and get signer
|
|
385
|
-
* manager.selectAccount("5GrwvaEF...");
|
|
486
|
+
* manager.selectAccount("5GrwvaEF…");
|
|
386
487
|
* const signer = manager.getSigner();
|
|
387
488
|
* ```
|
|
388
489
|
*/
|
|
@@ -401,13 +502,26 @@ declare class SignerManager {
|
|
|
401
502
|
private readonly dappName;
|
|
402
503
|
private readonly persistenceOption;
|
|
403
504
|
private resolvedPersistence;
|
|
505
|
+
private readonly onConnectCallback;
|
|
506
|
+
private onConnectController;
|
|
404
507
|
constructor(options?: SignerManagerOptions);
|
|
405
508
|
private getPersistence;
|
|
406
509
|
/** Get a snapshot of the current state. */
|
|
407
510
|
getState(): SignerState;
|
|
408
511
|
/**
|
|
409
|
-
* Subscribe to state changes.
|
|
410
|
-
*
|
|
512
|
+
* Subscribe to state changes.
|
|
513
|
+
*
|
|
514
|
+
* The callback fires synchronously on every state mutation, in
|
|
515
|
+
* registration order, inside the call stack that mutated state. It
|
|
516
|
+
* does **not** fire with the current state at subscription time —
|
|
517
|
+
* call {@link getState} if you need a priming read.
|
|
518
|
+
*
|
|
519
|
+
* For "fired once when the user connects" semantics, prefer the
|
|
520
|
+
* {@link SignerManagerOptions.onConnect} option instead of gating on
|
|
521
|
+
* `state.status` inside this callback — `subscribe` will fire many
|
|
522
|
+
* times while connected (`selectAccount`, account swaps, etc.).
|
|
523
|
+
*
|
|
524
|
+
* @returns an unsubscribe function.
|
|
411
525
|
*/
|
|
412
526
|
subscribe(callback: (state: SignerState) => void): () => void;
|
|
413
527
|
/**
|
|
@@ -421,7 +535,13 @@ declare class SignerManager {
|
|
|
421
535
|
* - `"dev"`: Connect using dev accounts (for testing)
|
|
422
536
|
*/
|
|
423
537
|
connect(providerType?: ProviderType): Promise<Result<SignerAccount[], SignerError>>;
|
|
424
|
-
/**
|
|
538
|
+
/**
|
|
539
|
+
* Disconnect from the current provider and reset state to initial.
|
|
540
|
+
*
|
|
541
|
+
* Reversible — subsequent `connect()` calls work normally. Cancels
|
|
542
|
+
* any in-flight `connect`, reconnect attempt, or `onConnect` callback
|
|
543
|
+
* (`ctx.signal` becomes aborted).
|
|
544
|
+
*/
|
|
425
545
|
disconnect(): void;
|
|
426
546
|
/**
|
|
427
547
|
* Select an account by address.
|
|
@@ -477,7 +597,12 @@ declare class SignerManager {
|
|
|
477
597
|
createRingVRFProof(dotNsIdentifier: string, derivationIndex: number, location: RingLocation, message: Uint8Array): Promise<Result<Uint8Array, SignerError>>;
|
|
478
598
|
/**
|
|
479
599
|
* Destroy the manager and release all resources.
|
|
480
|
-
*
|
|
600
|
+
*
|
|
601
|
+
* **Terminal** — clears all subscribers, cancels in-flight work, and
|
|
602
|
+
* marks the instance unusable. Any subsequent method returns
|
|
603
|
+
* `DestroyedError`. Idempotent. Use in framework teardown (React
|
|
604
|
+
* `useEffect` cleanup, HMR dispose). For a reversible reset, use
|
|
605
|
+
* {@link disconnect} instead.
|
|
481
606
|
*/
|
|
482
607
|
destroy(): void;
|
|
483
608
|
private connectToProvider;
|
|
@@ -488,6 +613,8 @@ declare class SignerManager {
|
|
|
488
613
|
private getHostProvider;
|
|
489
614
|
private cancelConnect;
|
|
490
615
|
private cancelReconnect;
|
|
616
|
+
private cancelOnConnect;
|
|
617
|
+
private fireOnConnect;
|
|
491
618
|
private disconnectInternal;
|
|
492
619
|
private persistAccount;
|
|
493
620
|
private loadPersistedAccount;
|
|
@@ -561,4 +688,4 @@ declare class DevProvider implements SignerProvider {
|
|
|
561
688
|
onAccountsChange(_callback: (accounts: SignerAccount[]) => void): Unsubscribe;
|
|
562
689
|
}
|
|
563
690
|
|
|
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 };
|
|
691
|
+
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
|
|
|
@@ -517,6 +517,13 @@ function initialState() {
|
|
|
517
517
|
error: null
|
|
518
518
|
};
|
|
519
519
|
}
|
|
520
|
+
function resolveSelectedAccount(accounts, preferredAddress) {
|
|
521
|
+
if (preferredAddress) {
|
|
522
|
+
const found = accounts.find((a) => a.address === preferredAddress);
|
|
523
|
+
if (found) return found;
|
|
524
|
+
}
|
|
525
|
+
return accounts[0] ?? null;
|
|
526
|
+
}
|
|
520
527
|
var SignerManager = class {
|
|
521
528
|
state;
|
|
522
529
|
provider = null;
|
|
@@ -532,6 +539,8 @@ var SignerManager = class {
|
|
|
532
539
|
dappName;
|
|
533
540
|
persistenceOption;
|
|
534
541
|
resolvedPersistence;
|
|
542
|
+
onConnectCallback;
|
|
543
|
+
onConnectController = null;
|
|
535
544
|
constructor(options) {
|
|
536
545
|
this.ss58Prefix = options?.ss58Prefix ?? DEFAULT_SS58_PREFIX;
|
|
537
546
|
this.hostTimeout = options?.hostTimeout ?? DEFAULT_HOST_TIMEOUT;
|
|
@@ -540,6 +549,7 @@ var SignerManager = class {
|
|
|
540
549
|
this.dappName = options?.dappName ?? DEFAULT_DAPP_NAME;
|
|
541
550
|
this.persistenceOption = options?.persistence;
|
|
542
551
|
this.resolvedPersistence = options?.persistence;
|
|
552
|
+
this.onConnectCallback = options?.onConnect;
|
|
543
553
|
this.state = initialState();
|
|
544
554
|
}
|
|
545
555
|
async getPersistence() {
|
|
@@ -555,8 +565,19 @@ var SignerManager = class {
|
|
|
555
565
|
return this.state;
|
|
556
566
|
}
|
|
557
567
|
/**
|
|
558
|
-
* Subscribe to state changes.
|
|
559
|
-
*
|
|
568
|
+
* Subscribe to state changes.
|
|
569
|
+
*
|
|
570
|
+
* The callback fires synchronously on every state mutation, in
|
|
571
|
+
* registration order, inside the call stack that mutated state. It
|
|
572
|
+
* does **not** fire with the current state at subscription time —
|
|
573
|
+
* call {@link getState} if you need a priming read.
|
|
574
|
+
*
|
|
575
|
+
* For "fired once when the user connects" semantics, prefer the
|
|
576
|
+
* {@link SignerManagerOptions.onConnect} option instead of gating on
|
|
577
|
+
* `state.status` inside this callback — `subscribe` will fire many
|
|
578
|
+
* times while connected (`selectAccount`, account swaps, etc.).
|
|
579
|
+
*
|
|
580
|
+
* @returns an unsubscribe function.
|
|
560
581
|
*/
|
|
561
582
|
subscribe(callback) {
|
|
562
583
|
this.subscribers.add(callback);
|
|
@@ -580,6 +601,7 @@ var SignerManager = class {
|
|
|
580
601
|
}
|
|
581
602
|
this.cancelConnect();
|
|
582
603
|
this.cancelReconnect();
|
|
604
|
+
this.cancelOnConnect();
|
|
583
605
|
this.connectController = new AbortController();
|
|
584
606
|
const signal = this.connectController.signal;
|
|
585
607
|
this.disconnectInternal();
|
|
@@ -587,10 +609,17 @@ var SignerManager = class {
|
|
|
587
609
|
const targetProvider = providerType ?? "host";
|
|
588
610
|
return this.connectToProvider(targetProvider, signal);
|
|
589
611
|
}
|
|
590
|
-
/**
|
|
612
|
+
/**
|
|
613
|
+
* Disconnect from the current provider and reset state to initial.
|
|
614
|
+
*
|
|
615
|
+
* Reversible — subsequent `connect()` calls work normally. Cancels
|
|
616
|
+
* any in-flight `connect`, reconnect attempt, or `onConnect` callback
|
|
617
|
+
* (`ctx.signal` becomes aborted).
|
|
618
|
+
*/
|
|
591
619
|
disconnect() {
|
|
592
620
|
this.cancelConnect();
|
|
593
621
|
this.cancelReconnect();
|
|
622
|
+
this.cancelOnConnect();
|
|
594
623
|
this.disconnectInternal();
|
|
595
624
|
this.setState(initialState());
|
|
596
625
|
log3.info("disconnected");
|
|
@@ -709,13 +738,19 @@ var SignerManager = class {
|
|
|
709
738
|
}
|
|
710
739
|
/**
|
|
711
740
|
* Destroy the manager and release all resources.
|
|
712
|
-
*
|
|
741
|
+
*
|
|
742
|
+
* **Terminal** — clears all subscribers, cancels in-flight work, and
|
|
743
|
+
* marks the instance unusable. Any subsequent method returns
|
|
744
|
+
* `DestroyedError`. Idempotent. Use in framework teardown (React
|
|
745
|
+
* `useEffect` cleanup, HMR dispose). For a reversible reset, use
|
|
746
|
+
* {@link disconnect} instead.
|
|
713
747
|
*/
|
|
714
748
|
destroy() {
|
|
715
749
|
if (this.isDestroyed) return;
|
|
716
750
|
this.isDestroyed = true;
|
|
717
751
|
this.cancelConnect();
|
|
718
752
|
this.cancelReconnect();
|
|
753
|
+
this.cancelOnConnect();
|
|
719
754
|
this.disconnectInternal();
|
|
720
755
|
this.subscribers.clear();
|
|
721
756
|
this.state = initialState();
|
|
@@ -745,8 +780,7 @@ var SignerManager = class {
|
|
|
745
780
|
this.cleanups.push(accountUnsub);
|
|
746
781
|
const accounts = result.value;
|
|
747
782
|
const persisted = await this.loadPersistedAccount();
|
|
748
|
-
const
|
|
749
|
-
const selectedAccount = restoredAccount ?? (accounts.length > 0 ? accounts[0] : null);
|
|
783
|
+
const selectedAccount = resolveSelectedAccount(accounts, persisted);
|
|
750
784
|
this.setState({
|
|
751
785
|
status: "connected",
|
|
752
786
|
accounts,
|
|
@@ -756,6 +790,7 @@ var SignerManager = class {
|
|
|
756
790
|
});
|
|
757
791
|
if (selectedAccount) {
|
|
758
792
|
this.persistAccount(selectedAccount.address);
|
|
793
|
+
this.fireOnConnect(selectedAccount);
|
|
759
794
|
}
|
|
760
795
|
log3.info("connected", { provider: type, accounts: accounts.length });
|
|
761
796
|
return result;
|
|
@@ -821,13 +856,20 @@ var SignerManager = class {
|
|
|
821
856
|
});
|
|
822
857
|
this.cleanups.push(accountUnsub);
|
|
823
858
|
const accounts = result.value;
|
|
859
|
+
const selectedAccount = resolveSelectedAccount(
|
|
860
|
+
accounts,
|
|
861
|
+
this.state.selectedAccount?.address
|
|
862
|
+
);
|
|
824
863
|
this.setState({
|
|
825
864
|
status: "connected",
|
|
826
865
|
accounts,
|
|
827
866
|
activeProvider: providerType,
|
|
828
|
-
selectedAccount
|
|
867
|
+
selectedAccount,
|
|
829
868
|
error: null
|
|
830
869
|
});
|
|
870
|
+
if (selectedAccount) {
|
|
871
|
+
this.fireOnConnect(selectedAccount);
|
|
872
|
+
}
|
|
831
873
|
log3.info("reconnected", { provider: providerType });
|
|
832
874
|
return result;
|
|
833
875
|
},
|
|
@@ -861,16 +903,38 @@ var SignerManager = class {
|
|
|
861
903
|
return null;
|
|
862
904
|
}
|
|
863
905
|
cancelConnect() {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
this.connectController = null;
|
|
867
|
-
}
|
|
906
|
+
this.connectController?.abort();
|
|
907
|
+
this.connectController = null;
|
|
868
908
|
}
|
|
869
909
|
cancelReconnect() {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
910
|
+
this.reconnectController?.abort();
|
|
911
|
+
this.reconnectController = null;
|
|
912
|
+
}
|
|
913
|
+
cancelOnConnect() {
|
|
914
|
+
this.onConnectController?.abort();
|
|
915
|
+
this.onConnectController = null;
|
|
916
|
+
}
|
|
917
|
+
fireOnConnect(account) {
|
|
918
|
+
const callback = this.onConnectCallback;
|
|
919
|
+
if (!callback) return;
|
|
920
|
+
this.cancelOnConnect();
|
|
921
|
+
const controller = new AbortController();
|
|
922
|
+
this.onConnectController = controller;
|
|
923
|
+
const ctx = {
|
|
924
|
+
signal: controller.signal,
|
|
925
|
+
requestResourceAllocation
|
|
926
|
+
};
|
|
927
|
+
queueMicrotask(async () => {
|
|
928
|
+
try {
|
|
929
|
+
await callback(account, ctx);
|
|
930
|
+
} catch (cause) {
|
|
931
|
+
log3.warn("onConnect callback threw", { cause });
|
|
932
|
+
} finally {
|
|
933
|
+
if (this.onConnectController === controller) {
|
|
934
|
+
this.onConnectController = null;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
});
|
|
874
938
|
}
|
|
875
939
|
disconnectInternal() {
|
|
876
940
|
for (const cleanup of this.cleanups) {
|