@formo/analytics 1.38.0 → 1.38.2

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 (34) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +15 -4
  2. package/dist/cjs/src/FormoAnalytics.js +37 -6
  3. package/dist/cjs/src/FormoAnalyticsProvider.d.ts +8 -0
  4. package/dist/cjs/src/FormoAnalyticsProvider.js +41 -32
  5. package/dist/cjs/src/evm/EvmEventTracker.d.ts +100 -9
  6. package/dist/cjs/src/evm/EvmEventTracker.js +357 -19
  7. package/dist/cjs/src/evm/EvmProviderRegistry.d.ts +34 -2
  8. package/dist/cjs/src/evm/EvmProviderRegistry.js +61 -4
  9. package/dist/cjs/src/evm/EvmRequestTracker.d.ts +21 -4
  10. package/dist/cjs/src/evm/EvmRequestTracker.js +54 -28
  11. package/dist/cjs/src/version.d.ts +1 -1
  12. package/dist/cjs/src/version.js +1 -1
  13. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +67 -1
  14. package/dist/cjs/src/wagmi/WagmiEventHandler.js +286 -39
  15. package/dist/cjs/src/wagmi/types.d.ts +6 -0
  16. package/dist/cjs/src/wagmi/utils.js +9 -1
  17. package/dist/esm/src/FormoAnalytics.d.ts +15 -4
  18. package/dist/esm/src/FormoAnalytics.js +37 -6
  19. package/dist/esm/src/FormoAnalyticsProvider.d.ts +8 -0
  20. package/dist/esm/src/FormoAnalyticsProvider.js +39 -31
  21. package/dist/esm/src/evm/EvmEventTracker.d.ts +100 -9
  22. package/dist/esm/src/evm/EvmEventTracker.js +357 -19
  23. package/dist/esm/src/evm/EvmProviderRegistry.d.ts +34 -2
  24. package/dist/esm/src/evm/EvmProviderRegistry.js +61 -4
  25. package/dist/esm/src/evm/EvmRequestTracker.d.ts +21 -4
  26. package/dist/esm/src/evm/EvmRequestTracker.js +54 -28
  27. package/dist/esm/src/version.d.ts +1 -1
  28. package/dist/esm/src/version.js +1 -1
  29. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +67 -1
  30. package/dist/esm/src/wagmi/WagmiEventHandler.js +286 -39
  31. package/dist/esm/src/wagmi/types.d.ts +6 -0
  32. package/dist/esm/src/wagmi/utils.js +9 -1
  33. package/dist/index.umd.min.js +1 -1
  34. package/package.json +2 -2
@@ -139,10 +139,54 @@ var EvmEventTracker = /** @class */ (function () {
139
139
  * Announcement-driven cleanup must not touch them: they are never in an
140
140
  * announcement list, so "missing from the announcement" is their normal
141
141
  * state, not evidence of removal. A Set rather than a WeakSet because
142
- * suppressed adoptions retry from it; the registry holds these providers
143
- * strongly anyway, and untrack removes them.
142
+ * the corrected-detect pass iterates it; the registry's detail list
143
+ * holds these providers strongly for the instance's life anyway.
144
144
  */
145
145
  this.externallyRegistered = new Set();
146
+ /**
147
+ * Registered providers whose session this SDK has NOT yet learned.
148
+ *
149
+ * Only these are retried on page hits and opt-in. Re-running adoption for
150
+ * every registered provider looked idempotent but was not: adoption is
151
+ * the same path a live `accountsChanged` takes, and that path treats a
152
+ * provider with a different address from the active one as a wallet
153
+ * switch. With two registered providers, or one registered next to a
154
+ * discovered wallet, every page hit emitted a disconnect and a connect
155
+ * that no user action caused.
156
+ *
157
+ * A provider enters at registration (it may have no session yet, or be
158
+ * refused because tracking is suppressed), again whenever a handler
159
+ * refuses its signal while suppressed (noted on entry AND at the
160
+ * suppressed commit, since the handlers gate on the active provider and
161
+ * re-check suppression after an await), and again for all of them when
162
+ * an opt-out purges identity. It leaves the first time a handler commits
163
+ * its session unsuppressed, or when it is untracked. Retrying reads the
164
+ * provider's SYNCHRONOUS accounts only - no RPC - so a registered
165
+ * provider whose session never fires `accountsChanged` (a wallet that
166
+ * signals `connect` alone while `autocapture.connect` is off, which
167
+ * installs a chain-only observer) is still adopted on the next hit. "No
168
+ * RPC" holds for the pending provider itself; the handler's switch
169
+ * arbitration may still ask the ACTIVE provider for its accounts, as it
170
+ * does for any live signal.
171
+ *
172
+ * Replay goes through the accounts handler, which has live wallet-switch
173
+ * semantics: a different provider with a different address is a switch.
174
+ * So a pending provider is replayed only while no OTHER wallet is active
175
+ * and known - it waits, at no cost, until that wallet is gone - unless
176
+ * its own signal was refused (`latestRefused`): then the replay does
177
+ * exactly what the live signal would have done, switch included.
178
+ */
179
+ this.pendingAdoptions = new Set();
180
+ /**
181
+ * Registered providers whose session ended and have not signalled a
182
+ * new one. Some providers keep stale synchronous `accounts` after a
183
+ * disconnect; replaying those would re-install a session that is over.
184
+ * Any later session signal (connect, accountsChanged) lifts this.
185
+ */
186
+ this.awaitingNewSession = new Set();
187
+ /** One retry scan at a time; a call during a scan queues one more. */
188
+ this.retryInFlight = false;
189
+ this.retryRequested = false;
146
190
  /**
147
191
  * The connect this SDK has already reported for a provider.
148
192
  *
@@ -151,10 +195,89 @@ var EvmEventTracker = /** @class */ (function () {
151
195
  * which is when a record must lapse.
152
196
  */
153
197
  this._announcedConnect = new WeakMap();
198
+ /** Set by `cleanup()`; every awaited continuation checks it. */
199
+ this.disposed = false;
200
+ /** Bumped when a registered provider's session ends. See reopenAdoption. */
201
+ this.sessionGenerations = new WeakMap();
154
202
  }
203
+ EvmEventTracker.prototype.dropRefusal = function (provider) {
204
+ if (this.latestRefused === provider)
205
+ this.latestRefused = undefined;
206
+ };
207
+ /**
208
+ * An opt-out purges wallet identity. Every registered session is then
209
+ * unknown again (pending; no refusal is ADDED, so the opt-in replay of a
210
+ * merely connected wallet does not switch, while a refusal already
211
+ * standing from an excluded route keeps its meaning), and the
212
+ * opt-in retry must re-learn all of them: with the active wallet's
213
+ * address gone, the accounts handler cannot even tell a second wallet's
214
+ * accounts apart from the active one's, and would ignore them.
215
+ */
216
+ EvmEventTracker.prototype.markRegisteredAdoptionsPending = function () {
217
+ var _this = this;
218
+ this.externallyRegistered.forEach(function (provider) {
219
+ return _this.pendingAdoptions.add(provider);
220
+ });
221
+ };
222
+ /**
223
+ * Remember a registered provider's signal that suppression refuses.
224
+ *
225
+ * Replay privilege (`latestRefused`) goes only to a provider whose
226
+ * session the replay can actually read - synchronous accounts - so an
227
+ * accountless `connect` (a chain-only observation, or a session still
228
+ * pairing) cannot displace an earlier refusal that is adoptable.
229
+ */
230
+ EvmEventTracker.prototype.noteRefusalIfSuppressed = function (provider) {
231
+ if (this.deps.isTrackingSuppressed() &&
232
+ this.externallyRegistered.has(provider)) {
233
+ this.pendingAdoptions.add(provider);
234
+ var adoptable = false;
235
+ try {
236
+ adoptable = readProviderAccounts(provider).length > 0;
237
+ }
238
+ catch (_a) {
239
+ /* unreadable state: pending, not privileged */
240
+ }
241
+ if (adoptable)
242
+ this.latestRefused = provider;
243
+ }
244
+ };
245
+ /** A handler has learned this provider's session; nothing is pending. */
246
+ EvmEventTracker.prototype.settleAdoption = function (provider) {
247
+ this.pendingAdoptions.delete(provider);
248
+ this.awaitingNewSession.delete(provider);
249
+ this.dropRefusal(provider);
250
+ };
251
+ /**
252
+ * A registered provider's session has ended. Its NEXT session is not
253
+ * yet learned, and may announce itself in a way no handler adopts (a
254
+ * `connect` alone while `autocapture.connect` is off installs a
255
+ * chain-only observer), so it is pending again for the page-hit retry.
256
+ */
257
+ EvmEventTracker.prototype.reopenAdoption = function (provider) {
258
+ var _a;
259
+ if (this.externallyRegistered.has(provider)) {
260
+ this.pendingAdoptions.add(provider);
261
+ this.awaitingNewSession.add(provider);
262
+ // A lookup still in flight for the session that just ended must not
263
+ // record a refusal for it when it resolves.
264
+ this.sessionGenerations.set(provider, ((_a = this.sessionGenerations.get(provider)) !== null && _a !== void 0 ? _a : 0) + 1);
265
+ // The refusal marker described the session that just ended. Left
266
+ // standing, a later connect-only session would inherit it and be
267
+ // replayed as a switch away from another active wallet.
268
+ this.dropRefusal(provider);
269
+ }
270
+ };
155
271
  /** Stop listening for wallet announcements. Called from SDK teardown. */
156
272
  EvmEventTracker.prototype.cleanup = function () {
157
273
  var _a;
274
+ // A replay in flight (awaiting the active wallet's accounts) must not
275
+ // resume into a torn-down instance and commit a session there.
276
+ this.disposed = true;
277
+ this.pendingAdoptions.clear();
278
+ this.awaitingNewSession.clear();
279
+ this.latestRefused = undefined;
280
+ this.retryRequested = false;
158
281
  try {
159
282
  (_a = this.unsubscribeDiscovery) === null || _a === void 0 ? void 0 : _a.call(this);
160
283
  }
@@ -163,6 +286,10 @@ var EvmEventTracker = /** @class */ (function () {
163
286
  }
164
287
  this.unsubscribeDiscovery = undefined;
165
288
  };
289
+ EvmEventTracker.prototype.sessionGeneration = function (provider) {
290
+ var _a;
291
+ return (_a = this.sessionGenerations.get(provider)) !== null && _a !== void 0 ? _a : 0;
292
+ };
166
293
  /** Drop a provider's reported connect. Called when it stops being active. */
167
294
  EvmEventTracker.prototype.forgetAnnouncedConnect = function (provider) {
168
295
  this._announcedConnect.delete(provider);
@@ -324,6 +451,9 @@ var EvmEventTracker = /** @class */ (function () {
324
451
  void this.detectWallets([
325
452
  __assign(__assign({}, detail), { info: __assign(__assign({}, detail.info), this.registry.infoFor(provider)) }),
326
453
  ]);
454
+ // Pending until a handler commits its session unsuppressed: the
455
+ // session may not exist yet, or adoption may be refused right below.
456
+ this.pendingAdoptions.add(provider);
327
457
  var accounts = readProviderAccounts(provider);
328
458
  if (accounts.length > 0) {
329
459
  void this.onAccountsChanged(provider, accounts);
@@ -331,14 +461,19 @@ var EvmEventTracker = /** @class */ (function () {
331
461
  return true;
332
462
  };
333
463
  /**
334
- * Re-run session adoption for every registered external provider.
464
+ * Finish what registration could not.
465
+ *
466
+ * A registered provider's session may not have existed at registration,
467
+ * or its adoption was refused because tracking was suppressed (opt-out,
468
+ * excluded route) - and an existing session may never emit another
469
+ * accountsChanged, so nothing else would ever retry. Called when
470
+ * suppression can have ended (opt-in, page navigation).
335
471
  *
336
- * Registration while tracking was suppressed (opt-out, excluded route)
337
- * reached the adoption path and was refused - and a provider whose
338
- * session already exists may never emit another accountsChanged, so
339
- * nothing would ever retry. Called when suppression can have ended
340
- * (opt-in, page navigation). Idempotent: an already-adopted wallet is
341
- * deduplicated by the same state and markers as any repeated signal.
472
+ * Adoption is retried ONLY for providers not yet adopted
473
+ * (`pendingAdoptions`), only from their synchronous accounts, and only
474
+ * once suppression has actually ended. The corrected-detect pass below
475
+ * runs for every registered provider: it is deduplicated per session by
476
+ * rdns, so repeating it is free.
342
477
  */
343
478
  EvmEventTracker.prototype.retryExternalAdoptions = function () {
344
479
  var _this = this;
@@ -361,11 +496,97 @@ var EvmEventTracker = /** @class */ (function () {
361
496
  },
362
497
  ]);
363
498
  }
364
- var accounts = readProviderAccounts(provider);
365
- if (accounts.length > 0) {
366
- void _this.onAccountsChanged(provider, accounts);
367
- }
368
499
  });
500
+ if (this.disposed ||
501
+ this.pendingAdoptions.size === 0 ||
502
+ this.deps.isTrackingSuppressed()) {
503
+ // Nothing to finish, or still suppressed: adopting now would be
504
+ // refused again, and the entry must survive for the next chance.
505
+ return;
506
+ }
507
+ // ONE scan at a time, providers awaited in turn, and the entry is NOT
508
+ // removed here: only a handler that commits the session removes it.
509
+ // Fired concurrently - two overlapping scans from repeated page hits,
510
+ // or two providers within one - the later call made the earlier
511
+ // one's observation stale; a stale return dropped a provider whose
512
+ // session was never learned, and a second scan replayed a provider
513
+ // the first was still switching to, emitting the old wallet's
514
+ // disconnect twice. A call that lands during a scan queues exactly
515
+ // one more scan, so nothing that changed meanwhile is missed.
516
+ if (this.retryInFlight) {
517
+ this.retryRequested = true;
518
+ return;
519
+ }
520
+ this.retryInFlight = true;
521
+ void (function () { return __awaiter(_this, void 0, void 0, function () {
522
+ var first, ordered, _i, ordered_1, provider, accounts, otherWalletActive, _a;
523
+ var _this = this;
524
+ return __generator(this, function (_b) {
525
+ switch (_b.label) {
526
+ case 0:
527
+ _b.trys.push([0, , 9, 10]);
528
+ _b.label = 1;
529
+ case 1:
530
+ this.retryRequested = false;
531
+ first = [this.wallet.provider, this.latestRefused].filter(function (p) { return !!p && _this.pendingAdoptions.has(p); });
532
+ ordered = Array.from(new Set(__spreadArray(__spreadArray([], first, true), Array.from(this.pendingAdoptions), true)));
533
+ _i = 0, ordered_1 = ordered;
534
+ _b.label = 2;
535
+ case 2:
536
+ if (!(_i < ordered_1.length)) return [3 /*break*/, 7];
537
+ provider = ordered_1[_i];
538
+ if (this.disposed || this.deps.isTrackingSuppressed())
539
+ return [3 /*break*/, 7];
540
+ accounts = void 0;
541
+ try {
542
+ accounts = readProviderAccounts(provider);
543
+ }
544
+ catch (_c) {
545
+ // A wallet whose state getters throw (transport disposed)
546
+ // stays pending; it must not abort the scan for the others,
547
+ // nor surface as an unhandled rejection from this task.
548
+ return [3 /*break*/, 6];
549
+ }
550
+ if (accounts.length === 0 || this.awaitingNewSession.has(provider)) {
551
+ // No session yet, or none since the last one ended: stays
552
+ // pending, at no cost.
553
+ return [3 /*break*/, 6];
554
+ }
555
+ otherWalletActive = this.wallet.provider !== undefined &&
556
+ this.wallet.provider !== provider;
557
+ if (otherWalletActive && this.latestRefused !== provider) {
558
+ // Never signalled, merely connected: replaying it now would
559
+ // be reported as a wallet switch nobody made. Waits for
560
+ // that wallet to go.
561
+ return [3 /*break*/, 6];
562
+ }
563
+ _b.label = 3;
564
+ case 3:
565
+ _b.trys.push([3, 5, , 6]);
566
+ return [4 /*yield*/, this.onAccountsChanged(provider, accounts)];
567
+ case 4:
568
+ _b.sent();
569
+ return [3 /*break*/, 6];
570
+ case 5:
571
+ _a = _b.sent();
572
+ return [3 /*break*/, 6];
573
+ case 6:
574
+ _i++;
575
+ return [3 /*break*/, 2];
576
+ case 7:
577
+ if (this.retryRequested &&
578
+ !this.disposed &&
579
+ !this.deps.isTrackingSuppressed()) return [3 /*break*/, 1];
580
+ _b.label = 8;
581
+ case 8: return [3 /*break*/, 10];
582
+ case 9:
583
+ this.retryInFlight = false;
584
+ this.retryRequested = false;
585
+ return [7 /*endfinally*/];
586
+ case 10: return [2 /*return*/];
587
+ }
588
+ });
589
+ }); })();
369
590
  };
370
591
  EvmEventTracker.prototype.registerAccountsChangedListener = function (provider) {
371
592
  var _this = this;
@@ -386,6 +607,11 @@ var EvmEventTracker = /** @class */ (function () {
386
607
  return __generator(this, function (_a) {
387
608
  switch (_a.label) {
388
609
  case 0:
610
+ // A listener that could not be removed at cleanup (its provider's
611
+ // removeListener threw) still fires; it must not touch a torn-down
612
+ // instance.
613
+ if (this.disposed)
614
+ return [2 /*return*/];
389
615
  logger_1.logger.info("onAccountsChanged", accounts);
390
616
  claims = accounts.length > 0 || this.wallet.provider === provider;
391
617
  observation = claims
@@ -421,11 +647,12 @@ var EvmEventTracker = /** @class */ (function () {
421
647
  */
422
648
  EvmEventTracker.prototype._handleAccountsChanged = function (provider, accounts, observation) {
423
649
  return __awaiter(this, void 0, void 0, function () {
424
- var error_1, address, currentStoredAddress, newProviderAddress, activeProviderAccounts, error_2, nextChainId, wasDisconnected, providerInfo, effectiveChainId;
650
+ var error_1, address, session, currentStoredAddress, newProviderAddress, activeProviderAccounts, error_2, nextChainId, wasDisconnected, providerInfo, effectiveChainId;
425
651
  return __generator(this, function (_a) {
426
652
  switch (_a.label) {
427
653
  case 0:
428
654
  if (!(accounts.length === 0)) return [3 /*break*/, 9];
655
+ this.reopenAdoption(provider);
429
656
  if (!(this.wallet.provider === provider)) return [3 /*break*/, 7];
430
657
  logger_1.logger.info("OnAccountsChanged: Detecting disconnect, current state:", {
431
658
  evmAddress: this.wallet.evmAddress,
@@ -448,6 +675,9 @@ var EvmEventTracker = /** @class */ (function () {
448
675
  case 2:
449
676
  // Pass EVM state explicitly to ensure we have the data for the disconnect event
450
677
  _a.sent();
678
+ // Torn down during the emission: nothing below may run.
679
+ if (this.disposed)
680
+ return [2 /*return*/];
451
681
  return [3 /*break*/, 4];
452
682
  case 3:
453
683
  error_1 = _a.sent();
@@ -472,6 +702,12 @@ var EvmEventTracker = /** @class */ (function () {
472
702
  logger_1.logger.warn("onAccountsChanged: Invalid address received", accounts[0]);
473
703
  return [2 /*return*/];
474
704
  }
705
+ // Before the active-provider gates below: a registered provider that
706
+ // is not the active one can return early without ever reaching the
707
+ // suppressed commit, and its session may never signal again.
708
+ this.awaitingNewSession.delete(provider);
709
+ this.noteRefusalIfSuppressed(provider);
710
+ session = this.sessionGeneration(provider);
475
711
  if (!(this.wallet.provider && this.wallet.provider !== provider)) return [3 /*break*/, 26];
476
712
  currentStoredAddress = this.wallet.evmAddress;
477
713
  newProviderAddress = (0, address_1.validateAndChecksumAddress)(address);
@@ -487,12 +723,24 @@ var EvmEventTracker = /** @class */ (function () {
487
723
  return [4 /*yield*/, this.registry.accountsOf(this.wallet.provider)];
488
724
  case 11:
489
725
  activeProviderAccounts = _a.sent();
726
+ if (this.disposed)
727
+ return [2 /*return*/];
728
+ if (this.sessionGeneration(provider) !== session) {
729
+ // This provider's session ended during the probe: the signal
730
+ // describes nothing that still exists, refusal included.
731
+ this.dropRefusal(provider);
732
+ return [2 /*return*/];
733
+ }
490
734
  // The probe is asynchronous too, so check before issuing anything.
491
735
  // Every branch below reads the CURRENT evm state, so a switch that
492
736
  // went stale during the probe would emit a false disconnect for
493
737
  // whoever claimed the namespace, and clear them.
494
- if (!this.wallet.isCurrent(observation))
738
+ if (!this.wallet.isCurrent(observation)) {
739
+ // Superseded: this signal's refusal, if noted, no longer describes
740
+ // a session the replay may switch to.
741
+ this.dropRefusal(provider);
495
742
  return [2 /*return*/];
743
+ }
496
744
  logger_1.logger.info("OnAccountsChanged: Checking current provider accounts", {
497
745
  activeProvider: this.registry.infoFor(this.wallet.provider).name,
498
746
  accountsLength: activeProviderAccounts
@@ -518,6 +766,9 @@ var EvmEventTracker = /** @class */ (function () {
518
766
  })];
519
767
  case 12:
520
768
  _a.sent();
769
+ // Torn down during the emission: nothing below may run.
770
+ if (this.disposed)
771
+ return [2 /*return*/];
521
772
  return [3 /*break*/, 14];
522
773
  case 13:
523
774
  logger_1.logger.debug("OnAccountsChanged: Disconnect event skipped during provider switch (autocapture.disconnect: false)");
@@ -525,12 +776,31 @@ var EvmEventTracker = /** @class */ (function () {
525
776
  this.wallet.clear('evm');
526
777
  _a.label = 14;
527
778
  case 14:
528
- if (!this.wallet.isCurrent(observation))
779
+ if (!this.wallet.isCurrent(observation)) {
780
+ // Superseded: this signal's refusal, if noted, no longer describes
781
+ // a session the replay may switch to.
782
+ this.dropRefusal(provider);
529
783
  return [2 /*return*/];
784
+ }
530
785
  // Clear state and let the new provider become active
531
786
  this.wallet.clearProvider();
532
787
  return [3 /*break*/, 16];
533
788
  case 15:
789
+ // Ignored: the active wallet has accounts and this signal's
790
+ // address is the same, or the active address is unknown (a
791
+ // discovered wallet whose identity an opt-out purged; a
792
+ // registered one is re-learned before this point). THIS
793
+ // provider's session is not adopted; it stays pending without
794
+ // its refusal marker, so it is not probed again on every page
795
+ // hit while the active wallet stands, and is adopted once that
796
+ // wallet is gone. A live signal is ignored here in exactly the
797
+ // same way, so the replay changes nothing about who is active.
798
+ // While SUPPRESSED this is the refusal itself, not a verdict:
799
+ // the marker stands for the replay that runs once suppression
800
+ // ends.
801
+ if (!this.deps.isTrackingSuppressed()) {
802
+ this.dropRefusal(provider);
803
+ }
534
804
  logger_1.logger.info("OnAccountsChanged: Current provider still has accounts and same address, ignoring new provider", {
535
805
  activeProvider: this.registry.infoFor(this.wallet.provider).name,
536
806
  eventProvider: this.registry.infoFor(provider).name,
@@ -553,6 +823,9 @@ var EvmEventTracker = /** @class */ (function () {
553
823
  })];
554
824
  case 18:
555
825
  _a.sent();
826
+ // Torn down during the emission: nothing below may run.
827
+ if (this.disposed)
828
+ return [2 /*return*/];
556
829
  return [3 /*break*/, 20];
557
830
  case 19:
558
831
  logger_1.logger.debug("OnAccountsChanged: Disconnect event skipped for old provider (autocapture.disconnect: false)");
@@ -560,8 +833,12 @@ var EvmEventTracker = /** @class */ (function () {
560
833
  this.wallet.clear('evm');
561
834
  _a.label = 20;
562
835
  case 20:
563
- if (!this.wallet.isCurrent(observation))
836
+ if (!this.wallet.isCurrent(observation)) {
837
+ // Superseded: this signal's refusal, if noted, no longer describes
838
+ // a session the replay may switch to.
839
+ this.dropRefusal(provider);
564
840
  return [2 /*return*/];
841
+ }
565
842
  _a.label = 21;
566
843
  case 21: return [3 /*break*/, 26];
567
844
  case 22:
@@ -582,6 +859,9 @@ var EvmEventTracker = /** @class */ (function () {
582
859
  })];
583
860
  case 23:
584
861
  _a.sent();
862
+ // Torn down during the emission: nothing below may run.
863
+ if (this.disposed)
864
+ return [2 /*return*/];
585
865
  return [3 /*break*/, 25];
586
866
  case 24:
587
867
  logger_1.logger.debug("OnAccountsChanged: Disconnect event skipped for failed provider check (autocapture.disconnect: false)");
@@ -589,8 +869,12 @@ var EvmEventTracker = /** @class */ (function () {
589
869
  this.wallet.clear('evm');
590
870
  _a.label = 25;
591
871
  case 25:
592
- if (!this.wallet.isCurrent(observation))
872
+ if (!this.wallet.isCurrent(observation)) {
873
+ // Superseded: this signal's refusal, if noted, no longer describes
874
+ // a session the replay may switch to.
875
+ this.dropRefusal(provider);
593
876
  return [2 /*return*/];
877
+ }
594
878
  return [3 /*break*/, 26];
595
879
  case 26:
596
880
  // Set provider if none exists (first connection)
@@ -599,6 +883,8 @@ var EvmEventTracker = /** @class */ (function () {
599
883
  }
600
884
  // If both the provider and address are the same, no-op
601
885
  if (this.wallet.provider === provider && address === this.wallet.evmAddress) {
886
+ // This session is the one already known; nothing is pending for it.
887
+ this.settleAdoption(provider);
602
888
  return [2 /*return*/];
603
889
  }
604
890
  nextChainId = this.registry.resolveChainId(provider);
@@ -609,9 +895,14 @@ var EvmEventTracker = /** @class */ (function () {
609
895
  // events.)
610
896
  if (this.deps.isTrackingSuppressed()) {
611
897
  this.wallet.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
898
+ // Again after the await: suppression may have begun mid-flight.
899
+ this.noteRefusalIfSuppressed(provider);
612
900
  }
613
901
  else {
614
902
  this.wallet.set('evm', { address: address, chainId: nextChainId });
903
+ // Adopted unsuppressed, whichever path got here first: a retry for
904
+ // this provider would now be a repeat, not a completion.
905
+ this.settleAdoption(provider);
615
906
  }
616
907
  providerInfo = this.registry.infoFor(provider);
617
908
  effectiveChainId = nextChainId || 0;
@@ -670,6 +961,11 @@ var EvmEventTracker = /** @class */ (function () {
670
961
  return __generator(this, function (_a) {
671
962
  switch (_a.label) {
672
963
  case 0:
964
+ // A listener that could not be removed at cleanup (its provider's
965
+ // removeListener threw) still fires; it must not touch a torn-down
966
+ // instance.
967
+ if (this.disposed)
968
+ return [2 /*return*/];
673
969
  logger_1.logger.info("onChainChanged", chainIdHex);
674
970
  nextChainId = (0, chain_1.parseChainId)(chainIdHex);
675
971
  // Record it for THIS provider regardless of which one is active. This is
@@ -770,9 +1066,16 @@ var EvmEventTracker = /** @class */ (function () {
770
1066
  args[_i] = arguments[_i];
771
1067
  }
772
1068
  var connection = args[0];
1069
+ if (_this.disposed)
1070
+ return;
773
1071
  if (typeof (connection === null || connection === void 0 ? void 0 : connection.chainId) !== "string")
774
1072
  return;
775
1073
  _this.registry.rememberChain(provider, (0, chain_1.parseChainId)(connection.chainId));
1074
+ _this.awaitingNewSession.delete(provider);
1075
+ // This observer adopts nothing, but a registered provider's connect
1076
+ // refused by suppression is still a refused signal: once suppression
1077
+ // ends, its replay may switch, as the full handler's would.
1078
+ _this.noteRefusalIfSuppressed(provider);
776
1079
  };
777
1080
  provider.on("connect", listener);
778
1081
  this.registry.addListener(provider, "connect", listener);
@@ -831,6 +1134,9 @@ var EvmEventTracker = /** @class */ (function () {
831
1134
  return __generator(this, function (_a) {
832
1135
  switch (_a.label) {
833
1136
  case 0:
1137
+ if (this.disposed)
1138
+ return [2 /*return*/];
1139
+ this.reopenAdoption(provider);
834
1140
  if (this.wallet.provider !== provider)
835
1141
  return [2 /*return*/];
836
1142
  // As in the accountsChanged disconnect path: the reported connect ends
@@ -852,6 +1158,9 @@ var EvmEventTracker = /** @class */ (function () {
852
1158
  case 2:
853
1159
  // Pass current state explicitly to ensure we have the data for the disconnect event
854
1160
  _a.sent();
1161
+ // Torn down during the emission: nothing below may run.
1162
+ if (this.disposed)
1163
+ return [2 /*return*/];
855
1164
  return [3 /*break*/, 4];
856
1165
  case 3:
857
1166
  e_1 = _a.sent();
@@ -873,11 +1182,20 @@ var EvmEventTracker = /** @class */ (function () {
873
1182
  };
874
1183
  EvmEventTracker.prototype.onConnected = function (provider, connection) {
875
1184
  return __awaiter(this, void 0, void 0, function () {
876
- var disconnectsBefore, stateTicket, chainId, address, committed, movedElsewhere, wasDisconnected, isActiveProvider, providerInfo, effectiveChainId, providerInfo, e_2;
1185
+ var disconnectsBefore, stateTicket, chainId, session, address, committed, movedElsewhere, wasDisconnected, isActiveProvider, providerInfo, effectiveChainId, providerInfo, e_2;
877
1186
  return __generator(this, function (_a) {
878
1187
  switch (_a.label) {
879
1188
  case 0:
1189
+ // A listener that could not be removed at cleanup (its provider's
1190
+ // removeListener threw) still fires; it must not touch a torn-down
1191
+ // instance.
1192
+ if (this.disposed)
1193
+ return [2 /*return*/];
880
1194
  logger_1.logger.info("onConnected", connection);
1195
+ // A session signal, and BEFORE the account lookup below: suppression
1196
+ // that ends while the lookup is in flight must not lose the refusal.
1197
+ this.awaitingNewSession.delete(provider);
1198
+ this.noteRefusalIfSuppressed(provider);
881
1199
  disconnectsBefore = this.wallet.disconnectsSoFar("evm");
882
1200
  stateTicket = this.wallet.currentObservation("evm");
883
1201
  _a.label = 1;
@@ -888,9 +1206,18 @@ var EvmEventTracker = /** @class */ (function () {
888
1206
  chainId = (0, chain_1.parseChainId)(connection.chainId);
889
1207
  // Record it for this provider before anything can bail out below.
890
1208
  this.registry.rememberChain(provider, chainId);
1209
+ session = this.sessionGeneration(provider);
891
1210
  return [4 /*yield*/, this.registry.addressOf(provider)];
892
1211
  case 2:
893
1212
  address = _a.sent();
1213
+ if (this.disposed)
1214
+ return [2 /*return*/];
1215
+ if (this.sessionGeneration(provider) !== session) {
1216
+ // Disconnected while the lookup was in flight: a former account
1217
+ // arriving now must not record a refusal for an ended session.
1218
+ this.dropRefusal(provider);
1219
+ return [2 /*return*/];
1220
+ }
894
1221
  // A newer signal arrived while we were resolving the address. Claiming
895
1222
  // the namespace now would write this stale view over it, and would make
896
1223
  // a disconnect still in flight look stale so it skipped its cleanup. A
@@ -898,6 +1225,7 @@ var EvmEventTracker = /** @class */ (function () {
898
1225
  // is reported normally.
899
1226
  if (this.wallet.disconnectsSoFar("evm") !== disconnectsBefore) {
900
1227
  logger_1.logger.info("onConnected: The wallet disconnected after this observation began; dropping it");
1228
+ this.dropRefusal(provider);
901
1229
  return [2 /*return*/];
902
1230
  }
903
1231
  committed = this.wallet.evmAddress;
@@ -908,10 +1236,15 @@ var EvmEventTracker = /** @class */ (function () {
908
1236
  !this.wallet.isCurrent(stateTicket) &&
909
1237
  movedElsewhere) {
910
1238
  logger_1.logger.info("onConnected: A newer signal from this provider has overtaken this connect observation; dropping it");
1239
+ this.dropRefusal(provider);
911
1240
  return [2 /*return*/];
912
1241
  }
913
1242
  if (chainId && address) {
914
1243
  wasDisconnected = !this.wallet.evmAddress;
1244
+ // Same refusal as the accounts path, and before the active-provider
1245
+ // gate: a registered provider that connects while another one is
1246
+ // active is not adopted here, and may never signal again.
1247
+ this.noteRefusalIfSuppressed(provider);
915
1248
  // Set provider if none exists
916
1249
  if (!this.wallet.provider) {
917
1250
  this.wallet.provider = provider;
@@ -923,12 +1256,15 @@ var EvmEventTracker = /** @class */ (function () {
923
1256
  if (isActiveProvider) {
924
1257
  if (this.deps.isTrackingSuppressed()) {
925
1258
  this.wallet.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
1259
+ // Again after the await: suppression may have begun mid-flight.
1260
+ this.noteRefusalIfSuppressed(provider);
926
1261
  }
927
1262
  else {
928
1263
  this.wallet.set('evm', {
929
1264
  chainId: chainId,
930
1265
  address: (0, address_1.validateAndChecksumAddress)(address) || undefined,
931
1266
  });
1267
+ this.settleAdoption(provider);
932
1268
  }
933
1269
  }
934
1270
  // Conditionally emit connect event based on tracking configuration.
@@ -1153,6 +1489,8 @@ var EvmEventTracker = /** @class */ (function () {
1153
1489
  this.registry.rememberChain(provider, chainId);
1154
1490
  };
1155
1491
  EvmEventTracker.prototype.untrackProvider = function (provider) {
1492
+ // An untracked provider has nothing left to finish.
1493
+ this.settleAdoption(provider);
1156
1494
  try {
1157
1495
  this.registry.removeListeners(provider);
1158
1496
  // Only stop tracking it if the listeners actually came off. "Tracked"